ES6學習筆記一 (塊級作用域、函數(shù))

第一章 塊級作用域綁定

letconst

都是不存在提升凡桥,聲明的都是塊級標識符
都禁止重聲明

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)

letconst聲明不會像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)建一個新的綁定,但該綁定不會添加為全局對象的屬性冀宴。換句話說灭贷,用letconst不能覆蓋全局變量,而只能遮蔽它略贮。

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ū)

第一章介紹letconst時我們介紹了臨時死區(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ù)的使用限制

  1. 每個函數(shù)最多只能聲明一個不定參數(shù)雷猪,而且一定要放在所有參數(shù)的末尾
  2. 不定參數(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);
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市鹉梨,隨后出現(xiàn)的幾起案子讳癌,更是在濱河造成了極大的恐慌,老刑警劉巖存皂,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件晌坤,死亡現(xiàn)場離奇詭異,居然都是意外死亡旦袋,警方通過查閱死者的電腦和手機骤菠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來疤孕,“玉大人商乎,你說我怎么就攤上這事〖婪В” “怎么了鹉戚?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長专控。 經(jīng)常有香客問我抹凳,道長,這世上最難降的妖魔是什么伦腐? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任赢底,我火速辦了婚禮,結(jié)果婚禮上柏蘑,老公的妹妹穿的比我還像新娘幸冻。我一直安慰自己,他們只是感情好辩越,可當我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布嘁扼。 她就那樣靜靜地躺著,像睡著了一般黔攒。 火紅的嫁衣襯著肌膚如雪趁啸。 梳的紋絲不亂的頭發(fā)上强缘,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天,我揣著相機與錄音不傅,去河邊找鬼旅掂。 笑死,一個胖子當著我的面吹牛访娶,可吹牛的內(nèi)容都是我干的商虐。 我是一名探鬼主播,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼崖疤,長吁一口氣:“原來是場噩夢啊……” “哼秘车!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起劫哼,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤叮趴,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后权烧,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體眯亦,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年般码,在試婚紗的時候發(fā)現(xiàn)自己被綠了妻率。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡板祝,死狀恐怖宫静,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情扔字,我是刑警寧澤囊嘉,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站革为,受9級特大地震影響扭粱,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜震檩,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一琢蛤、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧抛虏,春花似錦博其、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至沸毁,卻和暖如春峰髓,著一層夾襖步出監(jiān)牢的瞬間傻寂,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工携兵, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留疾掰,地道東北人。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓徐紧,卻偏偏與公主長得像静檬,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子并级,可洞房花燭夜當晚...
    茶點故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內(nèi)容

  • 函數(shù)參數(shù)的默認值 基本用法 在ES6之前拂檩,不能直接為函數(shù)的參數(shù)指定默認值,只能采用變通的方法死遭。 上面代碼檢查函數(shù)l...
    呼呼哥閱讀 3,382評論 0 1
  • 1.函數(shù)參數(shù)的默認值 (1).基本用法 在ES6之前广恢,不能直接為函數(shù)的參數(shù)指定默認值凯旋,只能采用變通的方法呀潭。
    趙然228閱讀 688評論 0 0
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)至非,斷路器钠署,智...
    卡卡羅2017閱讀 134,651評論 18 139
  • 一、ES6簡介 ? 歷時將近6年的時間來制定的新 ECMAScript 標準 ECMAScript 6(亦稱 ...
    一歲一枯榮_閱讀 6,071評論 8 25
  • 現(xiàn)在覺得自己除了理智荒椭,其他一無是處谐鼎。有時候可能感性一點,更開心趣惠。
    簡箋筧閱讀 131評論 0 0