предисловие
На работе мы можем легко узнать, как использовать грамматику через поисковые системы или официальные документы, но знаете ли вы принцип? Думаю, некоторые студенты не должны уметь внятно объяснить принцип его реализации. Как мы все знаем, технологические обновления и итерации в наши дни происходят очень быстро.По словам автора Vue Ю Юйси, Vue3.x выпустит официальную версию во второй половине этого года, а адрес видео здесь.VUE CONF Ханчжоу 3.0 Прогресс. Если вы используете Vue или изучаете Vue, я рекомендую вам посмотреть полное видео, которое может оказаться очень полезным для вашего будущего знакомства с версией 3.0. Эта статья основана на версии Vue2.x. Обсуждение в основном сосредоточено на следующих трех вопросах.
- Зачем
dataБыть написанным как функция, но не разрешено быть записанным как объект?- Что именно означает перехват данных, часто упоминаемый в Vue?
- Изменения массива в экземпляре Vue
lengthИли почему назначение индекса не может обновить представление?
Tips
Если вы освоили причины и принципы этих трех проблем;
Если вы думаете, вам не нужно освоить принцип;
Будьте умнее — см. последнюю строчку этой статьи.
Далее мы ответим на эти три вопроса один за другим.
Вопрос первый
Зачем
dataБыть написанным как функция, но не разрешено быть записанным как объект?
Чтобы понять эту проблему, мы должны сначала знать следующие три момента.
Пункты к сведению
dataявляется свойством экземпляра Vue. 2. Объект — это ссылка на адрес памяти. 3. Функции имеют свою область видимости.
Первый пункт бесспорен,dataСвойства прикреплены к экземпляру Vue.
Во-вторых, типы данных JS делятся на базовые типы и ссылочные типы, базовые типы хранятся в памяти стека, ссылочные типы хранятся в памяти кучи, а ссылочные типы указывают на адрес кучи в памяти стека. Следующие два примера помогут вам ясно понять это предложение.
Присвоение базового типа
var a = 10;
var b = 10;
var c = a;
console.log(a === b); // true
a ++ ;
console.log(a); // 11
console.log(c); // 10
Этот код присваивает a и b значение 10, соответственно, a и b конгруэнтны. Затем используйте a для инициализации c, тогда значение c также равно 10. Но 10 в c совершенно не зависит от значения в a, значение является просто копией значения в a, после чего две переменные могут участвовать в любой операции, не влияя друг на друга. Конкретное расположение показано на диаграмме ниже.
присвоение ссылочного типа
var a = {};
var b = {};
var c = a;
console.log(a === b); // false
a.name = 'Marry';
a.say = () => console.log('Hi Marry!');
console.log(c.name); // 'Marry'
console.log(c.say()); // 'Hi Marry!'
Код выше. Сначала объявляются два пустых объекта a и b, а затем a присваивается c. Поскольку объекты являются ссылками на адреса в памяти стека, адреса разных объектов различны, поэтому они не совпадают. Затем добавьте новые атрибуты и методы к a, и c также может иметь этот атрибут и метод, главным образом потому, что c и a указывают на один и тот же адрес в куче памяти. Диаграмма его взаимосвязи показана на следующей диаграмме.
Что касается третьего пункта, то большинство студентов с основами JS должны понимать, что каждая функция имеет свою область применения.
Выше приведено описание трех точек внимания, затем мы объясним первую проблему на двух примерах:ЗачемdataБыть написанным как функция, но не разрешено быть записанным как объект?
данные - это код примера объекта
function MyCompnent() {}
MyCompnent.prototype.data = {
age: 12
};
var JackMa = new MyCompnent();
var PonyMa = new MyCompnent();
console.log(JackMa.data.age === PonyMa.data.age); // true
JackMa.data.age = 13;
console.log('JackMa ' + JackMa.data.age + '岁;' + 'PonyMa ' + PonyMa.data.age + '岁');
// JackMa 13岁;PonyMa 13岁
В приведенном выше примере мы создаем конструктор MyCompnent, который действует как Vue и объявляетdataсвойство, которое фактически эквивалентноVue.$data. Затем объявите две ссылки, измените значение одной ссылки, и другая ссылка также изменится.Этот принцип фактически аналогичен присвоению типа ссылки.
пример кода, где данные являются функцией
function MyCompnent() {
this.data = this.data();
}
MyCompnent.prototype.data = function() {
return {
age: 12
}
};
var JackMa = new MyCompnent();
var PonyMa = new MyCompnent();
console.log(JackMa.data.age === PonyMa.data.age); // true
JackMa.data = {age: 13};
console.log('JackMa ' + JackMa.data.age + '岁;' + 'PonyMa ' + PonyMa.data.age + '岁');
// JackMa 13岁;PonyMa 12岁
Приведенный выше код имитируетdataКогда это функция, если вы измените экземпляр экземпляраdataзначение свойства, оно не повлияет на значение другого экземпляраdataценность .
Tips
В процессе собеседования часто задают вопросы о типах данных JS.Если вы ответите только на основные типы и ссылочные типы, вы можете получить только половину баллов, но если вы можете ответить на такие вопросы, как места хранения и привести примеры, он должен быть бонусом.
резюме
Внутри ВьюdataПричина, по которой свойства не могут быть записаны в формате объектов, заключается в том, что объекты являются ссылками на адреса, а не независимыми. Если файл .vue имеет несколько подкомпонентов, которые вместе получают переменную, изменение значения этой переменной в одном из подкомпонентов повлияет на значение этой переменной в других компонентах. Если они написаны как функции, то в них есть понятие области видимости, которые изолированы друг от друга и не затрагиваются.
Вопрос второй
Что именно означает перехват данных, часто упоминаемый в Vue?
Я считаю, что большинство студентов, которые использовали или понимали Vue, слышали о нем.захват данных, а затем спросите, почему вы можете ответить на один или два, таких как геттеры, сеттеры и тому подобное. Сегодня я систематически расскажу вам о красоте угона данных. Сначала давайте взглянем на картинку ниже.
Рисунок выше полностью описывает механизм работы Vue, во-первых, когда данные изменяются, они будут проходить черезDataпроцесс, затемDepбудет выдано уведомление (notify),РассказыватьWatcherПроизошло изменение данных, затемWatcherЭто будет передано функции рендеринга, чтобы сообщить ему, что данные изменились, и представление можно визуализировать (представление, управляемое данными), а затем выполняется функция рендеринга.renderспособ обновленияVNODE, который мы называем виртуальным DOM Наконец, виртуальный DOM локально обновляет представление, которое необходимо отобразить в соответствии с оптимальным алгоритмом. здесьDataТолько что сделал то, о чем мы сегодня поговорим - перехват данных.
Чтобы получить более глубокое понимание того, как угнать, нам нужно взглянуть на реализацию исходного кода.
Observer
/**
* Vue中的每一个变量都是由 Observer 构造函数生成的。
* 细心的你可能会发现,你打印出来任何一个Vue上的引用类型属性,后面都有 __ob__: Observer 的字样。
*/
var Observer = function Observer (value) {
this.value = value;
// 这里把发布者 Dep 注册了
this.dep = new Dep();
// ···
// 此处调用 walk
this.walk(value);
};
/**
* 此处会将 obj 里面的每一个值用 defineReactive?1 处理,而它就是今晚的主角。
*/
Observer.prototype.walk = function walk (obj) {
var keys = Object.keys(obj);
for (var i = 0; i < keys.length; i++) {
defineReactive?1(obj, keys[i]);
}
};
захват данных
/**
* 这个函数就是数据劫持的根据地,里面为对象重写了 get 和 set 方法以及固有属性 enumerable 等。
*
*/
function defineReactive?1 (
obj,
key,
val,
customSetter,
shallow
) {
// ···
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
// ···
return value
},
set: function reactiveSetter (newVal) {
// ···
// 此处最为关键,这个函数的主要作用就是通过 notify 告诉 Watcher 有数据变化了。
dep.notify();
}
});
}
Dep
/**
* subs 是所有 Watcher 的收集器,类型为数组;notify 实则是调用了每个Watcher的 update方法 。
*/
Dep.prototype.notify = function notify () {
var subs = this.subs.slice();
// ···
for (var i = 0, l = subs.length; i < l; i++) {
subs[i].update();
}
};
Watcher
/**
* 更新视图的最直观的方法就是 Watcher 上的 update 方法 , Dep subs 反复调用
* 这里最终都是调用 run 方法。
*/
Watcher.prototype.update = function update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true;
} else if (this.sync) {
this.run();
} else {
queueWatcher(this);
}
};
/**
* run 方法内的 cb 方法建立 Watcher 和 VNode 之间的关联关系,从而引发视图的更新。
*/
Watcher.prototype.run = function run () {
// ···
if (this.user) {
// ···
this.cb.call(this.vm, value, oldValue);
// ···
} else {
this.cb.call(this.vm, value, oldValue);
}
// ···
};
резюме
Пока что мы не только понимаем принцип захвата данных, но и знаем, кто их перехватывает, что он делал после захвата, кто стал причиной обновления представления и так далее. Вы лучше понимаете механизм работы Vue?
Вопрос третий
Изменения массива в экземпляре Vue
lengthИли почему назначение индекса не может обновить представление?
Реконструкция сцены
В приведенном выше примере он выполняется во время смонтированного цикла для удобства отладки.windows.vm = this;.weekСодержит пять элементов с понедельника по пятницу, мы стараемся изменитьweekизlengthприсваивает значение 3 и его элементу с индексом 4周八, результаты недействительны. Итак, как это может работать? Пожалуйста, смотрите изображение ниже.
push, если вы хотите попробовать, вы найдете массив вызововsliceметод не работает. Пока Vue извлекает собственный метод массива, который может изменить исходный массив, он обрабатывается повторно. Только методы, обрабатываемые Vue, могут обновлять представления. Далее я объясню вам этот вывод с точки зрения встроенных методов и исходного кода.
встроенный метод
__proto__встроенныйpop,pushи т. д. метод для нескольких массивов, во втором__proto__Не только вышеперечисленные, но и многие другие методы. Видно, что Vue захватывает API массива.
Анализ исходного кода
var methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
];
/**
* Intercept mutating methods and emit events
* 此方法主要作用就是遍历数组局部方法,调用的同时去调用 dep 的 notify 通知 Watcher 进而更新视图
*/
methodsToPatch.forEach(function (method) {
// cache original method
var original = arrayProto[method];
def(arrayMethods, method, function mutator () {
var args = [], len = arguments.length;
while ( len-- ) args[ len ] = arguments[ len ];
var result = original.apply(this, args);
var ob = this.__ob__;
var inserted;
switch (method) {
case 'push':
case 'unshift':
inserted = args;
break
case 'splice':
inserted = args.slice(2);
break
}
if (inserted) { ob.observeArray(inserted); }
// 这一步至关重要
ob.dep.notify();
return result
});
});
резюме
Здесь мы знаем, что Vue перехватывает API массива, который может изменить исходный массив, так что каждый вызов будет выполняться.dep.notify()метод обновления представления.
заключительные замечания
Времени поделиться всегда так мало, но я хотел бы сказать вам: мы часто сталкиваемся с такими проблемами на работе, я надеюсь, мы зададим себе еще один вопрос, почему? Изучить, как это реализовано, освоить концепцию дизайна, научиться дизайн-мышлению, а не просто знать, как его использовать. Это позволит вам расти еще больше!
Наконец, если вы хотитепоставить лайк, что даст мне много мотивации!