vue底层实现原理_vue底层实现原理

### 几种实现双向绑定的做法

主流的mvc(vm)框架,都实现了单向数据绑定,而双向数据绑定无非就是在单向绑定的基础上给可输入元素(input,textarea)等添加了change(input)事件,来动态修改model和view,并没有多高深。

实现数据绑定的做法

1. 发布者-订阅者(backbone.js)

2. 脏值检查(angular.js)

3. 数据劫持(vue.js)

#### 发布者-订阅者

一般通过sub,pub的方式实现数据和视图的绑定监听,更新数据的方式通常做法 vm.set('property',value),这种方式毕竟太low,我们更希望**vm.property=value这种方式更新数据,同时自动更新视图**。

以下两种方式:

#### 脏值检查

angular.js是通过脏值检测的方式对比数据是否有变更,来决定是否更新视图,最简单的方式就是

**通过setInterval()定时轮询检测数据变动**,当然google不会这么low,**angular只有在指定的事件触发时进入脏值检测**,大致如下:

* DOM事件,譬如用户输入文本,点击按钮等(ng-click)

* XHR响应事件($http)

* 浏览器Location变更事件($location)

* Timer事件($timeout,$interval)

* 执行$digest()或$apply()

### 数据劫持

vue.js采用**数据劫持结合发布者-订阅者模式**的方式,通过**Object.defineProperty()来劫持各个属性的setter和getter,在数据变动时发布消息给订阅者,触发相应的监听回调**。

### 整理思路

vue是通过数据劫持的方式来做数据绑定的,其中最核心的方法便是通过Object.defineProperty()来实现对属性的劫持,达到监听数据变动的目的。

要实现mvvm的双向绑定

1. 实现一个数据监听器Observer,能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者

2. 实现一个指令解析器compile,对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数

3. 实现一个Watcher,作为连接Observer和Compile的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定

### 1. 实现Observer

可以利用Object.defineProperty()来监听属性变动

那么将**需要observe的数据对象进行递归遍历,包括自属性对象的属性,都加上setter和getter**

这样的话,给这个对象的某个值赋值,就会触发setter,那么就能坚挺到了数据变化。

```

var data={name:'hhh'}

observe(data);

function observe(data){

if(!data || typeof data !=='object'){

return;

}

//去除所有属性遍历

Object.keys(data).forEach(function(key){

defineReactive(data,key,data[key]);

});

};

function defineReactive(data,key,val){

observe(val);//监听子属性

Object.defineProperty(data,key,{

enumerable:true,//可枚举

configurable:false,//不能在define

get:function(){

return val;

},

set:function(newVal){

console.log('监听到值的变化了',val,newVal);

val=newVal;

}

})

}

```

这样我们已经可以监听每个数据的变化了,那么监听到变化怎么通知订阅者呢?接下来需要实现一个消息订阅器,很简单,维护一个数组,用来收集订阅者,数据变动触发notify,在调用订阅者的update方法

```

function defineReactive(data, key, val){

var dep = new Dep();

observe(val);

Object.defineProperty(data,key,{

//省略

set:function(newVal){

if(val === newVal) return;

console.log('监听到值的变化了',val,newVal);

val=newVal;

dep.notify();//通知所有订阅者

}

})

}

function Dep(){

this.subs=[];

}

Dep.prototype={

addSub: function(sub){

this.subs.push(sub);

},

notify:function(){

this.subs.forEach(function(sub){

sub.update();

})

}

}

```

那么谁是订阅者?怎么网订阅器里添加订阅者?

上面的思路整理中,我们已经明确订阅者应该是Watcher,而且 var dep = new Dep();是在defineReactive方法内部定义的,所以想通过dep添加订阅者,就必须要在闭包内操作,所以我们在getter里面动手脚

```

// Observer.js

var dep = new Dep();

observe(val); // 监听子属性

Object.defineProperty(data, key, {

get: function(){

//由于需要在闭包内添加watcher,所以通过Dep定义一个全局target属性,暂存watcher,添加完移除

Dep.target && addDep(Dep.target);

return val;

}

})

function Dep() {

this.subs = [];

}

Dep.prototype = {

addSub: function(sub) {

this.subs.push(sub);

},

notify: function() {

this.subs.forEach(function(sub) {

sub.update();

});

}

};

//Watcher.js

Watcher.prototype = {

get:function(key){

Dep.target = this;

this.value = data[key];//这里会触发属性的getter,从而添加订阅者

Dep.target = null

}

}

```

这里已经实现了一个Observer了,已经具备了监听数据和数据变化通知订阅者的功能。

### 2. 实现Compile

compile:解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图。

因为遍历接卸的过程有多次操作dom,为了提高效率,将根节点el转换成碎片fragment进行接卸编译操作,解析完成,在将fragment添加回原来的真是dom节点中

#### Object.defineProperty()方法

会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象

`Object.defineProperty(obj, prop, descriptor)`

descriptor:将被定义或修改的属性描述符

返回值:传递给函数的对象。

描述:该方法允许精确添加或修改对象的属性。通过赋值操作添加的普通属性是可枚举的(for in,Object.keys),这些属性的值可以被改变,也可以被删除,这个**方法允许修改默认的额外选项(配置)**。默认情况下,使用Object.defineProperty添加的属性是不可修改的。

#### 属性描述符

**数据描述符和存取描述符**

数据描述符:是一个具有值的属性,该值可能是可写的,也可能不是可写的。

存取描述符:有getter-setter函数对描述的属性。

描述符必须是这两种形式之一。

数据描述符和存取描述符均具有以下可选键值:

configurable:true,**true时,该属性描述符才能够被改变**,默认false。

enumerable:**true时,该属性才能够出现对象的枚举属性中**。默认false。

**数据描述符**同时具有以下可选键值:

value:属性值,默认undefined

writable:true时,value才能被赋值运算符改变,默认false。

**存取描述符**同时具有以下可选键值:

get:一个给属性提供getter的方法**,如果没有getter则为undefined**,当访问该属性时,改方法会被执行,方法执行时没有参数传入,但是会传入this对象(this并以一定是定义改属性的对象)。

set:一个给属性提供setter的方法,如果没有setter则为undefined,当属性值修改时,触发改方法。

描述符可同时具有的键值

如果一个描述符**不具有value,writable,get 和 set 任意一个关键字,那么它将被认为是一个数据描述符**。如果一个描述符同时有(value或writable)和(get或set)关键字,将会产生**一个异常**。

记住:这些选项不一定是自身属性,如果是继承来的也要考虑。

为了确认保留这些默认值,你可能要在这之前**冻结 Object.prototype**,明确指定所有的选项,或者通过 **Object.create(null)将__proto__属性指向null**。


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部