不知道为什么最近掘金的文章都以“字节跳动面试官”开头。我很惭愧地说我去过掘金队。23333最近真的是面试季,说说Vuex的源码吧。看完你会发现,Vue和Vuex的实现原理主要就那么几行代码。Vue双向绑定要说Vuex的双向绑定,就得先从Vue的双向绑定说起。大多数关于Vue的双向绑定的文章都讲的很详细。这里我简化一下,因为从Vue的源码来看重点还是在Vuex上,Vue的双向绑定主要做了两件事:数据劫持和观察者数据劫持实现:(源码简化)//旧的version可以通过Object.defineProperty//src/core/observer/index.jsObject递归实现。defineProperty(obj,key,{enumerable:true,configurable:true,get:functionreactiveGetter(){constvalue=getter?getter.call(obj):valif(Dep.target){dep.depend()if(childOb){childOb.dep.depend()}if(Array.isArray(value)){dependArray(value)}}returnvalue},set:functionreactiveSetter(newVal){constvalue=getter?getter.call(obj):valif(newVal===value||(newVal!==newVal&&value!==value)){return}if(setter){setter.call(obj,newVal)}else{val=newVal}childOb=!shallow&&observe(newVal)dep.notify()}})这里无非是劫持了对象的get和set方法。在proxied属性的get方法中,当dep.Target存在时,会调用dep.depend(),重点:2行代码Object.definePropertydep.depend()//最新版本可以实现Proxy(data,{get(target,key){returntarget[key];},set(target,key,value){让val=Reflect.set(target,key,value);_that.$dep[key].forEach(item=>item.update());returnval;}})从上面的代码可以看出,无非就是劫持了对象的get和set方法。除了数据劫持之外最重要的部分就是Dep和Watcher,其实就是观察者模式。用最简单的代码实现下面的Vue观察者模式。观察者模式实现:(源码简化)//ObserverclassDep{constructor(){this.subs=[]}addSub(sub){this.subs.push(sub)}depend(){if(Dep.target){Dep.target.addDep(this);}}notify(){this.subs.forEach(sub=>sub.update())}}//观察者类Watcher{constructor(vm,expOrFn){this.vm=vm;this.getter=expOrFn;这个值;}get(){Dep.target=this;varvm=this.vm;varvalue=this.getter.call(vm,vm);返回值;}evaluate(){this.value=this.get();}addDep(dep){dep.addSub(this);}update(){console.log('update,value:',this.value)}}//观察者实例vardep=newDep();//观察者实例varwatcher=newWatcher({x:1},(val)=>val);观察者.评估();//Observer监听被观察对象dep.depend()dep.notify()重点:3件事:通过watcher.evaluate()将自己的实例赋值给Dep.target,调用dep.depend(),推送dep实例,通过数据劫持将watcher实例推送到dep.subs中,并调用被劫持对象的set方法,调用dep.subs中的所有watcher.update()就是从这个双向绑定中完成的。有了以上作为vuex插件的铺垫,我们就可以很容易的说明vuex的原理了。Vuex只是Vue的一个插件。Vuex只能在vue上使用,因为它高度依赖Vue的双向绑定和插件系统。Vuex的注入代码比较简单,调用了applyMixin方法。现在的版本其实是调用了Vue.mixin,在所有组件的beforeCreate生命周期中注入了一个this.$store这样的对象。//src/store.jsexportfunctioninstall(_Vue){if(Vue&&_Vue===Vue){return}Vue=_VueapplyMixin(Vue)}//src/mixins.jsexport默认函数(Vue){constversion=Number(Vue.version.split('.')[0])if(version>=2){Vue.mixin({beforeCreate:vuexInit})}else{const_init=Vue.prototype._initVue.prototype._init=function(options={}){options.init=options.init?[vuexInit].concat(options.init):vuexInit_init.call(this,options)}}functionvuexInit(){constoptions=this.$options//store注入if(options.store){this.$store=typeofoptions.store==='函数'?options.store():options.store}elseif(options.parent&&options.parent.$store){this.$store=options.parent.$store}}}划重点:1行代码Vue.mixin那么Vuex.Store是如何实现的呢?//src/store.jsconstructor(options={}){const{plugins=[],strict=false}=options//存储内部状态this._committing=falsethis._actions=Object.create(null)this._actionSubscribers=[]this._mutations=Object.create(null)this._wrappedGetters=Object.create(null)this._modules=newModuleCollection(选项)this._modulesNamespaceMap=Object.create(null)this._subscribers=[]this._watcherVM=newVue()conststore=thisconst{dispatch,commit}=thisthis.dispatch=functionboundDispatch(type,payload){returndispatch.call(store,type,payload)}this.commit=functionboundCommit(type,payload,options){returncommit.call(store,type,payload,options)}//严格modethis.strict=strictconststate=this._modules.root.state//初始化根模块。//这也递归地注册所有子模块//并收集所有模块gettersinsidethis._wrappedGettersinstallModule(this,state,[],this._modules.root)resetStoreVM(this,state)//应用插件plugins.forEach(plugin=>plugin(this))}划重点:其实上面大部分代码不需要关注——其实重点是一行代码resetStoreVM(this,状态)。那么resetStoreVM里面有什么?//src/store.jsfunctionresetStoreVM(store,state,hot){Vue.config.silent=truestore._vm=newVue({data:{$$state:state},computed})}重点:还是一行代码:新Vue。通过Vue自带的双向绑定,注入给你,你以为就完了吗?NoNoNo,你在vue里传这个的时候,调用store数据怎么办?//获取状态时,返回$$satevarprototypeAccessors$1={state:{configurable:true}};prototypeAccessors$1.state.get=function(){returnthis._vm._data.$$state};//在原型中定义状态Object.defineProperties(Store.prototype,prototypeAccessors$1);其实就是为了得到这个._vm._data.$$state。最后,原创不易,欢迎关注公众号《前端进阶教程》认真学习前端,共同进步。
