1. 程式人生 > >Vue.js 原理

Vue.js 原理

vue原理

必看:https://segmentfault.com/a/1190000006599500#articleHeader3

  vue採用資料劫持並結合釋出者-訂閱者模式的方式實現雙向資料繫結的。

資料劫持

  通過Object.defineProperty()來劫持各個屬性的settergetter

Object.defineProperty方法會=能夠在一個物件上直接定義一個新屬性,或者修改一個物件的現有屬性,並返回這個物件。

Object.defineProperty語法

參考文章:http://www.cnblogs.com/kidney/p/6052935.html#!comments

Object.defineProperty(obj, prop, descriptor)
// obj 要在其上定義屬性的物件 // prop 要定義或修改的屬性名稱 // descriptor 將被定義或者修改的屬性描述 // 返回值為被傳遞給函式的物件

訪問器屬性

  訪問器屬性是物件中一種特殊屬性,它不能直接在物件中設定,而且必須通過Ojbect.defineProperty()方法單獨定義。

var obj = {}
Object.defineProperty(obj,'red',{
    get: function () {
        console.log('get方法被呼叫了');
    },
    set: function(value) {
        console.log(
'set方法被呼叫,引數為' + value); } }); console.log(obj); // {} obj.red; // get方法被呼叫了 obj.red = 'green'; // set方法被呼叫,引數為green

  get 和 set 方法內部的this都是指向obj的,這就意味著get和set函式可操作物件內部的值。另外,訪問器屬性的值會“覆蓋”同名的普通屬性,這是因為訪問器屬性會被優先訪問,語氣同名的普通屬性則會被忽略。

實現一個簡單的雙向繫結

<input type="text" id="inp">
<p id="par"></p>
<script>
    var
obj = {} Object.defineProperty(obj, 'red', { set: function (v) { document.getElementById('inp').value = v document.getElementById('par').innerHTML = v } }); document.addEventListener('keyup',function(ev){ obj.red = ev.target.value // 獲取事件目標裡最先觸發的元素 }) console.log(obj); // {} </script>

簡單模擬vue雙向資料繫結

思路分析:

  1. 實現一個數據監聽器Observer,能夠對資料物件的所有屬性進行監聽,如果變動,可以拿到最新值並通知訂閱者。

  2. 實現一個指令解析器Compile,對每個元素節點的志林進行掃描和解析,根據志林模板替代資料,以及繫結的相應的更新函式。

  3. 實現一個Watcher,作為連線Observer和Compile的橋樑,能夠訂閱並受到每個屬性變動的通知,執行志林繫結的相應回撥函式,從而更新檢視。

  4. mvvm入口函式,整合以上三者。

1、實現Observer

認識Object.keys()

獲取物件的鍵名,返回陣列

var data = {
    name : 'king',
    age: 20,
    gender: 'boy'
}
console.log(Object.keys(data)) // ["name", "age", "gender"]

實現監聽者Observer

我們可以利用Object.defineProperty()來監聽屬性變化,那麼將需要observe的資料進行遞迴遍歷,包括他子屬性物件的屬性,都加上settergetter

那麼,如果給物件的某個屬性賦值時,就會觸發setter,那麼就能監聽到資料變化。

var obj = {name: 'houfee'}

// 監聽器監聽資料變化
function observe(data) {
    // 判斷是否為物件
    if (!data || typeof data !== 'object') {
        return;
    }
    // 遍歷物件的鍵名,每次遍歷鍵名呼叫dafineReactive方法
    Object.keys(data).forEach(function (key) {
        dafineReactive(data, key, data[key]);
    })
}

// defineProperty() 監聽資料變化
function dafineReactive(data, key, val) {
    observe(val); // 監聽子屬性
    Object.defineProperty(data, key, {
        enumerable: true,
        configurable: false,
        get: function () {
            return val;
        },
        set: function (newVal) {
            console.log('監聽到屬性變化了', val, '---->', newVal);
            val = newVal;
        }
    });
}

console.log(obj); // {name: "houfee"}
// 呼叫Observer監聽方法
observe(obj)
// 使用定時器驗證監聽
setTimeout(function () {
    console.log('定時器監聽變化');
    obj.name = '一直走'
    console.log(obj);
}, 2000)

  以上我們就監聽到了每一個數據的變化,那麼監聽到變化之後就該通知訂閱者了,所以接下來我們需要實現一個訊息訂閱器:需要維護一個數組,用來收集訂閱器,資料變動就觸發notify,再呼叫訂閱者的update方法:

// defineProperty() 監聽資料變化
function dafineReactive(data, key, val) {
    var dep = new Dep(); // 訂閱者
    observe(val); // 監聽子屬性
    Object.defineProperty(data, key, {
        enumerable: true,
        configurable: false,
        get: function () {
            return val;
        },
        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,呼叫訂閱者的update方法
    notify: function () {
        this.subs.forEach(function (sub) {
            sub.update();
        })
    }
}

  新增訂閱者:上面的思路整理中我們已經明確訂閱者應該是Watcher, 而且var dep = new Dep();是在 defineReactive方法內部定義的,所以想通過dep新增訂閱者,就必須要在閉包內操作,所以我們可以在 getter裡面動手腳:

Object.defineProperty(data, key, {
    enumerable: true,
    configurable: false,
    get: function () {
        // 由於需要在閉包內新增watcher,所以
        //通過Dep定義一個全域性target屬性,暫存watcher, 新增完移除
        Dep.target && dep.addDep(Dep.target)
        return val;
    },
    set: function (newVal) {
        if (val === newVal) return;
        console.log('監聽到屬性變化了', val, '---->', newVal);
        val = newVal;
        dep.notify(); // 每次資料變動通知訂閱者
    }
});


// Watcher.js
watcher.prototype = {
    get: function (key) {
        Dep.target = this;
        // 這裡會觸發屬性的getter,從而新增訂閱者
        this.value = data[key];
        Dep.target = null;
    }
}

  

這裡已經實現了一個Observer了,已經具備了監聽資料和資料變化通知訂閱者的功能。

 

2、實現Compile

  Compile主要實現解析模板指令,將模板中的變數替換成資料,然後初始化渲染頁面,並肩每個指令對飲的節點繫結更新函式,新增監聽資料的訂閱者,一旦資料有變動,收到通知,更新檢視:

 

因為遍歷解析過程中有多次操作DOM節點,為了提高效能和效率,會先將根節點el轉換成文件碎片fragment進行解析編譯操作,解析完成,在將fragment新增會原來的真實DOM節點中:

// 未完待續中......參考文章