字節跳動面試官:請說一下vuex工作原理(重點就幾行代碼而已啦)

不知為何掘金的文章最近都流行以 "字節跳動面試官" 作為開頭,不蹭一波都不好意思說逛過掘金了。23333

最近是真到了面試的季節,那么就說一下 Vuex 的源碼吧。看完你會發現,Vue和Vuex的實現原理主要就那么幾行代碼。

Vue雙向綁定

要說 Vuex 的雙向綁定那么必須先從 Vue 的雙向綁定開始

Vue 的雙向綁定大部分文章都說的很詳細,這里精簡點說一下,因為重點還是講 Vuex

從Vue的源碼來看,Vue的雙向綁定主要做了2件事

  1. 數據劫持
  2. 添加觀察者

數據劫持實現:(源碼精簡)

// 老版本通過 Object.defineProperty 遞歸可以實現

// src/core/observer/index.js
Object.defineProperty(obj, key, {
  enumerable: true,
  configurable: true,
  get: function reactiveGetter () {
    const value = getter ? getter.call(obj) : val
    if (Dep.target) {
      dep.depend()
      if (childOb) {
        childOb.dep.depend()
      }
      if (Array.isArray(value)) {
        dependArray(value)
      }
    }
    return value
  },
  set: function reactiveSetter (newVal) {
    const value = getter ? getter.call(obj) : val
    if (newVal === value || (newVal !== newVal && value !== value)) {
      return
    }
    if (setter) {
      setter.call(obj, newVal)
    } else {
      val = newVal
    }
    childOb = !shallow && observe(newVal)
    dep.notify()
  }
})

這里無非就是劫持了對象的get和set方法。在所代理的屬性的get方法中,當dep.Target存在的時候會調用 dep.depend()

劃重點:2行代碼

  1. Object.defineProperty
  2. dep.depend()
// 最新版可以通過 Proxy 實現
Proxy(data, {
  get(target, key) {
    return target[key];
  },
  set(target, key, value) {
    let val = Reflect.set(target, key, value);
      _that.$dep[key].forEach(item => item.update());
    return val;
  }
})

從上面的代碼看出,無非就劫持了對象的get和set方法。在數據劫持之外最重要的部分就是 DepWatcher,這其實是一個觀察者模式。用最簡單的代碼實現以下 Vue 的觀察者模式。

觀察者模式實現:(源碼精簡)

    // 觀察者
    class Dep {
        constructor() {
            this.subs = []
        }
        
        addSub(sub) {
            this.subs.push(sub)
        }
        
        depend() {
            if (Dep.target) { 
                Dep.target.addDep(this);
            }
        }
        
        notify() {
            this.subs.forEach(sub => sub.update())
        }
    }
    
    // 被觀察者
    class Watcher {
        constructor(vm, expOrFn) {
            this.vm = vm;
            this.getter = expOrFn;
            this.value;
        }

        get() {
            Dep.target = this;
            
            var vm = this.vm;
            var value = this.getter.call(vm, vm);
            return value;
        }

        evaluate() {
            this.value = this.get();
        }

        addDep(dep) {
            dep.addSub(this);
        }
        
        update() {
            console.log('更新, value:', this.value)
        }
    }
    
    // 觀察者實例
    var dep = new Dep();
    
    //  被觀察者實例
    var watcher = new Watcher({x: 1}, (val) => val);
    watcher.evaluate();
    
    // 觀察者監聽被觀察對象
    dep.depend()
    
    dep.notify()

劃重點:3件事

  1. 通過 watcher.evaluate() 將自身實例賦值給 Dep.target
  2. 調用 dep.depend() 將dep實例將 watcher 實例 push 到 dep.subs中
  3. 通過數據劫持,在調用被劫持的對象的 set 方法時,調用 dep.subs 中所有的 watcher.update()

從此。雙向綁定完成。

vuex插件

有了上文作為鋪墊,我們就可以很輕松的來解釋vuex的原理了。

Vuex僅僅是Vue的一個插件。Vuex只能使用在vue上,因為其高度依賴于Vue的雙向綁定和插件系統。

Vuex的注入代碼比較簡單,調用了一下applyMixin方法,現在的版本其實就是調用了Vue.mixin,在所有組件的 beforeCreate生命周期注入了設置 this.$store這樣一個對象。

// src/store.js
export function install (_Vue) {
  if (Vue && _Vue === Vue) {
    return
  }
  Vue = _Vue
  applyMixin(Vue)
}
// src/mixins.js
export default function (Vue) {
  const version = Number(Vue.version.split('.')[0])

  if (version >= 2) {
    Vue.mixin({ beforeCreate: vuexInit })
  } else {
    const _init = Vue.prototype._init
    Vue.prototype._init = function (options = {}) {
      options.init = options.init
        ? [vuexInit].concat(options.init)
        : vuexInit
      _init.call(this, options)
    }
  }

  function vuexInit () {
    const options = this.$options
    // store injection
    if (options.store) {
      this.$store = typeof options.store === 'function'
        ? options.store()
        : options.store
    } else if (options.parent && options.parent.$store) {
      this.$store = options.parent.$store
    }
  }
}

劃重點:1行代碼 Vue.mixin

那么 Vuex.Store 是如何實現的呢?

// src/store.js
constructor (options = {}) {
  const {
    plugins = [],
    strict = false
  } = options

  // store internal state
  this._committing = false
  this._actions = Object.create(null)
  this._actionSubscribers = []
  this._mutations = Object.create(null)
  this._wrappedGetters = Object.create(null)
  this._modules = new ModuleCollection(options)
  this._modulesNamespaceMap = Object.create(null)
  this._subscribers = []
  this._watcherVM = new Vue()

  const store = this
  const { dispatch, commit } = this
  this.dispatch = function boundDispatch (type, payload) {
    return dispatch.call(store, type, payload)
}
  this.commit = function boundCommit (type, payload, options) {
    return commit.call(store, type, payload, options)
}

  // strict mode
  this.strict = strict

  const state = this._modules.root.state

  // init root module.

  // this also recursively registers all sub-modules
  // and collects all module getters inside this._wrappedGetters
  installModule(this, state, [], this._modules.root)

  resetStoreVM(this, state)

  // apply plugins
  plugins.forEach(plugin => plugin(this))

}

劃重點:其實上面的代碼絕大部分都不需要關注的 - -。其實重點就是一行代碼resetStoreVM(this, state)

那么 resetStoreVM 里面是什么呢?

// src/store.js
function resetStoreVM (store, state, hot) {
  Vue.config.silent = true
  store._vm = new Vue({
    data: {
      $$state: state
    },
    computed
  })
}

劃重點:還是一行代碼:new Vue。通過 Vue自己的雙向綁定然后注入給

你是不是以為就這樣結束了呢?NoNoNo,當你再Vue中通過 this 如果調用 store的數據呢?

// 當獲取state時,返回以雙向綁定的$$sate
var prototypeAccessors$1 = { state: { configurable: true } };

prototypeAccessors$1.state.get = function () {
  return this._vm._data.$$state
};

// 將state定義在原型中
Object.defineProperties( Store.prototype, prototypeAccessors$1 );

其實就是獲取 this._vm._data.$$state 而已啦。

### 最后

歡迎關注公眾號「前端進階課」認真學前端,一起進階。
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,363評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,497評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,305評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,962評論 1 311
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,727評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,193評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,257評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,411評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,945評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,777評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,978評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,519評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,216評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,657評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,960評論 2 373

推薦閱讀更多精彩內容