знакомыйvueВсе ваши друзья должны знать, что когда дело доходит доvueПринцип работы, самое главное: отзывчивый, виртуальныйdomа такжеdiffАлгоритмы, компиляция шаблонов, сегодня углубимся вместеvueотзывчивость, исследуетvue2.xПринципы и недостатки реактивной реализации, а такжеvue3.0Как версия переписывает реактивную реализацию.
1. Что отзывчиво
vueЯвляетсяMVVMРамки, так называемыйMVVM, Ядром является представление, управляемое данными, популярная вещь в том, что пользователь не работает напрямуюdom, но манипулируя данными, когда данные изменяются,vueВнутренне прослушивает изменения данных, а затем обновляет представление. Точно так же действия пользователя (события) в представлении, в свою очередь, изменят данные. С другой стороны, отзывчивость — это первый шаг в реализации представления, управляемого данными, то есть в отслеживании изменений данных, чтобы пользователи могли уведомлять пользователей при настройке данных.vueОбновление внутреннего вида
Например
<template>
<div>
<div> {{ name }} </div>
<button @click="changeName">改名字</button>
</div>
</template>
<script>
export default {
data () {
return {
name: 'A'
}
},
methods: {
changeName () {
this.name = 'B'
}
}
}
</script>
Над кодом нажмитеbuttonПосле кнопки,nameсвойства изменятся, и страница отобразитсяAстанетB
2. vue2.xРеализовать отзывчивый
2.1 Основной API --- Object.defineProperty()
Я думаю, что большинство людей знали vue, они должны знать более или менее, суть отзывчивости vue заключается в том,Object.defineProperty(), вот краткий обзор
const data = {}
let name = 'A'
Object.defineProperty(data, 'name', {
get () {
return name
},
set (val) {
name = val
}
})
console.log(data.name) // get()
data.name = 'B' // set()
Как видно из приведенного выше кода, использование Object.defineProperty() заключается в определении свойства (метода) для объекта и предоставлении двух внутренних реализаций set и get, чтобы мы могли получить или установить это свойство (метод )
2.2 Как реализовать отзывчивость
Во-первых, мы определяем начальные данные следующим образом
const data = {
name: 'A',
age: 18,
isStudent: true,
gender: 'male',
girlFriend: {
name: 'B',
age: '19',
isStudent: true,
gender: 'female',
parents: {
mother: {
name: 'C',
age: '44',
isStudent: false,
gender: 'female'
},
father: {
name: 'D',
age: '46',
isStudent: false,
gender: 'male'
}
}
},
hobbies: ['basketball', 'one-piece', 'football', 'hiking']
}
Мы также определяем метод, который отображает представление
function renderView () {
// 数据变化时,渲染视图
}
И основной метод, реализующий отзывчивость, этот метод принимает три параметра:targetсам объект данных,keyа такжеvalueявляется объектомkeyи соответствующийvalue
function bindReactive (target, key, value) {
}
Наконец, мы определяем метод входа, который реализует отзывчивость.
function reactive () {
// ...
}
Наш последний звонок
const reactiveData = reactive(data)
2.2.1 Для примитивных типов и объектов
В приведенных выше данных мы имитируем простое введение информации о человеке, и мы видим, что значения разрыва слов объектов включают строки, числа, логические значения, объекты и массивы. Для примитивных типов, таких как строки, числа и логические значения, мы можем просто вернуть их напрямую.
function reactive (target) {
// 首先,不是对象直接返回
if (typeof target !== 'object' || target === null) {
return target
}
}
const reactiveData = reactive(data)
Если значение поля является ссылочным типом, таким как объект, нам нужно обойти объект и установить каждое ключевое значение объекта для выполненияObject.defineProperty(), обратите внимание, что эту процедуру необходимо вызывать рекурсивно, поскольку, как показано в приведенных нами данных, объекты могут быть вложены в несколько слоев. мы определяем функциюbindReactiveописать процесс ответного прослушивания объектов
function bindReactive (target, key, value) {
Object.defineProperty(target, key, {
get () {
return value
},
set (val) {
value = val
// 触发视图更新
renderView()
}
})
}
function reactive (target) {
// 首先,不是对象直接返回
if (typeof target !== 'object' || target === null) {
return target
}
// 遍历对象,对每个key进行响应式监听
for (let key in target) {
bindReactive(target, key, target[key])
}
}
const reactiveData = reactive(data)
Учитывая рекурсию, нам нужно выполнить основной методbindReactiveВ начале рекурсивный вызовreactiveРеактивное прослушивание свойств объекта и рекурсивные вызовы при установке (обновлении) данныхreactiveupdate, поэтому наш основной методbindReactiveстали
function bindReactive (target, key, value) {
reactive(value)
Object.defineProperty(target, key, {
get () {
return value
},
set (val) {
reactive(val)
value = val
// 触发视图更新
renderView()
}
})
}
function reactive (target) {
// 首先,不是对象直接返回
if (typeof target !== 'object' || target === null) {
return target
}
// 遍历对象,对每个key进行响应式监听
for (let key in target) {
bindReactive(target, key, target[key])
}
}
const reactiveData = reactive(data)
Приведенный выше код можно оптимизировать за один шаг, то есть при установке, если новое установленное значение совпадает с предыдущим значением, обновление представления не будет запущено, поэтому наш метод становится
function bindReactive (target, key, value) {
reactive(value)
Object.defineProperty(target, key, {
get () {
return value
},
set (newVal) {
if (newVal !== value) {
reactive(newVal)
value = newVal
// 触发视图更新
renderView()
}
}
})
}
function reactive (target) {
// 首先,不是对象直接返回
if (typeof target !== 'object' || target === null) {
return target
}
// 遍历对象,对每个key进行响应式监听
for (let key in target) {
bindReactive(target, key, target[key])
}
}
const reactiveData = reactive(data)
На данный момент мы реализовали адаптивный мониторинг для примитивных типов и объектов, при изменении данных будет вызываться метод renderView (этот метод может делать что угодно) для обновления представления после обновления данных.
2.2.2 для массивов
Очевидно, хотяObject.defineProperty()Хорошо сделано для отзывчивого прослушивания примитивных типов и простых объектов, но этот метод бессилен для массивов. Итак, как Vue реализует оперативный мониторинг массивов?
Сначала мы снова вернемся к официальной документации vue.
Как видите, vue выполняет массивpush, pop, shift, unshiftВ ожидании метода вы можете оперативно отслеживать изменения массива, тем самым запуская обновление представления.
Но все мы знаем, что эти родные для массива методы не имеют возможности оперативно обновлять представление, поэтому мы можем это знать,vueЭти методы массива надо было переписать, поэтому теперь проблема изменилась с того, как реализовать отзывчивость массива, как переписать апи массива.
Здесь используется основной методObject.create(prototype), этот метод заключается в создании объекта, прототип которого указывает на параметрprototype, поэтому мы также можем переписать эти методы массива:
// 数组的原型
const prototype = Array.prototype
// 创建一个新的原型对象,他的原型是数组的原型(于是newPrototype上具有所有数组的api)
const newPrototype = Object.create(prototype)
const methods = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']
methods.forEach(method => {
newPrototype[method] = () => {
prototype[method].call(this, ...args)
// 视图更新
renderView()
}
})
Реализовали отзывчивость массива и улучшили метод входаreactive
function bindReactive (target, key, value) {
reactive(value)
Object.defineProperty(target, key, {
get () {
return value
},
set (newVal) {
if (newVal !== value) {
reactive(newVal)
value = newVal
// 触发视图更新
renderView()
}
}
})
}
function reactive (target) {
// 首先,不是对象直接返回
if (typeof target !== 'object' || target === null) {
return target
}
// 对于数组,原型修改
if (Array.isArray(target)) {
target.__proto__ = newPrototype
}
// 遍历对象,对每个key进行响应式监听
for (let key in target) {
bindReactive(target, key, target[key])
}
}
const reactiveData = reactive(data)
До сих пор мы объясняли принцип отзывчивости версии vue2.x.
2.3 Недостатки адаптивной реализации версии vue2.x
В ходе нашего анализа мы также увидели недостатки адаптивной реализации версии vue2.x:
-
Object.defineProperty()Этот API не может нативно отслеживать массивы в ответ. - В процессе реализации для глубоко вложенных данных рекурсия потребляет много производительности.
- Мы заметили,
Object.defineProperty()У этой реализации, как и у реализации массивов, есть проблема, то есть нет возможности отслеживать последующее ручное добавление и удаление элементов атрибута, таких как массивы, установка и изменение значения напрямую через индекс не вызовет срабатывания view update, конечно же, vue предоставляет намvue.setа такжеvue.deleteТакойapi, но неудобно ведь
3. vue3.0Реализовать отзывчивый
недавноvue3.0Он также был официально выпущен, и хотя официально он не рекламировался, некоторые изменения в нем заслуживают нашего внимания и изучения.
3.1 Proxyа такжеReflect
Из-за проблем с отзывчивой реализацией версии vue2.x,vueОфициальный полностью переписал адаптивную реализацию в версии 3.0, используяProxyа такжеReflectзаменятьObject.defineProperty().
3.1.1 Proxy
Сначала взгляните на определение прокси MDN:
The Proxy object is used to define custom behavior for fundamental operations(e.g. property lookup, assignment, enumeration, function invocation, etc).
Перевод на китайский язык, вероятно, таков: прокси-объекты используются для определения пользовательского поведения для некоторых основных операций (таких как поиск, присваивание, перечисление, вызовы функций и т. д.). Основное использование:
let proxy = new Proxy(target, handler)
Значение вышеуказанных параметров: (Примечаниеtargetможет быть собственным массивом)
-
target: использоватьProxyОбернутый целевой объект (может быть любым типом объекта, включая原生数组, Функция или даже другое агентство). -
handler: объект, свойствами которого являются функции, определяющие поведение агента при выполнении операции.
Возьмите каштан:
let handler = {
get: function(target, name){
return name in target ? target[name] : 'sorry, not found';
}
};
let p = new Proxy({}, handler);
p.a = 1;
p.b = undefined;
console.log(p.a, p.b); // 1, undefined
console.log('c' in p, p.c); // false, 'sorry, not found'
3.1.2 Reflect
Сначала взгляните на определение Reflect в MDN:
Reflect is a built-in object that provides methods for interceptable JavaScript operations. The methods are the same as those of proxy handlers. Reflect is not a function object, so it's not constructible.
Вероятно, это означает: Reflect — это встроенный объект, предоставляющий методы для перехвата операций JavaScript. Эти методы аналогичны обработчикам прокси. Reflect не является функциональным объектом, поэтому его нельзя построить.
Объект Reflect предоставляет множество методов, вот лишь несколько распространенных методов, используемых для реализации адаптивности:
-
Reflect.get(): получить значение свойства объекта, аналогичноtarget[name]. -
Reflect.set(): Функция для присвоения значения свойству. вернутьBoolean, если обновление прошло успешно, вернутьtrue. -
Reflect.has(): чтобы определить, есть ли у объекта атрибут, иinФункция оператора точно такая же. -
Reflect.deleteProperty(): Как оператор удаления функции, он эквивалентен выполнению удаления цели [имя].
Следовательно, мы можем объединитьProxyа такжеReflectПолное отзывчивое прослушивание
3.2 Proxyа такжеReflectРеализовать отзывчивый
Код непосредственно размещен ниже, и метод, который мы реализовали ранее, изменен:
function bindReactive (target) {
if (typeof target !== 'object' || target == null) {
// 不是对象或数组,则直接返回
return target
}
// 因为Proxy原生支持数组,所以这里不需要自己实现
// if (Array.isArray(target)) {
// target.__proto__ = newPrototype
// }
// 传给Proxy的handler
const handler = {
get(target, key) {
const reflect = Reflect.get(target, key)
// 当我们获取对象属性时,Proxy只会递归到获取的层级,不会继续递归子层级
return bindReactive(reflect)
},
set(target, key, val) {
// 重复的数据,不处理
if (val === target[key]) {
return true
}
// 这里可以根具是否是已有的key,做不同的操作
if (Reflect.has(key)) {
} else {
}
const success = Reflect.set(target, key, val)
// 设置成功与否
return success
},
deleteProperty(target, key) {
const success = Reflect.deleteProperty(target, key)
// 删除成功与否
return success
}
}
// 生成proxy对象
const proxy = new Proxy(target, handler)
return proxy
}
// 实现数据响应式监听
const reactiveData = bindReactive(data)
Из приведенного выше кода мы видим, что дляvue2.xХорошо решены проблемы отзывчивости:
-
ProxyПоддержка мониторинга собственных массивов -
ProxyДанные сбора данных будут возвращаться только к тому уровню, который необходимо получить, и не будут продолжать повторяться. -
ProxyМожет отслеживать ручное добавление и удаление данных
Разве это неvue3.0Отзывчивое решение идеально, ответ - нет, основная причина в том, чтоProxyа такжеReflectпроблемы с совместимостью браузера и не может бытьpolyfill.
4. Резюме
В данной статье подробно анализируетсяvueпринцип отзывчивости, для2.xа также3.0Есть отличия в реализации версий, и у каждой есть свои преимущества и недостатки.Ни одно решение не идеально.Я верю, что в будущем, когда проблем с совместимостью браузеров будет все меньше и меньше, жизнь будет лучше!