源码学习
如何监听一个对象的变化
以上 还是很简单的。
再巩固一下get和set,用了以后对象的writable属性可就么有了哟。
两个缺陷 一无array 二对象的值若被设置成了object则该object的值不会被observe到
命名真是个问题…所有的递归循环都可以叫walk。不要叫getDeepObjs这么蠢的啦…
思路上也有不同。他的是递归循环时 每到一个对象都去observe这个单独的对象。而我的是先将对象扁平化再去循环observe...
当然我也是有自己的考虑的啦!
emitter.js
一个 Emit 构造函数,其原型上拥有 on、off、once、emit、applyEmit 这些方法
/**
* Simple event emitter based on component/emitter.
*
* @constructor
* @param {Object} ctx - the context to call listners with.
*/
function Emitter (ctx) {
this._ctx = ctx || this
}
var p = Emitter.prototype
/**
* Listen on the given `event` with `fn`.
*
* @param {String} event
* @param {Function} fn
* @return {Emitter}
*/
p.on = function (event, fn) {
this._cbs = this._cbs || {}
//下行的初始化方式蛮喜欢的!
;(this._cbs[event] || (this._cbs[event] = []))
.push(fn)
return this
}
/**
* Adds an `event` listener that will be invoked a single
* time then automatically removed.
*
* @param {String} event
* @param {Function} fn
* @return {Emitter}
*/
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
}
/**
* Remove the given callback for `event` or all
* registered callbacks.
*
* @param {String} event
* @param {Function} fn
* @return {Emitter}
*/
//off处值得注意的是有解绑全部回调函数,解绑某事件的全部回调函数,解绑某事件的某回调函数之分。并且指针相同的对象是可以用===来比较的!
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]
if (cb === fn || cb.fn === fn) {
callbacks.splice(i, 1)
break
}
}
return this
}
/**
* The internal, faster emit with fixed amount of arguments
* using Function.call.
*
* @param {Object} event
* @return {Emitter}
*/
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
}
/**
* The external emit using Function.apply, used
* by Vue instance event methods.
*
* @param {Object} event
* @return {Emitter}
*/
p.applyEmit = function (event) {
this._cbs = this._cbs || {}
var callbacks = this._cbs[event], args
if (callbacks) {
//!!!还好我查了一下arr.slice(0)有什么用 原来这是一个从前duplicate array的cheap way呀!
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
}
module.exports = Emitter
utils.js
/**
* Mix properties into target object.
*
* @param {Object} to
* @param {Object} from
*/
exports.mixin = function (to, from) {
for (var key in from) {
if (to[key] !== from[key]) {
to[key] = from[key]
}
}
}
/**
* Mixin including non-enumerables, and copy property descriptors.
*
* @param {Object} to
* @param {Object} from
*/
exports.deepMixin = function (to, from) {
Object.getOwnPropertyNames(from).forEach(function (key) {
var descriptor = Object.getOwnPropertyDescriptor(from, key)
Object.defineProperty(to, key, descriptor)
})
}
/**
* Proxy a property on one object to another.
*
* @param {Object} to
* @param {Object} from
* @param {String} key
*/
exports.proxy = function (to, from, key) {
if (to.hasOwnProperty(key)) return
Object.defineProperty(to, key, {
enumerable: true,
configurable: true,
get: function () {
return from[key]
},
set: function (val) {
from[key] = val
}
})
}
/**
* Object type check. Only returns true
* for plain JavaScript objects.
*
* @param {*} obj
* @return {Boolean}
*/
exports.isObject = function (obj) {
return Object.prototype.toString.call(obj) === '[object Object]'
}
/**
* Array type check.
*
* @param {*} obj
* @return {Boolean}
*/
exports.isArray = function (obj) {
return Array.isArray(obj)
}
/**
* Define a non-enumerable property
*
* @param {Object} obj
* @param {String} key
* @param {*} val
* @param {Boolean} [enumerable]
*/
exports.define = function (obj, key, val, enumerable) {
Object.defineProperty(obj, key, {
value : val,
enumerable : !!enumerable,
writable : true,
configurable : true
})
}
/**
* Augment an target Object or Array by either
* intercepting the prototype chain using __proto__,
* or copy over property descriptors
*
* @param {Object|Array} target
* @param {Object} proto
*/
if ('__proto__' in {}) {
exports.augment = function (target, proto) {
target.__proto__ = proto
}
} else {
exports.augment = exports.deepMixin
}
其实看到里面用的最广的是define和augment。前者是给对象添加新的值,后者是给对象指定原型链<相当于对象的继承>。
有趣的是在Array中,使用arr.proto=another obj这样指定了原型链后,arr不仅拥有了原型链上的方法,Array.isArray(arr)还是true。
observer.js
Observer.create做了什么?
- 为一个value添加$observer属性<即new Observer()>。
new Observer()时发生了什么<即$observer的构成>?
- 给Emitter绑定this并执行<结果:this._ctx=this>。
- 定义了this.value, type和parents。
Object部分
- “ _.augment(value, objectAugmentations)”:给value加上了$add和$delete的继承方法、在$add时会去observe和convert这些属性。
- “this.walk(value)”:遍历value的所有属性,分别调用observe方法和convert方法,去observe和convert这些属性。
- observe方法: 入参<键值对>
继续给值<亦即子value>添加$observer属性。即对值而言重复上述所有步骤。
给子value的parents属性上添加{ob:自己的这个this,key: 键名}
// 这里有个点。一开始我不确定为什么子value的parents属性是个数组而不是仅一个值,后知后觉子value是可能有多个parents的。因为不同地方的键值可以指向同一个对象呀!
- convert方法:入参<键值对>
这里就是传统意义上通过Object.defineProperty对值的变化进行订阅的部分。get的话直接返回value。set的话,先unobserve value,再observe新的value。
- unobserve方法:入参<值>
这里的unobserve还蛮巧妙的,其实就是移除“值.$observer”里的parents里的ob === this项。<所以其实值还是在的,只是切断了它和某个key的联系而已>
Arrray部分
- “ _.augment(value, arrayAugmentations)”:关于数组的步骤就比较多一点了。
首先是定义可操作方法数组。执行操作时一方面通过Array.prototype[method].apply(this, arguments)获得正确答案,另一方面要对改变的index以及变动的items进行订阅更新。大头戏其实是更新:
每次数组被操作后,可得到index<发生变化index>, inserted<被插入items>,removed<被移除items>这三个关键字;
若有inserted,则执行$observer.link(index, inserted);
若有removed,则执行$observer.unlink(index, removed);
最后,还记得每一个$observer.parents属性吗?数组可以视为index-item的键值对对象,对于数组的item而言,parent的key是数组的index,所以如果数组的index与item的映射发生改变的话,也需要对每个item的parent里记录的key进行更新。除了push与pop外,其余操作都一定会改变映射,所以判断依据是if (method !== 'push' && method !== 'pop') {ob.updateIndices()}
除去以上更新外,还另定义了.\$set与.$remove<实质为调用splice>的语法糖。
- link方法:入参
遍历每个item, 并且调用observe方法,传入的键值对为
- unlink方法: 同上 调用unobserve方法
- updateIndices方法:
通过this.\$observer.value取到当前arr。遍历arr,对于每个item,定位到其$observer.parents中指向this的parent,更新该parent的key