不知為何掘金的文章最近都流行以 "字節(jié)跳動面試官" 作為開頭,不蹭一波都不好意思說逛過掘金了。23333
最近是真到了面試的季節(jié),那么就說一下 Vuex 的源碼吧。看完你會發(fā)現(xiàn),Vue和Vuex的實(shí)現(xiàn)原理主要就那么幾行代碼。
Vue雙向綁定
要說 Vuex 的雙向綁定那么必須先從 Vue 的雙向綁定開始
Vue 的雙向綁定大部分文章都說的很詳細(xì),這里精簡點(diǎn)說一下,因?yàn)橹攸c(diǎn)還是講 Vuex
從Vue的源碼來看,Vue的雙向綁定主要做了2件事
- 數(shù)據(jù)劫持
- 添加觀察者
數(shù)據(jù)劫持實(shí)現(xiàn):(源碼精簡)
// 老版本通過 Object.defineProperty 遞歸可以實(shí)現(xiàn)
// 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方法中,當(dāng)dep.Target存在的時候會調(diào)用 dep.depend(),
劃重點(diǎn):2行代碼
- Object.defineProperty
- dep.depend()
// 最新版可以通過 Proxy 實(shí)現(xiàn)
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方法。在數(shù)據(jù)劫持之外最重要的部分就是 Dep 和 Watcher,這其實(shí)是一個觀察者模式。用最簡單的代碼實(shí)現(xiàn)以下 Vue 的觀察者模式。
觀察者模式實(shí)現(xiàn):(源碼精簡)
// 觀察者
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)
}
}
// 觀察者實(shí)例
var dep = new Dep();
// 被觀察者實(shí)例
var watcher = new Watcher({x: 1}, (val) => val);
watcher.evaluate();
// 觀察者監(jiān)聽被觀察對象
dep.depend()
dep.notify()
劃重點(diǎn):3件事
- 通過
watcher.evaluate()將自身實(shí)例賦值給Dep.target - 調(diào)用
dep.depend()將dep實(shí)例將 watcher 實(shí)例 push 到 dep.subs中 - 通過數(shù)據(jù)劫持,在調(diào)用被劫持的對象的 set 方法時,調(diào)用 dep.subs 中所有的
watcher.update()
從此。雙向綁定完成。
vuex插件
有了上文作為鋪墊,我們就可以很輕松的來解釋vuex的原理了。
Vuex僅僅是Vue的一個插件。Vuex只能使用在vue上,因?yàn)槠涓叨纫蕾囉赩ue的雙向綁定和插件系統(tǒng)。
Vuex的注入代碼比較簡單,調(diào)用了一下applyMixin方法,現(xiàn)在的版本其實(shí)就是調(diào)用了Vue.mixin,在所有組件的 beforeCreate生命周期注入了設(shè)置 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
}
}
}
劃重點(diǎn):1行代碼 Vue.mixin
那么 Vuex.Store 是如何實(shí)現(xiàn)的呢?
// 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))
}
劃重點(diǎn):其實(shí)上面的代碼絕大部分都不需要關(guān)注的 - -。其實(shí)重點(diǎn)就是一行代碼resetStoreVM(this, state)。
那么 resetStoreVM 里面是什么呢?
// src/store.js
function resetStoreVM (store, state, hot) {
Vue.config.silent = true
store._vm = new Vue({
data: {
$$state: state
},
computed
})
}
劃重點(diǎn):還是一行代碼:new Vue。通過 Vue自己的雙向綁定然后注入給
你是不是以為就這樣結(jié)束了呢?NoNoNo,當(dāng)你再Vue中通過 this 如果調(diào)用 store的數(shù)據(jù)呢?
// 當(dāng)獲取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 );
其實(shí)就是獲取 this._vm._data.$$state 而已啦。
### 最后
歡迎關(guān)注公眾號「前端進(jìn)階課」認(rèn)真學(xué)前端,一起進(jìn)階。