предисловие
Прежде всего, приглашаю всех подписаться на меняБлог на гитхабе, что можно рассматривать как небольшое поощрение для меня, ведь у меня нет денег, чтобы писать вещи, и я могу продолжать на своем собственном энтузиазме и всеобщем поощрении. В ближайшие дни я должен сосредоточиться на написании серии статей о внутренних принципах Vue и React.Заинтересованные студенты могут обратить внимание или Star.
две предыдущие статьиРеактивные данные и основы зависимости от данныха такжеРазмышления об отзывчивости массивов VueМы представили содержание, связанное с адаптивными данными. Учащиеся, которые еще не читали его, могут щелкнуть ссылку выше, чтобы узнать об этом. Если вы читали две статьи выше, значит, у вас достаточно знаний в этой области.Пришло время взглянуть на то, как Vue реализует внутреннее реагирование на данные. В настоящее время код Vue очень большой, но он включает в себя вещи, которые нас не волнуют, такие как серверный рендеринг. Чтобы сосредоточиться на той части, которую мы хотим изучить, на этот раз мы читаем ранний код Vue. . Ты сможешьcheckout
прибытьздесьПроверьте соответствующий код.
Я видел часть исходного кода React по частям и раньше. Когда я увидел исходный код Vue, я подумал, что он действительно превосходен. Разделение между модулями было очень хорошим, а читаемость была очень высокой. Реактивные данные Vue находятся вObserver
Модуль реализован, можем посмотретьObserver
как это достигается.
Модель публикации-подписки
Если вы читали две предыдущие статьи, то должны обнаружить проблему: код, реагирующий на данные, слишком связан с другими кодами, например:
//代码来源于文章:响应式数据与数据依赖基本原理
//定义对象的单个响应式属性
function defineReactive(obj, key, value){
observify(value);
Object.defineProperty(obj, key, {
configurable: true,
enumerable: true,
set: function(newValue){
var oldValue = value;
value = newValue;
//可以在修改数据时触发其他的操作
console.log("newValue: ", newValue, " oldValue: ", oldValue);
},
get: function(){
return value;
}
});
}
Например, приведенный выше код,set
Код внутренней обработки связан со всем ответом данных, если в следующий раз мы захотимset
Для выполнения других операций вset
Контент внутри функции очень недружественный и не соответствует принципу Open Close (OCP: Open Close Principle). Конечно, Vue не будет разработан таким образом.Чтобы решить эту проблему, Vue представилмодель публикации-подписки. На самом деле модель публикации-подписки — это модель, с которой хорошо знакомы фронтенд-инженеры, также известная какШаблон наблюдателя, который является способом определенияодин ко многимКогда состояние объекта изменяется, другие объекты, наблюдающие за ним, будут уведомлены. Наше самое распространенное событие DOM — этомодель публикации-подписки. Например:
document.body.addEventListener("click", function(){
console.log("click event");
});
В приведенном выше коде мы слушаемbody
изclick
события, хотя мы не знаемclick
Когда произойдет событие, но мы можем гарантировать, что если это произойдетbody
изclick
События, мы должны иметь возможность получать уведомления, то есть вызывается функция обратного вызова. В JavaScript, поскольку функции являются гражданами первого класса, мы редко используем традиционную модель публикации-подписки и в основном используеммодель событияспособ достижения. Существует также модель событий, реализованная в Vue, на которую мы можем взглянуть. Поскольку разделение между модулями Vue очень хорошее, прежде чем смотреть код, мы можем на самом деле взглянуть на соответствующий файл модульного теста, вы будете знать, какую функцию будет выполнять этот модуль, и даже если вы хотите, вы можете сделать Внедрите аналогичный модуль в исходный код Vue для запуска.
Раннее использование кода Vuejasmine
выполнять модульные тесты,emitter_spec.js
— это файл модульного теста для модели событий. Сначала краткое введениеjasmine
Используемые функции, вы можете обратиться к следующему коду, чтобы понять конкретные функции:
-
describe
представляет собой набор тестовых единиц -
it
это тестовый случай -
beforeEach
в каждом тестовом примереit
выполнить перед выполнением -
expect
Функция ожидания, используемая для логического сравнения ожидаемых и фактических значений. -
createSpy
Используется для создания шпиона, а роль шпиона заключается в мониторинге функцийпередачасоответствующую информацию ипараметры выполнения функции
var Emitter = require('../../../src/emitter')
var u = undefined
// 代码有删减
describe('Emitter', function () {
var e, spy
beforeEach(function () {
e = new Emitter()
spy = jasmine.createSpy('emitter')
})
it('on', function () {
e.on('test', spy)
e.emit('test', 1, 2 ,3)
expect(spy.calls.count()).toBe(1)
expect(spy).toHaveBeenCalledWith(1, 2, 3)
})
it('once', function () {
e.once('test', spy)
e.emit('test', 1, 2 ,3)
e.emit('test', 2, 3, 4)
expect(spy.calls.count()).toBe(1)
expect(spy).toHaveBeenCalledWith(1, 2, 3)
})
it('off', function () {
e.on('test1', spy)
e.on('test2', spy)
e.off()
e.emit('test1')
e.emit('test2')
expect(spy.calls.count()).toBe(0)
})
it('apply emit', function () {
e.on('test', spy)
e.applyEmit('test', 1)
e.applyEmit('test', 1, 2, 3, 4, 5)
expect(spy).toHaveBeenCalledWith(1)
expect(spy).toHaveBeenCalledWith(1, 2, 3, 4, 5)
})
})
Как можно заметитьEmitter
Экземпляр объекта предоставляет следующие интерфейсы для внешнего мира:
-
on
: Зарегистрируйте интерфейс прослушивания, параметрыназвание событияа такжефункция слушателя -
emit
: функция триггера события, параметрназвание события -
off
: Отменить функцию регистрации соответствующего события, параметрыназвание событияа такжефункция слушателя -
once
: а такжеon
Точно так же прослушиватель будет уведомлен только в первый раз, а затем прослушиватель будет удален.
После прочтения приведенного выше кода модульного теста мы в основном поняли, что будет делать этот модуль, теперь давайте посмотрим на соответствующий код:
// 删去了注释并且对代码顺序有调整
// ctx是监听回调函数的执行作用域(this)
function Emitter (ctx) {
this._ctx = ctx || this
}
var p = Emitter.prototype
p.on = function (event, fn) {
this._cbs = this._cbs || {}
;(this._cbs[event] || (this._cbs[event] = []))
.push(fn)
return this
}
// 三种模式
// 不传参情况清空所有监听函数
// 仅传事件名则清除该事件的所有监听函数
// 传递事件名和回调函数,则对应仅删除对应的监听事件
p.off = function (event, fn) {
this._cbs = this._cbs || {}
// all
if (!arguments.length) {
this._cbs = {}
return this
}
// specific event
var callbacks = this._cbs[event]
if (!callbacks) return this
// remove all handlers
if (arguments.length === 1) {
delete this._cbs[event]
return this
}
// remove specific handler
var cb
for (var i = 0; i < callbacks.length; i++) {
cb = callbacks[i]
// 这边的代码之所以会有cb.fn === fn要结合once函数去看
// 给once传递的监听函数其实已经被wrapped过
// 但是仍然可以通过原来的监听函数去off掉
if (cb === fn || cb.fn === fn) {
callbacks.splice(i, 1)
break
}
}
return this
}
// 触发对应事件的所有监听函数,注意最多只能用给监听函数传递三个参数(采用call)
p.emit = function (event, a, b, c) {
this._cbs = this._cbs || {}
var callbacks = this._cbs[event]
if (callbacks) {
callbacks = callbacks.slice(0)
for (var i = 0, len = callbacks.length; i < len; i++) {
callbacks[i].call(this._ctx, a, b, c)
}
}
return this
}
// 触发对应事件的所有监听函数,传递参数个数不受限制(采用apply)
p.applyEmit = function (event) {
this._cbs = this._cbs || {}
var callbacks = this._cbs[event], args
if (callbacks) {
callbacks = callbacks.slice(0)
args = callbacks.slice.call(arguments, 1)
for (var i = 0, len = callbacks.length; i < len; i++) {
callbacks[i].apply(this._ctx, args)
}
}
return this
}
// 通过调用on与off事件事件,在第一次触发之后就`off`对应的监听事件
p.once = function (event, fn) {
var self = this
this._cbs = this._cbs || {}
function on () {
self.off(event, on)
fn.apply(this, arguments)
}
on.fn = fn
this.on(event, on)
return this
}
Мы видим, что приведенный выше код использует шаблон прототипа для созданияEmitter
Добрый. Запустите этот модуль с Кармой, все кейсы пройдены, теперь мы его прочиталиEmitter
Ну это небольшая разминка, давайте посмотримObserver
модуль.
Observer
Внешняя функция
Согласно приведенной выше идее, давайте сначала посмотримObserver
Соответствующий тестовый примерobserver_spec.js
,из-заObserver
Тестовые случаи очень длинные, я объясню их в комментариях к коду и постараюсь максимально упростить тестовые примеры, чтобы мы могли понять соответствующие функции модуля. Надеюсь, вы терпеливо прочитаете его.
//测试用例是精简版,否则太冗长
var Observer = require('../../../src/observe/observer')
var _ = require('../../../src/util') //Vue内部使用工具方法
var u = undefined
Observer.pathDelimiter = '.' //配置Observer路径分隔符
describe('Observer', function () {
var spy
beforeEach(function () {
spy = jasmine.createSpy('observer')
})
//我们可以看到我们通过Observer.create函数可以将数据变为可响应化,
//然后我们监听get事件可以在属性被读取时触发对应事件,注意对象嵌套的情况(例如b.c)
it('get', function () {
Observer.emitGet = true
var obj = {
a: 1,
b: {
c: 2
}
}
var ob = Observer.create(obj)
ob.on('get', spy)
var t = obj.b.c
expect(spy).toHaveBeenCalledWith('b', u, u)
expect(spy).toHaveBeenCalledWith('b.c', u, u)
Observer.emitGet = false
})
//我们可以监听响应式数据的set事件,当响应式数据修改的时候,会触发对应的时间
it('set', function () {
var obj = {
a: 1,
b: {
c: 2
}
}
var ob = Observer.create(obj)
ob.on('set', spy)
obj.b.c = 4
expect(spy).toHaveBeenCalledWith('b.c', 4, u)
})
//带有$与_开头的属性都不会被处理
it('ignore prefix', function () {
var obj = {
_test: 123,
$test: 234
}
var ob = Observer.create(obj)
ob.on('set', spy)
obj._test = 234
obj.$test = 345
expect(spy.calls.count()).toBe(0)
})
//访问器属性也不会被处理
it('ignore accessors', function () {
var obj = {
a: 123,
get b () {
return this.a
}
}
var ob = Observer.create(obj)
obj.a = 234
expect(obj.b).toBe(234)
})
// 对数属性的get监听,注意嵌套的情况
it('array get', function () {
Observer.emitGet = true
var obj = {
arr: [{a:1}, {a:2}]
}
var ob = Observer.create(obj)
ob.on('get', spy)
var t = obj.arr[0].a
expect(spy).toHaveBeenCalledWith('arr', u, u)
expect(spy).toHaveBeenCalledWith('arr.0.a', u, u)
expect(spy.calls.count()).toBe(2)
Observer.emitGet = false
})
// 对数属性的get监听,注意嵌套的情况
it('array set', function () {
var obj = {
arr: [{a:1}, {a:2}]
}
var ob = Observer.create(obj)
ob.on('set', spy)
obj.arr[0].a = 2
expect(spy).toHaveBeenCalledWith('arr.0.a', 2, u)
})
// 我们看到可以通过监听mutate事件,在push调用的时候对应触发事件
// 触发事件第一个参数是"",代表的是路径名,具体源码可以看出,对于数组变异方法都是空字符串
// 触发事件第二个参数是数组本身
// 触发事件第三个参数比较复杂,其中:
// method属性: 代表触发的方法名称
// args属性: 代表触发方法传递参数
// result属性: 代表触发变异方法之后数组的结果
// index属性: 代表变异方法对数组发生变化的最开始元素
// inserted属性: 代表数组新增的元素
// remove属性: 代表数组删除的元素
// 其他的变异方法: pop、shift、unshift、splice、sort、reverse内容都是非常相似的
// 具体我们就不一一列举的了,如果有疑问可以自己看到全部的单元测试代码
it('array push', function () {
var arr = [{a:1}, {a:2}]
var ob = Observer.create(arr)
ob.on('mutate', spy)
arr.push({a:3})
expect(spy.calls.mostRecent().args[0]).toBe('')
expect(spy.calls.mostRecent().args[1]).toBe(arr)
var mutation = spy.calls.mostRecent().args[2]
expect(mutation).toBeDefined()
expect(mutation.method).toBe('push')
expect(mutation.index).toBe(2)
expect(mutation.removed.length).toBe(0)
expect(mutation.inserted.length).toBe(1)
expect(mutation.inserted[0]).toBe(arr[2])
})
// 我们可以看到响应式数据中存在$add方法,类似于Vue.set,可以监听add事件
// 可以向响应式对象中添加新一个属性,如果之前存在该属性则操作会被忽略
// 并且新赋值的对象也必须被响应化
// 我们省略了对象数据$delete方法的单元测试,功能类似于Vue.delete,与$add方法相反,可以用于删除对象的属性
// 我们省略了数组的$set方法的单元测试,功能也类似与Vue.set,可以用于设置数组对应数字下标的值
// 我们省略了数组的$remove方法的单元测试,功能用于移除数组给定下标的值或者给定的值,例如:
// var arr = [{a:1}, {a:2}]
// var ob = Observer.create(arr)
// arr.$remove(0) => 移除对应下标的值 或者
// arr.$remove(arr[0]) => 移除给定的值
it('object.$add', function () {
var obj = {a:{b:1}}
var ob = Observer.create(obj)
ob.on('add', spy)
// ignore existing keys
obj.$add('a', 123)
expect(spy.calls.count()).toBe(0)
// add event
var add = {d:2}
obj.a.$add('c', add)
expect(spy).toHaveBeenCalledWith('a.c', add, u)
// check if add object is properly observed
ob.on('set', spy)
obj.a.c.d = 3
expect(spy).toHaveBeenCalledWith('a.c.d', 3, u)
})
// 下面的测试用例用来表示如果两个不同对象parentA、parentB的属性指向同一个对象obj,那么该对象obj改变时会分别parentA与parentB的监听事件
it('shared observe', function () {
var obj = { a: 1 }
var parentA = { child1: obj }
var parentB = { child2: obj }
var obA = Observer.create(parentA)
var obB = Observer.create(parentB)
obA.on('set', spy)
obB.on('set', spy)
obj.a = 2
expect(spy.calls.count()).toBe(2)
expect(spy).toHaveBeenCalledWith('child1.a', 2, u)
expect(spy).toHaveBeenCalledWith('child2.a', 2, u)
// test unobserve
parentA.child1 = null
obj.a = 3
expect(spy.calls.count()).toBe(4)
expect(spy).toHaveBeenCalledWith('child1', null, u)
expect(spy).toHaveBeenCalledWith('child2.a', 3, u)
})
})
реализация исходного кода
множество
Если мы можем настаивать на том, чтобы увидеть здесь, мы на полпути Великого похода, мы уже знаемOberver
Функции, предоставленные извне, теперь давайте посмотримOberver
Принцип внутренней реализации.
Oberver
Модуль фактически принимаетнаследование композиции(Заемный конструктор+прототипное наследование) по наследствуEmitter
, целью которого является наследованиеEmitter
изon
, off
,emit
и другие методы. В приведенном выше тестовом примере мы обнаружили, что не использовалиnew
метод прямого созданияOberver
экземпляр объекта вместо использования фабричного методаOberver.create
метод для создания, давайте посмотрим на исходный код дальше, потому что кода много, я постараюсь разбить его на маленькие кусочки:
// 代码出自于observe.js
// 为了方便讲解我对代码顺序做了改变,要了解详细的情况可以查看具体的源码
var _ = require('../util')
var Emitter = require('../emitter')
var arrayAugmentations = require('./array-augmentations')
var objectAugmentations = require('./object-augmentations')
var uid = 0
/**
* Type enums
*/
var ARRAY = 0
var OBJECT = 1
function Observer (value, type, options) {
Emitter.call(this, options && options.callbackContext)
this.id = ++uid
this.value = value
this.type = type
this.parents = null
if (value) {
_.define(value, '$observer', this)
if (type === ARRAY) {
_.augment(value, arrayAugmentations)
this.link(value)
} else if (type === OBJECT) {
if (options && options.doNotAlterProto) {
_.deepMixin(value, objectAugmentations)
} else {
_.augment(value, objectAugmentations)
}
this.walk(value)
}
}
}
var p = Observer.prototype = Object.create(Emitter.prototype)
Observer.pathDelimiter = '\b'
Observer.emitGet = false
Observer.create = function (value, options) {
if (value &&
value.hasOwnProperty('$observer') &&
value.$observer instanceof Observer) {
return value.$observer
} if (_.isArray(value)) {
return new Observer(value, ARRAY, options)
} else if (
_.isObject(value) &&
!value.$scope // avoid Vue instance
) {
return new Observer(value, OBJECT, options)
}
}
Начнем сObserver.create
кажется, что еслиvalue
Значение не отвечает (в зависимости от того, содержит ли оно$observer
атрибут для оценки), используйте оператор new для создания экземпляра наблюдателя (различайте объект OBJECT и массив ARRAY). Далее мы видимObserver
Как конструктор , сначала заимствованEmitter
Конструктор:
Emitter.call(this, options && options.callbackContext)
С прототипным наследованием
var p = Observer.prototype = Object.create(Emitter.prototype)
Комбинаторное наследованиеEmitter
,следовательноObserver
наследоватьEmitter
свойства (ctx
) и методы (on
,emit
Ждать). Мы видим, чтоObserver
Обладает следующими свойствами:
-
id
: Уникальный идентификатор ответных данных. -
value
: Необработанные данные -
type
: определяет, является ли это массивом или объектом -
parents
: определяет родителя ответных данных, их может быть несколько, напримерvar obj = { a : { b: 1}}
, в процессе{b: 1}
в ответном процессеparents
Свойство указывает наobj
из$observer
.
Рассмотрим сначала присвоение значения данным$observer
свойство, которое указывает на сам объект экземпляра._.define
внутри черезdefineProperty
Реализовано:
define = function (obj, key, val, enumerable) {
Object.defineProperty(obj, key, {
value : val,
enumerable : !!enumerable,
writable : true,
configurable : true
})
}
Давайте сначала посмотрим, как работать с данными типа массива
if (type === ARRAY) {
_.augment(value, arrayAugmentations)
this.link(value)
}
Если вы читали мои первые две статьи, то помните, что в то время мы также делали упор на принцип отзывчивости массива.Общий принцип заключается в том, что мы маскируем мутацию исходного массива, устанавливая для массива новый объект-прототип объект.метод, общий принцип может быть таким:
function observifyArray(array){
var aryMethods = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'];
var arrayAugmentations = Object.create(Array.prototype);
aryMethods.forEach((method)=> {
let original = Array.prototype[method];
arrayAugmentations[method] = function () {
// 调用对应的原生方法并返回结果
// do everything you what do !
return original.apply(this, arguments);
};
});
array.__proto__ = arrayAugmentations;
}
Возвращаясь к исходному коду Vue, хотя мы знаем, что основной принцип определенно тот же, нам все же нужно взглянуть наarrayAugmentations
что это такое? подarrayAugmentations
Код довольно длинный. Мы объясним обоснование в комментариях:
// 代码来自于array-augmentations.js
var _ = require('../util')
var arrayAugmentations = Object.create(Array.prototype)
// 这边操作和我们之前的实现方式非常相似
// 创建arrayAugmentations原型继承`Array.prototype`从而可以调用数组的原生方法
// 然后通过arrayAugmentations覆盖数组的变异方法,基本逻辑大致相同
['push','pop','shift','unshift','splice','sort','reverse'].forEach(function (method) {
var original = Array.prototype[method]
// 覆盖arrayAugmentations中的变异方法
_.define(arrayAugmentations, method, function () {
var args = _.toArray(arguments)
// 这里调用了原生的数组变异方法,并获得结果
var result = original.apply(this, args)
var ob = this.$observer
var inserted, removed, index
// 下面switch这一部分代码看起来很长,其实目的就是针对于不同的变异方法生成:
// insert removed inserted 具体的含义对照之前的解释,了解即可
switch (method) {
case 'push':
inserted = args
index = this.length - args.length
break
case 'unshift':
inserted = args
index = 0
break
case 'pop':
removed = [result]
index = this.length
break
case 'shift':
removed = [result]
index = 0
break
case 'splice':
inserted = args.slice(2)
removed = result
index = args[0]
break
}
// 如果给数组中插入新的数据,则需要调用ob.link
// link函数其实在上面的_.augment(value, arrayAugmentations)之后也被调用了
// 具体的实现我们可以先不管
// 我们只要知道其目的就是分别对插入的数据执行响应化
if (inserted) ob.link(inserted, index)
// 其实从link我们就可以猜出unlink是干什么的
// 主要就是对删除的数据解除响应化,具体实现逻辑后面解释
if (removed) ob.unlink(removed)
// updateIndices我们也先不讲是怎么实现的,
// 目的就是更新子元素在parents的key
// 因为push和pop是不会改变现有元素的位置,因此不需要调用
// 而诸如splce shift unshift等变异方法会改变对应下标值,因此需要调用
if (method !== 'push' && method !== 'pop') {
ob.updateIndices()
}
// 同样我们先不考虑propagate内部实现,我们只要propagate函数的目的就是
// 触发自身及其递归触发父级的事件
// 如果数组中的数据有插入或者删除,则需要对外触发"length"被改变
if (inserted || removed) {
ob.propagate('set', 'length', this.length)
}
// 对外触发mutate事件
// 可以对照我们之前讲的测试用例'array push',就是在这里触发的,回头看看吧
ob.propagate('mutate', '', this, {
method : method,
args : args,
result : result,
index : index,
inserted : inserted || [],
removed : removed || []
})
return result
})
})
// 可以回看一下测试用例 array set,目的就是设置对应下标的值
// 其实就是调用了splice变异方法, 其实我们在Vue中国想要改变某个下标的值的时候
// 官网给出的建议无非是Vue.set或者就是splice,都是相同的原理
// 注意这里的代码忽略了超出下标范围的值
_.define(arrayAugmentations, '$set', function (index, val) {
if (index >= this.length) {
this.length = index + 1
}
return this.splice(index, 1, val)[0]
})
// $remove与$add都是一个道理,都是调用的是`splice`函数
_.define(arrayAugmentations, '$remove', function (index) {
if (typeof index !== 'number') {
index = this.indexOf(index)
}
if (index > -1) {
return this.splice(index, 1)[0]
}
})
module.exports = arrayAugmentations
Вышеприведенный код относительно длинный, и конкретное объяснение было прокомментировано в коде. Здесь мы узналиarrayAugmentations
, Давайте посмотрим_.augment
Что вы наделали. Мы в статьеРазмышления об отзывчивости массивов VueЯ упомянул, что Vue передается через__proto__
для достижения отзывчивости массива, но из-за__proto__
Это нестандартный атрибут. Хотя многие производители браузеров в основном реализовали этот атрибут, все еще есть некоторые версии Android, которые не поддерживают этот атрибут. Vue должен справиться с этим._.augment
Ответственный за эту часть:
exports.augment = '__proto__' in {}
? function (target, proto) {
target.__proto__ = proto
}
: exports.deepMixin
exports.deepMixin = function (to, from) {
Object.getOwnPropertyNames(from).forEach(function (key) {
var desc =Object.getOwnPropertyDescriptor(from, key)
Object.defineProperty(to, key, desc)
})
}
Мы видим, что если браузер не поддерживает__proto__
вызовdeepMixin
функция. а такжеdeepMixin
Реализация также очень проста, просто используйтеObject.defineProperty
оригинальный объектдескриптор свойстваПрисваивается целевому объекту. Затем вызывается функция:
this.link(value)
оlink
Функции, которые мы уже видели в комментариях выше:
if (inserted) ob.link(inserted, index)
В то время наше объяснение заключалось в том, чтобы сделать вновь вставленные данные отзывчивыми. Зная функцию, давайте посмотрим на реализацию кода:
// p === Observer.prototype
p.link = function (items, index) {
index = index || 0
for (var i = 0, l = items.length; i < l; i++) {
this.observe(i + index, items[i])
}
}
p.observe = function (key, val) {
var ob = Observer.create(val)
if (ob) {
// register self as a parent of the child observer.
var parents = ob.parents
if (!parents) {
ob.parents = parents = Object.create(null)
}
if (parents[this.id]) {
_.warn('Observing duplicate key: ' + key)
return
}
parents[this.id] = {
ob: this,
key: key
}
}
}
На самом деле логика кода очень проста,link
Функция будет вызываться для элементов после заданного индекса массива (по умолчанию 0)this.observe
, а такжеobserve
В самом деле, для данногоval
рекурсивный вызов значенияObserver.create
, Сделайте данные отзывчивыми и установите соответствующую связь между наблюдателем родителя и текущим экземпляром. На самом деле мы обнаружили, что Vue не только реагирует на вставленные данные, но и вызывает удаленные элементы.unlink
, конкретный код вызова:
if (removed) ob.unlink(removed)
Мы грубо сказали, что его использование заключается в том, чтобы отключить удаленные данные, давайте посмотрим на конкретную реализацию:
p.unlink = function (items) {
for (var i = 0, l = items.length; i < l; i++) {
this.unobserve(items[i])
}
}
p.unobserve = function (val) {
if (val && val.$observer) {
val.$observer.parents[this.id] = null
}
}
Код очень простой, достаточно вызвать данныеunobserve
,а такжеunobserve
Основная цель функции - удалить родителяobserver
Отношения с текущими данными и ссылки больше не сохраняются, что позволяет ядру браузера при необходимости освобождать место в памяти.
существуетarrayAugmentations
На самом деле он также называлсяObserver
Один из двух методов-прототипов:
ob.updateIndices()
другой:
ob.propagate('set', 'length', this.length)
Первый взглядupdateIndices
Функция, функцией функции на тот момент было обновление ключа дочернего элемента у родителей, давайте посмотрим на конкретную реализацию:
p.updateIndices = function () {
var arr = this.value
var i = arr.length
var ob
while (i--) {
ob = arr[i] && arr[i].$observer
if (ob) {
ob.parents[this.id].key = i
}
}
}
Затем посмотрите на функциюpropagate
:
p.propagate = function (event, path, val, mutation) {
this.emit(event, path, val, mutation)
if (!this.parents) return
for (var id in this.parents) {
var parent = this.parents[id]
if (!parent) continue
var key = parent.key
var parentPath = path
? key + Observer.pathDelimiter + path
: key
parent.ob.propagate(event, parentPath, val, mutation)
}
}
Мы говорили это раньшеpropagate
Функция функции состоит в том, чтобы инициировать событие самой себя и ее рекурсивного родительского триггера, первый вызовemit
Время внешнего срабатывания функции, его параметры: имя события, путь, значение,mutatin
объект. Затем рекурсивно вызовите родительское событие, и соответствующие триггеры измененияpath
параметр.parentPath
равныйparents[id].key
+ Observer.pathDelimiter
+ path
До сих пор мы узнали, как Vue обрабатывает отзывчивость массивов, и теперь нам нужно увидеть, как обрабатывать отзывчивость объектов.
Объект
существуетObserver
Код для обработки объекта в конструкторе 's:
if (type === OBJECT) {
if (options && options.doNotAlterProto) {
_.deepMixin(value, objectAugmentations)
} else {
_.augment(value, objectAugmentations)
}
this.walk(value)
}
Как и с массивами, нам сначала нужно понятьobjectAugmentations
Внутренняя реализация:
var _ = require('../util')
var objectAgumentations = Object.create(Object.prototype)
_.define(objectAgumentations, '$add', function (key, val) {
if (this.hasOwnProperty(key)) return
_.define(this, key, val, true)
var ob = this.$observer
ob.observe(key, val)
ob.convert(key, val)
ob.emit('add:self', key, val)
ob.propagate('add', key, val)
})
_.define(objectAgumentations, '$delete', function (key) {
if (!this.hasOwnProperty(key)) return
delete this[key]
var ob = this.$observer
ob.emit('delete:self', key)
ob.propagate('delete', key)
})
в сравнении сarrayAugmentations
,objectAgumentations
Внутренняя реализация намного проще,objectAgumentations
Добавлено два метода:$add
а также$delete
.
$add
Используется для добавления новых свойств к объекту, если объект имеет значение ключа доkey
свойства ничего не делают, иначе сначала используйте_.define
назначьте свойство, затем вызовитеob.observe
Цель состоит в том, чтобы рекурсивно вызывать так, чтобыval
Значение отзывчиво. а такжеconvert
Роль функции заключается в преобразовании свойства в свойство доступа.getter/setter
Чтобы мы могли следить за доступом к свойству или его изменением, я могу взглянуть на него.convert
Внутренняя реализация функции:
p.convert = function (key, val) {
var ob = this
Object.defineProperty(ob.value, key, {
enumerable: true,
configurable: true,
get: function () {
if (Observer.emitGet) {
ob.propagate('get', key)
}
return val
},
set: function (newVal) {
if (newVal === val) return
ob.unobserve(val)
val = newVal
ob.observe(key, newVal)
ob.emit('set:self', key, newVal)
ob.propagate('set', key, newVal)
}
})
}
convert
Внутренняя реализация функции не сложна, вget
функция, если глобальнаяObserver.emitGet
switch, при доступе к свойству он вызоветpropagate
Сам триггер и аналог родителяget
мероприятие. существуетset
функция, первый вызовunobserve
Ценностный контакт между парой является реактивным, после чего следует вызовob.observe
Сделайте вновь назначенные данные отзывчивыми. наконец, сначала запустите себяset:self
мероприятие, затем позвонитеpropagate
Сам триггер и аналог родителяset
мероприятие.
$delete
Атрибут, используемый для удаления объекта, если атрибут не существует, выйдите напрямую, в противном случае используйте сначалаdelete
Оператор удаляет свойства объекта, а затем запускает собственный внешнийdelete:self
мероприятие, затем позвонитеdelete
Сам триггер и соответствующий родительdelete
мероприятие.
закончить смотретьobjectAgumentations
После этого мыObserver
Конструктор знает, что если переданные параметры существуютop.doNotAlterProto
означает не изменять прототип объекта, использоватьdeepMixin
функция будет$add
а также$delete
функция добавляется к объекту, иначе функция беретсяarguments
функция будет$add
а также$delete
добавляется к прототипу объекта. наконец позвонилwalk
функция, посмотримwalk
Является ли внутренним, как достичь:
p.walk = function (obj) {
var key, val, descriptor, prefix
for (key in obj) {
prefix = key.charCodeAt(0)
if (
prefix === 0x24 || // $
prefix === 0x5F // _
) {
continue
}
descriptor = Object.getOwnPropertyDescriptor(obj, key)
// only process own non-accessor properties
if (descriptor && !descriptor.get) {
val = obj[key]
this.observe(key, val)
this.convert(key, val)
}
}
}
первый ходobj
Каждый атрибут в , если да$
или_
Имена атрибутов, начинающиеся с, не обрабатываются. Затем получить дескриптор атрибута, если он не существуетget
функция, вызовите значение свойстваobserve
функция, чтобы сделать данные реактивными, затем вызовитеconvert
функция преобразует свойство в свойство доступаgetter/setter
Позволяет отслеживать свойство при доступе к нему или изменении.
Суммировать
На данный момент мы прочитали весьObserver
Весь код модуля, по сути, базовый принцип аналогичен тому, что мы представляли ранее, но гранулярность декомпозиции каждой функции в коде Vue очень мала, что делает логику кода очень понятной. Увидев это, я рекомендую вам также клонировать исходный код Vue, перейти к соответствующему номеру версии, прочитать его самостоятельно, запустить тестовый пример, нажать точку останова и попытаться отладить его, это должно помочь вам понять этот модуль.
Наконец, если вам интересна эта серия статей, подписывайтесь на меняБлог на гитхабеЭто воодушевление для меня, спасибо всем за вашу поддержку!