Object.defineProperty()监听对象属性的变化
Object.defineProperty()
Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。
语法
:
Object.defineProperty(obj, prop, descriptor)
// 参数:
// obj: 要在其上定义属性的对象。
// prop: 要定义或修改的属性名称。
// descriptor: 要被定义或修改的属性描述符。
// 返回:此对象。
给对象添加属性的方式有
:
-
字面量:
var obj = { name1: "aa" }
-
对象.属性名 = 值
obj.name2 = "bb"
-
对象[“属性名”] = 值
obj["name3"] = "cc"
-
Object.definedProperty()
Object.defineProperty(obj,"name4",{ value:"dd", writable: false // value属性值默认是只读的 })
属性描述符
Object.defineProperty(对象, 属性名,{
configurable:false,
enumerable:false,
writable:false,
value:undefined,
get:function(){},
set:function(){}
})
属性描述符 | 描述 |
---|---|
configurable | 为 true 时,属性才能重新被定义(再写一次Object.defineProperty())。默认为 false。 |
enumerable | 为 true 时,该属性才能够出现在对象的枚举属性中,即可以使用for in循环访问。默认为 false。 |
writable | 为 true 时,value属性值才能被修改。默认为 false,value属性值是只读的。 |
value | 该属性对应的初始值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。默认为 undefined。 |
get | 一个给属性提供 getter 的方法,如果没有 getter 则为 undefined。当访问该属性时,该方法会被执行。默认为 undefined。 |
set | 一个给属性提供 setter 的方法,如果没有 setter 则为 undefined。当属性值修改时,触发执行该方法。该方法将接受唯一参数,即该属性新的参数值。默认为 undefined。 |
描述符可同时具有的键值:
configurable | enumerable | value | writable | get | set | |
---|---|---|---|---|---|---|
数据描述符 | Yes | Yes | Yes | Yes | No | No |
存取描述符 | Yes | Yes | No | No | Yes | Yes |
注意
:
- 如果一个描述符同时有(value或writable)和(get或set)关键字,将会产生一个异常。
configurable 和enumerable
-
configurable: 能否再次修改
-
enumerable: 能否枚举
Object.defineProperty(obj,"c",{
// 不允许再次修改这个属性
configurable: false,
// 这个属性能否被for in 循环遍历
enumerable:true,
value:100
})
value 和 writable
-
value: 设置初始值
-
writable: 是否可以修改这个属性值
const obj = {
B:1
}
// 定义只读的对象属性A
Object.defineProperty(obj,"A",{
value: 1,
writable: false // value属性值是只读的
})
console.dir(obj) // {B:1,A:1}
console.log(obj.A) // 1
// 给对象的属性赋值,并不会修改属性的值。
obj.A =1001
console.log(obj.A) // 1
const定义的对象,它的属性还是可以修改的。可以通过writable设置为false来设置只读的属性,真正实现常量的效果。
对已有对象进行封装,以得到一个常量对象:
const obj = {
a:1,
b:2
}
function getConst(obj){
var _obj = {}
for(var key in obj){
Object.defineProperty(_obj,key,{
writable:false,
value: obj[key]
})
}
return _obj
}
var obj1 = getConst(obj)
console.log("设置之后的值是:",obj1) // 设置之后的值是: {a: 1, b: 2}
get 和 set
- 它们与value和writable是互斥的。
- 一旦使用它们,则这个属性就没有保存属性值的能力
- 用它们来做拦截器
实现常量对象
:
不允许修改对象的某个属性,修改了也无效。
- 使用get()返回值,定义只读的属性。
- 使用set(),在设置值时报错。
const obj = {}
Object.defineProperty(obj,"age",{
get(){
return 18
},
set(){
throw new Error("对不起,你没有权限设置age属性!")
}
})
console.dir(obj) // {age:18}
console.log(obj.age) // 18
obj.age = 80
console.log("设置之后的值是:",obj.age)
把一个已有对象设置成只读的对象:
const obj = {
a:1,
b:2
}
function getConst(obj){
var _obj = {}
for(var key in obj){
Object.defineProperty(_obj,key,{
get(){
return obj[key]
}
})
}
console.log(key)
return _obj
}
var obj1 = getConst(obj)
console.log("设置之后的值是:",obj1) // {a:2,b:2}
实现从数据到视图的变化
监听属性的变化
var obj = {salary:0}
Object.defineProperty(obj,"salary",{
get:function(){
console.info("获取属性值")
},
set:function(newVal){
console.info("属性值修改为:"+newVal)
}
})
obj.salary = 2000;
obj.salary;
// 在控制台输出:
// 属性值修改为:2000
// 获取属性值
封装函数监听全部的属性
function observe(obj) {
Object.keys(obj).forEach(key=>{
var val = obj[key]
Object.defineProperty(obj,key,{
set:function(newVal){
console.info( `${obj[key]}----->${newVal}`);
val = newVal;
},
get:function(){
console.info(`get....${key}`)
return val;
}
})
})
}
var data = {salary:1000,bonus:3000}
observe(data);
data.salary = 2000; // 更新属性值
data.bonus;
// 在控制台输出:
// get....salary
// 1000----->2000
// get....bonus
- 给对象的所有属性,都加上
监听
,这个步骤称之为数据劫持
。
结论
:
- Object.defineProperty()是vue2实现的核心原理。vue2中的数据双向绑定就是用它来实现的。
- 新发布的vue3中使用ES6中的Proxy来代替Object.defineProperty()。参考 Proxy
注意
:
- vue不支持ie8及更低版本,因为Object.definedProperty()在低版本中不可用。
vue2的数据双向绑定实现原理
vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()
来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
实现具体步骤
:
- 需要给observe的数据对象进行递归遍历,包括子属性对象的属性,都加上setter和getter,这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到数据变化。
- compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图。
- Watcher订阅者是Observer和Compile之间通信的桥梁,主要做的事情是:
- 在自身实例化时往属性订阅器(dep)里面添加自己。
- 自身必须有一个update()方法。
- 待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调,则功成身退。
- MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最后利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果。