摘要:過濾掉和簡單判斷是否是迭代器對象模擬行為對迭代器對象進行遍歷操作。看到這里你可能已經知道了,要實現的功能之一就是提供一個迭代器。原文鏈接參考迭代器和生成器系列之模擬實現一個數據結構展開語法循環
前言
es6新增了Set數據結構,它允許你存儲任何類型的唯一值,無論是原始值還是對象引用。這篇文章希望通過模擬實現一個Set來增加對它的理解。
原文鏈接
用在前面實際工作和學習過程中,你可能也經常用Set來對數組做去重處理
let unique = (array) => { return [ ...new Set(array) ] } console.log(unique([ 1, 2, 3, 4, 1, 2, 5 ])) // [1, 2, 3, 4, 5]基本語法
以下內容基本出自MDN,這里寫出來,純粹是為了便于后面的模擬操作。如果你已經很熟悉了,可以直接略過。
new Set([ iterable ])
可以傳遞一個可迭代對象,它的所有元素將被添加到新的 Set中。如果不指定此參數或其值為null,則新的 Set為空。
let s = new Set([ 1, 2, 3 ]) // Set(3)?{1, 2, 3} let s2 = new Set() // Set(0)?{} let s3 = new Set(null /* or undefined */) // Set(0)?{}實例屬性和方法
屬性
constructor Set的構造函數
size Set 長度
操作方法
Set.prototype.add(value)
在Set對象尾部添加一個元素。返回該Set對象。
Set.prototype.has(value)
返回一個布爾值,表示該值在Set中存在與否。
Set.prototype.delete(value)
移除Set中與這個值相等的元素,返回Set.prototype.has(value)在這個操作前會返回的值(即如果該元素存在,返回true,否則返回false)
Set.prototype.clear()
移除Set對象內的所有元素。沒有返回值
栗子
let s = new Set() s.add(1) // Set(1) {1} .add(2) // Set(2) {1, 2} .add(NaN) // Set(2) {1, 2, NaN} .add(NaN) // Set(2) {1, 2, NaN} // 注意這里因為添加完元素之后返回的是該Set對象,所以可以鏈式調用 // NaN === NaN 結果是false,但是Set中只會存一個NaN s.has(1) // true s.has(NaN) // true s.size // 3 s.delete(1) s.has(1) // false s.size // 2 s.clear() s // Set(0)?{}
遍歷方法
Set.prototype.keys()
返回一個新的迭代器對象,該對象包含Set對象中的按插入順序排列的所有元素的值。
Set.prototype.values()
返回一個新的迭代器對象,該對象包含Set對象中的按插入順序排列的所有元素的值。
Set.prototype.entries()
返回一個新的迭代器對象,該對象包含Set對象中的按插入順序排列的所有元素的值的[value, value]數組。為了使這個方法和Map對象保持相似, 每個值的鍵和值相等。
Set.prototype.forEach(callbackFn[, thisArg])
按照插入順序,為Set對象中的每一個值調用一次callBackFn。如果提供了thisArg參數,回調中的this會是這個參數。
栗子
let s = new Set([ "s", "e", "t" ]) s // SetIterator?{"s", "e", "t"} s.keys() // SetIterator?{"s", "e", "t"} s.values() // SetIterator?{"s", "e", "t"} s.entries() // SetIterator?{"s", "e", "t"} // log [ ...s ] // ["s", "e", "t"] [ ...s.keys() ] // ?["s", "e", "t"] [ ...s.values() ] // ?["s", "e", "t"] [ ...s.entries() ] // ?[["s", "s"], ["e", "e"], ["t", "t"]] s.forEach(function (value, key, set) { console.log(value, key, set, this) }) // s s Set(3)?{"s", "e", "t"} Window // e e Set(3)?{"s", "e", "t"} Window // t t Set(3)?{"s", "e", "t"} Window s.forEach(function () { console.log(this) }, { name: "qianlongo" }) // {name: "qianlongo"} // {name: "qianlongo"} // {name: "qianlongo"} for (let value of s) { console.log(value) } // s // e // t for (let value of s.entries()) { console.log(value) } // ["s", "s"] // ["e", "e"] // ["t", "t"]整體結構
以上回顧了一下Set的基本使用,我們可以開始嘗試模擬實現一把啦。你也可以直接點擊查看源碼。
目錄結構
├──set-polyfill
│ ├──iterator.js // 導出一個構造函數Iterator,模擬創建可迭代對象
│ ├──set.js // Set類
│ ├──utils.js // 輔助函數
│ ├──test.js // 測試
Set整體框架
class Set { constructor (iterable) {} get size () {} has () {} add () {} delete () {} clear () {} forEach () {} keys () {} values () {} entries () {} [ Symbol.iterator ] () {} }輔助方法
開始實現Set細節前,我們先看一下會用到的一些輔助方法
assert, 這個方法是學習vuex源碼時候看到的,感覺蠻實用的,主要用來對某些條件進行判斷,拋出錯誤。
const assert = (condition, msg) => { if (!condition) throw new Error(msg) }
isDef, 過濾掉null和undefined
const isDef = (value) => { return value != void 0 }
isIterable, 簡單判斷value是否是迭代器對象.
const isIterable = (value) => { return isDef(value) && typeof value[ Symbol.iterator ] === "function" }
forOf, 模擬for of行為, 對迭代器對象進行遍歷操作。
const forOf = (iterable, callback, ctx) => { let result iterable = iterable[ Symbol.iterator ]() result = iterable.next() while (!result.done) { callback.call(ctx, result.value) result = iterable.next() } }源碼實現
class Set { constructor (iterable) { // 使用數組來存儲Set的每一項元素 this.value = [] // 判斷是否使用new調用 assert(this instanceof Set, "Constructor Set requires "new"") // 過濾掉null和undefined if (isDef(iterable)) { // 是可迭代對象才進行下一步forOf元素添加 assert(isIterable(iterable), `${iterable} is not iterable`) // 循環可迭代對象,初始化 forOf(iterable, (value) => { this.add(value) }) } } // 獲取s.size時候會調用 size函數,返回value數組的長度 // https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/get get size () { return this.value.length } // 使用數組的includes方法判斷是否包含value // https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/includes // [ NaN ].includes(NaN)會返回true,正好Set也只能存一個NaN has (value) { return this.value.includes(value) } // 通過has方法判斷value是否存在,不存在則添加進數組,最后返回Set本身,支持鏈式調用 add (value) { if (!this.has(value)) { this.value.push(value) } return this } // 在刪除之前先判斷value是否存在用之當做返回值,存在則通過splice方法移除 delete (value) { let result = this.has(value) if (result) { this.value.splice(this.value.indexOf(value), 1) } return result } // 重新賦值一個空數組,即實現clear方法 clear () { this.value = [] } // 通過forOf遍歷 values返回的迭代對象,實現forEach forEach (callback, thisArg) { forOf(this.values(), (value) => { callback.call(thisArg, value, value, this) }) } // 返回一個迭代對象,該對象中的值是Set中的value keys () { return new Iterator(this.value) } // 同keys values () { return this.keys() } // 返回一個迭代對象,不同keys和values的是其值是[value, value] entries () { return new Iterator(this.value, (value) => [ value, value ]) } // 返回一個新的迭代器對象,該對象包含Set對象中的按插入順序排列的所有元素的值。 [ Symbol.iterator ] () { return this.values() } }
測試一把
執行 node test.js
size屬性和操作方法
const Set = require("./set") const s = new Set() s.add(1) .add(2) .add(NaN) .add(NaN) console.log(s) // Set { value: [ 1, 2, NaN ] } console.log(s.has(1)) // true console.log(s.has(NaN)) // true console.log(s.size) // 3 s.delete(1) console.log(s.has(1)) // false console.log(s.size) // 2 s.clear() console.log(s) // Set { value: [] }
上面的例子把Set的size屬性和操作方法過了一遍,打印出來的Set實例和原生的長得不太一樣,就先不管了。
遍歷方法
let s2 = new Set([ "s", "e", "t" ]) console.log(s2) // Set { value: [ "s", "e", "t" ] } console.log(s2.keys()) // Iterator {} console.log(s2.values()) // Iterator {} console.log(s2.entries()) // Iterator {} console.log([ ...s2 ]) // [ "s", "e", "t" ] console.log([ ...s2.keys() ]) // [ "s", "e", "t" ] console.log([ ...s2.values() ]) // [ "s", "e", "t" ] console.log([ ...s2.entries() ]) // [ [ "s", "s" ], [ "e", "e" ], [ "t", "t" ] ] s2.forEach(function (value, key, set) { console.log(value, key, set, this) }) // s s Set { value: [ "s", "e", "t" ] } global // e e Set { value: [ "s", "e", "t" ] } global // t t Set { value: [ "s", "e", "t" ] } global s2.forEach(function () { console.log(this) }, { name: "qianlongo" }) // { name: "qianlongo" } // { name: "qianlongo" } // { name: "qianlongo" } // {name: "qianlongo"} // {name: "qianlongo"} // {name: "qianlongo"} for (let value of s) { console.log(value) } // s // e // t for (let value of s.entries()) { console.log(value) } // ["s", "s"] // ["e", "e"] // ["t", "t"]
遍歷方法看起來也可以達到和前面例子一樣的效果,源碼實現部分基本就到這里啦,但是還沒完...
為什么[ ...s2 ]可以得到數組[ "s", "e", "t" ]呢?
s2 為什么可以被for of循環呢?
iterator(迭代器)從MDN找來這段話,在JavaScript中迭代器是一個對象,它提供了一個next() 方法,用來返回序列中的下一項。這個方法返回包含兩個屬性:done(表示遍歷是否結束)和 value(當前的值)。
迭代器對象一旦被創建,就可以反復調用next()。
function makeIterator(array){ var nextIndex = 0 return { next: function () { return nextIndex < array.length ? { done: false, value: array[ nextIndex++ ] } : { done: true, value: undefined } } }; } var it = makeIterator(["yo", "ya"]) console.log(it.next()) // { done: false, value: "yo" } console.log(it.next()) // { done: false, value: "ya" } console.log(it.next()) // { done: true, value: undefined }
這個時候可以講一下我們的iterator.js中的代碼了
class Iterator { constructor (arrayLike, iteratee = (value) => value) { this.value = Array.from(arrayLike) this.nextIndex = 0 this.len = this.value.length this.iteratee = iteratee } next () { let done = this.nextIndex >= this.len let value = done ? undefined : this.iteratee(this.value[ this.nextIndex++ ]) return { done, value } } [ Symbol.iterator ] () { return this } }
Iterator的實例有一個next方法,每次調用都會返回一個done屬性和value屬性,其語意和前面的解釋是一樣的。
let it = new Iterator(["yo", "ya"]) console.log(it.next()) // { done: false, value: "yo" } console.log(it.next()) // { done: false, value: "ya" } console.log(it.next()) // { done: true, value: undefined }
看到這里你可能已經知道了,Iterator要實現的功能之一就是提供一個迭代器。那這個又和上面的問題1和2有啥關系呢?我們再來看看for of
for of一個數據結構只要部署了Symbol.iterator屬性,就被視為具有iterator接口,就可以用for...of循環遍歷它的成員。也就是說,for...of循環內部調用的是數據結構的Symbol.iterator方法 for...of 循環
默認只有(Array,Map,Set,String,TypedArray,arguments)可被for of迭代。我們自定義的Set類不在這其中,前面的例子中卻在for of循環中打印出了想要的值。原因就是我們給Iterator類部署了Symbol.iterator方法,執行該方法便返回Iterator實例本身,它是一個可以被迭代的對象。
[ Symbol.iterator ] () { return this }
到這里上面的問題2就可以解釋通了。
再看看問題1 為什么[ ...s2 ]可以得到數組[ "s", "e", "t" ]呢?,原因也是我們給Set、keys、values、entries部署了Symbol.iterator,使之具有“iterator”接口,而擴展運算符...的特點之一就是任何具有Iterator接口的對象,都可以用擴展運算符轉為真正的數組。
結尾模擬過程中可能會有相應的錯誤,也不是和原生的實現完全一致。僅當學習之用,歡迎大家拍磚。
原文鏈接
參考Set
迭代器和生成器
ES6 系列之模擬實現一個 Set 數據結構
展開語法
for...of 循環
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/97783.html
摘要:原文從零到一,擼一個在線斗地主上篇作者背景朋友來深圳玩,若說到在深圳有什么好玩的,那當然是宅在家里斗地主了可是天算不如人算,撲克牌丟了幾張不全大熱天的,誰愿意出去買牌啊。 原文:從零到一,擼一個在線斗地主(上篇) | AlloyTeam作者:TAT.vorshen 背景:朋友來深圳玩,若說到在深圳有什么好玩的,那當然是宅在家里斗地主了!可是天算不如人算,撲克牌丟了幾張不全……大熱天的,...
摘要:前言本篇主要講解如何去優化鎖機制或者克服多線程因為鎖可導致性能下降的問題線程變量有這樣一個場景,前面是一大桶水,個人去喝水,為了保證線程安全,我們要在杯子上加鎖導致大家輪著排隊喝水,因為加了鎖的杯子是同步的,只能有一個人拿著這個唯一的杯子喝 前言 本篇主要講解如何去優化鎖機制或者克服多線程因為鎖可導致性能下降的問題 ThreadLocal線程變量 有這樣一個場景,前面是一大桶水,10個...
摘要:前言這篇主要來講解多線程中一個非常經典的設計模式包括它的基礎到拓展希望大家能夠有所收獲生產者消費者模式簡述此設計模式中主要分兩類線程生產者線程和消費者線程生產者提供數據和任務消費者處理數據和任務該模式的核心就是數據和任務的交互點共享內存緩 前言 這篇主要來講解多線程中一個非常經典的設計模式包括它的基礎到拓展希望大家能夠有所收獲 生產者-消費者模式簡述 此設計模式中主要分兩類線程:生產者...
摘要:作者簡介是推出的一個天挑戰。完整指南在從零到壹全棧部落。通過時分秒對一圈度,進行映射,確定每一個指針所需旋轉的角度。此前的代碼中,每秒都會重新一個對象,用來計算角度值,但如果讓這個角度值一直保持增長,也就不會出現逆時針回旋的問題了。 Day02 - JavaScript + CSS Clock 作者:?liyuechun 簡介:JavaScript30 是 Wes Bos 推出的一個...
閱讀 2380·2019-08-30 15:56
閱讀 1039·2019-08-30 15:55
閱讀 3203·2019-08-30 15:44
閱讀 933·2019-08-30 10:53
閱讀 1888·2019-08-29 16:33
閱讀 2470·2019-08-29 16:13
閱讀 719·2019-08-29 12:41
閱讀 874·2019-08-26 13:56