谈谈js的双向绑定

2017-04-26  本文已影响0人  文舞双全

实现的做法

  • 发布订阅者模式
  • 脏值检查
  • 数据劫持

先从一个简单的开始



letmvvm={};

Object.defineProperty(mvvm,'hello', {

get:function() {

returndocument.getElementsByTagName("input")[0].value;

},

set:function(val) {

document.getElementsByTagName("input")[0].value=val;

document.getElementsByTagName('span')[0].innerHTML=val;

console.log("set被调用了,参数是:", val)

}

})

document.getElementsByTagName("input")[0].addEventListener('keyup',function(e) {

mvvm.hello=e.target.value;

})

这里利用了Object.defineProperty()里的getter setter方法,在修改mvvm对象上的hello属性时候,我们可以做一些事情,当触发set的时候,把input,span的值都修改成新的val,然后在input上监听keyupevent, 触发keyup,将mvvm.hello设置为当前事件对象的value,同时触发了set,改变了input,span的值

问题来了

在上例中,我们只是监听了input的keyup事件,而且只是绑定了mvvm.如果有很多obj呢,如果不只是input呢,如果不只是input的keyup呢,所以我们需要做一个

通用的模式

Vue的数据绑定实现


Two-way data-binding

{{ text }}

function observe(obj, vm) {

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

defineReactive(vm, key, obj[key]);

});

}

function defineReactive(obj, key, val) {

var dep=newDep();

Object.defineProperty(obj, key, {

get:function() {

// 添加订阅者watcher到主题对象Dep

if(Dep.target) dep.addSub(Dep.target);

returnval

},

set:function(newVal) {

if(newVal===val)return

val=newVal;

// 作为发布者发出通知

dep.notify();

}

});

}

function nodeToFragment(node, vm) {

var flag=document.createDocumentFragment();

var child;

while(child=node.firstChild) {

compile(child,vm);

flag.append(child);// 将子节点劫持到文档片段中

}

return flag;

}

function compile(node, vm) {

var reg=/\{\{(.*)\}\}/;

// 节点类型为元素

if(node.nodeType===1) {

var attr = node.attributes;

// 解析属性

for(vari=0;i

if(attr[i].nodeName=='v-model') {

varname=attr[i].nodeValue;// 获取v-model绑定的属性名

node.addEventListener('input',function(e) {

// 给相应的data属性赋值,进而触发该属性的set方法

vm[name]=e.target.value;

});

node.value=vm[name];// 将data的值赋给该node

node.removeAttribute('v-model');

}

};

newWatcher(vm,node,name, 'input');

}

// 节点类型为text

if(node.nodeType===3) {

if(reg.test(node.nodeValue)) {

varname=RegExp.$1;// 获取匹配到的字符串

name=name.trim();

newWatcher(vm, node, name,'text');

}

}

}

function Watcher(vm, node, name, nodeType) {

Dep.target=this;

this.name=name;

this.node=node;

this.vm=vm;

this.nodeType=nodeType;

this.update();

Dep.target=null;

}

Watcher.prototype = {

update:function() {

this.get();

if(this.nodeType=='text') {

this.node.nodeValue=this.value;

}

if(this.nodeType=='input') {

this.node.value=this.value;

}

},

// 获取data中的属性值

get:function() {

this.value=this.vm[this.name];// 触发相应属性的get

}

}

function Dep() {

this.subs=[]

}

Dep.prototype = {

addSub:function(sub) {

this.subs.push(sub);

},

notify:function() {

this.subs.forEach(function(sub) {

sub.update();

});

}

};

function Vue(options) {

this.data=options.data;

var data=this.data;

observe(data, this);

var id=options.el;

var dom=nodeToFragment(document.getElementById(id), this);

// 编译完成后,将dom返回到app中

document.getElementById(id).appendChild(dom);

}

var vm = new Vue({

el:'app',

data: {

text:'hello world'

}

});

上一篇 下一篇

猜你喜欢

热点阅读