![70個JavaScript知識點詳細總結(上)【實踐】](http://p2.ttnews.xyz/loading.gif)
英文原文地址:https://dev.to/macmacky/70-javascript-interview-questions-5gfi#61-what-are-the-ways-of-making-objects-in-javascript
前面一段時間,小編也陸陸續續整理了不少的JavaSxript 知識點的相關文章,不知小夥們都學的怎麼樣了?咱們一起來回顧一下:
《 》
《 》
《 》
《 》
《 》
《 》
《 》
《 》
好了,廢話少說,進入正題:
1.undefined 和 null 有什麼區別?
在理解undefined和null之間的差異之前,我們先來看看它們的相似類。
它們屬於 JavaScript 的 7 種基本類型。
<code> let primitiveTypes = ['string','number','null','undefined','boolean','symbol', 'bigint'];/<code>
它們是屬於虛值,可以使用Boolean(value)或!!value將其轉換為布爾值時,值為false。
<code>console.log(!!null); // false
console.log(!!undefined); // false
console.log(Boolean(null)); // false
console.log(Boolean(undefined)); // false/<code>
接著來看看它們的區別。
undefined是未指定特定值的變量的默認值,或者沒有顯式返回值的函數,如:console.log(1),還包括對象中不存在的屬性,這些 JS 引擎都會為其分配 undefined 值。
<code>let _thisIsUndefined;
const doNothing = () => {};
const someObj = {
a : "ay",
b : "bee",
c : "si"
};
console.log(_thisIsUndefined); // undefined
console.log(doNothing()); // undefined
console.log(someObj["d"]); // undefined/<code>
null是“不代表任何值的值”。 null是已明確定義給變量的值。在此示例中,當fs.readFile方法未引發錯誤時,我們將獲得null值。
<code>fs.readFile('path/to/file', (e,data) => {
console.log(e); // 當沒有錯誤發生時,打印 null
if(e){
console.log(e);
}
console.log(data);
});/<code>
在比較null和undefined時,我們使用==時得到true,使用===時得到false:
<code> console.log(null == undefined); // true
console.log(null === undefined); // false/<code>
2. && 運算符能做什麼
&& 也可以叫邏輯與,在其操作數中找到第一個虛值表達式並返回它,如果沒有找到任何虛值表達式,則返回最後一個真值表達式。它採用短路來防止不必要的工作。
<code>console.log(false && 1 && []); // false
console.log(" " && true && 5); // 5/<code>
使用if語句
<code>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();
}
}
});/<code>
使用&&操作符
<code>const router: Router = Router();
router.get('/endpoint', (req: Request, res: Response) => {
let conMobile: PoolConnection;
try {
//do some db operations
} catch (e) {
conMobile && conMobile.release()
}
});/<code>
3. || 運算符能做什麼
||也叫或邏輯或,在其操作數中找到第一個真值表達式並返回它。這也使用了短路來防止不必要的工作。在支持 ES6 默認函數參數之前,它用於初始化函數中的默認參數值。
<code>console.log(null || 1 || undefined); // 1
function logName(name) {
var n = name || "Mark";
console.log(n);
}
logName(); // "Mark"/<code>
4. 使用 + 或一元加運算符是將字符串轉換為數字的最快方法嗎?
根據MDN文檔,+是將字符串轉換為數字的最快方法,因為如果值已經是數字,它不會執行任何操作。
5. DOM 是什麼?
DOM 代表文檔對象模型,是 HTML 和 XML 文檔的接口(API)。當瀏覽器第一次讀取(解析)HTML文檔時,它會創建一個大對象,一個基於 HTM L文檔的非常大的對象,這就是DOM。它是一個從 HTML 文檔中建模的樹狀結構。DOM 用於交互和修改DOM結構或特定元素或節點。
假設我們有這樣的 HTML 結構:
<code>
<title>Document Object Model/<title>
<label>
/<code>
等價的DOM是這樣的:
![70個JavaScript知識點詳細總結(上)【實踐】](http://p2.ttnews.xyz/loading.gif)
JS 中的document對象表示DOM。它為我們提供了許多方法,我們可以使用這些方法來選擇元素來更新元素內容,等等。
6. 什麼是事件傳播?
當事件發生在DOM元素上時,該事件並不完全發生在那個元素上。在“冒泡階段”中,事件冒泡或向上傳播至父級,祖父母,祖父母或父級,直到到達window為止;而在“捕獲階段”中,事件從window開始向下觸發元素 事件或event.target。
事件傳播有三個階段:
- 捕獲階段–事件從 window 開始,然後向下到每個元素,直到到達目標元素。
- 目標階段–事件已達到目標元素。
- 冒泡階段–事件從目標元素冒泡,然後上升到每個元素,直到到達 window。
7. 什麼是事件冒泡?
當事件發生在DOM元素上時,該事件並不完全發生在那個元素上。在冒泡階段,事件冒泡,或者事件發生在它的父代,祖父母,祖父母的父代,直到到達window為止。
假設有如下的 HTML 結構:
<code>/<code>
1
對應的 JS 代碼:
<code>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');
})
});/<code>
addEventListener方法具有第三個可選參數useCapture,其默認值為false,事件將在冒泡階段中發生,如果為true,則事件將在捕獲階段中發生。如果單擊child元素,它將分別在控制檯上記錄child,parent,grandparent,html,document和window,這就是事件冒泡。
8. 什麼是事件捕獲?
當事件發生在 DOM 元素上時,該事件並不完全發生在那個元素上。在捕獲階段,事件從window開始,一直到觸發事件的元素。
假設有如下的 HTML 結構:
<code>/<code>
1
對應的 JS 代碼:
<code>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');
})
});/<code>
addEventListener方法具有第三個可選參數useCapture,其默認值為false,事件將在冒泡階段中發生,如果為true,則事件將在捕獲階段中發生。如果單擊child元素,它將分別在控制檯上打印window,document,html,grandparent和parent,這就是事件捕獲。
9. event.preventDefault() 和 event.stopPropagation()方法之間有什麼區別?
event.preventDefault() 方法可防止元素的默認行為。如果在表單元素中使用,它將阻止其提交。如果在錨元素中使用,它將阻止其導航。如果在上下文菜單中使用,它將阻止其顯示或顯示。 event.stopPropagation()方法用於阻止捕獲和冒泡階段中當前事件的進一步傳播。
10. 如何知道是否在元素中使用了event.preventDefault()方法?
我們可以在事件對象中使用event.defaultPrevented屬性。它返回一個布爾值用來表明是否在特定元素中調用了event.preventDefault()。
11. 為什麼此代碼 obj.someprop.x 會引發錯誤?
<code>const obj = {};
console.log(obj.someprop.x);/<code>
顯然,由於我們嘗試訪問someprop屬性中的x屬性,而 someprop 並沒有在對象中,所以值為 undefined。記住對象本身不存在的屬性,並且其原型的默認值為undefined。因為undefined沒有屬性x,所以試圖訪問將會報錯。
12. 什麼是 event.target ?
簡單來說,event.target是發生事件的元素或觸發事件的元素。
假設有如下的 HTML 結構:
<code>/<code>
<button>
Button
/<button>
JS 代碼如下:
<code>function clickFunc(event) {
console.log(event.target);
}/<code>
如果單擊 button,即使我們將事件附加在最外面的div上,它也將打印 button 標籤,因此我們可以得出結論event.target是觸發事件的元素。
13. 什麼是 event.currentTarget??
event.currentTarget是我們在其上顯式附加事件處理程序的元素。
假設有如下的 HTML 結構:
<code>/<code>
<button>
Button
/<button>
JS 代碼如下:
<code>function clickFunc(event) {
console.log(event.currentTarget);
}/<code>
如果單擊 button,即使我們單擊該 button,它也會打印最外面的div標籤。在此示例中,我們可以得出結論,event.currentTarget是附加事件處理程序的元素。
14. == 和 === 有什麼區別?
==用於一般比較,===用於嚴格比較,==在比較的時候可以轉換數據類型,===嚴格比較,只要類型不匹配就返回flase。
先來看看 == 這兄弟:
強制是將值轉換為另一種類型的過程。在這種情況下,==會執行隱式強制。在比較兩個值之前,==需要執行一些規則。
假設我們要比較x == y的值。
- 如果x和y的類型相同,則 JS 會換成===操作符進行比較。
- 如果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方法來獲取該對象的原始值。
舉個例子。
這些例子都返回true。
第一個示例符合條件1,因為x和y具有相同的類型和值。
第二個示例符合條件4,在比較之前將y轉換為數字。
第三個例子符合條件2。
第四個例子符合條件7,因為y是boolean類型。
第五個示例符合條件8。使用toString()方法將數組轉換為字符串,該方法返回1,2。
最後一個示例符合條件8。使用toString()方法將對象轉換為字符串,該方法返回[object Object]。
如果使用===運算符,則第一個示例以外的所有比較將返回false,因為它們的類型不同,而第一個示例將返回true,因為兩者的類型和值相同。
15. 為什麼在 JS 中比較兩個相似的對象時返回 false?
先看下面的例子:
<code>let a = { a: 1 };
let b = { a: 1 };
let c = a;
console.log(a === b); // 打印 false,即使它們有相同的屬性
console.log(a === c); // true/<code>
JS 以不同的方式比較對象和基本類型。在基本類型中,JS 通過值對它們進行比較,而在對象中,JS 通過引用或存儲變量的內存中的地址對它們進行比較。這就是為什麼第一個console.log語句返回false,而第二個console.log語句返回true。a和c有相同的引用地址,而a和b沒有。
16. !! 運算符能做什麼?
!!運算符可以將右側的值強制轉換為布爾值,這也是將值轉換為布爾值的一種簡單方法。
<code>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/<code>
17. 如何在一行中計算多個表達式的值?
可以使用逗號運算符在一行中計算多個表達式。它從左到右求值,並返回右邊最後一個項目或最後一個操作數的值。
<code>let x = 5;
x = (x++ , x = addFive(x), x *= 2, x -= 5, x += 10);
function addFive(num) {
return num + 5;
}/<code>
上面的結果最後得到x的值為27。首先,我們將x的值增加到6,然後調用函數addFive(6)並將6作為參數傳遞並將結果重新分配給x,此時x的值為11。之後,將x的當前值乘以2並將其分配給x,x的更新值為22。然後,將x的當前值減去5並將結果分配給x x更新後的值為17。最後,我們將x的值增加10,然後將更新的值分配給x,最終x的值為27。
18. 什麼是提升?
提升是用來描述變量和函數移動到其(全局或函數)作用域頂部的術語。
為了理解提升,需要來了解一下執行上下文。執行上下文是當前正在執行的“代碼環境”。執行上下文有兩個階段:編譯和執行。
編譯-在此階段,JS 引薦獲取所有函數聲明並將其提升到其作用域的頂部,以便我們稍後可以引用它們並獲取所有變量聲明(使用var關鍵字進行聲明),還會為它們提供默認值: undefined。
執行——在這個階段中,它將值賦給之前提升的變量,並執行或調用函數(對象中的方法)。
注意:只有使用var聲明的變量,或者函數聲明才會被提升,相反,函數表達式或箭頭函數,let和const聲明的變量,這些都不會被提升。
假設在全局使用域,有如下的代碼:
<code>console.log(y);
y = 1;
console.log(y);
console.log(greet("Mark"));
function greet(name){
return 'Hello ' + name + '!';
}
var y;/<code>
上面分別打印:undefined,1, Hello Mark!。
上面代碼在編譯階段其實是這樣的:
<code>function greet(name) {
return 'Hello ' + name + '!';
}
var y; // 默認值 undefined
// 等待“編譯”階段完成,然後開始“執行”階段
/*
console.log(y);
y = 1;
console.log(y);
console.log(greet("Mark"));
*//<code>
編譯階段完成後,它將啟動執行階段調用方法,並將值分配給變量。
<code>function greet(name) {
return 'Hello ' + name + '!';
}
var y;
//start "execution" phase
console.log(y);
y = 1;
console.log(y);
console.log(greet("Mark"));/<code>
19. 什麼是作用域?
JavaScript 中的作用域是我們可以有效訪問變量或函數的區域。JS 有三種類型的作用域:全局作用域、函數作用域和塊作用域(ES6)。
- 全局作用域——在全局命名空間中聲明的變量或函數位於全局作用域中,因此在代碼中的任何地方都可以訪問它們。
<code>//global namespace
var g = "global";
function globalFunc(){
function innerFunc(){
console.log(g); // can access "g" because "g" is a global variable
}
innerFunc();
}/<code>
- 函數作用域——在函數中聲明的變量、函數和參數可以在函數內部訪問,但不能在函數外部訪問。
<code>function myFavoriteFunc(a) {
if (true) {
var b = "Hello " + a;
}
return b;
}
myFavoriteFunc("World");
console.log(a); // Throws a ReferenceError "a" is not defined
console.log(b); // does not continue here/<code>
- 塊作用域-在塊{}中聲明的變量(let,const)只能在其中訪問。
<code> function testBlock(){
if(true){
let z = 5;
}
return z;
}
testBlock(); // Throws a ReferenceError "z" is not defined/<code>
作用域也是一組用於查找變量的規則。如果變量在當前作用域中不存在,它將向外部作用域中查找並搜索,如果該變量不存在,它將再次查找直到到達全局作用域,如果找到,則可以使用它,否則引發錯誤,這種查找過程也稱為作用域鏈。
<code> /* 作用域鏈
內部作用域->外部作用域-> 全局作用域
*/
// 全局作用域
var variable1 = "Comrades";
var variable2 = "Sayonara";
function outer(){
// 外部作用域
var variable1 = "World";
function inner(){
// 內部作用域
var variable2 = "Hello";
console.log(variable2 + " " + variable1);
}
inner();
}
outer(); // Hello World/<code>
20. 什麼是閉包?
這可能是所有問題中最難的一個問題,因為閉包是一個有爭議的話題,這裡從個人角度來談談,如果不妥,多多海涵。
閉包就是一個函數在聲明時能夠記住當前作用域、父函數作用域、及父函數作用域上的變量和參數的引用,直至通過作用域鏈上全局作用域,基本上閉包是在聲明函數時創建的作用域。
看看小例子:
<code> // 全局作用域
var globalVar = "abc";
function a(){
console.log(globalVar);
}
a(); // "abc"/<code>
在此示例中,當我們聲明a函數時,全局作用域是a閉包的一部分。
變量globalVar在圖中沒有值的原因是該變量的值可以根據調用函數a的位置和時間而改變。但是在上面的示例中,globalVar變量的值為abc。
來看一個更復雜的例子:
<code>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");/<code>
上面打印結果是 guess outer inner。
當我們調用outerFunc函數並將返回值innerFunc函數分配給變量x時,即使我們為outerVar變量分配了新值outer-2,outerParam也繼續保留outer值,因為重新分配是在調用outerFunc之後發生的,並且當我們調用outerFunc函數時,它會在作用域鏈中查找outerVar的值,此時的outerVar的值將為 "outer"。
現在,當我們調用引用了innerFunc的x變量時,innerParam將具有一個inner值,因為這是我們在調用中傳遞的值,而globalVar變量值為guess,因為在調用x變量之前,我們將一個新值分配給globalVar。
下面這個示例演示沒有理解好閉包所犯的錯誤:
<code>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
}/<code>
由於閉包,此代碼無法正常運行。var關鍵字創建一個全局變量,當我們 push 一個函數時,這裡返回的全局變量i。因此,當我們在循環後在該數組中調用其中一個函數時,它會打印5,因為我們得到i的當前值為5,我們可以訪問它,因為它是全局變量。
因為閉包在創建變量時會保留該變量的引用而不是其值。我們可以使用IIFES或使用 let 來代替 var 的聲明。
21. JavaScript 中的虛值是什麼?
<code> const falsyValues = ['', 0, null, undefined, NaN, false];/<code>
簡單的來說虛值就是是在轉換為布爾值時變為 false 的值。
22. 如何檢查值是否虛值?
使用 Boolean 函數或者 !! 運算符。
23. 'use strict' 是幹嘛用的?
"use strict" 是 ES5 特性,它使我們的代碼在函數或整個腳本中處於嚴格模式。嚴格模式幫助我們在代碼的早期避免 bug,併為其添加限制。
嚴格模式的一些限制:
- 變量必須聲明後再使用
- 函數的參數不能有同名屬性,否則報錯
- 不能使用with語句
- 不能對只讀屬性賦值,否則報錯
- 不能使用前綴 0 表示八進制數,否則報錯
- 不能刪除不可刪除的屬性,否則報錯
- 不能刪除變量delete prop,會報錯,只能刪除屬性delete global[prop]
- eval不能在它的外層作用域引入變量
- eval和arguments不能被重新賦值
- arguments不會自動反映函數參數的變化
- 不能使用arguments.callee
- 不能使用arguments.caller
- 禁止this指向全局對象
- 不能使用fn.caller和fn.arguments獲取函數調用的堆棧
- 增加了保留字(比如protected、static和interface)
設立”嚴格模式”的目的,主要有以下幾個:
- 消除Javascript語法的一些不合理、不嚴謹之處,減少一些怪異行為;
- 消除代碼運行的一些不安全之處,保證代碼運行的安全;
- 提高編譯器效率,增加運行速度;
- 為未來新版本的Javascript做好鋪墊。
24. JavaScript 中 this 值是什麼?
基本上,this指的是當前正在執行或調用該函數的對象的值。this值的變化取決於我們使用它的上下文和我們在哪裡使用它。
<code>const carDetails = {
name: "Ford Mustang",
yearBought: 2005,
getName(){
return this.name;
},
isRegistered: true
};
console.log(carDetails.getName()); // Ford Mustang/<code>
這通常是我們期望結果的,因為在getName方法中我們返回this.name,在此上下文中,this指向的是carDetails對象,該對象當前是執行函數的“所有者”對象。
接下我們做些奇怪的事情:
<code>var name = "Ford Ranger";
var getCarName = carDetails.getName;
console.log(getCarName()); // Ford Ranger/<code>
上面打印Ford Ranger,這很奇怪,因為在第一個console.log語句中打印的是Ford Mustang。這樣做的原因是getCarName方法有一個不同的“所有者”對象,即window對象。在全局作用域中使用var關鍵字聲明變量會在window對象中附加與變量名稱相同的屬性。請記住,當沒有使用“use strict”時,在全局作用域中this指的是window對象。
<code>console.log(getCarName === window.getCarName); // true
console.log(getCarName === this.getCarName); // true/<code>
本例中的this和window引用同一個對象。
解決這個問題的一種方法是在函數中使用apply和call方法。
<code>console.log(getCarName.apply(carDetails)); // Ford Mustang
console.log(getCarName.call(carDetails)); // Ford Mustang/<code>
apply和call方法期望第一個參數是一個對象,該對象是函數內部this的值。
IIFE或立即執行的函數表達式,在全局作用域內聲明的函數,對象內部方法中的匿名函數和內部函數的this具有默認值,該值指向window對象。
<code> (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" 對象
});/<code>
如果我們要獲取myFavoriteObj對象中的name屬性(即Marko Polo)的值,則有兩種方法可以解決此問題。
一種是將 this 值保存在變量中。
<code>const myFavoriteObj = {
guessThis(){
const self = this; // 把 this 值保存在 self 變量中
function getName(){
console.log(self.name);
}
getName();
},
name: 'Marko Polo',
thisIsAnnoying(callback){
callback();
}
};/<code>
第二種方式是使用箭頭函數
<code> 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();
}
};/<code>
箭頭函數沒有自己的 this。它複製了這個封閉的詞法作用域中this值,在這個例子中,this值在getName內部函數之外,也就是myFavoriteObj對象。
25. 對象的 prototype(原型) 是什麼?
簡單地說,原型就是對象的藍圖。如果它存在當前對象中,則將其用作屬性和方法的回退。它是在對象之間共享屬性和功能的方法,這也是JavaScript實現繼承的核心。
<code>const o = {};
console.log(o.toString()); // logs [object Object]/<code>
即使o對象中不存在o.toString方法,它也不會引發錯誤,而是返回字符串[object Object]。當對象中不存在屬性時,它將查看其原型,如果仍然不存在,則將其查找到原型的原型,依此類推,直到在原型鏈中找到具有相同屬性的屬性為止。原型鏈的末尾是Object.prototype。
<code>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./<code>
26. 什麼是 IIFE,它的用途是什麼?
IIFE或立即調用的函數表達式是在創建或聲明後將被調用或執行的函數。創建IIFE的語法是,將function (){}包裹在在括號()內,然後再用另一個括號()調用它,如:(function(){})()
<code>(function(){
...
} ());
(function () {
...
})();
(function named(params) {
...
})();
(() => {
});
(function (global) {
...
})(window);
const utility = (function () {
return {
...
}
})/<code>
這些示例都是有效的IIFE。倒數第二個救命表明我們可以將參數傳遞給IIFE函數。最後一個示例表明,我們可以將IIFE的結果保存到變量中,以便稍後使用。
IIFE的一個主要作用是避免與全局作用域內的其他變量命名衝突或汙染全局命名空間,來個例子。
<code>/<code>
假設我們引入了一個omelibr.js的鏈接,它提供了一些我們在代碼中使用的全局函數,但是這個庫有兩個方法我們沒有使用:createGraph和drawGraph,因為這些方法都有bug。我們想實現自己的createGraph和drawGraph方法。
解決此問題的一種方法是直接覆蓋:
<code>
/<code>
當我們使用這個解決方案時,我們覆蓋了庫提供給我們的那兩個方法。
另一種方式是我們自己改名稱:
<code>
/<code>
當我們使用這個解決方案時,我們把那些函數調用更改為新的函數名。
還有一種方法就是使用IIFE:
<code>
/<code>
在此解決方案中,我們要聲明瞭graphUtility 變量,用來保存IIFE執行的結果,該函數返回一個包含兩個方法createGraph和drawGraph的對象。
IIFE 還可以用來解決一個常見的面試題:
<code>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);
})/<code>
假設我們有一個帶有list-group類的ul元素,它有5個li子元素。當我們單擊單個li元素時,打印對應的下標值。但在此外上述代碼不起作用,這裡每次點擊 li 打印 i 的值都是5,這是由於閉包的原因。
閉包只是函數記住其當前作用域,父函數作用域和全局作用域的變量引用的能力。當我們在全局作用域內使用var關鍵字聲明變量時,就創建全局變量i。因此,當我們單擊li元素時,它將打印5,因為這是稍後在回調函數中引用它時i的值。
使用 IIFE 可以解決此問題:
<code>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);
}/<code>
該解決方案之所以行的通,是因為IIFE會為每次迭代創建一個新的作用域,我們捕獲i的值並將其傳遞給currentIndex參數,因此調用IIFE時,每次迭代的currentIndex值都是不同的。
27. Function.prototype.apply 方法的用途是什麼?
apply() 方法調用一個具有給定this值的函數,以及作為一個數組(或類似數組對象)提供的參數。
<code>const details = {
message: 'Hello World!'
};
function getMessage(){
return this.message;
}
getMessage.apply(details); // 'Hello World!'/<code>
call()方法的作用和 apply() 方法類似,區別就是call()方法接受的是參數列表,而apply()方法接受的是一個參數數組。
<code>const person = {
name: "Marko Polo"
};
function greeting(greetingMessage) {
return `${greetingMessage} ${this.name}`;
}
greeting.apply(person, ['Hello']); // "Hello Marko Polo!"/<code>
28. Function.prototype.call 方法的用途是什麼?
call() 方法使用一個指定的 this 值和單獨給出的一個或多個參數來調用一個函數。
<code>const details = {
message: 'Hello World!'
};
function getMessage(){
return this.message;
}
getMessage.call(details); // 'Hello World!'/<code>
注意:該方法的語法和作用與 apply() 方法類似,只有一個區別,就是 call() 方法接受的是一個參數列表,而 apply() 方法接受的是一個包含多個參數的數組。
<code>const person = {
name: "Marko Polo"
};
function greeting(greetingMessage) {
return `${greetingMessage} ${this.name}`;
}
greeting.call(person, 'Hello'); // "Hello Marko Polo!"/<code>
29. Function.prototype.apply 和 Function.prototype.call 之間有什麼區別?
apply()方法可以在使用一個指定的 this 值和一個參數數組(或類數組對象)的前提下調用某個函數或方法。call()方法類似於apply(),不同之處僅僅是call()接受的參數是參數列表。
<code>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/<code>
30. Function.prototype.bind 的用途是什麼?
bind() 方法創建一個新的函數,在 bind() 被調用時,這個新函數的 this 被指定為 bind() 的第一個參數,而其餘參數將作為新函數的參數,供調用時使用。
<code>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 (
<>
value={this.state.value}
onChange={this.handleChange}
/>
>
)
}
}/<code>
31. 什麼是函數式編程? JavaScript 的哪些特性使其成為函數式語言的候選語言?
函數式編程(通常縮寫為FP)是通過編寫純函數,避免共享狀態、可變數據、副作用 來構建軟件的過程。數式編程是聲明式 的而不是命令式 的,應用程序的狀態是通過純函數流動的。與面向對象編程形成對比,面向對象中應用程序的狀態通常與對象中的方法共享和共處。
函數式編程是一種編程範式 ,這意味著它是一種基於一些基本的定義原則(如上所列)思考軟件構建的方式。當然,編程範示的其他示例也包括面向對象編程和過程編程。
函數式的代碼往往比命令式或面向對象的代碼更簡潔,更可預測,更容易測試 - 但如果不熟悉它以及與之相關的常見模式,函數式的代碼也可能看起來更密集雜亂,並且 相關文獻對新人來說是不好理解的。
JavaScript支持閉包和高階函數是函數式編程語言的特點。
32. 什麼是高階函數?
高階函數只是將函數作為參數或返回值的函數。
<code>function higherOrderFunction(param,callback){
return callback(param);
}/<code>
33. 為什麼函數被稱為一等公民?
在JavaScript中,函數不僅擁有一切傳統函數的使用方式(聲明和調用),而且可以做到像簡單值一樣賦值(var func = function(){})、傳參(function func(x,callback){callback();})、返回(function(){return function(){}}),這樣的函數也稱之為第一級函數(First-class Function)。不僅如此,JavaScript中的函數還充當了類的構造函數的作用,同時又是一個Function類的實例(instance)。這樣的多重身份讓JavaScript的函數變得非常重要。
34. 手動實現 Array.prototype.map 方法
map() 方法創建一個新數組,其結果是該數組中的每個元素都調用一個提供的函數後返回的結果。
<code>function map(arr, mapCallback) {
// 首先,檢查傳遞的參數是否正確。
if (!Array.isArray(arr) || !arr.length || typeof mapCallback !== 'function') {
return [];
} else {
let result = [];
// 每次調用此函數時,我們都會創建一個 result 數組
// 因為我們不想改變原始數組。
for (let i = 0, len = arr.length; i < len; i++) {
result.push(mapCallback(arr[i], i, arr));
// 將 mapCallback 返回的結果 push 到 result 數組中
}
return result;
}
}/<code>
70個JS知識點,目前只總結了34個。未完結,請持續關注,馬上更新。。。。
推薦JS相關文章
《 》
《 》
《 》
《 》
《 》
《 》
《 》
《 》
英文原文地址:https://dev.to/macmacky/70-javascript-interview-questions-5gfi#61-what-are-the-ways-of-making-objects-in-javascript
閱讀更多 Echa攻城獅 的文章
關鍵字: 設計模式 知識點 ECMAScript