1. undefined 和 null 有什么區(qū)別燎潮?
相似之處:
它們屬于 JavaScript 的 7 種基本類型澜公。
使用Boolean(value)或!!value將其轉(zhuǎn)換為布爾值時(shí),值為false
console.log(!!undefined); // false
console.log(!!null); // false
console.log(Boolean(null)); // false
console.log(Boolean(undefined)); // false
區(qū)別:
undefined是指未指定值的變量的默認(rèn)值聪富,或者函數(shù)沒有指定返回值,會默認(rèn)返回undefined
let _undefined;
let fun = function () {}
let obj = {
name:'Aiva',
age:1
}
console.log(_undefined); // undefined
console.log(fun()); // undefined
console.log(obj.sex); // undefined
null是“不代表任何值的值”草娜。 null是已明確定義給變量的值塘淑。
在比較null和undefined時(shí)盐须,我們使用==時(shí)得到true,使用===時(shí)得到false:
console.log(undefined == null); // true
console.log(undefined === null); // false
使用typeof判斷數(shù)據(jù)類型時(shí)衔瓮,undefined會返回undefined,null會返回object浊猾。
console.log(undefined); // undefined
console.log(null); // object
2. && 運(yùn)算符能做什么?
&& 也叫邏輯與,在表達(dá)式中從左往右查找热鞍,找到一個(gè)虛值表達(dá)式并返回它葫慎,如果沒有找到任何虛值表達(dá)式衔彻,則會返回最后一個(gè)虛值表達(dá)式。
console.log(1 && 0 && true); // 0
console.log(true && 222 && 1); // 1
在if語句中使用
let flag;
if (true && 22) {
flag = true;
}else {
flag = true;
}
console.log(flag); // true
3. || 運(yùn)算符能做什么
||也叫或邏輯或偷办,在其操作數(shù)中找到第一個(gè)真值表達(dá)式并返回它艰额。
在支持 ES6 默認(rèn)函數(shù)參數(shù)之前,它用于初始化函數(shù)中的默認(rèn)參數(shù)值椒涯。
console.log(1 || 0 || true); // 1
console.log('' || 0 || 12); // 12
function fun(x) {
var x = x || 0;
}
4. 使用 + 或一元加運(yùn)算符是將字符串轉(zhuǎn)換為數(shù)字的最快方法嗎柄沮?
+是將字符串轉(zhuǎn)換為數(shù)字的最快方法,因?yàn)槿绻狄呀?jīng)是數(shù)字废岂,它不會執(zhí)行任何操作祖搓。
5. DOM 是什么?
DOM 代表文檔對象模型湖苞,是 HTML 和 XML 文檔的接口(API)拯欧。當(dāng)瀏覽器第一次讀取(解析)HTML文檔時(shí),它會創(chuàng)建一個(gè)大對象财骨,一個(gè)基于 HTM L文檔的非常大的對象镐作,這就是DOM。它是一個(gè)從 HTML 文檔中建模的樹狀結(jié)構(gòu)蚓再。DOM 用于交互和修改DOM結(jié)構(gòu)或特定元素或節(jié)點(diǎn)滑肉。
JS 中的document對象表示DOM。它為我們提供了許多方法摘仅,我們可以使用這些方法來選擇元素來更新元素內(nèi)容靶庙,等等。
6. 什么是事件傳播?
當(dāng)事件發(fā)生在DOM元素上時(shí)娃属,該事件并不完全發(fā)生在那個(gè)元素上六荒。在“冒泡階段”中,事件冒泡或向上傳播至父級矾端,祖父母掏击,祖父母或父級,直到到達(dá)window為止秩铆;而在“捕獲階段”中砚亭,事件從window開始向下觸發(fā)元素 事件或event.target。
事件傳播有三個(gè)階段:
- 捕獲階段–事件從 window 開始殴玛,然后向下到每個(gè)元素捅膘,直到到達(dá)目標(biāo)元素。
- 目標(biāo)階段–事件已達(dá)到目標(biāo)元素滚粟。
- 冒泡階段–事件從目標(biāo)元素冒泡寻仗,然后上升到每個(gè)元素,直到到達(dá) window凡壤。
7. 什么是事件冒泡署尤?
當(dāng)事件發(fā)生在DOM元素上時(shí)耙替,該事件并不完全發(fā)生在那個(gè)元素上。在冒泡階段曹体,事件冒泡俗扇,或者事件會向上傳播,發(fā)生在它的父代混坞,祖父母狐援,祖父母的父代钢坦,直到到達(dá)window為止究孕。
<div class="grandparent">
<div class="parent">
<div class="child">1</div>
</div>
</div>
<script>
function addEvent(el, event, callback, isCapture = false) {
if (!el || !event || !callback || typeof callback !== 'function') return;
if (typeof el === 'string') {
el = document.querySelector(el);
};
el.addEventListener(event, callback, isCapture);
}
addEvent(document, 'DOMContentLoaded', () => {
const child = document.querySelector('.child');
const parent = document.querySelector('.parent');
const grandparent = document.querySelector('.grandparent');
addEvent(child, 'click', function (e) {
console.log('child');
});
addEvent(parent, 'click', function (e) {
console.log('parent');
});
addEvent(grandparent, 'click', function (e) {
console.log('grandparent');
});
addEvent(document, 'click', function (e) {
console.log('document');
});
addEvent('html', 'click', function (e) {
console.log('html');
})
addEvent(window, 'click', function (e) {
console.log('window');
})
});
</script>
addEventListener方法具有第三個(gè)可選參數(shù)useCapture,其默認(rèn)值為false爹凹,事件將在冒泡階段中發(fā)生厨诸,如果為true,則事件將在捕獲階段中發(fā)生禾酱。如果單擊child元素微酬,它將分別在控制臺上記錄child,parent颤陶,grandparent颗管,html,document和window滓走,這就是事件冒泡垦江。
8. 什么是事件捕獲?
當(dāng)事件發(fā)生在 DOM 元素上時(shí)搅方,該事件并不完全發(fā)生在那個(gè)元素上比吭。在捕獲階段,事件從window開始姨涡,一直到觸發(fā)事件的元素衩藤。
<div class="grandparent">
<div class="parent">
<div class="child">1</div>
</div>
</div>
<script>
function addEvent(el, event, callback, isCapture = false) {
if (!el || !event || !callback || typeof callback !== 'function') return;
if (typeof el === 'string') {
el = document.querySelector(el);
};
el.addEventListener(event, callback, isCapture);
}
addEvent(document, 'DOMContentLoaded', () => {
const child = document.querySelector('.child');
const parent = document.querySelector('.parent');
const grandparent = document.querySelector('.grandparent');
addEvent(child, 'click', function (e) {
console.log('child');
});
addEvent(parent, 'click', function (e) {
console.log('parent');
});
addEvent(grandparent, 'click', function (e) {
console.log('grandparent');
});
addEvent(document, 'click', function (e) {
console.log('document');
});
addEvent('html', 'click', function (e) {
console.log('html');
})
addEvent(window, 'click', function (e) {
console.log('window');
})
});
</script>
addEventListener方法具有第三個(gè)可選參數(shù)useCapture,其默認(rèn)值為false涛漂,事件將在冒泡階段中發(fā)生赏表,如果為true,則事件將在捕獲階段中發(fā)生匈仗。如果單擊child元素瓢剿,它將分別在控制臺上打印window,document锚沸,html跋选,grandparent和parent,這就是事件捕獲哗蜈。
9. event.preventDefault() 和 event.stopPropagation()方法之間有什么區(qū)別前标?
event.preventDefault() 方法可防止元素的默認(rèn)行為坠韩。如果在表單元素中使用,它將阻止其提交炼列。如果在錨元素中使用只搁,它將阻止其導(dǎo)航。如果在上下文菜單中使用俭尖,它將阻止其顯示或顯示氢惋。 event.stopPropagation()方法用于阻止捕獲和冒泡階段中當(dāng)前事件的進(jìn)一步傳播。
10. 如何知道是否在元素中使用了event.preventDefault()方法稽犁?
我們可以在事件對象中使用event.defaultPrevented屬性焰望。它返回一個(gè)布爾值用來表明是否在特定元素中調(diào)用了event.preventDefault()。
11. 為什么此代碼 obj.someprop.x 會引發(fā)錯(cuò)誤?
const obj = {};
console.log(obj.someprop.x);
顯然已亥,由于我們嘗試訪問someprop屬性中的x屬性熊赖,而 someprop 并沒有在對象中,所以值為 undefined虑椎。記住對象本身不存在的屬性震鹉,并且其原型的默認(rèn)值為undefined。因?yàn)閡ndefined沒有屬性x捆姜,所以試圖訪問將會報(bào)錯(cuò)传趾。
12. 什么是 event.target ?
簡單來說泥技,event.target是發(fā)生事件的元素或觸發(fā)事件的元素浆兰。
假設(shè)有如下的 HTML 結(jié)構(gòu):
<div onclick="clickFunc(event)" style="text-align: center;margin:15px;
border:1px solid red;border-radius:3px;">
<div style="margin: 25px; border:1px solid royalblue;border-radius:3px;">
<div style="margin:25px;border:1px solid skyblue;border-radius:3px;">
<button style="margin:10px">
Button
</button>
</div>
</div>
</div>
<script>
function clickFunc(event) {
console.log(event.target);
}
</script>
如果單擊 button,即使我們將事件附加在最外面的div上零抬,它也將打印 button 標(biāo)簽镊讼,因此我們可以得出結(jié)論event.target是觸發(fā)事件的元素。
13. 什么是 event.currentTarget?
event.currentTarget是我們在其上顯式附加事件處理程序的元素平夜。
<div onclick="clickFunc(event)" style="text-align: center;margin:15px;
border:1px solid red;border-radius:3px;">
<div style="margin: 25px; border:1px solid royalblue;border-radius:3px;">
<div style="margin:25px;border:1px solid skyblue;border-radius:3px;">
<button style="margin:10px">
Button
</button>
</div>
</div>
</div>
<script>
function clickFunc(event) {
console.log(event.currentTarget);
}
</script>
如果單擊 button蝶棋,即使我們單擊該 button,它也會打印最外面的div標(biāo)簽忽妒。在此示例中玩裙,我們可以得出結(jié)論,event.currentTarget是附加事件處理程序的元素段直。
14. == 和 === 有什么區(qū)別吃溅?
==用于一般比較,===用于嚴(yán)格比較鸯檬,==在比較的時(shí)候可以轉(zhuǎn)換數(shù)據(jù)類型决侈,===嚴(yán)格比較,只要類型不匹配就返回flase喧务。
先來看看 == :
強(qiáng)制是將值轉(zhuǎn)換為另一種類型的過程赖歌。在這種情況下枉圃,==會執(zhí)行隱式強(qiáng)制。在比較兩個(gè)值之前庐冯,==需要執(zhí)行一些規(guī)則孽亲。
假設(shè)我們要比較x == y的值。
如果x和y的類型相同展父,則 JS 會換成===操作符進(jìn)行比較返劲。
如果x為null, y為undefined,則返回true栖茉。
如果x為undefined且y為null篮绿,則返回true。
如果x的類型是number, y的類型是string衡载,那么返回x == toNumber(y)搔耕。
如果x的類型是string, y的類型是number隙袁,那么返回toNumber(x) == y痰娱。
如果x為類型是boolean,則返回toNumber(x)== y菩收。
如果y為類型是boolean梨睁,則返回x == toNumber(y)。
如果x是string娜饵、symbol或number坡贺,而y是object類型,則返回x == toPrimitive(y)箱舞。
如果x是object遍坟,y是string,symbol 則返回toPrimitive(x) == y晴股。
剩下的 返回 false
注意:toPrimitive首先在對象中使用valueOf方法愿伴,然后使用toString方法來獲取該對象的原始值。
舉個(gè)例子
x | y | x == y |
---|---|---|
5 | 5 | true |
1 | '1' | true |
null | undefined | true |
0 | false | true |
'1,2' | [1,2] | true |
'[object Object]' | {} | true |
這些例子都返回true电湘。
第一個(gè)示例符合條件1隔节,因?yàn)閤和y具有相同的類型和值。
第二個(gè)示例符合條件4寂呛,在比較之前將y轉(zhuǎn)換為數(shù)字怎诫。
第三個(gè)例子符合條件2。
第四個(gè)例子符合條件7贷痪,因?yàn)閥是boolean類型幻妓。
第五個(gè)示例符合條件8。使用toString()方法將數(shù)組轉(zhuǎn)換為字符串劫拢,該方法返回1,2肉津。
最后一個(gè)示例符合條件8胖缤。使用toString()方法將對象轉(zhuǎn)換為字符串,該方法返回[object Object]阀圾。
x | y | x === y |
---|---|---|
5 | 5 | true |
1 | '1' | false |
null | undefined | false |
0 | false | false |
'1,2' | [1,2] | false |
'[object Object]' | {} | false |
如果使用===運(yùn)算符哪廓,則第一個(gè)示例以外的所有比較將返回false,因?yàn)樗鼈兊念愋筒煌鹾妫谝粋€(gè)示例將返回true涡真,因?yàn)閮烧叩念愋秃椭迪嗤?/p>
15. 為什么在 JS 中比較兩個(gè)相似的對象時(shí)返回 false?
let a = { a: 1 };
let b = { a: 1 };
let c = a;
console.log(a === b); // 打印 false肾筐,即使它們有相同的屬性
console.log(a === c); // true
JS 以不同的方式比較對象和基本類型哆料。在基本類型中,JS 通過值對它們進(jìn)行比較吗铐,而在對象中东亦,JS 通過引用或存儲變量的內(nèi)存中的地址對它們進(jìn)行比較。這就是為什么第一個(gè)console.log語句返回false唬渗,而第二個(gè)console.log語句返回true典阵。a和c有相同的引用地址,而a和b沒有镊逝。
16. !! 運(yùn)算符能做什么壮啊?
!!運(yùn)算符可以將右側(cè)的值強(qiáng)制轉(zhuǎn)換為布爾值,這也是將值轉(zhuǎn)換為布爾值的一種簡單方法撑蒜。
console.log(!!null); // false
console.log(!!undefined); // false
console.log(!!''); // false
console.log(!!0); // false
console.log(!!NaN); // false
console.log(!!' '); // true
console.log(!!{}); // true
console.log(!![]); // true
console.log(!!1); // true
console.log(!![].length); // false
17. 如何在一行中計(jì)算多個(gè)表達(dá)式的值歹啼?
可以使用逗號運(yùn)算符在一行中計(jì)算多個(gè)表達(dá)式。它從左到右求值座菠,并返回右邊最后一個(gè)項(xiàng)目或最后一個(gè)操作數(shù)的值狸眼。
let x = 5;
x = (x++ , x = addFive(x), x *= 2, x -= 5, x += 10);
function addFive(num) {
return num + 5;
}
上面的結(jié)果最后得到x的值為27。首先浴滴,我們將x的值增加到6拓萌,然后調(diào)用函數(shù)addFive(6)并將6作為參數(shù)傳遞并將結(jié)果重新分配給x,此時(shí)x的值為11巡莹。之后司志,將x的當(dāng)前值乘以2并將其分配給x,x的更新值為22降宅。然后骂远,將x的當(dāng)前值減去5并將結(jié)果分配給x x更新后的值為17。最后腰根,我們將x的值增加10激才,然后將更新的值分配給x,最終x的值為27。
18. 什么是變量提升瘸恼?
變量提升是用來描述變量和函數(shù)移動到其(全局或函數(shù))作用域頂部的術(shù)語劣挫。
為了理解變量提升,需要來了解一下執(zhí)行上下文东帅。執(zhí)行上下文是當(dāng)前正在執(zhí)行的“代碼環(huán)境”压固。執(zhí)行上下文有兩個(gè)階段:編譯和執(zhí)行。
編譯:在此階段靠闭,JS 引薦獲取所有函數(shù)聲明并將其提升到其作用域的頂部帐我,以便我們稍后可以引用它們并獲取所有變量聲明(使用var關(guān)鍵字進(jìn)行聲明),還會為它們提供默認(rèn)值: undefined愧膀。
執(zhí)行:在這個(gè)階段中拦键,它將值賦給之前提升的變量,并執(zhí)行或調(diào)用函數(shù)(對象中的方法)檩淋。
注意:只有使用var聲明的變量芬为,或者函數(shù)聲明才會被提升,相反蟀悦,函數(shù)表達(dá)式或箭頭函數(shù)媚朦,let和const聲明的變量,這些都不會被提升熬芜。
假設(shè)在全局使用域莲镣,有如下的代碼:
console.log(y);
y = 1;
console.log(y);
console.log(greet("Mark"));
function greet(name){
return 'Hello ' + name + '!';
}
var y;
上面分別打印:undefined,1, Hello Mark!涎拉。
上面代碼在編譯階段其實(shí)是這樣的:
function greet(name) {
return 'Hello ' + name + '!';
}
var y; // 默認(rèn)值 undefined
// 等待“編譯”階段完成,然后開始“執(zhí)行”階段
/*
console.log(y);
y = 1;
console.log(y);
console.log(greet("Mark"));
*/
編譯階段完成后的圆,它將啟動執(zhí)行階段調(diào)用方法鼓拧,并將值分配給變量。
function greet(name) {
return 'Hello ' + name + '!';
}
var y;
//start "execution" phase
console.log(y);
y = 1;
console.log(y);
console.log(greet("Mark"));
什么是作用域越妈?
JavaScript 中的作用域是我們可以有效訪問變量或函數(shù)的區(qū)域季俩。JS 有三種類型的作用域:全局作用域、函數(shù)作用域和塊作用域(ES6)梅掠。
- 全局作用域:在全局命名空間中聲明的變量或函數(shù)位于全局作用域中酌住,因此在代碼中的任何地方都可以訪問它們。
var g = "global";
function globalFunc(){
function innerFunc(){
console.log(g); // global
}
innerFunc();
}
- 函數(shù)作用域:在函數(shù)中聲明的變量阎抒、函數(shù)和參數(shù)可以在函數(shù)內(nèi)部訪問酪我,但不能在函數(shù)外部訪問。
function myFavoriteFunc(a) {
if (true) {
var b = "Hello " + a;
}
return b;
}
myFavoriteFunc("World");
console.log(a); // a is not defined
console.log(b); // b is not defined
- 塊作用域:在塊{}中聲明的變量(let且叁,const)只能在其中訪問都哭。
function testBlock(){
if(true){
let z = 5;
}
return z;
}
testBlock(); // z is not defined
作用域也是一組用于查找變量的規(guī)則。如果變量在當(dāng)前作用域中不存在,它將向外部作用域中查找并搜索欺矫,如果該變量不存在纱新,它將再次查找直到到達(dá)全局作用域,如果找到穆趴,則可以使用它脸爱,否則引發(fā)錯(cuò)誤,這種查找過程也稱為作用域鏈未妹。
/* 作用域鏈
內(nèi)部作用域->外部作用域-> 全局作用域
*/
// 全局作用域
var variable1 = "Comrades";
var variable2 = "Sayonara";
function outer(){
// 外部作用域
var variable1 = "World";
function inner(){
// 內(nèi)部作用域
var variable2 = "Hello";
console.log(variable2 + " " + variable1);
}
inner();
}
outer(); // Hello World
20. 什么是閉包阅羹?
這可能是所有問題中最難的一個(gè)問題,因?yàn)殚]包是一個(gè)有爭議的話題教寂,這里從個(gè)人角度來談?wù)勀笥悖绻煌缀觯喽嗪:?/p>
閉包就是一個(gè)函數(shù)在聲明時(shí)能夠記住當(dāng)前作用域甥角、父函數(shù)作用域、及父函數(shù)作用域上的變量和參數(shù)的引用控妻,直至通過作用域鏈上全局作用域迂烁,基本上閉包是在聲明函數(shù)時(shí)創(chuàng)建的作用域看尼。
看看小例子:
// 全局作用域
var globalVar = "abc";
function a(){
console.log(globalVar);
}
a(); // "abc"
在此示例中,當(dāng)我們聲明a函數(shù)時(shí)盟步,全局作用域是a閉包的一部分藏斩。
來看一個(gè)更復(fù)雜的例子:
var globalVar = "global";
var outerVar = "outer"
function outerFunc(outerParam) {
function innerFunc(innerParam) {
console.log(globalVar, outerParam, innerParam);
}
return innerFunc;
}
const x = outerFunc(outerVar);
outerVar = "outer-2";
globalVar = "guess"
x("inner");
上面打印結(jié)果是 guess outer inner。
當(dāng)我們調(diào)用outerFunc函數(shù)并將返回值innerFunc函數(shù)分配給變量x時(shí)却盘,即使我們?yōu)閛uterVar變量分配了新值outer-2狰域,outerParam也繼續(xù)保留outer值,因?yàn)橹匦路峙涫窃谡{(diào)用outerFunc之后發(fā)生的黄橘,并且當(dāng)我們調(diào)用outerFunc函數(shù)時(shí)兆览,它會在作用域鏈中查找outerVar的值,此時(shí)的outerVar的值將為 "outer"塞关。
現(xiàn)在抬探,當(dāng)我們調(diào)用引用了innerFunc的x變量時(shí),innerParam將具有一個(gè)inner值帆赢,因?yàn)檫@是我們在調(diào)用中傳遞的值小压,而globalVar變量值為guess,因?yàn)樵谡{(diào)用x變量之前椰于,我們將一個(gè)新值分配給globalVar怠益。
下面這個(gè)示例演示沒有理解好閉包所犯的錯(cuò)誤:
const arrFuncs = [];
for(var i = 0; i < 5; i++){
arrFuncs.push(function (){
return i;
});
}
console.log(i); // i is 5
for (let i = 0; i < arrFuncs.length; i++) {
console.log(arrFuncs[i]()); // 都打印 5
}
由于閉包,此代碼無法正常運(yùn)行廉羔。var關(guān)鍵字創(chuàng)建一個(gè)全局變量溉痢,當(dāng)我們 push 一個(gè)函數(shù)時(shí)僻造,這里返回的全局變量i。因此孩饼,當(dāng)我們在循環(huán)后在該數(shù)組中調(diào)用其中一個(gè)函數(shù)時(shí)髓削,它會打印5,因?yàn)槲覀兊玫絠的當(dāng)前值為5镀娶,我們可以訪問它立膛,因?yàn)樗侨肿兞俊?/p>
因?yàn)殚]包在創(chuàng)建變量時(shí)會保留該變量的引用而不是其值。我們可以使用IIFES或使用 let 來代替 var 的聲明梯码。
21. JavaScript 中的虛值是什么宝泵?
const falsyValues = ['', 0, null, undefined, NaN, false];
簡單的來說虛值就是是在轉(zhuǎn)換為布爾值時(shí)變?yōu)?false 的值。
22. 如何檢查值是否虛值轩娶?
使用 Boolean 函數(shù)或者 !! 運(yùn)算符儿奶。
23. 'use strict' 是干嘛用的?
"use strict" 是 ES5 特性鳄抒,它使我們的代碼在函數(shù)或整個(gè)腳本中處于嚴(yán)格模式闯捎。嚴(yán)格模式幫助我們在代碼的早期避免 bug,并為其添加限制许溅。
嚴(yán)格模式的一些限制:
變量必須聲明后再使用
函數(shù)的參數(shù)不能有同名屬性瓤鼻,否則報(bào)錯(cuò)
不能使用with語句
不能對只讀屬性賦值,否則報(bào)錯(cuò)
不能使用前綴 0 表示八進(jìn)制數(shù)贤重,否則報(bào)錯(cuò)
不能刪除不可刪除的屬性茬祷,否則報(bào)錯(cuò)
不能刪除變量delete prop,會報(bào)錯(cuò)并蝗,只能刪除屬性delete global[prop]
eval不能在它的外層作用域引入變量
eval和arguments不能被重新賦值
arguments不會自動反映函數(shù)參數(shù)的變化
不能使用arguments.callee
不能使用arguments.caller
禁止this指向全局對象
不能使用fn.caller和fn.arguments獲取函數(shù)調(diào)用的堆棧
增加了保留字(比如protected祭犯、static和interface)
設(shè)立”嚴(yán)格模式”的目的,主要有以下幾個(gè):
消除Javascript語法的一些不合理借卧、不嚴(yán)謹(jǐn)之處盹憎,減少一些怪異行為;
消除代碼運(yùn)行的一些不安全之處,保證代碼運(yùn)行的安全铐刘;
提高編譯器效率,增加運(yùn)行速度影晓;
為未來新版本的Javascript做好鋪墊镰吵。
24. JavaScript 中 this 值是什么?
基本上挂签,this指的是當(dāng)前正在執(zhí)行或調(diào)用該函數(shù)的對象的值疤祭。this值的變化取決于我們使用它的上下文和我們在哪里使用它。
const carDetails = {
name: "Ford Mustang",
yearBought: 2005,
getName(){
return this.name;
},
isRegistered: true
};
console.log(carDetails.getName()); // Ford Mustang
這通常是我們期望的結(jié)果饵婆,因?yàn)樵趃etName方法中我們返回this.name勺馆,在此上下文中,this指向的是carDetails對象,該對象當(dāng)前是執(zhí)行函數(shù)的“所有者”對象草穆。
接下我們做些奇怪的事情:
var name = "Ford Ranger";
var getCarName = carDetails.getName;
console.log(getCarName()); // Ford Ranger
上面打印Ford Ranger灌灾,這很奇怪,因?yàn)樵诘谝粋€(gè)console.log語句中打印的是Ford Mustang悲柱。這樣做的原因是getCarName方法有一個(gè)不同的“所有者”對象锋喜,即window對象。在全局作用域中使用var關(guān)鍵字聲明變量會在window對象中附加與變量名稱相同的屬性豌鸡。請記住嘿般,當(dāng)沒有使用“use strict”時(shí),在全局作用域中this指的是window對象涯冠。
console.log(getCarName === window.getCarName); // true
console.log(getCarName === this.getCarName); // true
本例中的this和window引用同一個(gè)對象炉奴。
解決這個(gè)問題的一種方法是在函數(shù)中使用apply和call方法。
console.log(getCarName.apply(carDetails)); // Ford Mustang
console.log(getCarName.call(carDetails)); // Ford Mustang
apply和call方法期望第一個(gè)參數(shù)是一個(gè)對象蛇更,該對象是函數(shù)內(nèi)部this的值瞻赶。
IIFE或立即執(zhí)行的函數(shù)表達(dá)式,在全局作用域內(nèi)聲明的函數(shù)械荷,對象內(nèi)部方法中的匿名函數(shù)和內(nèi)部函數(shù)的this具有默認(rèn)值共耍,該值指向window對象。
(function (){
console.log(this);
})(); // 打印 "window" 對象
function iHateThis(){
console.log(this);
}
iHateThis(); // 打印 "window" 對象
const myFavoriteObj = {
guessThis(){
function getName(){
console.log(this.name);
}
getName();
},
name: 'Marko Polo',
thisIsAnnoying(callback){
callback();
}
};
myFavoriteObj.guessThis(); // 打印 "window" 對象
myFavoriteObj.thisIsAnnoying(function (){
console.log(this); // 打印 "window" 對象
});
如果我們要獲取myFavoriteObj對象中的name屬性(即Marko Polo)的值吨瞎,則有兩種方法可以解決此問題痹兜。
一種是將 this 值保存在變量中。
const myFavoriteObj = {
guessThis(){
const self = this; // 把 this 值保存在 self 變量中
function getName(){
console.log(self.name);
}
getName();
},
name: 'Marko Polo',
thisIsAnnoying(callback){
callback();
}
};
第二種方式是使用箭頭函數(shù)
const myFavoriteObj = {
guessThis(){
const getName = () => {
//copies the value of "this" outside of this arrow function
console.log(this.name);
}
getName();
},
name: 'Marko Polo',
thisIsAnnoying(callback){
callback();
}
};
箭頭函數(shù)沒有自己的 this颤诀。它復(fù)制了這個(gè)封閉的詞法作用域中this值字旭,在這個(gè)例子中,this值在getName內(nèi)部函數(shù)之外崖叫,也就是myFavoriteObj對象遗淳。
25. 對象的 prototype(原型) 是什么?
簡單地說心傀,原型就是對象的藍(lán)圖屈暗。如果它存在當(dāng)前對象中,則將其用作屬性和方法的回退脂男。它是在對象之間共享屬性和功能的方法养叛,這也是JavaScript實(shí)現(xiàn)繼承的核心。
const o = {};
console.log(o.toString()); // [object Object]
即使o對象中不存在o.toString方法宰翅,它也不會引發(fā)錯(cuò)誤弃甥,而是返回字符串[object Object]。當(dāng)對象中不存在屬性時(shí)汁讼,它將查看其原型淆攻,如果仍然不存在阔墩,則將其查找到原型的原型,依此類推瓶珊,直到在原型鏈中找到具有相同屬性的屬性為止啸箫。原型鏈的末尾是Object.prototype。
console.log(o.toString === Object.prototype.toString); // true
26. 什么是 IIFE艰毒,它的用途是什么筐高?
IIFE或立即調(diào)用的函數(shù)表達(dá)式,是在創(chuàng)建或聲明后將被調(diào)用或執(zhí)行的函數(shù)丑瞧。創(chuàng)建IIFE的語法是柑土,將function (){}包裹在在括號()內(nèi),然后再用另一個(gè)括號()調(diào)用它绊汹,如:(function(){})()
(function(){
...
} ());
(function () {
...
})();
(function named(params) {
...
})();
(() => {
});
(function (global) {
...
})(window);
const utility = (function () {
return {
...
}
})
這些示例都是有效的IIFE稽屏。倒數(shù)第二個(gè)救命表明我們可以將參數(shù)傳遞給IIFE函數(shù)。最后一個(gè)示例表明西乖,我們可以將IIFE的結(jié)果保存到變量中狐榔,以便稍后使用。
IIFE的一個(gè)主要作用是避免與全局作用域內(nèi)的其他變量命名沖突或污染全局命名空間获雕,來個(gè)例子薄腻。
<script src="https://cdnurl.com/somelibrary.js"></script>
假設(shè)我們引入了一個(gè)somelibr.js的鏈接,它提供了一些我們在代碼中使用的全局函數(shù)届案,但是這個(gè)庫有兩個(gè)方法我們沒有使用:createGraph和drawGraph庵楷,因?yàn)檫@些方法都有bug。我們想實(shí)現(xiàn)自己的createGraph和drawGraph方法楣颠。
解決此問題的一種方法是直接覆蓋:
<script src="https://cdnurl.com/somelibrary.js"></script>
<script>
function createGraph() {
// createGraph logic here
}
function drawGraph() {
// drawGraph logic here
}
</script>
當(dāng)我們使用這個(gè)解決方案時(shí)尽纽,我們覆蓋了庫提供給我們的那兩個(gè)方法。
另一種方式是我們自己改名稱:
<script src="https://cdnurl.com/somelibrary.js"></script>
<script>
function myCreateGraph() {
// createGraph logic here
}
function myDrawGraph() {
// drawGraph logic here
}
</script>
當(dāng)我們使用這個(gè)解決方案時(shí)童漩,我們把那些函數(shù)調(diào)用更改為新的函數(shù)名弄贿。
還有一種方法就是使用IIFE:
<script src="https://cdnurl.com/somelibrary.js"></script>
<script>
const graphUtility = (function () {
function createGraph() {
// createGraph logic here
}
function drawGraph() {
// drawGraph logic here
}
return {
createGraph,
drawGraph
}
})
</script>
在此解決方案中,我們要聲明了graphUtility 變量矫膨,用來保存IIFE執(zhí)行的結(jié)果差凹,該函數(shù)返回一個(gè)包含兩個(gè)方法createGraph和drawGraph的對象。
IIFE 還可以用來解決一個(gè)常見的面試題:
var li = document.querySelectorAll('.list-group > li');
for (var i = 0, len = li.length; i < len; i++) {
li[i].addEventListener('click', function (e) {
console.log(i);
})
假設(shè)我們有一個(gè)帶有l(wèi)ist-group類的ul元素侧馅,它有5個(gè)li子元素直奋。當(dāng)我們單擊單個(gè)li元素時(shí),打印對應(yīng)的下標(biāo)值施禾。但在此外上述代碼不起作用,這里每次點(diǎn)擊 li 打印 i 的值都是5搁胆,這是由于閉包的原因弥搞。
閉包只是函數(shù)記住其當(dāng)前作用域邮绿,父函數(shù)作用域和全局作用域的變量引用的能力。當(dāng)我們在全局作用域內(nèi)使用var關(guān)鍵字聲明變量時(shí)攀例,就創(chuàng)建全局變量i船逮。因此,當(dāng)我們單擊li元素時(shí)粤铭,它將打印5挖胃,因?yàn)檫@是稍后在回調(diào)函數(shù)中引用它時(shí)i的值。
使用 IIFE 可以解決此問題:
var li = document.querySelectorAll('.list-group > li');
for (var i = 0, len = li.length; i < len; i++) {
(function (currentIndex) {
li[currentIndex].addEventListener('click', function (e) {
console.log(currentIndex);
})
})(i);
}
該解決方案之所以行的通梆惯,是因?yàn)镮IFE會為每次迭代創(chuàng)建一個(gè)新的作用域酱鸭,我們捕獲i的值并將其傳遞給currentIndex參數(shù),因此調(diào)用IIFE時(shí)垛吗,每次迭代的currentIndex值都是不同的凹髓。
27. Function.prototype.apply 方法的用途是什么?
apply() 方法調(diào)用一個(gè)具有給定this值的函數(shù)怯屉,以及作為一個(gè)數(shù)組(或類似數(shù)組對象)提供的參數(shù)蔚舀。
const details = {
message: 'Hello World!'
};
function getMessage(){
return this.message;
}
getMessage.apply(details); // 'Hello World!'
call()方法的作用和 apply() 方法類似,區(qū)別就是call()方法接受的是參數(shù)列表锨络,而apply()方法接受的是一個(gè)參數(shù)數(shù)組赌躺。
const person = {
name: "Marko Polo"
};
function greeting(greetingMessage) {
return `${greetingMessage} ${this.name}`;
}
greeting.apply(person, ['Hello']); // "Hello Marko Polo!"
28. Function.prototype.call 方法的用途是什么?
call() 方法使用一個(gè)指定的 this 值和單獨(dú)給出的一個(gè)或多個(gè)參數(shù)來調(diào)用一個(gè)函數(shù)羡儿。
const details = {
message: 'Hello World!'
};
function getMessage(){
return this.message;
}
getMessage.call(details); // 'Hello World!'
注意:該方法的語法和作用與 apply() 方法類似礼患,只有一個(gè)區(qū)別,就是 call() 方法接受的是一個(gè)參數(shù)列表失受,而 apply() 方法接受的是一個(gè)包含多個(gè)參數(shù)的數(shù)組讶泰。
const person = {
name: "Marko Polo"
};
function greeting(greetingMessage) {
return `${greetingMessage} ${this.name}`;
}
greeting.call(person, 'Hello'); // "Hello Marko Polo!"
29. Function.prototype.apply 和 Function.prototype.call 之間有什么區(qū)別?
apply()方法可以在使用一個(gè)指定的 this 值和一個(gè)參數(shù)數(shù)組(或類數(shù)組對象)的前提下調(diào)用某個(gè)函數(shù)或方法拂到。call()方法類似于apply()痪署,不同之處僅僅是call()接受的參數(shù)是參數(shù)列表。
const obj1 = {
result:0
};
const obj2 = {
result:0
};
function reduceAdd(){
let result = 0;
for(let i = 0, len = arguments.length; i < len; i++){
result += arguments[i];
}
this.result = result;
}
reduceAdd.apply(obj1, [1, 2, 3, 4, 5]); // 15
reduceAdd.call(obj2, 1, 2, 3, 4, 5); // 15
30. Function.prototype.bind 的用途是什么兄旬?
bind() 方法創(chuàng)建一個(gè)新的函數(shù)狼犯,在 bind() 被調(diào)用時(shí),這個(gè)新函數(shù)的 this 被指定為 bind() 的第一個(gè)參數(shù)领铐,而其余參數(shù)將作為新函數(shù)的參數(shù)悯森,供調(diào)用時(shí)使用。
import React from 'react';
class MyComponent extends React.Component {
constructor(props){
super(props);
this.state = {
value : ""
}
this.handleChange = this.handleChange.bind(this);
// 將 “handleChange” 方法綁定到 “MyComponent” 組件
}
handleChange(e){
//do something amazing here
}
render(){
return (
<>
<input type={this.props.type}
value={this.state.value}
onChange={this.handleChange}
/>
</>
)
}
}
31. 什么是函數(shù)式編程? JavaScript 的哪些特性使其成為函數(shù)式語言的候選語言绪撵?
函數(shù)式編程(通称耙觯縮寫為FP)是通過編寫純函數(shù),避免共享狀態(tài)音诈、可變數(shù)據(jù)幻碱、副作用 來構(gòu)建軟件的過程绎狭。數(shù)式編程是聲明式 的而不是命令式 的,應(yīng)用程序的狀態(tài)是通過純函數(shù)流動的褥傍。與面向?qū)ο缶幊绦纬蓪Ρ壤芩唬嫦驅(qū)ο笾袘?yīng)用程序的狀態(tài)通常與對象中的方法共享和共處。
函數(shù)式編程是一種編程范式 恍风,這意味著它是一種基于一些基本的定義原則(如上所列)思考軟件構(gòu)建的方式蹦狂。當(dāng)然,編程范示的其他示例也包括面向?qū)ο缶幊毯瓦^程編程朋贬。
函數(shù)式的代碼往往比命令式或面向?qū)ο蟮拇a更簡潔凯楔,更可預(yù)測,更容易測試 - 但如果不熟悉它以及與之相關(guān)的常見模式兄世,函數(shù)式的代碼也可能看起來更密集雜亂啼辣,并且 相關(guān)文獻(xiàn)對新人來說是不好理解的。
JavaScript支持閉包和高階函數(shù)是函數(shù)式編程語言的特點(diǎn)御滩。
32. 什么是高階函數(shù)鸥拧?
高階函數(shù)只是將函數(shù)作為參數(shù)或返回值的函數(shù)。
function higherOrderFunction(param,callback){
return callback(param);
}
33. 為什么函數(shù)被稱為一等公民削解?
在JavaScript中富弦,函數(shù)不僅擁有一切傳統(tǒng)函數(shù)的使用方式(聲明和調(diào)用),而且可以做到像簡單值一樣賦值(var func = function(){})氛驮、傳參(function func(x,callback){callback();})腕柜、返回(function(){return function(){}}),這樣的函數(shù)也稱之為第一級函數(shù)(First-class Function)矫废。不僅如此盏缤,JavaScript中的函數(shù)還充當(dāng)了類的構(gòu)造函數(shù)的作用,同時(shí)又是一個(gè)Function類的實(shí)例(instance)蓖扑。這樣的多重身份讓JavaScript的函數(shù)變得非常重要唉铜。
34. 手動實(shí)現(xiàn) Array.prototype.map 方法
map() 方法創(chuàng)建一個(gè)新數(shù)組,其結(jié)果是該數(shù)組中的每個(gè)元素都調(diào)用一個(gè)提供的函數(shù)后返回的結(jié)果律杠。
function map(arr, mapCallback) {
// 首先潭流,檢查傳遞的參數(shù)是否正確。
if (!Array.isArray(arr) || !arr.length || typeof mapCallback !== 'function') {
return [];
} else {
let result = [];
// 每次調(diào)用此函數(shù)時(shí)柜去,我們都會創(chuàng)建一個(gè) result 數(shù)組
// 因?yàn)槲覀儾幌敫淖冊紨?shù)組灰嫉。
for (let i = 0, len = arr.length; i < len; i++) {
result.push(mapCallback(arr[i], i, arr));
// 將 mapCallback 返回的結(jié)果 push 到 result 數(shù)組中
}
return result;
}
}
35. 手動實(shí)現(xiàn)Array.prototype.filter方法
filter() 方法創(chuàng)建一個(gè)新數(shù)組, 其包含通過所提供函數(shù)實(shí)現(xiàn)的測試的所有元素。
function filter(arr, filterCallback) {
// 首先嗓奢,檢查傳遞的參數(shù)是否正確讼撒。
if (!Array.isArray(arr) || !arr.length || typeof filterCallback !== 'function')
{
return [];
} else {
let result = [];
// 每次調(diào)用此函數(shù)時(shí),我們都會創(chuàng)建一個(gè) result 數(shù)組
// 因?yàn)槲覀儾幌敫淖冊紨?shù)組。
for (let i = 0, len = arr.length; i < len; i++) {
// 檢查 filterCallback 的返回值是否是真值
if (filterCallback(arr[i], i, arr)) {
// 如果條件為真椿肩,則將數(shù)組元素 push 到 result 中
result.push(arr[i]);
}
}
return result; // return the result array
}
}
36. 手動實(shí)現(xiàn)Array.prototype.reduce方法
reduce() 方法對數(shù)組中的每個(gè)元素執(zhí)行一個(gè)由您提供的reducer函數(shù)(升序執(zhí)行)瞻颂,將其結(jié)果匯總為單個(gè)返回值。
function reduce(arr, reduceCallback, initialValue) {
// 首先郑象,檢查傳遞的參數(shù)是否正確。
if (!Array.isArray(arr) || !arr.length || typeof reduceCallback !== 'function')
{
return [];
} else {
// 如果沒有將initialValue傳遞給該函數(shù)茬末,我們將使用第一個(gè)數(shù)組項(xiàng)作為initialValue
let hasInitialValue = initialValue !== undefined;
let value = hasInitialValue ? initialValue : arr[0];
厂榛、
// 如果有傳遞 initialValue,則索引從 1 開始丽惭,否則從 0 開始
for (let i = hasInitialValue ? 0 : 1, len = arr.length; i < len; i++) {
value = reduceCallback(value, arr[i], i, arr);
}
return value;
}
}
37. arguments 的對象是什么击奶?
arguments對象是函數(shù)中傳遞的參數(shù)值的集合。它是一個(gè)類似數(shù)組的對象责掏,因?yàn)樗幸粋€(gè)length屬性柜砾,我們可以使用數(shù)組索引表示法arguments[1]來訪問單個(gè)值,但它沒有數(shù)組中的內(nèi)置方法换衬,如:forEach痰驱、reduce、filter和map瞳浦。
我們可以使用Array.prototype.slice將arguments對象轉(zhuǎn)換成一個(gè)數(shù)組担映。
function one() {
return Array.prototype.slice.call(arguments);
}
注意:箭頭函數(shù)中沒有arguments對象。
function one() {
return arguments;
}
const two = function () {
return arguments;
}
const three = function three() {
return arguments;
}
const four = () => arguments;
four(); // Throws an error - arguments is not defined
當(dāng)我們調(diào)用函數(shù)four時(shí)叫潦,它會拋出一個(gè)ReferenceError: arguments is not defined error蝇完。使用rest語法,可以解決這個(gè)問題矗蕊。
const four = (...args) => args;
這會自動將所有參數(shù)值放入數(shù)組中短蜕。
38. 如何創(chuàng)建一個(gè)沒有 prototype(原型)的對象?
我們可以使用Object.create方法創(chuàng)建沒有原型的對象傻咖。
const o1 = {};
console.log(o1.toString()); // [object Object]
const o2 = Object.create(null);
console.log(o2.toString());
// throws an error o2.toString is not a function
39. 為什么在調(diào)用這個(gè)函數(shù)時(shí)朋魔,代碼中的b會變成一個(gè)全局變量?
function myFunc() {
let a = b = 0;
}
myFunc();
原因是賦值運(yùn)算符是從右到左的求值的。這意味著當(dāng)多個(gè)賦值運(yùn)算符出現(xiàn)在一個(gè)表達(dá)式中時(shí)没龙,它們是從右向左求值的铺厨。所以上面代碼變成了這樣:
function myFunc() {
let a = (b = 0);
}
myFunc();
首先,表達(dá)式b = 0求值硬纤,在本例中b沒有聲明解滓。因此,JS引擎在這個(gè)函數(shù)外創(chuàng)建了一個(gè)全局變量b筝家,之后表達(dá)式b = 0的返回值為0洼裤,并賦給新的局部變量a。
我們可以通過在賦值之前先聲明變量來解決這個(gè)問題溪王。
function myFunc() {
let a,b;
a = b = 0;
}
myFunc();
40. ECMAScript 是什么腮鞍?
ECMAScript 是編寫腳本語言的標(biāo)準(zhǔn)值骇,這意味著JavaScript遵循ECMAScript標(biāo)準(zhǔn)中的規(guī)范變化,因?yàn)樗荍avaScript的藍(lán)圖移国。
ECMAScript 和 Javascript吱瘩,本質(zhì)上都跟一門語言有關(guān),一個(gè)是語言本身的名字迹缀,一個(gè)是語言的約束條件
只不過發(fā)明JavaScript的那個(gè)人(Netscape公司)使碾,把東西交給了ECMA(European Computer Manufacturers Association),這個(gè)人規(guī)定一下他的標(biāo)準(zhǔn)祝懂,因?yàn)楫?dāng)時(shí)有java語言了票摇,又想強(qiáng)調(diào)這個(gè)東西是讓ECMA這個(gè)人定的規(guī)則,所以就這樣一個(gè)神奇的東西誕生了砚蓬,這個(gè)東西的名稱就叫做ECMAScript矢门。
javaScript = ECMAScript + DOM + BOM(自認(rèn)為是一種廣義的JavaScript)
ECMAScript說什么JavaScript就得做什么!
JavaScript(狹義的JavaScript)做什么都要問問ECMAScript我能不能這樣干灰蛙!如果不能我就錯(cuò)了祟剔!能我就是對的!
突然感覺JavaScript好沒有尊嚴(yán)缕允,為啥要搞個(gè)人出來約束自己峡扩,
那個(gè)人被創(chuàng)造出來也好委屈,自己被創(chuàng)造出來完全是因?yàn)橐s束JavaScript障本。
41. ES6或ECMAScript 2015有哪些新特性教届?
- 箭頭函數(shù)
- 類
- 模板字符串
- 加強(qiáng)的對象字面量
- 對象解構(gòu)
- Promise
- 生成器
- 模塊
- Symbol
- 代理
- Set
- 函數(shù)默認(rèn)參數(shù)
- rest 和展開
- 塊作用域
42. var,let和const的區(qū)別是什么?
var聲明的變量會掛載在window上驾霜,而let和const聲明的變量不會:
var a = 100;
console.log(a,window.a); // 100 100
let b = 10;
console.log(b,window.b); // 10 undefined
const c = 1;
console.log(c,window.c); // 1 undefined
var聲明變量存在變量提升案训,let和const不存在變量提升:
console.log(a); // undefined ===> a已聲明還沒賦值,默認(rèn)得到undefined值
var a = 100;
console.log(b); // 報(bào)錯(cuò):b is not defined ===> 找不到b這個(gè)變量
let b = 10;
console.log(c); // 報(bào)錯(cuò):c is not defined ===> 找不到c這個(gè)變量
const c = 10;
let和const聲明形成塊作用域
if(1){
var a = 100;
let b = 10;
}
console.log(a); // 100
console.log(b) // 報(bào)錯(cuò):b is not defined ===> 找不到b這個(gè)變量
-------------------------------------------------------------
if(1){
var a = 100;
const c = 1;
}
console.log(a); // 100
console.log(c) // 報(bào)錯(cuò):c is not defined ===> 找不到c這個(gè)變量
同一作用域下let和const不能聲明同名變量粪糙,而var可以
var a = 100;
console.log(a); // 100
var a = 10;
console.log(a); // 10
-------------------------------------
let a = 100;
let a = 10;
// 控制臺報(bào)錯(cuò):Identifier 'a' has already been declared ===> 標(biāo)識符a已經(jīng)被聲明了潦刃。
暫存死區(qū)
var a = 100;
if(1){
a = 10;
//在當(dāng)前塊作用域中存在a使用let/const聲明的情況下融求,給a賦值10時(shí)窃这,只會在當(dāng)前作用域找變量a钧唐,
// 而這時(shí),還未到聲明時(shí)候寞酿,所以控制臺Error:a is not defined
let a = 1;
}
const
/*
* 1家夺、一旦聲明必須賦值,不能使用null占位。
*
* 2伐弹、聲明后不能再修改
*
* 3拉馋、如果聲明的是復(fù)合類型數(shù)據(jù),可以修改其屬性
*
* */
const a = 100;
const list = [];
list[0] = 10;
console.log(list); // [10]
const obj = {a:100};
obj.name = 'apple';
obj.a = 10000;
console.log(obj); // {a:10000,name:'apple'}
43. 什么是箭頭函數(shù)?
箭頭函數(shù)表達(dá)式的語法比函數(shù)表達(dá)式更簡潔煌茴,并且沒有自己的this随闺,arguments,super或new.target蔓腐。箭頭函數(shù)表達(dá)式更適用于那些本來需要匿名函數(shù)的地方矩乐,并且它不能用作構(gòu)造函數(shù)。
//ES5 Version
var getCurrentDate = function (){
return new Date();
}
//ES6 Version
const getCurrentDate = () => new Date();
在本例中合住,ES5 版本中有function(){}聲明和return關(guān)鍵字绰精,這兩個(gè)關(guān)鍵字分別是創(chuàng)建函數(shù)和返回值所需要的。在箭頭函數(shù)版本中透葛,我們只需要()括號,不需要 return 語句卿樱,因?yàn)槿绻覀冎挥幸粋€(gè)表達(dá)式或值需要返回僚害,箭頭函數(shù)就會有一個(gè)隱式的返回。
//ES5 Version
function greet(name) {
return 'Hello ' + name + '!';
}
//ES6 Version
const greet = (name) => `Hello ${name}`;
const greet2 = name => `Hello ${name}`;
我們還可以在箭頭函數(shù)中使用與函數(shù)表達(dá)式和函數(shù)聲明相同的參數(shù)繁调。如果我們在一個(gè)箭頭函數(shù)中有一個(gè)參數(shù)萨蚕,則可以省略括號。
const getArgs = () => arguments
const getArgs2 = (...rest) => rest
箭頭函數(shù)不能訪問arguments對象蹄胰。所以調(diào)用第一個(gè)getArgs函數(shù)會拋出一個(gè)錯(cuò)誤岳遥。相反,我們可以使用rest參數(shù)來獲得在箭頭函數(shù)中傳遞的所有參數(shù)裕寨。
const data = {
result: 0,
nums: [1, 2, 3, 4, 5],
computeResult() {
// 這里的“this”指的是“data”對象
const addAll = () => {
return this.nums.reduce((total, cur) => total + cur, 0)
};
this.result = addAll();
}
};
箭頭函數(shù)沒有自己的this值浩蓉。它捕獲詞法作用域函數(shù)的this值,在此示例中宾袜,addAll函數(shù)將復(fù)制computeResult 方法中的this值捻艳,如果我們在全局作用域聲明箭頭函數(shù),則this值為 window 對象庆猫。
44. 什么是類认轨?
類(class)是在 JS 中編寫構(gòu)造函數(shù)的新方法。它是使用構(gòu)造函數(shù)的語法糖月培,在底層中使用仍然是原型和基于原型的繼承嘁字。
//ES5 Version
function Person(firstName, lastName, age, address){
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
this.address = address;
}
Person.self = function(){
return this;
}
Person.prototype.toString = function(){
return "[object Person]";
}
Person.prototype.getFullName = function (){
return this.firstName + " " + this.lastName;
}
//ES6 Version
class Person {
constructor(firstName, lastName, age, address){
this.lastName = lastName;
this.firstName = firstName;
this.age = age;
this.address = address;
}
static self() {
return this;
}
toString(){
return "[object Person]";
}
getFullName(){
return `${this.firstName} ${this.lastName}`;
}
}
重寫方法并從另一個(gè)類繼承。
//ES5 Version
Employee.prototype = Object.create(Person.prototype);
function Employee(firstName, lastName, age, address, jobTitle, yearStarted) {
Person.call(this, firstName, lastName, age, address);
this.jobTitle = jobTitle;
this.yearStarted = yearStarted;
}
Employee.prototype.describe = function () {
return `I am ${this.getFullName()} and I have a position of ${this.jobTitle}
and I started at ${this.yearStarted}`;
}
Employee.prototype.toString = function () {
return "[object Employee]";
}
//ES6 Version
class Employee extends Person { //Inherits from "Person" class
constructor(firstName, lastName, age, address, jobTitle, yearStarted) {
super(firstName, lastName, age, address);
this.jobTitle = jobTitle;
this.yearStarted = yearStarted;
}
describe() {
return `I am ${this.getFullName()} and I have a position of ${this.jobTitle}
and I started at ${this.yearStarted}`;
}
toString() { // Overriding the "toString" method of "Person"
return "[object Employee]";
}
}
所以我們要怎么知道它在內(nèi)部使用原型杉畜?
class Something {
}
function AnotherSomething(){
}
const as = new AnotherSomething();
const s = new Something();
console.log(typeof Something); // "function"
console.log(typeof AnotherSomething); // "function"
console.log(as.toString()); // "[object Object]"
console.log(as.toString()); // "[object Object]"
console.log(as.toString === Object.prototype.toString); // true
console.log(s.toString === Object.prototype.toString); // true
45. 什么是模板字符串纪蜒?
模板字符串是在 JS 中創(chuàng)建字符串的一種新方法。我們可以通過使用反引號使模板字符串化寻行。
//ES5 Version
var greet = 'Hi I\'m Mark';
//ES6 Version
let greet = `Hi I'm Mark`;
在 ES5 中我們需要使用一些轉(zhuǎn)義字符來達(dá)到多行的效果霍掺,在模板字符串不需要這么麻煩:
//ES5 Version
var lastWords = '\n'
+ ' I \n'
+ ' Am \n'
+ 'Iron Man \n';
//ES6 Version
let lastWords = `
I
Am
Iron Man
`;
在ES5版本中,我們需要添加\n以在字符串中添加新行。在模板字符串中杆烁,我們不需要這樣做牙丽。
//ES5 Version
function greet(name) {
return 'Hello ' + name + '!';
}
//ES6 Version
function greet(name) {
return `Hello ${name} !`;
}
在 ES5 版本中,如果需要在字符串中添加表達(dá)式或值兔魂,則需要使用+運(yùn)算符烤芦。在模板字符串s中,我們可以使用${expr}嵌入一個(gè)表達(dá)式析校,這使其比 ES5 版本更整潔构罗。
46. 什么是對象解構(gòu)?
對象析構(gòu)是從對象或數(shù)組中獲取或提取值的一種新的智玻、更簡潔的方法遂唧。假設(shè)有如下的對象:
const employee = {
firstName: "Marko",
lastName: "Polo",
position: "Software Developer",
yearHired: 2017
};
從對象獲取屬性,早期方法是創(chuàng)建一個(gè)與對象屬性同名的變量吊奢。這種方法很麻煩盖彭,因?yàn)槲覀円獮槊總€(gè)屬性創(chuàng)建一個(gè)新變量。假設(shè)我們有一個(gè)大對象页滚,它有很多屬性和方法召边,用這種方法提取屬性會很麻煩。
var firstName = employee.firstName;
var lastName = employee.lastName;
var position = employee.position;
var yearHired = employee.yearHired;
使用解構(gòu)方式語法就變得簡潔多了:
{ firstName, lastName, position, yearHired } = employee;
我們還可以為屬性取別名:
let { firstName: fName, lastName: lName, position, yearHired } = employee;
當(dāng)然如果屬性值為 undefined 時(shí)裹驰,我們還可以指定默認(rèn)值:
let { firstName = "Mark", lastName: lName, position, yearHired } = employee;
47. 什么是 ES6 模塊隧熙?
模塊使我們能夠?qū)⒋a基礎(chǔ)分割成多個(gè)文件,以獲得更高的可維護(hù)性幻林,并且避免將所有代碼放在一個(gè)大文件中贞盯。在 ES6 支持模塊之前,有兩個(gè)流行的模塊滋将。
- CommonJS-Node.js
- AMD(異步模塊定義)-瀏覽器
基本上邻悬,使用模塊的方式很簡單,import用于從另一個(gè)文件中獲取功能或幾個(gè)功能或值随闽,同時(shí)export用于從文件中公開功能或幾個(gè)功能或值父丰。
導(dǎo)出
使用 ES5 (CommonJS)
// 使用 ES5 CommonJS - helpers.js
exports.isNull = function (val) {
return val === null;
}
exports.isUndefined = function (val) {
return val === undefined;
}
exports.isNullOrUndefined = function (val) {
return exports.isNull(val) || exports.isUndefined(val);
}
使用 ES6 模塊
// 使用 ES6 Modules - helpers.js
export function isNull(val){
return val === null;
}
export function isUndefined(val) {
return val === undefined;
}
export function isNullOrUndefined(val) {
return isNull(val) || isUndefined(val);
}
在另一個(gè)文件中導(dǎo)入函數(shù)
// 使用 ES5 (CommonJS) - index.js
const helpers = require('./helpers.js'); // helpers is an object
const isNull = helpers.isNull;
const isUndefined = helpers.isUndefined;
const isNullOrUndefined = helpers.isNullOrUndefined;
// or if your environment supports Destructuring
const { isNull, isUndefined, isNullOrUndefined } = require('./helpers.js');
-------------------------------------------------------
// ES6 Modules - index.js
import * as helpers from './helpers.js'; // helpers is an object
// or
import { isNull, isUndefined, isNullOrUndefined as isValid } from './helpers.js';
// using "as" for renaming named exports
在文件中導(dǎo)出單個(gè)功能或默認(rèn)導(dǎo)出
使用 ES5 (CommonJS)
// 使用 ES5 (CommonJS) - index.js
class Helpers {
static isNull(val) {
return val === null;
}
static isUndefined(val) {
return val === undefined;
}
static isNullOrUndefined(val) {
return this.isNull(val) || this.isUndefined(val);
}
}
module.exports = Helpers;
使用ES6 Modules
// 使用 ES6 Modules - helpers.js
class Helpers {
static isNull(val) {
return val === null;
}
static isUndefined(val) {
return val === undefined;
}
static isNullOrUndefined(val) {
return this.isNull(val) || this.isUndefined(val);
}
}
export default Helpers
從另一個(gè)文件導(dǎo)入單個(gè)功能
使用ES5 (CommonJS)
// 使用 ES5 (CommonJS) - index.js
const Helpers = require('./helpers.js');
console.log(Helpers.isNull(null));
使用 ES6 Modules
import Helpers from '.helpers.js'
console.log(Helpers.isNull(null));
48. 什么是Set對象,它是如何工作的掘宪?
Set 對象允許你存儲任何類型的唯一值蛾扇,無論是原始值或者是對象引用。
我們可以使用Set構(gòu)造函數(shù)創(chuàng)建Set實(shí)例魏滚。
const set1 = new Set();
const set2 = new Set(["a","b","c","d","d","e"]);
我們可以使用add方法向Set實(shí)例中添加一個(gè)新值镀首,因?yàn)閍dd方法返回Set對象,所以我們可以以鏈?zhǔn)降姆绞皆俅问褂胊dd鼠次。如果一個(gè)值已經(jīng)存在于Set對象中更哄,那么它將不再被添加芋齿。
set2.add("f");
set2.add("g").add("h").add("i").add("j").add("k").add("k");
// 后一個(gè)“k”不會被添加到set對象中,因?yàn)樗呀?jīng)存在了
我們可以使用has方法檢查Set實(shí)例中是否存在特定的值成翩。
set2.has("a") // true
set2.has("z") // true
我們可以使用size屬性獲得Set實(shí)例的長度觅捆。
set2.size // returns 10
可以使用clear方法刪除 Set 中的數(shù)據(jù)。
set2.clear();
我們可以使用Set對象來刪除數(shù)組中重復(fù)的元素麻敌。
const numbers = [1, 2, 3, 4, 5, 6, 6, 7, 8, 8, 5];
const uniqueNums = [...new Set(numbers)]; // [1,2,3,4,5,6,7,8]
49. 什么是回調(diào)函數(shù)栅炒?
回調(diào)函數(shù)是一段可執(zhí)行的代碼段,它作為一個(gè)參數(shù)傳遞給其他的代碼术羔,其作用是在需要的時(shí)候方便調(diào)用這段(回調(diào)函數(shù))代碼赢赊。
在JavaScript中函數(shù)也是對象的一種,同樣對象可以作為參數(shù)傳遞給函數(shù)级历,因此函數(shù)也可以作為參數(shù)傳遞給另外一個(gè)函數(shù)释移,這個(gè)作為參數(shù)的函數(shù)就是回調(diào)函數(shù)。
const btnAdd = document.getElementById('btnAdd');
btnAdd.addEventListener('click', function clickCallback(e) {
// do something useless
});
在本例中寥殖,我們等待id為btnAdd的元素中的click事件秀鞭,如果它被單擊,則執(zhí)行clickCallback函數(shù)扛禽。回調(diào)函數(shù)向某些數(shù)據(jù)或事件添加一些功能皱坛。
數(shù)組中的reduce编曼、filter和map方法需要一個(gè)回調(diào)作為參數(shù)∈1伲回調(diào)的一個(gè)很好的類比是掐场,當(dāng)你打電話給某人,如果他們不接贩猎,你留下一條消息熊户,你期待他們回調(diào)。調(diào)用某人或留下消息的行為是事件或數(shù)據(jù)吭服,回調(diào)是你希望稍后發(fā)生的操作嚷堡。
50. Promise 是什么?
Promise 是異步編程的一種解決方案:從語法上講艇棕,promise是一個(gè)對象蝌戒,從它可以獲取異步操作的消息;從本意上講沼琉,它是承諾北苟,承諾它過一段時(shí)間會給你一個(gè)結(jié)果。promise有三種狀態(tài):pending(等待態(tài))打瘪,fulfiled(成功態(tài))友鼻,rejected(失敗態(tài))傻昙;狀態(tài)一旦改變,就不會再變彩扔。創(chuàng)造promise實(shí)例后妆档,它會立即執(zhí)行。
fs.readFile('somefile.txt', function (e, data) {
if (e) {
console.log(e);
}
console.log(data);
});
如果我們在回調(diào)內(nèi)部有另一個(gè)異步操作借杰,則此方法存在問題过吻。我們將有一個(gè)混亂且不可讀的代碼。此代碼稱為“回調(diào)地獄”蔗衡。
// 回調(diào)地獄
fs.readFile('somefile.txt', function (e, data) {
//your code here
fs.readdir('directory', function (e, files) {
//your code here
fs.mkdir('directory', function (e) {
//your code here
})
})
})
如果我們在這段代碼中使用promise纤虽,它將更易于閱讀、理解和維護(hù)绞惦。
promReadFile('file/path')
.then(data => {
return promReaddir('directory');
})
.then(data => {
return promMkdir('directory');
})
.catch(e => {
console.log(e);
})
promise有三種不同的狀態(tài):
- pending:初始狀態(tài)逼纸,完成或失敗狀態(tài)的前一個(gè)狀態(tài)
- fulfilled:操作成功完成
- rejected:操作失敗
pending 狀態(tài)的 Promise 對象會觸發(fā) fulfilled/rejected 狀態(tài),在其狀態(tài)處理方法中可以傳入?yún)?shù)/失敗信息济蝉。當(dāng)操作成功完成時(shí)杰刽,Promise 對象的 then 方法就會被調(diào)用;否則就會觸發(fā) catch王滤。如:
const myFirstPromise = new Promise((resolve, reject) => {
setTimeout(function(){
resolve("成功!");
}, 250);
});
myFirstPromise.then((data) => {
console.log("Yay! " + data);
}).catch((e) => {...});
51. 什么是 async/await 及其如何工作贺嫂?
async/await是 JS 中編寫異步或非阻塞代碼的新方法。它建立在Promises之上雁乡,相對于 Promise 和回調(diào)第喳,它的可讀性和簡潔度都更高。但是踱稍,在使用此功能之前曲饱,我們必須先學(xué)習(xí)Promises的基礎(chǔ)知識,因?yàn)檎缥抑八f珠月,它是基于Promise構(gòu)建的扩淀,這意味著幕后使用仍然是Promise。
使用 Promise
function callApi() {
return fetch("url/to/api/endpoint")
.then(resp => resp.json())
.then(data => {
//do something with "data"
}).catch(err => {
//do something with "err"
});
}
使用async/await
在async/await中啤挎,我們使用 tru/catch 語法來捕獲異常驻谆。
async function callApi() {
try {
const resp = await fetch("url/to/api/endpoint");
const data = await resp.json();
//do something with "data"
} catch (e) {
//do something with "err"
}
}
注意: 使用 async關(guān)鍵聲明函數(shù)會隱式返回一個(gè)Promise。
const giveMeOne = async () => 1;
giveMeOne()
.then((num) => {
console.log(num); // logs 1
});
注意: await關(guān)鍵字只能在async function中使用侵浸。在任何非async function的函數(shù)中使用await關(guān)鍵字都會拋出錯(cuò)誤旺韭。await關(guān)鍵字在執(zhí)行下一行代碼之前等待右側(cè)表達(dá)式(可能是一個(gè)Promise)返回。
const giveMeOne = async () => 1;
function getOne() {
try {
const num = await giveMeOne();
console.log(num);
} catch (e) {
console.log(e);
}
}
// Uncaught SyntaxError: await is only valid in async function
async function getTwo() {
try {
const num1 = await giveMeOne(); // 這行會等待右側(cè)表達(dá)式執(zhí)行完成
const num2 = await giveMeOne();
return num1 + num2;
} catch (e) {
console.log(e);
}
}
await getTwo(); // 2
52. 展開(spread )運(yùn)算符和 剩余(Rest) 運(yùn)算符有什么區(qū)別掏觉?
展開運(yùn)算符(spread)是三個(gè)點(diǎn)(...)区端,可以將一個(gè)數(shù)組轉(zhuǎn)為用逗號分隔的參數(shù)序列。說的通俗易懂點(diǎn)澳腹,有點(diǎn)像化骨綿掌织盼,把一個(gè)大元素給打散成一個(gè)個(gè)單獨(dú)的小元素杨何。
剩余運(yùn)算符也是用三個(gè)點(diǎn)(...)表示,它的樣子看起來和展開操作符一樣沥邻,但是它是用于解構(gòu)數(shù)組和對象危虱。在某種程度上,剩余元素和展開元素相反唐全,展開元素會“展開”數(shù)組變成多個(gè)元素埃跷,剩余元素會收集多個(gè)元素和“壓縮”成一個(gè)單一的元素。
function add(a, b) {
return a + b;
};
const nums = [5, 6];
const sum = add(...nums);
console.log(sum);
在本例中邮利,我們在調(diào)用add函數(shù)時(shí)使用了展開操作符弥雹,對nums數(shù)組進(jìn)行展開。所以參數(shù)a的值是5 延届,b的值是6剪勿,所以sum 是11。
function add(...rest) {
return rest.reduce((total,current) => total + current);
};
console.log(add(1, 2)); // 3
console.log(add(1, 2, 3, 4, 5)); // 15
在本例中方庭,我們有一個(gè)add函數(shù)厕吉,它接受任意數(shù)量的參數(shù),并將它們?nèi)肯嗉有的睿缓蠓祷乜倲?shù)头朱。
const [first, ...others] = [1, 2, 3, 4, 5];
console.log(first); // 1
console.log(others); // [2,3,4,5]
這里,我們使用剩余操作符提取所有剩余的數(shù)組值龄减,并將它們放入除第一項(xiàng)之外的其他數(shù)組中髓窜。
53. 什么是默認(rèn)參數(shù)?
默認(rèn)參數(shù)是在 JS 中定義默認(rèn)變量的一種新方法欺殿,它在ES6或ECMAScript 2015版本中可用。
//ES5 Version
function add(a,b){
a = a || 0;
b = b || 0;
return a + b;
}
//ES6 Version
function add(a = 0, b = 0){
return a + b;
}
add(1); // returns 1
我們還可以在默認(rèn)參數(shù)中使用解構(gòu)鳖敷。
function getFirst([first, ...rest] = [0, 1]) {
return first;
}
getFirst(); // 0
getFirst([10,20,30]); // 10
function getArr({ nums } = { nums: [1, 2, 3, 4] }){
return nums;
}
getArr(); // [1, 2, 3, 4]
getArr({nums:[5,4,3,2,1]}); // [5,4,3,2,1]
我們還可以使用先定義的參數(shù)再定義它們之后的參數(shù)脖苏。
function doSomethingWithValue(value = "Hello World", callback = () => { console.log(value) }) {
callback();
}
doSomethingWithValue(); //"Hello World"
54. 什么是包裝對象(wrapper object)?
我們現(xiàn)在復(fù)習(xí)一下JS的數(shù)據(jù)類型定踱,JS數(shù)據(jù)類型被分為兩大類棍潘,基本類型和引用類型。
基本類型:Undefined,Null,Boolean,Number,String,Symbol,BigInt
引用類型:Object,Array,Date,RegExp等崖媚,說白了就是對象亦歉。
其中引用類型有方法和屬性,但是基本類型是沒有的畅哑,但我們經(jīng)常會看到下面的代碼:
let name = "marko";
console.log(typeof name); // "string"
console.log(name.toUpperCase()); // "MARKO"
name類型是 string肴楷,屬于基本類型,所以它沒有屬性和方法荠呐,但是在這個(gè)例子中赛蔫,我們調(diào)用了一個(gè)toUpperCase()方法砂客,它不會拋出錯(cuò)誤,還返回了對象的變量值呵恢。
原因是基本類型的值被臨時(shí)轉(zhuǎn)換或強(qiáng)制轉(zhuǎn)換為對象鞠值,因此name變量的行為類似于對象。除null和undefined之外的每個(gè)基本類型都有自己包裝對象渗钉。也就是:String彤恶,Number,Boolean鳄橘,Symbol和BigInt声离。在這種情況下,name.toUpperCase()在幕后看起來如下:
console.log(new String(name).toUpperCase()); // "MARKO"
在完成訪問屬性或調(diào)用方法之后挥唠,新創(chuàng)建的對象將立即被丟棄抵恋。
55. 隱式和顯式轉(zhuǎn)換有什么區(qū)別)?
隱式強(qiáng)制轉(zhuǎn)換是一種將值轉(zhuǎn)換為另一種類型的方法宝磨,這個(gè)過程是自動完成的弧关,無需我們手動操作。
假設(shè)我們下面有一個(gè)例子唤锉。
console.log(1 + '6'); // 16
console.log(false + true); // 1
console.log(6 * '2'); // 12
第一個(gè)console.log語句結(jié)果為16世囊。在其他語言中,這會拋出編譯時(shí)錯(cuò)誤窿祥,但在 JS 中株憾,1被轉(zhuǎn)換成字符串,然后與+運(yùn)算符連接晒衩。我們沒有做任何事情嗤瞎,它是由 JS 自動完成。
第二個(gè)console.log語句結(jié)果為1听系,JS 將false轉(zhuǎn)換為boolean 值為 0贝奇,,true為1,因此結(jié)果為1靠胜。
第三個(gè)console.log語句結(jié)果12掉瞳,它將'2'轉(zhuǎn)換為一個(gè)數(shù)字,然后乘以6 * 2浪漠,結(jié)果是12陕习。
而顯式強(qiáng)制是將值轉(zhuǎn)換為另一種類型的方法,我們需要手動轉(zhuǎn)換址愿。
console.log(1 + parseInt('6'));
在本例中该镣,我們使用parseInt函數(shù)將'6'轉(zhuǎn)換為number ,然后使用+運(yùn)算符將1和6相加响谓。
56. 什么是NaN拌牲?以及如何檢查值是否為NaN俱饿?
NaN表示“非數(shù)字”是 JS 中的一個(gè)值,該值是將數(shù)字轉(zhuǎn)換或執(zhí)行為非數(shù)字值的運(yùn)算結(jié)果塌忽,因此結(jié)果為NaN拍埠。
let a;
console.log(parseInt('abc')); // NaN
console.log(parseInt(null)); // NaN
console.log(parseInt(undefined)); // NaN
console.log(parseInt(++a)); // NaN
console.log(parseInt({} * 10)); // NaN
console.log(parseInt('abc' - 2)); // NaN
console.log(parseInt(0 / 0)); // NaN
console.log(parseInt('10a' * 10)); // NaN
JS 有一個(gè)內(nèi)置的isNaN方法,用于測試值是否為isNaN值土居,但是這個(gè)函數(shù)有一個(gè)奇怪的行為枣购。
console.log(isNaN()); // true
console.log(isNaN(undefined)); // true
console.log(isNaN({})); // true
console.log(isNaN(String('a'))); // true
console.log(isNaN(() => { })); // true
所有這些console.log語句都返回true,即使我們傳遞的值不是NaN擦耀。
在ES6中棉圈,建議使用Number.isNaN方法,因?yàn)樗_實(shí)會檢查該值(如果確實(shí)是NaN)眷蜓,或者我們可以使自己的輔助函數(shù)檢查此問題分瘾,因?yàn)樵?JS 中,NaN是唯一的值吁系,它不等于自己德召。
function checkIfNaN(value) {
return value !== value;
}
57. 如何判斷值是否為數(shù)組?
我們可以使用Array.isArray方法來檢查值是否為數(shù)組汽纤。當(dāng)傳遞給它的參數(shù)是數(shù)組時(shí)上岗,它返回true,否則返回false蕴坪。
console.log(Array.isArray(5)); // false
console.log(Array.isArray("")); // false
console.log(Array.isArray()); // false
console.log(Array.isArray(null)); // false
console.log(Array.isArray({ length: 5 })); // false
console.log(Array.isArray([])); // true
如果環(huán)境不支持此方法肴掷,則可以使用polyfill實(shí)現(xiàn)。
function isArray(value){
return Object.prototype.toString.call(value) === "[object Array]"
}
當(dāng)然還可以使用傳統(tǒng)的方法:
let a = []
if (a instanceof Array) {
console.log('是數(shù)組')
} else {
console.log('非數(shù)組')
}
58. 如何在不使用%模運(yùn)算符的情況下檢查一個(gè)數(shù)字是否是偶數(shù)背传?
我們可以對這個(gè)問題使用按位&運(yùn)算符呆瞻,&對其操作數(shù)進(jìn)行運(yùn)算,并將其視為二進(jìn)制值径玖,然后執(zhí)行與運(yùn)算栋烤。
function isEven(num) {
if (num & 1) {
return false
} else {
return true
}
}
0 二進(jìn)制數(shù)是 000
1 二進(jìn)制數(shù)是 001
2 二進(jìn)制數(shù)是 010
3 二進(jìn)制數(shù)是 011
4 二進(jìn)制數(shù)是 100
5 二進(jìn)制數(shù)是 101
6 二進(jìn)制數(shù)是 110
7 二進(jìn)制數(shù)是 111
以此類推...
與運(yùn)算的規(guī)則如下:
a | b | a & b |
---|---|---|
0 | 0 | 0 |
0 | 1 | 0 |
1 | 1 | 1 |
因此,當(dāng)我們執(zhí)行console.log(5&1)這個(gè)表達(dá)式時(shí)挺狰,結(jié)果為1。首先买窟,&運(yùn)算符將兩個(gè)數(shù)字都轉(zhuǎn)換為二進(jìn)制丰泊,因此5變?yōu)?01,1變?yōu)?01始绍。
然后瞳购,它使用按位懷運(yùn)算符比較每個(gè)位(0和1)。 101&001亏推,從表中可以看出学赛,如果a & b為1年堆,所以5&1結(jié)果為1。
101 & 001
101
001
001
首先我們比較最左邊的1&0盏浇,結(jié)果是0变丧。
然后我們比較中間的0&0,結(jié)果是0绢掰。
然后我們比較最后1&1痒蓬,結(jié)果是1。
最后滴劲,得到一個(gè)二進(jìn)制數(shù)001攻晒,對應(yīng)的十進(jìn)制數(shù),即1班挖。
由此我們也可以算出console.log(4 & 1) 結(jié)果為0鲁捏。知道4的最后一位是0,而0 & 1 將是0萧芙。如果你很難理解這一點(diǎn)给梅,我們可以使用遞歸函數(shù)來解決此問題。
function isEven(num) {
if (num < 0 || num === 1) return false;
if (num == 0) return true;
return isEven(num - 2);
}
59. 如何檢查對象中是否存在某個(gè)屬性末购?
檢查對象中是否存在屬性有三種方法破喻。
第一種使用 in 操作符號:
const o = {
"prop" : "bwahahah",
"prop2" : "hweasa"
};
console.log("prop" in o); // true
console.log("prop1" in o); // false
第二種使用 hasOwnProperty 方法,hasOwnProperty() 方法會返回一個(gè)布爾值盟榴,指示對象自身屬性中是否具有指定的屬性(也就是曹质,是否有指定的鍵)。
console.log(o.hasOwnProperty("prop2")); // true
console.log(o.hasOwnProperty("prop1")); // false
第三種使用括號符號obj["prop"]擎场。如果屬性存在羽德,它將返回該屬性的值,否則將返回undefined迅办。
console.log(o["prop"]); // "bwahahah"
console.log(o["prop1"]); // undefined
60. AJAX 是什么宅静?
即異步的 JavaScript 和 XML,是一種用于創(chuàng)建快速動態(tài)網(wǎng)頁的技術(shù)站欺,傳統(tǒng)的網(wǎng)頁(不使用 AJAX)如果需要更新內(nèi)容姨夹,必需重載整個(gè)網(wǎng)頁面。使用AJAX則不需要加載更新整個(gè)網(wǎng)頁矾策,實(shí)現(xiàn)部分內(nèi)容更新
用到AJAX的技術(shù):
- HTML - 網(wǎng)頁結(jié)構(gòu)
- CSS - 網(wǎng)頁的樣式
- JavaScript - 操作網(wǎng)頁的行為和更新DOM
- XMLHttpRequest API - 用于從服務(wù)器發(fā)送和獲取數(shù)據(jù)
- PHP磷账,Python,Nodejs - 某些服務(wù)器端語言
61. 如何在 JS 中創(chuàng)建對象贾虽?
使用對象字面量:
const o = {
name: "Mark",
greeting() {
return `Hi, I'm ${this.name}`;
}
};
o.greeting(); //returns "Hi, I'm Mark"
使用構(gòu)造函數(shù):
function Person(name) {
this.name = name;
}
Person.prototype.greeting = function () {
return `Hi, I'm ${this.name}`;
}
const mark = new Person("Mark");
mark.greeting(); //returns "Hi, I'm Mark"
使用 Object.create 方法:
const n = {
greeting() {
return `Hi, I'm ${this.name}`;
}
};
const o = Object.create(n); // sets the prototype of "o" to be "n"
o.name = "Mark";
console.log(o.greeting()); // logs "Hi, I'm Mark"
62. Object.seal 和 Object.freeze 方法之間有什么區(qū)別逃糟?
這兩種方法之間的區(qū)別在于,當(dāng)我們對一個(gè)對象使用Object.freeze方法時(shí),該對象的屬性是不可變的绰咽,這意味著我們不能更改或編輯這些屬性的值菇肃。而在Obj.Engor方法中,我們可以改變現(xiàn)有的屬性取募。
Object.freeze()
Object.freeze() 方法可以凍結(jié)一個(gè)對象琐谤。一個(gè)被凍結(jié)的對象再也不能被修改;凍結(jié)了一個(gè)對象則不能向這個(gè)對象添加新的屬性矛辕,不能刪除已有屬性笑跛,不能修改該對象已有屬性的可枚舉性、可配置性聊品、可寫性飞蹂,以及不能修改已有屬性的值。此外翻屈,凍結(jié)一個(gè)對象后該對象的原型也不能被修改陈哑。freeze() 返回和傳入的參數(shù)相同的對象。
Object.seal()
Object.seal()方法封閉一個(gè)對象伸眶,阻止添加新屬性并將所有現(xiàn)有屬性標(biāo)記為不可配置惊窖。當(dāng)前屬性的值只要可寫就可以改變。
方法的相同點(diǎn):
ES5新增厘贼。
對象不可能擴(kuò)展界酒,也就是不能再添加新的屬性或者方法。
對象已有屬性不允許被刪除嘴秸。
對象屬性特性不可以重新配置毁欣。
方法不同點(diǎn):
Object.seal方法生成的密封對象,如果屬性是可寫的岳掐,那么可以修改屬性值凭疮。
Object.freeze方法生成的凍結(jié)對象,屬性都是不可寫的串述,也就是屬性值無法更改执解。
63. in 運(yùn)算符和 Object.hasOwnProperty 方法有什么區(qū)別?
如你所知纲酗,這兩個(gè)特性都檢查對象中是否存在屬性衰腌,它將返回true或false。它們之間的區(qū)別在于觅赊,in操作符還會檢查對象的原型鏈右蕊,如果屬性在當(dāng)前對象中沒有找到,而hasOwnProperty方法只檢查屬性是否存在于當(dāng)前對象中茉兰,而忽略原型鏈。
hasOwnPropert方法
hasOwnPropert()方法返回值是一個(gè)布爾值欣簇,指示對象自身屬性中是否具有指定的屬性规脸,因此這個(gè)方法會忽略掉那些從原型鏈上繼承到的屬性坯约。
看下面的例子:
Object.prototype.phone= '15345025546';
let obj = {
name: '西門大官人',
age: '28'
}
console.log(obj.hasOwnProperty('phone')) // false
console.log(obj.hasOwnProperty('name')) // true
可以看到,如果在函數(shù)原型上定義一個(gè)變量phone莫鸭,hasOwnProperty方法會直接忽略掉闹丐。
in 運(yùn)算符
如果指定的屬性在指定的對象或其原型鏈中,則in 運(yùn)算符返回true被因。
還是用上面的例子來演示:
console.log('phone' in obj) // true
可以看到in運(yùn)算符會檢查它或者其原型鏈?zhǔn)欠癜哂兄付Q的屬性卿拴。
64. 有哪些方法可以處理 JS 中的異步代碼?
- 回調(diào)
- Promise
- async/await
還有一些庫:async.js, bluebird, q, co
65. 函數(shù)表達(dá)式和函數(shù)聲明之間有什么區(qū)別?
看下面的例子:
hoistedFunc();
notHoistedFunc();
function hoistedFunc(){
console.log("注意:我會被提升");
}
var notHoistedFunc = function(){
console.log("注意:我沒有被提升");
}
notHoistedFunc調(diào)用拋出異常:Uncaught TypeError: notHoistedFunc is not a function,而hoistedFunc調(diào)用不會给郊,因?yàn)閔oistedFunc會被提升到作用域的頂部他托,而notHoistedFunc 不會。
66. 調(diào)用函數(shù)北滥,可以使用哪些方法?
在 JS 中有4種方法可以調(diào)用函數(shù)。
作為函數(shù)調(diào)用——如果一個(gè)函數(shù)沒有作為方法壕曼、構(gòu)造函數(shù)、apply等浊、call 調(diào)用時(shí)腮郊,此時(shí) this 指向的是 window 對象(非嚴(yán)格模式)
//Global Scope
function add(a,b){
console.log(this);
return a + b;
}
add(1,5); // 打印 "window" 對象和 6
const o = {
method(callback){
callback();
}
}
o.method(function (){
console.log(this); // 打印 "window" 對象
});
作為方法調(diào)用——如果一個(gè)對象的屬性有一個(gè)函數(shù)的值,我們就稱它為方法筹燕。調(diào)用該方法時(shí)轧飞,該方法的this值指向該對象。
const details = {
name : "Marko",
getName(){
return this.name;
}
}
details.getName(); // Marko
// the "this" value inside "getName" method will be the "details" object
作為構(gòu)造函數(shù)的調(diào)用-如果在函數(shù)之前使用new關(guān)鍵字調(diào)用了函數(shù)庄萎,則該函數(shù)稱為構(gòu)造函數(shù)踪少。構(gòu)造函數(shù)里面會默認(rèn)創(chuàng)建一個(gè)空對象,并將this指向該對象糠涛。
function Employee(name, position, yearHired) {
// creates an empty object {}
// then assigns the empty object to the "this" keyword
// this = {};
this.name = name;
this.position = position;
this.yearHired = yearHired;
// inherits from Employee.prototype
// returns the "this" value implicitly if no
// explicit return statement is specified
};
const emp = new Employee("Marko Polo", "Software Developer", 2017);
使用apply和call方法調(diào)用——如果我們想顯式地指定一個(gè)函數(shù)的this值援奢,我們可以使用這些方法,這些方法對所有函數(shù)都可用忍捡。
const obj1 = {
result:0
};
const obj2 = {
result:0
};
function reduceAdd(){
let result = 0;
for(let i = 0, len = arguments.length; i < len; i++){
result += arguments[i];
}
this.result = result;
}
reduceAdd.apply(obj1, [1, 2, 3, 4, 5]); // reduceAdd 函數(shù)中的 this 對象將是 obj1
reduceAdd.call(obj2, 1, 2, 3, 4, 5); // reduceAdd 函數(shù)中的 this 對象將是 obj2
67. 什么是緩存及它有什么作用集漾?
緩存是建立一個(gè)函數(shù)的過程,這個(gè)函數(shù)能夠記住之前計(jì)算的結(jié)果或值砸脊。使用緩存函數(shù)是為了避免在最后一次使用相同參數(shù)的計(jì)算中已經(jīng)執(zhí)行的函數(shù)的計(jì)算具篇。這節(jié)省了時(shí)間,但也有不利的一面凌埂,即我們將消耗更多的內(nèi)存來保存以前的結(jié)果驱显。
68. 手動實(shí)現(xiàn)緩存方法
function memoize(fn) {
const cache = {};
return function (param) {
if (cache[param]) {
console.log('cached');
return cache[param];
} else {
let result = fn(param);
cache[param] = result;
console.log(`not cached`);
return result;
}
}
}
const toUpper = (str ="")=> str.toUpperCase();
const toUpperMemoized = memoize(toUpper);
toUpperMemoized("abcdef");
toUpperMemoized("abcdef");
這個(gè)緩存函數(shù)適用于接受一個(gè)參數(shù)。我們需要改變下,讓它接受多個(gè)參數(shù)埃疫。
const slice = Array.prototype.slice;
function memoize(fn) {
const cache = {};
return (...args) => {
const params = slice.call(args);
console.log(params);
if (cache[params]) {
console.log('cached');
return cache[params];
} else {
let result = fn(...args);
cache[params] = result;
console.log(`not cached`);
return result;
}
}
}
const makeFullName = (fName, lName) => `${fName} ${lName}`;
const reduceAdd = (numbers, startingValue = 0) =>
numbers.reduce((total, cur) => total + cur, startingValue);
const memoizedMakeFullName = memoize(makeFullName);
const memoizedReduceAdd = memoize(reduceAdd);
memoizedMakeFullName("Marko", "Polo");
memoizedMakeFullName("Marko", "Polo");
memoizedReduceAdd([1, 2, 3, 4, 5], 5);
memoizedReduceAdd([1, 2, 3, 4, 5], 5);
69. 為什么typeof null 返回 object伏恐?如何檢查一個(gè)值是否為 null?
typeof null == 'object'總是返回true栓霜,因?yàn)檫@是自 JS 誕生以來null的實(shí)現(xiàn)翠桦。曾經(jīng)有人提出將typeof null == 'object'修改為typeof null == 'null',但是被拒絕了胳蛮,因?yàn)檫@將導(dǎo)致更多的bug销凑。
我們可以使用嚴(yán)格相等運(yùn)算符===來檢查值是否為null。
function isNull(value){
return value === null;
}
70. new 關(guān)鍵字有什么作用仅炊?
new關(guān)鍵字與構(gòu)造函數(shù)一起使用以創(chuàng)建對象在JavaScript中斗幼。
下面看看例子:
function Employee(name, position, yearHired) {
this.name = name;
this.position = position;
this.yearHired = yearHired;
};
const emp = new Employee("Marko Polo", "Software Developer", 2017);
- new關(guān)鍵字做了4件事:
- 創(chuàng)建空對象 {}
- 將空對象分配給 this 值
- 將空對象的proto指向構(gòu)造函數(shù)的prototype
- 如果沒有使用顯式return語句,則返回this
根據(jù)上面描述的茂洒,它將首先創(chuàng)建一個(gè)空對象{}孟岛,然后它將this值賦給這個(gè)空對象this={},并向這個(gè)對象添加屬性督勺。因?yàn)槲覀儧]有顯式的return語句渠羞,所以它會自動為我們返回this。