這是一篇很長的文章,所以請耐心聽我一秒鐘或一個小時。每個問題的每個答案都有一個向上箭頭↑連結,可讓您返回到問題列表,這樣您就不會浪費時間上下滾動。
[12.什麼是event.target
?](#12-什麼是 eventtarget-
)
[26.什麼是 IIFE,它有什麼用?](#26-what-is-an-iife-what-is-the-use-of-it
)
[31.什麼是函數式程式設計以及 JavaScript 的哪些特性使其成為函數式語言的候選者?](#31-什麼是函數式程式設計和 javascript 的特性是什麼-使其成為函數式語言的候選者
)
[34.手動實作Array.prototype.map
方法。](#34-手動實作 arrayprototypemap-method
)
[66.一個函數有多少種呼叫方式?](
undefined
和null
有什麼差別?^在了解undefined
和null
之間的差異之前,我們必須先了解它們之間的相似之處。
let primitiveTypes = ['string','number','null','undefined','boolean','symbol', 'bigint'];
Boolean(value)
或!!value
將其轉換為布林值時計算結果為 false 的值。 console.log(!!null); //logs false
console.log(!!undefined); //logs false
console.log(Boolean(null)); //logs false
console.log(Boolean(undefined)); //logs false
好吧,我們來談談差異。
undefined
是尚未指派特定值的變數的預設值。或一個沒有明確回傳值的函數。 console.log(1)
。或物件中不存在的屬性。 JavaScript 引擎為我們完成了指派undefined
值的任務。 let _thisIsUndefined;
const doNothing = () => {};
const someObj = {
a : "ay",
b : "bee",
c : "si"
};
console.log(_thisIsUndefined); //logs undefined
console.log(doNothing()); //logs undefined
console.log(someObj["d"]); //logs undefined
null
是「代表無值的值」 。 null
是已明確定義給變數的值。在此範例中,當fs.readFile
方法未引發錯誤時,我們得到null
值。 fs.readFile('path/to/file', (e,data) => {
console.log(e); //it logs null when no error occurred
if(e){
console.log(e);
}
console.log(data);
});
當比較null
和undefined
時,使用==
時我們得到true
,使用===
時得到false
。您可以在此處閱讀原因。
console.log(null == undefined); // logs true
console.log(null === undefined); // logs false
&&
運算子的作用是什麼?^ &&
或邏輯 AND運算子在其運算元中尋找第一個假表達式並傳回它,如果沒有找到任何假表達式,則傳回最後一個表達式。它採用短路來防止不必要的工作。在我的一個專案中關閉資料庫連線時,我在catch
區塊中使用了它。
console.log(false && 1 && []); //logs false
console.log(" " && true && 5); //logs 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()
}
});
||
是什麼意思?運營商做什麼?↑ ||
or邏輯 OR運算子尋找其運算元中的第一個真值表達式並傳回它。這也採用短路來防止不必要的工作。在ES6預設函數參數被支援之前,它被用來初始化函數中的預設參數值。
console.log(null || 1 || undefined); //logs 1
function logName(name) {
var n = name || "Mark";
console.log(n);
}
logName(); //logs "Mark"
^根據MDN 文件, +
是將字串轉換為數字的最快方法,因為如果該值已經是數字,它不會對該值執行任何操作。
^ DOM代表文件物件模型,是 HTML 和 XML 文件的介面 ( API )。當瀏覽器第一次讀取(解析)我們的 HTML 文件時,它會建立一個大物件,一個基於 HTML 文件的非常大的物件,這就是DOM 。它是根據 HTML 文件建模的樹狀結構。 DOM用於互動和修改DOM 結構或特定元素或節點。
想像一下,如果我們有這樣的 HTML 結構。
<!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>
等效的DOM應該是這樣的。
JavaScript中的document
物件代表DOM 。它為我們提供了許多方法,我們可以用來選擇元素來更新元素內容等等。
↑當某個事件發生在DOM元素上時,該事件並非完全發生在該元素上。在冒泡階段,事件向上冒泡,或到達其父級、祖父母、祖父母的父級,直到一直到達window
,而在捕獲階段,事件從window
開始向下到達觸發的元素事件或<a href="#12-what-is-eventtarget-">event.target</a>
。
事件傳播分為三個階段。
↑當某個事件發生在DOM元素上時,該事件並非完全發生在該元素上。在冒泡階段,事件向上冒泡,或到達其父級、祖父母、祖父母的父級,直到一直到達window
。
如果我們有一個像這樣的範例標記。
<div class="grandparent">
<div class="parent">
<div class="child">1</div>
</div>
</div>
還有我們的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
方法有第三個可選參數useCapture ,預設值為false
事件將在冒泡階段發生,如果為true
,事件將在捕獲階段發生。如果我們點擊child
元素,它會分別在控制台上記錄child
、 parent
元素、 grandparent
、 html
、 document
和window
。這就是事件冒泡。
↑當某個事件發生在DOM元素上時,該事件並非完全發生在該元素上。在捕獲階段,事件從window
開始一直到觸發事件的元素。
如果我們有一個像這樣的範例標記。
<div class="grandparent">
<div class="parent">
<div class="child">1</div>
</div>
</div>
還有我們的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');
}, true);
addEvent(parent, 'click', function (e) {
console.log('parent');
}, true);
addEvent(grandparent, 'click', function (e) {
console.log('grandparent');
}, true);
addEvent(document, 'click', function (e) {
console.log('document');
}, true);
addEvent('html', 'click', function (e) {
console.log('html');
}, true)
addEvent(window, 'click', function (e) {
console.log('window');
}, true)
});
addEventListener
方法有第三個可選參數useCapture ,預設值為false
事件將在冒泡階段發生,如果為true
,事件將在捕獲階段發生。如果我們點擊child
元素,它會分別在控制台上記錄window
、 document
、 html
、 grandparent
、 parent
和child
。這就是事件捕獲。
event.preventDefault()
和event.stopPropagation()
方法有什麼差別?↑ event.preventDefault()
方法阻止元素的預設行為。如果在form
元素中使用,它會阻止其提交。如果在anchor
元素中使用,它會阻止其導航。如果在contextmenu
中使用,它會阻止其顯示或顯示。而event.stopPropagation()
方法會停止事件的傳播或停止事件在冒泡或捕獲階段發生。
event.preventDefault()
方法?↑我們可以使用事件物件中的event.defaultPrevented
屬性。它傳回一個boolean
,指示是否在特定元素中呼叫了event.preventDefault()
。
obj.someprop.x
會拋出錯誤?const obj = {};
console.log(obj.someprop.x);
^顯然,由於我們嘗試存取 a 的原因,這會引發錯誤
someprop
屬性中的x
屬性具有undefined
值。請記住,物件中的屬性本身並不存在,且其原型具有預設值undefined
且undefined
沒有屬性x
。
↑最簡單來說, event.target是發生事件的元素或觸發事件的元素。
HTML 標記範例。
<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>
JavaScript 範例。
function clickFunc(event) {
console.log(event.target);
}
如果您單擊按鈕,它會記錄按鈕標記,即使我們將事件附加在最外部的div
上,它也會始終記錄按鈕,因此我們可以得出結論, event.target是觸發事件的元素。
↑ event.currentTarget是我們明確附加事件處理程序的元素。
複製問題 12中的標記。
HTML 標記範例。
<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);
}
如果您按一下該按鈕,即使我們按一下該按鈕,它也會記錄最外層的div標記。在此範例中,我們可以得出結論, event.currentTarget是我們附加事件處理程序的元素。
==
和===
有什麼差別?^ ==
__(抽象相等)__ 和===
__(嚴格相等)__ 之間的區別在於==
在強制轉換後按值進行比較,而===
在不進行強制轉換的情況下按值和類型進行比較。
讓我們更深入地研究==
。那麼首先我們來談談強制。
強制轉換是將一個值轉換為另一種類型的過程。在本例中, ==
進行隱式強制轉換。在比較兩個值之前, ==
需要執行一些條件。
假設我們必須比較x == y
值。
x
和y
具有相同的類型。然後將它們與===
運算子進行比較。
如果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
是 type object
然後回傳x == toPrimitive(y)
。
x
是object
且x
是string
、 symbol
然後返回toPrimitive(x) == y
。
false
。注意: toPrimitive
首先使用物件中的valueOf
方法,然後使用toString
方法來取得該物件的原始值。
讓我們舉個例子。
| 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
。
第一個範例屬於條件一,因為x
和y
具有相同的類型和值。
第二個範例轉到條件四,在比較之前將y
轉換為number
。
第三個例子涉及條件二。
第四個範例轉到條件七,因為y
是boolean
。
第五個範例適用於條件八。使用toString()
方法將陣列轉換為string
,該方法傳回1,2
。
最後一個例子適用於條件十。使用傳回[object Object]
的toString()
方法將該物件轉換為string
。
| x
| y
| x === y
|
| ------------- |:-------------:| ----------------: |
| 5
| 5
| true
|
| 1
| '1'
| false
|
| null
| undefined
| false
|
| 0
| false
| false
|
| '1,2'
| [1,2]
| false
|
| '[object Object]'
| {}
| false
|
如果我們使用===
運算符,則除第一個範例之外的所有比較都將傳回false
,因為它們不具有相同的類型,而第一個範例將傳回true
,因為兩者俱有相同的類型和值。
^假設我們有下面的例子。
let a = { a: 1 };
let b = { a: 1 };
let c = a;
console.log(a === b); // logs false even though they have the same property
console.log(a === c); // logs true hmm
JavaScript以不同的方式比較物件和基元。在基元中,它透過值來比較它們,而在物件中,它透過引用或儲存變數的記憶體位址來比較它們。這就是為什麼第一個console.log
語句回傳false
而第二個console.log
語句回傳true
的原因。 a
和c
有相同的引用,而a
和b
則不同。
↑雙非運算子或!!將右側的值強制轉換為布林值。基本上,這是一種將值轉換為布林值的奇特方法。
console.log(!!null); //logs false
console.log(!!undefined); //logs false
console.log(!!''); //logs false
console.log(!!0); //logs false
console.log(!!NaN); //logs false
console.log(!!' '); //logs true
console.log(!!{}); //logs true
console.log(!![]); //logs true
console.log(!!1); //logs true
console.log(!![].length); //logs false
↑我們可以使用,
或逗號運算子來計算一行中的多個表達式。它從左到右計算並傳回右側最後一項或最後一個操作數的值。
let x = 5;
x = (x++ , x = addFive(x), x *= 2, x -= 5, x += 10);
function addFive(num) {
return num + 5;
}
如果記錄x
的值,它將是27 。首先,我們增加x 的值,它將是6 ,然後我們呼叫函數addFive(6)
並將 6 作為參數傳遞,並將結果分配給x
, x
的新值將是11 。之後,我們將x
的當前值乘以2並將其分配給x
, x
的更新值將是22 。然後,我們將x
的當前值減去 5 並將結果指派給x
,更新後的值將是17 。最後,我們將x
的值增加 10 並將更新後的值指派給x
,現在x
的值將是27 。
^提升是一個術語,用於描述將變數和函數移動到其(全域或函數)作用域的頂部(即我們定義該變數或函數的位置)。
要理解提升,我必須解釋執行上下文。
執行上下文是目前正在執行的「程式碼環境」。執行上下文有兩個階段:編譯和執行。
編譯- 在此階段,它獲取所有函數聲明並將它們提升到作用域的頂部,以便我們稍後可以引用它們並獲取所有*變數聲明(使用 var 關鍵字聲明) ,並將它們提升並給它們一個默認值未定義*的 .
執行- 在此階段,它將值指派給先前提升的變數,並執行或呼叫函數(物件中的方法) 。
注意:只有使用var關鍵字宣告的函數宣告和變數才會被提升,而不是函數表達式或箭頭函數、 let
和const
關鍵字。
好吧,假設我們在下面的全域範圍內有一個範例程式碼。
console.log(y);
y = 1;
console.log(y);
console.log(greet("Mark"));
function greet(name){
return 'Hello ' + name + '!';
}
var y;
此程式碼記錄undefined
, 1
, Hello Mark!
分別。
所以編譯階段看起來像這樣。
function greet(name) {
return 'Hello ' + name + '!';
}
var y; //implicit "undefined" assignment
//waiting for "compilation" phase to finish
//then start "execution" phase
/*
console.log(y);
y = 1;
console.log(y);
console.log(greet("Mark"));
*/
出於範例目的,我對變數和函數呼叫的賦值進行了評論。
編譯階段完成後,它開始執行階段,呼叫方法並向變數賦值。
function greet(name) {
return 'Hello ' + name + '!';
}
var y;
//start "execution" phase
console.log(y);
y = 1;
console.log(y);
console.log(greet("Mark"));
↑ JavaScript 中的作用域是我們可以有效存取變數或函數的區域。 JavaScript 有三種類型的作用域。全域作用域、函數作用域和區塊作用域(ES6) 。
//global namespace
var g = "global";
function globalFunc(){
function innerFunc(){
console.log(g); // can access "g" because "g" is a global variable
}
innerFunc();
}
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
{}
內宣告的變數( let
、 const
)只能在區塊內存取。 function testBlock(){
if(true){
let z = 5;
}
return z;
}
testBlock(); // Throws a ReferenceError "z" is not defined
範圍也是一組查找變數的規則。如果一個變數在當前作用域中不存在,它會在外部作用域中查找並蒐索該變數,如果不存在,它會再次查找,直到到達全域作用域。如果該變數存在,那麼我們可以使用它,如果不存在,我們可以使用它來拋出錯誤。它搜尋最近的變數,一旦找到它就停止搜尋或尋找。這稱為作用域鏈。
/* Scope Chain
Inside inner function perspective
inner's scope -> outer's scope -> global's scope
*/
//Global Scope
var variable1 = "Comrades";
var variable2 = "Sayonara";
function outer(){
//outer's scope
var variable1 = "World";
function inner(){
//inner's scope
var variable2 = "Hello";
console.log(variable2 + " " + variable1);
}
inner();
}
outer();
// logs Hello World
// because (variable2 = "Hello") and (variable1 = "World") are the nearest
// variables inside inner's scope.
^這可能是所有這些問題中最難的問題,因為閉包是一個有爭議的話題。那我就從我的理解來解釋。
閉包只是函數在宣告時記住其當前作用域、其父函數作用域、其父函數的父函數作用域上的變數和參數的引用的能力,直到在作用域鏈的幫助下到達全域作用域。基本上它是聲明函數時建立的作用域。
例子是解釋閉包的好方法。
//Global's Scope
var globalVar = "abc";
function a(){
//testClosures's Scope
console.log(globalVar);
}
a(); //logs "abc"
/* Scope Chain
Inside a function perspective
a's scope -> global's scope
*/
在此範例中,當我們宣告a
函數時,全域作用域是a's
閉包的一部分。
變數globalVar
在影像中沒有值的原因是該變數的值可以根據我們呼叫a
位置和時間而改變。
但在上面的範例中, globalVar
變數的值為abc 。
好吧,讓我們來看一個複雜的例子。
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");
這將列印“猜測外部內部”。對此的解釋是,當我們呼叫outerFunc
函數並將innerFunc
函數的回傳值指派給變數x
時,即使我們將新值outer-2指派給outerVar
變數, outerParam
也會具有outer值,因為
重新分配發生在呼叫outer
函數之後,當我們呼叫outerFunc
函數時,它會在作用域鏈中尋找outerVar
的值,而outerVar
的值為「outer」 。現在,當我們呼叫引用了innerFunc
的x
變數時,
innerParam
的值為inner,因為這是我們在呼叫中傳遞的值,而globalVar
變數的值為猜測,因為在呼叫x
變數之前,我們為globalVar
分配了一個新值,並且在呼叫x
時作用域鏈中globalVar
的值是猜測。
我們有一個例子來示範沒有正確理解閉包的問題。
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]()); // all logs "5"
}
由於Closures的原因,此程式碼無法按我們的預期工作。
var
關鍵字建立一個全域變數,當我們推送一個函數時
我們返回全域變數i
。因此,當我們在循環之後呼叫該陣列中的其中一個函數時,它會記錄5
,因為我們得到
i
的目前值為5
,我們可以存取它,因為它是全域變數。因為閉包保留該變數的引用,而不是其建立時的值。我們可以使用IIFES或將var
關鍵字變更為let
來解決此問題,以實現區塊作用域。
const falsyValues = ['', 0, null, undefined, NaN, false];
假值是轉換為布林值時變成false 的值。
"use strict"
有什麼作用?^ "use strict"
是JavaScript中的 ES5 功能,它使我們的程式碼在函數或整個腳本中處於嚴格模式。嚴格模式幫助我們避免程式碼早期出現錯誤並為其加入限制。
嚴格模式給我們的限制。
function returnY(){
"use strict";
y = 123;
return y;
}
"use strict";
var NaN = NaN;
var undefined = undefined;
var Infinity = "and beyond";
"use strict";
const obj = {};
Object.defineProperty(obj, 'x', {
value : '1'
});
delete obj.x;
"use strict";
function someFunc(a, b, b, c){
}
"use strict";
eval("var x = 1;");
console.log(x); //Throws a Reference Error x is not defined
undefined
。 "use strict";
function showMeThis(){
return this;
}
showMeThis(); //returns undefined
嚴格模式的限制遠不止這些。
this
的值是什麼?↑基本上, this
是指目前正在執行或呼叫函數的物件的值。我說目前是因為它的值會根據我們使用它的上下文和使用它的位置而改變。
const carDetails = {
name: "Ford Mustang",
yearBought: 2005,
getName(){
return this.name;
},
isRegistered: true
};
console.log(carDetails.getName()); // logs Ford Mustang
這是我們通常所期望的,因為在getName方法中我們傳回this.name
,在此上下文中this
指的是carDetails
物件,該物件目前是正在執行的函數的「擁有者」物件。
好吧,讓我們加入一些程式碼讓它變得奇怪。在console.log
語句下面加入這三行程式碼
var name = "Ford Ranger";
var getCarName = carDetails.getName;
console.log(getCarName()); // logs Ford Ranger
第二個console.log
語句印製了「Ford Ranger」一詞,這很奇怪,因為在我們的第一個console.log
語句中它印了「Ford Mustang」 。原因是getCarName
方法有一個不同的「擁有者」物件,即window
物件。在全域作用域中使用var
關鍵字聲明變數會在window
物件中附加與變數同名的屬性。請記住,當未使用"use strict"
時,全域範圍內的this
指的是window
物件。
console.log(getCarName === window.getCarName); //logs true
console.log(getCarName === this.getCarName); // logs true
本例中的this
和window
指的是同一個物件。
解決此問題的一種方法是使用函數中的<a href="#27-what-is-the-use-functionprototypeapply-method">apply</a>
和<a href="#28-what-is-the-use-functionprototypecall-method">call</a>
方法。
console.log(getCarName.apply(carDetails)); //logs Ford Mustang
console.log(getCarName.call(carDetails)); //logs Ford Mustang
apply
和call
方法期望第一個參數是一個物件,該物件將是該函數內this
的值。
IIFE (即立即呼叫函數表達式) 、在全域作用域中宣告的函數、物件內部方法中的匿名函數和內部函數都有一個指向window物件的預設值。
(function (){
console.log(this);
})(); //logs the "window" object
function iHateThis(){
console.log(this);
}
iHateThis(); //logs the "window" object
const myFavoriteObj = {
guessThis(){
function getThis(){
console.log(this);
}
getThis();
},
name: 'Marko Polo',
thisIsAnnoying(callback){
callback();
}
};
myFavoriteObj.guessThis(); //logs the "window" object
myFavoriteObj.thisIsAnnoying(function (){
console.log(this); //logs the "window" object
});
如果我們想要取得myFavoriteObj
物件中的name
屬性(Marko Polo)的值,有兩種方法可以解決這個問題。
首先,我們將this
的值保存在變數中。
const myFavoriteObj = {
guessThis(){
const self = this; //saves the this value to the "self" variable
function getName(){
console.log(self.name);
}
getName();
},
name: 'Marko Polo',
thisIsAnnoying(callback){
callback();
}
};
在此圖像中,我們保存this
的值,該值將是myFavoriteObj
物件。所以我們可以在getName
內部函數中存取它。
其次,我們使用ES6箭頭函數。
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();
}
};
箭頭函數沒有自己的this
。它複製封閉詞法範圍的this
值,或複製getName
內部函數外部的this
值(即myFavoriteObj
物件)。我們也可以根據函數的呼叫方式來決定this
的值。
prototype
是什麼?↑最簡單的prototype
是一個物件的藍圖。如果目前物件中確實存在它,則將其用作屬性和方法的後備。這是在物件之間共享屬性和功能的方式。這是 JavaScript原型繼承的核心概念。
const o = {};
console.log(o.toString()); // logs [object Object]
即使o.toString
方法不存在於o
物件中,它也不會拋出錯誤,而是傳回字串[object Object]
。當物件中不存在屬性時,它會尋找其原型,如果仍然不存在,則會尋找原型的原型,依此類推,直到在原型鏈中找到具有相同屬性的屬性。原型鏈的末尾在Object.prototype之後為null
。
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.
^ IIFE或立即呼叫函數表達式是在建立或宣告後將被呼叫或執行的函數。建立IIFE的語法是,我們將function (){}
包裝在括號()
或分組運算子內,以將函數視為表達式,然後用另一個括號()
呼叫它。所以IIFE看起來像這樣(function(){})()
。
(function () {
}());
(function () {
})();
(function named(params) {
})();
(() => {
})();
(function (global) {
})(window);
const utility = (function () {
return {
//utilities
};
})();
這些範例都是有效的IIFE 。倒數第二個範例顯示我們可以將參數傳遞給IIFE函數。最後一個範例表明我們可以將IIFE的結果保存到變數中,以便稍後引用它。
IIFE的最佳用途是進行初始化設定功能,並避免與全域範圍內的其他變數發生命名衝突或污染全域名稱空間。讓我們舉個例子。
<script src="https://cdnurl.com/somelibrary.js"></script>
假設我們有一個指向庫somelibrary.js
的連結,該庫公開了我們可以在程式碼中使用的一些全域函數,但該庫有兩個我們不使用createGraph
和drawGraph
方法,因為這些方法中有錯誤。我們想要實作我們自己的createGraph
和drawGraph
方法。
<script src="https://cdnurl.com/somelibrary.js"></script>
<script>
function createGraph() {
// createGraph logic here
}
function drawGraph() {
// drawGraph logic here
}
</script>
當我們使用這個解決方案時,我們將覆蓋庫為我們提供的這兩種方法。
<script src="https://cdnurl.com/somelibrary.js"></script>
<script>
function myCreateGraph() {
// createGraph logic here
}
function myDrawGraph() {
// drawGraph logic here
}
</script>
當我們使用此解決方案時,我們還將這些函數呼叫更改為新函數名稱。
<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>
在此解決方案中,我們建立一個實用程式變數,它是IIFE的結果,它傳回一個包含createGraph
和drawGraph
兩個方法的物件。
IIFE解決的另一個問題就是這個例子。
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);
})
}
假設我們有一個ul
元素,其類別為list-group ,並且它有 5 個li
子元素。當我們點擊單一li
元素時,我們希望console.log
i
的值。
但我們想要的程式碼中的行為不起作用。相反,它會在對li
元素的任何點擊中記錄5
。我們遇到的問題是由於閉包的工作方式造成的。閉包只是函數記住其當前作用域、其父函數作用域和全域作用域中的變數引用的能力。當我們在全域範圍內使用var
關鍵字聲明變數時,顯然我們正在建立一個全域變數i
。因此,當我們單擊li
元素時,它會記錄5 ,因為這是我們稍後在回調函數中引用它時的i
值。
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);
}
這個解決方案之所以有效,是因為IIFE為每次迭代建立一個新範圍,並且我們捕獲i
的值並將其傳遞到currentIndex
參數中,因此當我們呼叫IIFE時,每次迭代的currentIndex
值都是不同的。
Function.prototype.apply
方法有什麼用?^ apply
呼叫一個函數,在呼叫時指定this
或該函數的「所有者」物件。
const details = {
message: 'Hello World!'
};
function getMessage(){
return this.message;
}
getMessage.apply(details); // returns 'Hello World!'
這個方法的工作方式類似於<a href="#28-what-is-the-use-functionprototypecall-method">Function.prototype.call</a>
唯一的差異是我們傳遞參數的方式。在apply
中,我們將參數作為陣列傳遞。
const person = {
name: "Marko Polo"
};
function greeting(greetingMessage) {
return `${greetingMessage} ${this.name}`;
}
greeting.apply(person, ['Hello']); // returns "Hello Marko Polo!"
Function.prototype.call
方法有什麼用?^此call
呼叫一個函數,指定呼叫時該函數的this
或「擁有者」物件。
const details = {
message: 'Hello World!'
};
function getMessage(){
return this.message;
}
getMessage.call(details); // returns 'Hello World!'
這個方法的工作方式類似於<a href="#27-what-is-the-use-functionprototypeapply-method">Function.prototype.apply</a>
唯一的差異是我們傳遞參數的方式。在call
中,我們直接傳遞參數,對於每個參數,
用逗號分隔它們。
const person = {
name: "Marko Polo"
};
function greeting(greetingMessage) {
return `${greetingMessage} ${this.name}`;
}
greeting.call(person, 'Hello'); // returns "Hello Marko Polo!"
Function.prototype.apply
和Function.prototype.call
有什麼差別?↑ apply
和call
之間的唯一區別是我們如何在被呼叫的函數中傳遞參數。在apply
中,我們將參數作為陣列傳遞,而在call
中,我們直接在參數列表中傳遞參數。
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]); // returns 15
reduceAdd.call(obj2, 1, 2, 3, 4, 5); // returns 15
Function.prototype.bind
的用法是什麼?↑ bind
方法傳回一個新綁定的函數
到特定的this
值或“所有者”物件,因此我們可以稍後在程式碼中使用它。 call
、 apply
方法立即呼叫函數,而不是像bind
方法那樣傳回一個新函數。
import React from 'react';
class MyComponent extends React.Component {
constructor(props){
super(props);
this.state = {
value : ""
}
this.handleChange = this.handleChange.bind(this);
// Binds the "handleChange" method to the "MyComponent" component
}
handleChange(e){
//do something amazing here
}
render(){
return (
<>
<input type={this.props.type}
value={this.state.value}
onChange={this.handleChange}
/>
</>
)
}
}
^函數式程式設計是一種聲明式程式設計範式或模式,它介紹如何使用表達式來計算值而不改變傳遞給它的參數的函數來建立應用程式。
JavaScript陣列具有map 、 filter 、 reduce方法,這些方法是函數式程式設計世界中最著名的函數,因為它們非常有用,而且它們不會改變或改變陣列,這使得這些函數變得純粹,並且JavaScript 支援閉包和高階函數,它們是函數式程式語言的一個特徵。
const words = ["Functional", "Procedural", "Object-Oriented"];
const wordsLength = words.map(word => word.length);
const data = [
{ name: 'Mark', isRegistered: true },
{ name: 'Mary', isRegistered: false },
{ name: 'Mae', isRegistered: true }
];
const registeredUsers = data.filter(user => user.isRegistered);
const strs = ["I", " ", "am", " ", "Iron", " ", "Man"];
const result = strs.reduce((acc, currentStr) => acc + currentStr, "");
^高階函數是可以傳回函數或接收具有函數值的一個或多個參數的函數。
function higherOrderFunction(param,callback){
return callback(param);
}
^ JavaScript 中的 __Functions__ 是一流物件,因為它們被視為該語言中的任何其他值。它們可以分配給變數,可以是稱為方法的物件的屬性,可以是陣列中的專案,可以作為參數傳遞給函數,也可以作為函數的值返回。函數與JavaScript中任何其他值之間的唯一區別是函數可以被呼叫。
Array.prototype.map
方法。function map(arr, mapCallback) {
// First, we check if the parameters passed are right.
if (!Array.isArray(arr) || !arr.length || typeof mapCallback !== 'function') {
return [];
} else {
let result = [];
// We're making a results array every time we call this function
// because we don't want to mutate the original array.
for (let i = 0, len = arr.length; i < len; i++) {
result.push(mapCallback(arr[i], i, arr));
// push the result of the mapCallback in the 'result' array
}
return result; // return the result array
}
}
正如Array.prototype.map
方法的MDN描述。
map() 方法建立一個新陣列,其中包含對呼叫陣列中的每個元素呼叫所提供函數的結果。
Array.prototype.filter
方法。function filter(arr, filterCallback) {
// First, we check if the parameters passed are right.
if (!Array.isArray(arr) || !arr.length || typeof filterCallback !== 'function')
{
return [];
} else {
let result = [];
// We're making a results array every time we call this function
// because we don't want to mutate the original array.
for (let i = 0, len = arr.length; i < len; i++) {
// check if the return value of the filterCallback is true or "truthy"
if (filterCallback(arr[i], i, arr)) {
// push the current item in the 'result' array if the condition is true
result.push(arr[i]);
}
}
return result; // return the result array
}
}
正如Array.prototype.filter
方法的 MDN 描述。
filter() 方法建立一個新陣列,其中包含透過所提供函數實現的測試的所有元素。
Array.prototype.reduce
方法。↑ ``js
函數reduce(arr,reduceCallback,initialValue){
// 首先,我們檢查傳遞的參數是否正確。
if (!Array.isArray(arr) || !arr.length || typeof reduceCallback !== 'function')
{
return [];
} 別的 {
// If no initialValue has been passed to the function we're gonna use the
let hasInitialValue = initialValue !== undefined;
let value = hasInitialValue ? initialValue : arr[0];
// first array item as the initialValue
// Then we're gonna start looping at index 1 if there is no
// initialValue has been passed to the function else we start at 0 if
// there is an initialValue.
for (let i = hasInitialValue ? 0 : 1, len = arr.length; i < len; i++) {
// Then for every iteration we assign the result of the
// reduceCallback to the variable value.
value = reduceCallback(value, arr[i], i, arr);
}
return value;
}
}
As the MDN description of the <code>Array.prototype.reduce</code> method.
__The reduce() method executes a reducer function (that you provide) on each element of the array, resulting in a single output value.__
###37. What is the __arguments__ object?
[↑](#the-questions "Back To Questions") The __arguments__ object is a collection of parameter values pass in a function. It's an __Array-like__ object because it has a __length__ property and we can access individual values using array indexing notation <code>arguments[1]</code> but it does not have the built-in methods in an array <code>forEach</code>,<code>reduce</code>,<code>filter</code> and <code>map</code>.
It helps us know the number of arguments pass in a function.
We can convert the <code>arguments</code> object into an array using the <code>Array.prototype.slice</code>.
函數一(){
返回 Array.prototype.slice.call(參數);
}
Note: __the <code>arguments</code> object does not work on ES6 arrow functions.__
函數一(){
返回參數;
}
常數二 = 函數 () {
返回參數;
}
常量三 = 函數三() {
返回參數;
}
const 四 = () => 參數;
四(); // 拋出錯誤 - 參數未定義
When we invoke the function <code>four</code> it throws a <code>ReferenceError: arguments is not defined</code> error. We can solve this problem if your enviroment supports the __rest syntax__.
const 四 = (...args) => args;
This puts all parameter values in an array automatically.
###38. How to create an object without a __prototype__?
[↑](#the-questions "Back To Questions") We can create an object without a _prototype_ using the <code>Object.create</code> method.
常數 o1 = {};
console.log(o1.toString());
// Logs [object Object] 取得此方法到Object.prototype
const o2 = Object.create(null);
// 第一個參數是物件「o2」的原型,在此
// case 將為 null 指定我們不需要任何原型
console.log(o2.toString());
// 拋出錯誤 o2.toString 不是函數
###39. Why does <code>b</code> in this code become a global variable when you call this function?
[↑](#the-questions "Back To Questions")
函數 myFunc() {
令a = b = 0;
}
myFunc();
The reason for this is that __assignment operator__ or __=__ has right-to-left __associativity__ or __evaluation__. What this means is that when multiple assignment operators appear in a single expression they evaluated from right to left. So our code becomes likes this.
函數 myFunc() {
令 a = (b = 0);
}
myFunc();
First, the expression <code>b = 0</code> evaluated and in this example <code>b</code> is not declared. So, The JS Engine makes a global variable <code>b</code> outside this function after that the return value of the expression <code>b = 0</code> would be 0 and it's assigned to the new local variable <code>a</code> with a <code>let</code> keyword.
We can solve this problem by declaring the variables first before assigning them with value.
函數 myFunc() {
令 a,b;
a = b = 0;
}
myFunc();
###40. <div id="ecmascript">What is __ECMAScript__</div>?
[↑](#the-questions "Back To Questions") __ECMAScript__ is a standard for making scripting languages which means that __JavaScript__ follows the specification changes in __ECMAScript__ standard because it is the __blueprint__ of __JavaScript__.
###41. What are the new features in __ES6__ or __ECMAScript 2015__?
[↑](#the-questions "Back To Questions")
* [Arrow Functions](#43-what-are-arrow-functions)
* [Classes](#44-what-are-classes)
* [Template Strings](#45-what-are-template-literals)
* __Enhanced Object literals__
* [Object Destructuring](#46-what-is-object-destructuring)
* [Promises](#50-what-are-promises)
* __Generators__
* [Modules](#47-what-are-es6-modules)
* Symbol
* __Proxies__
* [Sets](#48-what-is-the-set-object-and-how-does-it-work)
* [Default Function parameters](#53-what-are-default-parameters)
* [Rest and Spread](#52-whats-the-difference-between-spread-operator-and-rest-operator)
* [Block Scoping with <code>let</code> and <code>const</code>](#42-whats-the-difference-between-var-let-and-const-keywords)
###42. What's the difference between <code>var</code>, <code>let</code> and <code>const</code> keywords?
[↑](#the-questions "Back To Questions") Variables declared with <code>var</code> keyword are _function scoped_.
What this means that variables can be accessed across that function even if we declare that variable inside a block.
函數給MeX(showX) {
如果(顯示X){
var x = 5;
}
返回x;
}
console.log(giveMeX(false));
console.log(giveMeX(true));
The first <code>console.log</code> statement logs <code>undefined</code>
and the second <code>5</code>. We can access the <code>x</code> variable due
to the reason that it gets _hoisted_ at the top of the function scope. So our function code is intepreted like this.
函數給MeX(showX) {
變數 x; // 有一個預設值未定義
如果(顯示X){
x = 5;
}
返回x;
}
If you are wondering why it logs <code>undefined</code> in the first <code>console.log</code> statement remember variables declared without an initial value has a default value of <code>undefined</code>.
Variables declared with <code>let</code> and <code>const</code> keyword are _block scoped_. What this means that variable can only be accessed on that block <code>{}</code> on where we declare it.
函數給MeX(showX) {
如果(顯示X){
let x = 5;
}
返回x;
}
函數給MeY(顯示Y){
如果(顯示Y){
let y = 5;
}
返回y;
}
If we call this functions with an argument of <code>false</code> it throws a <code>Reference Error</code> because we can't access the <code>x</code> and <code>y</code> variables outside that block and those variables are not _hoisted_.
There is also a difference between <code>let</code> and <code>const</code> we can assign new values using <code>let</code> but we can't in <code>const</code> but <code>const</code> are mutable meaning. What this means is if the value that we assign to a <code>const</code> is an object we can change the values of those properties but can't reassign a new value to that variable.
###43. What are __Arrow functions__?
[↑](#the-questions "Back To Questions") __Arrow Functions__ are a new way of making functions in JavaScript. __Arrow Functions__ takes a little time in making functions and has a cleaner syntax than a __function expression__ because we omit the <code>function</code> keyword in making them.
//ES5版本
var getCurrentDate = 函數 (){
返回新日期();
}
//ES6版本
const getCurrentDate = () => new Date();
In this example, in the ES5 Version have <code>function(){}</code> declaration and <code>return</code> keyword needed to make a function and return a value respectively. In the __Arrow Function__ version we only need the <code>()</code> parentheses and we don't need a <code>return</code> statement because __Arrow Functions__ have a implicit return if we have only one expression or value to return.
//ES5版本
函數問候(名稱){
return '你好' + 名字 + '!';
}
//ES6版本
const 問候 = (name) => Hello ${name}
;
constgreet2 = 名稱 => Hello ${name}
;
We can also parameters in __Arrow functions__ the same as the __function expressions__ and __function declarations__. If we have one parameter in an __Arrow Function__ we can omit the parentheses it is also valid.
const getArgs = () => 參數
const getArgs2 = (...休息) => 休息
__Arrow functions__ don't have access to the <code>arguments</code> object. So calling the first <code>getArgs</code> func will throw an Error. Instead we can use the __rest parameters__ to get all the arguments passed in an arrow function.
常量資料 = {
結果:0,
數字:[1,2,3,4,5],
計算結果() {
// "this" here refers to the "data" object
const addAll = () => {
// arrow functions "copies" the "this" value of
// the lexical enclosing function
return this.nums.reduce((total, cur) => total + cur, 0)
};
this.result = addAll();
}
};
__Arrow functions__ don't have their own <code>this</code> value. It captures or gets the <code>this</code> value of lexically enclosing function or in this example, the <code>addAll</code> function copies the <code>this</code> value of the <code>computeResult</code> method and if we declare an arrow function in the global scope the value of <code>this</code> would be the <code>window</code> object.
###44. What are __Classes__?
[↑](#the-questions "Back To Questions") __Classes__ is the new way of writing _constructor functions_ in __JavaScript__. It is _syntactic sugar_ for using _constructor functions_, it still uses __prototypes__ and __Prototype-Based Inheritance__ under the hood.
//ES5版本
函數人(名字,姓氏,年齡,地址){
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
this.address = address;
}
Person.self = 函數(){
return this;
}
Person.prototype.toString = function(){
return "[object Person]";
}
Person.prototype.getFullName = function (){
return this.firstName + " " + this.lastName;
}
//ES6版本
類人{
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}`;
}
}
__Overriding Methods__ and __Inheriting from another class__.
//ES5版本
Employee.prototype = Object.create(Person.prototype);
函數 Employee(名字, 姓氏, 年齡, 地址, 職位名稱, 開始年份) {
Person.call(this, 名字, 姓氏, 年齡, 地址);
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 () {
返回“[物件員工]”;
}
//ES6版本
class Employee extends Person { //繼承自「Person」類
建構函數(名字,姓氏,年齡,地址,工作標題,開始年份){
super(firstName, lastName, age, address);
this.jobTitle = jobTitle;
this.yearStarted = yearStarted;
}
描述() {
return `I am ${this.getFullName()} and I have a position of ${this.jobTitle} and I started at ${this.yearStarted}`;
}
toString() { // 重寫「Person」的「toString」方法
return "[object Employee]";
}
}
So how do we know that it uses _prototypes_ under the hood?
類別東西{
}
函數 AnotherSomething(){
}
const as = new AnotherSomething();
const s = new Something();
console.log(typeof Something); // 記錄“函數”
console.log(AnotherSomething 類型); // 記錄“函數”
console.log(as.toString()); // 記錄“[物件物件]”
console.log(as.toString()); // 記錄“[物件物件]”
console.log(as.toString === Object.prototype.toString);
console.log(s.toString === Object.prototype.toString);
// 兩個日誌都回傳 true 表示我們仍在使用
// 底層原型,因為 Object.prototype 是
// 原型鏈的最後一部分和“Something”
// 和「AnotherSomething」都繼承自Object.prototype
###45. What are __Template Literals__?
[↑](#the-questions "Back To Questions") __Template Literals__ are a new way of making __strings__ in JavaScript. We can make __Template Literal__ by using the backtick or back-quote symbol.
//ES5版本
vargreet = '嗨,我是馬克';
//ES6版本
讓問候 = Hi I'm Mark
;
In the ES5 version, we need to escape the <code>'</code> using the <code>\\</code> to _escape_ the normal functionality of that symbol which in this case is to finish that string value. In Template Literals, we don't need to do that.
//ES5版本
var 最後一個字 = '\n'
' 在'
'我\n'
'鋼鐵人\n';
//ES6版本
讓最後一個單字=`
I
Am
鋼鐵人
`;
In the ES5 version, we need to add this <code>\n</code> to have a new line in our string. In Template Literals, we don't need to do that.
//ES5版本
函數問候(名稱){
return '你好' + 名字 + '!';
}
//ES6版本
const 問候 = 名稱 => {
返回Hello ${name} !
;
}
In the ES5 version, If we need to add an expression or value in a string we need to use the <code>+</code> or string concatenation operator. In Template Literals, we can embed an expression using <code>${expr}</code> which makes it cleaner than the ES5 version.
###46. What is __Object Destructuring__?
[↑](#the-questions "Back To Questions") __Object Destructuring__ is a new and cleaner way of __getting__ or __extracting__ values from an object or an array.
Suppose we have an object that looks like this.
常量僱員 = {
名字:“馬可”,
姓氏:“波羅”,
職位:“軟體開發人員”,
聘用年份:2017
};
The old way of getting properties from an object is we make a variable t