[TOC]
# ES6系列
在我們開發的時候,可能認為應該默認使用 let 而不是 var ,這種情況下,對于需要寫保護的變量要使用 const。然而另一種做法日益普及:默認使用 const,只有當確實需要改變變量的值的時候才使用 let。這是因為大部分的變量的值在初始化后不應再改變,而預料之外的變量之的改變是很多 bug 的源頭。
## 內建迭代器
為了更好的訪問對象中的內容,比如有的時候我們僅需要數組中的值,但有的時候不僅需要使用值還需要使用索引,ES6 為數組、Map、Set 集合內建了以下三種迭代器:
1. entries() 返回一個遍歷器對象,用來遍歷\[鍵名, 鍵值\]組成的數組。對于數組,鍵名就是索引值。
2. keys() 返回一個遍歷器對象,用來遍歷所有的鍵名。
3. values() 返回一個遍歷器對象,用來遍歷所有的鍵值。
~~~
let set = new Set(['a', 'b', 'c']);
console.log(set.keys()); // SetIterator?{"a", "b", "c"} 注意keys、values返回的是遍歷器對象
console.log([...set.keys()]); // ["a", "b", "c"]
~~~
因此我們可以用 for...of 配合內建迭代器來遍歷
~~~
var colors = ["red", "green", "blue"];
for (let index of colors.keys()) {
console.log(index);
}
// 0
// 1
// 2
for (let color of colors.values()) {
console.log(color);
}
// red
// green
// blue
for (let item of colors.entries()) {
console.log(item);
}
// [ 0, "red" ]
// [ 1, "green" ]
// [ 2, "blue" ]
~~~
## WeakMap
WeakMaps 保持了對鍵名所引用的對象的**弱引用**,即垃圾回收機制不將該引用考慮在內。只要所引用的對象的其他引用都被清除,垃圾回收機制就會釋放該對象所占用的內存。也就是說,一旦不再需要,WeakMap 里面的鍵名對象和所對應的鍵值對會自動消失,不用手動刪除引用。
也正是因為這樣的特性,WeakMap 內部有多少個成員,取決于垃圾回收機制有沒有運行,運行前后很可能成員個數是不一樣的,而垃圾回收機制何時運行是不可預測的,因此 ES6 規定 WeakMap 不可遍歷。
所以 WeakMap 不像 Map,一是沒有遍歷操作(即沒有keys()、values()和entries()方法),也沒有 size 屬性,也不支持 clear 方法,所以 WeakMap只有四個方法可用:get()、set()、has()、delete()。
**應用**
### 1\. 在 DOM 對象上保存相關數據
傳統使用 jQuery 的時候,我們會通過 $.data() 方法在 DOM 對象上儲存相關信息(就比如在刪除按鈕元素上儲存帖子的 ID 信息),jQuery 內部會使用一個對象管理 DOM 和對應的數據,當你將 DOM 元素刪除,DOM 對象置為空的時候,相關聯的數據并不會被刪除,你必須手動執行 $.removeData() 方法才能刪除掉相關聯的數據,WeakMap 就可以簡化這一操作:
~~~js
let wm = new WeakMap(), element = document.querySelector(".element");
wm.set(element, "data");
let value = wm.get(elemet);
console.log(value); // data
element.parentNode.removeChild(element);
element = null;
~~~
### 2\. 數據緩存
從上一個例子,我們也可以看出,當我們需要關聯對象和數據,比如在不修改原有對象的情況下儲存某些屬性或者根據對象儲存一些計算的值等,而又不想管理這些數據的死活時非常適合考慮使用 WeakMap。數據緩存就是一個非常好的例子:
~~~js
const cache = new WeakMap();
function countOwnKeys(obj) {
if (cache.has(obj)) {
console.log('Cached');
return cache.get(obj);
} else {
console.log('Computed');
const count = Object.keys(obj).length;
cache.set(obj, count);
return count;
}
}
~~~
### 3\. 私有屬性
WeakMap 也可以被用于實現私有變量,不過在 ES6 中實現私有變量的方式有很多種,這只是其中一種:
~~~js
const privateData = new WeakMap();
class Person {
constructor(name, age) {
privateData.set(this, { name: name, age: age });
}
getName() {
return privateData.get(this).name;
}
getAge() {
return privateData.get(this).age;
}
}
export default Person;
~~~
## Promise 紅綠燈問題
題目:紅燈三秒亮一次,綠燈一秒亮一次,黃燈2秒亮一次;如何讓三個燈不斷交替重復亮燈?(用 Promse 實現)
三個亮燈函數已經存在:
~~~js
function red(){
console.log('red');
}
function green(){
console.log('green');
}
function yellow(){
console.log('yellow');
}
~~~
利用 then 和遞歸實現:
~~~js
function red(){
console.log('red');
}
function green(){
console.log('green');
}
function yellow(){
console.log('yellow');
}
var light = function(timmer, cb){
return new Promise(function(resolve, reject) {
setTimeout(function() {
cb();
resolve();
}, timmer);
});
};
var step = function() {
Promise.resolve().then(function(){
return light(3000, red);
}).then(function(){
return light(2000, green);
}).then(function(){
return light(1000, yellow);
}).then(function(){
step();
});
}
step();
~~~
## Promise 的局限性
### 1\. 錯誤被吃掉
首先我們要理解,什么是錯誤被吃掉,是指錯誤信息不被打印嗎?
并不是,舉個例子:
~~~js
throw new Error('error');
console.log(233333);
~~~
在這種情況下,因為 throw error 的緣故,代碼被阻斷執行,并不會打印 233333,再舉個例子:
~~~js
const promise = new Promise(null);
console.log(233333);
~~~
以上代碼依然會被阻斷執行,這是因為如果通過無效的方式使用 Promise,并且出現了一個錯誤阻礙了正常 Promise 的構造,結果會得到一個立刻跑出的異常,而不是一個被拒絕的 Promise。
然而再舉個例子:
~~~js
let promise = new Promise(() => {
throw new Error('error')
});
console.log(2333333);
~~~
這次會正常的打印`233333`,說明 Promise 內部的錯誤不會影響到 Promise 外部的代碼,而這種情況我們就通常稱為 “吃掉錯誤”。
其實這并不是 Promise 獨有的局限性,try..catch 也是這樣,同樣會捕獲一個異常并簡單的吃掉錯誤。
而正是因為錯誤被吃掉,Promise 鏈中的錯誤很容易被忽略掉,這也是為什么會一般推薦在 Promise 鏈的最后添加一個 catch 函數,因為對于一個沒有錯誤處理函數的 Promise 鏈,任何錯誤都會在鏈中被傳播下去,直到你注冊了錯誤處理函數。
### 2\. 單一值
Promise 只能有一個完成值或一個拒絕原因,然而在真實使用的時候,往往需要傳遞多個值,一般做法都是構造一個對象或數組,然后再傳遞,then 中獲得這個值后,又會進行取值賦值的操作,每次封裝和解封都無疑讓代碼變得笨重。
說真的,并沒有什么好的方法,建議是使用 ES6 的解構賦值:
~~~js
Promise.all([Promise.resolve(1), Promise.resolve(2)])
.then(([x, y]) => {
console.log(x, y);
});
~~~
### 3\. 無法取消
Promise 一旦新建它就會立即執行,無法中途取消。
### 4\. 無法得知 pending 狀態
當處于 pending 狀態時,無法得知目前進展到哪一個階段(剛剛開始還是即將完成)。
# ES6 的 Class 與 ES5 的對應關系
ES6 中:
~~~js
class Person {
constructor(name) {
this.name = name;
}
sayHello() {
return 'hello, I am ' + this.name;
}
}
var kevin = new Person('Kevin');
kevin.sayHello(); // hello, I am Kevin
~~~
對應到 ES5 中就是:
~~~js
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function () {
return 'hello, I am ' + this.name;
};
var kevin = new Person('Kevin');
kevin.sayHello(); // hello, I am Kevin
~~~
我們可以看到 ES5 的構造函數 Person,對應 ES6 的 Person 類的 constructor 方法。
值得注意的是:**類的內部所有定義的方法,都是不可枚舉的(non-enumerable)**
以上面的例子為例,在 ES6 中:
~~~js
Object.keys(Person.prototype); // []
Object.getOwnPropertyNames(Person.prototype); // ["constructor", "sayHello"]
~~~
然而在 ES5 中:
~~~js
Object.keys(Person.prototype); // ['sayHello']
Object.getOwnPropertyNames(Person.prototype); // ["constructor", "sayHello"]
~~~
## 實例屬性
以前,我們定義實例屬性,只能寫在類的 constructor 方法里面。比如:
~~~js
class Person {
constructor() {
this.state = {
count: 0
};
}
}
~~~
然而現在有一個提案,對實例屬性和靜態屬性都規定了新的寫法,而且 Babel 已經支持。現在我們可以寫成:
~~~js
class Person {
state = {
count: 0
};
}
~~~
對應到 ES5 都是:
~~~js
function Person() {
this.state = {
count: 0
};
}
~~~
## 靜態方法
所有在類中定義的方法,都會被實例繼承。如果在一個方法前,加上 static 關鍵字,就表示該方法不會被實例繼承,而是直接通過類來調用,這就稱為“靜態方法”。
ES6 中:
~~~js
class Person {
static sayHello() {
return 'hello';
}
}
Person.sayHello() // 'hello'
var kevin = new Person();
kevin.sayHello(); // TypeError: kevin.sayHello is not a function
~~~
對應 ES5:
~~~js
function Person() {}
Person.sayHello = function() {
return 'hello';
};
Person.sayHello(); // 'hello'
var kevin = new Person();
kevin.sayHello(); // TypeError: kevin.sayHello is not a function
~~~
## 靜態屬性
靜態屬性指的是 Class 本身的屬性,即 Class.propName,而不是定義在實例對象(this)上的屬性。以前,我們添加靜態屬性只可以這樣:
~~~js
class Person {}
Person.name = 'kevin';
~~~
因為上面提到的提案,現在可以寫成:
~~~js
class Person {
static name = 'kevin';
}
~~~
對應到 ES5 都是:
~~~js
function Person() {};
Person.name = 'kevin';
~~~
## getter 和 setter
與 ES5 一樣,在“類”的內部可以使用 get 和 set 關鍵字,對某個屬性設置存值函數和取值函數,攔截該屬性的存取行為。
~~~js
class Person {
get name() {
return 'kevin';
}
set name(newName) {
console.log('new name 為:' + newName)
}
}
let person = new Person();
person.name = 'daisy';
// new name 為:daisy
console.log(person.name);
// kevin
~~~
對應到 ES5 中:
~~~js
function Person(name) {}
Person.prototype = {
get name() {
return 'kevin';
},
set name(newName) {
console.log('new name 為:' + newName)
}
}
let person = new Person();
person.name = 'daisy';
// new name 為:daisy
console.log(person.name);
// kevin
~~~
# Class extends 實現繼承
ES6
~~~
class Parent {
constructor(name) {
this.name = name;
}
}
class Child extends Parent {
constructor(name, age) {
super(name); // 調用父類的 constructor(name)
this.age = age;
}
}
var child1 = new Child('kevin', '18');
console.log(child1);
~~~
值得注意的是:
super 關鍵字表示父類的構造函數,相當于 ES5 的 Parent.call(this)。
子類必須在 constructor 方法中調用 super 方法,否則新建實例時會報錯。這是因為子類沒有自己的 this 對象,而是繼承父類的 this 對象,然后對其進行加工。如果不調用 super 方法,子類就得不到 this 對象。
也正是因為這個原因,在子類的構造函數中,只有調用 super 之后,才可以使用 this 關鍵字,否則會報錯。
對應的 ES5 的寄生組合式繼承
~~~
function Parent (name) {
this.name = name;
}
Parent.prototype.getName = function () {
console.log(this.name)
}
function Child (name, age) {
Parent.call(this, name);
this.age = age;
}
Child.prototype = Object.create(Parent.prototype);
var child1 = new Child('kevin', '18');
console.log(child1);
~~~
原型鏈示意圖:

## 子類的 \_\_proto\_\_
在 ES6 中,父類的靜態方法,可以被子類繼承。舉個例子:
~~~js
class Foo {
static classMethod() {
return 'hello';
}
}
class Bar extends Foo {
}
Bar.classMethod(); // 'hello'
~~~
這是因為 Class 作為構造函數的語法糖,同時有 prototype 屬性和 \_\_proto\_\_ 屬性,因此同時存在兩條繼承鏈。
(1)子類的 \_\_proto\_\_ 屬性,表示構造函數的繼承,總是指向父類。
(2)子類 prototype 屬性的 \_\_proto\_\_ 屬性,表示方法的繼承,總是指向父類的 prototype 屬性。
~~~js
class Parent {
}
class Child extends Parent {
}
console.log(Child.__proto__ === Parent); // true
console.log(Child.prototype.__proto__ === Parent.prototype); // true
~~~
ES6 的原型鏈示意圖為:

我們會發現,相比寄生組合式繼承,ES6 的 class 多了一個`Object.setPrototypeOf(Child, Parent)`的步驟。
# defineProperty 與 Proxy
## definePropety
ES5 提供了 Object.defineProperty 方法,該方法可以在一個對象上定義一個新屬性,或者修改一個對象的現有屬性,并返回這個對象。
**語法**
> Object.defineProperty(obj, prop, descriptor)
**參數**
~~~
obj: 要在其上定義屬性的對象。
prop: 要定義或修改的屬性的名稱。
descriptor: 將被定義或修改的屬性的描述符。
~~~
舉個例子:
~~~js
var obj = {};
Object.defineProperty(obj, "num", {
value : 1,
writable : true,
enumerable : true,
configurable : true
});
// 對象 obj 擁有屬性 num,值為 1
~~~
雖然我們可以直接添加屬性和值,但是使用這種方式,我們能進行更多的配置。
函數的第三個參數 descriptor 所表示的屬性描述符有兩種形式:**數據描述符和存取描述符**。
**兩者均具有以下兩種鍵值**:
- configurable:當且僅當該屬性的 configurable 為 true 時,該屬性描述符才能夠被改變,也能夠被刪除。默認為 false。
- enumerable:當且僅當該屬性的 enumerable 為 true 時,該屬性才能夠出現在對象的枚舉屬性中。默認為 false。
**數據描述符同時具有以下可選鍵值**:
- value:該屬性對應的值。可以是任何有效的 JavaScript 值(數值,對象,函數等)。默認為 undefined。
- writable:當且僅當該屬性的 writable 為 true 時,該屬性才能被賦值運算符改變。默認為 false。
**存取描述符同時具有以下可選鍵值**:
- get:一個給屬性提供 getter 的方法,如果沒有 getter 則為 undefined。該方法返回值被用作屬性值。默認為 undefined。
- set:一個給屬性提供 setter 的方法,如果沒有 setter 則為 undefined。該方法將接受唯一參數,并將該參數的新值分配給該屬性。默認為 undefined。
值得注意的是:
**屬性描述符必須是數據描述符或者存取描述符兩種形式之一,不能同時是兩者**。這就意味著你可以:
~~~js
Object.defineProperty({}, "num", {
value: 1,
writable: true,
enumerable: true,
configurable: true
});
~~~
也可以:
~~~js
var value = 1;
Object.defineProperty({}, "num", {
get : function(){
return value;
},
set : function(newValue){
value = newValue;
},
enumerable : true,
configurable : true
});
~~~
但是不可以:
~~~js
// 報錯
Object.defineProperty({}, "num", {
value: 1,
get: function() {
return 1;
}
});
~~~
此外,所有的屬性描述符都是非必須的,但是 descriptor 這個字段是必須的,如果不進行任何配置,你可以這樣:
~~~js
var obj = Object.defineProperty({}, "num", {});
console.log(obj.num); // undefined
~~~
## Setters 和 Getters
之所以講到 defineProperty,是因為我們要使用存取描述符中的 get 和 set,這兩個方法又被稱為 getter 和 setter。由 getter 和 setter 定義的屬性稱做”存取器屬性“。
當程序查詢存取器屬性的值時,JavaScript 調用 getter方法。這個方法的返回值就是屬性存取表達式的值。當程序設置一個存取器屬性的值時,JavaScript 調用 setter 方法,將賦值表達式右側的值當做參數傳入 setter。從某種意義上講,這個方法負責“設置”屬性值。可以忽略 setter 方法的返回值。
舉個例子:
~~~js
var obj = {}, value = null;
Object.defineProperty(obj, "num", {
get: function(){
console.log('執行了 get 操作')
return value;
},
set: function(newValue) {
console.log('執行了 set 操作')
value = newValue;
}
})
obj.num = 1 // 執行了 set 操作
console.log(obj.num); // 執行了 get 操作 // 1
~~~
## proxy
使用 defineProperty 只能重定義屬性的讀取(get)和設置(set)行為,到了 ES6,提供了 Proxy,可以重定義更多的行為,比如 in、delete、函數調用等更多行為。
Proxy 這個詞的原意是代理,用在這里表示由它來“代理”某些操作,ES6 原生提供 Proxy 構造函數,用來生成 Proxy 實例。我們來看看它的語法:
~~~js
var proxy = new Proxy(target, handler);
~~~
proxy 對象的所有用法,都是上面這種形式,不同的只是handler參數的寫法。其中,new Proxy()表示生成一個Proxy實例,target參數表示所要攔截的目標對象,handler參數也是一個對象,用來定制攔截行為。
~~~js
var proxy = new Proxy({}, {
get: function(obj, prop) {
console.log('設置 get 操作')
return obj[prop];
},
set: function(obj, prop, value) {
console.log('設置 set 操作')
obj[prop] = value;
}
});
proxy.time = 35; // 設置 set 操作
console.log(proxy.time); // 設置 get 操作 // 35
~~~
簡單地和上面使用 defineProperty 來實現比較一下,可以看出:
- 使用 Proxy 一次性地攔截了所有的屬性的設置與讀取
- ???
- 序言 & 更新日志
- H5
- Canvas
- 序言
- Part1-直線、矩形、多邊形
- Part2-曲線圖形
- Part3-線條操作
- Part4-文本操作
- Part5-圖像操作
- Part6-變形操作
- Part7-像素操作
- Part8-漸變與陰影
- Part9-路徑與狀態
- Part10-物理動畫
- Part11-邊界檢測
- Part12-碰撞檢測
- Part13-用戶交互
- Part14-高級動畫
- CSS
- SCSS
- codePen
- 速查表
- 面試題
- 《CSS Secrets》
- SVG
- 移動端適配
- 濾鏡(filter)的使用
- JS
- 基礎概念
- 作用域、作用域鏈、閉包
- this
- 原型與繼承
- 數組、字符串、Map、Set方法整理
- 垃圾回收機制
- DOM
- BOM
- 事件循環
- 嚴格模式
- 正則表達式
- ES6部分
- 設計模式
- AJAX
- 模塊化
- 讀冴羽博客筆記
- 第一部分總結-深入JS系列
- 第二部分總結-專題系列
- 第三部分總結-ES6系列
- 網絡請求中的數據類型
- 事件
- 表單
- 函數式編程
- Tips
- JS-Coding
- Framework
- Vue
- 書寫規范
- 基礎
- vue-router & vuex
- 深入淺出 Vue
- 響應式原理及其他
- new Vue 發生了什么
- 組件化
- 編譯流程
- Vue Router
- Vuex
- 前端路由的簡單實現
- React
- 基礎
- 書寫規范
- Redux & react-router
- immutable.js
- CSS 管理
- React 16新特性-Fiber 與 Hook
- 《深入淺出React和Redux》筆記
- 前半部分
- 后半部分
- react-transition-group
- Vue 與 React 的對比
- 工程化與架構
- Hybird
- React Native
- 新手上路
- 內置組件
- 常用插件
- 問題記錄
- Echarts
- 基礎
- Electron
- 序言
- 配置 Electron 開發環境 & 基礎概念
- React + TypeScript 仿 Antd
- TypeScript 基礎
- React + ts
- 樣式設計
- 組件測試
- 圖標解決方案
- Storybook 的使用
- Input 組件
- 在線 mock server
- 打包與發布
- Algorithm
- 排序算法及常見問題
- 劍指 offer
- 動態規劃
- DataStruct
- 概述
- 樹
- 鏈表
- Network
- Performance
- Webpack
- PWA
- Browser
- Safety
- 微信小程序
- mpvue 課程實戰記錄
- 服務器
- 操作系統基礎知識
- Linux
- Nginx
- redis
- node.js
- 基礎及原生模塊
- express框架
- node.js操作數據庫
- 《深入淺出 node.js》筆記
- 前半部分
- 后半部分
- 數據庫
- SQL
- 面試題收集
- 智力題
- 面試題精選1
- 面試題精選2
- 問答篇
- 2025面試題收集
- Other
- markdown 書寫
- Git
- LaTex 常用命令
- Bugs