zoukankan      html  css  js  c++  java
  • 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函数,赋值其实是传参

     

           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>
    

      

    二、极简双向绑定的实现

     

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

     

           以上就是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>
    

       

    三、分解任务

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

     

     

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

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

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

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

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

    四、DocumentFragment

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

          

    五、数据初始化绑定

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

    六、响应式的数据绑定

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

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

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

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

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

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

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

    八、双向绑定的实现

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

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

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

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

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

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

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

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

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

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

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

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

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

    JavaScript 模板引擎实现原理解析

    1、入门实例

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

    复制代码
      <script type="template" id="template">
        <h2>
          <a href="{{href}}">
            {{title}}
          </a>
        </h2>
        <img src="{{imgSrc}}" alt="{{title}}">
      </script>
    复制代码

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

    复制代码
    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"
        }
      ];
    复制代码

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

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

    复制代码
    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;
    复制代码

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

    复制代码
    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);
    复制代码

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

    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多行,功能却比我们上面的强大很多。我们先来看一看源码:

    复制代码
    (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(/[
    	
    ]/g, " ") 
              .split("<%").join("	") 
              .replace(/((^|%>)[^	]*)'/g, "$1
    ")
              .replace(/	=(.*?)%>/g, "',$1,'")  
              .split("	").join("');")  
              .split("%>").join("p.push('") 
              .split("
    ").join("\'")
          + "');}return p.join('');");
       
        return data ? fn( data ) : fn;
      };
    })();
    复制代码

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

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

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

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

    复制代码
    // 代码整个放在一个立即执行函数里面
    (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
          // 去除换行制表符 .replace(/[ ]/g, " ")
          
          // 将左分隔符变成 .split("<%").join(" ")
          
          // 去掉模板中单引号的干扰 .replace(/((^|%>)[^ ]*)'/g, "$1 ")
          
          // 为 html 中的变量变成 ",xxx," 的形式, 如: =users[i].url%> 变成 ',users[i].url,'
          // 注意这里只有一个单引号,还不配对 .replace(/ =(.*?)%>/g, "',$1,'")
          
          // 这时候,只有JavaScript 语句前面才有 " ", 将 变成 ');
          // 这样就可把 html 标签添加到数组p中,而javascript 语句 不需要 push 到里面。
          .split(" ").join("');")
          
          // 这时候,只有JavaScript 语句后面才有 "%>", 将 %> 变成 p.push('
          // 上一步我们再 html 标签后加了 ');, 所以要把 p.push(' 语句放在 html 标签放在前面,这样就可以变成 JavaScript 语句 .split("%>").join("p.push('")
          // 将上面可能出现的干扰的单引号进行转义
          .split(" ").join("\'")
        // 将数组 p 变成字符串。 + "');}return p.join('');"); return data ? fn( data ) : fn; }; })();
    复制代码

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

    下面是 new Function 的基本用法:

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

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

    复制代码
      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('');
    复制代码

    里面的 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戳这里!  

  • 相关阅读:
    Java中的线程Thread方法之---interrupt() 分类: Android Java 2014-02-26 08:51 3189人阅读 评论(2) 收藏
    Java中的对象Object方法之---wait()和notifiy() 分类: Java Android 2014-02-26 08:50 1599人阅读 评论(0) 收藏
    Java中的线程Thread方法之---suspend()和resume() 分类: Java 2014-02-25 14:37 1650人阅读 评论(0) 收藏
    Java中的线程Thread方法之---join() 分类: Android Java 2014-02-25 13:38 1393人阅读 评论(0) 收藏
    Java中的线程Thread方法之---stop() 分类: Java 2014-02-25 09:59 3075人阅读 评论(1) 收藏
    抓包工具Fidder详解(主要来抓取Android中app的请求) 分类: Android 2014-02-24 09:32 10064人阅读 评论(5) 收藏
    XML的解析 分类: JavaWeb Java Android 2014-02-17 18:22 1764人阅读 评论(3) 收藏
    XML文件定义约束 分类: JavaWeb 2014-02-17 17:49 1127人阅读 评论(0) 收藏
    Android中的广播Broadcast详解 分类: Android 2014-02-13 10:59 8414人阅读 评论(5) 收藏
    GitHub错误处理:fatal:could not read Username for 'https://github.com': No such file or directory 分类: Java 2014-02-11 19:39 2346人阅读 评论(0) 收藏
  • 原文地址:https://www.cnblogs.com/libin-1/p/6083166.html
Copyright © 2011-2022 走看看