什么?ES6 中还有 Tail Calls!
前言
先吐槽一件事,最近把原先的 TOP 域名更換到 CN 域名,并且用 Gatsby 重建個(gè)人站點(diǎn),之前是用采用 HTTPS 部署的方式繞過阿里云的域名備案系統(tǒng)。更換 CN 域名后,這招不管用了,?? 域名必須要備案了,等待幕布郵寄中……
有人要問了,都 9102 年,ES10 都出來了,怎么還在講 ES6,非也!本文針對(duì) ES6 幾個(gè)不為人知、和重要的特性做講解,精彩的在后面!
基礎(chǔ)篇
Let + Const
ES6 除了固有的函數(shù)作用域,還引入了塊級(jí)作用域({})
function f() {{let x; // ①{// 包含在當(dāng)前塊中,與 ① 中的 x 分屬不同作用域const x = "sneaky";// 錯(cuò)誤,const 定義的變量不可以重新賦值,如果 const 定義了一個(gè)對(duì)象,那么對(duì)象的屬性是可以修改的x = "foo";}// let 定義的變量可以重新賦值x = "bar";// 錯(cuò)誤,x 在 ① 塊中已被定義let x = "inner";} } 復(fù)制代碼默認(rèn)、剩余、展開參數(shù)(Default + Rest + Spread)
function f(x, y=12) {// y 等于 12 如果不傳遞 (或者傳遞 undefined)return x + y; } f(3); // 15 復(fù)制代碼function f(x, ...y) {// y 是一個(gè)數(shù)組return x * y.length; } f(3, "hello", true); // 6 復(fù)制代碼function f(x, y, z) {return x + y + z; } // 將數(shù)組的每一項(xiàng)作為參數(shù)傳遞 f(...[1,2,3]); // 6 復(fù)制代碼解構(gòu)(Destructuring)
var [a, ,b] = [1,2,3]; a === 1; // true b === 3; // truevar { op: a, lhs: { op: b }, rhs: c } = getASTNode()// var {op: op, lhs: lhs, rhs: rhs} = getASTNode() var {op, lhs, rhs} = getASTNode() // 參數(shù)解構(gòu) function g({name: x}) {console.log(x); } g({name: 5})var [a] = []; a === undefined; // truevar [a = 1] = []; a === 1; // true// 解構(gòu) + 默認(rèn)參數(shù) function r({x, y, w = 10, h = 10}) {return x + y + w + h; } r({x:1, y:2}) === 23 // true 復(fù)制代碼箭頭函數(shù)(Arrows and Lexical This)
// 除了支持返回語句,還可以將表達(dá)式作為返回主體 const foo = () => ({ name: 'es6' }); const bar = (num) => (num++, num ** 2);foo(); // 返回一個(gè)對(duì)象 { name: 'es6' } bar(3); // 執(zhí)行多個(gè)表達(dá)式,并返回最后一個(gè)表達(dá)式的值 16 復(fù)制代碼JS 中 this 的指向問題一直都是面試高頻考點(diǎn),不少人在實(shí)戰(zhàn)中也掉入坑中,總結(jié)起來就是一句話:“ this 永遠(yuǎn)指向調(diào)用它的那個(gè)對(duì)象”,而箭頭函數(shù)則改寫了這一規(guī)則,就是
箭頭函數(shù)共享當(dāng)前代碼上下文的 this
什么意思呢?可以理解為
- 箭頭函數(shù)不會(huì)創(chuàng)建自己的 this,它只會(huì)從自己的作用域鏈的上一層繼承 this,如果上一層還是箭頭函數(shù),則繼續(xù)向上查找,直至全局作用域,在瀏覽器環(huán)境下即 window。
- 函數(shù)具有作用域鏈,對(duì)象則不具有
因此,在下面的代碼中,傳遞給 setInterval 的函數(shù)內(nèi)的 this 與 sayHello 函數(shù)中的 this 一致:
const bob = {name: 'Bob',sayHello() {setTimeout(() => {console.log(`hello, I am ${this.name}`);}, 1000);} }; const hello = bob.sayHello;bob.sayHello(); // hello, I am Bob // 作為對(duì)象的方法調(diào)用,sayHello的this指向bob hello(); // hello, I am undefined // 作為普通函數(shù)調(diào)用,相當(dāng)于window.hello(),this指向全局對(duì)象 hello.call({name:'Mike'}); // hello, I am Mike // call,apply調(diào)用,第一個(gè)參數(shù)為this指向的對(duì)象 復(fù)制代碼language = 'Python'; const obj = {language: 'TS',speak() {language = 'GO';return function() {return () => {console.log(`I speak ${this.language}`);};};} };obj.speak()()(); // 做個(gè)小測試,會(huì)打印什么呢? 復(fù)制代碼箭頭函數(shù)還有以下特點(diǎn)
- 由于箭頭函數(shù)沒有自己的 this 指針,通過 call 或 apply 調(diào)用,第一個(gè)參數(shù)會(huì)被忽略
- 不綁定 Arguments 對(duì)象,其引用上一層作用域鏈的 Arguments 對(duì)象
- 不能用作構(gòu)造器,和 new 一起用會(huì)拋出錯(cuò)誤。
- 沒有 prototype 屬性。
現(xiàn)在你應(yīng)該明白為何 React 中的函數(shù)寫法都為箭頭函數(shù),就是為了綁定 this
Symbols
ES6 引入了一種新的原始數(shù)據(jù)類型 Symbol,表示獨(dú)一無二的值,它的功能類似于一種標(biāo)識(shí)唯一性的 ID
// 每個(gè) Symbol 實(shí)例都是唯一的。因此,當(dāng)你比較兩個(gè) Symbol 實(shí)例的時(shí)候,將總會(huì)返回 false const s1 = Symbol('macOS'); const s2 = Symbol('macOS');// Symbol.for 機(jī)制有點(diǎn)類似于單例模式 const s3 = Symbol.for('windows'); // 注冊一個(gè)全局 Symbol const s4 = Symbol.for('windows'); // 已存在相同名稱的 Symbol,返回全局 Symbols1 === s2; // false s3 === s4; // true復(fù)制代碼let key = Symbol('key');function MyClass(privateData) {// 注意,Symbol值作為對(duì)象屬性名時(shí),不能用點(diǎn)運(yùn)算符this[key] = privateData; }MyClass.prototype = {doStuff() {console.log(this[key]);} };// Symbol的一些特性必須要瀏覽器的原生實(shí)現(xiàn),不可被 transpiled 或 polyfilled typeof key // symbollet c = new MyClass('hello'); c.key; // undefined c[key]; // hello復(fù)制代碼應(yīng)用場景
- 更好的設(shè)計(jì)我們的數(shù)據(jù)對(duì)象,讓“對(duì)內(nèi)操作”和“對(duì)外選擇性輸出”變得更加優(yōu)雅。
在實(shí)際應(yīng)用中,我們經(jīng)常會(huì)需要使用 Object.keys() 或者 for...in 來枚舉對(duì)象的屬性名,那在這方面,Symbol 類型的 key 表現(xiàn)的會(huì)有什么不同之處呢?來看以下示例代碼:
let obj = {[Symbol('name')]: '一斤代碼',age: 18,title: 'Engineer' }Object.keys(obj) // ['age', 'title']for (let p in obj) {console.log(p) // 分別會(huì)輸出:'age' 和 'title' }Object.getOwnPropertyNames(obj) // ['age', 'title'] 復(fù)制代碼也正因?yàn)檫@樣一個(gè)特性,當(dāng)使用 JSON.stringify() 將對(duì)象轉(zhuǎn)換成 JSON 字符串的時(shí)候,Symbol 屬性也會(huì)被排除在輸出內(nèi)容之外:
JSON.stringify(obj) // {"age":18,"title":"Engineer"} 復(fù)制代碼由上代碼可知,Symbol 類型的 key 是不能通過 Object.keys() 或者 for...in 來枚舉的,所以,利用該特性,我們可以把一些不需要對(duì)外操作和訪問的屬性使用 Symbol 來定義。
- 消除魔術(shù)字符串
上面的代碼中那樣,我們需要為常量賦一個(gè)唯一的值(比如這里的 'AUDIO'),'AUDIO' 就是一個(gè)魔術(shù)字符串,它本身沒意義,只是為了保證常量唯一的關(guān)系。常量一多,就變得十分臃腫且難以理解
現(xiàn)在有了 Symbol,我們大可不必這么麻煩了:
// 保證了三個(gè)常量的值是唯一的 const TYPE_AUDIO = Symbol() const TYPE_VIDEO = Symbol() const TYPE_IMAGE = Symbol() 復(fù)制代碼增強(qiáng)的對(duì)象字面量(Enhanced Object Literals)
const obj = {// 允許設(shè)置原型__proto__: theProtoObj,// 允許覆蓋屬性['__proto__']: somethingElse,// 屬性簡寫,等于 ‘handler: handler’handler,// 計(jì)算 (動(dòng)態(tài)) 屬性名['prop_' + (() => 42)()]: 42 }; obj.prop_42 // 42 obj.__proto__ // somethingElse 復(fù)制代碼__proto__ 需要原生支持,它在之前的 ECMAScript 版本中被移除,但大多數(shù)瀏覽器都實(shí)現(xiàn)了這一特性,包括 Node 環(huán)境
Map + Set + WeakMap + WeakSet
Set
Set 是 ES6 中新增的數(shù)據(jù)結(jié)構(gòu),它允許創(chuàng)建唯一值的集合。集合中的值可以是簡單的基本類型(如字符串或數(shù)值),但更復(fù)雜的對(duì)象類型(如對(duì)象或數(shù)組)也可以,亦或是一個(gè)新的 Set
let animals = new Set();animals.add('?'); animals.add('?'); animals.add('?'); animals.add('?'); console.log(animals.size); // 4 animals.add('?'); console.log(animals.size); // 4console.log(animals.has('?')); // true animals.delete('?'); console.log(animals.has('?')); // falseanimals.forEach(animal => {console.log(`Hey ${animal}!`); });// Hey ?! // Hey ?! // Hey ?!animals.clear(); console.log(animals.size); // 0 復(fù)制代碼我們還可以傳入一個(gè)數(shù)組來初始化集合
let myAnimals = new Set(['?', '?', '?', '?']);myAnimals.add(['?', '?']); myAnimals.add({ name: 'Rud', type: '?' }); console.log(myAnimals.size); // 4// Set 內(nèi)置了遍歷器,可以調(diào)用 forEach, for…of myAnimals.forEach(animal => {console.log(animal); });// ? // ? // ["?", "?"] // Object { name: "Rud", type: "?" } 復(fù)制代碼Map
與普通對(duì)象(Object)不同,Map 的鍵名(Key)可以是任何類型,不再局限于字符串(String),包括但不限于 objects 或 functions
let things = new Map();const myFunc = () => '?';things.set('?', 'Car'); things.set('?', 'House'); things.set('??', 'Airplane'); things.set(myFunc, '? Key is a function!');things.size; // 4things.has('?'); // truethings.has(myFunc) // true things.has(() => '?'); // false things.get(myFunc); // '? Key is a function!'things.delete('??'); things.has('??'); // falsethings.clear(); things.size; // 0// 鏈?zhǔn)皆O(shè)置鍵值對(duì) things.set('?', 'Wrench').set('?', 'Guitar').set('?', 'Joystick');const myMap = new Map();// 甚至鍵名可以是另一個(gè) Map things.set(myMap, 'Oh gosh!'); things.size; // 4 things.get(myMap); // 'Oh gosh!'復(fù)制代碼可以通過傳入包含兩個(gè)元素的數(shù)組來初始化 Map
const funArray = [['?', 'Champagne'],['?', 'Lollipop'],['?', 'Confetti'], ];let funMap = new Map(funArray); funMap.get('?'); // Champagne 復(fù)制代碼WeakMap
WeakMap 對(duì)象是一組鍵/值對(duì)的集合,其中的鍵是弱引用的。其鍵必須是對(duì)象,而值可以是任意的。它最重要的特性是 WeakMap 保持了對(duì)鍵名所引用的對(duì)象的弱引用
我們可以通過 Node 來證明一下這個(gè)問題:
// 允許手動(dòng)執(zhí)行垃圾回收機(jī)制 node --expose-gcglobal.gc(); // 返回 Nodejs 的內(nèi)存占用情況,單位是 bytes process.memoryUsage(); // heapUsed: 4640360 ≈ 4.4Mlet map = new Map(); let key = new Array(5 * 1024 * 1024); // new Array 當(dāng)為 Obj map.set(key, 1); global.gc(); process.memoryUsage(); // heapUsed: 46751472 注意這里大約是 44.6M// 所以當(dāng)你設(shè)置 key = null 時(shí),只是去掉了 key 對(duì) Obj 的強(qiáng)引用 // 并沒有去除 arr 對(duì) Obj 的強(qiáng)引用,所以 Obj 還是不會(huì)被回收掉 key = null; global.gc(); process.memoryUsage(); // heapUsed: 46754648 ≈ 44.6M// 這句話其實(shí)是無用的,因?yàn)?key 已經(jīng)是 null 了 map.delete(key); global.gc(); process.memoryUsage(); // heapUsed: 46755856 ≈ 44.6M 復(fù)制代碼node --expose-gcglobal.gc(); process.memoryUsage(); // heapUsed: 4638992 ≈ 4.4Mconst wm = new WeakMap(); let key = new Array(5 * 1024 * 1024); wm.set(key, 1); global.gc(); process.memoryUsage(); // heapUsed: 46776176 ≈ 44.6M// 當(dāng)我們設(shè)置 key = null 的時(shí)候,就只有 wm 對(duì)所引用對(duì)象的弱引用 // 下次垃圾回收機(jī)制執(zhí)行的時(shí)候,該引用對(duì)象就會(huì)被回收掉。 key = null; global.gc(); process.memoryUsage(); // heapUsed: 4800792 ≈ 4.6M 復(fù)制代碼應(yīng)用場景
傳統(tǒng)使用 jQuery 的時(shí)候,我們會(huì)通過 $.data() 方法在 DOM 對(duì)象上儲(chǔ)存相關(guān)信息(就比如在刪除按鈕元素上儲(chǔ)存帖子的 ID 信息),jQuery 內(nèi)部會(huì)使用一個(gè)對(duì)象管理 DOM 和對(duì)應(yīng)的數(shù)據(jù),當(dāng)你將 DOM 元素刪除,DOM 對(duì)象置為空的時(shí)候,相關(guān)聯(lián)的數(shù)據(jù)并不會(huì)被刪除,你必須手動(dòng)執(zhí)行 $.removeData() 方法才能刪除掉相關(guān)聯(lián)的數(shù)據(jù),WeakMap 就可以簡化這一操作:
let wm = new WeakMap(), element = document.querySelector(".element"); wm.set(element, "data");let value = wm.get(elemet); console.log(value); // dataelement.parentNode.removeChild(element); element = null; 復(fù)制代碼WeakSet
特性與 WeakMap 相似
遍歷器(Iterators + For..Of)
遍歷器(Iterator)它是一種接口,為各種不同的數(shù)據(jù)結(jié)構(gòu)提供統(tǒng)一的訪問機(jī)制。任何數(shù)據(jù)結(jié)構(gòu)只要部署 Iterator 接口,就可以完成遍歷操作(即依次處理該數(shù)據(jù)結(jié)構(gòu)的所有成員)。 Iterator 的作用有三個(gè):
- 為各種數(shù)據(jù)結(jié)構(gòu),提供一個(gè)統(tǒng)一的、簡便的訪問接口;
- 使得數(shù)據(jù)結(jié)構(gòu)的成員能夠按某種次序排列;
- ES6 創(chuàng)造了一種新的遍歷命令 for...of 循環(huán),Iterator 接口主要供 for...of 消費(fèi)。
ES6 規(guī)定,默認(rèn)的 Iterator 接口部署在數(shù)據(jù)結(jié)構(gòu)的 Symbol.iterator 屬性,或者說,一個(gè)數(shù)據(jù)結(jié)構(gòu)只要具有 Symbol.iterator 屬性,就可以認(rèn)為是“可遍歷的”(iterable)
let fibonacci = {[Symbol.iterator]() {let pre = 0, cur = 1;return {next() {[pre, cur] = [cur, pre + cur]; // 數(shù)組解構(gòu)return { done: false, value: cur }}}} }for (var n of fibonacci) {// 當(dāng)n超過1000時(shí)停止if (n > 1000)break;console.log(n); } 復(fù)制代碼上面代碼中,對(duì)象 fibonacci 是可遍歷的(iterable),因?yàn)榫哂?Symbol.iterator 屬性。執(zhí)行這個(gè)屬性,會(huì)返回一個(gè)遍歷器對(duì)象。該對(duì)象的根本特征就是具有 next 方法。每次調(diào)用 next 方法,都會(huì)返回一個(gè)代表當(dāng)前成員的信息對(duì)象,具有 value 和 done 兩個(gè)屬性
原生具備 Iterator 接口的數(shù)據(jù)結(jié)構(gòu)如下
- Array
- Map
- Set
- String
- TypedArray
- 函數(shù)的 arguments 對(duì)象
- NodeList 對(duì)象
for...in 和 for..of 的差別
- for...in 遍歷鍵名(Key)并轉(zhuǎn)化為字符串,for...of 遍歷鍵值(Value)
- for...in 語句以任意順序遍歷一個(gè)對(duì)象自有的、繼承的、可枚舉的、非 Symbol 的屬性
- for...in 更適合遍歷對(duì)象,for...of 更適合遍歷數(shù)組。
新增 API(Math + Number + String + Object APIs)
我們先來看看新增的 Number.EPSILON,不少人都是懵逼的狀態(tài),WTF?
先來看看的 JS 世界中的一道送命題
事出必有因,這是因?yàn)?JS 的數(shù)值采用了 IEEE 754 標(biāo)準(zhǔn),而且 JS 是弱類型語言,所以數(shù)字都是以64位雙精度浮點(diǎn)數(shù)據(jù)類型儲(chǔ)存。也就是說,JS 語言底層根本沒有整數(shù),所有數(shù)字都是小數(shù)!當(dāng)我們以為在用整數(shù)進(jìn)行計(jì)算時(shí),都會(huì)被轉(zhuǎn)換為小數(shù)
而浮點(diǎn)數(shù)都是以多位二進(jìn)制的方式進(jìn)行存儲(chǔ)的
十進(jìn)制的0.1用二進(jìn)制表示為:0.0 0011 0011 0011 0011…,循環(huán)部分是0011
十進(jìn)制0.2用二進(jìn)制表示為:0.0011 0011 0011 0011…,循環(huán)部分是0011
由于存儲(chǔ)空間有限,最后計(jì)算機(jī)會(huì)舍棄后面的數(shù)值,所以我們最后就只能得到一個(gè)近似值
JS中采用的 IEEE 754 的雙精度標(biāo)準(zhǔn)也是一樣的道理在存儲(chǔ)空間有限的情況下,當(dāng)出現(xiàn)這種無法整除的小數(shù)的時(shí)候就會(huì)取一個(gè)近似值,在 JS 中如果這個(gè)近似值足夠近似,那么 JS 就會(huì)認(rèn)為他就是那個(gè)值。
console.log(0.1000000000000001) // 0.1000000000000001 (中間14個(gè)0,不會(huì)被近似處理,輸出本身) console.log(0.10000000000000001) // 0.1 (中間15個(gè)0,js會(huì)認(rèn)為兩個(gè)值足夠近似,所以輸出0.1) 復(fù)制代碼那么這個(gè)近似的界限如何判斷呢?
ES6的 Number.EPSILON就是一個(gè)界限,它表示 1 與大于 1 的最小浮點(diǎn)數(shù)之間的差。
對(duì)于 64 位浮點(diǎn)數(shù)來說,大于 1 的最小浮點(diǎn)數(shù)相當(dāng)于二進(jìn)制的1.00..001,小數(shù)點(diǎn)后面有連續(xù) 51 個(gè)零。這個(gè)值減去 1 之后,就等于 2 的 -52 次方
Number.EPSILON === Math.pow(2, -52) // true Number.EPSILON // 2.220446049250313e-16 Number.EPSILON.toFixed(20) // "0.00000000000000022204" 復(fù)制代碼Number.EPSILON 實(shí)際上是 JavaScript 能夠表示的最小精度。誤差如果小于這個(gè)值,就可以認(rèn)為已經(jīng)沒有意義了,即不存在誤差了。
0.1 + 0.2 - 0.3 // 5.551115123125783e-175.551115123125783e-17.toFixed(20) // '0.00000000000000005551' 復(fù)制代碼0.00000000000000005551 < 0.00000000000000022204 // true 復(fù)制代碼顯然,0.30000000000000004 不存在誤差,不會(huì)被近似處理
我們可以通過以下手段來達(dá)到我們想要的效果
function withinErrorMargin (left, right) {return Math.abs(left - right) < Number.EPSILON * Math.pow(2, 2); }0.1 + 0.2 === 0.3 // false withinErrorMargin(0.1 + 0.2, 0.3) // true 復(fù)制代碼其他一些新增的 API
Number.isInteger(Infinity) // false Number.isNaN("NaN") // falseMath.sign(-5) // 判斷一個(gè)數(shù)到底是正數(shù)、負(fù)數(shù)、還是零 -1 Math.hypot(3, 4) // 返回所有參數(shù)的平方和的平方根 5 Math.imul(-2, -2) // 返回兩個(gè)數(shù)以 32 位帶符號(hào)整數(shù)形式相乘的結(jié)果 4"abcde".includes("cd") // true "abc".repeat(3) // "abcabcabc"Array.from(document.querySelectorAll("*")) // 返回一個(gè)真正的數(shù)組 Array.of(1, 2, 3) // [1,2,3] [0, 0, 0].fill(7, 1) // [0,7,7] [1,2,3].findIndex(x => x == 2) // 1 ["a", "b", "c"].entries() // [0, "a"], [1,"b"], [2,"c"] ["a", "b", "c"].keys() // 0, 1, 2 ["a", "b", "c"].values() // "a", "b", "c"Object.assign(Point, { origin: new Point(0,0) }) // 合并對(duì)象 復(fù)制代碼二進(jìn)制和八進(jìn)制字面量(Binary and Octal Literals)
0b111 === 7 // true 二進(jìn)制 0o111 === 73 // true 八進(jìn)制 0x111 === 273 // true 十六進(jìn)制 復(fù)制代碼進(jìn)階篇
尾遞歸(Tail Calls)
假設(shè)現(xiàn)在要實(shí)現(xiàn)一個(gè)階乘函數(shù),即 5!= 120,我們很容易想到遞歸實(shí)現(xiàn)
function factorial(n) {if (n === 1) return 1;return n * factorial(n - 1); } 復(fù)制代碼但遞歸非常耗費(fèi)內(nèi)存,因?yàn)樾枰瑫r(shí)保存成千上百個(gè)調(diào)用記錄,很容易發(fā)生"棧溢出"錯(cuò)誤(stack overflow)。但對(duì)于尾遞歸來說,由于只存在一個(gè)調(diào)用記錄,所以永遠(yuǎn)不會(huì)發(fā)生"棧溢出"錯(cuò)誤。
何為調(diào)用記錄,在示例代碼中,由于最后一步返回了一個(gè)表達(dá)式,內(nèi)存會(huì)保留 n 這個(gè)變量的信息和 factorial(n - 1) 調(diào)用下一次函數(shù)的位置,形成一層層的調(diào)用棧
尾遞歸的實(shí)現(xiàn),往往需要改寫遞歸函數(shù),確保最后一步只調(diào)用自身,返回函數(shù)本身。做到這一點(diǎn)的方法,就是把所有用到的內(nèi)部變量改寫成函數(shù)的參數(shù)。尾遞歸優(yōu)化如下
function factorial(n, acc = 1) { ;if (n <= 1) return acc;return factorial(n - 1, n * acc); }factorial(100000) 復(fù)制代碼由此可見,"尾調(diào)用優(yōu)化"對(duì)遞歸操作意義重大,所以一些函數(shù)式編程語言將其寫入了語言規(guī)格。ES6 也是如此,第一次明確規(guī)定,所有 ECMAScript 的實(shí)現(xiàn),都必須部署"尾調(diào)用優(yōu)化"。這就是說,在 ES6 中,只要使用尾遞歸,就不會(huì)發(fā)生棧溢出,相對(duì)節(jié)省內(nèi)存。
ES6的尾調(diào)用優(yōu)化只在嚴(yán)格模式下開啟,正常模式是無效的。
反射(Reflect)
Reflect 對(duì)象與 Proxy 對(duì)象一樣,也是 ES6 為了操作對(duì)象而提供的新 API。Reflect 對(duì)象的設(shè)計(jì)目的有這樣幾個(gè)。
- 將Object對(duì)象的一些明顯屬于語言內(nèi)部的方法(比如 Object.defineProperty),放到 Reflect 對(duì)象上
- 修改某些 Object 方法的返回結(jié)果,讓其變得更合理。比如,Object.defineProperty(obj, name, desc) 在無法定義屬性時(shí),會(huì)拋出一個(gè)錯(cuò)誤,而 Reflect.defineProperty(obj, name, desc) 則會(huì)返回 false。
獲取屬性名的方法有很多,以上面的代碼為例子,它們的區(qū)別如下
| Object.getOwnPropertyNames(O) | [ 'a', 'b' ] | 獲取除 Symbol 外的所有屬性 |
| Object.getOwnPropertySymbols(O) | [ Symbol(c) ] | 只獲取 Symbol 屬性 |
| OReflect.ownKeys(O) | [ 'a', 'b', Symbol(c) ] | 獲取所有屬性 |
| for...in | a | 獲取除 Symbol 外的可枚舉屬性 |
代理(Proxy)
Proxy 可以理解成,在目標(biāo)對(duì)象之前架設(shè)一層“攔截”,外界對(duì)該對(duì)象的訪問,都必須先通過這層攔截,因此提供了一種機(jī)制,可以對(duì)外界的訪問進(jìn)行過濾和改寫。Proxy 這個(gè)詞的原意是代理,用在這里表示由它來“代理”某些操作,可以譯為“代理器”。
// 代理一個(gè)對(duì)象 var target = {}; var handler = {get: function (receiver, name) {return `Hello, ${name}!`;} };var p = new Proxy(target, handler); p.world; // "Hello, world!" 復(fù)制代碼// 代理一個(gè)函數(shù) var target = function () { return "I am the target"; }; var handler = {apply: function (receiver, ...args) {return "I am the proxy";} };var p = new Proxy(target, handler); p(); // "I am the proxy" 復(fù)制代碼// 代理會(huì)將所有應(yīng)用到它的操作轉(zhuǎn)發(fā)到這個(gè)對(duì)象上 let target = {}; let p = new Proxy(target, {});p.a = 37; target.a; // 37 操作轉(zhuǎn)發(fā)到目標(biāo) 復(fù)制代碼// 如何實(shí)現(xiàn) a == 1 && a == 2 && a == 3,利用Proxy的get劫持 const a = new Proxy({},{val: 1,get() {return () => this.val++;}} ); a == 1 && a == 2 && a == 3; // true 復(fù)制代碼由于 ES5 的限制,Proxy 不能被 transpiled or polyfilled,自己親自入的坑,由于在項(xiàng)目中使用了 Mobx5.x,其內(nèi)部是用 Proxy 寫的,結(jié)果 IE11 不支持 ES6,只得回退版本 Mobx 到 4.x
生成器(Generators)
Generator 函數(shù)是 ES6 提供的一種異步編程解決方案。 Generator 函數(shù)有多種理解角度。語法上,首先可以把它理解成,Generator 函數(shù)是一個(gè)狀態(tài)機(jī),封裝了多個(gè)內(nèi)部狀態(tài)。
形式上,Generator 函數(shù)是一個(gè)普通函數(shù),但是有兩個(gè)特征。一是,function 關(guān)鍵字與函數(shù)名之間有一個(gè)星號(hào);二是,函數(shù)體內(nèi)部使用 yield 表達(dá)式,定義不同的內(nèi)部狀態(tài)
function* helloWorldGenerator() {yield 'hello'; // yield使Generator函數(shù)暫停了執(zhí)行,并將結(jié)果返回給調(diào)用者yield 'world'; // 當(dāng)下一次調(diào)用時(shí),從它中斷的地方恢復(fù)執(zhí)行return 'ending'; }var hw = helloWorldGenerator(); a = hw.next(); // { value: 'hello', done: false } b = hw.next(); // { value: 'world', done: false } c = hw.next(); // { value: 'ending', done: true } 復(fù)制代碼可以利用這種暫停執(zhí)行的特性,來實(shí)現(xiàn)惰性求值
向Generator傳遞數(shù)據(jù)
function* sayFullName() {const firstName = yield;const secondName = yield;console.log(firstName + ' ' + secondName); } let fullName = sayFullName(); fullName.next(); // 第一次調(diào)用,代碼暫停在 const firstName = yield,因?yàn)闆]有通過 yield 發(fā)送任何值,因此 next 將返回 undefined fullName.next('Handsome'); // 第二次調(diào)用,傳入了值 Handsome,yield 被 Handsome 替代,因此 firstName 的值變?yōu)?Handsome,代碼執(zhí)行恢復(fù) // 直到再次遇到 const secondName = yield 暫停執(zhí)行 fullName.next('Jack'); // 第三次調(diào)用,傳入了值 Jack,yield 被 Jack 替代,因此 secondName 的值變?yōu)?Jack,代碼執(zhí)行恢復(fù) // 打印 Handsome Jack 復(fù)制代碼使用Generator處理異步調(diào)用
let generator; let getDataOne = () => {setTimeout(() => {generator.next('dummy data one');}, 1000); }; let getDataTwo = () => {setTimeout(() => {generator.next('dummy data one');}, 1000); };function* main() {let dataOne = yield getDataOne();let dataTwo = yield getDataTwo();console.log(dataOne, dataTwo); } generator = main(); generator.next(); // 執(zhí)行 getDataOne(),然后 yield 暫停 // 直至一秒后 generator.next('dummy data one') 恢復(fù)代碼執(zhí)行,并賦值 dataOne console.log('i am previous print'); // i am previous print // dummy data one dummy data one 復(fù)制代碼Promises
Promises 是一個(gè)異步編程的解決方案,比傳統(tǒng)的解決方案——回調(diào)函數(shù)和事件——更合理和更強(qiáng)大。
所謂 Promise,簡單說就是一個(gè)容器,里面保存著某個(gè)未來才會(huì)結(jié)束的事件(通常是一個(gè)異步操作)的結(jié)果。從語法上說,Promise 是一個(gè)對(duì)象,從它可以獲取異步操作的消息。Promise 提供統(tǒng)一的 API,各種異步操作都可以用同樣的方法進(jìn)行處理。
function timeout(duration = 0) {return new Promise((resolve, reject) => {setTimeout(resolve, duration);}) }var p = timeout(1000).then(() => {return timeout(2000); }).then(() => {throw new Error("hmm"); }).catch(err => {return Promise.all([timeout(100), timeout(200)]); }) 復(fù)制代碼這里強(qiáng)調(diào)幾點(diǎn)
- 不要?jiǎng)儕Z函數(shù) return 的能力,很多人寫 Promise,照樣有大量嵌套,掉進(jìn) Promise 地獄,要記得及時(shí) return,避免嵌套
- 當(dāng)需要多個(gè)請求全部結(jié)束時(shí),才更新數(shù)據(jù),可以用 Promise.all(fetch1,fetch2)
- 當(dāng)需要從多個(gè)請求中,接受最先返回?cái)?shù)據(jù)的那個(gè)請求,可以用 Promise.race(fetch1,fetch2)
結(jié)尾
ES6 是 ECMAScript 一個(gè)非常重要的版本,我們必須深入理解,不僅能提高我們書寫代碼的能力,還能增強(qiáng)業(yè)務(wù)能力
附上一張我之前精心整理的思維導(dǎo)圖
本文參考資料
- Learn ES2015
- ECMAScript 6 入門
- ES6 系列之 WeakMap
轉(zhuǎn)載于:https://juejin.im/post/5cf0ebc26fb9a07ee27afc60
總結(jié)
以上是生活随笔為你收集整理的什么?ES6 中还有 Tail Calls!的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 华为云DevCloud重金悬赏,开发者大
- 下一篇: 回调地狱解决方案之Promise