做者:小土豆biubiubiujavascript
博客园:www.cnblogs.com/HouJiao/html
简书:www.jianshu.com/u/cb1c3884e…vue
微信公众号:土豆妈的碎碎念(扫码关注,一块儿吸猫,一块儿听故事,一块儿学习前端技术)java
码字不易,点赞鼓励哟~面试
一块儿学习vue源码的第一篇来说讲vue双向数据绑定的源码实现。设计模式
vue中的双向数据绑定主要是经过变化侦测
这种方式去实现的,这篇文章主要总结的是Object的变化侦测
。数组
vue双向数据绑定中Object和Array的实现原理是不同的浏览器
咱们在面试的时候,若是面试者的技术栈包含vue
框架,那么面试官会有很大的概率甩出“你了解vue中双向数据绑定的原理吗”这个问题。我也听过一些回答,你们通常都能说出一个词叫发布-订阅
。那在深刻去问的时候,或者说你能不能给我实现一个简单的双向数据绑定,基本就回答不上来了。微信
说到这里我已经抛出三个名词了:双向数据绑定
、变化侦测
、发布-订阅
。
前面说过双向数据绑定就是经过变化侦测这种方式去实现的。那这里的发布-订阅
我理解是软件的设计思想,它比变化侦测更深一层,已经到了代码的设计模式这一层了。
因此咱们能够说双向数据绑定就是经过变化侦测这种方式去实现的,也能够说双向数据绑定是经过发布-订阅这种模式去实现的。我我的以为二者说法都没有问题,只是描述方式不同。
那不论是叫变化侦测仍是发布-订阅,有一些实际生活中的例子能够便于咱们理解它们。
后面的不少描述都会混用这两个名词,不用纠结叫法,了解说的是同一个东西便可
好比咱们常常玩的微博:
有一个用户kk很喜欢某个博主MM,而后就在微博上关注了博主MM。
以后每一次博主MM在微博上发表一些吃吃喝喝的动态,微博客户端都会主动将动态推送给用户kk。
在过了一段时间,博主MM爆出一个很差的新闻,用户kk便将博主MM的微博取关了。
复制代码
在这个实际场景中,咱们能够称博主MM
是一个发布者
。用户kk
是一个订阅者
。微博客户端
就是一个管理者
的角色,它时刻侦测这博主MM的动态,在博主MM更新动态时主动将动态推送给订阅者。
基于咱们前面这些,想来你们应该能理解发布订阅/变化侦测
大体的设计思想和须要关注的几个点了:
1.如何侦测数据的变化(或者说如何侦测到发布者的发布的内容)
2.如何收集保存订阅者(也就是微博客户端这个管理者的角色如何实现)。
3.订阅者如何实现。
复制代码
接着就咱们总结的这几个点逐个去解读vue的源码实现。
看过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中,执行后控制台的打印结果以下:
能够看到,当咱们修改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方法中。
到这里,相信你们已经很轻松能写出代码了。
<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是什么,暂时理解它是一个订阅者/依赖对象
。
那如今咱们就知道depObject
实际上就是一个Watcher
实例。那如何触发depend
方法添加订阅者呢?
在前面编写侦测数据变化代码时,触发depend
方法添加依赖的逻辑在属性的get
方法中。
那vue源码的设计是在Watcher
初始化的时候触发数据属性的get
方法,便可以将订阅者添加到订阅器中。
下面将代码贴出来。
/* * 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
方法中进行调用。
代码实现以下。
/* * vm: vue实例对象 * exp: 对象的属性 * cb: 真正包含数据更新逻辑的函数 */
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);
}
}
复制代码
到这里呢,咱们遗漏了一个逻辑,先看看前面实现的订阅器Dep
的depend
方法。
depend() {
// 这里能够先不用关注depObject是什么
// 就先暂时理解它是一个订阅者/依赖对象
this.addSub(depObject);
}
复制代码
关于这个depObject
咱们说过它是一个订阅者
,即Watcher
的一个实例,那如何获取Watcher这个实例
呢?
咱们回头再看看这个depend
方法的触发流程:
即建立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.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 = null
和if(Dep.target)
的判断是很是必须的步骤。
如今咱们将Observer
、Dep
、Watcher
的完整代码贴出来。
/* * 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();
}
})
}
复制代码
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()
}
}
复制代码
/* * vm: vue实例对象 * exp: 对象的属性 * cb: 真正包含数据更新逻辑的函数 */
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的双向数据绑定源码实现梳理完毕。
结束语:
个人vue源码的学习途径主要会参考我本身刚入手的《深刻浅出vue.js》这本书,同时会参考网上一些内容。
我会尽可能将从源码中解读出的内容,以一种更通俗易懂的方式总结出来。
若是个人内容能给你带来帮助,能够持续关注我,或者在评论区指出不足之处。
同时由于是源码学习,因此这个过程当中我也充当一个源码搬运工的角色,不创造代码只搬运并解读源码。
复制代码
做者:小土豆biubiubiu
简书:www.jianshu.com/u/cb1c3884e…
微信公众号:土豆妈的碎碎念(扫码关注,一块儿吸猫,一块儿听故事,一块儿学习前端技术)
码字不易,点赞鼓励哟~ ![]()