vue2.X 中的 defineProperty API
Vue 初始化过程
Vue 的初始化过程,分别有Observer、Compiler和Watcher,当我们 new Vue 的时候,会调用Observer,通过 Object.defineProperty 遍历 vue 对象的 data、computed 或者 props(如果是组件的话)的所有属性进行监听。同时通过Compiler解析模板指令,解析到属性后就 new 一个Watcher并绑定更新函数到 watcher 当中,Observer 和 Compiler 就通过属性来进行关联。
vue2.X的响应式:
基于Object.defineproperty 中的getter和setter函数进行数据劫持完成数据响应式的。
Object.defineproperty()方法会直接在一个对象上定义一个新属性或修改对象现有属性,并返回此对象。
-
Object.defineProperty(obj, prop, descriptor) 的参数
obj 要定义属性的对象(目标对象)
prop 要定义或修改的属性的名称
descriptor 目标对象属性的一些特征(是一个对象)
descriptor 下有6个参数
参数1:
value: 属性值
参数2:
writable:对象属性值是否可以被修改 true允许 false不允许
参数3:
configurable: 对象属性是否可以被删除 true允许 false不允许
参数4:
enumerable: 对象属性是否可被枚举
参数5:
get(): 给一个属性提供getter方法,当访问这个对象的属性值得时候触发该方法
参数6:
set(): 给一个属性提供setter方法,当设置属性值得时候触发该方法
-
get
当访问对象中的属性时,会调用getter函数。执行时不传入任何参数,但是会传入 this 对象(由于继承关系,这里的this并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值
-
set
当属性值被修改时,会调setter函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的 this 对象。默认为 undefined。
-
Object.defineProperty 监听简单的对象
// 触发更新视图
function updateView() {
console.log('更新视图')
}
const obj = {}
function defineReactive (obj,key,value){
Object.defineProperty(obj,key,{
get(){
console.log(key,value,'get')
return value
},
set(newVal){
if (newVal !== value) {
value= newVal
updateView()
console.log(key,value,'set')
}
}
})
}
defineReactive(obj, 'name','zhu') // 添加属性 name
obj.name='li' // 修改name属性,调用set 方法
console.log(obj.name,'obj') // 读取name属性,调用get
-
Object.defineProperty 监听复杂的对象
const obj= {
name: 'zhu',
age: 20,
info: {
address: '北京' // 需要深度监听
},
}
function updateView() {
console.log('更新视图')
}
function defineReactive (obj,key,value){
Object.defineProperty(obj,key,{
get(){
console.log(key,value,'get')
return value
},
set(newVal){
if (newVal !== value) {
value= newVal
updateView()
console.log(key,value,'set')
}
}
})
}
// 监听对象属性
function observer(obj) {
if (typeof obj !== 'object' || obj=== null) {
// 不是对象或数组
return obj
}
// 循环定义各个属性
for (let key in obj) {
defineReactive(obj, key, obj[key])
}
}
// 监听数据
observer(obj)
obj.name='li' // 调用set,改变name为li
console.log(obj.name,'name') // 调用get
obj.info.address='上海' //没有调用set
console.log(obj.info.address,'address') // 结果输出北京,并没有调用set,也就是说该属性没有被监听到
obj.age= {num:22} // 此时的data.age = {num:21}可以监听到
console.log(obj.age.num) // 没有调用get,也就是说该属性没有被监听到
-
Object.defineProperty 监听复杂的多层嵌套的对象
const obj= {
name: 'zhu',
age: 20,
info: {
address: '北京' // 需要深度监听
},
}
function updateView() {
console.log('更新视图')
}
function defineReactive (obj,key,value){
// 深度监听
observer(value)
Object.defineProperty(obj,key,{
get(){
console.log(key,value,'get')
return value
},
set(newVal){
if (newVal !== value) {
// 深度监听
observer(value)
value= newVal
updateView()
console.log(key,value,'set')
}
}
})
}
// 监听对象属性
function observer(obj) {
if (typeof obj !== 'object' || obj=== null) {
// 不是对象或数组
return obj
}
// 循环定义各个属性
for (let key in obj) {
defineReactive(obj, key, obj[key])
}
}
// 监听数据
observer(obj)
obj.info.address='上海' // 调用了set,改变了属性的value
console.log(obj.info.address,'address') // 结果输出上海,也就是说该属性被监听到
obj.age={num:28} // // 调用了set,改变了属性的value
console.log(obj.age.num,'0') // 调用了get方法,也就是说该属性被监听到
-
Object.defineProperty 监听的缺点
- 深度监听,需要递归到底,一次性计算量大
- 新增属性,监听不到 —— 使用Vue.set
// 上面例子
obj.sex='男' // 并没有出发set
console.log(obj.sex) // 没有出发get
// 解决方法 Vue.set
Vue.set(obj.'sex','男')
- 删除属性,监听不到 —— 使用Vue.set
// 上面例子
delete obj.age
// 解决方法 Vue.set
Vue.delete(obj.'age')
- 数组,监听不到 —— 使用push()、pop()、shift()、unshift()、splice()、sort()、reverse() 7种方法
Object.defineProperty本身是可以监控到数组下标的变化的,但是在 Vue 中,从性能/体验的性价比考虑,尤大大就弃用了这个特性。