日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

70道關(guān)于JavaScript的常見面試題解答

 板橋胡同37號 2020-01-30

1.undefined 和 null 有什么區(qū)別?

在理解undefinednull之間的差異之前,我們先來看看它們的相似類。
它們屬于 JavaScript 的 7 種基本類型。
let primitiveTypes = ['string','number','null','undefined','boolean','symbol', 'bigint'];
它們是屬于虛值,可以使用Boolean(value)!!value將其轉(zhuǎn)換為布爾值時(shí),值為false。
console.log(!!null); // falseconsole.log(!!undefined); // false
console.log(Boolean(null)); // falseconsole.log(Boolean(undefined)); // false
接著來看看它們的區(qū)別。
undefined是未指定特定值的變量的默認(rèn)值,或者沒有顯式返回值的函數(shù),如:console.log(1),還包括對象中不存在的屬性,這些 JS 引擎都會為其分配 undefined 值。
let _thisIsUndefined;const doNothing = () => {};const someObj = { a : 'ay', b : 'bee', c : 'si'};
console.log(_thisIsUndefined); // undefinedconsole.log(doNothing()); // undefinedconsole.log(someObj['d']); // undefined
null“不代表任何值的值”。 null是已明確定義給變量的值。在此示例中,當(dāng)fs.readFile方法未引發(fā)錯(cuò)誤時(shí),我們將獲得null值。
fs.readFile('path/to/file', (e,data) => { console.log(e); // 當(dāng)沒有錯(cuò)誤發(fā)生時(shí),打印 null if(e){ console.log(e); } console.log(data); });
在比較nullundefined時(shí),我們使用==時(shí)得到true,使用===時(shí)得到false:
console.log(null == undefined); // true console.log(null === undefined); // false

2. && 運(yùn)算符能做什么

&& 也可以叫邏輯與,在其操作數(shù)中找到第一個(gè)虛值表達(dá)式并返回它,如果沒有找到任何虛值表達(dá)式,則返回最后一個(gè)真值表達(dá)式。它采用短路來防止不必要的工作。
console.log(false && 1 && []); // falseconsole.log(' ' && true && 5); // 5
使用if語句
const router: Router = Router();
router.get('/endpoint', (req: Request, res: Response) => { let conMobile: PoolConnection; try { //do some db operations } catch (e) { if (conMobile) { conMobile.release(); } }});
使用&&操作符
const router: Router = Router();
router.get('/endpoint', (req: Request, res: Response) => { let conMobile: PoolConnection; try { //do some db operations } catch (e) { conMobile && conMobile.release() }});

3. || 運(yùn)算符能做什么

||也叫或邏輯或,在其操作數(shù)中找到第一個(gè)真值表達(dá)式并返回它。這也使用了短路來防止不必要的工作。在支持 ES6 默認(rèn)函數(shù)參數(shù)之前,它用于初始化函數(shù)中的默認(rèn)參數(shù)值。
console.log(null || 1 || undefined); // 1
function logName(name) { var n = name || 'Mark'; console.log(n);}
logName(); // 'Mark'

4. 使用 + 或一元加運(yùn)算符是將字符串轉(zhuǎn)換為數(shù)字的最快方法嗎?

根據(jù)MDN文檔,+是將字符串轉(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)。
假設(shè)我們有這樣的 HTML 結(jié)構(gòu):
<!DOCTYPE html><html lang='en'>
<head> <meta charset='UTF-8'> <meta name='viewport' content='width=device-width, initial-scale=1.0'> <meta http-equiv='X-UA-Compatible' content='ie=edge'> <title>Document Object Model</title></head>
<body> <div> <p> <span></span> </p> <label></label> <input> </div></body>
</html>
等價(jià)的DOM是這樣的:

JS 中的document對象表示DOM。它為我們提供了許多方法,我們可以使用這些方法來選擇元素來更新元素內(nèi)容,等等。

6. 什么是事件傳播?

當(dāng)事件發(fā)生在DOM元素上時(shí),該事件并不完全發(fā)生在那個(gè)元素上。在“冒泡階段”中,事件冒泡或向上傳播至父級,祖父母,祖父母或父級,直到到達(dá)window為止;而在“捕獲階段”中,事件從window開始向下觸發(fā)元素 事件或event.target。
事件傳播有三個(gè)階段:
  1. 捕獲階段–事件從 window 開始,然后向下到每個(gè)元素,直到到達(dá)目標(biāo)元素。

  2. 目標(biāo)階段–事件已達(dá)到目標(biāo)元素。

  3. 冒泡階段–事件從目標(biāo)元素冒泡,然后上升到每個(gè)元素,直到到達(dá) window。

7. 什么是事件冒泡?

當(dāng)事件發(fā)生在DOM元素上時(shí),該事件并不完全發(fā)生在那個(gè)元素上。在冒泡階段,事件冒泡,或者事件發(fā)生在它的父代,祖父母,祖父母的父代,直到到達(dá)window為止。
假設(shè)有如下的 HTML 結(jié)構(gòu):
<div class='grandparent'> <div class='parent'> <div class='child'>1</div> </div></div>
對應(yīng)的 JS 代碼:
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'); })
});
addEventListener方法具有第三個(gè)可選參數(shù)useCapture,其默認(rèn)值為false,事件將在冒泡階段中發(fā)生,如果為true,則事件將在捕獲階段中發(fā)生。如果單擊child元素,它將分別在控制臺上記錄child,parentgrandparent,htmldocumentwindow,這就是事件冒泡。

8. 什么是事件捕獲?

當(dāng)事件發(fā)生在 DOM 元素上時(shí),該事件并不完全發(fā)生在那個(gè)元素上。在捕獲階段,事件從window開始,一直到觸發(fā)事件的元素。
假設(shè)有如下的 HTML 結(jié)構(gòu):
<div class='grandparent'> <div class='parent'> <div class='child'>1</div> </div></div>
對應(yīng)的 JS 代碼:
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'); })
});
addEventListener方法具有第三個(gè)可選參數(shù)useCapture,其默認(rèn)值為false,事件將在冒泡階段中發(fā)生,如果為true,則事件將在捕獲階段中發(fā)生。如果單擊child元素,它將分別在控制臺上打印window,document,html,grandparentparent,這就是事件捕獲。

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)?/span>undefined沒有屬性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>
JS 代碼如下:
function clickFunc(event) { console.log(event.target);}
如果單擊 button,即使我們將事件附加在最外面的div上,它也將打印 button 標(biāo)簽,因此我們可以得出結(jié)論event.target是觸發(fā)事件的元素。

13. 什么是 event.currentTarget??

event.currentTarget是我們在其上顯式附加事件處理程序的元素。
假設(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>
JS 代碼如下:
function clickFunc(event) { console.log(event.currentTarget);}
如果單擊 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的值。
  1. 如果xy的類型相同,則 JS 會換成===操作符進(jìn)行比較。

  2. 如果xnullyundefined,則返回true。

  3. 如果xundefinedynull,則返回true。

  4. 如果x的類型是numbery的類型是string,那么返回x == toNumber(y)

  5. 如果x的類型是stringy的類型是number,那么返回toNumber(x) == y

  6. 如果x為類型是boolean,則返回toNumber(x)== y。

  7. 如果y為類型是boolean,則返回x == toNumber(y)。

  8. 如果xstring、symbolnumber,而yobject類型,則返回x == toPrimitive(y)。

  9. 如果xobject,ystringsymbol 則返回toPrimitive(x) == y。

  10. 剩下的 返回 false

注意:toPrimitive首先在對象中使用valueOf方法,然后使用toString方法來獲取該對象的原始值。
舉個(gè)例子。
xy
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)?/span>xy具有相同的類型和值。
第二個(gè)示例符合條件4,在比較之前將y轉(zhuǎn)換為數(shù)字。
第三個(gè)例子符合條件2。
第四個(gè)例子符合條件7,因?yàn)?/span>yboolean類型。
第五個(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)閮烧叩念愋秃椭迪嗤?/span>

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。ac有相同的引用地址,而ab沒有。

16. !! 運(yùn)算符能做什么?

!!運(yùn)算符可以將右側(cè)的值強(qiáng)制轉(zhuǎn)換為布爾值,這也是將值轉(zhuǎn)換為布爾值的一種簡單方法。
console.log(!!null); // falseconsole.log(!!undefined); // falseconsole.log(!!''); // falseconsole.log(!!0); // falseconsole.log(!!NaN); // falseconsole.log(!!' '); // trueconsole.log(!!{}); // trueconsole.log(!![]); // trueconsole.log(!!1); // trueconsole.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并將其分配給xx的更新值為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ù),letconst聲明的變量,這些都不會被提升。
假設(shè)在全局使用域,有如下的代碼:
console.log(y);y = 1;console.log(y);console.log(greet('Mark'));
function greet(name){ return 'Hello ' + name + '!';}
var y;
上面分別打?。?/span>undefined,1Hello 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'));

19. 什么是作用域?

JavaScript 中的作用域是我們可以有效訪問變量或函數(shù)的區(qū)域。JS 有三種類型的作用域:全局作用域、函數(shù)作用域塊作用域(ES6)
  • 全局作用域——在全局命名空間中聲明的變量或函數(shù)位于全局作用域中,因此在代碼中的任何地方都可以訪問它們。

//global namespacevar g = 'global';
function globalFunc(){ function innerFunc(){ console.log(g); // can access 'g' because 'g' is a global variable } 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); // Throws a ReferenceError 'a' is not definedconsole.log(b); // does not continue here
  • 塊作用域-在塊{}中聲明的變量(let,const)只能在其中訪問。

function testBlock(){ if(true){ let z = 5; } return z; }
testBlock(); // Throws a ReferenceError '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ù)?,如果不妥,多多海涵?/span>
閉包就是一個(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閉包的一部分。

變量globalVar在圖中沒有值的原因是該變量的值可以根據(jù)調(diào)用函數(shù)a的位置和時(shí)間而改變。但是在上面的示例中,globalVar變量的值為abc。
來看一個(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)?/span>outerVar變量分配了新值outer-2outerParam也繼續(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)用引用了innerFuncx變量時(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)槲覀兊玫?/span>i的當(dāng)前值為5,我們可以訪問它,因?yàn)樗侨肿兞俊?/span>
因?yàn)殚]包在創(chuàng)建變量時(shí)會保留該變量的引用而不是其值。我們可以使用IIFES或使用 let 來代替 var 的聲明。

21. JavaScript 中的虛值是什么?

const falsyValues = ['', 0, null, undefined, NaN, false];
簡單的來說虛值就是是在轉(zhuǎn)換為布爾值時(shí)變?yōu)?nbsp;false 的值。

22. 如何檢查值是否虛值?

使用 Boolean 函數(shù)或者 !! 運(yùn)算符。

23. 'use strict' 是干嘛用的?

'use strict' 是 ES5 特性,它使我們的代碼在函數(shù)或整個(gè)腳本中處于嚴(yán)格模式。嚴(yán)格模式幫助我們在代碼的早期避免 bug,并為其添加限制。
嚴(yán)格模式的一些限制:
  1. 變量必須聲明后再使用

  2. 函數(shù)的參數(shù)不能有同名屬性,否則報(bào)錯(cuò)

  3. 不能使用with語句

  4. 不能對只讀屬性賦值,否則報(bào)錯(cuò)

  5. 不能使用前綴 0 表示八進(jìn)制數(shù),否則報(bào)錯(cuò)

  6. 不能刪除不可刪除的屬性,否則報(bào)錯(cuò)

  7. 不能刪除變量delete prop,會報(bào)錯(cuò),只能刪除屬性delete global[prop]

  8. eval不能在它的外層作用域引入變量

  9. evalarguments不能被重新賦值

  10. arguments不會自動反映函數(shù)參數(shù)的變化

  11. 不能使用arguments.callee

  12. 不能使用arguments.caller

  13. 禁止this指向全局對象

  14. 不能使用fn.callerfn.arguments獲取函數(shù)調(diào)用的堆棧

  15. 增加了保留字(比如protected、staticinterface


設(shè)立”嚴(yán)格模式”的目的,主要有以下幾個(gè):
  1. 消除Javascript語法的一些不合理、不嚴(yán)謹(jǐn)之處,減少一些怪異行為;

  2. 消除代碼運(yùn)行的一些不安全之處,保證代碼運(yùn)行的安全;

  3. 提高編譯器效率,增加運(yùn)行速度;

  4. 為未來新版本的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)樵?/span>getName方法中我們返回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); // trueconsole.log(getCarName === this.getCarName); // true
本例中的thiswindow引用同一個(gè)對象。
解決這個(gè)問題的一種方法是在函數(shù)中使用applycall方法。
console.log(getCarName.apply(carDetails)); // Ford Mustangconsole.log(getCarName.call(carDetails)); // Ford Mustang
applycall方法期望第一個(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()); // logs [object Object]
即使o對象中不存在o.toString方法,它也不會引發(fā)錯(cuò)誤,而是返回字符串[object Object]。當(dāng)對象中不存在屬性時(shí),它將查看其原型,如果仍然不存在,則將其查找到原型的原型,依此類推,直到在原型鏈中找到具有相同屬性的屬性為止。原型鏈的末尾是Object.prototype。
console.log(o.toString === Object.prototype.toString); // logs true// which means we we're looking up the Prototype Chain and it reached// the Object.prototype and used the 'toString' method.

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:///somelibrary.js'></script>
假設(shè)我們引入了一個(gè)omelibr.js的鏈接,它提供了一些我們在代碼中使用的全局函數(shù),但是這個(gè)庫有兩個(gè)方法我們沒有使用:createGraphdrawGraph,因?yàn)檫@些方法都有bug。我們想實(shí)現(xiàn)自己的createGraphdrawGraph方法。
解決此問題的一種方法是直接覆蓋:
<script src='https:///somelibrary.js'></script><script> function createGraph() { // createGraph logic here } function drawGraph() { // drawGraph logic here }</script>
當(dāng)我們使用這個(gè)解決方案時(shí),我們覆蓋了庫提供給我們的那兩個(gè)方法。
另一種方式是我們自己改名稱:
<script src='https:///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:///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è)方法createGraphdrawGraph的對象。
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è)帶有list-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)?span>IIFE會為每次迭代創(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]); // 15reduceAdd.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ù)式編程(通??s寫為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、filtermap。
我們可以使用Array.prototype.slicearguments對象轉(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,letconst的區(qū)別是什么?

var聲明的變量會掛載在window上,而letconst聲明的變量不會:
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聲明變量存在變量提升,letconst不存在變量提升:
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;
letconst聲明形成塊作用域
if(1){ var a = 100; let b = 10;}
console.log(a); // 100console.log(b) // 報(bào)錯(cuò):b is not defined ===> 找不到b這個(gè)變量
-------------------------------------------------------------
if(1){ var a = 100; const c = 1;}console.log(a); // 100console.log(c) // 報(bào)錯(cuò):c is not defined ===> 找不到c這個(gè)變量
同一作用域下letconst不能聲明同名變量,而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,supernew.target。箭頭函數(shù)表達(dá)式更適用于那些本來需要匿名函數(shù)的地方,并且它不能用作構(gòu)造函數(shù)。
//ES5 Versionvar getCurrentDate = function (){ return new Date();}
//ES6 Versionconst getCurrentDate = () => new Date();
在本例中,ES5 版本中有function(){}聲明和return關(guān)鍵字,這兩個(gè)關(guān)鍵字分別是創(chuàng)建函數(shù)和返回值所需要的。在箭頭函數(shù)版本中,我們只需要()括號,不需要 return 語句,因?yàn)槿绻覀冎挥幸粋€(gè)表達(dá)式或值需要返回,箭頭函數(shù)就會有一個(gè)隱式的返回。
//ES5 Versionfunction greet(name) { return 'Hello ' + name + '!';}
//ES6 Versionconst 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 VersionEmployee.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 Versionclass 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); // trueconsole.log(s.toString === Object.prototype.toString); // true

45. 什么是模板字符串?

模板字符串是在 JS 中創(chuàng)建字符串的一種新方法。我們可以通過使用反引號使模板字符串化。
//ES5 Versionvar greet = 'Hi I\'m Mark';
//ES6 Versionlet greet = `Hi I'm Mark`;
在 ES5 中我們需要使用一些轉(zhuǎn)義字符來達(dá)到多行的效果,在模板字符串不需要這么麻煩:
//ES5 Versionvar lastWords = '\n' + ' I \n' + ' Am \n' + 'Iron Man \n';

//ES6 Versionlet lastWords = ` I Am Iron Man`;
在ES5版本中,我們需要添加\n以在字符串中添加新行。在模板字符串中,我們不需要這樣做。
//ES5 Versionfunction greet(name) { return 'Hello ' + name + '!';}

//ES6 Versionfunction 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.jsexports.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.jsexport 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.jsconst helpers = require('./helpers.js'); // helpers is an objectconst isNull = helpers.isNull;const isUndefined = helpers.isUndefined;const isNullOrUndefined = helpers.isNullOrUndefined;
// or if your environment supports Destructuringconst { isNull, isUndefined, isNullOrUndefined } = require('./helpers.js');-------------------------------------------------------
// ES6 Modules - index.jsimport * 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.jsclass 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.jsclass 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.jsconst 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)?/span>add方法返回Set對象,所以我們可以以鏈?zhǔn)降姆绞皆俅问褂?/span>add。如果一個(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') // trueset2.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});
在本例中,我們等待idbtnAdd的元素中的click事件,如果它被單擊,則執(zhí)行clickCallback函數(shù)?;卣{(diào)函數(shù)向某些數(shù)據(jù)或事件添加一些功能。
數(shù)組中的reduce、filtermap方法需要一個(gè)回調(diào)作為參數(shù)?;卣{(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之上,讓異步代碼的可讀性和簡潔度都更高。
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)); // 3console.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); // 1console.log(others); // [2,3,4,5]
這里,我們使用剩余操作符提取所有剩余的數(shù)組值,并將它們放入除第一項(xiàng)之外的其他數(shù)組中。

53. 什么是默認(rèn)參數(shù)?

默認(rèn)參數(shù)是在 JS 中定義默認(rèn)變量的一種新方法,它在ES6或ECMAScript 2015版本中可用。
//ES5 Versionfunction add(a,b){ a = a || 0; b = b || 0; return a + b;}
//ES6 Versionfunction 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(); // 0getFirst([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變量的行為類似于對象。除nullundefined之外的每個(gè)基本類型都有自己包裝對象。也就是:String,Number,Boolean,SymbolBigInt。在這種情況下,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'); // 16console.log(false + true); // 1console.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,,true1,因此結(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)算符將16相加。

56. 什么是NaN?以及如何檢查值是否為NaN?

NaN表示“非數(shù)字”是 JS 中的一個(gè)值,該值是將數(shù)字轉(zhuǎn)換或執(zhí)行為非數(shù)字值的運(yùn)算結(jié)果,因此結(jié)果為NaN
let a;
console.log(parseInt('abc')); // NaNconsole.log(parseInt(null)); // NaNconsole.log(parseInt(undefined)); // NaNconsole.log(parseInt(++a)); // NaNconsole.log(parseInt({} * 10)); // NaNconsole.log(parseInt('abc' - 2)); // NaNconsole.log(parseInt(0 / 0)); // NaNconsole.log(parseInt('10a' * 10)); // NaN
JS 有一個(gè)內(nèi)置的isNaN方法,用于測試值是否為isNaN值,但是這個(gè)函數(shù)有一個(gè)奇怪的行為。
console.log(isNaN()); // trueconsole.log(isNaN(undefined)); // trueconsole.log(isNaN({})); // trueconsole.log(isNaN(String('a'))); // trueconsole.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)); // falseconsole.log(Array.isArray('')); // falseconsole.log(Array.isArray()); // falseconsole.log(Array.isArray(null)); // falseconsole.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)?/span>101,1變?yōu)?/span>001。
然后,它使用按位懷運(yùn)算符比較每個(gè)位(01)。 101&001,從表中可以看出,如果a & b1,所以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); // trueconsole.log('prop1' in o); // false
第二種使用 hasOwnProperty 方法,hasOwnProperty() 方法會返回一個(gè)布爾值,指示對象自身屬性中是否具有指定的屬性(也就是,是否有指定的鍵)。
console.log(o.hasOwnProperty('prop2')); // trueconsole.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):
  1. ES5新增。

  2. 對象不可能擴(kuò)展,也就是不能再添加新的屬性或者方法。

  3. 對象已有屬性不允許被刪除。

  4. 對象屬性特性不可以重新配置。


方法不同點(diǎn):
  • Object.seal方法生成的密封對象,如果屬性是可寫的,那么可以修改屬性值。

  • Object.freeze方法生成的凍結(jié)對象,屬性都是不可寫的,也就是屬性值無法更改。

63. in 運(yùn)算符和 Object.hasOwnProperty 方法有什么區(qū)別?

如你所知,這兩個(gè)特性都檢查對象中是否存在屬性,它將返回truefalse。它們之間的區(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')) // falseconsole.log(obj.hasOwnProperty('name')) // true
可以看到,如果在函數(shù)原型上定義一個(gè)變量phonehasOwnProperty方法會直接忽略掉。
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)?/span>hoistedFunc會被提升到作用域的頂部,而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);
  • 使用applycall方法調(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 對象將是 obj1reduceAdd.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。

    本站是提供個(gè)人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多