您好,登錄后才能下訂單哦!
JavaScript 的新解構賦值得名于數組或對象可以 “解構” 并提取出組成部分的概念。在 第 1 部分 中,學習了如何在局部變量中使用解構。它在函數參數聲明中也很有用。
如果某個函數需要一個對象,可以在函數開始之前,利用解構功能提取出該對象的相關部分。可通過向函數的參數添加解構語法來實現此目的,
let person = {
firstName: "Ted",
lastName: "Neward",
age: 45,
favoriteLanguages: [
"ECMAScript", "Java", "C#", "Scala", "F#"
]
}
function displayDetails({firstName, age}) {
console.log(`${firstName} is ${age} years old`);
}
displayDetails(person);
displayDetails 函數僅關心傳入的對象的 firstName 和 age 字段。向對象語法添加一個解構賦值,可在函數調用中有效地提取出這些值。調用語法本身保持不變,重構遺留代碼來使用新語法變得更容易。
也可以在函數中使用解構數組語法,但它沒有您即將看到的其他一些功能那么令人印象深刻。
ECMAScript 對函數參數執行了語法改動。為函數調用引入了默認參數值、剩余參數和展開運算符,
首先將介紹默認參數,這可能是 3 個概念中最容易理解的概念。
let sayHello = function(message = "Hello world!") {
console.log(message);
}
sayHello(); // prints "Hello world!"
sayHello("Howdy!"); // prints "Howdy!"
基本上講:在調用位置指定了一個參數,那么該參數將接受傳遞的值;未指定值,則會分配默認值。
與 第 1 部分 中介紹的一些更新一樣,新的默認參數實質上就是語法糖。以前的 ECMAScript 版本中,
var sayHello = function(message) {
if (message === undefined) {
message = "Hello world!";
}
console.log(message);
}
sayHello(); // prints "Hello world!"
sayHello("Howdy!"); // prints "Howdy!"
ECMAScript 庫中的一種更常見的做法是,定義函數或方法來接受一個或多個固定參數,后跟一組通過用戶定義方式細化或修改調用的可選參數。在過去,可以通過訪問靜默構建并傳遞給每個函數調用的內置 arguments 參數來實現此目的:
function greet(firstName) {
var args = Array.prototype.slice.call(arguments, greet.length);
console.log("Hello",firstName);
if (args !== undefined)
args.forEach(function(arg) { console.log(arg); });
}
greet("Ted");
greet("Ted", "Hope", "you", "like", "this!");
function greet(firstName, ...args) {
console.log("Hello",firstName);
args.forEach(function(arg) { console.log(arg); });
}
greet("Ted");
greet("Ted", "Hope", "you", "like", "this!");
注意,剩余參數(第一個清單中的 args)不需要測試存在與否;該語言可確保它將以長度為 0 的數組形式存在,即使沒有傳遞其他參數。
展開運算符(Spread operator)在某些方面與剩余參數的概念正好相反。剩余參數將會收集傳入某個給定調用的一些可選值,展開運算符獲取一個值數組并 “展開” 它們,基本上講,就是解構它們以用作被調用的函數的各個參數。
展開運算符的最簡單用例是將各個元素串聯到一個數組中:
let arr1 = [0, 1, 2];
let arr2 = [...arr1, 3, 4, 5];
console.log(arr2); // prints 0,1,2,3,4,5
沒有展開運算符語法,需要提取第一個數組中的每個元素并附加到第二個數組,然后才添加剩余元素。
也可以在函數調用中使用展開運算符;事實上,這是您最有可能使用它的地方:
function printPerson(first, last, age) {
console.log(first, last age);
}
let args = ["Ted", "Neward", 45];
printPerson(...args);
注意,不同于剩余參數,展開運算符是在調用點上使用,而不是在函數定義中使用。
本節將介紹最重要的更新。只需記住,JavaScript 程序中的原始語法仍然可行。
箭頭函數語法,這是一種用于創建函數字面量的速記符號。從 ECMAScript 6 開始,可以使用所謂的粗箭頭(與細箭頭相對)創建函數字面量,就像這樣:
let names = ["Ted","Jenni","Athen"];
names.forEach((n) => console.log(n));
箭頭函數也很容易理解:箭頭前的括號將參數捕獲到函數主體,箭頭本身表示函數主題的開頭。如果主體僅包含一條語句或表達式,則不需要使用花括號。如果主體包含多條語句或表達式,那么可以通過在箭頭后輸入花括號來表示它們:
let names = ["Ted","Jenni","Athen"];
names.forEach((n) => {
console.log(n)
});
如果只有一個參數,您可以選擇完全省略括號,
names.forEach(n => console.log(n));
注意,箭頭函數的主體是只有一個值的單個表達式,則無需顯式返回,而是應該將單一表達式隱式返回給箭頭函數的調用方。但是,如果主體不只一條語句或表達式,則必須使用花括號,而且所有返回的值都必須通過常用的 “return” 語法發回給調用方。
箭頭函數不能直接取代函數關鍵字。一般而言,您應該繼續使用 function 定義方法(即與一個對象實例關聯的函數)。為對象無關的場景保留箭頭函數,比如 Array.forEach 或 Array.map 調用的主體。因為箭頭函數對待 this 的方式與普通函數不同,所以在方法定義中使用它們可能導致意料之外的結果。
this 參數引用的對象上會調用一個方法,如下所示:
let bob = {
firstName: "Bob",
lastName: "Robertson",
displayMe: function() {
for (let m in this) {
console.log(m,"=",this[m]);
}
}
};
bob.displayMe();
上面的參數顯然引用了實例 bob,而且忠實地打印出 firstName、lastName 和 displayMe 方法(因為它也是該對象的成員)的名稱和值。
當從一個存在于全局范圍的函數引用 this 時,情況會變得有點怪異:
let displayThis = function() {
for (let m in this) {
console.log(m);
}
};
displayThis()
ECMAScript 將全局范圍定義為一個對象,所以當在全局范圍內的函數使用時,this 引用全局范圍對象,在上面的情況中,它忠實地打印出全局范圍的每個成員,包括頂級全局變量、函數和對象(比如上面的示例中的 “console”)。
出于這個原因,我們也可以在兩種不同的上下文中重用該函數,知道它每次將或多或少執行一些我們期望的操作:
let displayThis = function() {
for (let m in this) {
console.log(m);
}
};
displayThis(); // this == global object
let bob = {
firstName: "Bob",
lastName: "Robertson",
displayMe: displayThis
};
bob.displayMe(); // this == bob
可能此語法有點奇怪,但只要您理解了規則,就不是問題。直到您嘗試使用 ECMAScript 構造函數作為對象類型時,情況才會真正偏離主題:
function Person() {
// The Person() constructor defines "this" as an instance
// of itself
this.age = 0;
setInterval(function growUp() {
// In non-strict mode, the growUp() function defines "this"
// as the global object; thus, "this.age" refers to a global
// "age" value, not the one defined on the instance of Person
this.age++;
}, 1000);
}
var p = new Person();
// Every second, p.age is supposed to go up by one.
// But because the "this" in the "growUp" function literal
// refers to the global object, and not "p", p.age will
// never change from 0.
為了解決與 this 相關的定義問題,箭頭函數擁有所謂的詞法 this 綁定。這意味著箭頭函數在定義函數時使用 this 值,而不是在執行它時。
采用規則:完全理解新 this 規則可能需要一段時間。新箭頭函數規則并不總是這么直觀。作為開發人員,可以計劃對 “內聯” 函數使用箭頭函數,對方法使用傳統函數。如果這么做,各個方面都應按預期工作。
或許理解這一區別的最簡單方法是借助一個舊的 Node.js 對象 EventEmitter。回想一下,EventEmitter(獲取自 events 模塊)是一個簡單的發布-訂閱式消息系統:您可以在某個特定事件名稱上的發射器上注冊回調,當該事件被 “發出” 時,則按注冊的順序觸發回調。
如果向 EventEmitter 注冊一個遺留函數,捕獲的 this 將是在運行時確定的參數。但是如果您向 EventEmitter 注冊一個箭頭函數,this 將在定義箭頭函數時綁定:
let EventEmitter = require('events');
let ee = new EventEmitter();
ee.on('event', function() {
console.log("function event fired", this);
});
ee.on('event', () => {
console.log("arrow event fired", this);
});
var bob = {
firstName: "Bob",
lastName: "Robertson"
};
bob.handleEventLegacy = function() {
console.log("function event fired", this);
};
bob.handleEventArrow = () => {
console.log("arrow event fired", this);
};
ee.on('event', bob.handleEventLegacy);
ee.on('event', bob.handleEventArrow);
ee.emit('event');
在觸發函數事件時,this 被綁定到 EventEmitter 本身,而箭頭事件未綁定到任何目標(它們分別打印一個空對象)。
生成器函數旨在生成一個值流供其他方使用。許多函數語言都使用了生成器,它們在其中可能名為流 或序列。現在 ECMAScript 中也引入了它們。
要了解生成器的實際工作原理,需要稍作解釋。首先,想象一組簡單的名稱:
var names = ["Ted", "Charlotte", "Michael", "Matthew"];
var getName = (function() {
var current = 0;
return function() {
if (current > names.length)
return undefined;
else {
var temp = names[current];
current++;
return temp;
}
};
})();
console.log(getName()); // prints Ted
console.log(getName()); // prints Charlotte
console.log(getName()); // prints Michael
console.log(getName()); // prints Matthew
console.log(getName()); // prints undefined
起初,上面的函數返回函數的方式可能看起來很陌生。這是必要的,因為 getName 函數需要在多個函數調用中跟蹤它的狀態。在類似 C 的語言中,可以將狀態存儲在getName 函數內的靜態變量中,但像類似的 Java 和 C# 一樣,ECMAScript 不支持在函數中使用靜態變量。在這種情況下,我們將使用閉包,以便函數字面量在返回后繼續綁定到 “當前” 變量,使用該變量存儲自己的狀態。
要理解的重要一點是,此函數不會一次獲取一個有限的值序列(采用返回數組的形式),它一次獲取一個元素,直到沒有剩余的元素。
但是如果要返回的元素永遠用不完,該怎么辦?
var getName = (function() {
var current = 0;
return function() {
switch (current++) {
case 0: return "Ted";
case 1: return "Charlotte";
case 2: return "Michael";
case 3: return "Matthew";
default: return undefined;
}
};
})();<< span="">
從技術上講,您看到的仍是一個迭代器,但它的實現看起來與來自樣本的迭代器截然不同;這里沒有集合,只有一組硬編碼的值。
從本質上講,它是一個沒有關聯集合的迭代器,這突出了一個重要的事實:我們的函數生成的值的來源 現在深度封裝在離調用方很遠的地方。這進而引入了一個更有趣的想法:調用方可能不知道最初沒有集合,不知道生成的值永無止境。這就是一些語言所稱的無限流。
斐波納契數列(全球每種函數語言的 “Hello World” 等效程序)就是這樣一個無限流:
var fibo = (function() {
var prev1 = undefined;
var prev2 = undefined;
return function() {
if (prev1 == undefined && prev2 == undefined) {
prev1 = 0;
return 0;
}
if (prev1 == 0 && prev2 == undefined) {
prev1 = 1;
prev2 = 0;
return 1;
}
else {
var ret = prev1 + prev2;
prev2 = prev1;
prev1 = ret;
return ret;
}
};
})();
無限流 是一個從不會用完要返回的值的流。在這種情況下,斐波拉契數列沒有邏輯終點。
JavaScript 中的反應式編程非常復雜。如果您打算了解更多的信息,可以訪問 JavaScript 反應式編程 GitHub 頁面。
盡管起初看起來很奇怪,但無限流的概念是其他一些基于 ECMAScript 的有趣技術(比如反應式編程)的核心。想想如果我們將用戶事件(比如移動鼠標、單擊按鈕和按鍵)視為無限流,函數從流中獲取每個事件并進行處理,結果會怎樣?
構建無限流所需的代碼量非常大,所以 ECMAScript 6 定義了一種新語法(和一個新關鍵字)來讓代碼更加簡潔。在這里可以看到,我重寫了清單 17 中的示例:
function* getName() {
yield "Ted";
yield "Charlotte";
yield "Michael";
yield "Matthew";
}
let names = getName();
console.log(names.next().value);
console.log(names.next().value);
console.log(names.next().value);
console.log(names.next().value);
console.log(names.next().value);
同樣地,該函數將按順序打印出每個名稱。當它用完所有名稱時,它會不停地打印 “undefined”。在語法上,yield 關鍵字看起來類似于 return,但事實上,它表示 “返回但記住我在此函數中的位置,以便下次調用它時,從離開的位置開始執行。”這顯然比傳統的 return 更復雜。
生成器的使用與第一個示例稍微不同:我們捕獲了 getName 函數的返回值,然后像迭代器一樣使用該對象。這是 ECMAScript 6 中的一個特意的設計決定。從技術上講,生成器函數返回一個 Generator 對象,該對象用于從生成器函數獲取各個值。新語法旨在盡可能地模擬迭代器。
談到迭代器,還有最后一個需要知道的語法更改。
經典的 for 循環在 ECMAScript 6 具有了新形式,這是由于添加了一個輔助關鍵字:of。在許多方面,新語法與 for-in 沒多大區別,但它支持生成器函數。
返回到清單 19 中的斐波納契數列,這是向函數添加 for-of 關鍵字時發生的情況:
function* fibo() { // a generator function
yield 0;
yield 1;
let [prev, curr] = [0, 1];
while (true) {
[prev, curr] = [curr, prev + curr];
yield curr;
}
}
for (let n of fibonacci()) {
console.log(n);
// By the way, this is an infinite stream, so this loop
// will never terminate unless you break out of it
}
for-of 和 for-in 之間存在著細微區別,但在大多數情況下,您可以使用 for-of 直接取代舊語法。它添加了隱式使用生成器的能力 — 就像我們在無限流示例中使用 getName() 執行的操作一樣。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。