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()方法的详细介绍