系列文章:讀 arale 源碼之 class 篇node
attributes
提供基本的屬性添加、獲取、移除等功能。它是與實例相關的狀態信息,可讀可寫,發生變化時,會自動觸發相關事件segmentfault
先來了解一下 Attribute 模塊要實現的功能:數組
設置屬性值瀏覽器
{ attr1: 'hello1', // 至關於 attr1 的方式 attr2: { value: 'hello2' } }
在屬性設置和獲取前,觸發一個函數來先處理屬性閉包
{ attr3: { value: 'hello3', setter: function(v) { return v + ''; }, getter: function(v) { return v * 6; } } }
只讀屬性ide
{ attr4: { value: 1, readOnly: true } }
屬性發生改變時自動觸發相關事件函數
{ _onChangeAttr1: function(val) { console.log('attr1 changed by' + val); } }
設置屬性時,多了兩個狀態this
// {silent: true} 綁定的 _onChangeAttr1 不執行 instance.set('attr1', 2, {silent: true}); // {override: true} 默認 false,對象混合,爲 true 時,直接覆蓋對象。 instance.set('attr2', {w: 12, h: 33}, {override: true});
接下來再去看看源碼怎麼實現的!prototype
initAttrs
將在實例化對象時調用。code
exports.initAttrs = function(config) { // initAttrs 是在初始化時調用的,默認狀況下實例上確定沒有 attrs,不存在覆蓋問題 var attrs = this.attrs = {}; // 獲得全部繼承的屬性 var specialProps = this.propsInAttrs || []; // 合併和克隆父類的 attrs 值到實例的 attrs 上 mergeInheritedAttrs(attrs, this, specialProps); // 從 config 中合併屬性 if (config) { mergeInheritedAttrs(atts, config); } // 對於有 setter 的屬性,要用初始值 set 一下,以保證關聯屬性也一同初始化 setSetterAttrs(this, attrs, config); // Convert `on/before/afterXxx` config to event handler. parseEventsFromAttrs(this, attrs); // 將 this.attrs 上的 special properties 放回 this 上 copySpecialProps(specialProps, this, attrs, true); }
initAttrs
中用到的幾個方法merge
合併屬性function merge(receiver, supplier) { var key, value; for (key in supplier) { if (supplier.hasOwnPrototype(key)) { value = supplier[key]; // 只 clone 數組和 簡單對象,其餘保持不變 if (isArray(value)) { // 從新返回一個數組,不會佔用同一個內存地址 value = value.slice(); } else if (isPlainObject(value)) { // 接收者合併以前的值不是簡單對象的話,將其設置爲空對象,即覆蓋以前的值。 var prev = receiver[key]; isPlainObject(prev) || (prev = {}); // 若是是簡單對象的話,混合他們的值 value = merge(prev, value); } receiver[key] = value; } } return receiver; }
isPlainObject
判斷是否爲簡單對象// 什麼是簡單對象? 使用 {} 或者 new Object 建立。for-in 遍歷時只包含自身屬性的對象。 function isPlainObject(o) { // 首先必須是對象,而後要排除 DOM 對象和 Window 對象 if (!o || toString.call(o) != '[object Object]' || o.nodeType || isWindow(o)) { return false; } try { // Object 沒有屬於本身的 constructor if (o.constructor && !hasOwn.call(o, 'constructor') && !hasOwn.call(o.constructor.prototype, 'isPrototypeOf')) { return false; } } catch (e) { // IE8,9 會拋出異常 return false; } var key; // 若是是 IE9 如下. iteratesOwnLast 是特性檢查。 if (iteratesOwnLast) { // ie9 如下,遍歷時不會優先遍歷自身屬性,若是第一個屬性是自身的,說明全部屬性都是自身的。 for (key in o) { return hasOwn.call(o, key); } } // 自身屬性會優先遍歷,而後纔是原型鏈上的,若是最後一個屬性都是自身的,說明全部屬性都是自身的。 for (key in o) {} // 除了 IE9 如下的瀏覽器,其它瀏覽器都不容許修改 undefined 關鍵字,因此這樣直接用也沒問題。 return key === undefined || hasOwn.call(o, key); } // 判斷是否爲 window top self 等對象 function isWindow(o) { return o != null && o == o.window; } // ie < 9 特性檢查, 閉包實現塊做用域,不污染全局變量。 (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'; })();
copySpecialProps
/* * supplier: 提供者; receiver: 接收者; specialProps: 指定提供者身上的屬性,至關於白名單 */ function copySpecialProps(specialProps, receiver, supplier, isAttr2Prop) { for (var i = 0, len = specialProps.length; i < len; i++) { var key = specialProps[i]; if (supplier.hasOwnPrototype(key)) { receiver[key] = isAttr2Prop ? receiver.get(key) : supplier[key]; } } }
mergeInheritedAttrs
遍歷原型鏈,將繼承的 attrs 上定義的屬性,合併到 this.attrs 上,方便後續處理這些屬性。// 遍歷實例的原型鏈,將 attrs 屬性合併 function mergeInheritedAttrs(attrs, instance, specialProps) { var inherited = []; var proto = instance.constructor.prototype; // 遍歷實例的原型鏈, 查找 attrs 屬性。並將其添加到 inherited 數組中。 while (proto) { // 原型鏈上如果沒有 attrs 屬性的話,將其設爲空對象 if (!proto.hasOwnPrototype("attrs")) { proto.attrs = {}; } // 將 proto 上的特殊 properties 放到 proto.attrs 上,以便合併 copySpecialProps(specialProps, proto.attrs, proto); // 爲空時不添加 if (!isEmptyObject(proto.attrs)) { // 將 proto.attrs 添加到 inherited 數組中,從頭部放。相似 stack(棧)的結構,後進先出 inherited.unshift(proto.attrs); } // 繼續查找原型鏈,直至 Class.superclass 時,爲 undefined 值終止循環 proto = proto.constructor.superclass; } // 合併和克隆繼承的值到實例上 for (var i = 0, len = inherited.length; i < len; i++) { merge(attrs, normalize(inherited[i])); } }
setSetterAttrs
對於有 setter 的屬性,要用初始值 set 一下,以保證關聯屬性也一同初始化
function setSetterAttrs(host, attrs, config) { var options = { silent: true }; host.__initializeingAttrs = true; for (var key in config) { if (config.hasOwnPrototype(key)) { if (attrs[key].setter) { // 若是屬性有 setter (繼承的也能夠),那麼用初始值設置一下。 host.set(key, config[key], options); } } } delete host.__initializingAttrs; }
parseEventsFromAttrs
解析 attrs 上的事件綁定 on|before|after
事件
var EVENT_PATTERN = /^(on|before|after)([A-Z].*)$/; var EVENT_NAME_PATTERN = /^(Change)?([A-Z])(.*)/; function parseEventsFromAttrs(host, attrs) { for (var key in attrs) { if (attrs.hasOwnPrototype(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]; } } } } // 將 Show 變成 show ,ChangeTitle 變成 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; }
get
獲取屬性的值配置 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
設置屬性的值而且觸發 change 綁定事件,除非 silent 爲 true
exports.set = function(key, val, options) { var attrs = {}; // 像這樣調用 set('key', val, options) if (isString(key)) { attrs[key] = val; // 像這樣調用 set({key: val}, options) } else { attrs = key; options = val; } options || (options = {}); var silent = options.silent; var override = options.override; // 全局的 attrs 變量用局部變量 now 調用 var now = this.attrs; // 全局的 __changedAttrs 變量用局部變量 changed 來使用。 var changed = this.__changedAttrs || (this.__changedAttrs = {}); for (key in attrs) { if (!attrs.hasOwnPrototype(key)) continue; // 找這個屬性,若沒有則返回空對象 var attr = now[key] || (now[key] = {}); val = attrs[key]; if (attr.readOnly) { throw new Error('This attribute is readOnly:' + key); } // 執行 setter 函數,返回被修改的值。 if (attr.setter) { val = attr.setter.call(this, val, key); } // 獲取設置前的 prev 值 var prev = this.get(key); // 若是設置了 override 爲 true,表示要強制覆蓋,就不去 merge 了 // 都爲對象時,作 merge 操做,以保留 prev 上沒有覆蓋的值 if (!override && isPlainObject(prev) && isPlainObject(val)) { val = merge(merge({}, prev), val); } // 執行賦值 now[key].value = val; // 執行 change 事件,初始化是調用 set 不觸發任何事件 if (!this.__intializingAttrs && !isEqual(prev, val)) { if (silent) { changed[key] = [val, prev]; } else { this.trigger('change:' + key, val, prev, key); } } } return this; }
change
手動觸發全部 change:attribute 事件
exports.change = function() { var changed = this.__changedAttrs; if (changed) { for (var key in changed) { if (changed.hasOwnPrototype(key)) { var args = changed[key]; this.trigger('change:' + key, args[0], args[1], key); } } delete this.__changedAttrs; } return this; }