当学习成为习惯,知识就成为常识。感谢您的关注、点赞、收藏和评论。有新视频和文章会第一时间发到微信公众号。欢迎关注:李永宁lyn的文章已收录到github仓库liyongning/blog。欢迎收看星空。前言上一篇Vue2系列手写补丁-??diff实现DOMdiff流程,完成页面响应数据的更新。目标本文的目标是实现计算属性,完成计算属性在模板中的展示。涉及知识点:计算属性的本质计算属性的缓存原理实现下面来实现计算属性。_init/src/index.js/***初始化配置对象*@param{*}options*/Vue.prototype._init=function(options){//...//初始化options.data//代理数据对象Vue实例上的每个属性//为数据对象上的每个属性设置响应能力initData(this)//初始化计算选项并将计算属性代理给Vue实例//结合watcher实现缓存initComputed(this)//安装运行时渲染工具函数renderHelper(this)//...}initComputed/src/initComputed.js/***初始化computed配置项*为每个item实例化一个Watcher,并将其computed属性委托给Vue实例上*结合watcher.dirty和watcher.evalute实现computed缓存*@param{*}vmVue实例*/exportdefaultfunctioninitComputed(vm){//获取computed配置项constcomputed=vm.$options.computed//记录watcherconstwatcher=vm._watcher=Object.create(null)//遍历计算的objectfor(letkeyincomputed){//实例化Watcher,回调函数默认延迟执行watcher[key]=newWatcher(computed[key],{lazy:true},vm)//代理计算属性key到Vue实例defineComputed(vm,key)}}defineComputed/src/initComputed.js/***将计算属性代理到Vue实例*@param{*}vmVue实例*@param{*}键的计算属性computed*/functiondefineComputed(vm,key){//属性描述符constdescriptor={get:function(){constwatcher=vm._watcher[key]if(watcher.dirty){//表示当前计算回调函数在本次渲染周期内还没有执行过//执行evalute,通知watcher执行计算回调函数,并获取回调函数的返回值watcher.evalute()}returnwatcher.value},set:function(){console.log('nosetter')}}//将计算属性代理给Vue实例Object.defineProperty(vm,key,descriptor)}Watcher/src/watcher.js/***@param{*}cb回调函数负责更新DOM回调函数*@param{*}optionswatcher配置项*/exportdefaultfunctionWatcher(cb,options={},vm=null){//备份cb函数this._cb=cb//回调函数执行后的值this.value=null//计算属性实现了原理缓存,标记本次渲染周期是否执行过当前回调函数this.dirty=!!options.lazy//Vue实例this.vm=vm//不懒惰执行时,直接执行cb函数,属性读取vm.xx的调用会出现在cb函数中,从而收集依赖!options.lazy&&this.get()}watcher.get/src/watcher.js/***负责执行Watcher的cb函数*依赖执行时收集*/Watcher.prototype.get=function(){pushTarget(this)this.value=this._cb.apply(this.vm)popTarget()}watcher.update/src/watcher.js/***响应式数据更新时,dep通知watcher执行update方法,*让update方法执行this._cb函数更新DOM*/Watcher.prototype.update=function(){//通过Promise,把this._cb的执行放在this后面.dirty=true//否则,点击按钮时,不会执行计算属性的第一次计算,//因为this._cb执行时,组件会更新,this.dirty还是//最后一次获取计算属性的值时为false,导致无法获取计算属性的最新值//但是异步更新队列之后就不需要这个了,当然毕竟本质是异步更新对象是PromisePromise.resolve().then(()=>{this._cb()})//执行完_cb函数后,DOM就起来了dirty,进入下一个渲染周期,所以设置dirty为falsejsWatcher.prototype.evalute=function(){//执行get触发执行计算函数(cb)this.get()//设置dirty为false,在刷新周期内实现计算缓存this.dirty=false}pushTarget/src/dep.js//存放所有的Dep.target//为什么有多个Dep.target?//组件会生成一个渲染Watcher,如果用户在渲染过程中处理了Watcher,//比如计算计算属性,然后evalute将被执行->get//如果直接赋值Dep.target,那么Dep.target的最后一个值——渲染Watcher会丢失//计算属性后渲染的响应数据无法完成依赖收集consttargetStack=[]/***备份本次传入的Watcher并赋值给Dep.target*@param{*}targetWatcherinstance*/exportfunctionpushTarget(target){//备份传入的watchertargetStack.push(target)Dep.target=target}popTarget/src/dep.js/***将Dep.target重置为之前的Watcher或null*/exportfunctionpopTarget(){targetStack.pop()Dep.target=targetStack[targetStack.length-1]}结果不错。至此,Vue计算属性的实现就完成了。如果能看到如下效果图,说明一切正常。GIF地址:https://gitee.com/liyongning/...可以看到页面上的computed属性已经正常显示,也可以响应式更新,并且具备缓存能力(查看computed通过控制台输出)。至此,手写Vue系列的最后一部分就剩下了——手写Vue系列的异步更新队列。链接配套视频,微信回复公众号:《精通Vue技术栈源码原理视频版》精通Vue技术栈源码原理专栏github仓库liyongning/Vue欢迎Stargithub仓库liyongning/Lyn-Vue-DOM欢迎Stargithub仓库liyongning/Lyn-Vue-Template欢迎Star感谢大家的关注、点赞、收藏和评论,我们下期再见。当学习成为一种习惯,知识就成为常识。感谢您的关注、点赞、收藏和评论。有新视频和文章会第一时间发到微信公众号。欢迎关注:李永宁lyn的文章已收录到github仓库liyongning/blog。欢迎收看星空。
