1. Введение
Привет всем, яВакагава. Добро пожаловать, чтобы следовать за мнойПубличный аккаунт Wakagawa Vision, недавно организованныйЧтение исходного кода, если вам интересно, вы можете добавить меня в WeChatruochuan12Участвуйте, общайтесь и учитесь вместе, и добивайтесь прогресса вместе.
написано раньше"Изучение серии "Общая архитектура исходного кода""ВключатьjQuery,underscore,lodash,vuex,sentry,axios,redux,koa,vue-devtools,vuex4Более десяти статей с исходным кодом. Последние три из них:
50 строк серийного кода Обещание, как реализована луковая модель коа?
Выпущен Vue 3.2, как Ю Юйси выпустил Vue.js?
Практические основные функции инструментов в исходном коде Vue3, понятные новичкам.
Написание относительно сложного исходного кода стоило мне времени и энергии, и я не получил много лайков от чтения.На самом деле, это был настоящий удар. С точки зрения объема чтения и выгоды для читателей это не может способствовать непрерывному выпуску статей авторами. Так что измените свое мнение и напишите несколько относительно простых для понимания статей.На самом деле исходный код не так сложен, как можно себе представить, по крайней мере многие люди могут понять. Гёте однажды сказал: «Читать хорошую книгу — значит разговаривать с благородными людьми». То же самое можно получить: чтение исходного кода — это тоже способ обучения и общения с автором.
Эта статья возникла изЧитайте исходный код вместеВопросы от друзей в группе задавайте @ Ruo Chuan, "Почему данные в данных могут быть получены напрямую с этим?", я прочитал исходный код, чтобы ответить на него. Я подумал, что если кто-то снова спросит в следующий раз, мне нужно будет ответить на него снова. В то время я планировал написать статью, чтобы рассказать читателям, чтобы они сами исследовали принцип, поэтому я есть эта статья.
Прочитав эту статью, вы узнаете:
1. 如何学习调试 vue2 源码
2. data 中的数据为什么可以用 this 直接获取到
3. methods 中的方法为什么可以用 this 直接获取到
4. 学习源码中优秀代码和思想,投入到自己的项目中
Эта статья не сложная, б/уVueВсе могут понять, надеюсь, каждый сможет отладить и научиться читать исходный код.
Глядя на исходный код, можно сделать смелое предположение и, наконец, тщательно его проверить.
2. Пример: это может напрямую получать данные и методы
Как мы все знаем, это может вывести我是若川из. Любознательные люди задаются вопросом, почемуthisможет получить к нему прямой доступ.
const vm = new Vue({
data: {
name: '我是若川',
},
methods: {
sayName(){
console.log(this.name);
}
},
});
console.log(vm.name); // 我是若川
console.log(vm.sayName()); // 我是若川
так почемуthis.xxxможет получитьdataданные можно получить изmethodsметод.
Как мы сами создаем и пишем функции?Vueэффект.
function Person(options){
}
const p = new Person({
data: {
name: '若川'
},
methods: {
sayName(){
console.log(this.name);
}
}
});
console.log(p.name);
// undefined
console.log(p.sayName());
// Uncaught TypeError: p.sayName is not a function
Если бы это были вы, как бы вы это сделали. С проблемами давайте отлаживатьVue2Изучение исходного кода.
3. Подготовьте среду для отладки исходного кода
Создайте новую папку локальноexamples, создайте новый файлindex.htmlдокумент.
существует<body></body>добавить следующееjs.
<script src="https://unpkg.com/vue@2.6.14/dist/vue.js"></script>
<script>
const vm = new Vue({
data: {
name: '我是若川',
},
methods: {
sayName(){
console.log(this.name);
}
},
});
console.log(vm.name);
console.log(vm.sayName());
</script>
Установите его глобальноnpm i -g http-serverЗапустите службу.
npm i -g http-server
cd examples
http-server .
// 如果碰到端口被占用,也可以指定端口
http-server -p 8081 .
так что вhttp://localhost:8080/Откройте только что написанноеindex.htmlстраница вверх.
Для читателей, не очень знакомых с отладкой, можно прочитать эту статью«Навыки отладки отладчика, которые легко игнорировать во внешнем интерфейсе», скриншоты размечены очень подробно.
отладка: в
F12Включите отладку,sourceпанель, напримерconst vm = new Vue({Ударьте точку останова.
После обновления страницы нажмитеF11Введите функцию, затем точка останова войдет в конструктор Vue.
3.1 Конструктор Vue
function Vue (options) {
if (!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword');
}
this._init(options);
}
// 初始化
initMixin(Vue);
stateMixin(Vue);
eventsMixin(Vue);
lifecycleMixin(Vue);
renderMixin(Vue);
Стоит отметить, что:if (!(this instanceof Vue)){}Судите, используется лиnewКлючевое слово вызывает конструктор.
Вообще говоря, мы не должны писать это в обычное время.
Конечно, глядя на библиотеку исходного кода, вы также можете вызывать свои собственные функции.new. ноvueОбычно для проекта требуется толькоnew Vue()Один раз, так что не надо.
а такжеjQueryисходный код является внутреннимnew, для пользователя нетnewструктура.
jQuery = function( selector, context ) {
// 返回new之后的对象
return new jQuery.fn.init( selector, context );
};
потому что использоватьjQueryЧасто звонили.
фактическиjQueryТакже можетnewиз. и безnewявляется эффектом.
Если вы не понимаетеnewПолезность операторов можно найти в моей предыдущей статье.Интервьюер спросил: Могу ли я смоделировать новый оператор, реализующий JS?
Отладка: продолжить
this._init(options);нажмите точку останова, нажмитеF11в функцию.
3.2 Функция инициализации _init
Войти_initПосле функции эта функция относительно длинная и сделала много вещей Мы предполагаем, что это то же самое, что иdataа такжеmethodsСоответствующая реализация находится вinitState(vm)В функции.
// 代码有删减
function initMixin (Vue) {
Vue.prototype._init = function (options) {
var vm = this;
// a uid
vm._uid = uid$3++;
// a flag to avoid this being observed
vm._isVue = true;
// merge options
if (options && options._isComponent) {
// optimize internal component instantiation
// since dynamic options merging is pretty slow, and none of the
// internal component options needs special treatment.
initInternalComponent(vm, options);
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
);
}
// expose real self
vm._self = vm;
initLifecycle(vm);
initEvents(vm);
initRender(vm);
callHook(vm, 'beforeCreate');
initInjections(vm); // resolve injections before data/props
// 初始化状态
initState(vm);
initProvide(vm); // resolve provide after data/props
callHook(vm, 'created');
};
}
Отладка: тогда мы находимся в
initState(vm)Функция предназначена для точки останова здесь, нажмитеF8Вы можете перейти прямо к этой точке останова и нажатьF11затем введитеinitStateфункция.
3.3 состояние инициализации initState
Судя по названию функции, основными функциями этой функции являются:
初始化 props
初始化 methods
监测数据
初始化 computed
初始化 watch
function initState (vm) {
vm._watchers = [];
var opts = vm.$options;
if (opts.props) { initProps(vm, opts.props); }
// 有传入 methods,初始化方法
if (opts.methods) { initMethods(vm, opts.methods); }
// 有传入 data,初始化 data
if (opts.data) {
initData(vm);
} else {
observe(vm._data = {}, true /* asRootData */);
}
if (opts.computed) { initComputed(vm, opts.computed); }
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch);
}
}
Сосредоточимся на инициализации
methods, а затем посмотрите на инициализациюdata.
отладка: в
initMethodsПоставьте точку останова на этом предложении, и в то же времяinitData(vm)Поставьте точку останова вinitMethodsПосле функции вы можете напрямую нажатьF8назадinitData(vm)функция. продолжайте нажиматьF11, введите сначалаinitMethodsфункция.
3.4 Метод инициализации initMethods
function initMethods (vm, methods) {
var props = vm.$options.props;
for (var key in methods) {
{
if (typeof methods[key] !== 'function') {
warn(
"Method \"" + key + "\" has type \"" + (typeof methods[key]) + "\" in the component definition. " +
"Did you reference the function correctly?",
vm
);
}
if (props && hasOwn(props, key)) {
warn(
("Method \"" + key + "\" has already been defined as a prop."),
vm
);
}
if ((key in vm) && isReserved(key)) {
warn(
"Method \"" + key + "\" conflicts with an existing Vue instance method. " +
"Avoid defining component methods that start with _ or $."
);
}
}
vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm);
}
}
initMethodsфункции, есть в основном некоторые суждения.
判断 methods 中的每一项是不是函数,如果不是警告。
判断 methods 中的每一项是不是和 props 冲突了,如果是,警告。
判断 methods 中的每一项是不是已经在 new Vue实例 vm 上存在,而且是方法名是保留的 _ $ (在JS中一般指内部变量标识)开头,如果是警告。
Помимо этих суждений, мы можем видеть, чтоinitMethodsФункция фактически пересекает входящиеmethodsобъект и использоватьbindУказатель this связанной функцииvm, то есть,new Vueобъект экземпляра.
Вот почему мы можемthisПрямой доступmethodsПричина функции внутри.
мы можем двигать мышь вверхbindпеременная, нажмитеaltключ, вы можете увидеть, где определена функция, вот218行, нажмите, чтобы перейти сюдаbindреализация.
3.4.1 bind возвращает функцию, измените ее, чтобы она указывала на
function polyfillBind (fn, ctx) {
function boundFn (a) {
var l = arguments.length;
return l
? l > 1
? fn.apply(ctx, arguments)
: fn.call(ctx, a)
: fn.call(ctx)
}
boundFn._length = fn.length;
return boundFn
}
function nativeBind (fn, ctx) {
return fn.bind(ctx)
}
var bind = Function.prototype.bind
? nativeBind
: polyfillBind;
Проще говоря, он совместим со старой версией, которая не поддерживает родную функцию привязки. В то же время он совместим с методом записи, и оценивается количество параметров, и использованиеcallа такжеapplyРеализовано якобы из-за проблем с производительностью.
если дляcall、apply、bindНезнакомые с использованием и реализацией, вы можете проверить мойИнтервьюер спрашивает сериюнаписано наИнтервьюер спросил: Могу ли я смоделировать вызов и применить методы JS?
Интервьюер спросил: Могу ли я смоделировать метод привязки, реализующий JS?
Отладка: завершена
initMethodsфункция, нажмитеF8вернуться к предыдущемуinitData(vm)точка останова функции.
3.5 initData инициализирует данные
initDataФункции также являются некоторыми суждениями. В основном сделайте следующее:
先给 _data 赋值,以备后用。
最终获取到的 data 不是对象给出警告。
遍历 data ,其中每一项:
如果和 methods 冲突了,报警告。
如果和 props 冲突了,报警告。
不是内部私有的保留属性,做一层代理,代理到 _data 上。
最后监测 data,使之成为响应式的数据。
function initData (vm) {
var data = vm.$options.data;
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {};
if (!isPlainObject(data)) {
data = {};
warn(
'data functions should return an object:\n' +
'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
vm
);
}
// proxy data on instance
var keys = Object.keys(data);
var props = vm.$options.props;
var methods = vm.$options.methods;
var i = keys.length;
while (i--) {
var key = keys[i];
{
if (methods && hasOwn(methods, key)) {
warn(
("Method \"" + key + "\" has already been defined as a data property."),
vm
);
}
}
if (props && hasOwn(props, key)) {
warn(
"The data property \"" + key + "\" is already declared as a prop. " +
"Use prop default value instead.",
vm
);
} else if (!isReserved(key)) {
proxy(vm, "_data", key);
}
}
// observe data
observe(data, true /* asRootData */);
}
3.5.1 getData для получения данных
Когда это функция, функция вызывается, и выполнение получает объект.
function getData (data, vm) {
// #7573 disable dep collection when invoking data getters
pushTarget();
try {
return data.call(vm, vm)
} catch (e) {
handleError(e, vm, "data()");
return {}
} finally {
popTarget();
}
}
3.5.2 прокси-сервер
На самом деле, используйтеObject.definePropertyопределить объект
Использование здесь:this.xxxдоступthis._data.xxx.
/**
* Perform no operation.
* Stubbing args to make Flow happy without leaving useless transpiled code
* with ...rest (https://flow.org/blog/2017/05/07/Strict-Function-Call-Arity/).
*/
function noop (a, b, c) {}
var sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop
};
function proxy (target, sourceKey, key) {
sharedPropertyDefinition.get = function proxyGetter () {
return this[sourceKey][key]
};
sharedPropertyDefinition.set = function proxySetter (val) {
this[sourceKey][key] = val;
};
Object.defineProperty(target, key, sharedPropertyDefinition);
}
3.5.3 Object.defineProperty определяет свойства объекта
Object.definePropertyЭто очень важный API. Существует также API, который определяет несколько свойств:Object.defineProperties(obj, props) (ES5)
Object.definePropertyВовлекая более важные точки знаний, интервью часто проверяются.
value——当试图获取属性时所返回的值。
writable——该属性是否可写。
enumerable——该属性在for in循环中是否会被枚举。
configurable——该属性是否可被删除。
set()——该属性的更新操作所调用的函数。
get()——获取属性值时所调用的函数。
См. эту ссылку для подробного примера
3.6 Некоторые функции, встречающиеся в тексте и, наконец, единообразно объясненные
3.6.1 Является ли hasOwn свойством, принадлежащим самому объекту
В режиме отладки нажмитеaltkey, наведите указатель мыши на имя метода, вы увидите место, где определена функция. Нажмите, чтобы прыгнуть.
/**
* Check whether an object has the property.
*/
var hasOwnProperty = Object.prototype.hasOwnProperty;
function hasOwn (obj, key) {
return hasOwnProperty.call(obj, key)
}
hasOwn({ a: undefined }, 'a') // true
hasOwn({}, 'a') // false
hasOwn({}, 'hasOwnProperty') // false
hasOwn({}, 'toString') // false
// 是自己的本身拥有的属性,不是通过原型链向上查找的。
3.6.2 Является ли isReserved внутренней частной зарезервированной строкой, начинающейся с $ и _
/**
* Check if a string starts with $ or _
*/
function isReserved (str) {
var c = (str + '').charCodeAt(0);
return c === 0x24 || c === 0x5F
}
isReserved('_data'); // true
isReserved('$options'); // true
isReserved('data'); // false
isReserved('options'); // false
4. Наконец, используйте более 60 строк кода для реализации упрощенной версии.
function noop (a, b, c) {}
var sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop
};
function proxy (target, sourceKey, key) {
sharedPropertyDefinition.get = function proxyGetter () {
return this[sourceKey][key]
};
sharedPropertyDefinition.set = function proxySetter (val) {
this[sourceKey][key] = val;
};
Object.defineProperty(target, key, sharedPropertyDefinition);
}
function initData(vm){
const data = vm._data = vm.$options.data;
const keys = Object.keys(data);
var i = keys.length;
while (i--) {
var key = keys[i];
proxy(vm, '_data', key);
}
}
function initMethods(vm, methods){
for (var key in methods) {
vm[key] = typeof methods[key] !== 'function' ? noop : methods[key].bind(vm);
}
}
function Person(options){
let vm = this;
vm.$options = options;
var opts = vm.$options;
if(opts.data){
initData(vm);
}
if(opts.methods){
initMethods(vm, opts.methods)
}
}
const p = new Person({
data: {
name: '若川'
},
methods: {
sayName(){
console.log(this.name);
}
}
});
console.log(p.name);
// 未实现前: undefined
// '若川'
console.log(p.sayName());
// 未实现前:Uncaught TypeError: p.sayName is not a function
// '若川'
5. Резюме
Базовые знания, используемые в этой статье, в основном следующие:
构造函数
this 指向
call、bind、apply
Object.defineProperty
等等基础知识。
Эта статья основана на ответах на сомнения группы чтения исходного кода и подробно описывает, как отлаживатьVueисходный код, чтобы найти ответ.
Ответьте на вопрос в начале статьи:
пройти черезthisпрямой доступ кmethodsПричина функции внутри: потому чтоmethodsметод черезbindуказанныйthisявляется экземпляром нового Vue (vm).
пройти черезthisпрямой доступ кdataПричина данных внутри: атрибуты в данных в конечном итоге будут храниться вnew Vueслучай (vm)Вверх_dataобъект, доступthis.xxx, это доступObject.definePropertyпосле проксиthis._data.xxx.
VueПреимущество этой конструкции в том, что ее легко получить. Есть и неудобства, т.props,methodsа такжеdataЭти трое склонны к конфликтам.
Общая сложность статьи невелика, но настоятельно рекомендуется, чтобы читатели и друзья отлаживали ее самостоятельно. После отладки вы можете обнаружить, что:VueИсходный код не так сложен, как я себе представлял, и я могу понять его часть.
Вдохновение: когда мы работаем с широко используемыми технологиями и фреймворками или библиотеками, проявляйте любопытство и больше думайте о внутреннем устройстве. Чтобы быть в состоянии знать, что это такое, знать, что это такое. чем многие люди.
Вы можете подумать, почему в синтаксисе шаблона можно опуститьthisКак написать ключевое слово, оно фактически используется при компиляции внутреннего шаблона.with. Читатели со свободными возможностями могут изучить этот принцип.
Наконец, добро пожаловать, чтобы добавить меня на WeChatruochuan12общаться, участвоватьЧитайте исходный код вместеДействия, все вместе изучают исходный код и вместе добиваются прогресса.
О группе && Exchange
недавно организованныйЧтение исходного кода, если вам интересно, вы можете добавить меня в WeChatruochuan12Участвуйте в долгосрочном обмене и обучении.
Автор: Чанг ИВакагаваНазвание смешано в реках и озерах. добро пожаловать, чтобы добавить меня wechatruochuan12. По дороге на фронт | Знаю очень мало, только хорошо учусь.
Обратите внимание на паблик аккаунта Ruochuan Vision, Изучайте исходный код вместе каждую неделю, учитесь читать исходный код и переходите к расширенному интерфейсу.
Блог Вакагавы
segmentfaultКолонна Вакагава Видение, открылВидение ВакагаваКолонка, добро пожаловать на внимание~
Колонка самородков, добро пожаловать, обратите внимание~
Колонна видений Чжиху Руочуань, открылВидение ВакагаваКолонка, добро пожаловать на внимание ~
github blog, спроситьstar^_^~