Перейти к основному содержимому

[Hard] 📄 Двустороннее связывание данных

1. Объясните базовый принцип реализации двустороннего связывания в Vue2 и Vue3

Объясните внутренний механизм двустороннего связывания в Vue2 и Vue3.

Чтобы понять двустороннее связывание Vue, необходимо разобраться в том, как работает система реактивности и как Vue2 и Vue3 реализуют её по-разному.

Реализация Vue2

Vue2 использует Object.defineProperty для реализации реактивности. Он оборачивает свойства объекта с помощью getter и setter, чтобы отслеживать чтение/запись.

1. Перехват данных

При инициализации данных компонента в Vue2 Vue обходит каждое свойство и конвертирует его в getter/setter через Object.defineProperty, чтобы можно было наблюдать за чтением и записью.

2. Сбор зависимостей

Когда функция рендеринга выполняется и читает реактивные свойства, срабатывает getter. Vue записывает зависимости, чтобы затронутые компоненты могли быть уведомлены позже.

3. Отправка обновлений

Когда данные изменяются, срабатывает setter. Vue уведомляет зависимые watchers/компоненты и запускает перерисовку для обновления DOM.

Пример Vue2

function defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
console.log(`get ${key}: ${val}`);
return val;
},
set: function reactiveSetter(newVal) {
console.log(`set ${key}: ${newVal}`);
val = newVal;
},
});
}

const data = { name: 'Pitt' };
defineReactive(data, 'name', data.name);

console.log(data.name); // срабатывает getter
data.name = 'Vue2 Reactivity'; // срабатывает setter

Ограничения Vue2

Object.defineProperty имеет ряд ограничений:

  • Не может обнаружить добавление/удаление свойств без Vue.set() / Vue.delete()
  • Не может надёжно обнаружить прямое присваивание по индексу массива без патченных методов массива
  • Накладные расходы на производительность из-за глубокого обхода и предварительного определения getter/setter

Реализация Vue3

Vue3 использует ES6 Proxy, что обеспечивает более широкие возможности перехвата и лучшие характеристики производительности.

1. Перехват данных на основе Proxy

Vue3 оборачивает целые объекты с помощью new Proxy вместо определения getter/setter для каждого ключа. Это позволяет перехватывать больше операций, включая добавление/удаление свойств.

2. Более эффективное отслеживание зависимостей

Vue3 больше не нуждается в предварительном определении getter/setter для каждого свойства. Ловушки Proxy могут обрабатывать операции динамически (например, get, set, has, deleteProperty и т.д.).

3. Более точное срабатывание обновлений

Vue3 может отслеживать зависимости более точно и уменьшать количество ненужных перерисовок.

Пример Vue3

function reactive(target) {
const handler = {
get(target, key, receiver) {
const result = Reflect.get(target, key, receiver);
console.log(`get ${key}: ${result}`);
return result;
},
set(target, key, value, receiver) {
const success = Reflect.set(target, key, value, receiver);
console.log(`set ${key}: ${value}`);
return success;
},
};

return new Proxy(target, handler);
}

const data = reactive({ name: 'Vue 3' });

console.log(data.name);
data.name = 'Vue 3 Reactivity';
console.log(data.name);

Сравнение Vue2 и Vue3

ХарактеристикаVue2Vue3
Основной механизмObject.definePropertyProxy
Обнаружение новых свойствТребуется Vue.set()Нативная поддержка
Обнаружение удаления свойствТребуется Vue.delete()Нативная поддержка
Обнаружение присваивания по индексу массиваОграниченоНативная поддержка
ПроизводительностьПредварительный глубокий обходЛенивый + более эффективный
Поддержка браузеровIE9+Без поддержки IE11

Заключение

Vue2 использует Object.defineProperty, который работает, но имеет известные ограничения. Vue3 использует Proxy, обеспечивая более полную и гибкую систему реактивности с лучшей производительностью.

2. Почему Vue3 использует Proxy вместо Object.defineProperty?

Почему Vue3 выбрал Proxy вместо Object.defineProperty?

Основные причины

1. Более широкие возможности перехвата

Proxy может перехватывать множество операций, тогда как Object.defineProperty покрывает только get/set свойств.

// Операции, которые может перехватывать Proxy
const handler = {
get() {},
set() {},
has() {},
deleteProperty() {},
ownKeys() {},
getOwnPropertyDescriptor() {},
defineProperty() {},
preventExtensions() {},
getPrototypeOf() {},
isExtensible() {},
setPrototypeOf() {},
apply() {},
construct() {},
};

2. Нативное отслеживание индексов массива

// Ограничение Vue2
const arr = [1, 2, 3];
arr[0] = 10; // часто не отслеживается в модели defineProperty Vue2

// Vue3
const arr2 = reactive([1, 2, 3]);
arr2[0] = 10; // отслеживается

3. Нативная поддержка динамического добавления/удаления свойств

// Vue2 требует специальный API
Vue.set(obj, 'newKey', 'value');

// Vue3 нативно
const obj = reactive({});
obj.newKey = 'value';
delete obj.newKey;

4. Лучшая модель производительности

// Vue2: глубокий обход + defineReactive для каждого ключа
function observe(obj) {
Object.keys(obj).forEach((key) => {
defineReactive(obj, key, obj[key]);
if (typeof obj[key] === 'object') {
observe(obj[key]);
}
});
}

// Vue3: обёртка proxy
function reactive(obj) {
return new Proxy(obj, handler);
}

5. Более простая внутренняя реализация

Ядро реактивности Vue3 чище и проще в поддержке.

Почему Vue2 не использовал Proxy

Главная причина — совместимость с браузерами:

  • Vue2 (выпущен в 2016) нуждался в широкой поддержке, включая IE
  • Proxy нельзя полностью полифиллить
  • Vue3 отказался от поддержки IE11, поэтому Proxy стал допустимым решением

Практическое сравнение

// ===== Ограничения Vue2 =====
const vm = new Vue({
data: {
obj: { a: 1 },
arr: [1, 2, 3],
},
});

// ненадёжно реактивно в Vue2 без специальных API
vm.obj.b = 2;
delete vm.obj.a;
vm.arr[0] = 10;
vm.arr.length = 0;

// Обходное решение Vue2
Vue.set(vm.obj, 'b', 2);
Vue.delete(vm.obj, 'a');
vm.arr.splice(0, 1, 10);

// ===== Нативная поддержка Vue3 =====
const state = reactive({
obj: { a: 1 },
arr: [1, 2, 3],
});

state.obj.b = 2;
delete state.obj.a;
state.arr[0] = 10;
state.arr.length = 0;

Резюме

Vue3 использует Proxy, чтобы:

  1. Обеспечить полное покрытие реактивности (добавление/удаление/изменение по индексу)
  2. Улучшить производительность (меньше предварительного обхода)
  3. Упростить реализацию
  4. Улучшить опыт разработчика (меньше специальных API)

Компромисс: отсутствие поддержки устаревшего IE11, что приемлемо для современных веб-целей.

Справочные материалы