vue2和vue3的數據響應式原理剖析, Proxy 真香?

寫在前面

相信你們都知道,vue實現數據雙向綁定無非就是採用數據劫持的方式,結合發佈訂閱模式,經過Object.defineProperty()來劫持各個屬性的setter,getter以監聽屬性的變更,在數據變更時發佈消息給訂閱者,以上是vue2.x的實現原理,3.0的話就是proxy替換Object.defineProperty(),其餘流程沒有改變。總結一下數據相應原理就發佈訂閱模式(觀察者模式)+ 數據劫持(defineProperty or proxy)html

什麼是觀察者模式?

一般又被稱爲 發佈-訂閱者模式 或 消息機制,它定義了對象間的一種一對多的依賴關係,只要當一個對象的狀態發生改變時,全部依賴於它的對象都獲得通知並被自動更新,解決了主體對象與觀察者之間功能的耦合,即一個對象狀態改變給其餘對象通知的問題。vue

文字繁瑣,看名字高大尚,咱們舉個生活中栗子 平時想了解某個服裝品牌的信息,可是又不想總是去官網查看,因而偷偷關注了公衆號(訂閱者),公衆號有最新產品產品上市就推送消息給你(發佈者),沒錯這個就是了數組

咱們工做中也有常常看到,好比:數據結構

document.querySelector('#btn').addEventListener('click',function () {
        alert('You click this btn');
    },false)複製代碼

簡單代碼實現

const EventHub = {
  _messager: {},
  on (type, fn) {
    if (this._messager[type] === undefined) {
      this._messager[type] = [fn]
    } else {
      this._messager[type].push(fn)
    }
  },
  emit (type, arv) {
    const params = {
      type,
      arv: arv || {}
    }
    const len = this._messager[type]
    for (let i = 0; i < len.length; i++) {
      const fn = len[i];
      fn.call(this, params)
    }
  }
}

EventHub.on('say', function (data) {
  console.log(data.arv.text)
})
EventHub.on('say', function (data) {
  console.log('hhhhh')
})
EventHub.on('hi', function () {
  console.log('hi,小田田')
})
setTimeout(() => {
  // 發佈
  EventHub.emit('say', {text: 'ys大佬們,我要發佈信息了'})
  EventHub.emit('hi')
}, 3000)複製代碼

觀察者模式使用

最熟悉的莫過於咱們天天使用vue框架了,其中數據的雙向綁定就是利用 Object.defineProperty() 和觀察者模式實現的。app

看看簡單實現代碼:框架

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <div id="demo"></div>
    <input type="text" id="inp">
    <script>
        var obj  = {};
        var demo = document.querySelector('#demo')
        var inp = document.querySelector('#inp')
        Object.defineProperty(obj, 'name', {
            get: function() {
                return val;
            },
            set: function (newVal) {//當該屬性被賦值的時候觸發
                inp.value = newVal;
                demo.innerHTML = newVal;
            }
        })
        inp.addEventListener('input', function(e) {
            // 給obj的name屬性賦值,進而觸發該屬性的set方法
            obj.name = e.target.value;
        });
        obj.name = 'fei';//在給obj設置name屬性的時候,觸發了set這個方法
    </script>
</body>
</html複製代碼

固然真正的實現過程是複雜的,是這樣子的ide

vue2.x vs vue3 響應式原理對比

vue2.x函數

Vue.prototype.observe= function(obj){
    var value;
    var that = this;
    for(var key in obj){
        value = obj[key];
        if(typeof value === 'object'){
            this.observe(value);
        }else{
            Object.defineProperty(this.$data,key,{
                get:function(){
                    return value
                },
                set:function(newValue){
                    value = newValue;
                    self.render();
                }
            })
        }
    }
}複製代碼

vue3.0oop

Vue.prototype.observe= function(obj){
    var value;
    var that = this;
    // 用proxy就不須要for in了,即監聽的顆粒度減少了,拿到的信息更多了。
    this.$data = new Proxy(obj,{
        get:function(target,key,reveive){
            return target[key];// 不須要中間變量了
        },
        set: function (target, key, newValue,reveive){
            target[key] = newValue;
        }
    })
}複製代碼

Vue 3.0與Vue 2.0的區別僅是數據劫持的方式由Object.defineProperty更改成Proxy代理,其餘代碼不變。this

Proxy 能夠作哪些有意思的事情?

等不及可選鏈:深層取值(get)

平時取數據的時候,常常會遇到深層數據結構,若是不作任何處理,很容易形成 JS 報錯。 爲了不這個問題,也許你會用多個 && 進行處理:

const country = {
    name: 'china',
    province: {
        name: 'guangdong',
        city: {
            name: 'shenzhen'
        }
    }
}
const cityName = country.province
    && country.province.city
    && country.province.city.name;複製代碼

最新的 ES 提案中提供了可選鏈的語法糖,支持咱們用下面的語法來深層取值。

country?.province?.city?.name複製代碼

proxy的實現

function noop() {}
function get (obj) {
    // 注意這裏攔截的是 noop 函數
    return new Proxy(noop, {
        // 這裏支持返回執行的時候傳入的參數
        apply(target, context, [arg]) {
            return obj;
        },
        get(target, prop) {
            return get(obj[prop]);
        }
    })
}
const obj = {
    person: {}
}

get(obj)() === obj; // true
get(obj).person.name(); // undefined複製代碼

管道

在最新的 ECMA 提案中,出現了原生的管道操做符 |>,在 RxJS 和 NodeJS 中都有相似的 pipe 概念。使用 Proxy 也能夠實現 pipe 功能,只要使用 get 對屬性訪問進行攔截就能輕易實現,將訪問的方法都放到 stack 數組裏面,一旦最後訪問了 execute 就返回結果。

const pipe = (value) => {
    const stack = [];
    const proxy = new Proxy({}, {
        get(target, prop) {
            if (prop === 'execute') {
                return stack.reduce(function (val, fn) {
                    return fn(val);
                }, value);
            }
            stack.push(window[prop]);
            return proxy;
        }
    })
    return proxy;
}
var double = n => n * 2;
var pow = n => n * n;
pipe(3).double.pow.execute複製代碼
相關文章
相關標籤/搜索