字节跳动面试官:请说一下vuex工作原理(重点就几行代码而已啦)

不知为何掘金的文章最近都流行以 "字节跳动面试官" 作为开头,不蹭一波都不好意思说逛过掘金了。23333 最近是真到了面试的季节,那么就说一下 Vuex 的源码吧。看完你会发现,Vue和Vuex的实现原理主要就那么几行代码。 Vue双向绑定 要说 Vuex 的双向绑定那么必须先从 Vue 的双向绑定开始 Vue 的双向绑定大部分文章都说的很详细,这里精简点说一下,因为重点还是讲 Vuex 从Vue的源码来看,Vue的双向绑定主要做了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行代码 Object.defineProperty 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方法。在数据劫持之外最重要的部分就是 Dep 和 Watcher,这其实是一个观察者模式。用最简单的代码实现以下 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件事 通过 watcher.evaluate() 将自身实例赋值给 Dep.target 调用 dep.depend() 将dep实例将 watcher 实例 push 到 dep.subs中 通过数据劫持,在调用被劫持的对象的 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 而已啦。 ### 最后 欢迎关注公众号「前端进阶课」认真学前端,一起进阶。

本文章由javascript技术分享原创和收集

发表评论 (审核通过后显示评论):