成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專(zhuān)欄INFORMATION COLUMN

immer.js 簡(jiǎn)介及源碼解析

Profeel / 2892人閱讀

摘要:例如維護(hù)一份在內(nèi)部,來(lái)判斷是否有變化,下面這個(gè)例子就是一個(gè)構(gòu)造函數(shù),如果將它的實(shí)例傳入對(duì)象作為第一個(gè)參數(shù),就能夠后面的處理對(duì)象中使用其中的方法上面這個(gè)構(gòu)造函數(shù)相比源代碼省略了很多判斷的部分。

博客鏈接:下一代狀態(tài)管理工具 immer 簡(jiǎn)介及源碼解析

JS 里面的變量類(lèi)型可以大致分為基本類(lèi)型和引用類(lèi)型。在使用過(guò)程中,引用類(lèi)型經(jīng)常會(huì)產(chǎn)生一些無(wú)法意識(shí)到的副作用,所以在現(xiàn)代 JS 開(kāi)發(fā)過(guò)程中,大家都有意識(shí)的寫(xiě)下斷開(kāi)引用的不可變數(shù)據(jù)類(lèi)型。

// 引用帶來(lái)的副作用
var a = [{ val: 1 }]
var b = a.map(item => item.val = 2)

// 期望:b 的每一個(gè)元素的 val 值變?yōu)?2
console.log(a[0].val) // 2

從上述例子我們可以發(fā)現(xiàn),本意是只想讓 b 中的每一個(gè)元素的值變?yōu)?2 ,但卻無(wú)意中改掉了 a 中每一個(gè)元素的結(jié)果,這是不符合預(yù)期的。接下來(lái)如果某個(gè)地方使用到了 a ,很容易發(fā)生一些我們難以預(yù)料并且難以 debug 的 bug。

在有了這樣的問(wèn)題之后,一般來(lái)說(shuō)當(dāng)需要傳遞一個(gè)對(duì)象進(jìn)一個(gè)函數(shù)時(shí),我們可以使用 Object.assign 或者 ... 對(duì)對(duì)象進(jìn)行解構(gòu),成功斷掉一層的引用。

例如上面的問(wèn)題我們可以改用下面的這種寫(xiě)法:

var a = [{ val: 1 }]
var b = a.map(item => ({ ...item, val: 2 }))

console.log(a[0].val) // 1
console.log(b[0].val) // 2

這樣做其實(shí)還會(huì)有一個(gè)問(wèn)題,無(wú)論是 Object.assign 還是 ... 的解構(gòu)操作,斷掉的引用也只是一層,如果對(duì)象嵌套超過(guò)一層,這樣做還是有一定的風(fēng)險(xiǎn)。

var a = [
  { val: 1, desc: { text: "a" } }
]
var b = a.map(item => ({ ...item, val: 2 }))

console.log(a === b)           // false
console.log(a.desc === b.desc) // true

這樣一來(lái),后面的代碼如果一不小心在一個(gè)函數(shù)內(nèi)部給 b.desc 對(duì)象里面的內(nèi)容通過(guò)“點(diǎn)”進(jìn)行賦值,就一定會(huì)改變具有相同引用的 a.desc 部分的值,這當(dāng)然是不符合我們的預(yù)期的。

所以在這之后,大多數(shù)情況下我們會(huì)考慮 深拷貝 這樣的操作來(lái)完全避免上面遇到的所有問(wèn)題。深拷貝,顧名思義就是在遍歷過(guò)程中,如果遇到了可能出現(xiàn)引用的數(shù)據(jù)類(lèi)型,就會(huì)遞歸的完全創(chuàng)建一個(gè)新的類(lèi)型。

// 一個(gè)簡(jiǎn)單的深拷貝函數(shù),去掉了一些膠水部分
// 用戶(hù)態(tài)輸入一定是一個(gè) Plain Object,并且所有 value 也是 Plain Object
function deepClone(a) {
  const keys = Object.keys(a)
  return keys.reduce((memo, current) => {
    const value = a[current]
    if (typeof value === "object") {
      return {
        ...memo,
        [current]: deepClone(value),
      }
    }
    return {
      ...memo,
      [current]: value,
    }
  }, {})
}

用上面的 deepClone 函數(shù)進(jìn)行簡(jiǎn)單測(cè)試

var a = {
  val: 1,
  desc: {
    text: "a",
  },
}
var b = deepClone(a)

b.val = 2
console.log(a.val) // 1
console.log(b.val) // 2

b.desc.text = "b"
console.log(a.desc.text) // "a"
console.log(b.desc.text) // "b"

上面的這個(gè) deepClone 可以滿(mǎn)足簡(jiǎn)單的需求,但是真正在生產(chǎn)工作中,我們需要考慮非常多的因素。舉例來(lái)說(shuō):

key 里面 getter,setter 以及原型鏈上的內(nèi)容如何處理

value 是一個(gè) Symbol 如何處理

value 是其他非 Plain Object 如何處理

value 內(nèi)部出現(xiàn)了一些循環(huán)引用如何處理

因?yàn)橛刑嗖淮_定因素,所以我還是推薦使用大型開(kāi)源項(xiàng)目里面的工具函數(shù),比較常用的為大家所熟知的就是 lodash.cloneDeep,無(wú)論是安全性還是效果都有所保障。

其實(shí),這樣的概念我們常稱(chēng)作 immutable ,意為不可變的數(shù)據(jù),其實(shí)理解為不可變關(guān)系更為恰當(dāng)。每當(dāng)我們創(chuàng)建一個(gè)被 deepClone 過(guò)的數(shù)據(jù),新的數(shù)據(jù)進(jìn)行有副作用 (side effect) 的操作都不會(huì)影響到之前的數(shù)據(jù),這也就是 immutable 的精髓和本質(zhì)。

然而 deepClone 這種函數(shù)雖然斷絕了引用關(guān)系實(shí)現(xiàn)了 immutable,但是開(kāi)銷(xiāo)實(shí)在太大。所以在 2014 年,facebook 的 immutable-js 橫空出世,即保證了 immutable ,又兼顧了性能。

immutable-js 簡(jiǎn)介

immutable-js 使用了另一套數(shù)據(jù)結(jié)構(gòu)的 API ,與我們的常見(jiàn)操作有些許不同,它將所有的原生對(duì)象都會(huì)轉(zhuǎn)化成 immutable-js 的內(nèi)部對(duì)象,并且任何操作最終都會(huì)返回一個(gè)新的 immutable 的值。

上面的例子使用 immutable-js 就需要這樣改造一下:

const { fromJS } = require("immutable")
const data = {
  val: 1,
  desc: {
    text: "a",
  },
}

const a = fromJS(data)

const b = a.set("val", 2)
console.log(a.get("val")) // 1
console.log(b.get("val")) // 2

const pathToText = ["desc", "text"]
const c = a.setIn([...pathToText], "c")
console.log(a.getIn([...pathToText])) // "a"
console.log(c.getIn([...pathToText])) // "c"

對(duì)于性能方面,immutable-js 也有它的優(yōu)勢(shì),舉個(gè)簡(jiǎn)單的例子:

const { fromJS } = require("immutable")
const data = {
  content: {
    time: "2018-02-01",
    val: "Hello World",
  },
  desc: {
    text: "a",
  },
}

const a = fromJS(data)
const b = a.setIn(["desc", "text"], "b")
console.log(b.get("desc") === a.get("desc"))       // false
console.log(b.get("content") === a.get("content")) // true

const c = a.toJS()
const d = b.toJS()
console.log(c.desc === d.desc)       // false
console.log(c.content === d.content) // false

從上面的例子可以看出來(lái),在 immutable-js 的數(shù)據(jù)結(jié)構(gòu)中,深層次的對(duì)象在沒(méi)有修改的情況下仍然能夠保證嚴(yán)格相等。這里的嚴(yán)格相等就可以認(rèn)為是沒(méi)有新建這個(gè)對(duì)象,仍然在內(nèi)部保持著之前的引用,但是修改卻不會(huì)同步的修改。

經(jīng)常使用 React 的同學(xué)肯定也對(duì) immutable-js 不陌生,這也就是為什么 immutable-js 會(huì)極大提高 React 頁(yè)面性能的原因之一了。

當(dāng)然能夠達(dá)到 immutable 效果的當(dāng)然不只這幾個(gè)個(gè)例,這篇文章我主要想介紹實(shí)現(xiàn) immutable 的庫(kù)其實(shí)是 immer。

immer 簡(jiǎn)介

immer 的作者同時(shí)也是 mobx 的作者,一個(gè)看起來(lái)非常感性的中年大叔。mobx 又像是把 Vue 的一套東西融合進(jìn)了 React,已經(jīng)在社區(qū)取得了不錯(cuò)的反響。immer 則是他在 immutable 方面所做的另一個(gè)實(shí)踐,在 2018-02-01,immer 成功發(fā)布了 1.0.0 版本,我差不多在一個(gè)月前開(kāi)始關(guān)注這個(gè)項(xiàng)目,所以大清早看到作者在 twitter 上發(fā)的通告,有感而發(fā)今天寫(xiě)下這篇文章,算是簡(jiǎn)單介紹一下 immer 這個(gè) immutable 框架的使用以及內(nèi)部簡(jiǎn)單的實(shí)現(xiàn)原理。

與 immutable-js 最大的不同,immer 是使用原生數(shù)據(jù)結(jié)構(gòu)的 API 而不是內(nèi)置的 API,舉個(gè)簡(jiǎn)單例子:

const produce = require("immer")

const state = {
  done: false,
  val: "string",
}

const newState = produce(state, (draft) => {
  draft.done = true
})

console.log(state.done) // false
console.log(newState.done) // true

所有需要更改的邏輯都可以放進(jìn) produce 的第二個(gè)參數(shù)的函數(shù)內(nèi)部,即使給對(duì)象內(nèi)的元素直接賦值,也不會(huì)對(duì)原對(duì)象產(chǎn)生任何影響。

簡(jiǎn)單介紹完使用之后,下面就開(kāi)始簡(jiǎn)單介紹它的內(nèi)部實(shí)現(xiàn)。不過(guò)在這之前,想先通過(guò)上面的例子簡(jiǎn)單的發(fā)散思考一下。

通過(guò)文章最開(kāi)始的例子我們就能明白,給函數(shù)傳入一個(gè)對(duì)象,直接通過(guò)“點(diǎn)”操作符對(duì)里面的一個(gè)屬性進(jìn)行更改是一定會(huì)改變外面的結(jié)果的。而上面的這個(gè)例子中,draft 參數(shù)穿入進(jìn)去,與 state 一樣也有 done 這個(gè)屬性,但是在通過(guò) draft.done 改變值之后,原來(lái)的 state.done 并沒(méi)有發(fā)生改變。其實(shí)到這里,結(jié)合之前研究 vue 源碼的經(jīng)驗(yàn),我當(dāng)時(shí)就篤定,這里一定用了 Object.defineProperty,draft 通過(guò)“點(diǎn)”操作的之后,一些數(shù)據(jù)的結(jié)果被劫持了,然后做了一些新的操作。

immer 原理解析

真正翻開(kāi)源碼,誠(chéng)然里面確實(shí)有 defineProperty 的身影,不過(guò)在另一個(gè)標(biāo)準(zhǔn)的文件中,用了一種新的方式,那就是 ES6 中新增的 Proxy 對(duì)象。而在日常的業(yè)務(wù)過(guò)程中,應(yīng)該很少有前端工程師會(huì)用到 Proxy 對(duì)象,因?yàn)樗膽?yīng)用場(chǎng)景確實(shí)有些狹隘,所以這里簡(jiǎn)單介紹一下 Proxy 對(duì)象的使用。

Proxy 對(duì)象接受兩個(gè)參數(shù),第一個(gè)參數(shù)是需要操作的對(duì)象,第二個(gè)參數(shù)是設(shè)置對(duì)應(yīng)攔截的屬性,這里的屬性同樣也支持 get,set 等等,也就是劫持了對(duì)應(yīng)元素的讀和寫(xiě),能夠在其中進(jìn)行一些操作,最終返回一個(gè) Proxy 對(duì)象。

const proxy = new Proxy({}, {
  get(target, key) {
    console.log("proxy get key", key)
  },
  set(target, key, value) {
    console.log("value", value)
  }
})

proxy.info     // "proxy get key info"
proxy.info = 1 // "value 1"

上面這個(gè)例子中傳入的第一個(gè)參數(shù)是一個(gè)空對(duì)象,當(dāng)然我們可以用其他對(duì)象有內(nèi)容的對(duì)象代替它。例如維護(hù)一份 state 在內(nèi)部,來(lái)判斷是否有變化,下面這個(gè)例子就是一個(gè)構(gòu)造函數(shù),如果將它的實(shí)例傳入 Proxy 對(duì)象作為第一個(gè)參數(shù),就能夠后面的處理對(duì)象中使用其中的方法:

class Store {
  constructor(state) {
    this.modified = false
    this.source = state
    this.copy = null
  }
  get(key) {
    if (!this.modified) return this.source[key]
    return this.copy[key]
  }
  set(key, value) {
    if (!this.modified) this.modifing()
    return this.copy[key] = value
  }
  modifing() {
    if (this.modified) return
    this.modified = true
    this.copy = Array.isArray(this.source)
      ? this.source.slice()
      : { ...this.source }
  }
}

上面這個(gè)構(gòu)造函數(shù)相比源代碼省略了很多判斷的部分。實(shí)例上面有 modified,source,copy 三個(gè)屬性,有 get,set,modifing 三個(gè)方法。modified 作為內(nèi)置的 flag,判斷如何進(jìn)行設(shè)置和返回。

里面最關(guān)鍵的就應(yīng)該是 modifing 這個(gè)函數(shù),如果觸發(fā)了 setter 并且之前沒(méi)有改動(dòng)過(guò)的話,就會(huì)手動(dòng)將 modified 這個(gè) flag 設(shè)置為 true,并且手動(dòng)通過(guò)原生的 API 實(shí)現(xiàn)一層 immutable。

對(duì)于 Proxy 的第二個(gè)參數(shù),就更加簡(jiǎn)單了。在這個(gè)例子中,只是簡(jiǎn)單做一層轉(zhuǎn)發(fā),任何對(duì)元素的讀取和寫(xiě)入都轉(zhuǎn)發(fā)到前面的實(shí)例內(nèi)部方法去。

const PROXY_FLAG = "@@SYMBOL_PROXY_FLAG"
const handler = {
  get(target, key) {
    if (key === PROXY_FLAG) return target
    return target.get(key)
  },
  set(target, key, value) {
    return target.set(key, value)
  },
}

這里在 getter 里面加一個(gè) flag 的目的就在于將來(lái)從 proxy 對(duì)象中獲取 store 實(shí)例更加方便。

最終我們能夠完成這個(gè) produce 函數(shù),創(chuàng)建 store 實(shí)例后創(chuàng)建 proxy 實(shí)例。然后將創(chuàng)建的 proxy 實(shí)例傳入第二個(gè)函數(shù)中去。這樣無(wú)論在內(nèi)部做怎樣有副作用的事情,最終都會(huì)在 store 實(shí)例內(nèi)部將它解決。最終得到了修改之后的 proxy 對(duì)象,而 proxy 對(duì)象內(nèi)部已經(jīng)維護(hù)了兩份 state ,通過(guò)判斷 modified 的值來(lái)確定究竟返回哪一份。

function produce(state, producer) {
  const store = new Store(state)
  const proxy = new Proxy(store, handler)

  producer(proxy)

  const newState = proxy[PROXY_FLAG]
  if (newState.modified) return newState.copy
  return newState.source
}

這樣,一個(gè)分割成 Store 構(gòu)造函數(shù),handler 處理對(duì)象和 produce 處理 state 這三個(gè)模塊的最簡(jiǎn)版就完成了,將它們組合起來(lái)就是一個(gè)最最最 tiny 版的 immer ,里面去除了很多不必要的校驗(yàn)和冗余的變量。但真正的 immer 內(nèi)部也有其他的功能,例如深度克隆情況下的結(jié)構(gòu)共享等等。

性能

性能方面,就用 immer 官方 README 里面的介紹來(lái)說(shuō)明情況。

這是一個(gè)關(guān)于 immer 性能的簡(jiǎn)單測(cè)試。這個(gè)測(cè)試使用了 100000 個(gè)組件元素,并且更新其中的 10000 個(gè)。freeze 表示狀態(tài)樹(shù)在生成之后已被凍結(jié)。這是一個(gè)最佳的開(kāi)發(fā)實(shí)踐,因?yàn)樗梢苑乐归_(kāi)發(fā)人員意外修改狀態(tài)樹(shù)。

通過(guò)上圖的觀察,基本可以得出:

從 immer 的角度來(lái)看,這個(gè)性能環(huán)境比其他框架和庫(kù)要惡劣的多,因?yàn)樗仨毚淼母?jié)點(diǎn)相對(duì)于其余的數(shù)據(jù)集來(lái)說(shuō)大得多

從 mutate 和 deepclone 來(lái)看,mutate 基準(zhǔn)確定了數(shù)據(jù)更改費(fèi)用的基線,沒(méi)有不可變性(或深度克隆情況下的結(jié)構(gòu)共享)

使用 Proxy 的 immer 大概是手寫(xiě) reducer 的兩倍,當(dāng)然這在實(shí)踐中可以忽略不計(jì)

immer 大致和 immutable-js 一樣快。但是,immutable-js 最后經(jīng)常需要 toJS 操作,這里的性能的開(kāi)銷(xiāo)是很大的。例如將不可變的 JS 對(duì)象轉(zhuǎn)換回普通的對(duì)象,將它們傳遞給組件中,或著通過(guò)網(wǎng)絡(luò)傳輸?shù)鹊龋ㄟ€有將從例如服務(wù)器接收到的數(shù)據(jù)轉(zhuǎn)換為 immutable-js 內(nèi)置對(duì)象的前期成本)

immer 的 ES5 實(shí)現(xiàn)速度明顯較慢。對(duì)于大多數(shù)的 reducer 來(lái)說(shuō),這并不重要,因?yàn)樘幚泶罅繑?shù)據(jù)的 reducer 可以完全不(或者僅部分)使用 immer 的 produce 函數(shù)。幸運(yùn)的是,immer 完全支持這種選擇性加入的情況

在 freeze 的版本中,只有 mutate,deepclone 和原生 reducer 才能夠遞歸地凍結(jié)全狀態(tài)樹(shù),而其他測(cè)試用例只凍結(jié)樹(shù)的修改部分

寫(xiě)在后面

其實(shí)縱觀 immer 的實(shí)現(xiàn),核心的原理就是放在了對(duì)對(duì)象讀寫(xiě)的劫持,從表現(xiàn)形式上立刻就能讓人想到 vue ,mobx 從核心原理上來(lái)說(shuō)也是對(duì)對(duì)象的讀寫(xiě)劫持,最近有另一篇非?;鸬奈恼?-- 如何讓 (a == 1 && a == 2 && a == 3) 為 true,也相信不少的小伙伴讀過(guò),除了那個(gè)肉眼不可見(jiàn)字符的答案,其他答案也算是對(duì)對(duì)象的讀寫(xiě)劫持從而達(dá)到目標(biāo)。

所以說(shuō)在 JS 中,很多知識(shí)相輔相成,有多少種方式能讓 (a == 1 && a == 2 && a == 3) 為 true,理論上有多少種答案就會(huì)有多少種 MVVM 的組成方式,甚至就有多少種方法能夠?qū)崿F(xiàn)這樣的 immutable。所以任何一點(diǎn)點(diǎn)小的知識(shí)點(diǎn)的聚合,未來(lái)都可能影響前端的發(fā)展。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/107140.html

相關(guān)文章

  • Immer.js簡(jiǎn)析

    摘要:所以整個(gè)過(guò)程只涉及三個(gè)輸入狀態(tài),中間狀態(tài),輸出狀態(tài)關(guān)鍵是是如何生成,如何應(yīng)用修改,如何生成最終的。至此基本把上的模式解析完畢。結(jié)束實(shí)現(xiàn)還是相當(dāng)巧妙的,以后可以在狀態(tài)管理上使用一下。 開(kāi)始 在函數(shù)式編程中,Immutable這個(gè)特性是相當(dāng)重要的,但是在Javascript中很明顯是沒(méi)辦法從語(yǔ)言層面提供支持,但是還有其他庫(kù)(例如:Immutable.js)可以提供給開(kāi)發(fā)者用上這樣的特性,所...

    Aceyclee 評(píng)論0 收藏0
  • Immer.js簡(jiǎn)析

    摘要:所以整個(gè)過(guò)程只涉及三個(gè)輸入狀態(tài),中間狀態(tài),輸出狀態(tài)關(guān)鍵是是如何生成,如何應(yīng)用修改,如何生成最終的。至此基本把上的模式解析完畢。結(jié)束實(shí)現(xiàn)還是相當(dāng)巧妙的,以后可以在狀態(tài)管理上使用一下。 開(kāi)始 在函數(shù)式編程中,Immutable這個(gè)特性是相當(dāng)重要的,但是在Javascript中很明顯是沒(méi)辦法從語(yǔ)言層面提供支持,但是還有其他庫(kù)(例如:Immutable.js)可以提供給開(kāi)發(fā)者用上這樣的特性,所...

    dackel 評(píng)論0 收藏0
  • immer.js 實(shí)戰(zhàn)講解文檔

    摘要:無(wú)奈網(wǎng)絡(luò)上完善的文檔實(shí)在太少,所以自己寫(xiě)了一份,本篇文章以貼近實(shí)戰(zhàn)的思路和流程,對(duì)進(jìn)行了全面的講解。這使得成為了真正的不可變數(shù)據(jù)。的使用非常靈活,多多思考,相信你還可以發(fā)現(xiàn)更多其他的妙用參考文檔官方文檔 文章在 github 開(kāi)源, 歡迎 Fork 、Star 前言 Immer 是 mobx 的作者寫(xiě)的一個(gè) immutable 庫(kù),核心實(shí)現(xiàn)是利用 ES6 的 proxy,幾乎以最小的成...

    zhiwei 評(píng)論0 收藏0
  • 精讀《源碼學(xué)習(xí)》

    摘要:精讀原文介紹了學(xué)習(xí)源碼的兩個(gè)技巧,并利用實(shí)例說(shuō)明了源碼學(xué)習(xí)過(guò)程中可以學(xué)到許多周邊知識(shí),都讓我們受益匪淺。討論地址是精讀源碼學(xué)習(xí)如果你想?yún)⑴c討論,請(qǐng)點(diǎn)擊這里,每周都有新的主題,周末或周一發(fā)布。 1. 引言 javascript-knowledge-reading-source-code 這篇文章介紹了閱讀源碼的重要性,精讀系列也已有八期源碼系列文章,分別是: 精讀《Immer.js》源...

    aboutU 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<