Vue2響應式原理

Vue響應式原理

利用Object.defineProperty()劫持data數據,裏面每個屬性都定義了get和set方法 監聽屬性的變化,

依賴收集:每個屬性也都有個數組 保存着誰(視圖watcher)依賴了它,當獲取屬性觸發get時,收集了依賴,

依賴更新:當屬性變化觸發set函數時,通知依賴,通知視圖(watcher),視圖(watcher)開始更新。

 

一、 Object.defineProperty()

 Object.defineProperty():在一個對象上定義一個新屬性,或者修改一個對象的現有屬性, 並返回這個對象。

      get 值是一個函數,當屬性被訪問時,會觸發 get 函數

      set 值同樣是一個函數,當屬性被賦值時,會觸發 set 函數

 

Vue 是怎麼知道數據被調用或改變的?

Vue對data對象中的每個屬性進行了get、set監聽,當屬性被調用時觸發get方法,當屬性被改變時觸發set方法,所以Vue 能知道數據被調用或改變。

代碼如下:

class KVue {
    constructor(options) {
        this.$options = options;
        if(this.$options == undefined) {
            return;
        }
        this.$data = this.$options.data; //把data賦值給this,在創建的實例中才能獲取到data
        this.observe(this.$data);
    }
    // 操作data:如果data是對象,則對屬性進行遍歷
    observe(value) {
        if(!value || typeof value !== 'object') {
            return;
        }

        Object.keys(value).forEach((key) => {
            this.defineReactive(value, key, value[key]);
        })
    }
    // 操作屬性:用Object.defineProperty()監聽屬性
    defineReactive(obj, key, val) {
        this.observe(val);
        Object.defineProperty(obj, key, {
            get () {
                return val;
            },
            set (newValue) {
                if(val === newValue) {
                    return;
                }
                val = newValue; 
                console.log(val);
            }
        })
    }
}

 

<!DOCTYPE html>
<html>
<head>
    <title></title>
</head>
<body>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script src="defineProperty.js"></script>
<script type="text/babel">

let kvue = new KVue({
    data: {
        name: 'fndjf',
        list: {
            bar: 'bar'
        }
    }
})
console.log(kvue)
console.log(kvue.$data.name)
kvue.$data.name = '1dgs';
console.log(kvue.$data.name)

console.log('--------------------');

console.log(kvue.$data.list.bar);
kvue.$data.list.bar = 'dfmd';
console.log(kvue.$data.list);

let kvue1 = new KVue();


</script>
</body>
</html>

 

二、依賴收集、更新

依賴收集:data中每個屬性都會有一個數組作爲依賴收集器,當頁面使用某個屬性(get)時,頁面的watcher就會被放到對應屬性的依賴收集器中。

依賴更新:通知屬性依賴收集器中的watcher進行更新

代碼如下: 

class KVue {
    constructor(options) {
        this.$options = options;
        if(this.$options == undefined) {
            return;
        }
        this.$data = this.$options.data; //把data賦值給this,在創建的實例中才能獲取到data
        this.observe(this.$data);

        // 模擬watcher創建
        new Watcher(); //已將實例賦值給Dep.target
        this.$data.name;// 將Dep.target插入name屬性中的dep
        new Watcher();//已將實例賦值給Dep.target,覆蓋了之前的Dep.target
        this.$data.list.bar; //將Dep.target插入list.bar屬性中的dep
    }
    // 操作data
    observe(value) {
        if(!value || typeof value !== 'object') {
            return;
        }

        Object.keys(value).forEach((key) => {
            this.defineReactive(value, key, value[key]);
        })
    }
    // 操作key
    defineReactive(obj, key, val) {
        // 遞歸監聽data屬性中的屬性
        this.observe(val);

        // 初始化一個屬性中的依賴
        const dep = new Dep();

        Object.defineProperty(obj, key, {
            get () {
                // Dep.target是調用該屬性的頁面的Watcher,添加到該屬性的依賴中
                Dep.target && dep.addDep(Dep.target);

                return val;
            },
            set (newValue) {
                if(val === newValue) {
                    return;
                }
                val = newValue; 
                console.log(val);

                // 通知依賴更新
                dep.notify();
            }
        })
    }
}

// Dep:依賴
class Dep {
    constructor() {
        // 存放若干依賴(watcher)
        this.$deps = [];
    }

    // 添加依賴
    addDep(dep) {
        this.$deps.push(dep);
    }

    // 通知依賴數組中的每個watcher更新
    notify() {
        this.$deps.forEach(dep => dep.update(dep));
    }
}

// Watcher:觀察者,做具體更新
class Watcher {
    constructor() {
        // 將當前watcher實例賦值給Dep的靜態屬性,即後一個watcher實例會覆蓋前一個watcher實例
        Dep.target = this;
    }

    update(dep) {
        console.log(dep);

        console.log('屬性更新了');
    }
}
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title></title>
</head>
<body>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script src="defineProperty.js"></script>
<script type="text/babel">

let kvue = new KVue({
    data: {
        name: 'fndjf',
        list: {
            bar: 'bar'
        }
    }
})
console.log(kvue)
console.log(kvue.$data.name)
kvue.$data.name = '1dgs';
console.log(kvue.$data.name)

console.log('--------------------');

console.log(kvue.$data.list.bar);
kvue.$data.list.bar = 'dfmd';
console.log(kvue.$data.list);

</script>
</body>
</html>

 

三、總結

  1. data中每個屬性都有個數組作爲依賴收集器,收集使用該屬性的頁面的watcher
  2. Object.defineProperty()  -  get ,用於 依賴收集
  3. Object.defineProperty()  -  set,用於 依賴更新
  4. watcher 可用於視圖更新

 

 

四、另一寫法

 

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>Page Title</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <script>
    // 1.實現整體架構(Vue類、Watcher類),用到了訂閱發佈者設計模式
    // 2.實現MVVM中的M到V,把數據綁定到視圖
    // 3.實現V-M,當文本框輸入文本時,觸發更新模型中的數據,同時更新相應的視圖

    class Vue {
        constructor(options) {
            this.$data = options.data; //獲取數據
            this.$el = document.querySelector(options.el); //獲取元素#app
            
            this._directive = {}; //存放訂閱者信息
            this.Observer(this.$data);
            this.Compile(this.$el);
        }

        // 劫持數據:
        Observer(data) { 
            for(let key in data) {
                this._directive[key] = [];
                let val = data[key];
                let watcher = this._directive[key];
                console.log(watcher);
                Object.defineProperty(data, key, {
                    get() {
                        return val;
                    },
                    set(newVal) {
                        if(val !== newVal) {
                            val = newVal;
                            // 更新視圖
                            watcher.forEach(element => {
                                element.update();
                            });
                        }
                    }
                })
            }
            
        }

        // 解析指令:收集依賴、更新視圖、訂閱
        Compile(el) {
            let node = el.children;
            console.log(node);
            for(let i = 0; i < node.length; i++) {
                // let node = node[i];
                if(node[i].children.length) {
                    this.Compile(node[i]);
                }
                if(node[i].hasAttribute('v-text')) {
                    let attrVal = node[i].getAttribute('v-text');  // myText myBox
                    this._directive[attrVal].push(new Watcher(node[i], this, attrVal, "innerHTML"));
                    console.log(this._directive);
                }
                if(node[i].hasAttribute('v-model')) {
                    let attrVal = node[i].getAttribute('v-model'); // myText myBox
                    this._directive[attrVal].push(new Watcher(node[i], this, attrVal, "innerHTML"));
                    // 文本框出現變化時更新模型
                    node[i].addEventListener('input', ()=>{
                        this.$data[attrVal] = node[i].value;
                    })
                }
            }
        }
    }

    // 訂閱者
    class Watcher {  //主要功能是更新視圖
        constructor(el, vm, exp, attr) {
            this.el = el;
            this.vm = vm;
            this.exp = exp;
            this.attr = attr;
            this.update();
        }
        update() {
            this.el[this.attr] = this.vm.$data[this.exp];
            // div.innerHTML = vm.$data.myText
        };
    }
    </script>
</head>
<body>
<div id="app">
    <div>
        <div v-text="myText"></div>
        <div v-text="myBox"></div>
        <input type="text" v-model="myText">
        <input type="text" v-model="myBox">
    </div>
</div>
<script>
const app = new Vue({
    el:'#app',
    data: {
        myText: 'textaaaaaa',
        myBox: 'myBoxaaaaa'
    }
})
</script>
</body>
</html>

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章