JavaScript進階(十四):defineProperty 的應用

上篇博客我們發現 defineProperty 會有一個問題,就是不管是數組還是 json,你去操作數據內部的東西,這時候它是無法響應的。

實際上來說,正常的情況下,defineProperty 它只監聽你讓它監聽的那一層。

如果我們想監聽對象內部的東西,我們就得在往裏加一層,就這麼簡單。

 

那麼在做之前,首先我們要解決幾個問題:

第一個問題,就是說,我們現在先不管遞歸啥的,就說一個事,我們現在在裏面設置,它就是獲取不到,怎麼辦?

很簡單,首先,當有人設置某個數據的時候,我們肯定有某種目的,不是說就在 set 裏面 console.log 一下就完了。

我們的目的一般情況下,在組件內部往往是要執行一個 render,或者其他的渲染操作。

所以現在我們不去管這個 render 實際幹了什麼,我們就先打印一下,證明我們確實執行了這個事:

那麼這個時候,我們來看一下:

可以看到,我們就設一個普通的值,是可以執行渲染操作的,一點問題沒有。

我們上篇博客是卡在了這裏:

相信對 vue 有了解的朋友肯定會知道,它裏面一般情況下會提供一個小小的方法,叫做 $set,所以我們也可以來做一個:

那麼這時候我們需要幾個東西:

1,obj。你得告訴我,要設置誰。

2,key。然後我需要設置這個東西里面的誰。

3,val。最後我還要知道,你要設置成多少。

那麼現在具體來說,我們要幹什麼呢?

我們就先往最簡單的考慮,兩件事:我要設置這個值,並且觸發重新渲染。

那麼現在別的我們先不管,我們來試試:

目前來看,確實是不觸發渲染的。

那我們換一個方法,用 $set:

你可以看到,沒問題。

我們這個 arr 裏面的數據也變了,渲染也出來了,真棒!

然後現在,我還要設置一個 a = 55,我就想用 app.$set 來設置。

那你爲啥不直接用 app.a = 55 呢?人家說我就不。

這時候,你可以發現,就有問題了:

觸發了 2 次渲染,這是怎麼回事呢?

很簡單,首先,你現在做的這個賦值操作,它本身就是可以被響應到的,然後我們又補了一個渲染,那這個就出事了。

雖然重複渲染出來的結果倒是沒什麼錯,但是性能上就有點喫緊了。

所以說在這種情況下,我們怎麼樣才能夠保證一件事呢,就是說,如果它沒渲染,我替你渲染。如果它已經渲染過了,我就不要在渲染了。

 

那麼怎麼辦呢?

非常的簡單,當然我們現在用的是一個稍微有一丁點近似的處理方法,在以後我們會更靠譜,但是思想是不變的,怎麼做呢?

首先,我的對象身上有一個狀態,叫做 updated:

簡單來說,當我下面 render 了,成功的渲染之後,我們就讓 updated 變成 true。

 

然後接下來,我們就做一個事,在我設置這個值之前,我會先手動的把這個 updated 設置成 false。

因爲各種各樣的原因,它這個值可能是 true,也可能是 false,不一定,所以這裏我們手動的讓它是 false。

然後接下來,我進行賦值操作 obj[key] = val。

進行這個操作的時候,是不是有可能 render 了,也有可能沒 render?

所以,我就看一下,如果你的 updated 是 false,意思就是你沒更新過,那麼這個時候,我纔會替它去做這個 render:

那麼我們把每種操作都試一次:

所以,我們就順便的做了一個 $set,這個 $set 可以幫助我們來解決這個問題,同時它也可以做各種各樣的事。

 

接下來,我們其實還有一個問題,比如對象裏面的賦值,它也是不響應的:

首先我們肯定一點,這個用 $set 是能解決的:

 

但是我們現在就想不用 $set 也能行,就直接賦值:app.obj.x = 99

我們現在想要做的,就是這樣的一個事。

那麼我要做的事非常的簡單,其實我需要的是在 for in 裏面做個判斷:

如果 name === 'obj',那麼我們就單獨處理。

else 的時候,我們就用原來那一套。

注意,現在這個時候,你真正返回出去的是 data.obj,你要監聽的,也是它:

所以,我們還要在 get 裏面,再做一次循環:

到這相信大家就可以看到,它的問題其實很多,就是非常非常的亂,我們這麼寫下去的話,這個東西就亂套了。

並且,還有其他的問題,我們不光要在 get 裏面來做這個事,set 裏面也需要走,爲什麼?

因爲在 set 這,它會變成一個新的對象,你要如果不去做事的話,那麼上面 get 裏面定義的 defineProperty 就沒了。

 

所以說,這個 defineProperty 最大的問題有這麼幾個:

1,對於像數組,json 這種東西,它的一個監聽是不完整的,它只能監聽表面那一層,裏面就監聽不到了。

2,如果我們想讓它監聽完整,沒問題,可以做,但是,你就需要去往它裏面遞歸寫很多層,輕則麻煩,重則影響性能,都不好。

所以我們現在的問題沒有必要跟它玩,沒意義,我們更多的是想用更好的那個東西,也就是 proxy。

 

當然,現在我們不管別的,先對 defineProperty 做一個收尾,不再探討它更深層次的問題。

比如,現在我們想要看看,用 defineProperty 怎麼實現我們之前的 HotList。

首先,我們需要對這個已有的類,做一些小小的改動。

比如,在 constructor 裏面,我們需要把所有的數據全都加上 defineProperty,以及我們需要在裏面響應它的更新。

 

首先,我們就不需要 setTitle、getTitle、setData、getData 了,因爲我們已經用 defineProperty 來解決了這個問題:

然後這個 render,雖然目前寫的很差,但是我們先保留暫時不動它。

 

接下來我們需要很多的改進,首先說實話,我們原來有多少數據都在參數裏直接傳,這事方便嗎?反正我覺得不方便。

我更希望把 HotList 改成另外一個樣子:

在接下來,我們就要改上面了。

這時候接收的參數就是一個大 json,options 了:

然後,之前我們做的時候,它裏面有各種各樣的 assert 判斷,我們要保持這個傳統,不過要稍微改一改。

首先,options 必須有,並且根元素也必須有:

然後在接下來,我們是不是要判斷一下這個 root 到底是什麼?

因爲我們之前的博客已經說過,這玩意有可能是一個字符串,有可能是一個 HTMLElement,所以我們需要做一個判斷。

這時候我們要說一個很重要的事了。

注意,我們平常在寫程序的時候,爲了確保可讀性,我們不會把所有的東西都堆在 constructor 裏面。

因爲 constructor 是別人認識你這個類的第一印象。

如果你這裏邊邏輯特別的龐大,那對別人閱讀你這個代碼會特別的混亂,也包括你自己去維護這個類的時候,也很費勁。

所以像這些比較有價值的操作,比如提取 root 的元素,我們會單獨拉出來:

接下來,這個 _getRoot 還有一個小小的技巧,你可以看到,在 _getRoot 裏面,我是把這個 root 直接 return 出去的,而並沒有這麼去做:

那麼這麼做,和我們返回出去有什麼區別呢?

有,明確的告訴大家,會有這樣的一個區別。

如果在裏面直接就對 this.root 進行賦值,那麼在 constructor 這,我們是不是就什麼都不用幹,直接調用就行:

而如果我裏面只是個 return 的話,我這就需要加一句,我把這個 root 要保存在自己身上:

注意,哪種寫法更好,後面這種寫法更好。

因爲,我們如果把屬性的添加,分散在各個函數裏面,這個代碼的可讀性會非常的差。

而如果說,我都集中在 constructor 這:

那麼別人一眼就能夠看出來,你這裏面加了個什麼,就會很方便。

當然順便一說,爲什麼要加個下劃線?因爲我不希望別人用這個東西,這是我自己的東西。

 

然後接下來,我們還要處理 data,這也是一個有點規模的操作,所以我們也把它單獨的抽到一個東西里面:

那麼這個時候,我們的 _defineData 就也搞定了。

 

並且,render 裏面也需要跟着改,因爲這時候已經沒有 parent 了,而是 _root:

 

那麼這些工作都完成了之後,我們就來試試:

可以看到,跟我們平常用的框架,效果上是不是一樣的?

當然,它背後的原理也是一樣的。

 

並且,我們現在還想實現一個 $set:

那麼,但凡這些無法響應的數據,我們就用 $set:

就這麼簡單。

所以說,我們真正想要去做一個可響應的對象,也並不難。

只是一個方不方便的問題,反正我們用這個 defineProperty 多少是費點勁。

所以下篇博客,我們就開始介紹 proxy。

 

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