Vue setter/getter 是何原理?
1 、 defineProperty 重定义对象
JS原生es5版本提供对象重新定义的接口 defineProperty
defineProperty 可以修改对象的访问器属性,对象属性值发生变化前后可以触发回调函数。
对象的访问器属性包括 2 种类型:数据描述符、 存取描述符
1.1 数据描述符
value:对象key的值,默认是 空字符串 ''
writeable:是否可写,默认 true
configurable:true是否可配置,默认 true
enumerable:true是否可枚举, 默认 true
Object.getOwnPropertyDescriptors(obj); { k:{ configurable: true, enumerable: true, value: 90, writable: true } }
1.2 存取描述符
set:function(){}属性访问器 进行写操作时调用该方法
get:function(){}属性访问器 进行读操作时调用该方法
属性描述符: configurable 、enumerable
configurable 、enumerable、 set 、 get
对象中新增key的value发生变化时会经过set和get方法。
var obj = {};
var temp = ''; Object.defineProperty(obj, 'name', { configurable: true, enumerable: true, get: function () { return temp; }, set: function (newValue) { temp = newValue } }); // 需要维护一个可访问的变量 temp
或写在 obj对象内,如下:
var obj = { tempValue: 'duyi', get name () { return this.tempValue; }, set name (newValue) { this.tempValue = newValue; } }; obj.name = 10; console.log( obj.name ); // 10
小结一次:到这里来基本上知道 getter setter是可以实现的,基于这个简单理论作出一个复杂逻辑。
2 、Observer 源码
/** * Observer类方法将对象修改为可被观察。 * 一旦应用了这个类方法, 对象的每一个key会被转换成 getter/setter * 用于收集依赖项和触发更新。 */ var Observer = function Observer (value) { this.value = value; // 保存被观察的值到方法的实例属性 this.dep = new Dep(); // 建立一个Dep实例 this.vmCount = 0; // vmCount 记录vm结构的个数 def(value, '__ob__', this); // value 对象 增加 ‘__ob__’ key,并赋值 this if (Array.isArray(value)) { // value 对象如果是数组 if (hasProto) { // 表示在[]上可以使用 __proto__
protoAugment(value, arrayMethods); // 将arrayMethods 添加到value的__proto__。arrayMethods 好像是重写了数组的一部分原生方法,后面再看 } else { copyAugment(value, arrayMethods, arrayKeys); // 说不定他不支持 __proto__ ,调用def方法增强对象。 } this.observeArray(value); // 然后将这个增强后的数组,每一项都执行observe } else { this.walk(value); // walk 在Observer的原型上,对象转换为 getter setter。 } }; /** * 遍历所有属性将它们转换为 getter/setter,仅当值类型为对象时调用。 */ Observer.prototype.walk = function walk (obj) { var keys = Object.keys(obj); for (var i = 0; i < keys.length; i++) { defineReactive$$1(obj, keys[i]); // defineReactive$$1 () 方法,这个方法才是实现 getter / setter 的原方法!!! } }; /** * 观察数组项列表 */ Observer.prototype.observeArray = function observeArray (items) { for (var i = 0, l = items.length; i < l; i++) { observe(items[i]); // observe 方法 } }; /** * 截获原型链使用 __proto__ 的方式来 * 增强一个目标的对象或数组,简称原型增强。 */ function protoAugment (target, src) { /* eslint-disable no-proto */ target.__proto__ = src; /* eslint-enable no-proto */ } /** * 增加对象原型properties */ function copyAugment (target, src, keys) { for (var i = 0, l = keys.length; i < l; i++) { var key = keys[i]; def(target, key, src[key]); } } /**
* 在Observer方法的原型属性 observerArray 上有用到!
* 大概意思是创建可观察实例,返回可观察实例或已有的可观察对象。 */ function observe (value, asRootData) {
// 如果不是object时,或是 VNode 的实例不进行 observe if (!isObject(value) || value instanceof VNode) { return } var ob; 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 } /** * 这里是实现getter setter的关键代码
* 这里是实现getter setter的关键代码
* 这里是实现getter setter的关键代码
*
* 在对象上定义一个 有反应的原型
* 传参:obj对象,key关键字,val值,customSetter在set的时候会被执行(第4个参数),shallow 默认为undefined,为 true 时不执行 observe 函数。
*/
function defineReactive$$1 ( obj, key, val, customSetter, shallow ) { var dep = new Dep(); var property = Object.getOwnPropertyDescriptor(obj, key); if (property && property.configurable === false) { return } // 预定义的getter和setter var getter = property && property.get; var setter = property && property.set; if ((!getter || setter) && arguments.length === 2) { val = obj[key]; } var childOb = !shallow && observe(val); Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { var 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) { var 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 (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(); } }); }
订阅observe对象的变化,通知触发update,参考订阅-发布模式。
/** * Dep 是可以有多个指令订阅的可观察对象,目的就是对一个目标深层处理 */ var uid = 0; var Dep = function Dep () { this.id = uid++; // 添加了2个实例属性,id 用于排序和 subs 数组统计sub this.subs = []; }; // 在 subs中添加 sub Dep.prototype.addSub = function addSub (sub) { this.subs.push(sub); }; // 从 subs中移除 sub Dep.prototype.removeSub = function removeSub (sub) { remove(this.subs, sub); }; Dep.prototype.depend = function depend () { if (Dep.target) { Dep.target.addDep(this); // addDep 是 Watcher 的原型方法,用于指令增加依赖 } }; Dep.prototype.notify = function notify () { // 稳定订阅列表 var subs = this.subs.slice(); if (!config.async) { // 如果不是在异步运行,在程序调度中 subs 不可以被排序! // 然后排序以确保正确的顺序。 subs.sort(function (a, b) { return a.id - b.id; }); } for (var i = 0, l = subs.length; i < l; i++) { // 然后顺序触发 Watcher 原型的 update 方法 subs[i].update(); } }; // 当前目标程序被评估,这个评估全局唯一,一次只要一个观察者可以被评估 Dep.target = null; var targetStack = []; // 将目标程序推送到目标栈 function pushTarget (target) { targetStack.push(target); Dep.target = target; } // 执行出栈先去掉 function popTarget () { targetStack.pop(); Dep.target = targetStack[targetStack.length - 1]; }
全局def方法,定义对象。
// def 方法比较简单 /** * 定义一个原型 * obj * key 对象的key关键字 * val 对象的value值 * 是否可枚举,默认可写可配置 */ function def (obj, key, val, enumerable) { Object.defineProperty(obj, key, { value: val, enumerable: !!enumerable, writable: true, configurable: true }); }