咱们知道对象是一个无序属性集合,建立一个包含属性的对象有3种方式:vue
var object1 = new Object()
object1.name = 'a'
var object2 = {}
object2.name = 'b'
var object3 = {}
Object.defineProperty(object3, 'name', {
enumerable: true,
configurable: true,
get() {
return 'c'
},
set() {
// do
}
})
复制代码
区别咱们先讲完属性类型后再来看。git
属性类型分为github
ECMA规范中定义放在2对方括号中的属性表示内部属性数组
相同点,都有bash
[[Configurable]]
字面理解是表示属性是否可配置——可否修改属性;可否经过delete删除属性;可否把属性修改成访问器属性。[[Enumerable]]
可否经过for-in
循环返回该属性。区别闭包
[[Writable]]
是否可写[[Value]]
属性的值[[Get]]
取值函数[[Set]]
赋值函数接着来看属性建立的区别app
object.name
赋值的时候,咱们实际上是对数据属性[[Value]]
赋值,取值也是同样object.name
取值赋值时,是经过访问器属性的[[Get]]
和[[Set]]
函数使用defineProperty注意点函数
// 假设咱们想修改a的值为123
var object = { a: 1 }
Object.defineProperty(object, 'a', {
enumerable: true,
configurable: true,
get() {
// 不能在函数中引用属性a,不然会形成循环引用
// 错误
return this.a + '23'
// 正确
return val + '23'
},
set(newVal) {
// 为了在原属性值的基础上修改属性,咱们能够利用闭包的特性
// 在初始化对象的时候会调用set函数,此时将属性(例如a)的值用闭包保存起来
// 接着取值的时候,就利用闭包中变量的值修改便可
val = newVal
}
})
// 其实也就是一个先赋值再取值修改的过程
复制代码
以上有感于vue早期源码学习系列之一:如何监听一个对象的变化学习
咱们知道vue对于监测数组的变化重写了数组的原型以达到目的,缘由是defineProperty不能检测到数组长度的变化,准确的说是经过改变length而增长的长度不能监测到。ui
咱们须要理解2个概念,即数组长度与数组索引
数组的length
属性,被初始化为
enumberable: false
configurable: false
writable: true
复制代码
也就是说,试图去删除和修改(并不是赋值)length属性是行不通的。
数组索引是访问数组值的一种方式,若是拿它和对象来比较,索引就是数组的属性key,它与length是2个不一样的概念。
var a = [a, b, c]
a.length = 10
// 只是显示的给length赋值,索引3-9的对应的value也会赋值undefined
// 可是索引3-9的key都是没有值的
// 咱们能够用for-in打印,只会打印0,1,2
for (var key in a) {
console.log(key) // 0,1,2
}
复制代码
当咱们给数组push值后,会给length赋值
length 和数字下标之间的关系 —— JavaScript 数组的 length 属性和其数字下标之间有着紧密的联系。数组内置的几个方法(例如 join、slice、indexOf 等)都会考虑 length 的值。另外还有一些方法(例如 push、splice 等)还会改变 length 的值。
这几个内置的方法在操做数组时,都会改变length的值,分2种状况
vm.$set
来添加监听验证数组的几个内部方法对索引的影响
// 仍是老套路,定义一个observe方法
function defineReactive(data, key, val) {
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function defineGet() {
console.log(`get key: ${key} val: ${val}`)
return val
},
set: function defineSet(newVal) {
console.log(`set key: ${key} val: ${newVal}`)
// 还记得咱们上面讨论的闭包么
// 此处将新的值赋给val,保存在内存中,从而达到赋值的效果
val = newVal
}
})
}
function observe(data) {
Object.keys(data).forEach(function(key) {
defineReactive(data, key, data[key])
})
}
let test = [1, 2, 3]
// 初始化
observe(test)
复制代码
console.log
时,你会发如今打印的过程当中是遍历这个数组的
打印的过程能够理解为
接下来咱们作以下操做
当咱们给length赋值时,能够看见并不会遍历数组去赋值索引。
小结
对于defineProperty来讲,处理数组与对象是一视同仁的,只是在初始化时去改写get
和set
达到监测数组或对象的变化,对于新增的属性,须要手动再初始化。对于数组来讲,只不过特别了点,push、unshift值也会新增索引,对于新增的索引也是能够添加observe从而达到监听的效果;pop、shift值会删除更新索引,也会触发defineProperty的get和set。对于从新赋值length的数组,不会新增索引,由于不清楚新增的索引有多少,根据ecma
规范定义,索引的最大值为2^32 - 1
,不可能循环去赋值索引的。
以上参考
引起我对这个问题的思考是
对我有所帮助是知乎@liuqipeng的回答
vue对数组的observe单独作了处理
if (Array.isArray(value)) {
const augment = hasProto
? protoAugment
: copyAugment
// 判断数组实例是否有__proto__属性,有就用protoAugment
// 而protoAugment司机就是重写实例的__proto__
// target.__proto__ = src
// 将新的arrayMethods重写到value上
augment(value, arrayMethods, arrayKeys)
// 而后初始化observe已存在索引的值
this.observeArray(value)
} else {
this.walk(value)
}
复制代码
再来看如何重写的arrayMethods
,在array.js
中,咱们能够看到
const arrayProto = Array.prototype
// 复制了数组构造函数的原型
// 这里须要注意的是数组构造函数的原型也是个数组
// 实例中指向原型的指针__proto__也是个数组
// 数组并无索引,由于length = 0
// 相反的拥有属性,属性名为数组方法,值为对应的函数
export const arrayMethods = Object.create(arrayProto)
// 对如下方法重写
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
复制代码
以下图,当我给__proto__
索引为0赋值时,是正常的,可是其他的属性依旧在后面。咱们能够这样认为,数组的构造函数的原型是个空数组,可是默认给你内置了几个方法。
咱们再来看为何只对这些方法重写?
methodsToPatch.forEach(function (method) {
// cache original method
const original = arrayProto[method]
// 这里的def很重要,其实也就是用object.defineProperty从新定义属性
// 但这里的arrayMethods是个数组,这就是为何上面咱们解释
// 数组构造函数原型是个空数组可是默认了属性方法
// 因此这里的定义是很巧妙的
def(arrayMethods, method, function mutator (...args) {
const result = original.apply(this, args)
// ob就是observe实例
const ob = this.__ob__
let inserted
switch (method) {
// 为何对push和unshift单独处理?
// 咱们在上看解释过,这2中方法会增长数组的索引,可是新增的索引位须要手动observe的
case 'push':
case 'unshift':
inserted = args
break
// 同理,splice的第三个参数,为新增的值,也须要手动observe
case 'splice':
inserted = args.slice(2)
break
}
// 其他的方法都是在原有的索引上更新,初始化的时候已经observe过了
if (inserted) ob.observeArray(inserted)
// notify change
// 而后通知全部的订阅者触发回调
ob.dep.notify()
return result
})
})
复制代码
最后,仍是贴一波博客地址为何defineProperty不能检测到数组长度的“变化”