欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

你了解vue3.0響應(yīng)式數(shù)據(jù)怎么實(shí)現(xiàn)嗎

 更新時(shí)間:2019年06月07日 10:27:37   作者:Guokai  
這篇文章主要介紹了你了解vue3.0響應(yīng)式數(shù)據(jù)怎么實(shí)現(xiàn)嗎,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧

從 Proxy 說起

什么是Proxy

proxy翻譯過來的意思就是”代理“,ES6對(duì)Proxy的定位就是target對(duì)象(原對(duì)象)的基礎(chǔ)上通過handler增加一層”攔截“,返回一個(gè)新的代理對(duì)象,之后所有在Proxy中被攔截的屬性,都可以定制化一些新的流程在上面,先看一個(gè)最簡(jiǎn)單的例子

const target = {}; // 要被代理的原對(duì)象
// 用于描述代理過程的handler
const handler = {
 get: function (target, key, receiver) {
  console.log(`getting ${key}!`);
  return Reflect.get(target, key, receiver);
 },
 set: function (target, key, value, receiver) {
  console.log(`setting ${key}!`);
  return Reflect.set(target, key, value, receiver);
 }
}
// obj就是一個(gè)被新的代理對(duì)象
const obj = new Proxy(target, handler);
obj.a = 1 // setting a!
console.log(obj.a) // getting a!

上面的例子中我們?cè)趖arget對(duì)象上架設(shè)了一層handler,其中攔截了針對(duì)target的get和set,然后我們就可以在get和set中間做一些額外的操作了

注意1:對(duì)Proxy對(duì)象的賦值操作也會(huì)影響到原對(duì)象target,同時(shí)對(duì)target的操作也會(huì)影響Proxy,不過直接操作原對(duì)象的話不會(huì)觸發(fā)攔截的內(nèi)容~

obj.a = 1; // setting a!
console.log(target.a) // 1 不會(huì)打印 "getting a!"

注意2:如果handler中沒有任何攔截上的處理,那么對(duì)代理對(duì)象的操作會(huì)直接通向原對(duì)象

const target = {};
const handler = {};
const obj = new Proxy(target, handler);
obj.a = 1;
console.log(target.a) // 1

既然proxy也是一個(gè)對(duì)象,那么它就可以做為原型對(duì)象,所以我們把obj的原型指向到proxy上后,發(fā)現(xiàn)對(duì)obj的操作會(huì)找到原型上的代理對(duì)象,如果obj自己有a屬性,則不會(huì)觸發(fā)proxy上的get,這個(gè)應(yīng)該很好理解

const target = {};
const obj = {};
const handler = {
  get: function(target, key){
      console.log(`get ${key} from ${JSON.stringify(target)}`);
      return Reflect.get(target, key);
  }
}
const proxy = new Proxy(target, handler);
Object.setPrototypeOf(obj, proxy);
proxy.a = 1;
obj.b = 1
console.log(obj.a) // get a from {"a": 1}  1
console.log(obj.b) // 1

ES6的Proxy實(shí)現(xiàn)了對(duì)哪些屬性的攔截?

通過上面的例子了解了Proxy的原理后,我們來看下ES6目前實(shí)現(xiàn)了哪些屬性的攔截,以及他們分別可以做什么? 下面是 Proxy 支持的攔截操作一覽,一共 13 種

  1. get(target, propKey, receiver):攔截對(duì)象屬性的讀取,比如proxy.foo和proxy['foo'];
  2. set(target, propKey, value, receiver):攔截對(duì)象屬性的設(shè)置,比如proxy.foo = v或proxy['foo'] = v,返回一個(gè)布爾值;
  3. has(target, propKey):攔截propKey in proxy的操作,返回一個(gè)布爾值。
  4. deleteProperty(target, propKey):攔截delete proxy[propKey]的操作,返回一個(gè)布爾值;
  5. ownKeys(target):攔截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for…in循環(huán),返回一個(gè)數(shù)組。該方法返回目標(biāo)對(duì)象所有自身的屬性的屬性名,而Object.keys()的返回結(jié)果僅包括目標(biāo)對(duì)象自身的可遍歷屬性;
  6. getOwnPropertyDescriptor(target, propKey):攔截Object.getOwnPropertyDescriptor(proxy, propKey),返回屬性的描述對(duì)象;
  7. defineProperty(target, propKey, propDesc):攔截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一個(gè)布爾值;
  8. preventExtensions(target):攔截Object.preventExtensions(proxy),返回一個(gè)布爾值;
  9. getPrototypeOf(target):攔截Object.getPrototypeOf(proxy),返回一個(gè)對(duì)象;
  10. isExtensible(target):攔截Object.isExtensible(proxy),返回一個(gè)布爾值;
  11. setPrototypeOf(target, proto):攔截Object.setPrototypeOf(proxy, proto),返回一個(gè)布爾值。如果目標(biāo)對(duì)象是函數(shù),那么還有兩種額外操作可以攔截;
  12. apply(target, object, args):攔截 Proxy 實(shí)例作為函數(shù)調(diào)用的操作,比如proxy(…args)、proxy.call(object, …args)、proxy.apply(…);
  13. construct(target, args):攔截 Proxy 實(shí)例作為構(gòu)造函數(shù)調(diào)用的操作,比如new proxy(…args);

以上是目前es6支持的proxy,具體的用法不做贅述,有興趣的可以到阮一峰老師的es6入門去研究每種的具體用法,其實(shí)思想都是一樣的,只是每種對(duì)應(yīng)了一些不同的功能~

實(shí)際場(chǎng)景中 Proxy 可以做什么?

實(shí)現(xiàn)私有變量

js的語法中沒有private這個(gè)關(guān)鍵字來修飾私有變量,所以基本上所有的class的屬性都是可以被訪問的,但是在有些場(chǎng)景下我們需要使用到私有變量,現(xiàn)在業(yè)界的一些做法都是使用”_變量名“來”約定“這是一個(gè)私有變量,但是如果哪天被別人從外部改掉的話,我們還是沒有辦法阻止的,然而,當(dāng)Proxy出現(xiàn)后,我們可以用代理來處理這種場(chǎng)景,看代碼:

const obj = {
  _name: 'nanjin',
  age: 19,
  getName: () => {
    return this._name;
  },
  setName: (newName) => {
    this._name = newName;
  }
}

const proxyObj = obj => new Proxy(obj, {
  get: (target, key) => {
    if(key.startsWith('_')){
      throw new Error(`${key} is private key, please use get${key}`)
    }
    return Reflect.get(target, key);
  },
  set: (target, key, newVal) => {
    if(key.startsWith('_')){
      throw new Error(`${key} is private key, please use set${key}`)
    }
    return Reflect.set(target, key, newVal);
  }
})

const newObj = proxyObj(obj);
console.log(newObj._name) // Uncaught Error: _name is private key, please use get_name
newObj._name = 'newname'; // Uncaught Error: _name is private key, please use set_name
console.log(newObj.age) // 19
console.log(newObj.getName()) // nanjin

可見,通過proxyObj方法,我們可以實(shí)現(xiàn)把任何一個(gè)對(duì)象都過濾一次,然后返回新的代理對(duì)象,被處理的對(duì)象會(huì)把所有_開頭的變量給攔截掉,更進(jìn)一步,如果有用過mobx的同學(xué)會(huì)發(fā)現(xiàn)mobx里面的store中的對(duì)象都是類似于這樣的

有handler 和 target,說明mobx本身也是用了代理模式,同時(shí)加上Decorator函數(shù),在這里就相當(dāng)于把proxyObj使用裝飾器的方式來實(shí)現(xiàn),Proxy + Decorator 就是mobx的核心原理啦~

vue響應(yīng)式數(shù)據(jù)實(shí)現(xiàn)

VUE的雙向綁定涉及到模板編譯,響應(yīng)式數(shù)據(jù),訂閱者模式等等,有興趣的可以看這里 ,因?yàn)檫@篇文章的主題是proxy,因此我們著重介紹一下數(shù)據(jù)響應(yīng)式的過程。

2.x版本

在當(dāng)前的vue2.x的版本中,在data中聲名一個(gè)obj后,vue會(huì)利用Object.defineProperty來遞歸的給data中的數(shù)據(jù)加上get和set,然后每次set的時(shí)候,加入額外的邏輯。來觸發(fā)對(duì)應(yīng)模板視圖的更新,看下偽代碼:

const defineReactiveData = data => {
  Object.keys(data).forEach(key => {
    let value = data[key];
    Object.defineProperty(data, key, {
     get : function(){
      console.log(`getting ${key}`)
      return value;
     },
     set : function(newValue){
      console.log(`setting ${key}`)
      notify() // 通知相關(guān)的模板進(jìn)行編譯
      value = newValue;
     },
     enumerable : true,
     configurable : true
    })
  })
}

這個(gè)方法可以給data上面的所有屬性都加上get和set,當(dāng)然這只是偽代碼,實(shí)際場(chǎng)景下我們還需要考慮如果某個(gè)屬性還是對(duì)象我們應(yīng)該遞歸下去,來試試:

const data = {
  name: 'nanjing',
  age: 19
}
defineReactiveData(data)
data.name // getting name 'nanjing'
data.name = 'beijing'; // setting name

可以看到當(dāng)我們get和set觸發(fā)的時(shí)候,已經(jīng)能夠同時(shí)觸發(fā)我們想要調(diào)用的函數(shù)拉,Vue雙向綁定過程中,當(dāng)改變this上的data的時(shí)候去更新模板的核心原理就是這個(gè)方法,通過它我們就能在data的某個(gè)屬性被set的時(shí)候,去觸發(fā)對(duì)應(yīng)模板的更新。

現(xiàn)在我們?cè)趤碓囋囅旅娴拇a:

const data = {
  userIds: ['01','02','03','04','05']
}
defineReactiveData(data);
data.userIds // getting userIds ["01", "02", "03", "04", "05"]
// get 過程是沒有問題的,現(xiàn)在我們嘗試給數(shù)組中push一個(gè)數(shù)據(jù)
data.userIds.push('06') // getting userIds 

what ? setting沒有被觸發(fā),反而因?yàn)槿×艘淮蝩serIds所以觸發(fā)了一次getting~,

不僅如此,很多數(shù)組的方法都不會(huì)觸發(fā)setting,比如:push,pop,shift,unshift,splice,sort,reverse這些方法都會(huì)改變數(shù)組,但是不會(huì)觸發(fā)set,所以Vue為了解決這個(gè)問題,重新包裝了這些函數(shù),同時(shí)當(dāng)這些方法被調(diào)用的時(shí)候,手動(dòng)去觸發(fā)notify();看下源碼:

// 獲得數(shù)組原型
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
// 重寫以下函數(shù)
const methodsToPatch = [
 'push',
 'pop',
 'shift',
 'unshift',
 'splice',
 'sort',
 'reverse',
]
methodsToPatch.forEach(function(method) {
 // 緩存原生函數(shù)
 const original = arrayProto[method]
 // 重寫函數(shù)
 def(arrayMethods, method, function mutator(...args) {
  // 先調(diào)用原生函數(shù)獲得結(jié)果
  const result = original.apply(this, args)
  const ob = this.__ob__
  let inserted
  // 調(diào)用以下幾個(gè)函數(shù)時(shí),監(jiān)聽新數(shù)據(jù)
  switch (method) {
   case 'push':
   case 'unshift':
    inserted = args
    break
   case 'splice':
    inserted = args.slice(2)
    break
  }
  if (inserted) ob.observeArray(inserted)
  // 手動(dòng)派發(fā)更新
  ob.dep.notify()
  return result
 })
})

上面是官方的源碼,我們可以實(shí)現(xiàn)一下push的偽代碼,為了省事,直接在prototype上下手了~

const push = Array.prototype.push;
Array.prototype.push = function(...args){
  console.log('push is happenning');
  return push.apply(this, args);
}
data.userIds.push('123') // push is happenning

通過這種方式,我們可以監(jiān)聽到這些的變化,但是vue官方文檔中有這么一個(gè)注意事項(xiàng)

由于 JavaScript 的限制,Vue 不能檢測(cè)以下變動(dòng)的數(shù)組:

  • 當(dāng)你利用索引直接設(shè)置一個(gè)項(xiàng)時(shí),例如:vm.items[indexOfItem] = newValue
  • 當(dāng)你修改數(shù)組的長(zhǎng)度時(shí),例如:vm.items.length = newLength

這個(gè)最根本的原因是因?yàn)檫@2種情況下,受制于js本身無法實(shí)現(xiàn)監(jiān)聽,所以官方建議用他們自己提供的內(nèi)置api來實(shí)現(xiàn),我們也可以理解到這里既不是defineProperty可以處理的,也不是包一層函數(shù)就能解決的,這就是2.x版本現(xiàn)在的一個(gè)問。 回到這篇文章的主題,vue官方會(huì)在3.x的版本中使用proxy來代替defineProperty處理響應(yīng)式數(shù)據(jù)的過程,我們先來模擬一下實(shí)現(xiàn),看看能否解決當(dāng)前遇到的這些問題;

 3.x版本

我們先來通過proxy實(shí)現(xiàn)對(duì)data對(duì)象的get和set的劫持,并返回一個(gè)代理的對(duì)象,注意,我們只關(guān)注proxy本身,所有的實(shí)現(xiàn)都是偽代碼,有興趣的同學(xué)可以自行完善

const defineReactiveProxyData = data => new Proxy(data, 
  {
    get: function(data, key){
      console.log(`getting ${key}`)
      return Reflect.get(data, key);
    },
    set: function(data, key, newVal){
      console.log(`setting ${key}`);
      if(typeof newVal === 'object'){ // 如果是object,遞歸設(shè)置代理
        return Reflect.set(data, key, defineReactiveProxyData(newVal));
      }
      return Reflect.set(data, key, newVal);
    }
  })
const data = {
  name: 'nanjing',
  age: 19
};
const vm = defineReactiveProxyData(data);
vm.name // getting name nanjing
vm.age = 20; // setting age 20

看起來我們的代理已經(jīng)起作用啦,之后只要在setting的時(shí)候加上notify()去通知模板進(jìn)行編譯就可以了,然后我們來嘗試設(shè)置一個(gè)數(shù)組看看;

vm.userIds = [1,2,3] // setting userIds
vm.userIds.push(1);
// getting userIds 因?yàn)槲覀儠?huì)先訪問一次userids
// getting push 調(diào)用了push方法,所以會(huì)訪問一次push屬性
// getting length 數(shù)組push的時(shí)候 length會(huì)變,所以需要先訪問原來的length
// setting 3 通過下標(biāo)設(shè)置的,所以set當(dāng)前的index是3
// setting length 改變了數(shù)組的長(zhǎng)度,所以會(huì)set length
// 4 返回新的數(shù)組的長(zhǎng)度

回顧2.x遇到的第一個(gè)問題,需要重新包裝Array.prototype上的一些方法,使用了proxy后不需要了,解決了~,繼續(xù)看下一個(gè)問題

vm.userIds.length = 2
// getting userIds 先訪問
// setting length 在設(shè)置
vm.userIds[1] = '123'
// getting userIds 先訪問
// setting 1 設(shè)置index=1的item
// "123"

從上面的例子中我們可以看到,不管是直接改變數(shù)組的length還是通過某一個(gè)下標(biāo)改變數(shù)組的內(nèi)容,proxy都能攔截到這次變化,這比defineProperty方便太多了,2.x版本中的第二個(gè)問題,在proxy中根本不會(huì)出現(xiàn)了。

總結(jié)1

通過上面的例子和代碼,我們看到Vue的響應(yīng)模式如果使用proxy會(huì)比現(xiàn)在的實(shí)現(xiàn)方式要簡(jiǎn)化和優(yōu)化很多,很快在即將來臨的3.0版本中,大家就可以體驗(yàn)到了。不過因?yàn)閜roxy本身是有兼容性的,比如ie瀏覽器,所以在低版本的場(chǎng)景下,vue會(huì)回退到現(xiàn)在的實(shí)現(xiàn)方式。

總結(jié)2

回歸到proxy本身,設(shè)計(jì)模式中有一種典型的代理模式,proxy就是js的一種實(shí)現(xiàn),它的好處在于,我可以在不污染本身對(duì)象的條件下,生成一個(gè)新的代理對(duì)象,所有的一些針對(duì)性邏輯放到代理對(duì)象上去實(shí)現(xiàn),這樣我可以由A對(duì)象,衍生出B,C,D…每個(gè)的處理過程都不一樣,從而簡(jiǎn)化代碼的復(fù)雜性,提升一定的可讀性,比如用proxy實(shí)現(xiàn)數(shù)據(jù)庫(kù)的ORM就是一種很好的應(yīng)用,其實(shí)代碼很簡(jiǎn)單,關(guān)鍵是要理解背后的思想,同時(shí)能夠舉一反三~

擴(kuò)展: 1.Proxy.revocable()

這個(gè)方法可以返回一個(gè)可取消的代理對(duì)象

const obj = {};
const handler = {};
const {proxy, revoke} = Proxy.revocable(obj, handler);
proxy.a = 1
proxy.a // 1
revoke();
proxy.a // Uncaught TypeError: Cannot perform 'get' on a proxy that has been revoked

一旦代理被取消了,就不能再?gòu)拇韺?duì)象訪問了

打印proxy 可以看到IsRevoked變?yōu)閠rue了

2.代理對(duì)象的this問題

因?yàn)閚ew Proxy出來的是一個(gè)新的對(duì)象,所以在如果你在target中有使用this,被代理后的this將指向新的代理對(duì)象,而不是原來的對(duì)象,這個(gè)時(shí)候,如果有些函數(shù)是原對(duì)象獨(dú)有的,就會(huì)出現(xiàn)this指向?qū)е碌膯栴},這種場(chǎng)景下,建議使用bind來強(qiáng)制綁定this

看代碼:

const target = new Date();
const handler = {};
const proxy = new Proxy(target, handler);

proxy.getDate(); // Uncaught TypeError: this is not a Date object.

因?yàn)榇砗蟮膶?duì)象并不是一個(gè)Date類型的,不具有g(shù)etDate方法的,所以我們需要在get的時(shí)候,綁定一下this的指向

const target = new Date();
const handler = {
  get: function(target, key){
    if(typeof target[key] === 'function'){
      return target[key].bind(target) // 強(qiáng)制綁定
      this到原對(duì)象
    }
    return Reflect.get(target, key)
  }
};
const proxy = new Proxy(target, handler);

proxy.getDate(); // 6

這樣就可以正常使用this啦,當(dāng)然具體的使用還要看具體的場(chǎng)景,靈活運(yùn)用吧!

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • VSCode Vue開發(fā)推薦插件和VSCode快捷鍵(小結(jié))

    VSCode Vue開發(fā)推薦插件和VSCode快捷鍵(小結(jié))

    這篇文章主要介紹了VSCode Vue開發(fā)推薦插件和VSCode快捷鍵(小結(jié)),文中通過圖文表格介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-08-08
  • Vue-Cli配置代理轉(zhuǎn)發(fā)解決跨域問題的方法

    Vue-Cli配置代理轉(zhuǎn)發(fā)解決跨域問題的方法

    本文主要介紹了Vue-Cli配置代理轉(zhuǎn)發(fā)解決跨域問題的方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-06-06
  • vue配置文件實(shí)現(xiàn)代理v2版本的方法

    vue配置文件實(shí)現(xiàn)代理v2版本的方法

    這篇文章主要介紹了vue配置文件實(shí)現(xiàn)代理v2版本的方法,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2019-06-06
  • VUE餓了么樹形控件添加增刪改功能的示例代碼

    VUE餓了么樹形控件添加增刪改功能的示例代碼

    本篇文章主要介紹了VUE餓了么樹形控件添加增刪改功能的示例代碼,非常具有實(shí)用價(jià)值,有興趣的可以了解一下
    2017-10-10
  • 使用use注冊(cè)Vue全局組件和全局指令的方法

    使用use注冊(cè)Vue全局組件和全局指令的方法

    下面小編就為大家分享一篇使用use注冊(cè)Vue全局組件和全局指令的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2018-03-03
  • vue項(xiàng)目查看vue版本及cli版本的實(shí)現(xiàn)方式

    vue項(xiàng)目查看vue版本及cli版本的實(shí)現(xiàn)方式

    這篇文章主要介紹了vue項(xiàng)目查看vue版本及cli版本的實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2020-10-10
  • vue路由中前進(jìn)后退的一些事兒

    vue路由中前進(jìn)后退的一些事兒

    這篇文章主要給大家介紹了關(guān)于vue路由中前進(jìn)后退的一些事兒,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用vue路由具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-05-05
  • 解決ElementUI中tooltip出現(xiàn)無法顯示的問題

    解決ElementUI中tooltip出現(xiàn)無法顯示的問題

    這篇文章主要介紹了解決ElementUI中tooltip出現(xiàn)無法顯示的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-03-03
  • Vue中android4.4不兼容問題的解決方法

    Vue中android4.4不兼容問題的解決方法

    這篇文章主要介紹了Vue中android4.4不兼容問題的解決方法,需要的朋友可以參考下
    2018-09-09
  • element-ui使用el-date-picker日期組件常見場(chǎng)景分析

    element-ui使用el-date-picker日期組件常見場(chǎng)景分析

    最近一直在使用 element-ui中的日期組件,所以想對(duì)日期組件常用的做一個(gè)簡(jiǎn)單的總結(jié),對(duì)element-ui el-date-picker日期組件使用場(chǎng)景分析感興趣的朋友一起看看吧
    2024-05-05

最新評(píng)論