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