一起学习vue源码 – Object的转变侦测

一起学习vue源码 - Object的转变侦测

 

作者:小土豆biubiubiu

博客园:www.cnblogs.com/HouJiao/

掘金:https://juejin.im/user/58c61b4361ff4b005d9e894d

简书:https://www.jianshu.com/u/cb1c3884e6d5

微信民众号:土豆妈的碎碎念(扫码关注,一起吸猫,一起听故事,一起学习前端手艺)

码字不易,点赞激励哟~

一.前言

  一起学习vue源码的第一篇,原本想起名为双向数据绑定原理,然则想来照样引用书中[深入浅出vue.js]对照专业的形貌作为问题。

  (主要是由于双向数据绑定中Object和Array的实现原理是不一样的,以是照样拆分的细一些对照好)

  总归来说,双向数据绑定就是通过转变侦测这种方式去实现的,这篇文章主要总结的是Object的转变侦测。

 

  我们在面试的时刻,若是面试者的手艺栈包罗vue框架,那么面试官会有很大的几率甩出“你领会vue中双向数据绑定的原理吗”这个问题。

  我也听过一些回覆,人人一样平常都能说出一个词叫“公布-订阅”。

  那在深入去问的时刻,或者说你能不能给我实现一个简朴的双向数据绑定,基本就回覆不上来了。

  

  说到这里我已经抛出三个名词了:双向数据绑定转变侦测公布-订阅

  前面说过双向数据绑定就是通过转变侦测这种方式去实现的。

  那这里的公布-订阅我明白是软件的设计头脑,它比转变侦测更深一层,已经到了代码的设计模式这一层了。

  以是我们可以说双向数据绑定就是通过转变侦测这种方式去实现的,也可以说双向数据绑定是通过公布-订阅这种模式去实现的。

  我小我私家以为两者说法都没有问题,只是形貌方式不一样。

 

  那不管是叫转变侦测照样公布-订阅,有一些现实生活中的例子可以便于我们明白它们。

  (后面的许多形貌都市混用这两个名词,不用纠结叫法,领会说的是同一个器械即可)

  好比我们经常玩的微博:

    有一个用户kk很喜欢某个博主MM,然后就在微博上关注了博主MM

    之后每一次博主MM在微博上揭晓一些吃吃喝喝的动态,微博客户端都市自动将动态推送给用户kk

    在过了一段时间,博主MM爆出一个欠好的新闻,用户kk便将博主MM的微博取关了。

 

  在这个现实场景中,我们可以称博主MM是一个公布者

  用户kk是一个订阅者

  微博客户端就是一个治理者的角色,它时刻侦测这博主MM的动态,在博主MM更新动态是自动将动态推送给订阅者。

  

  前面说了这么多想来人人应该能明白公布订阅/转变侦测大致的设计头脑和需要关注的几个点了:

    1.若何侦测数据的转变(或者说若何侦测到公布者的公布的内容)

    2.若何网络保留订阅者。

    3.订阅者若何实现。

  

  接着我们就我们总结的点逐个击破。

 

二.若何侦测数据的转变

  看过javascript高级程序设计的应该都知道Object类提供了一个方式defineProperty,在该方式中界说get和set就可以实现数据的侦测。

  备注:对Object.defineProperty不领会的可以移步这里

  

  下面就Object的defineProperty方式做一个示例演示。

var obj = {}; var name; Object.defineProperty(obj, 'name', { enumerable : true, configurable : true, get: function(){ console.log("get方式被挪用"); return name; }, set: function(newName){ console.log("set方式被挪用"); name = newName; } }) // 修改name属性时会触发set方式 obj.name = 'newTodou'; // 接见name属性时会触发get方式 var objName = obj.name;

  

  我们将这段代码引入一个html中,执行后控制台的打印效果如下:

  一起学习vue源码 - Object的转变侦测

  

  可以看到,当我们修改obj.name属性值时,挪用了name属性的set方式,打印了”set方式被挪用”;

  当我们接见obj.name属性值时,挪用name属性的get方式,打印了”get方式被挪用”。

  那么这就是我们说的“若何侦测数据的转变”这个问题的谜底,是不是很简朴呢。

提醒:

  接见数据属性值时会触发界说在属性上的get方式;修改数据属性值时触发界说在属性上的set方式。

  这句话很要害,希望可以切记,后面许多内容都跟这个相关。

 

  现实上到这里我们已经可以实现一个简朴的双向数据绑定:input输入框内容改变,实现输入框下方span文本内容改变。

 

  我们先梳理一下这整个的实现思绪:监听输入框的内容,将输入框的内容同步到span的innerText属性。

  监听输入框内容的转变可以通过keyup事宜,在事宜内部获取到input框中的内容,即获取到转变的数据,我们把这个数据保留到一个obj工具的name属性中。

  将输入框的内容同步到span的innerText属性这个操作相当于将转变的数据同步更新到视图中,更新的逻辑很简朴:

    spanEle.innerText = obj.name;

  我们需要思量的是在那里触发这个更新操作。

  在监听输入框内容转变的逻辑中我们说过会将转变的数据保留到obj.name中。

  那这个操作现实上就是为工具的属性赋值,会触发界说在属性上的set方式。

  那么将输入框的内容同步到span的innerText属性这个操作,很自然的就落到了name属性的set方式中。

  一起学习vue源码 - Object的转变侦测

  到这里,信赖人人已经很轻松能写出代码了。

<input type="text" id="name"/> <br/> <span id="text"></span> <script type="text/javascript"> var nameEle = document.getElementById("name"); var textEle = document.getElementById('text'); var obj = {}; Object.defineProperty(obj, 'name', { enumerable: true, configurable: true, get: function(){ return textEle.value; }, set: function(newName){ textEle.innerText = newName; } }) nameEle.onkeyup = function () { obj.name = event.target.value; } </script>

   

  接着还没完,我们知道一个工具内里一样平常都市有多个属性,vue data中一样平常也会存在多个或者多层的属性和数据,好比:

  data: {

    id: 12091,

    context: {

      index:1,

      result: 0

    }

  }

  以是我们得让工具中的所有属性都变得可侦测:递归遍历工具的所有属性,为每个属性都界说get和set方式。

  vue源码封装了一个Observer类来实现这个功效。

/* * obj数据现实上就是vue中的data数据 */ function Observer(obj){ this.obj = obj; this.walk(obj); } Observer.prototype.walk = function(obj) { // 获取obj工具中所有的属性 var keysArr = Object.keys(obj); keysArr.forEach(element =>{ defineReactive(obj, element, obj[element]); }) } // 参照源码,将该方式为自力一个方式 function defineReactive(obj, key, val) { // 若是obj是包罗多层数据属性的工具,就需要递归每一个子属性 if(typeof val === 'object'){ new Observer(val); } Object.defineProperty(obj, key,{ enumerable: true, configurable: true, get: function(){ return val; }, set: function(newVal) { val = newVal; } }) }

 

  到这里,数据观察这一步就完成了。

 

三.若何网络保留订阅者

  网络保留订阅者说的简朴点就是一个数据存储的问题,以是也不用太纠结,就将订阅者保持到数组中。

  前面我们说过微博的谁人例子:

    用户kk关注博主MM,对应的就是往数组中添加一个订阅者/元素

    用户kk取关博主MM,可以明白为从数组中移除一个订阅者/元素

    博主MM公布动态,微博客户端自动动态给用户kk,这可以明白为通知数据更新操作。

  

  那上面形貌的一整个内容就是网络保留订阅者需要关注的器械,书中[深入浅出vue.js]把它叫做若何网络依赖

  那么现在就我们说的内容,实现一个类Dep,后面把它称为订阅器,用于治理订阅者/治理依赖。

function Dep(){ this.subs = []; } Dep.prototype.addSub = function(sub){ this.subs.push(sub); } // 添加依赖 Dep.prototype.depend = function() { // 这里可以先不用关注depObject是什么 // 就先暂时明白它是一个订阅者/依赖工具 this.addSub(depObject); } // 移除依赖 Dep.prototype.removeSub = function(sub) { // 源码中是通过抽出来一个remove方式来实现移除的 if(this.subs.length > 0){ var index = this.subs.indexOf(sub); if(index > -1){ // 注重splice的用法 this.subs.splice(index, 1); } } } // 通知数据更新 Dep.prototype.notify = function() { for(var i = 0; i < this.subs.length; i++ ){ // 这里相当于依次挪用subs中每个元素的update方式 // update方式内部实现可以先不用关注,领会其目的就是为了更新数据 this.subs[i].update() } }

 

  依赖网络和治理实现了之后,我们需要思量两个问题:什么时刻添加依赖?什么时刻通知更新数据?

 

  在微博的例子中,用户kk关注博主MM,对应的就是往数组中添加一个订阅者/元素

  那对应到代码中,可以视作接见了工具的属性,那我们就可以在接见工具属性的时刻添加依赖。

 

  博主MM公布动态,微博客户端自动动态给用户kk,这可以明白为通知数据更新操作。

  在对应到代码中,可以视作修改了工具的属性,那我们就可以在修改工具属性的时刻通知数据更新。

  

  这段话可能不是很好明白,以是我们可以去遐想平时我们在vue中的操作:使用双花括号{{text}}在模板的div标签内插入数据。

  这个操作现实上就相当于是模板中的div便签读取而且依赖了vue中的data.text数据,那我们就可以将这个div作为一个依赖工具网络起来。

  之后当text数据发生转变后,我们就需要通知这个div标签更新它内部的数据。

 

  说了这么多,我们刚刚的提的什么时刻添加依赖,什么时刻通知更新数据这个问题就已经有谜底了:

    在get中添加依赖,在set中通知数据更新。

   

  关于添加依赖通知数据更新这两个操作均是Dep这个类的功效,接口分别为:Dep.depend和Dep.notify。

  那现在我们就将Observer这个类举行完善:get中添加依赖,在set中通知数据更新。

/* * obj数据现实上就是vue中的data数据 */ function Observer(obj){ this.obj = obj; if(Array.isArray(this.obj)){ //若是是数组,则会挪用数组的侦测方式 }else{ this.walk(obj); } } Observer.prototype.walk = function(obj) { // 获取obj工具中所有的属性 var keysArr = Object.keys(obj); keysArr.forEach(element =>{ defineReactive(obj, element, obj[element]); }) } // 参照源码,将该方式为自力一个方式 function defineReactive(obj, key, val) { // 若是obj是包罗多层数据属性的工具,就需要递归每一个子属性 if(typeof val === 'object'){ new Observer(val); } var dep = new Dep(); Object.defineProperty(obj, key,{ enumerable: true, configurable: true, get: function(){ // 在get中添加依赖  dep.depend(); return val; }, set: function(newVal) { val = newVal; // 在set中通知数据更新  dep.notify(); } }) }

 

四.若何实现订阅者

  照样前面微博的例子,其中用户KK被视为一个订阅者,vue源码中将界说为Watcher

  那订阅者需要做什么事情呢?

 

  先回首一下我们实现的订阅器Dep

  第一个功效就是添加订阅者。

depend() {
        // 这里可以先不用关注depObject是什么 // 就先暂时明白它是一个订阅者/依赖工具 this.addSub(depObject); }

  可以看到这段代码中那时的注释是“可以先不用关注depObject是什么,暂时明白它是一个订阅者/依赖工具”。

迄今为止最硬核的「Java8时间系统」设计原理与使用方法

  那现在我们就知道depObject现实上就是一个Watcher实例

 

  那若何触发depend方式添加订阅者呢?

  在前面编写侦测数据转变代码时,触发depend方式添加依赖的逻辑在属性的get方式中。

  一起学习vue源码 - Object的转变侦测

  那vue源码的设计是在Watcher初始化的时刻触发数据属性的get方式,即可以将订阅者添加到订阅器

  一起学习vue源码 - Object的转变侦测

  下面将代码贴出来。

/* * vm: vue实例工具 * exp: 属性名 */ function Watcher(vm, exp){ this.vm = vm; this.exp = exp; // 初始化的时刻触发数据属性的get方式,即可以将订阅者添加到订阅器中 this.value = this.get(); } // 触发数据属性的get方式: 接见数据属性即可实现 Watcher.prototype.get = function() { // 接见数据属性逻辑 var value = this.vm.data[this.exp]; return value; }

  

  这里对get方式的逻辑简朴的解读一下:

    数据属性的接见肯定是需要通报数据和对应的属性名才气实现。

    然后我们想一下vue中的data属性是可以使用vue的实例工具“.”操作符举行接见的。

    以是vue在这里设计的时刻没有直接将数据传入,而是通报一个vue实例,使用vue实例.data[‘属性名’]对属性举行接见,从而去触发属性的get方式。

  注重:vue还将接见到的数据属性值保留到了Watcher中value变量中。

 

  到这里,由订阅器Dep的depend方式顺藤摸瓜出来的Watcher的第一个功效就完成了,即:

    Watcher初始化的时刻触发数据属性的get方式,将订阅者添加到订阅器中。

 

  我们在接着摸瓜,看一下订阅器Dep的第二个功效:通知数据更新。

// 通知数据更新 notify() { for(let i = 0; i < this.subs.length; i++ ){ // 这里相当于依次挪用subs中每个元素的update方式 // update方式内部实现可以先不用关注,领会其目的就是为了更新数据 this.subs[i].update() } }

  这段代码最主要的一行:this.subs[i].update(),这行代码现实上触发的是订阅者Watcher实例的update方式

  (由于subs中的每一个元素就是一个订阅者实例)

 

  以是我们的Watcher的第二个功效就是需要实现一个真正包罗更新数据逻辑的update函数

 

  那什么叫真正更新数据的逻辑呢?

  照样vue的双花括号示例:使用双花括号{{text}}在模板的div标签内插入数据。

  当text数据发生转变后,真正更新数据的逻辑就是: div.innerText = newText;

  那Watcher中的update方式我们应该大致领会了。

 

  在说回vue的设计,它将真正更新数据的逻辑封装成一个函数,Watcher实例初始化的时刻通报给Watcher的组织函数,然后在update方式中举行挪用。

   一起学习vue源码 - Object的转变侦测

 

 

 

function Watcher(vm, exp, cb){ this.vm = vm; this.exp = exp; this.cb = cb; // 初始化的时刻触发数据属性的get方式,即可以将订阅者添加到订阅器中 this.value = this.get(); } // 触发数据属性的get方式: 接见数据属性即可实现 Watcher.prototype.get = function() { // 接见数据属性逻辑 var value = this.vm.data[this.exp]; return value; } Watcher.prototype.update = function() { // 当update被触发时,此时获取到的数据属性值是已经被修改事后的新值 var newValue = this.vm.data[this.exp]; // 触发通报给Watcher的更新数据的函数 this.cb.call(this.vm, newValue); }

 

  那简朴的update代码就实现了,不外vue在这里有做小小的优化。

  我们在get方式中接见了数据的属性,并将数据为修改前的初值保留到了this.value中。

  以是update方式的优化就是在执行update后续代码之前,先对this.value和newValue做一个对照,即对旧值和新值作对照。

  只有在新值和旧值不相等的情况下,才会触发cb函数。

Watcher.prototype.update = function() { // 当update被触发时,此时获取到的数据属性值是已经被修改事后的新值 var newValue = this.vm.data[this.exp]; var oldValue = this.value; if(oldValue !== newValue){ // 触发通报给Watcher的更新数据的函数 this.cb.call(this.vm, newValue); } }

 

五.代码弥补

  Watcher中触发数据属性get方式的执行已经弥补完毕,我们在看看订阅器Dep的depend方式。

depend() {
        // 这里可以先不用关注depObject是什么 // 就先暂时明白它是一个订阅者/依赖工具 this.addSub(depObject); }

 

  关于这个depObject我们说过它是一个订阅者,即Watcher的一个实例,那怎么获取Watcher这个实例呢?

  我们转头再看看这个depend触发的流程:

    

   一起学习vue源码 - Object的转变侦测

  即建立Watcher实例,挪用Watcher实例的get方式,从而触发数据属性上界说的get方式,最终触发 dep.depend方式。

 

  以是根据这个流程,在触发数据属性上界说的get方式之前,就必须将Watcher实例准备好。

  我们知道在初始化Watcher时,Watcher内部的this的指向就是Watcher实例。

  以是vue设计的时刻,在Watcher的get方式中把Watcher实例保留到了Dep的target属性上。

  这样Watcher实例化完成后,全局接见Dep.target就能获取到Watcher实例。

  以是现在将Watcher类的get方式举行弥补

// 触发数据属性的get方式: 接见数据属性即可实现 Watcher.prototype.get = function() { // 把Watcher实例保留到了Dep的target属性上 Dep.target = this; // 接见数据属性逻辑 var value = this.vm.data[this.exp]; // 将实例清空释放 Dep.target = null; return value; }

 

  备注:对于get方式中清空释放Dep.target的代码,是有一定缘故原由的。请先继续往下看,把Dep.depend的补全代码看完。

 

  接着我们需要将Dep中的depend方式举行补全。

// 添加依赖 Dep.prototype.depend = function() { // addSub添加的是一个订阅者/依赖工具 // Watcher实例就是订阅者,在Watcher实例初始化的时刻,已经将自己保留到了Dep.target中 if(Dep.target){ this.addSub(Dep.target); } }

 

  现在我在说一下清空释放Dep.target的代码。

  如果我们没有Dep.target = null这行代码,depend方式中也没有if(Dep.target)的判断。

  那第一个订阅者添加完成后是正常的,当数据发生转变后,代码执行逻辑:

    触发数据属性上界说的set方式,

    执行dep.notify

    执行Watcher实例的update方式

    ….

  后面的就不说了,我们看一下这个过程中执行Watcher实例的update方式这一步。

Watcher.prototype.update = function() { // 当update被触发时,此时获取到的数据属性值是已经被修改事后的新值 var newValue = this.vm.data[this.exp]; var oldValue = this.value; if(oldValue !== newValue){ // 触发通报给Watcher的更新数据的函数 this.cb.call(this.vm, newValue); } }

 

  可以看到,update方式中由于在执行真正更新数据的函数cb之前需要获取到新值。

  以是再次接见了数据属性,那可想而知,接见数据属性就会挪用属性的get方式。

  又由于dep.depend的执行没有任何条件判断,导致当前Watcher被植入订阅器两次。

  这显然是不正常的。因此,Dep.target = nullif(Dep.target)的判断是非常必须的步骤。

 

六.完整代码

  现在我们将Observer、Dep、Watcher的完整代码贴出来。

  Observer实现

/* * obj数据现实上就是vue中的data数据 */ function Observer(obj){ this.obj = obj; if(Array.isArray(this.obj)){ //若是是数组,则会挪用数组的侦测方式 }else{ this.walk(obj); } } Observer.prototype.walk = function(obj) { // 获取obj工具中所有的属性 var keysArr = Object.keys(obj); keysArr.forEach(element =>{ defineReactive(obj, element, obj[element]); }) } // 参照源码,将该方式为自力一个方式 function defineReactive(obj, key, val) { // 若是obj是包罗多层数据属性的工具,就需要递归每一个子属性 if(typeof val === 'object'){ new Observer(val); } var dep = new Dep(); Object.defineProperty(obj, key,{ enumerable: true, configurable: true, get: function(){ // 在get中添加依赖  dep.depend(); return val; }, set: function(newVal) { val = newVal; // 在set中通知数据更新  dep.notify(); } }) }

 

  Dep实现

function Dep(){ this.subs = []; } Dep.prototype.addSub = function(sub){ this.subs.push(sub); } // 添加依赖 Dep.prototype.depend = function() { // addSub添加的是一个订阅者/依赖工具 // Watcher实例就是订阅者,在Watcher实例初始化的时刻,已经将自己保留到了Dep.target中 if(Dep.target){ this.addSub(Dep.target); } } // 移除依赖 Dep.prototype.removeSub = function(sub) { // 源码中是通过抽出来一个remove方式来实现移除的 if(this.subs.length > 0){ var index = this.subs.indexOf(sub); if(index > -1){ // 注重splice的用法 this.subs.splice(index, 1); } } } // 通知数据更新 Dep.prototype.notify = function() { for(var i = 0; i < this.subs.length; i++ ){ // 这里相当于依次挪用subs中每个元素的update方式 // update方式内部实现可以先不用关注,领会其目的就是为了更新数据 this.subs[i].update() } }

 

  Watcher实现

function Watcher(vm, exp, cb){ this.vm = vm; this.exp = exp; this.cb = cb; // 初始化的时刻触发数据属性的get方式,即可以将订阅者添加到订阅器中 this.value = this.get(); } // 触发数据属性的get方式: 接见数据属性即可实现 Watcher.prototype.get = function() { // 把Watcher实例保留到了Dep的target属性上 Dep.target = this; // 接见数据属性逻辑 var value = this.vm.data[this.exp]; // 将实例清空释放 Dep.target = null; return value; } Watcher.prototype.update = function() { // 当update被触发时,此时获取到的数据属性值是已经被修改事后的新值 var newValue = this.vm.data[this.exp]; var oldValue = this.value; if(oldValue !== newValue){ // 触发通报给Watcher的更新数据的函数 this.cb.call(this.vm, newValue); } }

七.实践

  要害焦点的代码已经实现完成了,接下来就是使用了。

  由于这个过程中没有模板编译的实现,因此有些代码需要写死。

  回忆vue中双向数据绑定的用法。

  我们先写一段简朴的代码。

<html> <head> <meta charset="utf-8" /> <title>一起学习Vue源码-Object的转变侦测</title> </head> <body> <h1>一起学习Vue源码-Object的转变侦测</h1> <div id="box"> {{text}} </div> </body> <script type="text/javascript" src="./Dep.js"></script> <script type="text/javascript" src="./Observer.js"></script> <script type="text/javascript" src="./Watcher.js"></script> <script type='text/javascript'> /* * data: 数据 * el: 元素 * exp:工具的属性 * (通报这个exp牢固参数也是由于没有模板编译相关的代码,以是就暂时写死一个属性) */ function Vue(data, el, exp){ this.data = data; this.el = el; // 由于没有模板相关的代码,以是{{text}}的值使用这种方式举行剖析 this.innerHTML = this.data[exp]; } var data = { text: 'hello Vue' }; var el = document.getElementById('box'); var vm = new Vue(data, el); </script> </html> 

 

  这段代码运行后,浏览器中已经可以显示{{text}}的值了。

  备注:正常显示并不是由于我们对模板和花括号举行编译,而是使用el.innerHTML = data.text;这种写死的方式实现的。

  

  接着,第一步就是将数据变得可观察,即挪用Observer传入data数据,我们将代码写到Vue组织函数中。

 /* * data: 数据 * el: 元素 * exp:工具的属性 */ function Vue(data, el, exp){ this.data = data; this.el = el; this.exp = exp; // 由于没有模板相关的代码,以是{{text}}的值使用这种方式举行剖析 this.el.innerHTML = this.data[exp]; //初始化vue实例需要将data数据变得可观察 new Observer(data); }

 

  接着,手动为data的text属性建立一个订阅者,代码依然写在vue组织函数中。

  备注:手动建立订阅者也是由于没有模板编译代码,否则建立订阅者正常的逻辑是遍历模板动态建立订阅者。

/* * data: 数据 * el: 元素 * exp:工具的属性 */ function Vue(data, el, exp){ this.data = data; this.el = el; this.exp = exp; // 由于没有模板相关的代码,以是{{text}}的值使用这种方式举行剖析 this.el.innerHTML = this.data[exp]; //初始化vue实例需要将data数据变得可观察 new Observer(data); this.cb = function(newVal){ this.el.innerHTML = newVal; } // 建立一个订阅者 new Watcher(this, exp, this.cb); }

  建立订阅者的时刻有一个cb参数,cb就是我们前面一直说的谁人真正包罗更新数据逻辑的函数

  

  这些操作完成后,最后一步就是修改data.text的数据,若是修改完成后,div的内容发生转变,就证实我们这份代码已经乐成运行了。

  那修改data.text数据的逻辑我借用一个button来实现:监听button的click事宜,触发时将data.text的值改为”hello new vue”。

<html> <head> <meta charset="utf-8" /> <title>一起学习Vue源码-Object的转变侦测</title> </head> <body> <h1>一起学习Vue源码-Object的转变侦测</h1> <div id="box"> {{text}} </div> <br/> <button onclick="btnClick()">点击我改变div的内容</button> </body> <script type="text/javascript" src="./Dep.js"></script> <script type="text/javascript" src="./Observer.js"></script> <script type="text/javascript" src="./Watcher.js"></script> <script> /* * data: 数据 * el: 元素id * exp:工具的属性 * (通报这个exp牢固参数也是由于没有模板编译相关的代码,以是就暂时写死一个属性) * cb: 真正包罗数据更新逻辑的函数 */ function Vue(data, el, exp){ this.data = data; this.el = el; this.exp = exp; // 由于没有模板相关的代码,以是{{text}}的值使用这种方式举行剖析 this.el.innerHTML = this.data[exp]; this.cb = function(newVal){ this.el.innerHTML = newVal; } //初始化vue实例需要将data数据变得可观察 new Observer(data); //建立一个订阅者 new Watcher(this, exp, this.cb); } var data = { text: 'hello Vue' }; var el = document.getElementById('box'); var exp = 'text'; var vm = new Vue(data, el, exp); function btnClick(){ vm.data.text = "hello new vue"; } </script> </html>

  我们看一下效果。

  一起学习vue源码 - Object的转变侦测

  

   可以看到,我们的代码已经乐成运行。

   到此,这篇 “一起学习vue源码 – Object的转变侦测” 总结完成。

 

 

结束语:

 我的vue源码的学习途径主要会参考我自己刚入手的《深入浅出vue.js》这本书,同时会参考网上一些内容。

 我会只管将从源码中解读出的内容,以一种更通俗易懂的方式总结出来。

 若是我的内容能给你带来辅助,可以连续关注我,或者在谈论区指出不足之处。

 同时由于是源码学习,以是这个过程中我也充当一个源码搬运工的角色,不缔造代码只搬运并解读源码。

  

作者:小土豆biubiubiu

博客园:www.cnblogs.com/HouJiao/

掘金:https://juejin.im/user/58c61b4361ff4b005d9e894d

简书:https://www.jianshu.com/u/cb1c3884e6d5

微信民众号:土豆妈的碎碎念(扫码关注,一起吸猫,一起听故事,一起学习前端手艺)

一起学习vue源码 - Object的转变侦测

码字不易,点赞激励哟~

 

原创文章,作者:admin,如若转载,请注明出处:https://www.2lxm.com/archives/640.html