[TOC]
# let 與 const 的使用
⒈不存在變量提升 ? 變量要在聲明之后再使用
⒉暫時性死區 ? let 和 const 形成封閉作用域,該作用域(代碼塊)內使用變量之前都必須先聲明
⒊不允許重復聲明 ? 不允許在相同作用域內重復聲明同一變量
⒋塊級作用域 ? ① 防止內層變量覆蓋外層變量 ② 防止用于計數的循環變量泄露為全局變量
⒌const 保證的是變量指向的內存地址不得改動,可以為 const 聲明的數組或對象添加元素 / 屬性但是不能指向另一個地址
⒍let 和 const 聲明的變量不會添加到 window 對象中(var 會),而會添加到一個 Script 作用域

*****
例題:使用 var 聲明的變量來控制循環會泄漏為全局變量,每一層循環新的 i 值都會覆蓋舊的 i 值;換句話來講,所有的 i 指向一個全局變量 i
```js
var a = []
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i)
}
}
a[6]() // 10
```
使用 let,每一次循環的 i 其實都是一個新的變量;你可能會問,如果每一輪循環的變量 i 都是重新聲明的,那它怎么知道上一輪循環的值,從而計算出本輪循環的值?這是因為 JavaScript 引擎內部會記住上一輪循環的值,初始化本輪的變量 i 時,就在上一輪循環的基礎上進行計算。
```js
var a = []
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i)
}
}
a[6]() // 6
```
另外,for 循環還有一個特別之處,就是設置循環變量的那部分是一個父作用域,而循環體內部是一個單獨的子作用域。
```js
for (let i = 0; i < 3; i++) {
let i = 'abc'
console.log(i)
}
// abc
// abc
// abc
```
# 解構賦值
## 1、數組的解構賦值
例子:`let [a, b, c] = [1, 2, 3]`
兩邊都是方括號,從左往右依次匹配賦值
如果解構不成功,變量的值為 undefined;可以有 “不完全解構”,例如下面這段代碼:
```js
let [a, [b], d] = [1, [2, 3], 4]
a // 1
b // 2
d // 4
```
右邊不一定必須是數組,只要某種數據結構具有 Iterator 接口,都可以采用數組形式的解構賦值。
變量可以有默認值:`let [foo = true] = [ ]`
## 2、對象的解構賦值
例子:`let {foo, bar} = {foo:"aaa", bar:"bbb"}`
兩邊都是花括號,與數組的解構賦值的不同:變量的賦值是無序的
兩種寫法
① 變量與屬性同名
```js
let {bar, foo} = {foo:'aaa', bar:'bbb'}
```
② 變量名與屬性名不一致,在變量前給出匹配的 “模式”,即 ":" 之前的不是要賦值的變量而是給出了之后的變量應該匹配哪個屬性
```js
let obj = {first:'hello', last:'world'}
let {first: f, last: l} = obj
```
可以嵌套賦值,可以指定默認值
```js
let obj = {}
let arr = {}
({foo:obj.prop, bar:arr[0]} = {foo: 123, bar: true}) // 這里外面必須加個圓括號不然報錯…
```
## 3、字符串的解構賦值
字符串被轉換為一個類似數組的對象
```js
const[a,b,c,d,e] = 'hello' // h e l l o
```
規則:只要等號右邊的值不是對象或數組,就先將其轉為對象。
## 4、函數參數的解構賦值
```js
function add([x, y]) {
return x + y
}
console.log(add([1, 2]))
```
上面的代碼中,函數 add 的參數表面上是一個數組,但在傳入參數的那一刻,數組參數就被解構成變量 x 和 y 。
## 用途
1、提取 JSON 數據
```js
let jsonData = {
id: 42,
status: 'OK',
data: [867, 5309]
}
let {id, status, data:number} = jsonData
console.log(id, status, number) // 42 'OK' [867, 5309]
```
2、從函數返回多個值
```js
function example1() {
return [1, 2, 3]
}
let [a, b, c] = example1()
function example2() {
return {
foo: 1,
bar: 2
}
}
let {foo, bar} = example2()
console.log(a, b, c) // 1 2 3
console.log(foo, bar) // 1 2
```
3、函數參數的定義
解構賦值可以方便地將一組參數與變量名對應起來
```js
// 參數是一組有序的值
function f([x, y, z]) { ... }
f([1, 2, 3])
// 參數是一組無序的值
funtion f({x, y, z}) { ... }
f({z: 3, y: 2, x: 1})
```
4、輸入模塊的指定方法
加載模塊時,往往需要指定輸入的方法。解構賦值使得輸入語句非常清晰
```js
const { SourceMapConsumer, SourceNode } = require("source-map")
```
# 函數的擴展
## 參數默認值
- ES6 允許函數的參數指定為默認值,參數默認值是惰性求值的,即每次調用函數都會重新計算
- 參數變量的聲明是默認的,在函數體中不能用 let 或 const 再次聲明
- 函數的 length 屬性:返回 **沒有指定默認值** 的參數個數
```js
console.log(function (a, b, c = 5) {}.length); // 2
```
>length 屬性統計的是函數預期傳入的參數個數,指定了默認值的參數以及 rest 參數都不會計入 length 屬性
- 作用域問題:一旦設置了參數的默認值,函數進行聲明初始化時,參數會形成一個單獨的作用域。等到初始化結束,這個作用域就會消失。這種語法在不設置參數默認值時是不會出現的。
- 可以將參數默認值設為 undefined,表示這個參數是可省略的
- 定義了默認值的參數應該是函數的 **尾參數**,否則這個參數實際上是無法省略的
## rest 參數
形式:(…變量名),用于獲取函數的多余參數,這樣就不需要 arguments 對象了。rest 參數搭配的變量是一個數組。
>[warning]rest 參數只能是最后一個參數,否則會報錯
```js
// arguments 變量的寫法
function sortNumbers() {
return Array.prototype.slice.call(arguments).sort()
}
// rest 參數的寫法
function sortNumbers = (...numbers) => numbers.sort()
```
## 箭頭函數
ES6 允許使用箭頭(=>)定義函數,箭頭函數對于使用 function 關鍵字創建的函數有以下區別
- 箭頭函數沒有 arguments(建議使用更好的語法,剩余運算符替代)
- 箭頭函數沒有 prototype 屬性,不能用作構造函數(不能用 new 關鍵字調用)
- 箭頭函數沒有自己 this,箭頭函數的 this 始終等于它上層上下文中的 this
- 不可以使用 yield 命令,因此箭頭函數不能用作 Generator 函數
基礎語法:
```js
(參數1, 參數2, …, 參數N) => { 函數聲明 }
// 相當于:(參數1, 參數2, …, 參數N) => { return 表達式; }
(參數1, 參數2, …, 參數N) => 表達式(單一)
// 當只有一個參數時,圓括號是可選的:
(單一參數) => {函數聲明}
單一參數 => {函數聲明}
// 沒有參數的函數應該寫成一對圓括號。
() => {函數聲明}
// 加括號的函數體返回對象字面表達式:
參數 => ({foo: bar})
// 支持剩余參數和默認參數
(參數1, 參數2, ...rest) => {函數聲明}
(參數1 = 默認值1,參數2, …, 參數N = 默認值N) => {函數聲明}
```
```js
let controller = {
a: 1,
makeRequest: function () {
// 這里的 this 使用默認綁定規則,綁定到全局對象上(非嚴格模式),具體見 this 章節
setTimeout(function () {
console.log(this.a) // undefined
})
}
}
controller.makeRequest()
// 使用箭頭函數解決上面這個問題
let controller2 = {
a: 1,
makeRequest: function () {
setTimeout(() => {
console.log(this.a) // 1
})
}
}
controller2.makeRequest()
```
## 尾調用優化
函數調用自身稱為遞歸,如果尾調用自身就稱為尾遞歸。
遞歸非常耗費內存,因為需要同時保存成百上千個調用幀,而 ES6 的設計讓尾遞歸只存在一個調用幀。
```js
// 非尾遞歸的 Fibonacci
function Fibonacci(n) {
if (n <= 1) { return 1 }
return Fibonacci(n - 1) + Fibonacci(n - 2)
}
Fibonacci(100) // 堆棧溢出
// 使用尾遞歸
function Fibonacci2(n, ac1 = 1, ac2 = 1) {
if (n <= 1) { return ac2 }
return Fibonacci2(n - 1, ac2, ac1 + ac2)
}
console.log(Fibonacci2(100))
```
尾遞歸階乘:
```js
function factorial (n, acc = 1) {
if (n === 1) return acc
return factorial(n - 1, acc * n)
}
```
# 對象的擴展
比較重要的是 Object.assign() 方法的使用
`Object.assign(target, source1, source2)`:第一參數是目標對象,后面的參數都是源對象,該方法用于將源對象所有 **可枚舉屬性** 復制到目標對象
注意以下幾點:
- 如果目標對象與源對象有同名屬性,或多個源對象有同名屬性,則后面的屬性會覆蓋前面的屬性
- 復制的屬性是有限制的,只復制源對象的自身屬性(不復制繼承屬性),不復制不可枚舉屬性(enumerable: false)
- 實行的是淺復制,而不是深復制。即如果源對象某個屬性的值是對象,那么目標對象復制得到的是這個對象的引用
- 如果同名屬性是對象,Object.assign 的處理方法是替換而不是添加
```js
var target = { a: { b: 'c', d: 'e' } }
var source = { a: { b: 'hello' } }
Object.assign(target, source) // { a: { b: 'hello' } } 而不是 { a: { b: 'hello', d: 'e' } }
```
常見用途:
**為對象添加屬性:**
```js
class Point {
constructor(x, y) {
Object.assign(this, {x, y}) // 將 x 屬性和 y 屬性添加到了 Point 類的對象實例中
}
}
```
**為對象添加方法**
```js
Object.assign(SomeClass.prototype, {
someMethod(arg1, arg2) {
...
}
})
// 直接將方法添加到 SomeClass.prototype 中
```
**克隆對象**
```js
function clone(origin) {
return Object.assign({}, origin)
}
// 將原始對象復制到一個空對象;如果想要保持繼承鏈,可以采用以下代碼
function clone(origin) {
let originProto = Object.getPrototypeOf(origin)
return Object.assign(Object.create(originProto), origin)
}
```
**合并多個對象**
```js
const merge = (target, ...sources) => Object.assign(target, ...sources)
// 如果希望合并后返回一個新對象,可以這么改寫
const merge = (...sources) => Object.assign({}, ...sources)
```
**為屬性指定默認值**
```js
const DEFAULTS = {
logLevel: 0,
outputFormat: 'html'
}
function processContent(options) {
options = Object.assign({}, DEFAULTS, options)
console.log(options)
}
// DEFAULT對象是默認值,options對象是用戶提供的參數,如果兩者有同名屬性則options的屬性值會覆蓋DEFAULTS的屬性值
// 這么使用的前提是DEFAULTS對象和options對象的所有屬性的值只能是簡單類型
```
# 擴展運算符的使用
這里總結下擴展運算符用的比較多的地方
1、將數組轉換為用逗號分隔的參數序列
```js
const arr = []
arr.push(...[1, 2, 3, 4, 5])
console.log(arr) // [1, 2, 3, 4, 5]
```
2、某些場合可以替代函數的 apply 方法
```js
// ES5 的寫法
Math.max.apply(null, [14, 3, 77])
// ES6 的寫法
Math.max(...[14, 3, 77])
```
3、可用于將具有 iterator 接口的類似數組的對象轉換為真正的數組
```js
let map = new Map([
[1, 'one'],
[2, 'two'],
[3, 'three']
])
let arr = [...map.keys()] // [1, 2, 3]
```
4、用于對象的解構賦值:擴展運算符此時的作用相當于,將所有可遍歷的、但尚未被讀取的屬性(鍵值對)分配到指定的對象上面,注意這是淺復制
```js
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 }
x // 1
y // 2
z // { a: 3, b: 4 }
```
# 異步編程
## Promise
Promise 簡單來說就是一個容器,里面保存著某個未來才會結束的事件(通常是一個異步操作)的結果。從語法上來說,Promise 是一個對象,從它可以獲取異步操作的消息。
Promise 的提出是為了解決傳統異步編程的解決方案 - 回調函數和事件中回調地獄的問題。
比如下面這個串行讀取文件的例子:
```js
fs.readFile(path1, function (err, data) {
// read file1
fs.readFile(path2, function (err, data) {
// read file2
fs.readFile(path3, function (err, data) {
// read file3
// 更多的回調......
})
})
})
```
下面我們用 Promise 來實現相同的效果。
首先需要將 readFile 方法封裝為一個 Promise 對象
```js
function readFile_promise (path) {
return new Promise((resolve, reject) => {
fs.readFile(path, 'UTF-8', (err, data) => {
if (data) {
resolve(data)
} else {
reject(err)
}
})
})
}
```
然后鏈式調用:
```js
// 使用 Promise 的鏈式調用
readFile_promise('foo.txt').then(value => {
// ...
console.log(value)
return readFile_promise('bar.txt')
}).then(value => {
// ...
console.log(value)
return readFile_promise('baz.txt')
}).then(value => {
// ...
console.log(value)
})
```
如果使用 async / await 語法則更直觀了:
```js
async function readFile () {
let result1 = await readFile_promise('foo.txt') // 返回的是該異步操作的結果
let result2 = await readFile_promise('bar.txt')
let result3 = await readFile_promise('baz.txt')
}
```
下面簡單梳理下 Promise、Generator、async / await 的 API 和使用
1、三種狀態:Pending(進行中)、Fulfilled(已成功)、Rejected(已失敗)
2、構造函數:Promise()
```js
let promise = new Promise(function (resolve, reject) {
// ... some code
if (/* 異步操作成功 */) {
resolve(value)
} else {
reject (error)
}
})
```
Promise 構造函數接受一個函數作為參數,該函數的兩個參數分別是 resolve 和 reject,其是兩個函數,由 JavaScript 引擎提供,不需要自己部署
- resolve 函數將 Promise 對象的狀態從 Pending 變為 Fulfilled (完成)
- reject 函數則將 Pending 變為 Rejected 狀態
可以用 then 方法指定 Resolved 狀態(Fulfilled)和 Rejected 狀態的回調函數,通過參數接收 resolve 函數和 reject 函數傳出的值
簡單來說,是 Promise 的狀態變為 Resolved 時會觸發 then 方法綁定的回調函數(相當于事件監聽)
*****
3、`Promise.prototype.then()`
Promise 的實例的 then 方法是定義在原型對象 Promise.prototype 上的,then 方法的第一個參數是 Resolved 狀態的回調函數,第二個參數(可選)是 Rejected 狀態的回調函數,then 方法返回的是一個新的 Promise 實例,因此可以采用鏈式寫法,如下
```js
getJSON('/posts.json').then(function(json) {
return json.post
}).then(function(post) {
// ...
})
```
前一個回調函數的返回結果會作為參數傳遞給下一個回調函數
*****
4、`Promise.prototype.catch()`
其實這個方法是 .then(null, rejection) 的別名,使用這個方法可以更簡潔地指定發生錯誤時的回調函數
then 方法指定的回調函數如果在運行中拋出錯誤,也會被 catch 方法捕獲
```js
let promise = new Promise((resolve, reject) => {
throw new Error('test')
})
promise.catch(error => {
console.log(error) // Error: test
})
```
如果 Promise 狀態已經變成 Resolved,再拋出錯誤是無效的
```js
let promise = new Promise((resolve, reject) => {
resolve('ok')
throw new Error('test')
})
promise.then(value => {
console.log(value) // ok
}).catch(error => {
console.log(error)
})
```
Promise 對象的錯誤具有 “冒泡” 性質,會一直向后傳遞,直到被捕獲為止。也就是說,錯誤總是會被下一個 catch 語句捕獲
```js
getJSON('/post/1.json').then(post => {
return getJSON(post.commentURL)
}).then(comments => {
// some code
}).catch(error => {
// 處理前面 3 個 Promise 產生的錯誤
})
```
*****
5、`Promise.all()`
該方法接收一個數組作為參數,數組元素都為 Promise 對象(或者是具有 Iterator 接口,且返回的每個成員都是 Promise 實例的對象)
```js
var p = Promise.all([p1, p2, p3]
```
p 的狀態由 p1 p2 p3 決定,有兩種可能:
① 只有 p1 p2 p3 都變為 Fulfilled,p 才會變為 Fulfilled,此時p1 p2 p3 的返回值組成一個數組,傳遞給 p 的回調函數
② 只要 p1 p2 p3 中有一個變為 Rejected,p 就變為 Rejected,此時第一個被 Rejected 的實例的返回值會傳遞給 p 的回調函數
*****
6、`Promise.race()`
與 all 類似,接受一個 Promise 對象組成的數組作為參數,只要其中有一個 Promise 對象率先變為 Resolved 狀態那么 p 的狀態就跟著改變,率先改變的 Promise 實例的返回值會被傳遞給 p 的回調函數
7、`Promise.resolve()`
將一個現有的對象轉為 Promise 對象
8、`finally()`
不管狀態如何改變最后都會執行 finally 指定的回調方法
## Generator
特征:function 命令與函數名之間有一個星號,函數體內部使用 yield 語句定義不同的內狀態。
Generator 函數實際上是利用了遍歷器對象(Iterator Object),當執行一個 Generator 函數時,會有一個指向內部狀態的指針對象,調用遍歷器對象的 next 方法會使指針移動到下一個狀態,next()方法返回一個對象,value 屬性是當前 yield 語句的值, done 屬性是一個布爾值表示遍歷是否結束。
<span style="font-family: 楷體; font-size: 18px; font-weight: bold;">yield* 表達式</span>
如果在 Generator 函數內部調用另一個 Generator 函數,默認情況下是沒有效果的。
```js
function* foo () {
yield 'a'
yield 'b'
}
function* bar () {
yield 'x'
foo()
yield 'y'
}
for (let v of bar()) { // Generator 函數返回 iterator 對象,因此可以用 for...of 遍歷
console.log(v)
}
// x
// y
// yield* 語句用來在一個 Generator 函數里面執行另一個 Generator 函數
function* bar2 () {
yield 'x'
yield* foo()
yield 'y'
}
for (let v of bar2()) {
console.log(v)
}
// x
// a
// b
// y
```
從語法角度看,如果 yield 命令后跟的是一個遍歷器對象,那么需要在 yield 命令后加上星號,表明返回的是一個遍歷器對象。這被稱為 yield* 語句。
<span style="font-family: 楷體; font-size: 18px; font-weight: bold;">co 模塊</span>
co 模塊用于 Generator 函數的自動執行
```js
var gen = function* () {
var f1 = yield readFile('...')
var f2 = yield readFile('...')
}
var co = require('co')
co(gen)
```
<span style="font-family: 楷體; font-size: 18px; font-weight: bold;">應用:使用 Generator 函數部署 iterator 接口</span>
```js
function* iterEntries (obj) {
let keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
let key = keys[i]
yield [key, obj[key]]
}
}
let myObj = { foo: 3, bar: 7 }
for (let [key, value] of iterEntries(myObj)) {
console.log(key, value)
}
// foo 3
// bar 7
```
上述代碼中,myObj 是一個普通對象,通過 iterEntries 函數就有了 Iterator 接口。
## async await
async await 其實就相當于 Generator 函數的語法糖
其相比于 Generator 的改進如下:
① 內置執行器,Generator 函數的執行必須依靠執行器,async 函數自帶執行器,與普通函數一樣調用即可
② 更好的語義,async 表示函數里有異步操作,await 表示緊跟在后面的表達式需要等待
③ 更廣的適用性,await 命令后面可以是 Promise 對象和原始類型的值(如果不是 Promise 對象會被轉換為一個 Promise 對象并立即 resolve)
④ 返回值是 Promise,async 函數返回一個 Promise 對象,可以用 then 方法指定下一步操作
例子:按順序完成異步操作(依次遠程讀取一組 URL,然后按照讀取的順序輸出結果)
```js
// 繼發寫法,只有前一個 URL 返回結果后才會去讀取下一個 URL
async function logInOrder (urls) {
for (const url of urls) {
const response = await fetch(url)
console.log(await response.text())
}
}
// 并發寫法,同時發出遠程請求
async function logInOrder (urls) {
const textPromises = urls.map(async url => {
const response = await fetch(url)
return response.text()
})
// 按次序輸出
for (const textPromise of textPromises) {
console.log(await textPromise)
}
}
```
# Proxy 與 Reflect
`var proxy = new Proxy(target, handler)`:生成一個 Proxy 實例,target 參數表示所要攔截的目標對象,handler 參數也是一個對象,用來定值攔截行為;
對于可以設置但沒有設置攔截的操作,則直接落在目標對象上,按照原先的方式產生結果
Proxy 支持的攔截操作:參數 propKey 即要操作的對象的屬性名
- get(target, propKey, receiver):攔截對象屬性的讀取
- set(target, propKey, value, receiver):攔截對象屬性的設置
- has(target, propKey):攔截 propKey in proxy的操作,返回一個布爾值
- deleteProperty(target, propKey):攔截 delete proxy[propKey] 的操作,返回一個布爾值
.......
詳細的 API 請閱讀 [ES6 標準入門](http://es6.ruanyifeng.com/#docs/proxy) 就記一下與 defineProperty 的區別:當使用 defineProperty,我們修改原來的 obj 對象就可以觸發攔截,而使用 proxy,就必須修改代理對象,即 Proxy 的實例才可以觸發攔截。另外,Proxy 的可攔截屬性更多。
[https://github.com/mqyqingfeng/Blog/issues/107](https://github.com/mqyqingfeng/Blog/issues/107)
```
// 利用 Proxy 攔截構造函數的執行方法來實現單例模式
function proxy(func) {
let instance
let handler = { // Proxy 構造函數的第二個參數是一個對象,定制攔截的行為
construct(target, args) {
if (!instance) {
instance = Reflect.construct(func, args)
}
return instance
}
}
return new Proxy(func, handler) // 攔截構造函數
}
```
## Reflect
Reflect 對象的設計目的:
- 將 Object 對象的一些明顯屬于語言內部的方法放到 Reflect 對象上,如 Object.defineProperty
- 修改某些 Object 方法的返回結果,讓其變得更為合理。比如 Object.defineProperty(obj, name, desc) 在無法定義屬性時會拋出一個錯誤,而 Reflect.defineProperty(obj, name, desc) 則會返回 false
- 讓 Object 操作都變成函數行為。如 name in obj 和 delete obj[name] 改為 Reflect.has(obj, name) 和 Reflect.deleteProperty(obj, name)
- Reflect 對象的方法與 Proxy 對象的方法一一對應,只要是 Proxy 對象的方法,就能在 Reflect 對象上找到對應的方法
```
var obj = new Proxy({}, {
get: function (target, key, receiver) {
console.log(`getting ${key}`)
return Reflect.get(target, key, receiver)
},
set: function (target, key, value, receiver) {
console.log(`setting ${key}`)
return Reflect.set(target, key, value, receiver)
}
})
obj.count = 1 // setting count
console.log(++obj.count)
// getting count
// setting count
// 2
```
# iterator 和 for...of 循環
## 迭代器
所謂迭代器,其實就是一個具有 next() 方法的對象,每次調用 next() 都會返回一個結果對象,該結果對象有兩個屬性,value 表示當前的值,done 表示遍歷是否結束。
我們直接用 ES5 的語法創建一個迭代器:
```js
function createIterator(items) {
var i = 0;
return {
next: function () {
var done = i >= item.length;
var value = !done ? items[i++] : undefined;
return {
done: done,
value: value
};
}
};
}
// iterator 就是一個迭代器對象
var iterator = createIterator([1, 2, 3]);
console.log(iterator.next()); // { done: false, value: 1 }
console.log(iterator.next()); // { done: false, value: 2 }
console.log(iterator.next()); // { done: false, value: 3 }
console.log(iterator.next()); // { done: true, value: undefined }
```
## for of
除了迭代器之外,我們還需要一個可以遍歷迭代器對象的方式,ES6 提供了 for of 語句,我們直接用 for of 遍歷一下我們上節生成的遍歷器對象試試:
```js
var iterator = createIterator([1, 2, 3]);
for (let value of iterator) {
console.log(value);
}
```
結果報錯 `TypeError: iterator is not iterable`,表明我們生成的 iterator 對象并不是 iterable(可遍歷的),那什么才是可遍歷的呢?
其實一種數據結構只要部署了 Iterator 接口,我們就稱這種數據結構是“可遍歷的”(iterable)。
ES6 規定,默認的 Iterator 接口部署在數據結構的`Symbol.iterator`屬性,或者說,一個數據結構只要具有`Symbol.iterator`屬性,就可以認為是 "可遍歷的"(iterable)。
舉個例子:
```js
const obj = {
value: 1
};
for (value of obj) {
console.log(value);
}
// TypeError: iterator is not iterable
```
我們直接 for of 遍歷一個對象,會報錯,然而如果我們給該對象添加`Symbol.iterator`屬性:
```js
const obj = {
value: 1
};
obj[Symbol.iterator] = function () {
return createIterator([1, 2, 3]);
};
for (value of obj) {
console.log(value);
}
// 1
// 2
// 3
```
由此,我們也可以發現 for of 遍歷的其實是對象的 Symbol.iterator 屬性
## 默認有 iterator 接口的對象
* Array
* Map
* Set
* String
* TypedArray(類數組對象),如 arguments 對象,NodeList 對象
* Generator 對象
## for...of 與 for...in
- 對于數組的遍歷,for ... in 會返回數組中所有可枚舉的屬性(包括原型鏈上可枚舉的屬性), for ... of 只返回數組的下標對應的屬性值
- for...in 循環出的是 key,for...of 循環出的是 value,且 for...in 會遍歷對象的整個原型鏈,for...of 只遍歷當前對象
- for...of 不能循環普通的對象,需要通過和 Object.keys() 搭配使用,需要搭配具有 iterator 接口的對象使用,準確地說,for of 能遍歷數組(的值)是因為數組默認有 iterator 接口
```js
let myArray = [3, 5, 7, 9]
myArray.name = '數組'
// for in for of 應用于數組中的區別
for(let index in myArray) {
console.log(index) // 0 1 2 3 name
}
for(let value of myArray) {
console.log(value) // 3 5 7 9
}
// 應用于對象中
let myObject = {
property1: 'P1',
property2: 'P2',
property3: 'P3'
}
for(let value in myObject) {
console.log(value) // property1 property2 property3
}
for(let value of myObject) {
console.log(value) // myObject is not iterable
}
// How to make myObejct iterable? kind of complex
// Or we can use Object.keys()
for(let key of Object.keys(myObject)) {
// 使用Object.keys()方法獲取對象的key組成的數組
console.log(key) // property1 property2 property3
}
function A() {
this.property1 = 1
this.property2 = 2
this.property3 = 3
}
A.prototype.speak = function() {
console.log('speak')
}
let B = new A()
for(let key in B) {
console.log(key) // property1 property2 property3 speak
}
```
- 序言 & 更新日志
- 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