owned this note
owned this note
Published
Linked with GitHub
### ES5 已經有的defineProperty/物件get set 方法
#### 原本物件 vs defineProperty
https://huanming.medium.com/%E9%87%8D%E6%96%B0%E4%BA%86%E8%A7%A3-javascript-7-object-defineproperty-c1f62d59004f
靜態方法 Object.defineProperty() 會直接對一個物件定義、或是修改現有的屬性。執行後會回傳定義完的物件。
```
// defineProperty 預設不能寫入
let num = {
a: 1,
b: 2,
c: 3,
} // 依序帶入物件、屬性、參數
Object.defineProperty(num, 'a' {
value: 4,
writable: true,
configurable: true,
enumerable: true,
})
```
#### 針對已經有的屬性定義getter/setter
```
let wallet = {
total: 100,
}
Object.defineProperty(waller, 'save', {
configurable: true, // 新增這這兩行,原本預設是 false
enumerable: true, // 新增這這兩行,原本預設是 false
set: function(price) {
this.total = this.total + price / 2
},
get: function() {
return this.total / 2
} })
```
#### Vue2以defineProperty建立響應式
如果一個物件已經有的屬性可以設置get/set 偵測讀or寫入,簡單來說就可以設計一些追蹤資料更新邏輯在裡面(酷~)
**但有一個明顯缺點,如果使用者對原本的物件新增一個屬性,要怎們偵測跟原本物件沒有這個屬性,並且能夠新增?**
https://juejin.cn/post/6985542810900365349
```
function defineReactive (obj,property,val,customSetter,shallow) {
observe(val)
Object.defineProperty(obj, property, {
get() {
return val
},
set(newVal) {
val = newVal
}
})
}
// observer 簡單來說就是遍歷物件有沒有這個屬性
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
```
### 理解ES6 Proxy和Reflect
defineProperty偵測層面是針對物件屬性,ES6 Proxy則是針對物件變動去設計。
```
// 原本響應式物件
const obj = {
foo: 1
// 新增一個函數
bar() {
return this.foo
}
}
const p = new Proxy(obj,
get(target,key) {
track(target,key)
return target[key]
}
set(target,key,newVal) {
target[key] = newVal
trigger(target,key)
}
)
console.log(p.bar) // 1
```
JS物件本身已有get/set屬性,不過是針對已經建立的屬性進行操作
https://pjchender.dev/javascript/js-object/#gettersetter
https://playcode.io/javascript/getter-setter
https://www.explainthis.io/zh-hant/swe/what-is-arrow-function
https://kbpsystem777.github.io/You-Dont-Know-JS/scope&closures/ch5.html
#### **isShallow 淺追蹤**
https://vuejs.org/guide/extras/reactivity-in-depth.html#integration-with-external-state-systems
Vue 官網其實有提到,因為reactive本身是監測物件內每一個屬性有變化就需要觸發更新流程,本身是耗費CPU計算和記憶體的。
immer和Vue shallow結合,簡單來說透過immer代理每一次變動回傳一個變動後的物件,再丟入Vue shallow賦予它新的物件進行監聽,因為Vue 只需要監聽最外層屬性,所以對於大物件能夠結省效能。
immer 實作原理
https://juejin.cn/post/6926099651510665230
```
import produce from 'immer'
import { shallowRef } from 'vue'
export function useImmer(baseState) {
const state = shallowRef(baseState)
const update = (updater) => {
state.value = produce(state.value, updater)
}
// state.value 就是原本Vue的響應式物件
return [state, update]
}
```
**Vue Ref源碼解讀**
https://juejin.cn/post/7065985754177994788
```
class RefImpl<T> {
private _value: T // 輸入新的值
private _rawValue: T // 紀錄原本的值
public dep?: Dep = undefined // 追蹤effect 的key值
public readonly __v_isRef = true
constructor(
value: T,
public readonly __v_isShallow: boolean,
) {
this._rawValue = __v_isShallow ? value : toRaw(value)
this._value = __v_isShallow ? value : toReactive(value)
}
// isShallow 只需要第一層物件有響應式
// toRaw 將響應式物件Prxoy變成原本的物件
// toReactive 轉成響應式物件
get value() {
trackRefValue(this) // 追蹤有用到它的相關變數
return this._value
}
// 當我們對響應式資料賦值-------
set value(newVal) {
const useDirectValue =
this.__v_isShallow || isShallow(newVal) || isReadonly(newVal)
newVal = useDirectValue ? newVal : toRaw(newVal)
if (hasChanged(newVal, this._rawValue)) {
this._rawValue = newVal
this._value = useDirectValue ? newVal : toReactive(newVal)
triggerRefValue(this, DirtyLevels.Dirty, newVal)
}
}
}
// hasChange 用的的是 Object.is (物件需要在同一索引位置)
// 如果本身沒有使用shallow選項 則Vue自動轉換成toReative 深層式監聽物件
```
#### Reactive 本身是深層監聽的
https://github.com/vuejs/core/blob/main/packages/reactivity/src/baseHandlers.ts
https://juejin.cn/post/6951974032145121293
如果傳入的是物件,會對每一層屬性進行Prxoy屬性設定攔截器,這樣比如 ex: Product.name.id 更深層的數據變化才能監測到
Reactive handler 這裡也能利用isObject 攔截到非物件形式資料,避免Proxy error
```
if (isObject(res)) {
// Convert returned value into a proxy as well. we do the isObject check
// here to avoid invalid value warning. Also need to lazy access readonly
// and reactive here to avoid circular dependency.
return isReadonly ? readonly(res) : reactive(res); // 重点在这里。。。
}
return res;
```