Object.defineProperty()
Object.defineProperty() 方法會直接在一個對象上定義一個新屬性,或者修改一個對象的現有屬性,並返回此對象。它接收三個參數
-
obj:需要被定義新屬性或改變屬性的對象
-
prop:即將被定義或修改的屬性名稱
-
descriptor:即將被定義或修改的屬性的描述符,分爲數據描述符和存取描述符。
- 數據描述符:就是descriptor是一個具體的值,或者是一個擁有value屬性的對象:
Object.defineProperty(obj, 'data', 'abc') Object.defineProperty(obj, 'data', { value: 'abc', writable: false })
- 存取描述符:就是屬性的getter和setter函數
let value Object.defineProperty(obj, 'data', { get () { return obj.data }, set (newVal) { value = newVal } })
此外,還有兩種描述符共享的兩個鍵值:
- configurable:當且僅當該屬性的 configurable 鍵值爲 true 時,該屬性的描述符才能夠被改變,同時該屬性也能從對應的對象上被刪除。默認爲false。
- enumerable:當且僅當該屬性的 enumerable 鍵值爲 true 時,該屬性纔會出現在對象的枚舉屬性中。默認爲false。
關於Object.defineProperty()方法的更多介紹,[請點擊這裏](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty)
#### 實現數據響應式
- Object的響應式
我們先實現一個最簡單的頁面:
這裏面的obj.foo每一秒都會發生改變,觸發setter函數,在setter函數裏面我們就能更新視圖了,這裏我們就實現了一個簡單的頁面
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app"></div>
<script>
const app = document.getElementById('app')
function definedReactive(obj, key, val) {
Object.defineProperty(obj, key, {
get() {
console.log('get', key, val)
return val
},
set(newVal) {
if (newVal !== val) {
console.log('set', key, newVal)
val = newVal
// 使用視圖的更新函數
update()
}
}
})
}
function update() {
app.innerText = obj.foo
}
const obj = {}
definedReactive(obj, 'foo', '')
obj.foo = new Date().toLocaleTimeString()
setInterval(() => {
obj.foo = new Date().toLocaleTimeString()
}, 1000)
</script>
</body>
</html>
但是,很明顯,這個obj的結構非常簡單,一個key,一個value,那對於obj={foo: ‘foo’, baz: ‘baz’}這樣有多個key的對象,這樣處理明顯就不夠了,我們需要遍歷每個key,對每一個key都做響應式處理
- defineReactive.js
// 遍歷每個key
function observe (obj) {
if (!obj || typeof obj !== 'object') {
return
}
Object.keys(obj).forEach(key => {
defineReactive(obj, key, obj[key])
})
}
// 給每個key都設置getter和setter
function defineReactive (obj, key, val) {
Object.defineProperty(obj, key, {
get () {
console.log('get', key, val)
return val
},
set (newVal) {
if (newVal !== val) {
console.log('set', key, newVal)
val = newVal
}
}
})
}
let obj = { foo: 'foo', baz: 'baz' }
observe(obj)
obj.foo // get foo foo
obj.foo = 'fooooooooo' //set foo fooooooooo
對於更復雜一點的對象,obj={foo: ‘foo’, baz: {a: 1}},我們就需要使用遞歸去完成響應式處理了
// 遍歷每個key
function observe (obj) {
// 如果obj只是個普通值, 就結束遞歸
if (!obj || typeof obj !== 'object') {
return
}
Object.keys(obj).forEach(key => {
defineReactive(obj, key, obj[key])
})
}
// 給每個key都設置getter和setter
function defineReactive (obj, key, val) {
// 如果val也是個object,那就需要遞歸遍歷它,判斷條件已經卸載observe()裏面了,所以不需要加判斷
observe(val)
Object.defineProperty(obj, key, {
get () {
console.log('get', key, val)
return val
},
set (newVal) {
if (newVal !== val) {
// 對於每一個newval,也得對它進行遞歸
observe(newVal)
console.log('set', key, newVal)
val = newVal
}
}
})
}
let obj = { foo: 'foo', bar: 'bar', baz: { a: 1 } }
observe(obj)
obj.baz.a // get baz { a: [Getter/Setter] } get a 1
obj.baz.a = { b: 1 } // get baz { a: [Getter/Setter] } set a { b: [Getter/Setter] }
- 以上是對Object類型的變量做響應式處理,對於Array類型,我們需要做另外的處理
js中,能改變原數組的方法只有七個,分別是push()、pop()、shift()、unshift()、sort()、reverse()、splice(),所以,我們只需要重寫這七個方法,讓這些方法擁有通知更新的功能,就能實現數組的響應式。
// 1、拿出數組的原型
// 2、克隆數組的原型,不然所有數組都變了
// 3、修改七個方法
// 4、覆蓋需要響應式處理的數組的原型的這七個方法
const arrayProto = Array.prototype
const newProto = Object.create(arrayProto)
const methods = ['push', 'pop', 'shift', 'unshift', 'sort', 'reverse', 'splice']
methods.forEach(method => {
newProto[method] = function () {
// 執行這個方法原有的功能
arrayProto[method].apply(this, arguments)
// 這裏添加一個更新通知,並重新對arr做響應式處理
observe(this)
console.log(method + '執行了!')
}
})
修改一下原來的observe函數
// 遍歷每個key
function observe (obj) {
// 判斷值是否object,不是就結束遞歸
if (!obj || typeof obj !== 'object') {
return
}
// 判斷obj是否是數組
if (Array.isArray(obj)) {
// 替換掉arr原型上原有的七個方法,這樣寫能避免改變arrayProto的值
obj.__proto__ = Object.assign({}, arrayProto, newProto)
// 對每一個值進行遍歷,如果這個值是object,則需要做響應式處理
for (let i = 0; i < obj.length; i++) {
observe(obj[i])
}
} else {
Object.keys(obj).forEach(key => {
defineReactive(obj, key, obj[key])
})
}
}
做一下測試
let arr = [1, 2, 3]
observe(arr)
arr.push({ c: 4 }) // push執行了!
console.log(arr[3]) // { c: [Getter/Setter] }
arr[3].c = 5 // set c 5
點擊這裏查看關於Object.create()方法的詳細介紹
點擊這裏查看關於Object.assign()方法的詳細介紹