基础概念
Object.defineProperty
Object.defineProperty(..) 是用来添加一个新属性或者修改一个已有属性(如果它是 configurable)并对特性进行设置。
语法
Object.defineProperty(obj, prop, descriptor)
var obj = {}
Object.defineProperty(obj, 'a', {
value: 2,
writable: true,
configurable: true,
enumerable: true,
get: function() {
return this._a
},
set: function(val) {
this._a = val + 2
}
})
obj.a // 2
函数的第三个参数 descriptor 所表示的属性描述符有两种形式:数据描述符和存取描述符。
数据描述符和存取描述符都有的属性有:
- configurable
- enumerable 数据描述符可选属性有:
- value
- writable 存取描述符可选键值有:
- get
- set
在了解vue如何实现响应式之前,我们先要知道几个概念:
- 属性描述符
Object.defineProperty[[GET]]、[[PUT]]- 访问描述符
什么是属性描述符
es5之前,js语言本身没有提供可以直接检测属性特性的方法。从es5开始,所有的属性都具备了属性描述符。
var obj = {
m: 1
}
Object.getOwnPropertyDescriptor(obj, "m")
// {
// value: 1,
// writable: true,
// enumerable: true,
// configurable: true
// }
这就是属性描述符,除了数值2,还包含另外三个特性:writable(可写)、 enumerable(可枚举)和 configurable(可配置)
在创建普通对象时,属性描述符会使用默认值,都为true。也可以使用Object.defineProperty对其属性描述符进行设置。
[[GET]]
属性访问时,并非仅仅在对象中查找某个属性,参见下例,obj.m实则是在obj上实现了[[GET]]操作(类似函数调用:[[GET]]())
对象默认的内置 [[Get]] 操作首先在对象中查找是否有名称相同的属性, 如果找到就会返回这个属性的值。如果没有找到,就会进行原型链的查找
var obj = {
m: 1
}
obj.m // 1
如果无论如何都找不到,[[Get]] 操作就会返回undefined
[[PUT]]
与[[Get]]相对应的,就是[[PUT]]操作。
给属性赋值时,会触发[[PUT]],但能否[[PUT]]成功,还取决于属性描述符writable和访问描述符setter
什么是访问描述符
给一个属性定义 getter、setter 或者两者都有时,这个属性会被定义为“访问描述 符”(和“数据描述符”相对)
在 ES5 中可以使用 getter 和 setter 部分改写默认操作,但是只能应用在单个属性上,无法 应用在整个对象上。
- getter 是一个隐藏函数,会在获取属性值时调用
- setter 也是一个隐藏函数,会在设置属性值时调用
var obj = {
get m() {
return 2
}
}
obj.m = 3
obj.m // 2
当定义了getter函数时,赋值操作是没有意义的,除非再定义setter。setter 会覆盖单个属性默认的 [[Put]](也被称为赋值)操作。通常来说 getter 和 setter 是成对出现的(只定义一个的话 通常会产生意料之外的行为)
var obj = {
get m() {
return this._m
}
set m(val) {
this._m = val * 2
}
}
obj.m = 3
obj.m // 6
什么是响应式
- 修改data属性之后,vue立刻监听到,立刻被渲染到页面上
- 获取属性,如何监听到
- 赋值属性,如何监听到
- data属性被代理到vm上
- 为什么在data中定义的属性,可以用vm访问并操作
var vm = new Vue({ el: '#app', data: { name: 'wowo', age: 20 } }) vm.name // wowo vm.age // 20 vm.age = 22 vm.age // 22
vue2如何实现响应式
Object.defineProperty
var vm = {}
var data = {
name: 'wowo'
age: 20,
}
var key, value
for(key in value) {
(function watch(key) {
Object.defineProperty(vm, key, {
get: function() {
console.log('get') // 监听获取属性
return data[key]
}
set: function(val) {
console.log('set') // 监听赋值属性
data[key] = val
}
})
})(key)
}
vue3为何改用proxy
vue3改用proxy,是为了解决vue2使用defineProperty的缺陷 Object.defineProperty的缺陷:
-
Object.defineProperty无法监听数组的变化
vue中是可以监听数组的变化的,是提供了以下方法
push() pop() shift() unshift() splice() sort() reverse() -
Object.defineProperty只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历,如果属性值也是对象那么需要深度遍历,性能不高
proxy可以解决这两个缺陷:
- 可以监听到数组的变化
- proxy可以监听对象而非属性。对该对象的访问,都必须先通过proxy这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy直接可以劫持整个对象,并返回一个新对象。提升了性能。
proxy 最大的问题是浏览器支持度不够,而且很多效果无法使用 poilyfill 来弥补。