Attribute的源码解析:
--注:这部分的源码阅读起来比较费劲,可能解析的还不太到位,后续会多读几遍增添新的解释,后面随时更新。
// 负责 attributes 的初始化
// attributes 是与实例相关的状态信息,可读可写,发生变化时,会自动触发相关事件
exports.initAttrs = function(config) {
// initAttrs 是在初始化时调用的,默认情况下实例上肯定没有 attrs,不存在覆盖问题
// 在Base的initialize方法里进行调用,因此不存在覆盖的问题
var attrs = this.attrs = {};
// Get all inherited attributes.
// 获取所有需要继承的属性,specialProps是一个数组,里面存的是属性名称
var specialProps = this.propsInAttrs || [];
// 该方法的注释见下面,用于将所有的特殊指定的属性格式化固定格式后扩展到attrs里面
mergeInheritedAttrs(attrs, this, specialProps);
// Merge user-specific attributes from config.
if (config) {
mergeUserValue(attrs, config);
}
// 对于有 setter 的属性,要用初始值 set 一下,以保证关联属性也一同初始化
setSetterAttrs(this, attrs, config);
// Convert `on/before/afterXxx` config to event handler.
// 转化`on/before/afterXxx`的配置转化为事件处理
parseEventsFromAttrs(this, attrs);
// 将 this.attrs 上的 special properties 放回 this 上
copySpecialProps(specialProps, this, attrs, true);
};
// Get the value of an attribute.
// 从Base实例属性attrs里取对应的key值,都是:
// {
// value:,
// getter:,
// setter:
//}格式,有getter的默认会执行getter方法
exports.get = function(key) {
var attr = this.attrs[key] || {};
var val = attr.value;
return attr.getter ? attr.getter.call(this, val, key) : val;
};
// Set a hash of model attributes on the object, firing `"change"` unless you choose to silence it.
// 在对象上set模型属性hash值的时候默认会触发“change”事件,你也可以选择不触发
exports.set = function(key, val, options) {
var attrs = {};
// set("key", val, options)格式
if (isString(key)) {
attrs[key] = val;
}
// set({ "key": val, "key2": val2 }, options)格式(此时只有两个参数或一个,options可选)
else {
attrs = key;
options = val;
}
// 给options赋默认值防报错
options || (options = {});
// 设置了silent则不会触发change事件
var silent = options.silent;
var override = options.override;
var now = this.attrs;
// 纪录被修改过的属性
var changed = this.__changedAttrs || (this.__changedAttrs = {});
for (key in attrs) {
// key必须是attrs的实例属性
if (!attrs.hasOwnProperty(key)) continue;
// 纪录被修改前的attr值
var attr = now[key] || (now[key] = {});
val = attrs[key]; // 对应要设置的val值
if (attr.readOnly) {// 只读的属性不允许被set
throw new Error('This attribute is readOnly: ' + key);
}
// invoke setter 调用setter
if (attr.setter) {
val = attr.setter.call(this, val, key);
}
// 获取设置前的 prev 值
var prev = this.get(key);
// 获取需要设置的 val 值
// 如果设置了 override 为 true,表示要强制覆盖,就不去 merge 了
// 都为对象时,做 merge 操作,以保留 prev 上没有覆盖的值
if (!override && isPlainObject(prev) && isPlainObject(val)) {
val = merge(merge({},prev), val);
}
// set finally
now[key].value = val;
// invoke change event
// 初始化时对 set 的调用,不触发任何事件
// 初始化的时候会调用setSetterAttrs方法,在该方法内会调用set方法,此时this.__initializingAttrs为true
if (!this.__initializingAttrs && !isEqual(prev, val)) {
if (silent) {
changed[key] = [val, prev];
} else {
this.trigger('change:' + key, val, prev, key);
}
}
}
return this;
};
// Call this method to manually fire a `"change"` event for triggering
// a `"change:attribute"` event for each changed attribute.
// 为所有改变过的属性触发对应的事件
exports.change = function() {
var changed = this.__changedAttrs;
if (changed) {
for (var key in changed) {
if (changed.hasOwnProperty(key)) {
var args = changed[key];
this.trigger('change:' + key, args[0], args[1], key);
}
}
// 触发完后删除该属性以免重复触发
delete this.__changedAttrs;
}
return this;
};
// for test
exports._isPlainObject = isPlainObject;
// Helpers
// -------
var toString = Object.prototype.toString;
var hasOwn = Object.prototype.hasOwnProperty;
/**
* Detect the JScript [[DontEnum]] bug:
* In IE < 9 an objects own properties, shadowing non-enumerable ones, are
* made non-enumerable as well.
* https://github.com/bestiejs/lodash/blob/7520066fc916e205ef84cb97fbfe630d7c154158/lodash.js#L134-L144
*/
/** Detect if own properties are iterated after inherited properties (IE < 9) */
// 检测如果自持有的属性是否在被继承属性后被迭代
var iteratesOwnLast;
(function() {
var props = [];
function Ctor() {
this.x = 1;
}
Ctor.prototype = {
'valueOf': 1,
'y': 1
};
for (var prop in new Ctor()) {
props.push(prop);
}
iteratesOwnLast = props[0] !== 'x';
} ());
var isArray = Array.isArray ||
function(val) {
return toString.call(val) === '[object Array]';
};
function isString(val) {
return toString.call(val) === '[object String]';
}
function isFunction(val) {
return toString.call(val) === '[object Function]';
}
function isWindow(o) {
return o != null && o == o.window;
}
function isPlainObject(o) {
// Must be an Object.
// Because of IE, we also have to check the presence of the constructor
// property. Make sure that DOM nodes and window objects don't pass through, as well
// o必须是一个对象。因为IE,我们不得不检查constructor属性的存在性。我们也要保证DOM节点对象以及window对象也不能通过检测。
if (!o || toString.call(o) !== "[object Object]" || o.nodeType || isWindow(o)) {
return false;
}
try {
// Not own constructor property must be Object
// 没有直接持有constructor属性的必定是对象
if (o.constructor && !hasOwn.call(o, "constructor") && !hasOwn.call(o.constructor.prototype, "isPrototypeOf")) {
return false;
}
} catch(e) {
// IE8,9 Will throw exceptions on certain host objects #9897
return false;
}
var key;
// Support: IE<9
// Handle iteration over inherited properties before own properties.
// http://bugs.jquery.com/ticket/12199
// IE<9的情况下处理继承属性在自己拥有的属性前被迭代
if (iteratesOwnLast) {
for (key in o) {
// 继承的属性会被先迭代,所以有继承的属性的返回false,没有则返回true
return hasOwn.call(o, key);
}
}
// Own properties are enumerated firstly, so to speed up,
// if last one is own, then all properties are own.
// o为{}空对象的时候key为undefined;因为现代浏览器枚举对象属性的顺序是按照先枚举自身的属性后枚举继承的属性,那么如果最后一个被迭代的属性不是继承属性,那么该对象符合条件
for (key in o) {}
return key === undefined || hasOwn.call(o, key);
}
// 空对象:{}
function isEmptyObject(o) {
if (!o || toString.call(o) !== "[object Object]" || o.nodeType || isWindow(o) || !o.hasOwnProperty) {
return false;
}
for (var p in o) {
if (o.hasOwnProperty(p)) return false;
}
return true;
}
function merge(receiver, supplier) {
var key, value;
for (key in supplier) {
if (supplier.hasOwnProperty(key)) {
// 会对数组和plain 对象做复制,其他保持不变
receiver[key] = cloneValue(supplier[key], receiver[key]);
}
}
return receiver;
}
// 只 clone 数组和 plain object,其他的保持不变
function cloneValue(value, prev) {
if (isArray(value)) {
value = value.slice();
} else if (isPlainObject(value)) {
isPlainObject(prev) || (prev = {});
value = merge(prev, value);
}
return value;
}
var keys = Object.keys;
// 扩展Object的keys方法
if (!keys) {
keys = function(o) {
var result = [];
for (var name in o) {
if (o.hasOwnProperty(name)) {
result.push(name);
}
}
return result;
};
}
// attrs:base实例的attrs属性;instance:Base实例;specialProps:存放特殊属性名的数组
function mergeInheritedAttrs(attrs, instance, specialProps) {
// 用于存放非空的proto.attrs
var inherited = [];
// 获取实例构造函数的原型
var proto = instance.constructor.prototype;
while (proto) {
// 不要拿到 prototype 上的
if (!proto.hasOwnProperty('attrs')) {
// 如果没有attrs属性则将attrs赋值给proto
proto.attrs = {};
}
// 将 proto 上的特殊 properties 放到 proto.attrs 上,以便合并
// 简单的将proto的特殊属性复制到proto.attrs
copySpecialProps(specialProps, proto.attrs, proto);
// 为空时不添加
if (!isEmptyObject(proto.attrs)) {
inherited.unshift(proto.attrs);
}
// 向上回溯一级
proto = proto.constructor.superclass;
}
// Merge and clone default values to instance.
for (var i = 0,len = inherited.length; i < len; i++) {
// normalize将对象置为固定格式
// {
// value: 'xx',
// getter: fn,
// setter: fn,
// readOnly: boolean
// }
// 将所有继承过来的特殊属性merge给attrs
mergeAttrs(attrs, normalize(inherited[i]));
}
}
function mergeUserValue(attrs, config) {
mergeAttrs(attrs, normalize(config, true), true);
}
function copySpecialProps(specialProps, receiver, supplier, isAttr2Prop) {
for (var i = 0,len = specialProps.length; i < len; i++) {
var key = specialProps[i];
if (supplier.hasOwnProperty(key)) {
receiver[key] = isAttr2Prop ? receiver.get(key) : supplier[key];
}
}
}
var EVENT_PATTERN = /^(on|before|after)([A-Z].*)$/;
var EVENT_NAME_PATTERN = /^(Change)?([A-Z])(.*)/;
// 从attrs里解析事件,由onChangeTitle|befoerChangeTitle|afterChangeTitle转化为监听事件或切面编程
function parseEventsFromAttrs(host, attrs) {
for (var key in attrs) {
if (attrs.hasOwnProperty(key)) {
var value = attrs[key].value,
m;
if (isFunction(value) && (m = key.match(EVENT_PATTERN))) {
host[m[1]](getEventName(m[2]), value);
delete attrs[key];
}
}
}
}
// Converts `Show` to `show` and `ChangeTitle` to `change:title`
function getEventName(name) {
var m = name.match(EVENT_NAME_PATTERN);
var ret = m[1] ? 'change:': '';
ret += m[2].toLowerCase() + m[3];
return ret;
}
function setSetterAttrs(host, attrs, config) {
var options = {
silent: true
};
host.__initializingAttrs = true;
for (var key in config) {
if (config.hasOwnProperty(key)) {
if (attrs[key].setter) {
host.set(key, config[key], options);
}
}
}
delete host.__initializingAttrs;
}
var ATTR_SPECIAL_KEYS = ['value', 'getter', 'setter', 'readOnly'];
// normalize `attrs` to
//
// {
// value: 'xx',
// getter: fn,
// setter: fn,
// readOnly: boolean
// }
//
function normalize(attrs, isUserValue) {
var newAttrs = {};
for (var key in attrs) {
var attr = attrs[key];
if (!isUserValue && isPlainObject(attr) && hasOwnProperties(attr, ATTR_SPECIAL_KEYS)) {
newAttrs[key] = attr;
continue;
}
// 用户数据只保存value值
newAttrs[key] = {
value: attr
};
}
return newAttrs;
}
var ATTR_OPTIONS = ['setter', 'getter', 'readOnly'三个属性值赋给attr];
// 专用于 attrs 的 merge 方法
function mergeAttrs(attrs, inheritedAttrs, isUserValue) {
var key, value;
var attr;
for (key in inheritedAttrs) {
if (inheritedAttrs.hasOwnProperty(key)) {
value = inheritedAttrs[key];
attr = attrs[key];
if (!attr) {
// 如果attrs里没有对应的key值,则将其赋为一个空对象
attr = attrs[key] = {};
}
// 从严谨上来说,遍历 ATTR_SPECIAL_KEYS 更好
// 从性能来说,直接 人肉赋值 更快
// 这里还是选择 性能优先
// 只有 value 要复制原值,其他的直接覆盖即可
(value['value'] !== undefined) && (attr['value'] = cloneValue(value['value'], attr['value']));
// 如果是用户赋值,只要考虑value
if (isUserValue) continue;
for (var i in ATTR_OPTIONS) {
var option = ATTR_OPTIONS[i];
if (value[option] !== undefined) {
// 'setter', 'getter', 'readOnly'三个属性值赋给attr
attr[option] = value[option];
}
}
}
}
return attrs;
}
// 判断object对象是否有properties数组中的实例属性
function hasOwnProperties(object, properties) {
for (var i = 0,len = properties.length; i < len; i++) {
if (object.hasOwnProperty(properties[i])) {
return true;
}
}
return false;
}
// 对于 attrs 的 value 来说,以下值都认为是空值: null, undefined, '', [], {}
function isEmptyAttrValue(o) {
return o == null || // null, undefined
(isString(o) || isArray(o)) && o.length === 0 || // '', []
isEmptyObject(o); // {}
}
// 判断属性值 a 和 b 是否相等,注意仅适用于属性值的判断,非普适的 === 或 == 判断。
// 也可参考underscore的isEqual()方法
function isEqual(a, b) {
if (a === b) return true;
if (isEmptyAttrValue(a) && isEmptyAttrValue(b)) return true;
// Compare `[[Class]]` names.
var className = toString.call(a);
if (className != toString.call(b)) return false;
switch (className) {
// Strings, numbers, dates, and booleans are compared by value.
case '[object String]':
// Primitives and their corresponding object wrappers are
// equivalent; thus, `"5"` is equivalent to `new String("5")`.
return a == String(b);
case '[object Number]':
// `NaN`s are equivalent, but non-reflexive. An `equal`
// comparison is performed for other numeric values.
return a != +a ? b != +b: (a == 0 ? 1 / a == 1 / b: a == +b);
case '[object Date]':
case '[object Boolean]':
// Coerce dates and booleans to numeric primitive values.
// Dates are compared by their millisecond representations.
// Note that invalid dates with millisecond representations
// of `NaN` are not equivalent.
return + a == +b;
// RegExps are compared by their source patterns and flags.
case '[object RegExp]':
return a.source == b.source && a.global == b.global && a.multiline == b.multiline && a.ignoreCase == b.ignoreCase;
// 简单判断数组包含的 primitive 值是否相等
case '[object Array]':
var aString = a.toString();
var bString = b.toString();
// 只要包含非 primitive 值,为了稳妥起见,都返回 false
return aString.indexOf('[object') === -1 && bString.indexOf('[object') === -1 && aString === bString;
}
if (typeof a != 'object' || typeof b != 'object') return false;
// 简单判断两个对象是否相等,只判断第一层
if (isPlainObject(a) && isPlainObject(b)) {
// 键值不相等,立刻返回 false
if (!isEqual(keys(a), keys(b))) {
return false;
}
// 键相同,但有值不等,立刻返回 false
for (var p in a) {
if (a[p] !== b[p]) return false;
}
return true;
}
// 其他情况返回 false, 以避免误判导致 change 事件没发生
return false;
}
var Class = require('arale-class');
var Events = require('arale-events');
var Aspect = require('./aspect');
var Attribute = require('./attribute');
module.exports = Class.create({
Implements: [Events, Aspect, Attribute],
initialize: function(config) {
// 初始化,具体见Attribute的initAttrs方法
this.initAttrs(config);
// Automatically register `this._onChangeAttr` method as
// a `change:attr` event handler.
parseEventsFromInstance(this, this.attrs);
},
destroy: function() {
// 解绑所有事件
this.off();
for (var p in this) {
if (this.hasOwnProperty(p)) {
// 删除所有的属性
delete this[p];
}
}
// Destroy should be called only once, generate a fake destroy after called
// https://github.com/aralejs/widget/issues/50
// 在第一次调用 destroy 后,生成一个空的 destroy,这样不会报错也不会多执行
this.destroy = function() {};
}
});
function parseEventsFromInstance(host, attrs) {
for (var attr in attrs) {
if (attrs.hasOwnProperty(attr)) {
// 有_onChangeTitle格式的属性则添加对应的事件
var m = '_onChange' + ucfirst(attr);
if (host[m]) { // 对应的事件处理handler
host.on('change:' + attr, host[m]);
}
}
}
}
// 首字母大写
function ucfirst(str) {
return str.charAt(0).toUpperCase() + str.substring(1);
}