Происхождение: в привязке данных Vue отслеживаются изменения свойств объекта, а соответствующие обновления представления выполняются посредством сбора зависимостей и т. д.
Вопрос: Можно ли контролировать все типы изменения свойств объекта?
использовался раньшеObject.defineProperty
через объектgetter/setter
просто достичьОтслеживание изменений свойств объектаи просмотрите зависимости, чтобы выполнить соответствующую обработку зависимостей.
Однако это проблематично, особенно когда значением свойства в объекте является массив. Как сказано в документации Vue:
Из-за ограничений JavaScript Vue не может обнаружить следующие изменения массива:
- Когда вы устанавливаете элемент напрямую с помощью индекса, например.
vm.items[indexOfItem] = newValue
- Когда вы изменяете длину массива, например.
vm.items.length = newLength
отИсходный код VueВы также можете видеть, что массив действительно обрабатывается особым образом. Причина в том,Версии ES5 и ниже не могут обеспечить идеальное наследование массивов..
Эксперимент?
написано раньшеobserve
Был проведен простой эксперимент:
import { observe } from './mvvm'
const data = {
name: 'Jiang',
userInfo: {
gender: 0
},
list: []
}
// 此处直接使用了前面写好的 getter/setter
observe(data)
data.name = 'Solo'
data.userInfo.gender = 1
data.list.push(1)
console.log(data)
Результат таков:
По результатам видно проблемуdata
Значения атрибутов name, userInfo и list изменились, но изменения в списке массивов не произошли.observe
контролируется. какова причина? Проще говоря, метод манипулирования массивом, то естьArray.prototype
Метод, установленный выше, не запускает установщик свойства, потому что свойство не имеет операции присваивания.
Как решить эту проблему?
Способ решения этой проблемы во Vue — переписать общие методы массивов, а методы массивов после упаковки можно будет отслеживать при их вызове.
Здесь метод, который, как я думаю, похож на него, вероятно, состоит в том, чтобы перехватить работу массива через цепочку прототипов, чтобы реализовать мониторинг поведения при работе с массивом.
Реализация выглядит следующим образом:
// 让 arrExtend 先继承 Array 本身的所有属性
const arrExtend = Object.create(Array.prototype)
const arrMethods = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
/**
* arrExtend 作为一个拦截对象, 对其中的方法进行重写
*/
arrMethods.forEach(method => {
const oldMethod = Array.prototype[method]
const newMethod = function(...args) {
oldMethod.apply(this, args)
console.log(`${method}方法被执行了`)
}
arrExtend[method] = newMethod
})
export default {
arrExtend
}
нуждаться вdefineReactive
Код, добавленный в функцию:
if (Array.isArray(value)) {
value.__proto__ = arrExtend
}
есть тест:data.list.push(1)
Посмотрим на результаты:
Логика приведенного выше кода понятна с первого взгляда, и это также упрощение идей реализации в Vue. БудуarrExtend
Этот объект действует как перехватчик. Сначала пусть этот объект наследуетArray
Все атрибуты самого себя, чтобы это не повлияло на использование других атрибутов самого массива, а затем переписать соответствующую функцию, то есть после вызова исходного метода, чтобы уведомить другие связанные зависимости, что этот атрибут изменился, что такой же какObject.defineProperty
серединаsetter
Он делает почти то же самое, разница лишь в том, что его можно уточнить, какую именно операцию выполняет пользователь, меняется ли длина массива и т. д.
Есть ли другой способ?
В ES6 мы увидели освежающее свойство —Proxy
. Сначала рассмотрим понятия:
Вызывая new Proxy(), вы можете создать прокси для замены другого объекта (называемого целевым), прокси виртуализирует целевой объект, поэтому прокси и целевой объект могут казаться одним и тем же объектом для обработки.
Прокси позволяют перехватывать низкоуровневые операции над целевым объектом, что является внутренней возможностью движка JS. Поведение перехвата использует функцию (называемую ловушкой), которая реагирует на определенное действие.
Proxy
Как следует из названия, это означает прокси, то есть функцию, которая позволяет нам играть с объектами по желанию. когда мы проходимProxy
После проксирования объекта мы получим объект, практически идентичный проксируемому объекту, и сможем полностью мониторить этот объект.
Что такое полный мониторинг?Proxy
Это приводит к перехвату основной операции. Ранее мы использовали его, когда реализовывали объектный мониторинг.Object.defineProperty
, на самом деле это высокоуровневая операция, предоставляемая JS, то есть метод, предоставляемый после базовой инкапсуляции.Proxy
Строго говоря, мы можем напрямую перехватить базовую операцию объекта агента. Это эквивалентно достижению контроля над ним из базовой операции объекта.
Улучшить наш код?
const createProxy = data => {
if (typeof data === 'object' && data.toString() === '[object Object]') {
for (let k in data) {
if (typeof data[k] === 'object') {
defineObjectReactive(data, k, data[k])
} else {
defineBasicReactive(data, k, data[k])
}
}
}
}
function defineObjectReactive(obj, key, value) {
// 递归
createProxy(value)
obj[key] = new Proxy(value, {
set(target, property, val, receiver) {
if (property !== 'length') {
console.log('Set %s to %o', property, val)
}
return Reflect.set(target, property, val, receiver)
}
})
}
function defineBasicReactive(obj, key, value) {
Object.defineProperty(obj, key, {
enumerable: true,
configurable: false,
get() {
return value
},
set(newValue) {
if (value === newValue) return
console.log(`发现 ${key} 属性 ${value} -> ${newValue}`)
value = newValue
}
})
}
export default {
createProxy
}
Для свойств базового типа в объекте мы по-прежнему передаемObject.defineProperty
Реализовать отзывчивые свойства, потому что здесь нет болевой точки, а в реализации правильногоObject
При мониторинге свойств типа я использовал создание прокси, потому что наша предыдущая болевая точка заключалась в том, что мы не могли эффективно отслеживать изменения массива. Когда мы используем этот улучшенный метод, нам больше не нужно реализовывать мониторинг операций массива, переопределяя метод массива ранее, потому что в предыдущем методе много ограничений, мы не можем охватить все операции массива, и в то же время мы также не могу ответить на что-то вродеdata.array.length = 0
эта операция. После реализации через прокси все по другому. Мы можем отслеживать изменения в массиве снизу вверх. может дажеwatch
К изменению длины массива и всяким более подробным вещам. Это определенно решает большую проблему.
Давайте вызовем метод прямо сейчас, попробуйте?
let data = {
name: 'Jiang',
userInfo: {
gender: 0,
movies: []
},
list: []
}
createProxy(data)
data.name = 'Solo'
data.userInfo.gender = 0
data.userInfo.movies.push('星际穿越')
data.list.push(1)
Результат:
Результат отличный ~ мы реализовали мониторинг всех изменений свойств объектаProxy
Есть много других операций, таких как размещение прокси в качестве прототипа в цепочке прототипов, так что вы можете отслеживать только те свойства, которые не содержит подкласс, что очень мощно.Proxy
Его можно применять более широко, и существует множество сценариев. Тоже первый раз использую, надо закрепить ( ;´Д`)