相信你們都知道,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函數
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
平時取數據的時候,常常會遇到深層數據結構,若是不作任何處理,很容易形成 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複製代碼