1. ECMAScript 新特性整理匯總?cè)?/h1>

        共 24996字,需瀏覽 50分鐘

         ·

        2021-07-27 20:13

        來(lái)源 | 致前端 - https://www.zhiqianduan.com


        1、ECMAScript 概述

        ECMAScript他也是一門腳本語(yǔ)言,一般縮寫為ES,通常會(huì)把他看作為JavaScript的標(biāo)準(zhǔn)規(guī)范。
        但實(shí)際上JavaScript是ECMAScript的擴(kuò)展語(yǔ)言,因?yàn)镋CMAScript只是提供了最基本的語(yǔ)法,通俗點(diǎn)來(lái)說(shuō)只是約定了代碼的如何編寫,例如該怎么樣定義變量或函數(shù),怎樣去實(shí)現(xiàn)分支或者循環(huán)之類的語(yǔ)句,這只是停留在語(yǔ)言層面,并不能完成應(yīng)用中的實(shí)際功能的開發(fā)。
        JavaScript實(shí)現(xiàn)了標(biāo)準(zhǔn)開發(fā),在語(yǔ)言基礎(chǔ)上做了一定的擴(kuò)展,使得可以在瀏覽器環(huán)境中操作DOM和BOM,在Node環(huán)境可以讀寫文件等操作。
        總的來(lái)說(shuō)瀏覽器環(huán)境中的JavaScript等于ECMAScript加上web所提供的API,也就是DOM和BOM。
        在Node環(huán)境中所使用的JavaScript,實(shí)際上就等于是ECMAScript加上Node所提供的一系列API。例如fs或者net這樣的內(nèi)置模塊。
        2015年開始ECMAScript就保持每年一個(gè)大版本的迭代。JavaScript這門語(yǔ)言的變得越來(lái)越高級(jí),越來(lái)越便捷。

        2、 let 與塊級(jí)作用域

        在ES2015之前,ECMAScript當(dāng)中只有全局作用域和函數(shù)作用域兩種類型的作用域。在ES2015中又新增了一個(gè)塊級(jí)作用域。

        塊指的就是代碼中用一對(duì){}所包裹起來(lái)的范圍,例如if語(yǔ)句和for語(yǔ)句中的{}都會(huì)產(chǎn)生這里所說(shuō)的塊。

        if (true) {    consoel.log('yd');}
        for (var i = 0; i < 10; i++) { console.log('yd');}

        在ECMAScript2015以前,塊是沒有單獨(dú)的作用域的,這就導(dǎo)致在塊中定義的成員外部也可以訪問(wèn)到。

        例如在if當(dāng)中去定義了一個(gè)foo的變量,然后在if的外面打印這個(gè)foo,結(jié)果也是可以正常打印出來(lái)的。

        if (true) {    var foo = 'yd';}console.log(foo); // yd

        這一點(diǎn)對(duì)于復(fù)雜代碼是非常不利的,也是不安全的,有了塊級(jí)作用域之后,可以在代碼當(dāng)中通過(guò)let這個(gè)新的關(guān)鍵詞聲明變量。

        他的用法和var是一樣的,只不過(guò)通過(guò)let聲明的變量他只能在所聲明的這個(gè)代碼塊中被訪問(wèn)到。

        if (true) {    let foo = 'yd';}console.log(foo); // foo is not defined

        在快級(jí)內(nèi)定義的成員,外部是無(wú)法訪問(wèn)的。

        這樣一個(gè)特性非常適合聲明for循環(huán)當(dāng)中的計(jì)數(shù)器。

        for (let i = 0; i < 3; i++) {    for (let i = 0; i < 3; i++) {        console.log(i);     }}

        3、const

        他可以用來(lái)去聲明一個(gè)只讀的恒量或者叫常量,他的特點(diǎn)就是在let的基礎(chǔ)上多了一個(gè)只讀特性。

        所謂只讀指的就是變量一旦聲明過(guò)后就不能夠再被修改,如果在聲明過(guò)后再去修改這個(gè)成員就會(huì)出現(xiàn)錯(cuò)誤。

        const name = 'yd';
        name = 'zd'; // 錯(cuò)誤

        這里要注意的問(wèn)題const所聲明的成員不能被修改,并不是說(shuō)不允許修改恒量中的屬性成員。下面這種情況是允許的。

        const obj = {}obj.name = 'yd';

        4、 數(shù)組的解構(gòu)

        ECMAScript2015新增了從數(shù)組或?qū)ο笾蝎@取指定元素的一種快捷方式,這是一種新的語(yǔ)法,這種新語(yǔ)法叫做解構(gòu)。

        const arr = [100, 200, 300];const [foo, bar, baz] = arr;console.log(foo, bar, baz);

        如果只是想獲取其中某個(gè)位置所對(duì)應(yīng)的成員,例如只獲取第三個(gè)成員, 這里可以把前兩個(gè)成員都刪掉。但是需要保留對(duì)應(yīng)的逗號(hào)。確保解構(gòu)位置的格式與數(shù)組是一致的。這樣的話就能夠提取到指定位置的成員。

        const [, , baz] = arr;console.log(baz);

        除此之外還可以在解構(gòu)位置的變量名之前添加...表示提取從當(dāng)前位置開始往后的所有成員,最終所有的結(jié)果會(huì)放在一個(gè)數(shù)組當(dāng)中。

        const [foo, ...rest] = arr;console.log(rest);

        三個(gè)點(diǎn)的用法只能在解構(gòu)位置的最后一個(gè)成員上使用,另外如果解構(gòu)位置的成員個(gè)數(shù)小于被解構(gòu)的數(shù)組長(zhǎng)度,就會(huì)按照從前到后的順序去提取,多出來(lái)的成員就不會(huì)被提取。

        反之如果解構(gòu)位置的成員大于數(shù)組長(zhǎng)度,那么提取到的就是undefined。這和訪問(wèn)數(shù)組當(dāng)中一個(gè)不存在的下標(biāo)是一樣的。

        const [foo, bar, baz, more] = arr;console.log(more); // undefined

        如果需要給提取到的成員設(shè)置默認(rèn)值,這種語(yǔ)法也是支持的,只需要在解構(gòu)變量的后面跟上一個(gè)等號(hào),然后后面寫上一個(gè)默認(rèn)值。

        const [foo, bar, baz, more = 'default value'] = arr;console.log(more);

        5、 對(duì)象的解構(gòu)

        對(duì)象也同樣可以被解構(gòu),不過(guò)對(duì)象的結(jié)構(gòu)需要去根據(jù)屬性名去匹配提取,而不是位置。

        const obj = { name: 'yd', age: 18 };

        解構(gòu)他里面的成員就是在以前變量位置去使用一個(gè)對(duì)象字面量的{}, 然后在{}里同樣也是提取出來(lái)的數(shù)據(jù)所存放的變量名,不過(guò)這里的變量名還有一個(gè)很重要的作用就是去匹配被解構(gòu)對(duì)象中的成員,從而去提取指定成員的值。

        const obj = { name: 'yd', age: 18 };
        const { name } = obj;

        解構(gòu)對(duì)象的其他特點(diǎn)基本上和解構(gòu)數(shù)組是完全一致的。未匹配到的成員返回undefined,也可以設(shè)置默認(rèn)值。

        在對(duì)象當(dāng)中有一個(gè)特殊的情況,解構(gòu)的變量名是被解構(gòu)對(duì)象的屬性名,所以說(shuō)當(dāng)前作用域中如果有這個(gè)名稱就會(huì)產(chǎn)生沖突。這個(gè)時(shí)候可以使用重命名的方式去解決這個(gè)問(wèn)題。

        const obj = { name: 'yd', age: 18 };
        const { name: name1 } = obj;
        console.log(name1);

        解構(gòu)對(duì)象的應(yīng)用場(chǎng)景比較多,不過(guò)大部分的場(chǎng)景都是為了簡(jiǎn)化代碼,比如代碼中如果大量用到了console對(duì)象的方法,可以先把這個(gè)對(duì)象單獨(dú)解構(gòu)出來(lái),然后再去使用獨(dú)立的log方法。

        const { log } = console;log('1');

        6、模板字符串

        傳統(tǒng)定義字符串的方式需要通過(guò)'或者是"來(lái)標(biāo)識(shí)。ES2015新增了模板字符串,使用反引號(hào)`聲明,。如果在字符串中需要使用`,可以使用斜線去轉(zhuǎn)譯。

        在模板字符串當(dāng)中可以支持多行字符串。

        const str = `123456`

        模板字符串當(dāng)中還支持通過(guò)插值表達(dá)式的方式在字符串中去嵌入所對(duì)應(yīng)的數(shù)值,在字符串中可以使用${name}就可以在字符串當(dāng)中嵌入name變量中的值。

        const name = 'yd';const age = 18;
        const str = `my name is ${name}, I am ${age} years old`;

        那種方式會(huì)比之前字符串拼接方式要方便的多頁(yè)更直觀一點(diǎn),不容易寫錯(cuò),事實(shí)上${}里面的內(nèi)容就是標(biāo)準(zhǔn)的JavaScript也就是說(shuō)這里不僅僅可以嵌入變量,還可以嵌入任何標(biāo)準(zhǔn)的js語(yǔ)句。

        7、 帶標(biāo)簽的模板字符串

        模板字符串還有一個(gè)更高級(jí)的用法,就是在定義模板字符串的時(shí)候可以在前面添加一個(gè)標(biāo)簽,這個(gè)標(biāo)簽實(shí)際上是一個(gè)特殊的函數(shù)。添加標(biāo)簽會(huì)調(diào)用這個(gè)函數(shù)。

        const name = 'yd';const age = 18;
        const result = tag`My name is ${name}, I am ${age} years old`;

        函數(shù)可以接收到一個(gè)數(shù)組參數(shù),是模板字符串內(nèi)容分割過(guò)后的結(jié)果。

        const tag = (params) => {    consoel.log(params); // ['My name is ', ' I am ', ' years old'];}

        除了這個(gè)數(shù)組以外,還可以接收到所有在這個(gè)模板字符串中出現(xiàn)的表達(dá)式的返回值。

        const tag = (params, name, age) => {    consoel.log(params, name, age); // ['My name is ', ' I am ', ' years old']; 'yd' 18}
        const str = tag`hello ${'world'}`;

        8、字符串?dāng)U展方法

        字符串對(duì)象存在幾個(gè)非常常用的方法。分別是includes,startsWith和endsWith。

        1). startWith

        如果想要知道這個(gè)字符串是否以Error開頭。

        console.log(message.startsWith('Error')); // true

        2). endsWith

        同理如果想要知道這個(gè)字符串是否以.結(jié)尾。

        console.log(message.endsWith('.')); // true

        3). includes

        如果需要明確的是字符串中間是否包含某個(gè)內(nèi)容。

        console.log(message.includes('foo')); // true

        9、 函數(shù)參數(shù)默認(rèn)值

        以前想要為函數(shù)中的參數(shù)去定義默認(rèn)值需要在函數(shù)體中通過(guò)邏輯代碼來(lái)實(shí)現(xiàn)。

        function foo (enable) {    enable = enable === undefined ? true : enable;    console.log(enable); // false}
        foo(false);

        有了參數(shù)默認(rèn)值這個(gè)新功能以后,可以直接在形參的后面直接通過(guò)等號(hào)去設(shè)置一個(gè)默認(rèn)值。

        function foo (enable = true) {    console.log(enable); // false}
        foo(false);

        如果有多個(gè)參數(shù)的話,帶有默認(rèn)值的這種形參一定要出現(xiàn)在參數(shù)列表的最后。

        function foo (bar, enable = true) {    console.log(enable); // false}
        foo(false);

        10、剩余參數(shù)

        對(duì)于未知個(gè)數(shù)的參數(shù),以前都是使用arguments對(duì)象去獲取,arguments對(duì)象實(shí)際上是一個(gè)偽數(shù)組,在ES2015當(dāng)中新增了一個(gè)...操作符,也就是剩余操作符,可以在函數(shù)的形參前面加上..., 此時(shí)這個(gè)形參args就會(huì)以數(shù)組的形式去接收從當(dāng)前這個(gè)參數(shù)的位置開始往后所有的實(shí)參。

        // function foo() {//     console.log(arguments); // 參數(shù)集合// }
        function foo (...args) => { console.log(args); // 參數(shù)集合}
        foo(1, 2, 3, 4);

        因?yàn)榻邮盏氖撬械膮?shù),所以這種操作符只能出現(xiàn)在形參列表的最后一位,并且只可以使用一次。

        11、展開數(shù)組

        ...操作符除了可以收起剩余數(shù)據(jù)這還有一種spread的用法,意思就是展開。

        例如這里有一個(gè)數(shù)組,想要把數(shù)組當(dāng)中的每一個(gè)成員按照次序傳遞給console.log方法,最原始的辦法是通過(guò)下標(biāo)一個(gè)一個(gè)去找到數(shù)組當(dāng)中的每一個(gè)元素,分別傳入到console.log方法當(dāng)中。

        在ES2015當(dāng)中就沒有必要這么麻煩了,可以直接去調(diào)用console的log方法,然后通過(guò)...的操作符展開這里的數(shù)組。...操作符會(huì)把數(shù)組當(dāng)中的每一個(gè)成員按照次序傳遞到列表當(dāng)中。

        console.log( ...arr );

        12、 箭頭函數(shù)

        在ECMAScript當(dāng)中簡(jiǎn)化了函數(shù)表達(dá)式的定義方式允許使用=>這種類似箭頭的符號(hào)來(lái)去定義函數(shù),那這種函數(shù)一來(lái)簡(jiǎn)化了函數(shù)的定義,二來(lái)多了一些特性具體來(lái)看。

        傳統(tǒng)來(lái)定義一個(gè)函數(shù)需要使用function關(guān)鍵詞,現(xiàn)在可以使用ES2015來(lái)去定義一個(gè)完全相同的函數(shù)。

        function inc (number) {    return number + 1;}
        const inc = n => n + 1;

        此時(shí)你會(huì)發(fā)現(xiàn),相比于普通的函數(shù),剪頭函數(shù)確實(shí)大大簡(jiǎn)化了所定義函數(shù)這樣一些相關(guān)的代碼。

        剪頭函數(shù)的左邊是參數(shù)列表,如果有多個(gè)參數(shù)的話可以使用()包裹起來(lái),剪頭的右邊是函數(shù)體。

        如果在這個(gè)函數(shù)的函數(shù)體內(nèi)需要執(zhí)行多條語(yǔ)句,同樣可以使用{}去包裹。如果只有一句代碼可以省略{}。

        const inc = (n , m) => {    return  n + 1;};

        對(duì)比普通函數(shù)和剪頭函數(shù)的寫法你會(huì)發(fā)現(xiàn),使用剪頭函數(shù)會(huì)讓代碼更簡(jiǎn)短,而且更易讀。

        1. this

        相比普通函數(shù),箭頭函數(shù)有一個(gè)很重要的變化就是不會(huì)改變this的指向。

        定義一個(gè)person對(duì)象,然后在這個(gè)對(duì)象當(dāng)中去定義一個(gè)name屬性,然后再去定義一個(gè)sayHi的方法,這個(gè)方法中可以使用this去獲取當(dāng)前對(duì)象。

        const person = {    name: 'yd',    sayHi: function() {        console.log(this.name);    }}
        person.sayHi(); // yd

        這里把sayHi改為箭頭函數(shù)的方式。這個(gè)時(shí)候打印出來(lái)的name就是undefined

        const person = {    name: 'yd',    sayHi: () => {        console.log(this.name);    }}
        person.sayHi(); // undefined

        這就是箭頭函數(shù)和普通函數(shù)最重要的區(qū)別,在剪頭函數(shù)當(dāng)中沒有this的機(jī)制。所以說(shuō)不會(huì)改變this的指向。

        也就是說(shuō)在剪頭函數(shù)的外面this是什么,在里面拿到的就是什么,任何情況下都不會(huì)發(fā)生改變。

        13. 對(duì)象字面量的增強(qiáng)

        傳統(tǒng)的字面量要求必須在{}里面使用屬性名: 屬性值這種語(yǔ)法。即便說(shuō)屬性的值是一個(gè)變量,那也必須是屬性名: 變量名, 而現(xiàn)在如果變量名與添加到對(duì)象中的屬性名是一樣的,可以省略掉:變量名。

        const bar = '123';const obj = {    key: 'value',    bar}

        除此之外如果需要為對(duì)象添加一個(gè)普通的方法,現(xiàn)在可以省略里面的:function。

        const obj = {    method1 () {        console.log('method1');    }}
        console.log(obj)

        需要注意的是這種方法的背后他實(shí)際上就是普通的function,也就是說(shuō)如果通過(guò)對(duì)象去調(diào)用這個(gè)方法,那么內(nèi)部的this就會(huì)指向當(dāng)前對(duì)象。

        14. 動(dòng)態(tài)屬性名

        另外對(duì)象字面量還有一個(gè)很重要的變化就是,他可以使用表達(dá)式的返回值作為對(duì)象的屬性名。以前如果說(shuō)要為對(duì)象添加一個(gè)動(dòng)態(tài)的屬性名,只能在對(duì)象創(chuàng)建過(guò)后,然后通過(guò)索引器的方式也就是[]來(lái)去動(dòng)態(tài)添加。

        const obj = {};
        obj[Math.random()] = 123;

        在ES2015過(guò)后,對(duì)象字面量的屬性名直接可以通過(guò)[]直接去使用動(dòng)態(tài)的值了,這樣一個(gè)特性叫做計(jì)算屬性名,具體的用法就是在屬性名的位置用[]包起來(lái)。

        在里面就可以使用任意的表達(dá)式了。這個(gè)表達(dá)式的執(zhí)行結(jié)果將會(huì)作為這個(gè)對(duì)象的屬性名。

        const obj = {    [Math.random()]: 123,}

        15. Object.assign

        這個(gè)方法可以將多個(gè)源對(duì)象當(dāng)中的屬性復(fù)制到一個(gè)目標(biāo)對(duì)象當(dāng)中,如果對(duì)象當(dāng)中有相同的屬性,那么源對(duì)象當(dāng)中的屬性就會(huì)覆蓋掉目標(biāo)對(duì)象的屬性。

        這里所說(shuō)的源對(duì)象和目標(biāo)對(duì)象他們都是普通的對(duì)象,只不過(guò)用處不同,是從源對(duì)象當(dāng)中取,然后往目標(biāo)對(duì)象當(dāng)中放。

        例如這里先定義一個(gè)source1對(duì)象,在這個(gè)對(duì)象當(dāng)中定義一個(gè)a屬性和一個(gè)b屬性。然后再來(lái)定義一個(gè)target對(duì)象,這個(gè)對(duì)象當(dāng)中也定義一個(gè)a屬性,還有一個(gè)c屬性。

        const source1 = {    a: 123,    b: 123,}
        const target = { a: 456, c: 456}

        Object.assign支持傳入任意個(gè)數(shù)的參數(shù),其中第一個(gè)參數(shù)就是目標(biāo)對(duì)象,也就是說(shuō)所有源對(duì)象當(dāng)中的屬性都會(huì)復(fù)制到目標(biāo)對(duì)象當(dāng)中。這個(gè)方法的返回值也就是這個(gè)目標(biāo)對(duì)象。

        const result = Object.assign(target, source1);
        console.log(target, result === target); {a: 123, c: 456, b: 123 }// true

        Object.assign用來(lái)為options對(duì)象參數(shù)設(shè)置默認(rèn)值也是一個(gè)非常常見的應(yīng)用場(chǎng)景。

        const default = {    name: 'yd',    age: 18}
        const options = Object.assign(default, opt);

        16. Object.is

        is方法用來(lái)判斷兩個(gè)值是否相等。在此之前ECMAScript當(dāng)中去判斷兩個(gè)值是否相等可以使用==運(yùn)算符。或者是===嚴(yán)格相等運(yùn)算符。

        這兩者是區(qū)別是==會(huì)在比較之前自動(dòng)轉(zhuǎn)換數(shù)據(jù)類型,那也就會(huì)導(dǎo)致0 == false這種情況是成立的。

        而===就是嚴(yán)格去對(duì)比兩者之間的數(shù)值是否相同。因?yàn)?和false他們之間的類型不同所以說(shuō)他們是不會(huì)嚴(yán)格相等的。

        但是嚴(yán)格相等運(yùn)算符他也有兩個(gè)特殊情況,首先就是對(duì)于數(shù)字0,他的正負(fù)是沒有辦法區(qū)分的。

        其次對(duì)于NaN, 兩個(gè)NaN在===比較時(shí)是不相等的。以前認(rèn)為NaN是一個(gè)非數(shù)字,也就是說(shuō)他有無(wú)限種可能,所以兩個(gè)NaN他是不相等的,但在今天看來(lái),NaN他實(shí)際上就是一個(gè)特別的值,所以說(shuō)兩個(gè)NaN他應(yīng)該是完全相等的。

        所以在ES2015中就提出了一種新的同值比較的算法來(lái)解決這個(gè)問(wèn)題,通過(guò)Obejct.is正負(fù)零就可以被區(qū)分開,而且NaN也是等于NaN的。

        Object.is(+0, -0); // falseObject.is(NaN, NaN); // true

        不過(guò)一般情況下根本不會(huì)用到這個(gè)方法,大多時(shí)候還是使用嚴(yán)格相等運(yùn)算符,也就是===。

        17. Proxy

        專門為對(duì)象設(shè)置訪問(wèn)代理器的,那如果你不理解什么是代理可以想象成門衛(wèi),也就是說(shuō)不管你進(jìn)去那東西還是往里放東西都必須要經(jīng)過(guò)這樣一個(gè)代理。

        通過(guò)Proxy就可以輕松監(jiān)視到對(duì)象的讀寫過(guò)程,相比于defineProperty,Proxy的功能要更為強(qiáng)大甚至使用起來(lái)也更為方便。

        通過(guò)new Proxy的方式創(chuàng)建代理對(duì)象。

        Proxy構(gòu)造函數(shù)的第一個(gè)參數(shù)就是需要代理的對(duì)象,第二個(gè)參數(shù)是代理對(duì)象處理對(duì)象,這可以通過(guò)get方法來(lái)去監(jiān)視屬性的訪問(wèn),通過(guò)set方法來(lái)截取對(duì)象當(dāng)中設(shè)置屬性的過(guò)程。

        const person = {    name: 'yd',    age: 18}
        const personProxy = new Proxy(person, { get() {}, set() {}})

        get方法可以接收兩個(gè)參數(shù),第一個(gè)就是所代理的目標(biāo)對(duì)象,第二個(gè)就是外部所訪問(wèn)的這個(gè)屬性的屬性名。。

        {    get(target, property) {        console.log(target, property);        return property in target ? target[property] : undefined;    }}

        set方法接收三個(gè)參數(shù), 分別是代理目標(biāo)對(duì)象,以及要寫入的屬性名和屬性值。

        {    set(target, property, value) {        console.log(target, property, value);        if (property === 'age') {            if (!Number.isInteger(value)) {                throw new TypeError(``${value} must be a integer);            }        }        target[property] = value;    }}

        1. Proxy 對(duì)比 defineProperty

        相比于Object.defineProperty,Proxy到底有哪些優(yōu)勢(shì)。

        Object.defineProperty只能監(jiān)聽到對(duì)象屬性的讀取或?qū)懭耄琍roxy除讀寫外還可以監(jiān)聽對(duì)象中屬性的刪除,對(duì)對(duì)象當(dāng)中方法調(diào)用等。

        const person = {    name: 'yd',    age: 18}const personProxy = new Proxy(person, {    deleteProperty(target, property) {        console.log(target, property);        delete target[property];    },})

        除了delete以外, 還有很多其他的對(duì)象操作都能夠被監(jiān)視到,列舉如下。

        get: 讀取某個(gè)屬性

        set: 寫入某個(gè)屬性

        has:in操作符調(diào)用

        deleteProperty:delete操作符調(diào)用

        getProperty:Object.getPropertypeOf()setProperty:Object.setProtoTypeOf()isExtensible:Object.isExtensible()preventExtensions:Object.preventExtensions()getOwnPropertyDescriptor:Object.getOwnPropertyDescriptor()defineProperty:Object.defineProperty()ownKeys:Object.keys(),Object.getOwnPropertyNames(),Object.getOwnPropertSymbols()

        apply: 調(diào)用一個(gè)函數(shù)

        construct: 用new調(diào)用一個(gè)函數(shù)。

        第二點(diǎn)是對(duì)于數(shù)組對(duì)象進(jìn)行監(jiān)視更容易。

        通常想要監(jiān)視數(shù)組的變化,基本要依靠重寫數(shù)組方法,這也是Vue的實(shí)現(xiàn)方式,Proxy可以直接監(jiān)視數(shù)組的變化。

        const list = [];const listProxy = new Proxy(list, {    set(target, property, value) {        console.log(target, property, value);        target[property] = value;        return true; // 寫入成功    }});
        listProxy.push(100);

        Proxy內(nèi)部會(huì)自動(dòng)根據(jù)push操作推斷出來(lái)他所處的下標(biāo),每次添加或者設(shè)置都會(huì)定位到對(duì)應(yīng)的下標(biāo)property。數(shù)組其他的也謝操作方式都是類似的。

        最后Proxy是以非入侵的方式監(jiān)管了對(duì)象的讀寫,那也就是說(shuō)一個(gè)已經(jīng)定義好的對(duì)象不需要對(duì)對(duì)象本身去做任何的操作,就可以監(jiān)視到他內(nèi)部成員的讀寫,而defineProperty的方式就要求必須按特定的方式單獨(dú)去定義對(duì)象當(dāng)中那些被監(jiān)視的屬性。

        對(duì)于一個(gè)已經(jīng)存在的對(duì)象要想去監(jiān)視他的屬性需要做很多額外的操作。這個(gè)優(yōu)勢(shì)實(shí)際上需要有大量的使用然后在這個(gè)過(guò)程當(dāng)中去慢慢的體會(huì)。

        18. Reflect

        如果按照java或者c#這類語(yǔ)言的說(shuō)法,Reflect屬于一個(gè)靜態(tài)類,也就是說(shuō)他不能通過(guò)new的方式去構(gòu)建一個(gè)實(shí)例對(duì)象。只能夠去調(diào)用這個(gè)靜態(tài)類中的靜態(tài)方法。

        這一點(diǎn)應(yīng)該并不陌生,因?yàn)樵趈avascript中的Math對(duì)象也是相同的,Reflect內(nèi)部封裝了一系列對(duì)對(duì)象的底層操作,具體一共提供了14個(gè)靜態(tài)方法,其中有1個(gè)已經(jīng)被廢棄掉了,那還剩下13個(gè),仔細(xì)去查看Reflect的文檔會(huì)發(fā)現(xiàn)這13個(gè)方法的方法名與Proxy的處理對(duì)象里面的方法成員是完全一致的。

        其實(shí)這些方法就是Proxy處理對(duì)象那些方法內(nèi)部的默認(rèn)實(shí)現(xiàn).

        const obj = {    foo: '123',    bar: '456',}
        const Proxy = new Proxy(obj, { get(target, property) { console.log('實(shí)現(xiàn)監(jiān)視邏輯'); return Reflect.get(target, property); }})

        這也就表明在實(shí)現(xiàn)自定義的get或者set這樣的邏輯時(shí)更標(biāo)準(zhǔn)的做法是先去實(shí)現(xiàn)自己所需要的監(jiān)視邏輯,最后再去返回通過(guò)Reflect中對(duì)應(yīng)的方法的一個(gè)調(diào)用結(jié)果。

        個(gè)人認(rèn)為Reflect對(duì)象最大的意義就是他提供了一套統(tǒng)一操作Object的API,因?yàn)樵谶@之前去操作對(duì)象時(shí)有可能使用Object對(duì)象上的方法,也有可能使用像delete或者是in這樣的操作符,這些對(duì)于新手來(lái)說(shuō)實(shí)在是太亂了,并沒有什么規(guī)律。

        Reflect對(duì)象就很好的解決了這樣一個(gè)問(wèn)題,他統(tǒng)一了對(duì)象的操作方式。

        19. Promise

        Promise提供了一種全新的異步編程解決方案,通過(guò)鏈?zhǔn)秸{(diào)用的方式解決了在傳統(tǒng)異步編程過(guò)程中回調(diào)函數(shù)嵌套過(guò)深的問(wèn)題。

        關(guān)于Promise的細(xì)節(jié)有很多內(nèi)容,所以說(shuō)這里先不做詳細(xì)介紹在JavaScript異步編程的文章中已經(jīng)專門針對(duì)Promise進(jìn)行了詳細(xì)的分析。

        20. class類

        以前ECMAScript中都是通過(guò)定義函數(shù)以及函數(shù)的原型對(duì)象來(lái)去實(shí)現(xiàn)的類型,在構(gòu)造函數(shù)中可以通過(guò)this去訪問(wèn)當(dāng)前的實(shí)例對(duì)象,如果需要在這個(gè)類型所有的實(shí)例間去共享一些成員,可以借助于函數(shù)對(duì)象的prototype, 也就是原型去實(shí)現(xiàn)。

        function Person (name) {    this.name = name;}
        Person.prototype.say = function() { console.log(this.name);}

        ECMAScript2015可以使用一個(gè)叫做class的關(guān)鍵詞來(lái)聲明類型,這種獨(dú)立定義類型的語(yǔ)法,要更容易理解,結(jié)構(gòu)也會(huì)更加清晰。

        class Person {
        }

        這種語(yǔ)法與一些老牌面向?qū)ο笳Z(yǔ)言當(dāng)中class非常相似的。如果需要在構(gòu)造函數(shù)當(dāng)中做一些額外的邏輯,可以添加一個(gè)constructor方法,這個(gè)方法就是構(gòu)造函數(shù)。

        同樣可以在這個(gè)函數(shù)中使用this去訪問(wèn)當(dāng)前類型的實(shí)例對(duì)象。

        class Person {    constructor (name) {        this.name = name;    }    say() {        console.log(this.name);    }}

        1. 靜態(tài)方法

        類型中的方法分為實(shí)例方法和靜態(tài)方法,實(shí)例方法就是需要通過(guò)這個(gè)類型構(gòu)造的實(shí)例對(duì)象去調(diào)用,靜態(tài)方法是直接通過(guò)類型本身去調(diào)用??梢酝ㄟ^(guò)static關(guān)鍵字定義。

        class Person {    constructor (name) {        this.name = name;    }    say() {        console.log(this.name);    }    static create (name) {        return new Person(name);    }}

        調(diào)用靜態(tài)方法是直接通過(guò)類型然后通過(guò)成員操作符調(diào)用方法名字。

        const yd = Person.create('yd');

        注意:因?yàn)殪o態(tài)方法是掛載到類型上面的,所以說(shuō)在靜態(tài)方法內(nèi)部他不會(huì)指向某一個(gè)實(shí)例對(duì)象,而是當(dāng)前的類型。

        2. 類的繼承

        繼承是面向?qū)ο螽?dāng)中一個(gè)非常重要的特性,通過(guò)繼承這種特性能抽象出來(lái)相似類型之間重復(fù)的地方, 可以通過(guò)關(guān)鍵詞extends實(shí)現(xiàn)繼承。

        super對(duì)象指向父類, 調(diào)用它就是調(diào)用了父類的構(gòu)造函數(shù)。

        class Student extends Person {    constructor(name, number) {        super(name);        this.number = number;    }    hello () {        super.say();        console.log(this.number);    }}
        const s = new Student('yd', '100');
        s.hello();

        21. Set

        可以把他理解為集合,他與傳統(tǒng)的數(shù)組非常類似,不過(guò)Set內(nèi)部的成員是不允許重復(fù)的。那也就是說(shuō)每一個(gè)值在同一個(gè)Set中都是唯一的。

        通過(guò)這個(gè)類型構(gòu)造的實(shí)例就用來(lái)存放不同的數(shù)據(jù)??梢酝ㄟ^(guò)這個(gè)實(shí)例的add方法向集合當(dāng)中去添加數(shù)據(jù),由于add方法他會(huì)返回集合對(duì)象本身,所以可以鏈?zhǔn)秸{(diào)用。如果在這個(gè)過(guò)程中添加了之前已經(jīng)存在的值那所添加的這個(gè)值就會(huì)被忽略掉。

        const s = new Set();
        s.add(1).add(2).add(3).add(2);

        想要遍歷集合當(dāng)中的數(shù)據(jù),可以使用集合對(duì)象的forEach方法去傳遞一個(gè)回調(diào)函數(shù)。

        s.forEach(i => console.log(i));

        也可以使用for...of循環(huán)。

        for (let i of s) {    console.log(i);}

        可以通過(guò)size屬性來(lái)去獲取整個(gè)集合的長(zhǎng)度。

        console.log(s.size);

        has方法就用來(lái)判斷集合當(dāng)中是否存在某一個(gè)特定的值。

        console.log(s.has(100)); // false

        delete方法用來(lái)刪除集合當(dāng)中指定的值,刪除成功將會(huì)返回一個(gè)true。

        console.log(s.delete(3)); // true

        clear方法用于清除當(dāng)前集合當(dāng)中的全部?jī)?nèi)容。

        s.clear()

        22. Map

        Map結(jié)構(gòu)與對(duì)象非常類似,本質(zhì)上他們都是鍵值對(duì)集合但是這種對(duì)象結(jié)構(gòu)中的鍵,只能是字符串類型,如果說(shuō)用其他類型作為鍵會(huì)被轉(zhuǎn)換成字符串,出現(xiàn)[object object]。

        不同的對(duì)象轉(zhuǎn)換成字符串可能會(huì)變成相同的鍵名[object object]導(dǎo)致數(shù)據(jù)覆蓋丟失。

        Map類型才算是嚴(yán)格上的鍵值對(duì)類型,用來(lái)映射兩個(gè)任意類型之間鍵值對(duì)的關(guān)系??梢允褂眠@個(gè)對(duì)象的set方法去存數(shù)據(jù)。鍵可以是任意類型的數(shù)據(jù)。不需要擔(dān)心他會(huì)被轉(zhuǎn)換為字符串。

        const m = new Map();
        const key = {};
        m.set(key, 18);
        console.log(m);

        可以使用get方法獲取數(shù)據(jù),has方法判斷他里面是否存在某個(gè)鍵。delete方法刪除某個(gè)鍵。clear方法清空所有的鍵值。

        console.log(m.get(key));
        console.log(m.has(key));
        m.delete(key);
        m.clear();

        可以使用forEach方法遍歷。在這個(gè)方法的回調(diào)函數(shù)當(dāng)中第一個(gè)參數(shù)就是被遍歷的值,第二個(gè)參數(shù)是被遍歷的鍵。

        m.forEach((value, key) => {    console.log(value, key);})

        23. Symbol

        在ECMAScript2015之前對(duì)象的屬性名都是字符串,而字符串是有可能會(huì)重復(fù)的。如果重復(fù)的話就會(huì)產(chǎn)生沖突,比如在使用第三方模塊時(shí),如果需要擴(kuò)展第三方模塊,而這時(shí)就有可能把第三方模塊的方法覆蓋掉,導(dǎo)致代碼執(zhí)行異常。

        ES2015提供了一種全新的原始數(shù)據(jù)類型Symbol,翻譯過(guò)來(lái)的意思叫做符號(hào),翻譯過(guò)來(lái)就是表示一個(gè)獨(dú)一無(wú)二的值。

        通過(guò)Symbol函數(shù)就可以創(chuàng)建一個(gè)Symbol類型的數(shù)據(jù),而且這種類型的數(shù)據(jù)typeof的結(jié)果也是symbol,那這也就表示他確實(shí)是一個(gè)全新的類型。

        const s = Symbol();typeof s; // symbol類型

        這種類型最大的特點(diǎn)就是獨(dú)一無(wú)二,通過(guò)Symbol函數(shù)創(chuàng)建的每一個(gè)值都是唯一的永遠(yuǎn)不會(huì)重復(fù)。

        Symbol() === Symbol(); // false

        Symbol創(chuàng)建時(shí)允許接收一個(gè)字符串,作為這個(gè)值的描述文本, 對(duì)于多次使用Symbol時(shí)就可以區(qū)分出是哪一個(gè)Symbol,這個(gè)參數(shù)僅是描述作用,相同的描述字段生成的值仍是不同的。

        const s1 = Symbol('foo');const s2 = Symbol('foo');
        s1 === s2; // false

        ES2015開始對(duì)象允許使用Symbol作為屬性名。那也就是說(shuō)現(xiàn)在對(duì)象的屬性名可以是兩種類型,string和Symbol。

        const person = {    [Symbol()]: 123,    [Symbol()]: 456}

        Symbol除了用在對(duì)象中避免重復(fù)以外,還可以借助這種類型的特點(diǎn)來(lái)模擬實(shí)現(xiàn)對(duì)象的私有成員。以前私有成員都是通過(guò)約定,例如約定使用下劃線開頭就表示是私有成員。約定外界不允許訪問(wèn)下劃線開頭的成員。

        現(xiàn)在有了Symbol就可以使用Symbol去作為私有成員的屬性名了。在這個(gè)對(duì)象的內(nèi)部可以使用創(chuàng)建屬性時(shí)的Symbol。去拿到對(duì)應(yīng)的屬性成員。

        const name = Symbol();const person = {    [name]: 'yd',    say() {        return this[name];    }}

        截止到2020標(biāo)準(zhǔn),ECMAScript一共定義了8種數(shù)據(jù)類型。

        如果需要在全局去復(fù)用一個(gè)相同的Symbol值,可以使用全局變量的方式去實(shí)現(xiàn),或者是使用Symbol類型提供的一個(gè)靜態(tài)方法for,這個(gè)方法接收一個(gè)字符串作為參數(shù),相同的參數(shù)一定對(duì)應(yīng)相同的值。

        const s1 = Symbol.for('foo');const s2 = Symbol.for('foo');
        s1 === s2; // true

        這個(gè)方法維護(hù)了一個(gè)全局的注冊(cè)表,為字符串和Symbol提供了一個(gè)對(duì)應(yīng)關(guān)系。需要注意的是,在內(nèi)部維護(hù)的是字符串和Symbol的關(guān)系,那也就是說(shuō)如參數(shù)不是字符串,會(huì)轉(zhuǎn)換為字符串。

        const s1 = Symbol.for('true');const s2 = Symbol.for(true);
        s1 === s2; // true

        1. 常量

        在Symbol內(nèi)部提供了很多內(nèi)置的Symbol常量,用來(lái)去作為內(nèi)部方法的標(biāo)識(shí),可以讓自定義對(duì)象去實(shí)現(xiàn)一些js內(nèi)置的接口。

        如果想要自定義對(duì)象的toString標(biāo)簽,ECMAScript要求使用Symbol的值來(lái)去實(shí)現(xiàn)這樣一個(gè)接口。

        obj[Symbol.toStringTag] = 'test'obj.toString(); // [object test];

        這里的toStringTag就是內(nèi)置的一個(gè)Symbol常量,這種Symbol在后面為對(duì)象去實(shí)現(xiàn)迭代器時(shí)會(huì)經(jīng)常遇到。

        使用Symbol的值去作為對(duì)象的屬性名那這個(gè)屬性通過(guò)傳統(tǒng)的for in循環(huán)是無(wú)法拿到的。

        而且通過(guò)Object.keys方法也是獲取不到這樣Symbol類型的屬性名。JSON.stringify去序列化,Symbol屬性也會(huì)被隱藏掉。

        const obj = {    [Symbol()]: 'symbol value',    foo: 'normal value'}
        for (var key in obj) { console.log(key);}
        Object.keys(obj);

        總之這些特性都使得Symbol屬性,特別適合作為對(duì)象的私有屬性,當(dāng)然想要獲取這種類型的屬性名可以使用Object.getOwnPropertySymbols(obj)方法。

        Object.getOwnPropertySymbols(obj)

        這個(gè)方法的作用類似于Object.keys, 所不同的是Object.keys他只能獲取對(duì)象當(dāng)中字符串屬性名,而Object.getOwnPropertySymbols方法他獲取到的全是Symbol類型的屬性名。

        24. for…of

        for...of是ECMAScript2015之后新增的遍歷方式。未來(lái)會(huì)作為遍歷所有數(shù)據(jù)結(jié)構(gòu)的統(tǒng)一方式。

        const arr = [1, 2, 3, 4];for (const item of arr) {    console.log(item); // 1, 2,3,4    // break; // 終止循環(huán)}

        for…of循環(huán)拿到的就是數(shù)組中的每一個(gè)元素,而不是對(duì)應(yīng)的下標(biāo)。這種循環(huán)方式就可以取代之前常用的數(shù)組實(shí)例當(dāng)中的forEach方法。

        for...of循環(huán)可以使用break關(guān)鍵詞隨時(shí)去終止循環(huán)。

        除了數(shù)組可以直接被for...of循環(huán)去遍歷,一些偽數(shù)組對(duì)象也是可以直接被for...of去遍歷的,例如arguments,set,map。

        for...of在遍歷Map時(shí), 可以直接拿到鍵和值。鍵和值是直接以數(shù)組的形式返回的,也就是說(shuō)數(shù)組的第一個(gè)元素就是當(dāng)前的鍵名,第二個(gè)元素就是值。這就可以配合數(shù)組的解構(gòu)語(yǔ)法,直接拿到鍵和值。

        const m = new Map();m.set('foo', '123');m.set('bar', '345');
        for (const item if m) { console.log(item); // ['foo', '123'];}
        for (const [key, value] if m) { console.log(key, value); // 'foo', '123'}

        for...of是不能直接遍歷普通對(duì)象的,他要求被遍歷的對(duì)象必須存在一個(gè)叫做Iterable的接口。

        1. 可迭代接口

        可迭代接口就是一種可以被for...of循環(huán)統(tǒng)一遍歷訪問(wèn)的規(guī)格標(biāo)準(zhǔn),換句話說(shuō)只要這個(gè)數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)了可迭代接口他就能夠被for...of循環(huán)遍歷,那這也就是說(shuō)之前嘗試的那些能夠直接被for...of循環(huán)去遍歷的數(shù)據(jù)類型他都已經(jīng)在內(nèi)部實(shí)現(xiàn)了這個(gè)接口。

        iterable約定對(duì)象中必須要掛載一個(gè)叫做Symbol.iterable的方法,這個(gè)方法返回一個(gè)對(duì)象,對(duì)象上存在一個(gè)next方法,next方法也返回一個(gè)對(duì)象,對(duì)象中存在value和done兩個(gè)屬性,value是當(dāng)前遍歷到的值,done為是否為最后一個(gè)。

        每調(diào)用一次next就會(huì)后移一位。

        總結(jié)起來(lái)就是所有被for...of遍歷的數(shù)據(jù)類型必須包含一個(gè)叫做iterable的接口,也就是內(nèi)部必須掛載一個(gè)Symbol.iterable方法,這個(gè)方法需要返回一個(gè)帶有next方法的對(duì)象,不斷調(diào)用這個(gè)next方法就可以實(shí)現(xiàn)對(duì)內(nèi)部所有成員的遍歷。

        這就是for...of循環(huán)的內(nèi)部原理。

        2. 實(shí)現(xiàn)可迭代接口

        在這個(gè)對(duì)象中放一個(gè)數(shù)組store用來(lái)存放值得被遍歷的數(shù)據(jù),然后在next方法中去迭代這個(gè)數(shù)組,需要去維護(hù)一個(gè)下標(biāo)index,讓他默認(rèn)等于0;

        由于next中的函數(shù)并不是obj對(duì)象,所以使用self去存儲(chǔ)一下當(dāng)前的this供下面使用,在next方法中value就是self.store[index],done就是index >= self.store.length。完成以后需要讓index++, 也就是讓指針后移一位。

        const obj = {    store: [1, 2, 3, 4, 5],    [Symbol.iterable]: function() {        let index = 0;        const self = this;        return {            next: function() {                const result = {                    value: self.store[index],                    done: index >= self.store.length                }                index++;                return result;            }        }    }}
        for (const item of obj) { console.log(item); // 1, 2, 3, 4, 5}

        25. 生成器

        ECMAScript2015中還新增了一種生成器函數(shù)generator,是為了能夠在復(fù)雜的異步編程中減少回調(diào)函數(shù)嵌套產(chǎn)生的問(wèn)題,從而去提供更好的異步編程解決方案。

        定義生成器函數(shù)就是在普通的函數(shù)function后面添加一個(gè)*,這樣函數(shù)就變成了一個(gè)生成器函數(shù)。函數(shù)執(zhí)行之后會(huì)返回一個(gè)生成器對(duì)象。

        function * foo() {    return 100;}const result = foo();console.log(result);

        在這個(gè)對(duì)象上也和迭代器一樣有一個(gè)next方法,實(shí)際上生成器函數(shù)也實(shí)現(xiàn)了iterable接口,也就是迭代器接口協(xié)議。

        生成器函數(shù)在使用會(huì)配合一個(gè)叫做yield的關(guān)鍵字,yield關(guān)鍵詞與return關(guān)鍵詞類似,但是又有很大的不同。

        生成器函數(shù)會(huì)自動(dòng)返回一個(gè)生成器對(duì)象,調(diào)用這個(gè)生成器的next會(huì)讓這個(gè)函數(shù)的函數(shù)體開始執(zhí)行,執(zhí)行過(guò)程中一旦遇到y(tǒng)ield關(guān)鍵詞函數(shù)的執(zhí)行就會(huì)被暫停下來(lái),而且yield的值將會(huì)被作為next的結(jié)果返回,繼續(xù)調(diào)用next函數(shù)就會(huì)從暫停的位置繼續(xù)向下執(zhí)行到下一個(gè)yield直到這個(gè)函數(shù)完全結(jié)束。

        const * foo() {    console.log(1111);    yield 100;    console.log(2222);    yield 200;    console.log(3333);    yield 300;}

        生成器函數(shù)最大的特點(diǎn)就是惰性執(zhí)行,每調(diào)用一次next就會(huì)執(zhí)行一次yield。

        1. 生成器應(yīng)用

        了解了生成器函數(shù)的基本用法來(lái)看一個(gè)簡(jiǎn)單的應(yīng)用場(chǎng)景實(shí)現(xiàn)一個(gè)發(fā)號(hào)器。

        在實(shí)際業(yè)務(wù)開發(fā)過(guò)程中經(jīng)常需要用到自增的id,而且每次調(diào)用這個(gè)id都需要在原有的基礎(chǔ)上去+1,這里如果使用生成器函數(shù)去實(shí)現(xiàn)這樣一個(gè)功能是最合適的了。

        首先定義一個(gè)createId生成器函數(shù),然后定義一個(gè)初始的id等于1,然后通過(guò)一個(gè)死循環(huán)不斷的去yield id++。這里不需要擔(dān)心死循環(huán)的問(wèn)題,因?yàn)槊看卧趛ield過(guò)后這個(gè)方法會(huì)被暫停,循環(huán)自然也就會(huì)被暫停。直到下一次調(diào)用next再次去執(zhí)行一次又會(huì)被暫停下來(lái)。

        這樣在外部就可以通過(guò)這個(gè)方法去創(chuàng)建一個(gè)生成器對(duì)象id,每次調(diào)用一下這個(gè)生成器的next方法就能夠獲取到自增的value,也就是id。

        function * createId() {    let id = 1;    while(true) {        yield id++;    }}const id = createId();
        id.next().value;

        實(shí)現(xiàn)發(fā)號(hào)器是一個(gè)非常簡(jiǎn)單的需求,還可以使用生成器函數(shù)實(shí)現(xiàn)對(duì)象的iterator方法,因?yàn)樯善饕矊?shí)現(xiàn)了對(duì)象的iterator接口,而且使用生成器函數(shù)去實(shí)現(xiàn)iterator方法會(huì)比之前的方式簡(jiǎn)單很多。

        26. ES Modules

        ES Modules是ECMAScript2015中標(biāo)準(zhǔn)化的一套語(yǔ)言層面的模塊化標(biāo)準(zhǔn)規(guī)范,我之前寫過(guò)一篇模塊化發(fā)展歷程的文章,里面有詳細(xì)的介紹,里面和CommonJs以及其他標(biāo)準(zhǔn)做了統(tǒng)一的對(duì)比,感興趣的可以翻閱一下那篇文章。

        27. include方法

        這個(gè)方法檢查數(shù)組中是否存在某個(gè)元素。在這之前如果需要檢查數(shù)組中是否包含某個(gè)元素都是使用indexOf方法。

        但是indexOf不能查詢到數(shù)組中的NaN,現(xiàn)在有了includes方法之后直接可以判斷數(shù)組當(dāng)中是否存在某個(gè)指定的元素了,并且他返回的是一個(gè)布爾值,而且也可以判斷NaN

        28. **指數(shù)運(yùn)算符

        以前需要進(jìn)行指數(shù)運(yùn)算需要借助Math對(duì)象的pow方法來(lái)去實(shí)現(xiàn)。例如去求2的10次方。

        Math.pow(2, 10); // 表示2的10次方。

        指數(shù)運(yùn)算符,他就是語(yǔ)言本身的運(yùn)算符,就像是之前所使用的加減乘除運(yùn)算符一樣,使用起來(lái)也非常簡(jiǎn)單。

        2**10; // 2的10次方

        29. Object對(duì)象新增三個(gè)擴(kuò)展方法

        Object.keys返回的是所有的鍵組成的數(shù)組,Object.values返回的是所有值組成的數(shù)組。

        Object.entries將對(duì)象轉(zhuǎn)成數(shù)組,每個(gè)元素是鍵值對(duì)的數(shù)組,可以快速將對(duì)象轉(zhuǎn)為Map

        const l = Object.entries({a: 1, b: 2});const m = new Map(l);

        30. Object.getOwnPropertyDescriptors

        獲取對(duì)象的描述信息

        Object.assign復(fù)制時(shí),將對(duì)象的屬性和方法當(dāng)做普通屬性來(lái)復(fù)制,并不會(huì)復(fù)制完整的描述信息,比如this等.

        const p1 = {    a: 'y',    b: 'd',    get name() {        return `${this.a} ${this.b}`;    }}
        const p2 = Object.assign({}, p1);p2.a = 'z';p2.name; // y d; 發(fā)現(xiàn)并沒有修改到a的值,是因?yàn)閠his仍舊指向p1

        使用Object.getOwnPropertyDescriptors獲取完整描述信息

        const description = Object.getOwnPropertyDescriptors(p1);const p2 = Object.defineProperty({}, description);p2.a = 'z';p2.name; // z d

        31. String.prototype.String.prototype.padStart

        用給定的字符串在尾部拼接到指定長(zhǎng)度

        'abc'.padEnd(5, '1');  // abc11;

        用給定的字符串在首部拼接到指定長(zhǎng)度

        'abc'.padStart(5, '1'); // 11abc;

        32. 允許對(duì)象和數(shù)組在最后添加一個(gè)逗號(hào)

        [1, 2, 3,]
        {a: 1, b: 2, }

        33. async + await

        在函數(shù)聲明時(shí)加入async關(guān)鍵字,則函數(shù)會(huì)變?yōu)楫惒胶瘮?shù),當(dāng)使用await調(diào)用時(shí),只有等到被await的promise返回,函數(shù)才會(huì)向下執(zhí)行。

        const as = async () => {    const data = await ajax();}

        34. 收集剩余屬性

        將對(duì)象或者數(shù)組的剩余屬性收集到新對(duì)象中

        const data = {a: 1, b: 2, c: 3, d: 4};const {a, b, ...arg} = data;console.log(arg); // {c: 3, d: 4};

        事實(shí)上 Map、Set、String 同樣支持該能力。

        35. for of 支持異步迭代

        在此之前想要實(shí)現(xiàn)異步迭代想要在for of外層嵌套一個(gè)async函數(shù)

        async function () {    for (const fn of actions) {        await fn();    }}

        ES2018提供了一種新的書寫方式。

        async function() {    for await (const fn of actions) {        fn();    }}

        36. JSON 成為 ECMAScript 的完全子集

        在以前,行分隔符\u2028和段分隔符\u2029會(huì)導(dǎo)致JSON.parse拋出語(yǔ)法錯(cuò)誤異常。

        ECMAScript優(yōu)化了這個(gè)功能。

        JSON.stringify也做了改進(jìn),對(duì)于超出Unicode范圍的轉(zhuǎn)義序列,JSON.stringify()會(huì)輸出未知字符:

        JSON.stringify('\uDEAD'); // '"?"'

        37. 修正Function.prototpye.toString()

        在以前,返回的內(nèi)容中function關(guān)鍵字和函數(shù)名之間的注釋,以及函數(shù)名和參數(shù)列表左括號(hào)之間的空格,是不會(huì)被顯示出來(lái)的。現(xiàn)在會(huì)精確返回這些內(nèi)容,函數(shù)如何定義的,這就會(huì)如何顯示。

        38. Array.prorptype.flat()、Array.prorptype.flatMap()

        flat()用于對(duì)數(shù)組進(jìn)行降維,它可以接收一個(gè)參數(shù),用于指定降多少維,默認(rèn)為1。降維最多降到一維。

        const array = [1, [2, [3]]]array.flat() // [1, 2, [3]]array.flat(1) // [1, 2, [3]],默認(rèn)降 1 維array.flat(2) // [1, 2, 3]array.flat(3) // [1, 2, 3],最多降到一維

        flatMap()允許在對(duì)數(shù)組進(jìn)行降維之前,先進(jìn)行一輪映射,用法和map()一樣。然后再將映射的結(jié)果降低一個(gè)維度??梢哉f(shuō)arr.flatMap(fn)等效于arr.map(fn).flat(1)。但是根據(jù)MDN的說(shuō)法,flatMap()在效率上略勝一籌,誰(shuí)知道呢。

        flatMap()也可以等效為reduce()和concat()的組合,下面這個(gè)案例來(lái)自MDN,但是這不是一個(gè)map就能搞定的事么?

        var arr1 = [1, 2, 3, 4];
        arr1.flatMap(x => [x * 2]);// 等價(jià)于arr1.reduce((acc, x) => acc.concat([x * 2]), []);// [2, 4, 6, 8]

        39. String.prototype.trimStart()、String.prototype.trimEnd()

        用過(guò)字符串trim()的都知道這兩個(gè)函數(shù)各自負(fù)責(zé)只去掉單邊的多余空格。

        40. Object.fromEntries()

        從名字就能看出來(lái),這是Object.entries()的逆過(guò)程。Object.fromEntries()可以將數(shù)組轉(zhuǎn)化為對(duì)象。

        41. description of Symbol

        Symbol是新的原始類型,通常在創(chuàng)建Symbol時(shí)會(huì)附加一段描述。只有把這個(gè)Symbol轉(zhuǎn)成String才能看到這段描述,而且外層還套了個(gè) 'Symbol()' 字樣。ES2019為Symbol新增了description屬性,專門用于查看這段描述。

        const sym = Symbol('The description');String(sym) // 'Symbol(The description)'sym.description // 'The description'

        42. try…catch

        過(guò)去,catch后面必須有一組括號(hào),里面用一個(gè)變量代表錯(cuò)誤信息對(duì)象。現(xiàn)在這部分是可選的了,如果異常處理部分不需要錯(cuò)誤信息,可以把它省略,像寫if...else一樣寫try...catch。

        try {  throw new Error('Some Error')} catch {  handleError() // 這里沒有用到錯(cuò)誤信息,可以省略 catch 后面的 (e)。}

        43. Array.prototype.sort

        JavaScript中內(nèi)置的數(shù)組排序算法使用的是不穩(wěn)定的排序算法,也就是說(shuō)在每一次執(zhí)行后,對(duì)于相同數(shù)據(jù)來(lái)說(shuō),它們的相對(duì)位置是不一致的。

        var arr1 = [{a: 1, b: 2}, {a: 2, b: 2}, {a: 1, b: 3}, {a: 2, b: 4}, {a: 5, b: 3}];arr1.sort((a, b) => a.a - b.a);

        返回的結(jié)果第一次可能是這樣的:[{a: 1, b: 2}, {a: 1, b: 3}...]

        但是第二次就變成:[{a:1,b:3}, {a:1, b: 2}....]

        那么在es2019中,JavaScript內(nèi)部放棄了不穩(wěn)定的快排算法,而選擇使用Tim Sort這種穩(wěn)定的排序算法。優(yōu)化了這個(gè)功能。

        44. 類的私有屬性

        你可能不希望類內(nèi)部的所有內(nèi)容都是全局可用的。通過(guò)在變量或函數(shù)前面添加一個(gè)#可以將它們完全保留為類內(nèi)部使用

        class Message { #message = "Howdy" greet() { console.log(this.#message) }}
        const greeting = new Message()
        greeting.greet() // Howdy 內(nèi)部可以訪問(wèn)console.log(greeting.#message) // Private name #message is not defined 不能直接被訪問(wèn)

        45. Promise.allSettled

        當(dāng)處理多個(gè)Promise時(shí),特別是當(dāng)它們相互依賴時(shí),記錄每個(gè)Promise所發(fā)生的事情來(lái)調(diào)試錯(cuò)誤是很有必要的。

        通過(guò)Promise.allSettled可以創(chuàng)建一個(gè)新的Promise,它只在所有傳遞給它的Promise都完成時(shí)返回一個(gè)數(shù)組,其中包含每個(gè)Promise的數(shù)據(jù)。

        const p1 = new Promise((res, rej) => setTimeout(res, 1000));
        const p2 = new Promise((res, rej) => setTimeout(rej, 1000));
        Promise.allSettled([p1, p2]).then(data => console.log(data));
        // [// Object { status: "fulfilled", value: undefined},// Object { status: "rejected", reason: undefined}// ]

        Promise.all是當(dāng)多個(gè)promise全部成功,或出現(xiàn)第一個(gè)失敗就會(huì)結(jié)束。Promise.allSettled是所有都執(zhí)行完成,無(wú)論成功失敗。

        46. 合并空運(yùn)算符 ??

        假設(shè)變量不存在,希望給系統(tǒng)一個(gè)默認(rèn)值,一般會(huì)使用||運(yùn)算符。但是在javascript中空字符串,0,false都會(huì)執(zhí)行||運(yùn)算符,ECMAScript2020引入合并空運(yùn)算符解決該問(wèn)題,只允許在值為null或undefined時(shí)使用默認(rèn)值。

        const name = '';
        console.log(name || 'yd'); // yd;console.log(name ?? 'yd'); // '';

        47. 可選鏈運(yùn)算符 ?.

        業(yè)務(wù)代碼中經(jīng)常會(huì)遇到這樣的情況,a對(duì)象有個(gè)屬性b,b也是一個(gè)對(duì)象有個(gè)屬性c。

        const a = {    b: {        c: 123,    }}

        訪問(wèn)c,經(jīng)常會(huì)寫成a.b.c,但是如果b不存在時(shí),就會(huì)出錯(cuò)。

        ECMAScript2020定義可選鏈運(yùn)算符解決該問(wèn)題,在.之前添加一個(gè)?將鍵名變成可選。

        let person = {};console.log(person?.profile?.age ?? 18); // 18

        48. bigInt

        JavaScript可以處理的最大數(shù)字是2的53次方 - 1,可以在可以在Number.MAX_SAFE_INTEGER中看到。

        更大的數(shù)字則無(wú)法處理,ECMAScript2020引入BigInt數(shù)據(jù)類型來(lái)解決這個(gè)問(wèn)題。通過(guò)把字母n放在末尾, 可以運(yùn)算大數(shù)據(jù)。通過(guò)常規(guī)操作進(jìn)行加、減、乘、除、余數(shù)和冪等運(yùn)算。

        它可以由數(shù)字和十六進(jìn)制或二進(jìn)制字符串構(gòu)造。此外它還支持AND、OR、NOT和XOR之類的按位運(yùn)算。唯一無(wú)效的位運(yùn)算是零填充右移運(yùn)算符。

        const bigNum = 100000000000000000000000000000n;console.log(bigNum * 2n); // 200000000000000000000000000000n
        const bigInt = BigInt(1);console.log(bigInt); // 1n;
        const bigInt2 = BigInt('2222222222222222222');console.log(bigInt2); // 2222222222222222222n;

        BigInt是一個(gè)大整數(shù),不能存儲(chǔ)小數(shù)。

        49. 動(dòng)態(tài)導(dǎo)入import

        import('./a.js')返回一個(gè)Promise對(duì)象。

        const a = 123;export { a };
        import('./a.js').then(data => {    console.log(data.a); // 123;})

        50. globalThis

        標(biāo)準(zhǔn)化方式訪問(wèn)全局對(duì)象,globalThis在瀏覽器中window作為全局對(duì)象,在node中g(shù)lobal作為全局對(duì)象,ECMAScript2020提供globalThis作為語(yǔ)言的全局對(duì)象,方便代碼移植到不同環(huán)境中運(yùn)行。

        51. String.prototype.replaceAll()

        為了方便字符串的全局替換,ES2021將支持String.prototype.replaceAll()方法,可以不用寫正則表達(dá)式就可以完成字符串的全局替換

        'abc111'.replaceAll('1', '2'); // abc222

        52. Promise.any

        只要有一個(gè)promise是fulfilled時(shí),則返回一個(gè)resolved promise;所有的promise都是rejected時(shí),則返回一個(gè)rejected promise

        Promise.any([ Promise.reject(1), Promise.resolve(2) ]) .then(result => console.log('result:', result)) .catch(error => console.error('error:', error)); // result: 2

        53. 邏輯賦值運(yùn)算符

        邏輯賦值運(yùn)算符由邏輯運(yùn)算符和賦值表達(dá)式組合而成:

        a ||= b;// 與 a ||= b 等價(jià)a || (a = b);// 與 a ||= b 等價(jià)if (!a) {    a = b;}

        a &&= b;// 與 a &&= b 等價(jià)a && (a = b);// 與 a &&= b 等價(jià)if (a) { a = b;}
        a ??= b;// 與 a ??= b 等價(jià)a ?? (a = b);// 與 a ??= b 等價(jià)if (a === null || a === undefined) {    a = b;}

        注意:

        a = a || b; // 與 a ||= b 不等價(jià)a = a && b; // 與 a &&= b 不等價(jià)a = a ?? b; // 與 a ??= b 不等價(jià)

        54. 數(shù)字分隔符

        使用_對(duì)數(shù)字進(jìn)行分割,提高數(shù)字的可讀性,例如在日常生活中數(shù)字通常是每三位數(shù)字之間會(huì)用`, 分割,以方便人快速識(shí)別數(shù)字。在代碼中,也需要程序員較便捷的對(duì)數(shù)字進(jìn)行辨識(shí)。

        // 1000000000 不易辨識(shí)const count1 = 1000000000;
        // 1_000_000_000 很直觀const count2 = 1_000_000_000;
        console.log(count1 === count2); // true

        55. WeakRefs

        WeakRef實(shí)例可以作為對(duì)象的弱引用,對(duì)象的弱引用是指當(dāng)該對(duì)象應(yīng)該被GC回收時(shí)不會(huì)阻止GC的回收行為。而與此相反的,一個(gè)普通的引用(默認(rèn)是強(qiáng)引用)會(huì)將與之對(duì)應(yīng)的對(duì)象保存在內(nèi)存中。

        只有當(dāng)該對(duì)象沒有任何的強(qiáng)引用時(shí),JavaScript引擎GC才會(huì)銷毀該對(duì)象并且回收該對(duì)象所占的內(nèi)存空間。因此,訪問(wèn)弱引用指向的對(duì)象時(shí),很有可能會(huì)出現(xiàn)該對(duì)象已經(jīng)被回收。

        const ref = new WeakRef({ name: 'koofe' });let obj = ref.deref();if (obj) {  console.log(obj.name); // koofe}

        對(duì)于WeakRef對(duì)象的使用要慎重考慮,能不使用就盡量不要使用。

        學(xué)習(xí)更多技能

        請(qǐng)點(diǎn)擊下方公眾號(hào)


        瀏覽 70
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)

        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
          
          

            1. 最新日韩黄色电影网站 | 韩国三级片中文字幕 | 午夜18 视频在线观看 | 国产精品一区网站 | 一级婬片A片试看50分钟 |