不知爲什麼掘金的文章最近都流行以 "字節跳動面試官" 做爲開頭,不蹭一波都很差意思說逛過掘金了。23333前端
最近是真到了面試的季節,那麼就說一下 Vuex
的源碼吧。看完你會發現,Vue和Vuex的實現原理主要就那麼幾行代碼。vue
要說 Vuex
的雙向綁定那麼必須先從 Vue
的雙向綁定開始react
Vue
的雙向綁定大部分文章都說的很詳細,這裏精簡點說一下,由於重點仍是講 Vuex
面試
從Vue的源碼來看,Vue的雙向綁定主要作了2件事vuex
數據劫持實現:(源碼精簡)app
// 老版本經過 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()
,ide
劃重點:2行代碼this
// 最新版能夠經過 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方法。在數據劫持以外最重要的部分就是 Dep
和 Watcher
,這實際上是一個觀察者模式。用最簡單的代碼實現如下 Vue 的觀察者模式。lua
觀察者模式實現:(源碼精簡)spa
// 觀察者 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件事
watcher.evaluate()
將自身實例賦值給 Dep.target
dep.depend()
將dep實例將 watcher 實例 push 到 dep.subs中watcher.update()
今後。雙向綁定完成。
有了上文做爲鋪墊,咱們就能夠很輕鬆的來解釋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
而已啦。