объект вне досягаемости
Как мы все знаем, вVue
, прямое изменение значения свойства объекта не может вызвать реакцию. При непосредственном изменении значения свойства объекта вы обнаружите, что изменились только данные, но содержимое страницы не изменилось.
Какова причина?
причина в следующем:Vue
Реагирующая система основана наObject.defineProperty
Этот метод может отслеживать приобретение или изменение элемента в объекте, а данные, обрабатываемые этим методом, называются реагирующими данными. Однако у этого метода есть большой недостаток: добавление или удаление атрибутов не приведет к запуску мониторинга, например:
var vm = new Vue({
data () {
return {
obj: {
a: 1
}
}
}
})
// `vm.obj.a` 现在是响应式的
vm.obj.b = 2
// `vm.obj.b` 不是响应式的
Причина в том, что вVue
При инициализацииVue
внутреннее собраниеdata
Возвращаемое значение метода глубоко реагирует, чтобы сделать его чувствительными данными, поэтомуvm.obj.a
отзывчив. Однако после установкиvm.obj.b
не прошелVue
Отзывчивое крещение при инициализации, поэтому не должно быть отзывчивым.
Так,vm.obj.b
Можно ли сделать его отзывчивым? Конечно, поvm.$set
Метод вполне соответствует требованиям, я не буду здесь повторять соответствующие принципы, я должен написать статью об этом позже.vm.$set
Обоснование позади.
более убогий массив
Сказав так много выше, главный герой этой статьи, массив, еще не был упомянут, теперь пришло время появиться главному герою.
По сравнению с объектами ситуация с массивами еще более плачевна, взгляните на официальную документацию:
Из-за ограничений JavaScript,
Vue
Следующие измененные массивы не могут быть обнаружены:
- Когда вы используете индекс для прямой установки элемента, например:
vm.items[indexOfItem] = newValue
- Когда вы изменяете длину массива, например:
vm.items.length = newLength
Возможно, официальная документация не очень понятна, так что продолжим давать каштан:
var vm = new Vue({
data () {
return {
items: ['a', 'b', 'c']
}
}
})
vm.items[1] = 'x' // 不是响应性的
vm.items.length = 2 // 不是响应性的
Другими словами, массив не может даже следить за модификацией своих элементов, потому что,Vue
правильноdata
Когда элементы в объекте, возвращаемом методом, обрабатываются в ответ, если элемент является массивом, только сам массив будет обрабатываться в ответ, а внутренние элементы массива не будут обрабатываться в ответ.
Это также приводит к последствиям, как написано в официальной документации, и невозможно напрямую изменить внутренние элементы массива для срабатывания отзывчивости.
Итак, есть ли способ сломать его?
Конечно, официально предусмотрены методы массива 7. С помощью этих методов массива 7 вы можете с радостью вызвать ответ массива.7 методов массива:
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
Можно обнаружить, что эти 7 методов массива кажутся собственными методами массива.Почему эти 7 методов массива могут запускать приложение и запускать обновление представления?
Вы думаете: методы массива великолепны, методы массива могут делать все, что захотят?
Сури, эти 7 методов массива действительно могут делать все, что захотят.
Потому что это методы мутированного массива.
Идея мутации массива
Что такое метод мутирующего массива?
Метод мутирующего массива заключается в расширении функции метода массива при условии, что исходная функция метода массива остается неизменной.Vue
Так называемое расширение функций добавляет адаптивные функции.
Метод превращения обычного массива в мутированный массив состоит из двух шагов:
- Расширение функций
- захват массива
Расширение функций
Сначала мысленный вопрос:
Существует такое требование, чтобы «HelloWorld» можно было печатать в консоли каждый раз, когда вызывается функция, без изменения исходной функции и метода вызова.
На самом деле идея очень проста, разделена на три шага:
- Использовать новую оригинальную функцию кеша переменных
- переопределить исходную функцию
- Вызов исходной функции во вновь определенной функции
Взгляните на конкретную реализацию кода:
function A () {
console.log('调用了函数A')
}
const nativeA = A
A = function () {
console.log('HelloWorld')
nativeA()
}
Как видите, таким образом мы гарантируем, чтоA
Исходя из поведения функции, ее функция была расширена.
Далее мы используем этот метод для расширения функции исходного метода массива:
// 变异方法名称
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
const arrayProto = Array.prototype
// 继承原有数组的方法
const arrayMethods = Object.create(arrayProto)
mutationMethods.forEach(method => {
// 缓存原生数组方法
const original = arrayProto[method]
arrayMethods[method] = function (...args) {
const result = original.apply(this, args)
console.log('执行响应式功能')
return result
}
})
Как видно из кода, мы вызываемarrayMethods
Методы этого объекта имеют два случая:
- Метод расширения функции вызова: прямой вызов
arrayMethods
метод в - Вызов собственного метода: в этом случае найдите собственный метод, определенный в прототипе массива, через цепочку прототипов.
С помощью описанного выше метода мы реализовали расширение функции исходного метода массива, но перед нами стоит огромная проблема: как заставить экземпляр массива вызывать метод массива, расширенный функцией?
Решение этой проблемы: захват массива.
захват массива
Перехват массива по определению заключается в наследовании исходных методов экземпляра массива после замены метода расширения нашей функции.
Вдумайтесь, мы реализовали перед собой расширенный массивarrayMethods
, этот пользовательский массив наследуется от объекта массива, нам нужно только связать его с обычным экземпляром массива и позволить обычному массиву наследоваться от него.
Чтобы выполнить вышеуказанную операцию, она осуществляется через цепочку прототипов.
Метод реализации показан в следующем коде:
let arr = []
// 通过隐式原型继承arrayMethods
arr.__proto__ = arrayMethods
// 执行变异后方法
arr.push(1)
Путем расширения функций и перехвата массива мы, наконец, реализовали массив мутаций, давайте посмотримVue
Как исходный код реализует массив мутаций.
Анализ исходного кода
мы приходимsrc/core/observer/index.js
в серединеObserver
в классеconstructor
функция:
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
def(value, '__ob__', this)
// 检测是否是数组
if (Array.isArray(value)) {
// 能力检测
const augment = hasProto
? protoAugment
: copyAugment
// 通过能力检测的结果选择不同方式进行数组劫持
augment(value, arrayMethods, arrayKeys)
// 对数组的响应式处理
this.observeArray(value)
} else {
this.walk(value)
}
}
Observer
Этот классVue
Основной компонент отзывчивой системы, основная функция на этапе инициализации — сделать целевой объект отзывчивым. Здесь мы в основном сосредоточимся на обработке массивов.
Обработка массива в основном представляет собой следующий код
// 能力检测
const augment = hasProto
? protoAugment
: copyAugment
// 通过能力检测的结果选择不同方式进行数组劫持
augment(value, arrayMethods, arrayKeys)
// 对数组的响应式处理,很本文关系不大,略过
this.observeArray(value)
Сначала определитеaugment
константа, значение которой определяется выражениемhasProto
Принимать решение.
Давайте посмотримhasProto
:
export const hasProto = '__proto__' in {}
Его можно найти,hasProto
На самом деле это логическая константа, используемая для указания, поддерживает ли браузер прямое использование__proto__
(Неявный прототип).
Итак, первый фрагмент кода прост для понимания: выберите различные методы захвата массива в соответствии с результатом обнаружения возможности, если браузер поддерживает неявный прототип, затем вызовитеprotoAugment
использовать как метод захвата массива, в противном случае используйтеcopyAugment
.
Различные методы захвата массива
Теперь давайте посмотримprotoAugment
так же какcopyAugment
.
function protoAugment (target, src: Object, keys: any) {
/* eslint-disable no-proto */
target.__proto__ = src
/* eslint-enable no-proto */
}
можно увидеть,protoAugment
Функция предельно лаконична, и она согласуется с методом, упомянутым в идее мутации массива: напрямую подключать экземпляр массива к массиву мутаций через неявный прототип, и таким образом наследовать методы в массиве мутаций.
Далее мы смотрим наcopyAugment
:
function copyAugment (target: Object, src: Object, keys: Array<string>) {
for (let i = 0, l = keys.length; i < l; i++) {
const key = keys[i]
// Object.defineProperty的封装
def(target, key, src[key])
}
}
Поскольку в этом случае браузер не поддерживает прямое использование неявных прототипов, метод перехвата массива намного более громоздкий. Мы знаем, что первый параметр, полученный этой функцией, — это экземпляр массива, а второй параметр — массив мутаций, так что же такое третий параметр?
// 获取变异数组中所有自身属性的属性名
const arrayKeys = Object.getOwnPropertyNames(arrayMethods)
arrayKeys
Он определен в начале файла, то есть имена атрибутов всех собственных атрибутов в массиве мутаций, который является массивом.
оглядыватьсяcopyAugment
Функция очень понятна: определение всех методов в массиве мутаций непосредственно в самом экземпляре массива эквивалентно замаскированному захвату массива.
После реализации перехвата массива давайте посмотримVue
Как реализовать функцию расширения массива.
Расширение функций
Код расширения функции массива находится вsrc/core/observer/array.js
, код показан ниже:
import { def } from '../util/index'
// 缓存数组原型
const arrayProto = Array.prototype
// 实现 arrayMethods.__proto__ === Array.prototype
export const arrayMethods = Object.create(arrayProto)
// 需要进行功能拓展的方法
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
/**
* Intercept mutating methods and emit events
*/
methodsToPatch.forEach(function (method) {
// cache original method
// 缓存原生数组方法
const original = arrayProto[method]
// 在变异数组中定义功能拓展方法
def(arrayMethods, method, function mutator (...args) {
// 执行并缓存原生数组方法的执行结果
const result = original.apply(this, args)
// 响应式处理
const ob = this.__ob__
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
if (inserted) ob.observeArray(inserted)
// notify change
ob.dep.notify()
// 返回原生数组方法的执行结果
return result
})
})
Можно обнаружить, что способ реализации исходного кода такой же, как метод, который я использовал в идее мутации массива, за исключением того, что к нему добавлена адаптивная обработка.
Суммировать
Vue
Массив мутаций по сути является паттерном-декоратором, изучив его принципы, мы можем легко справиться с необходимостью расширения его функций, исходя из того, что в практической работе исходные функции остаются неизменными.