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可被枚舉的屬性:javascript

示例一:
    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開篇示例。css

   參考文章:https://segmentfault.com/a/1190000006599500html

1、訪問器屬性前端

       訪問器屬性是對象中的一種特殊屬性,它不能直接在對象中設置,而必須經過defineProperty()方法單獨定義。vue

       var obj = { };html5

       // 爲obj定義一個名爲hello的訪問器屬性java

       Object.defineProperty(obj, "hello", {node

         get: function () {return sth},jquery

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

       })

       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>

  

 

2、極簡雙向綁定的實現

 

       此例實現的效果是:隨文本框輸入文字的變化,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>

   

 

3、分解任務

       上述示例僅僅是爲了說明原理。咱們最終要實現的是:

 

 

       首先將該任務分紅幾個子任務:

   一、輸入框以及文本節點與data中的數據綁定

   二、輸入框內容變化時,data中的數據同步變化。即view => model的變化。

   三、data中的數據變化時,文本節點的內容同步變化。即model => view的變化。

       要實現任務一,須要對DOM進行編譯,這裏有一個知識點:DocumentFragment。

 

4、DocumentFragment

       DocumentFragment(文檔片斷)能夠看做節點容器,它能夠包含多個子節點,當咱們將它插入到DOM中時,只有它的子節點會插入目標節點,因此把它看做一組節點的容器。使用DocumentFragment處理節點,速度和性能遠遠優於直接操做DOM。Vue進行編譯時,就是將掛載目標的全部子節點劫持(真的是劫持)到DocumentFragment中,通過一番處理後,再將DocumentFragment總體返回插入掛載目標。

      

5、數據初始化綁定

       以上代碼實現了任務一,咱們能夠看到,hello world已經呈如今輸入框和文本節點中。

 

6、響應式的數據綁定

       再來看任務二的實現思路:當咱們在輸入框輸入數據的時候,首先觸發input事件(或者keyup、change事件),在相應的事件處理程序中,咱們獲取輸入框的value並賦值給vm實例的text屬性。咱們會利用defineProperty將data中的text劫持爲vm的訪問器屬性,所以給vm.text賦值,就會觸發set方法。在set方法中主要作兩件事,第一是更新屬性的值,第二留到任務三再說。

       任務二也就完成了,text屬性值會與輸入框的內容同步變化:

 

7、訂閱/發佈模式(subscribe&publish)

       text屬性變化了,set方法觸發了,可是文本節點的內容沒有變化。如何讓一樣綁定到text的文本節點也同步變化呢?這裏又有一個知識點:訂閱發佈模式。

       訂閱發佈模式(又稱觀察者模式)定義了一種一對多的關係,讓多個觀察者同時監聽某一個主題對象,這個主題對象的狀態發生改變時就會通知全部觀察者對象。

       發佈者發出通知 => 主題對象收到通知並推送給訂閱者 => 訂閱者執行相應操做

       以前提到的,當set方法觸發後作的第二件事就是做爲發佈者發出通知:「我是屬性text,我變了」。文本節點則是做爲訂閱者,在收到消息後執行相應的更新操做。

 

8、雙向綁定的實現

       回顧一下,每當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 模板引擎實現原理解析

一、入門實例

首先咱們來看一個簡單模板:

複製代碼
  <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.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 使用的是<%  %>。

 

三、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(/[\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;
  };
})();
複製代碼

初看是否是以爲有點懵,徹底不能理解的代碼。沒事,後面咱們會對源碼進行解釋的,咱們仍是先看一下所用的模板

  <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
      // 去除換行製表符\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; }; })();
複製代碼

上面代碼中,有一個要指出的就是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戳這裏!  

相關文章
相關標籤/搜索