第一章 塊級作用域綁定
let
和 const
都是不存在提升凡桥,聲明的都是塊級標識符
都禁止重聲明
var a = 30;
var message = 2;
// 這兩條都會拋出語法錯誤
let a = 40;
const message = 1;
每個const
聲明的常量必須進行初始化
const
定義的常量不能修改朴下,但是用const聲明的對象可以修改值
const name; // 語法錯誤:常量未初始化
const person = {
name: 'a'
};
person.name = 'b'; // 可以修改
// SyntaxError: "person" is read-only
person = {
name: 'Jone'
}
臨時死區(qū)(Temporal Dead Zone)
let
和const
聲明不會像var
一樣提升到作用域頂部朵耕,如果在聲明之前訪問這些變量葫隙,即使是相對安全的typeof
操作符也會觸發(fā)引用錯誤统屈。用let
來舉例(const也一樣)
if(1){ // 加不加if(1)結(jié)果一樣
// Uncaught ReferenceError: value is not defined
console.log(typeof value);
let value = 1;
}
console.log(typeof value2); // "undefined"
if(1){
let value2 = 1;
}
由于console.log(typeof value)
語句會拋出錯誤狂巢,因此用let定義并初始化變量value的語句不會執(zhí)行撑毛。此時的value還位于JavaScript社區(qū)所謂的臨時死區(qū)TDZ中。
JavaScript引擎在掃描代碼發(fā)現(xiàn)變量聲明時唧领,要么將它們提升至作用域頂部(var)藻雌,要么把聲明放到TDZ中(let、const)斩个。訪問TDZ中的變量會觸發(fā)運行時錯誤胯杭。只有執(zhí)行過變量聲明語句后,變量才會從TDZ中移除受啥,方可正常訪問做个。
上述的第二個例子 typeof 是在聲明變量value2的代碼塊外執(zhí)行的,此時value2并不在TDZ中滚局,這也就意味著不存在value2這個綁定居暖,typeof返回undefined
循環(huán)中的let
沒什么可說的,不用再使用IIFE了
循環(huán)中的const
for(const i=0; i<10; i++){ // Uncaught TypeError: Assignment to constant variable.
console.log(i)
}
var object = {a:1,b:1,c:1}; // 不會報錯
for(const key in object){
console.log(key)
}
第一個 for循環(huán)必然會報錯
第二個 不會報錯的原因是for-in和for-of循環(huán)中核畴,每次迭代不會像for循環(huán)一樣修改已有的綁定膝但,而是會創(chuàng)建一個新綁定
全局塊作用域綁定
在全局作用域中,var 聲明的變量會成為全局對象(瀏覽器環(huán)境中的window)的屬性谤草。這意味著var很可能會無意中覆蓋一個已經(jīng)存在的全局變量跟束。
var Test=1;
window.Test === Test; // true
如果在全局作用域中使用let或const莺奸,會在全局作用域下創(chuàng)建一個新的綁定,但該綁定不會添加為全局對象的屬性冀宴。換句話說灭贷,用let
或const
不能覆蓋全局變量,而只能遮蔽它略贮。
const foo = 1;
window.foo = 2;
console.log(foo); // 1
console.log(window.foo); // 2
塊級綁定最佳實踐的進化
ECMAScript6標準尚在開發(fā)中時甚疟,人們普遍認為應(yīng)該默認使用let
而不是var
。對很多JavaScript開發(fā)者而言逃延,let
實際上與他們想要的var
一樣览妖,直接替換符合邏輯。這種情況下揽祥,對于需要些保護的變量則要使用const讽膏。
然而,當更多開發(fā)者遷移到ECMAScript6后拄丰,另一種做法日益普及:默認使用const府树,只有確實需要改變變量的值時使用let
。因為大部分變量的值在初始化后不應(yīng)再改變料按,而預(yù)料外的變量值的改變是很多bug的源頭奄侠。
第二章 字符串和正則表達式
暫略
第三章 函數(shù)
1. 函數(shù)形參的默認值
JavaScript函數(shù)有一個特別的地方,無論在函數(shù)定義中聲明了多少形參载矿,都可以傳入任意數(shù)量的參數(shù)垄潮,也可以在定義函數(shù)時添加針對參數(shù)數(shù)量的處理邏輯,當已定義的形參無對應(yīng)的傳入?yún)?shù)時為期指定一個默認值恢准。
1.1 ECMAScript5中模擬默認參數(shù)
function makeRequest(url, timeout, callback){
timeout = timeout || 2000;
callback = callback || function(){}
}
這樣做的一個缺陷是 如果想指定給timeout的值就是0魂挂,也會被賦值2000甫题。所以更穩(wěn)妥的辦法是使用typeof
檢查參數(shù)類型
function makeRequest(url, timeout, callback){
timeout = (typeof timeout !== 'undefined')? timeout : 2000;
callback = (typeof callback !== 'undefined')? callback : function(){}
}
在流行的JavaScript庫中均使用類似的模式進行補全馁筐。
1.2 ECMAScript6中的默認參數(shù)值
function makeRequest(url, timeout=2000, callback=function(){}){
}
聲明函數(shù)時,可以為任意參數(shù)指定默認值坠非,在已指定默認值的參數(shù)后可以繼續(xù)聲明無默認值參數(shù)敏沉。如上例,callback可以無默認值炎码。
1.3 默認參數(shù)值對arguments對象的影響
ES5非嚴格模式下盟迟,函數(shù)命名參數(shù)的變化會體現(xiàn)在arguments對象中
function mixArgs(first, second){
console.log(first === arguments[0]); //true
console.log(second === arguments[1]); //true
first = 'c';
second = 'd';
console.log(first === arguments[0]); //true
console.log(second === arguments[1]); //true
}
嚴格模式下,first和second的值不會導(dǎo)致arguments改變潦闲。
function mixArgs(first, second){
"use strict";
console.log(first === arguments[0]); //true
console.log(second === arguments[1]); //true
first = 'c';
second = 'd';
console.log(first === arguments[0]); //false
console.log(second === arguments[1]); //false
}
在ES6函數(shù)使用默認參數(shù)值時攒菠,無論是否顯示定義了嚴格模式,arguments對象的行為都將與ES5嚴格模式下保持一致歉闰。默認參數(shù)值的存在使得arguments對象保持與命名參數(shù)分離辖众。
function mixArgs(first, second = 'b'){
console.log(arguments.length);
console.log(first === arguments[0]);
console.log(second === arguments[1]);
first = 'c';
second = 'd';
console.log(first === arguments[0]);
console.log(second === arguments[1]);
}
mixArgs('a');
// 1 true false false false false
1.4 默認參數(shù)表達式
關(guān)于默認參數(shù)值卓起,最有趣的挺特性可能是非原始值傳參。
function getValue(){
return 5;
}
function add(first, second = getValue()){
return first + second;
}
console.log(add(1,1)); // 2
console.log(add(1)); // 6
這段代碼中凹炸,如果不傳入最后一個參數(shù)戏阅,就會調(diào)用getValue()函數(shù)來得到正確的默認值。切記初次解析函數(shù)生命時啤它,不會調(diào)用getValue()方法奕筐,只有當調(diào)用add()函數(shù)且不傳入第二個參數(shù)時才會調(diào)用
let value = 5;
function getValue(){
return value++;
}
function add(first, second = getValue()){
return first + second;
}
add(1,1); // 2
add(1); // 6
add(1); // 7
注意,當使用函數(shù)調(diào)用結(jié)果作為默認參數(shù)值時变骡,如果忘記寫小括號离赫,例如,second=getValue塌碌, 則最終傳入的是對函數(shù)的引用笆怠,而不是函數(shù)調(diào)用的結(jié)果。
正因為默認參數(shù)實在函數(shù)調(diào)用時求值誊爹,所以可以使用先定義的參數(shù)作為后定義參數(shù)的默認值(反過來不可以)
function add(first, second = first){
return first + second;
}
add(1, 1); // 2
add(1); // 2
1.5 默認參數(shù)的臨時死區(qū)
第一章介紹let
和const
時我們介紹了臨時死區(qū)TDZ蹬刷,其實默認參數(shù)也有同樣的臨時死區(qū),在這里的參數(shù)不可訪問频丘。與let
聲明類似办成,定義參數(shù)時會為每個參數(shù)創(chuàng)建一個新的標識符綁定,該綁定在初始化之前不可被引用搂漠,如果試圖訪問會導(dǎo)致程序拋出錯誤迂卢。當調(diào)用函數(shù)時,會通過傳入的值或參數(shù)的默認值初始化該參數(shù)桐汤。
function getValue(value){
return value + 5;
}
function add(first, second = getValue(first)){
return first + second;
}
add(1,1); // 2
add(1); // 7
調(diào)用add(1, 1)和add(1)時實際相當于執(zhí)行以下代碼來創(chuàng)建fist和second參數(shù)值:
// add(1,1)時執(zhí)行的JavaScript代碼
let first = 1;
let second = 1;
// add(1)時的JavaScript代碼
let first = 1;
let second = getValue(first);
1.4節(jié)中提到過不可以 后定義的參數(shù)作為先定義參數(shù)的默認值而克。
function add(first=senond, second){
return first + second;
}
add(1,1) //2
// 執(zhí)行的函數(shù)
// let first = 1;
// let second = 1;
add(undefined, 1) //拋出錯誤
// 執(zhí)行的函數(shù)
// let first = second;
// let second = 1;
可見,調(diào)用add(undefined, 1)函數(shù)時怔毛,因為當first初始化時second尚未初始化员萍,所以會導(dǎo)致程序拋出錯誤,此時second尚處于臨時死區(qū)中拣度。
函數(shù)參數(shù)有自己的作用域和臨時死區(qū)碎绎,與其函數(shù)體的作用域是各自獨立的,也就是說參數(shù)的默認值不可訪問函數(shù)體內(nèi)聲明的變量抗果。
2. 處理無命名參數(shù)
到目前為止筋帖,本章中的示例使用到的參數(shù)都是命名參數(shù)。然而JavaScript的函數(shù)語法規(guī)定冤馏,無論函數(shù)已定義的命名參數(shù)有多少毛豆不限制調(diào)用時傳入的實際參數(shù)數(shù)量日麸,調(diào)用時總是可以傳入任意數(shù)量的參數(shù)。
2.1 ES5中的無命名參數(shù)
下面pick函數(shù)模仿了Underscore.js庫中的pick()方法逮光,返回一個給定對象的副本代箭,包含原始對象屬性的特定子集辕录。
function pick(object) {
let result = Object.create(null);
for (let i = 1, len = arguments.length; i < len; i++) {
result[arguments[i]] = object[arguments[i]];
}
return result;
}
let book = {
title: "Understanding ECMAScrpt6",
author: "NowhereToRun",
year: "2017"
};
let bookData = pick(book, "author", "year");
console.log(bookData);
關(guān)于pick函數(shù)應(yīng)該注意這樣幾件事情:
首先,并不容易發(fā)現(xiàn)這個函數(shù)可以接受任意數(shù)量的參數(shù)梢卸。
其次走诞,因為第一參數(shù)為命名參數(shù)并且已被使用,當你要查找需要拷貝的屬性名稱時蛤高,不得不從索引1而不是索引0開始遍歷arguments對象蚣旱。
而在ES6中,通過不定參數(shù)(rest parameters)的特性可以解決這個問題戴陡。
2.2 不定參數(shù)
function pick(object, ...keys) {
let result = Object.create(null);
for (let i = 0, len = keys.length; i < len; i++) {
result[keys[i]] = object[keys[i]];
}
return result;
}
不定參數(shù)keys包含的是object知乎傳入的所有參數(shù)(而arguments對象包含的則是所有傳入的參數(shù)塞绿,包括object)這樣一來你就可以放心地遍歷keys對象了。這種方法還有一個好處恤批,秩序看一眼函數(shù)就可以知曉該函數(shù)可以處理的參數(shù)數(shù)量异吻。
函數(shù)的length屬性統(tǒng)計的是函數(shù)命名參數(shù)的數(shù)量,不定參數(shù)的加入不會影響length屬性的值喜庞。在本例中诀浪,pick函數(shù)的length值為1,因為只會計算object延都。(即與改寫成為使用不定參數(shù)的函數(shù)之前一樣)
2.3 不定參數(shù)的使用限制
- 每個函數(shù)最多只能聲明一個不定參數(shù)雷猪,而且一定要放在所有參數(shù)的末尾
- 不定參數(shù)不能用于對象字面量setter之中
function pick(object, ...keys , last) {
// SyntaxError: Rest parameter must be last formal parameter
let result = Object.create(null);
for (let i = 0, len = keys.length; i < len; i++) {
result[keys[i]] = object[keys[i]];
}
return result;
}
let object = {
// SyntaxError: Setter function argument must not be a rest parameter
set name(...name){
// 執(zhí)行一些邏輯
}
}
2.4 不定參數(shù)對arguments對象的影響
無論是否使用不定參數(shù)晰房,arguments對象總是包含所有傳入函數(shù)的參數(shù)求摇。
3. 增強的Function構(gòu)造函數(shù)
Function構(gòu)造函數(shù)是JavaScript語法中很少被使用到的一部分,通常我們用它來動態(tài)創(chuàng)建新的函數(shù)殊者。這種構(gòu)造函數(shù)接受字符串形式的參數(shù)与境,分別為函數(shù)的參數(shù)及函數(shù)體。
var add = new Function("first", "second", "return first + second");
console.log(add(1, 1)); // 2
ES6中增強了Function構(gòu)造函數(shù)的功能猖吴,支持在創(chuàng)建函數(shù)時定義默認參數(shù)和不定參數(shù)摔刁。唯一需要做的是在參數(shù)名后面添加一個等號及一個默認值。
var add = new Function("first", "second = first", "return first + second");
console.log(add(1, 1)); // 2
console.log(add(1)); // 2
定義不定參數(shù)距误,只需在最后一個參數(shù)前添加...
var pickFirst = new Function("...args","return args[0]");
console.log(pickFirst(1,2,3,4)); // 1
4. 展開運算符
簡單粗暴簸搞,看個例子扁位,不解釋太多
let values = [25,50,75,100];
console.log(Math.max.apply(Math, values));
console.log(Math.max(...values));
可以將展開運算符與其他正常傳入的參數(shù)混合使用准潭。例如像限定返回的最小值為0.
let values = [-25, -50, -75, -100];
console.log(Math.max(...values, 0));
5. name屬性
由于在JavaScript中有多種定義函數(shù)的方式,因而辨別函數(shù)就是一項具有挑戰(zhàn)性的任務(wù)域仇,而且匿名函數(shù)表達式的廣泛使用更是加大了調(diào)試難度刑然。于是ES6中為所有的函數(shù)新增了name屬性。
function doSomething() { }
var doAnotherSomething = function () { };
console.log(doSomething.name); // doSomething
console.log(doAnotherSomething.name); // doAnotherSomething
var doSth = function doSthElse() { };
var person = {
get firstName() {
return "HeiHeiHei"
},
sayName: function () {
console.log(this.name);
}
}
console.log(doSth.name); // doSthElse
console.log(person.sayName.name); // sayName
console.log(person.firstName.name); // get firstName (undefined)
var bindName = function(){};
console.log(bindName.bind().name); // bound bindName
console.log((new Function).name); // anonymous
前兩個沒什么說的暇务。
第三個 doSth泼掠,可見權(quán)重怔软。(書上原話:函數(shù)表達式有一個名字,這個名字比函數(shù)本身被賦值的變量的權(quán)重高择镇,感覺好像不是一個意思)
第五個firstName挡逼,目前Chrome(59.0.3)測試是undefined
第六個,綁定函數(shù)的name屬性總是由被綁定函數(shù)的name屬性及字符串前綴bound
組成
第七個腻豌,通過Function構(gòu)造函數(shù)創(chuàng)建的函數(shù)家坎,其名稱將帶有前綴anonymous
切記,函數(shù)name屬性的值不一定引用同名變量吝梅,他只是協(xié)助調(diào)試用的額外信息虱疏,所以不能使用name屬性的值來獲取對于函數(shù)的引用
6. 明確函數(shù)的多重用途
ES5及早期版本中的函數(shù)具有多重功能,可以結(jié)合new使用苏携,函數(shù)內(nèi)的this值將指向一個新對象做瞪,函數(shù)最終會返回這個新對象。
function Person(name){
this.name = name;
}
var person = new Person("HaHa");
var noAPerson = Person("HaHa");
console.log(person); // Person {name: "HaHa"}
console.log(noAPerson); // undefined
console.log(name); // HaHa
給noAPerson賦值時右冻,沒有通過new關(guān)鍵字來調(diào)用Person()装蓬,最終返回undefined(因為是非嚴格模式,全局下會設(shè)置name屬性纱扭,嚴格模式下會直接報錯)矛物。通過new關(guān)鍵字調(diào)用Person()時才體現(xiàn)其能力。
在ES6中跪但,函數(shù)混亂的雙重身份終于將有一些改變履羞。
JavaScript函數(shù)有兩個不同的內(nèi)部方法[[Call]]和[[Construct]]。當通過new關(guān)鍵字調(diào)用時執(zhí)行的是[[Construct]]函數(shù)屡久,它負責創(chuàng)建一個通常稱為治理的新對象忆首,然后在執(zhí)行函數(shù)體,將this綁定到示例上被环;如果不通過new來調(diào)用函數(shù)糙及,則執(zhí)行[[Call]]函數(shù),從而直接執(zhí)行代碼中的函數(shù)體筛欢。具有[[Construct]]方法的函數(shù)被統(tǒng)稱為構(gòu)造函數(shù)浸锨。
切記,不是所有函數(shù)都有[[Construct]]方法版姑,因此不是所有函數(shù)都可以用new來調(diào)用柱搜,比如箭頭函數(shù)。
ES5中判斷函數(shù)被調(diào)用的方法
為了確定函數(shù)是被new調(diào)用剥险,通常使用instancsof聪蘸,但是也不完全可靠。
function Person(name) {
if (this instanceof Person) {
this.name = name;
} else {
return new Person(name);
// 或者直接拋出錯誤
// throw new Error("必須通過new關(guān)鍵字來調(diào)用")
}
}
var person = new Person("HaHa");
var person2 = Person("HaHa");
console.log(person); // Person {name: "HaHa"}
console.log(person2); // Person {name: "HaHa"}
// 下面這種寫法會錯誤的執(zhí)行,而且會修改person的值
var noAPerson = Person.call(person, "HeiHei");
console.log(noAPerson); // undefined
console.log(person); // Person {name: "HaHa"}
ES6 元屬性(Metaproperty)new.target
為了解決判斷函數(shù)是否通過new關(guān)鍵字調(diào)用的問題健爬,ES6引入了new.target這個元屬性控乾。元屬性是指非對象的屬性,其可以提供非對象目標的補充信息(例如new)娜遵。當調(diào)用函數(shù)的[[Construct]]方法時蜕衡,new.target被賦值為new操作符的目標,通常是新創(chuàng)建對象實例设拟,也就是函數(shù)體內(nèi)this的構(gòu)造函數(shù)衷咽;如果調(diào)用[[Call]]方法,則new.target的值為undefined蒜绽。
function Person(name){
if(typeof new.target === Person){
this.name = name;
} else{
throw new Error("必須通過new關(guān)鍵字來調(diào)用");
}
}
var person = new Person("haha");
console.log(person); // Person {name: "haha"}
var notAPerson = Person.call(person, "HeiHei"); // Error: 必須通過new關(guān)鍵字來調(diào)用
在函數(shù)外使用new.target是一個語法錯誤
7. 塊級函數(shù)
在ES3和早起版本中镶骗,在代碼塊中聲明一個塊級函數(shù)嚴格來說是一個語法錯誤,但是所有的瀏覽器仍然支持這個特性躲雅。但是很不幸鼎姊,每個瀏覽器對這個特性的支持都稍有不同,所以最好不要使用這個特性(最好的選擇是使用函數(shù)表達式)相赁。
為了遏制這種互相不兼容的行為相寇,ES5嚴格模式中引入了一個錯誤提示,當在代碼塊內(nèi)部聲明函數(shù)時程序會拋出錯誤:
"use strict";
if(1){
// 在ES5中拋出語法錯誤钮科,在ES6中不報錯
function doSomething(){}
}
在ES6中唤衫,會把doSomething視作一個塊級聲明,從而可以在定義該函數(shù)的代碼塊內(nèi)訪問和調(diào)用它绵脯。
在定義函數(shù)的代碼塊內(nèi)佳励,塊級函數(shù)會提升至頂部
"use strict";
if (1) {
console.log(typeof doSomething); // function
function doSomething() { }
doSomething();
}
console.log(typeof doSomething); // undefined
但是let定義的函數(shù)表達式不會提升至頂部
"use strict";
if (1) {
console.log(typeof doSomething); // ReferenceError: doSomething is not defined
let doSomething = function () { }
doSomething();
}
console.log(typeof doSomething);
非嚴格模式下的塊級函數(shù)
與嚴格模式下稍有不同,這些函數(shù)不在提升至代碼塊的頂部蛆挫,而是提升至外圍函數(shù)或全局作用域的頂部
if (1) {
console.log(typeof doSomething); // function
function doSomething() { }
doSomething();
}
console.log(typeof doSomething); // function
8. 箭頭函數(shù)
箭頭函數(shù)與傳統(tǒng)的JavaScript函數(shù)有些許不同赃承,主要集中在以下方面:
- 沒有this、super悴侵、arguments和new.target綁定 箭頭函數(shù)中的這些值由外圍最近一層非建投函數(shù)決定瞧剖。
- 不能通過new調(diào)用 箭頭函數(shù)沒有[[Construct]]方法,所以不能被用作構(gòu)造函數(shù)可免,如果通過new關(guān)鍵字調(diào)用建投函數(shù)抓于,程序會報錯。
- 沒有原型 由于不可以通過new方法調(diào)用浇借,因而沒有構(gòu)建原型的需求捉撮,所以箭頭函數(shù)不存在prototype這個屬性。
- 不可以改變this的綁定 函數(shù)內(nèi)部的this值不可以被改變逮刨,在函數(shù)的生命周期內(nèi)始終保持一致呕缭。
- 不支持arguments對象
- 不支持重復(fù)的命名參數(shù) 無論在嚴格還是費嚴格模式下堵泽,建投函數(shù)都不支持重復(fù)的命名參數(shù)修己;而在傳統(tǒng)函數(shù)的規(guī)定中恢总,只有在嚴格模式下才會不能有重復(fù)的命名參數(shù)。
箭頭函數(shù)同樣也有一個name屬性睬愤,這與其他函數(shù)的規(guī)則相同片仿。
箭頭函數(shù)語法
當箭頭函數(shù)只有一個參數(shù)時,可以直接寫參數(shù)名尤辱,箭頭緊隨其后砂豌,箭頭右側(cè)的表達式被求值后便立即返回,即使沒有顯示的返回語句光督,這個箭頭函數(shù)也可以返回傳入的第一個參數(shù)阳距,不需要更多的語法鋪墊。
let reflect = value => value;
// 相當于(babel轉(zhuǎn)換后的代碼)
var reflect = function reflect(value) {
return value;
};
如果傳入兩個或兩個以上的參數(shù)结借,要在參數(shù)的兩側(cè)添加一對小括號:
let sum = (num1, num2) => num1 + num2;
// 相當于
var sum = function sum(num1, num2) {
return num1 + num2;
};
如果沒有參數(shù)也要在聲明的時候?qū)懸粋€小括號
let getNmae = () => "NowhereToRun";
// 相當于
var getNmae = function getNmae() {
return "NowhereToRun";
};
如果函數(shù)有多個語句筐摘,可以像傳統(tǒng)的函數(shù)體一樣使用花括號包裹起來
let sum2 = (num1, num2) => {
let temp = num1 + num2;
return temp * num1;
}
// 相當于
var sum2 = function sum2(num1, num2) {
var temp = num1 + num2;
return temp * num1;
};
空函數(shù)
let doNothing = () => { };
// 相當于
var doNothing = function doNothing() {};
想在箭頭函數(shù)外返回一個對象字面量,則需要將該字面量包裹在小括號里(為了將其與函數(shù)體區(qū)分開來)
let getTempItem = id => ({ id: id, name: "Temp" });
// 相當于
var getTempItem = function getTempItem(id) {
return { id: id, name: "Temp" };
};
箭頭函數(shù)沒有this綁定
下面這段代碼PageHandler設(shè)計初衷是用來處理頁面上的交互船老,init初始化咖熟。
let PageHandler = {
id : "123456",
init: function(){
document.addEventListener('click', function(event){
this.doSomething(event.type); // Uncaught TypeError: this.doSomething is not a function
}, false);
},
doSomething: function(type){
console.log("Handling " + type + " for " + this.id);
}
};
但是this.doSomething(event.type);
中的this綁定的是document
(因為是document負責了調(diào)用)×希可以使用bind強行綁定
let PageHandler = {
id : "123456",
init: function(){
document.addEventListener('click', (function(event){
this.doSomething(event.type);
}).bind(this), false);
},
doSomething: function(type){
console.log("Handling " + type + " for " + this.id);
}
};
使用bind總覺的有些奇怪馍管,因為他實際上創(chuàng)建了另一個函數(shù)⌒胶可以使用箭頭函數(shù)來修正确沸。
箭頭函數(shù)沒有this綁定,必須通過查找作用域鏈來決定其值俘陷。如果箭頭函數(shù)被非箭頭函數(shù)包含张惹,this綁定的是最近一層費箭頭函數(shù)的this。否則this的值會被設(shè)置為undefined岭洲。
let PageHandler = {
id: "123456",
init: function () {
document.addEventListener('click', event =>
this.doSomething(event.type)
, false);
},
doSomething: function (type) {
console.log("Handling " + type + " for " + this.id);
}
};
此處的this和init函數(shù)里的this一致宛逗。
箭頭函數(shù)缺少正常函數(shù)所擁有的prototype屬性,它的設(shè)計初衷是“即用即棄”盾剩,所以不能用他來定義新的類型雷激。
var MyType = () => {};
new MyType(); // MyType is not a constructor
同樣,箭頭函數(shù)中的this值取決于該函數(shù)外部非箭頭函數(shù)的this值告私,且不能通過call屎暇、apply、或bind方法來改變this值驻粟。(使用不會報錯根悼,但是無效)
箭頭函數(shù)沒有arguments綁定
箭頭函數(shù)沒有自己的arguments對象凶异,且未來無論在哪個上下文中執(zhí)行,箭頭函數(shù)始終可以訪問外圍函數(shù)的arguments對象挤巡。
function createArrowFunctionReturningFirstArg(){
return () => arguments[0];
}
var arrowFunction = createArrowFunctionReturningFirstArg(5);
console.log(arrowFunction()); // 5
8. 尾調(diào)用優(yōu)化
ES6關(guān)于函數(shù)最有趣的變化可能是尾調(diào)用系統(tǒng)的引擎優(yōu)化剩彬。
function doSomething (){
return doSomethingElse(); // 尾調(diào)用
}
在ES5中,尾調(diào)用的實現(xiàn)與其他函數(shù)小勇的實現(xiàn)類似:創(chuàng)建一個新的棧幀(stack frame)矿卑,將其推入調(diào)用棧來表示函數(shù)調(diào)用喉恋。也就是說,在循環(huán)調(diào)用中母廷,每一個未用完的幀都會被保存在內(nèi)存中轻黑,當調(diào)用站變得過大時會造成程序問題。
ES6中的尾調(diào)用優(yōu)化
ES6中縮減了嚴格模式下尾調(diào)用棧的大星倮ァ(非嚴格模式下不受影響)氓鄙,如果滿足一下條件,尾調(diào)用不再創(chuàng)建新的棧幀业舍,而是清除并重用當前棧幀:
- 尾調(diào)用不訪問當前棧幀的變量(也就是說函數(shù)不是一個閉包)抖拦。
- 在函數(shù)內(nèi)部,尾調(diào)用是最后一條語句勤讽。
- 尾調(diào)用的結(jié)果作為函數(shù)值返回蟋座。
以下代碼滿足以上三個條件,可以被JavaScript引擎自動優(yōu)化:
"use strict";
function doSomething (){
return doSomethingElse();
}
如果做一個小的改動脚牍,不返回最終結(jié)果向臀,那么引擎就無法優(yōu)化當前函數(shù):
"use strict";
function doSomething (){
doSomethingElse();
}
同樣地,如果你定義了一個函數(shù)诸狭,在尾調(diào)用返回后執(zhí)行其他操作券膀,則函數(shù)也無法得到優(yōu)化:
"use strict";
function doSomething (){
return 1 + doSomethingElse();
}
在上面這個示例中,在返回doSomethingElse()的結(jié)果前將其加1驯遇,折足以去優(yōu)化空間芹彬。
還有另外一種意外情況,如果把函數(shù)調(diào)用的結(jié)果存儲在一個變量里叉庐,最后再返回這個變量舒帮,則可能導(dǎo)致引擎無法優(yōu)化:
function doSomething (){
var result = doSomethingElse();
return result;
}
可能最難避免的情況是閉包的使用,它可以訪問作用域中的所有變量陡叠,因而導(dǎo)致尾調(diào)用優(yōu)化失效:
"use strict";
function doSomething() {
var num = 1,
func = () => num;
// 無法優(yōu)化玩郊,這是一個閉包
return func();
}
如何利用尾調(diào)用優(yōu)化
實際上,尾調(diào)用的優(yōu)化發(fā)生在引擎背后枉阵,除非你嘗試優(yōu)化一個函數(shù)译红,否則無需思考此類問題。遞歸函數(shù)是其最主要的應(yīng)用場景兴溜,此時尾調(diào)用優(yōu)化的效果最顯著侦厚。
"use strict";
function factorial(n) {
if (n <= 1) {
return 1;
} else {
return n * factorial(n - 1);
}
}
由于在遞歸時執(zhí)行了乘法操作耻陕,因而當前版本的階乘函數(shù)無法被引擎優(yōu)化。如果n是一個非常大的數(shù)刨沦,則調(diào)用棧的尺寸就會不斷增長并存在最終導(dǎo)致棧溢出的潛在風險诗宣。
優(yōu)化這個函數(shù),首先要確保乘法不會在函數(shù)調(diào)用后執(zhí)行已卷,你可以通過默認參數(shù)來將乘法操作移除return語句梧田,結(jié)果函數(shù)可以攜帶著臨時結(jié)果進入到下一個迭代中淳蔼。下面這段代碼可以被ES6引擎優(yōu)化:
"use strict";
function factorial(n侧蘸, p = 1) {
if (n <= 1) {
return 1 * p;
} else {
let result = n * p;
return factorial(n - 1, result);
}
}