国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

immer.js 簡介及源碼解析

Profeel / 2812人閱讀

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

博客鏈接:下一代狀態管理工具 immer 簡介及源碼解析

JS 里面的變量類型可以大致分為基本類型和引用類型。在使用過程中,引用類型經常會產生一些無法意識到的副作用,所以在現代 JS 開發過程中,大家都有意識的寫下斷開引用的不可變數據類型。

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

// 期望:b 的每一個元素的 val 值變為 2
console.log(a[0].val) // 2

從上述例子我們可以發現,本意是只想讓 b 中的每一個元素的值變為 2 ,但卻無意中改掉了 a 中每一個元素的結果,這是不符合預期的。接下來如果某個地方使用到了 a ,很容易發生一些我們難以預料并且難以 debug 的 bug。

在有了這樣的問題之后,一般來說當需要傳遞一個對象進一個函數時,我們可以使用 Object.assign 或者 ... 對對象進行解構,成功斷掉一層的引用。

例如上面的問題我們可以改用下面的這種寫法:

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

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

這樣做其實還會有一個問題,無論是 Object.assign 還是 ... 的解構操作,斷掉的引用也只是一層,如果對象嵌套超過一層,這樣做還是有一定的風險。

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

這樣一來,后面的代碼如果一不小心在一個函數內部給 b.desc 對象里面的內容通過“點”進行賦值,就一定會改變具有相同引用的 a.desc 部分的值,這當然是不符合我們的預期的。

所以在這之后,大多數情況下我們會考慮 深拷貝 這樣的操作來完全避免上面遇到的所有問題。深拷貝,顧名思義就是在遍歷過程中,如果遇到了可能出現引用的數據類型,就會遞歸的完全創建一個新的類型。

// 一個簡單的深拷貝函數,去掉了一些膠水部分
// 用戶態輸入一定是一個 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 函數進行簡單測試

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"

上面的這個 deepClone 可以滿足簡單的需求,但是真正在生產工作中,我們需要考慮非常多的因素。舉例來說:

key 里面 getter,setter 以及原型鏈上的內容如何處理

value 是一個 Symbol 如何處理

value 是其他非 Plain Object 如何處理

value 內部出現了一些循環引用如何處理

因為有太多不確定因素,所以我還是推薦使用大型開源項目里面的工具函數,比較常用的為大家所熟知的就是 lodash.cloneDeep,無論是安全性還是效果都有所保障。

其實,這樣的概念我們常稱作 immutable ,意為不可變的數據,其實理解為不可變關系更為恰當。每當我們創建一個被 deepClone 過的數據,新的數據進行有副作用 (side effect) 的操作都不會影響到之前的數據,這也就是 immutable 的精髓和本質。

然而 deepClone 這種函數雖然斷絕了引用關系實現了 immutable,但是開銷實在太大。所以在 2014 年,facebook 的 immutable-js 橫空出世,即保證了 immutable ,又兼顧了性能。

immutable-js 簡介

immutable-js 使用了另一套數據結構的 API ,與我們的常見操作有些許不同,它將所有的原生對象都會轉化成 immutable-js 的內部對象,并且任何操作最終都會返回一個新的 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"

對于性能方面,immutable-js 也有它的優勢,舉個簡單的例子:

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

從上面的例子可以看出來,在 immutable-js 的數據結構中,深層次的對象在沒有修改的情況下仍然能夠保證嚴格相等。這里的嚴格相等就可以認為是沒有新建這個對象,仍然在內部保持著之前的引用,但是修改卻不會同步的修改。

經常使用 React 的同學肯定也對 immutable-js 不陌生,這也就是為什么 immutable-js 會極大提高 React 頁面性能的原因之一了。

當然能夠達到 immutable 效果的當然不只這幾個個例,這篇文章我主要想介紹實現 immutable 的庫其實是 immer。

immer 簡介

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

與 immutable-js 最大的不同,immer 是使用原生數據結構的 API 而不是內置的 API,舉個簡單例子:

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

所有需要更改的邏輯都可以放進 produce 的第二個參數的函數內部,即使給對象內的元素直接賦值,也不會對原對象產生任何影響。

簡單介紹完使用之后,下面就開始簡單介紹它的內部實現。不過在這之前,想先通過上面的例子簡單的發散思考一下。

通過文章最開始的例子我們就能明白,給函數傳入一個對象,直接通過“點”操作符對里面的一個屬性進行更改是一定會改變外面的結果的。而上面的這個例子中,draft 參數穿入進去,與 state 一樣也有 done 這個屬性,但是在通過 draft.done 改變值之后,原來的 state.done 并沒有發生改變。其實到這里,結合之前研究 vue 源碼的經驗,我當時就篤定,這里一定用了 Object.defineProperty,draft 通過“點”操作的之后,一些數據的結果被劫持了,然后做了一些新的操作。

immer 原理解析

真正翻開源碼,誠然里面確實有 defineProperty 的身影,不過在另一個標準的文件中,用了一種新的方式,那就是 ES6 中新增的 Proxy 對象。而在日常的業務過程中,應該很少有前端工程師會用到 Proxy 對象,因為它的應用場景確實有些狹隘,所以這里簡單介紹一下 Proxy 對象的使用。

Proxy 對象接受兩個參數,第一個參數是需要操作的對象,第二個參數是設置對應攔截的屬性,這里的屬性同樣也支持 get,set 等等,也就是劫持了對應元素的讀和寫,能夠在其中進行一些操作,最終返回一個 Proxy 對象。

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"

上面這個例子中傳入的第一個參數是一個空對象,當然我們可以用其他對象有內容的對象代替它。例如維護一份 state 在內部,來判斷是否有變化,下面這個例子就是一個構造函數,如果將它的實例傳入 Proxy 對象作為第一個參數,就能夠后面的處理對象中使用其中的方法:

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 }
  }
}

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

里面最關鍵的就應該是 modifing 這個函數,如果觸發了 setter 并且之前沒有改動過的話,就會手動將 modified 這個 flag 設置為 true,并且手動通過原生的 API 實現一層 immutable。

對于 Proxy 的第二個參數,就更加簡單了。在這個例子中,只是簡單做一層轉發,任何對元素的讀取和寫入都轉發到前面的實例內部方法去。

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 里面加一個 flag 的目的就在于將來從 proxy 對象中獲取 store 實例更加方便。

最終我們能夠完成這個 produce 函數,創建 store 實例后創建 proxy 實例。然后將創建的 proxy 實例傳入第二個函數中去。這樣無論在內部做怎樣有副作用的事情,最終都會在 store 實例內部將它解決。最終得到了修改之后的 proxy 對象,而 proxy 對象內部已經維護了兩份 state ,通過判斷 modified 的值來確定究竟返回哪一份。

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
}

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

性能

性能方面,就用 immer 官方 README 里面的介紹來說明情況。

這是一個關于 immer 性能的簡單測試。這個測試使用了 100000 個組件元素,并且更新其中的 10000 個。freeze 表示狀態樹在生成之后已被凍結。這是一個最佳的開發實踐,因為它可以防止開發人員意外修改狀態樹。

通過上圖的觀察,基本可以得出:

從 immer 的角度來看,這個性能環境比其他框架和庫要惡劣的多,因為它必須代理的根節點相對于其余的數據集來說大得多

從 mutate 和 deepclone 來看,mutate 基準確定了數據更改費用的基線,沒有不可變性(或深度克隆情況下的結構共享)

使用 Proxy 的 immer 大概是手寫 reducer 的兩倍,當然這在實踐中可以忽略不計

immer 大致和 immutable-js 一樣快。但是,immutable-js 最后經常需要 toJS 操作,這里的性能的開銷是很大的。例如將不可變的 JS 對象轉換回普通的對象,將它們傳遞給組件中,或著通過網絡傳輸等等(還有將從例如服務器接收到的數據轉換為 immutable-js 內置對象的前期成本)

immer 的 ES5 實現速度明顯較慢。對于大多數的 reducer 來說,這并不重要,因為處理大量數據的 reducer 可以完全不(或者僅部分)使用 immer 的 produce 函數。幸運的是,immer 完全支持這種選擇性加入的情況

在 freeze 的版本中,只有 mutate,deepclone 和原生 reducer 才能夠遞歸地凍結全狀態樹,而其他測試用例只凍結樹的修改部分

寫在后面

其實縱觀 immer 的實現,核心的原理就是放在了對對象讀寫的劫持,從表現形式上立刻就能讓人想到 vue ,mobx 從核心原理上來說也是對對象的讀寫劫持,最近有另一篇非常火的文章 -- 如何讓 (a == 1 && a == 2 && a == 3) 為 true,也相信不少的小伙伴讀過,除了那個肉眼不可見字符的答案,其他答案也算是對對象的讀寫劫持從而達到目標。

所以說在 JS 中,很多知識相輔相成,有多少種方式能讓 (a == 1 && a == 2 && a == 3) 為 true,理論上有多少種答案就會有多少種 MVVM 的組成方式,甚至就有多少種方法能夠實現這樣的 immutable。所以任何一點點小的知識點的聚合,未來都可能影響前端的發展。

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/107140.html

相關文章

  • Immer.js簡析

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

    Aceyclee 評論0 收藏0
  • Immer.js簡析

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

    dackel 評論0 收藏0
  • immer.js 實戰講解文檔

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

    zhiwei 評論0 收藏0
  • 精讀《源碼學習》

    摘要:精讀原文介紹了學習源碼的兩個技巧,并利用實例說明了源碼學習過程中可以學到許多周邊知識,都讓我們受益匪淺。討論地址是精讀源碼學習如果你想參與討論,請點擊這里,每周都有新的主題,周末或周一發布。 1. 引言 javascript-knowledge-reading-source-code 這篇文章介紹了閱讀源碼的重要性,精讀系列也已有八期源碼系列文章,分別是: 精讀《Immer.js》源...

    aboutU 評論0 收藏0

發表評論

0條評論

最新活動
閱讀需要支付1元查看
<