содержание
- Тип свойства объекта
- Длина массива и индекс
- Взлом Vue методов массива
тип недвижимости
Мы знаем, что объект — это неупорядоченный набор свойств, и есть 3 способа создать объект, содержащий свойства:
- Конструктор
- буквальный
- defineProperty
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
}
})
Давайте сначала поговорим о разнице, а затем рассмотрим типы атрибутов.
Типы атрибутов делятся на
- атрибут данных
- свойство доступа
Атрибуты, определенные в спецификации ECMA в 2 квадратных скобках, представляют собой внутренние атрибуты.
то же самое, есть
-
[[Configurable]]Буквальное понимание заключается в том, чтобы указать, может ли атрибут быть сконфигурирован — может ли атрибут быть изменен, может ли атрибут быть удален с помощью удаления, может ли атрибут быть изменен как атрибут доступа. -
[[Enumerable]]может пройтиfor-inВернитесь к этому свойству.
разница
- атрибут данных
-
[[Writable]]доступный для записи -
[[Value]]стоимость имущества
-
- свойство доступа
-
[[Get]]Функция значения -
[[Set]]функция присваивания
-
Далее, давайте посмотрим на разницу между созданием атрибута
- Первый и второй типы присваивания свойств одинаковы, разница заключается в способе создания объекта. В использовании
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 не может определить изменение длины массива, если быть точнымпутем изменения длиныУвеличение длины не может быть отслежено.
Нам нужно понять 2 понятия, а именно длину массива и индекс массива.
массивlengthсвойство, инициализированное как
enumberable: false
configurable: false
writable: true
То есть попытка удалить и изменить (не присвоить) свойство длины не сработает.
Индекс массива — это способ доступа к значению массива.Если вы сравниваете его с объектом, индекс — это ключ свойства массива, который представляет собой два разных понятия длины.
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
}
Когда мы помещаем значение в массив, мы присваиваем значение length
Связь между длиной и числовыми индексами- Существует тесная связь между свойством длины массива JavaScript и его числовым индексом. Некоторые встроенные методы работы с массивами (такие как join, slice, indexOf и т. д.) учитывают значение длины. Существуют также некоторые методы (такие как push, splice и т. д.), которые также изменяют значение длины.
Эти встроенные методы будут изменять значение длины при манипулировании массивом.Есть два случая.
- Уменьшить значение
- Когда мы сдвигаем массив, вы обнаружите, что он будет проходить по массиву (код проверен ниже), и значение, соответствующее индексу массива, обновляется соответствующим образом.В этом случае можно отслеживать defineProperty, потому что есть свойства ( индекс) существует.
- Добавленная стоимость
- При проталкивании значения длина массива будет +1, и индекс тоже будет +1, но индекс в это время новый, хотя defineProperty не может отслеживать новое свойство, но в vue новое свойство объекта может быть отображен вызов
vm.$setдобавить мониторинг - Вручную присваивая length большему значению, длина в это время будет обновляться, но соответствующий индекс не будет присвоен, то есть объект не имеет свойств, а defineProperty не может справиться с мониторингом неизвестных свойств, каким бы мощным он ни был.
- При проталкивании значения длина массива будет +1, и индекс тоже будет +1, но индекс в это время новый, хотя defineProperty не может отслеживать новое свойство, но в vue новое свойство объекта может быть отображен вызов
Проверить влияние нескольких внутренних методов массива на индекс
// 还是老套路,定义一个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, вы обнаружите, что массив просматривается в процессе печати
Процесс печати можно понимать как
- Найдите место в памяти, на которое указывает тестовая переменная, в виде массива длиной 3 и распечатайте его, но не знаете, каково значение, соответствующее индексу.
- указатель хода
Далее делаем следующее
- При пуше добавлялся индекс и менялась длина, но нового индекса не наблюдалось
- Измените значение, соответствующее новому индексу
- Всплывающее значение, соответствующее новому индексу
- Когда всплывает значение наблюдаемого индекса, запускается get
- В этот момент, когда я присваиваю значение исходному индексу, я обнаруживаю, что наблюдаемый набор не срабатывает. Видно, что после удаления индекса массива он не будет наблюдаться. Являются ли свойства объекта одинаковыми ? То же самое можно увидеть на рисунке ниже
- Измените значение индекса 1, набор срабатывания
- При несмещении значения с индексами 0 и 1 проходятся и сохраняются, а затем переназначаются
Когда мы присваиваем значение длине, мы видим, что мы не просматриваем массив для присвоения индекса.
резюме
Для defineProperty обработка массивов и объектов такая же, только переписывается во время инициализацииgetа такжеsetЧтобы отслеживать изменения в массивах или объектах, новые свойства необходимо повторно инициализировать вручную. Для массивов просто немного особенный.Значения push и unshift также добавят индекс.Для нового индекса можно также добавить наблюдение для достижения эффекта мониторинга;значения pop и shift удалит индекс обновить индекс и вызвать функцию defineProperty.get и set. Для массива, длина которого переназначается, новый индекс добавляться не будет, потому что непонятно, сколько новых индексов добавляется, согласноecmaСпецификация определяет, что максимальное значение индекса равно2^32 - 1, нельзя циклом присвоить индекс.
ссылка выше
Что заставило меня задуматься над этим вопросом
что мне помоглоЗнай почтиОтвет от @liuqipeng
Взлом Vue методов массива
Vue обрабатывает массив отдельно
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 не может обнаружить «изменение» длины массива