Vue.js双向绑定的实现原理和模板引擎实现原理(##########################################)

时间:2023-03-09 00:56:53
Vue.js双向绑定的实现原理和模板引擎实现原理(##########################################)

Vue.js双向绑定的实现原理

解析 神奇的 Object.defineProperty

这个方法了不起啊。。vue.js和avalon.js 都是通过它实现双向绑定的。。而且Object.observe也被草案发起人撤回了。。所以defineProperty更有必要了解一下了几行代码看他怎么用

    var a= {}
Object.defineProperty(a,"b",{
value:123
})
console.log(a.b);//123
很简单,,它接受三个参数,而且都是必填的。。 传入参数
第一个参数:目标对象 第二个参数:需要定义的属性或方法的名字。 第三个参数:目标属性所拥有的特性。(descriptor) 前两个参数不多说了,一看代码就懂,主要看第三个参数descriptor,看看有哪些取值 descriptor
他又以下取值,我们简单认识一下,后面例子,挨个介绍, value:属性的值(不用多说了) writable:如果为false,属性的值就不能被重写,只能为只读了 configurable:总开关,一旦为false,就不能再设置他的(value,writable,configurable) enumerable:是否能在for...in循环中遍历出来或在Object.keys中列举出来。 get:一会细说 set:一会细说 descriptor默认值
我们再看看第一个例子 var a= {}
Object.defineProperty(a,"b",{
value:123
})
console.log(a.b);//123
我们只设置了 value,别的并没有设置,但是 第一次的时候可以简单的理解为(暂时这样理解)它会默认帮我们把writable,configurable,enumerable。都设上值,而且值还都是false。。也就是说,上面代码和下面是等价的的( 仅限于第一次设置的时候) var a= {}
Object.defineProperty(a,"b",{
value:123,
writable:false,
enumerable:false,
configurable:false
})
console.log(a.b);//123
以上非常重要哦。。并且以上理解对set 和 get 不起作用哦
configurable
总开关,第一次设置 false 之后,,第二次什么设置也不行了,比如说 var a= {}
Object.defineProperty(a,"b",{
configurable:false
})
Object.defineProperty(a,"b",{
configurable:true
})
//error: Uncaught TypeError: Cannot redefine property: b
就会报错了。。注意上面讲的默认值。。。如果第一次不设置它会怎样。。会帮你设置为false。。所以。。第二次。再设置他会怎样?。。对喽,,会报错 writable
如果设置为fasle,就变成只读了。。 var a = {}; Object.defineProperty(o, "b", {
value : 123,
writable : false }); console.log(a.b); // 打印 37
a.b = 25; // 没有错误抛出(在严格模式下会抛出,即使之前已经有相同的值)
console.log(o.a); // 打印 37, 赋值不起作用。
enumerable
属性特性 enumerable 定义了对象的属性是否可以在 for...in 循环和 Object.keys() 中被枚举。 var a= {}
Object.defineProperty(a,"b",{
value:3445,
enumerable:true
})
console.log(Object.keys(a));// 打印["b"]
改为false var a= {}
Object.defineProperty(a,"b",{
value:3445,
enumerable:false //注意咯这里改了
})
console.log(Object.keys(a));// 打印[]
for...in 类似,,不赘述了 set 和 get
在 descriptor 中不能 同时设置访问器 (get 和 set) 和 wriable 或 value,否则会错,就是说想用(get 和 set),就不能用(wriable 或 value中的任何一个) set 和 get ,他俩干啥用的的, var a= {}
Object.defineProperty(a,"b",{
set:function(newValue){
console.log("你要赋值给我,我的新值是"+newValue)
},
get:function(){
console.log("你取我的值")
return 2 //注意这里,我硬编码返回2
}
})
a.b =1 //打印 你要赋值给我,我的新值是1
console.log(a.b) //打印 你取我的值
//打印 2 注意这里,和我的硬编码相同的
简单来说,, 这个 “b” 赋值 或者 取值的时候会分别触发 set 和 get 对应的函数 这就是实现 observe的关键啊。。下一篇,,我会分析vue的observe的实现源码,聊聊自己如何一步一步实现$watch var a = {
b: {
c:1
},
d:1 }
a.$watch("b.c",()=>console.log("哈哈哈"))

  

Object.keys(obj)返回参数obj可被枚举的属性:

示例一:
function Pasta(grain, width, shape) {
this.grain = grain;
this.width = width;
this.shape = shape;
this.toString = function () {
return (this.grain + ", " + this.width + ", " + this.shape);
}
} console.log(Object.keys(Pasta)); //console: []
var spaghetti = new Pasta("wheat", 0.2, "circle");
console.log(Object.keys(spaghetti)); //console: ["grain", "width", "shape", "toString"] 示例二: var arr = ["a", "b", "c"];
console.log(Object.keys(arr)); // console: ["0", "1", "2"] var obj = { 0 : "a", 1 : "b", 2 : "c"};
console.log(Object.keys(obj)); // console: ["0", "1", "2"] var an_obj = { 100: "a", 2: "b", 7: "c"};
console.log(Object.keys(an_obj)); // console: ["2", "7", "100"] var my_obj = Object.create({}, { getFoo : { value : function () { return this.foo } } });
my_obj.foo = 1; console.log(Object.keys(my_obj)); // console: ["foo"]

  

Vue.js最核心的功能有两个,一是响应式的数据绑定系统,二是组件系统。本文仅探究几乎所有Vue的开篇介绍都会提到的hello world双向绑定是怎样实现的。先讲涉及的知识点,再参考源码,用尽可能少的代码实现那个hello world开篇示例。

  参考文章:https://segmentfault.com/a/1190000006599500

一、访问器属性

访问器属性是对象中的一种特殊属性,它不能直接在对象中设置,而必须通过defineProperty()方法单独定义。

var obj = { };

// 为obj定义一个名为hello的访问器属性

Object.defineProperty(obj, "hello", {

get: function () {return sth},

set: function (val) {/* do sth */}

})

obj.hello // 可以像普通属性一样读取访问器属性

访问器属性的"值"比较特殊,读取或设置访问器属性的值,实际上是调用其内部特性:get和set函数。

obj.hello // 读取属性,就是调用get函数并返回get函数的返回值

obj.hello = "abc" // 为属性赋值,就是调用set函数,赋值其实是传参

Vue.js双向绑定的实现原理和模板引擎实现原理(##########################################)

get和set方法内部的this都指向obj,这意味着get和set函数可以操作对象内部的值。另外,访问器属性的会"覆盖"同名的普通属性,因为访问器属性会被优先访问,与其同名的普通属性则会被忽略(也就是所谓的被"劫持"了)。

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<script type="text/javascript">
var obj={}
Object.defineProperty(obj,"sb",{
get:function(val){
console.log("get方法执行了"+val);
},
set:function(val){
console.log("set方法执行了:"+val);
}
})
console.log(obj.sb);
obj.sb="这就是个傻B"
</script>
</head>
<body>
</body>
</html>

 Vue.js双向绑定的实现原理和模板引擎实现原理(##########################################) 

二、极简双向绑定的实现

Vue.js双向绑定的实现原理和模板引擎实现原理(##########################################)

此例实现的效果是:随文本框输入文字的变化,span中会同步显示相同的文字内容;在js或控制台显式的修改obj.name的值,视图会相应更新。这样就实现了model =>view以及view => model的双向绑定,并且是响应式的。

Vue.js双向绑定的实现原理和模板引擎实现原理(##########################################)

以上就是Vue实现双向绑定的基本原理。

<!DOCTYPE html>
<html> <head>
<meta charset="UTF-8">
<title></title>
<script type="text/javascript">
window.onload = function() { window.obj = {}
Object.defineProperty(obj, "sb", {
get: function(val) {
console.log("get方法执行了" + val);
},
set: function(val) {
document.getElementById("sb").value=val;
document.getElementById("test").innerText=val;
}
})
document.getElementById("sb").oninput = function() {
obj.sb=this.value;
}
}
</script>
</head> <body>
<input type="text" name="" id="sb" value="" />
<span id="test"></span>
</body> </html>

 Vue.js双向绑定的实现原理和模板引擎实现原理(##########################################)  

三、分解任务

上述示例仅仅是为了说明原理。我们最终要实现的是:

Vue.js双向绑定的实现原理和模板引擎实现原理(##########################################)

Vue.js双向绑定的实现原理和模板引擎实现原理(##########################################)

首先将该任务分成几个子任务:

   1、输入框以及文本节点与data中的数据绑定

   2、输入框内容变化时,data中的数据同步变化。即view => model的变化。

   3、data中的数据变化时,文本节点的内容同步变化。即model => view的变化。

要实现任务一,需要对DOM进行编译,这里有一个知识点:DocumentFragment。

四、DocumentFragment

DocumentFragment(文档片段)可以看作节点容器,它可以包含多个子节点,当我们将它插入到DOM中时,只有它的子节点会插入目标节点,所以把它看作一组节点的容器。使用DocumentFragment处理节点,速度和性能远远优于直接操作DOM。Vue进行编译时,就是将挂载目标的所有子节点劫持(真的是劫持)到DocumentFragment中,经过一番处理后,再将DocumentFragment整体返回插入挂载目标。

Vue.js双向绑定的实现原理和模板引擎实现原理(##########################################)

Vue.js双向绑定的实现原理和模板引擎实现原理(##########################################)

五、数据初始化绑定

Vue.js双向绑定的实现原理和模板引擎实现原理(##########################################)

Vue.js双向绑定的实现原理和模板引擎实现原理(##########################################)

Vue.js双向绑定的实现原理和模板引擎实现原理(##########################################)

Vue.js双向绑定的实现原理和模板引擎实现原理(##########################################)

以上代码实现了任务一,我们可以看到,hello world已经呈现在输入框和文本节点中。

Vue.js双向绑定的实现原理和模板引擎实现原理(##########################################)

六、响应式的数据绑定

再来看任务二的实现思路:当我们在输入框输入数据的时候,首先触发input事件(或者keyup、change事件),在相应的事件处理程序中,我们获取输入框的value并赋值给vm实例的text属性。我们会利用defineProperty将data中的text劫持为vm的访问器属性,因此给vm.text赋值,就会触发set方法。在set方法中主要做两件事,第一是更新属性的值,第二留到任务三再说。

Vue.js双向绑定的实现原理和模板引擎实现原理(##########################################)

Vue.js双向绑定的实现原理和模板引擎实现原理(##########################################)

任务二也就完成了,text属性值会与输入框的内容同步变化:

Vue.js双向绑定的实现原理和模板引擎实现原理(##########################################)

七、订阅/发布模式(subscribe&publish)

       text属性变化了,set方法触发了,但是文本节点的内容没有变化。如何让同样绑定到text的文本节点也同步变化呢?这里又有一个知识点:订阅发布模式。

订阅发布模式(又称观察者模式)定义了一种一对多的关系,让多个观察者同时监听某一个主题对象,这个主题对象的状态发生改变时就会通知所有观察者对象。

发布者发出通知 => 主题对象收到通知并推送给订阅者 => 订阅者执行相应操作

Vue.js双向绑定的实现原理和模板引擎实现原理(##########################################)

之前提到的,当set方法触发后做的第二件事就是作为发布者发出通知:“我是属性text,我变了”。文本节点则是作为订阅者,在收到消息后执行相应的更新操作。

八、双向绑定的实现

回顾一下,每当new一个Vue,主要做了两件事:第一个是监听数据:observe(data),第二个是编译HTML:nodeToFragement(id)。

在监听数据的过程中,会为data中的每一个属性生成一个主题对象dep。

在编译HTML的过程中,会为每个与数据绑定相关的节点生成一个订阅者watcher,watcher会将自己添加到相应属性的dep中。

我们已经实现:修改输入框内容 => 在事件回调函数中修改属性值 => 触发属性的set方法。

接下来我们要实现的是:发出通知dep.notify() => 触发订阅者的update方法 => 更新视图。

这里的关键逻辑是:如何将watcher添加到关联属性的dep中。

Vue.js双向绑定的实现原理和模板引擎实现原理(##########################################)

在编译HTML过程中,为每个与data关联的节点生成一个Watcher。Watcher函数中发生了什么呢?

Vue.js双向绑定的实现原理和模板引擎实现原理(##########################################)

首先,将自己赋给了一个全局变量Dep.target;

其次,执行了update方法,进而执行了get方法,get的方法读取了vm的访问器属性,从而触发了访问器属性的get方法,get方法中将该watcher添加到了对应访问器属性的dep中;

再次,获取属性的值,然后更新视图。

最后,将Dep.target设为空。因为它是全局变量,也是watcher与dep关联的唯一桥梁,任何时刻都必须保证Dep.target只有一个值。

Vue.js双向绑定的实现原理和模板引擎实现原理(##########################################)

Vue.js双向绑定的实现原理和模板引擎实现原理(##########################################)

Vue.js双向绑定的实现原理和模板引擎实现原理(##########################################)

至此,hello world双向绑定就基本实现了。文本内容会随输入框内容同步变化,在控制器中修改vm.text的值,会同步反映到文本内容中。

  完整代码:https://github.com/bison1994/two-way-data-binding

JavaScript 模板引擎实现原理解析

1、入门实例

首先我们来看一个简单模板:

Vue.js双向绑定的实现原理和模板引擎实现原理(##########################################)
  <script type="template" id="template">
<h2>
<a href="{{href}}">
{{title}}
</a>
</h2>
<img src="{{imgSrc}}" alt="{{title}}">
</script>
Vue.js双向绑定的实现原理和模板引擎实现原理(##########################################)

其中被{{ xxx }}包含的就是我们要替换的变量。
接着我们可能通过ajax或者其他方法获得数据。这里我们自己定义了数据,具体如下:

Vue.js双向绑定的实现原理和模板引擎实现原理(##########################################)
var data = [
{
title: "Create a Sticky Note Effect in 5 Easy Steps with CSS3 and HTML5",
href: "http://net.tutsplus.com/tutorials/html-css-techniques/create-a-sticky-note-effect-in-5-easy-steps-with-css3-and-html5/",
imgSrc: "https://d2o0t5hpnwv4c1.cloudfront.net/771_sticky/sticky_notes.jpg"
},
{
title: "Nettuts+ Quiz #8",
href: "http://net.tutsplus.com/articles/quizzes/nettuts-quiz-8-abbreviations-darth-sidious-edition/",
imgSrc: "https://d2o0t5hpnwv4c1.cloudfront.net/989_quiz2jquerybasics/quiz.jpg"
}
];
Vue.js双向绑定的实现原理和模板引擎实现原理(##########################################)

ok,现在的问题就是我们怎么把数据导入到模板里面呢?

第一种大家会想到的就是采用replace直接替换里面的变量:

Vue.js双向绑定的实现原理和模板引擎实现原理(##########################################)
template = document.querySelector('#template').innerHTML,
result = document.querySelector('.result'),
i = 0, len = data.length,
fragment = ''; for ( ; i < len; i++ ) {
fragment += template
.replace( /\{\{title\}\}/, data[i].title )
.replace( /\{\{href\}\}/, data[i].href )
.replace( /\{\{imgSrc\}\}/, data[i].imgSrc );
} result.innerHTML = fragment;
Vue.js双向绑定的实现原理和模板引擎实现原理(##########################################)

第二种的话,相对第一种比较灵活,采用的是正则替换,对于初级前端,很多人对正则掌握的并不是很好,一般也用的比较少。具体实现如下:

Vue.js双向绑定的实现原理和模板引擎实现原理(##########################################)
template = document.querySelector('#template').innerHTML,
result = document.querySelector('.result'),
attachTemplateToData; // 将模板和数据作为参数,通过数据里所有的项将值替换到模板的标签上(注意不是遍历模板标签,因为标签可能不在数据里存在)。
attachTemplateToData = function(template, data) {
var i = 0,
len = data.length,
fragment = ''; // 遍历数据集合里的每一个项,做相应的替换
function replace(obj) {
var t, key, reg;
      
       //遍历该数据项下所有的属性,将该属性作为key值来查找标签,然后替换
for (key in obj) {
reg = new RegExp('{{' + key + '}}', 'ig');
t = (t || template).replace(reg, obj[key]);
} return t;
} for (; i < len; i++) {
fragment += replace(data[i]);
} return fragment;
}; result.innerHTML = attachTemplateToData(template, data);
Vue.js双向绑定的实现原理和模板引擎实现原理(##########################################)

与第一种相比较,第二种代码看上去多了,但是功能实则更为强大了。第一种我们需要每次重新编写变量名,如果变量名比较多的话,会比较麻烦,且容易出错。第二种的就没有这些烦恼。

2、模板引擎相关知识

通过上面的例子,大家对模板引擎应该有个初步的认识了,下面我们来讲解一些相关知识。

2.1 模板存放

模板一般都是放置到 textarea/input 等表单控件,或者 script 等标签中。比如上面的例子,我们就是放在 script 标签上的。

2.2 模板获取

一般都是通过ID来获取,document.getElementById(“ID”):

//textarea或input则取value,其它情况取innerHTML
var html = /^(textarea|input)$/i.test(element.nodeName) ? element.value : element.innerHTML;

上面的是通用的模板获取方法,这样不管你是放在 textarea/input 还是 script 标签下都可以获取到。

2.3 模板函数

一般都是templateFun("id", data);其中id为存放模板字符串的元素id,data为需要装载的数据。

2.4 模板解析编译

模板解析主要是指将模板中 JavaScript 语句和 html 分离出来,编译的话将模板字符串编译成最终的模板。上面的例子比较简单,还没有涉及到模板引擎的核心。

2.5 模板解析编译

要指出的是,不同的模板引擎所用的分隔符可能是不一样,上面的例子用的是{{ }},而Jquery tmpl 使用的是<%  %>。

3、jQuery tmpl 实现原理解析

jQuery tmpl是由jQuery的作者写的,代码短小精悍。总共20多行,功能却比我们上面的强大很多。我们先来看一看源码:

Vue.js双向绑定的实现原理和模板引擎实现原理(##########################################)
(function(){
var cache = {}; this.tmpl = function tmpl(str, data){ var fn = !/\W/.test(str) ?
cache[str] = cache[str] ||
tmpl(document.getElementById(str).innerHTML) : new Function("obj",
"var p=[],print=function(){p.push.apply(p,arguments);};" + "with(obj){p.push('" + str
.replace(/[\r\t\n]/g, " ")
.split("<%").join("\t")
.replace(/((^|%>)[^\t]*)'/g, "$1\r")
.replace(/\t=(.*?)%>/g, "',$1,'")
.split("\t").join("');")
.split("%>").join("p.push('")
.split("\r").join("\\'")
+ "');}return p.join('');"); return data ? fn( data ) : fn;
};
})();
Vue.js双向绑定的实现原理和模板引擎实现原理(##########################################)

初看是不是觉得有点懵,完全不能理解的代码。没事,后面我们会对源码进行解释的,我们还是先看一下所用的模板

  <ul>
<% for ( var i = 0; i < users.length; i++ ) { %>
<li><a href="<%=users[i].url%>"><%=users[i].name%></a></li>
<% } %>
</ul>

可以发现,这个模板比入门例子的模板更为复杂,因为里面还夹杂着 JavaScript 代码。JavaScript 代码采用 <%  %> 包含。而要替换的变量则是用 <%=   %> 分隔开的。

下面我再来对代码做个注释。不过即使看了注释,你也不一定能很快理解,最好的办法是自己实际动手操作一遍。

Vue.js双向绑定的实现原理和模板引擎实现原理(##########################################)
// 代码整个放在一个立即执行函数里面
(function(){
// 用来缓存,有时候一个模板要用多次,这时候,我们直接用缓存就会很方便
var cache = {};

// tmpl绑定在this上,这里的this值得是window
this.tmpl = function tmpl(str, data){

// 只有模板才有非字母数字字符,用来判断传入的是模板id还是模板字符串,
// 如果是id的话,判断是否有缓存,没有缓存的话调用tmpl;
// 如果是模板的话,就调用new Function()解析编译
var fn = !/\W/.test(str) ?
cache[str] = cache[str] ||
tmpl(document.getElementById(str).innerHTML) : new Function("obj",
     // 注意这里整个是字符串,通过 + 号拼接
"var p=[],print=function(){p.push.apply(p,arguments);};" +
"with(obj){p.push('" + str
      // 去除换行制表符\t\n\r
.replace(/[\r\t\n]/g, " ")
      
      // 将左分隔符变成 \t
.split("<%").join("\t")
      
      // 去掉模板中单引号的干扰
.replace(/((^|%>)[^\t]*)'/g, "$1\r")
      
      // 为 html 中的变量变成 ",xxx," 的形式, 如:\t=users[i].url%> 变成 ',users[i].url,'
      // 注意这里只有一个单引号,还不配对
.replace(/\t=(.*?)%>/g, "',$1,'")
      
      // 这时候,只有JavaScript 语句前面才有 "\t", 将 \t 变成 ');
      // 这样就可把 html 标签添加到数组p中,而javascript 语句 不需要 push 到里面。
      .split("\t").join("');")
      
      // 这时候,只有JavaScript 语句后面才有 "%>", 将 %> 变成 p.push('
      // 上一步我们再 html 标签后加了 ');, 所以要把 p.push(' 语句放在 html 标签放在前面,这样就可以变成 JavaScript 语句
.split("%>").join("p.push('")

      // 将上面可能出现的干扰的单引号进行转义
      .split("\r").join("\\'")
    // 将数组 p 变成字符串。
+ "');}return p.join('');"); return data ? fn( data ) : fn;
};
})();
Vue.js双向绑定的实现原理和模板引擎实现原理(##########################################)

上面代码中,有一个要指出的就是new Function 的使用 方法。给 new Function() 传一个字符串作为函数的body来构造一个 JavaScript函数。编程中并不经常用到,但有时候应该是很有用的。

下面是 new Function 的基本用法:

// 最后一个参数是函数的 body(函数体),类型为 string;
// 前面的参数都是 索要构造的函数的参数(名字)
var myFunction = new Function('users', 'salary', 'return users * salary');

最后的字符串就是下面这种形式:

Vue.js双向绑定的实现原理和模板引擎实现原理(##########################################)
  var p = [],
print = function() {
p.push.apply(p, arguments);
};
with(obj) {
p.push(' <ul> ');
for (var i = 0; i < users.length; i++) {
p.push(' <li><a href="', users[i].url, '">', users[i].name, '</a></li> ');
}
p.push(' </ul> ');
}
return p.join('');
Vue.js双向绑定的实现原理和模板引擎实现原理(##########################################)

里面的 print 函数 在我们的模板里面是没有用到的。

要指出的是,采用 push 的方法在 IE6-8 的浏览器下会比 += 的形式快,但是在现在的浏览器里面, += 是拼接字符串最快的方法。实测表明现代浏览器使用 += 会比数组 push 方法快,而在 v8 引擎中,使用 += 方式比数组拼接快 4.7 倍。所以 目前有些更高级的模板引擎会 根据 javascript 引擎特性采用了两种不同的字符串拼接方式。

下面的代码是摘自腾讯的 artTemplate 的, 根据浏览器的类型来选择不同的拼接方式。功能越强大,所考虑的问题也会更多。

    var isNewEngine = ''.trim;// '__proto__' in {}
var replaces = isNewEngine
? ["$out='';", "$out+=", ";", "$out"]
: ["$out=[];", "$out.push(", ");", "$out.join('')"];

挑战:有兴趣的可以改用 += 来实现上面的代码。

总结

模板引擎原理总结起来就是:先获取html中对应的id下得innerHTML,利用开始标签和关闭标签进行字符串切分,其实是将模板划分成两部份内容,一部分是html部分,一部分是逻辑部分,通过区别一些特殊符号比如each、if等来将字符串拼接成函数式的字符串,将两部分各自经过处理后,再次拼接到一起,最后将拼接好的字符串采用new Function()的方式转化成所需要的函数。

目前模板引擎的种类繁多,功能也越来越强大,不同模板间实现原理大同小异,各有优缺,请按需选择。

vue双向数据绑定原理探究(附demo)

传送门

双向绑定的思想

双向数据绑定的思想就是数据层与UI层的同步,数据再两者之间的任一者发生变化时都会同步更新到另一者。

双向绑定的一些方法

目前,前端实现数据双向数据绑定的方法大致有以下三种:

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

思路:使用自定义的data属性在HTML代码中指明绑定。所有绑定起来的JavaScript对象以及DOM元素都将“订阅”一个发布者对象。任何时候如果JavaScript对象或者一个HTML输入字段被侦测到发生了变化,我们将代理事件到发布者-订阅者模式,这会反过来将变化广播并传播到所有绑定的对象和元素。

2.赃值检测(angular.js)

思路:通过轮询的方式检测数据变动。才特定的事件触发时进入赃值检测。

大致如下:

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

•   XHR响应事件 ( $http )

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

•   Timer事件( $timeout , $interval )

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

3.数据劫持(vue.js)

思路:使用Object.defineProperty对数据对象做属性get和set的监听,当有数据读取和赋值操作时则调用节点的指令,这样使用最通用的=等号赋值就可以触发了。

wue双向数据绑定小demo思路

①  构造一个Wue对象,定义该对象的属性el、data,创建对象的时候传相应数据,并执行init()方法。

1
2
3
4
5
var Wue=function(params){
    this.el=document.querySelector(params.el);
    this.data=params.data;
    this.init();
};

②  Init方法中执行bindText和bindModel方法,这两个方法分别是解析dom中绑定了w-model、w-text指令的html,并作相应处理。

1
2
3
4
init:function(){
            this.bindText();
            this.bindModel();
       }

③  bindText方法,把带有w-text指令的元素放进一个数组中,如:w-text=’demo’,然后令其innerHTML的值等于传进来的data[demo]。

1
2
3
4
5
6
7
8
9
bindText:function(){
          var textDOMs=this.el.querySelectorAll('[w-text]'),
          bindText;
          for(var i=0;i<textDOMs.length;i++){
             bindText=textDOMs[i].getAttribute('w-text');
             textDOMs[i].innerHTML=this.data[bindText];
          }
        }

④  bindModel方法,把带有w-model指令的元素(一般为form相关元素)放进一个数组中,如:w-model=’demo’,为每一个元素绑定keyup事件(兼容浏览器写法)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
bindModel:function(){
  var modelDOMs=this.el.querySelectorAll('[w-model]'),
  bindModel;
  var _that=this;
  for(var i=0;i<modelDOMs.length;i++){
    bindModel=modelDOMs[i].getAttribute('w-model');
    modelDOMs[i].value=this.data[bindModel]||'';
    //数据劫持
    this.defineObj(this.data,bindModel);
    if(document.addEventListener){
        modelDOMs[i].addEventListener('keyup',function(event) {
            console.log('test');
            e=event||window.event;
            _that.data[bindModel]=e.target.value;
        },false);
    }else{
        modelDOMs[i].attachEvent('onkeyup',function(event){
            e=event||window.event;
            _that.data[bindModel]=e.target.value;  
        },false);
    }
  
}

⑤  使用Object.defineProperty,定义set和get方法,并在set方法中调用bindText方法。这是利用了一旦w-model的值在input中被改变,会自动执行set方法,所以只有在这个方法中调用更新w-text的方法即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
defineObj:function(obj,prop,value){
          var val=value||'';
          var _that=this;
          try{
            Object.defineProperty(obj,prop,{
               get:function(){
                return val;
               },
               set:function(newVal){
                val=newVal;
                _that.bindText();
               }
            })
          }catch (err){
            console.log('Browser not support!')
          
        }

⑥使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
html:<br><h3>双向数据绑定demo</h3>
<div id="wrap">
    <input type="text" w-model='demo'>
    <h5 w-text='demo'></h5>
</div><br>js:
    <script src='../js/wue.js'></script>
    <script>
      new Wue({
        el:'#wrap',
        data:{
            demo:'winty'
        }
      })
    </script>

完整demo戳这里!