赞
踩
本文通过分析Vue 2.0 源码,探讨一下在Vue 2.0的初始化过程中,如何生成响应式数据。最后我们将关键的代码抽取出来,模拟出具体的实现过程。
在使用Vue过程中,我们通常会将要绑定到页面HTML元素中的数据作为data方法的返回值。Vue实例会将这些数据转换为响应式数据,以支持其单项或双向的数据绑定。
考虑到Vue使用者的业务数据结构可能非常复杂,例如对象中包含数组,数组中每项又是一个js对象,如下代码中的情况:
data(){
return {
stulist:[
{id: 1, name: 'Tom'},
{id: 2, name: 'Jack'}
]};
}
Observe方法通过递归调用的方式,为数据中的每个属性逐个生成getter和setter方法,为每个子对象逐一生成依赖收集中使用到的数据。
export function observe (value: any, asRootData: ?boolean): Observer | void { if (!isObject(value) || value instanceof VNode) { return } let ob: Observer | void if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { ob = value.__ob__ } else if ( shouldObserve && !isServerRendering() && (Array.isArray(value) || isPlainObject(value)) && Object.isExtensible(value) && !value._isVue ) { ob = new Observer(value) } if (asRootData && ob) { ob.vmCount++ } return ob }
该构造方法主要用于区分对象和数组两种数据结构,每种数据结构对应不同的处理方法。
constructor (value: any) { this.value = value this.dep = new Dep() this.vmCount = 0 def(value, '__ob__', this) if (Array.isArray(value)) { if (hasProto) { protoAugment(value, arrayMethods) } else { copyAugment(value, arrayMethods, arrayKeys) } this.observeArray(value) } else { this.walk(value) } }
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
export function defineReactive ( obj: Object, key: string, val: any, customSetter?: ?Function, shallow?: boolean ) { const dep = new Dep() const property = Object.getOwnPropertyDescriptor(obj, key) if (property && property.configurable === false) { return } // cater for pre-defined getter/setters const getter = property && property.get const setter = property && property.set if ((!getter || setter) && arguments.length === 2) { val = obj[key] } let childOb = !shallow && observe(val) Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { const value = getter ? getter.call(obj) : val if (Dep.target) { dep.depend() if (childOb) { childOb.dep.depend() if (Array.isArray(value)) { dependArray(value) } } } return value }, set: function reactiveSetter (newVal) { const value = getter ? getter.call(obj) : val /* eslint-disable no-self-compare */ if (newVal === value || (newVal !== newVal && value !== value)) { return } /* eslint-enable no-self-compare */ if (process.env.NODE_ENV !== 'production' && customSetter) { customSetter() } // #7981: for accessor properties without setter if (getter && !setter) return if (setter) { setter.call(obj, newVal) } else { val = newVal } childOb = !shallow && observe(newVal) dep.notify() } }) }
protoAugment方法用于修改数组对象的原型属性__proto__,对数组对象的七个方法进行重新定制,从而到达监控数组变化的需求。
function protoAugment (target, src: Object) {
/* eslint-disable no-proto */
target.__proto__ = src
/* eslint-enable no-proto */
}
传入的scr内容来自src\core\observer\array.js
const arrayProto = Array.prototype export const arrayMethods = Object.create(arrayProto) const methodsToPatch = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ] /** * Intercept mutating methods and emit events */ methodsToPatch.forEach(function (method) { // cache original method const original = arrayProto[method] def(arrayMethods, method, function mutator (...args) { const result = original.apply(this, args) const ob = this.__ob__ let inserted switch (method) { case 'push': case 'unshift': inserted = args break case 'splice': inserted = args.slice(2) break } if (inserted) ob.observeArray(inserted) // notify change ob.dep.notify() return result }) })
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
数组内数据是否可以通过索引值进行修改?
不可以,只有通过’push’, ‘pop’, ‘shift’, ‘unshift’, ‘splice’, ‘sort’, 'reverse’这七个方法对数组的修改,Vue才能监控到。
通过splice方法修改数组,为什么从要忽略前两个参数, 通过args.slice(2)来取得新添加的元素?
splice方法包含两个required参数,第一个是原数组中要添加的新元素的位置,第二个是要添加多少个新元素,从第三个参起才是要添加的新元素。所以要slice方法的参数是2,即忽略前两个参数。
在defineReactive 方法中为每个属性定义getter和setter方式的时候,为什么要通过一个中间变量value或val来获取或设置属性值,不能直接通过obj[key]方式完成具体操作?
这样做会触发死循环,在getter中,如果通过obj[key]返回属性值,会再次触发obj的getter方法,从而形成死循环调用。setter方法同理。
模拟代码并不需要nodejs环境,可以直接在Chrome浏览器的Console中运行。
(function () { var uid = 0; function Vue(optioanl) { if (!(this instanceof Vue)) { console.error('Vue is constructor and should be called with new keyword'); } const vm = this; const { isPlainObject } = Utils(); const initData = function (optioanl) { let data = vm._data = typeof optioanl.data === 'function' ? optioanl.data.call(this) : {}; if (!isPlainObject(data)) { data = {}; console.error('data function should return an object.'); } var keys = Object.keys(data); for (let key of keys) { proxy(vm, '_data', key); } const { observe } = Reactive(); observe(data); }; const proxy = function (target, sourceKey, key) { const handler = { get: function () { return this[sourceKey][key]; }, set: function (val) { this[sourceKey][key] = val; } } Object.defineProperty(target, key, handler); }; initData(optioanl); } function Observer(target) { const { walk, observeArray } = Reactive(); const { def } = Utils(); this.dep = new Dep(); this.value = target; this.observeArray = observeArray.bind(this); def(target, "__ob__", this); if (Array.isArray(target)) { target = setArrayProto(target); observeArray(target); } else { console.log(target); walk(target); } } function Dep() { this.id = uid++; this.notity = function(){ console.log("Notify"); }; this.subs = []; this.addSub = function(sub){ this.subs.push(sub); }.bind(this); this.depend = function(){ if (window.__target != null){ this.addSub(window.__target); } }.bind(this); } function setArrayProto(target) { const arrayProto = {}; const { def } = Utils(); const methods = ["splice", "push", "pop", "reverse", "sort", "shift", "unshift"]; methods.forEach(method => { def(arrayProto, method, function (...args) { const protoMethod = Array.prototype[method]; const ob = this.__ob__; const result = protoMethod.apply(this, args); let inserted; switch (method) { case "push": case "unshift": inserted = args; break; case "splice": inserted = args.slice(2); break; default: break; } if (inserted) { ob.observeArray(inserted); ob.dep.notity(); } return result; }, false); }); target.__proto__ = arrayProto; return target; } function Reactive() { const { hasOwn, isObject } = Utils(); const defineReactive = function (obj, key) { let value = obj[key]; let childObj = observe(value); let dep = new Dep(); Object.defineProperty(obj, key, { configurable: true, enumerable: true, get: function () { console.log(`Get ${key}'s value`); return value; }, set: function (newVal) { if (value === newVal) { return; } console.log(`${key}'s value is updated from ${value} to ${newVal}`); value = newVal; childObj = observe(newVal); dep.notity(); } }); } const walk = function (target) { const keys = Object.keys(target); for (let key of keys) { defineReactive(target, key); } } const observe = function (obj) { if (!isObject(obj)) { return; } let ob; if (hasOwn(obj, "__ob__") && obj["__ob__"] instanceof Observer) { ob = obj["__ob__"]; } ob = new Observer(obj); return ob; }; const observeArray = function (target) { for (var i = 0, l = target.length; i < l; ++i) { observe(target[i]); } } return { defineReactive, walk, observe, observeArray }; } function Utils() { const hasOwn = function (target, key) { const hasOwnProperty = Object.prototype.hasOwnProperty; return hasOwnProperty.call(target, key); } const isObject = function (target) { if (target != null && typeof target === "object") { return true; } return false; } const isPlainObject = function (obj) { return (Object.prototype.toString.call(obj) === "[object Object]"); } const def = function (target, key, val, enumerable) { Object.defineProperty(target, key, { value: val, configurable: true, enumerable: !!enumerable, writable: true }) } return { hasOwn, isObject, isPlainObject, def }; } var v = new Vue({ data() { return { name: "ly", age: 21, address: { district: "ABC", street: "DEF" }, cards: [ { id: 1, title: "credit card" }, { id: 2, title: "visa card" }, ] } } }); })();
src\shared\util.js
export function isObject (obj: mixed): boolean %checks {
return obj !== null && typeof obj === 'object'
}
src\core\util\lang.js
function def (obj: Object, key: string, val: any, enumerable?: boolean) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable,
writable: true,
configurable: true
})
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。