/** * @ignore * lang * @author yiminghe@gmail.com, lifesinger@gmail.com * */ (function (S, undefined) { var TRUE = true, FALSE = false, CLONE_MARKER = '__~ks_cloned', COMPARE_MARKER = '__~ks_compared'; S.mix(S, { /** * Checks to see whether two object are equals. * @param a 比较目标1 * @param b 比较目标2 * @param [mismatchKeys] internal usage * @param [mismatchValues] internal usage * @return {Boolean} a.equals(b) * @member KISSY */ equals: function (a, b, /*internal use*/mismatchKeys, /*internal use*/mismatchValues) { // inspired by jasmine mismatchKeys = mismatchKeys || []; mismatchValues = mismatchValues || []; if (a === b) { return TRUE; } if (a === undefined || a === null || b === undefined || b === null) { // need type coercion return a == null && b == null; } if (a instanceof Date && b instanceof Date) { return a.getTime() == b.getTime(); } if (typeof a == 'string' && typeof b == 'string') { return (a == b); } if (typeof a==='number' && typeof b==='number') { return (a == b); } if (typeof a === 'object' && typeof b === 'object') { return compareObjects(a, b, mismatchKeys, mismatchValues); } // Straight check return (a === b); }, /** * Creates a deep copy of a plain object or array. Others are returned untouched. * @param input * @member KISSY * @param {Function} [filter] filter function * @return {Object} the new cloned object * refer: http://www.w3.org/TR/html5/common-dom-interfaces.html#safe-passing-of-structured-data */ clone: function (input, filter) { // 稍微改改就和规范一样了 :) // Let memory be an association list of pairs of objects, // initially empty. This is used to handle duplicate references. // In each pair of objects, one is called the source object // and the other the destination object. var memory = {}, ret = cloneInternal(input, filter, memory); S.each(memory, function (v) { // 清理在源对象上做的标记 v = v.input; if (v[CLONE_MARKER]) { try { delete v[CLONE_MARKER]; } catch (e) { v[CLONE_MARKER] = undefined; } } }); memory = null; return ret; }, /** * Gets current date in milliseconds. * @method * refer: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Date/now * http://j-query.blogspot.com/2011/02/timing-ecmascript-5-datenow-function.html * http://kangax.github.com/es5-compat-table/ * @member KISSY * @return {Number} current time */ now: Date.now || function () { return +new Date(); } }); function cloneInternal(input, f, memory) { var destination = input, isArray, isPlainObject, k, stamp; if (!input) { return destination; } // If input is the source object of a pair of objects in memory, // then return the destination object in that pair of objects . // and abort these steps. if (input[CLONE_MARKER]) { // 对应的克隆后对象 return memory[input[CLONE_MARKER]].destination; } else if (typeof input === 'object') { // 引用类型要先记录 var constructor = input.constructor; if (S.inArray(constructor, [Boolean, String, Number, Date, RegExp])) { destination = new constructor(input.valueOf()); } // ImageData , File, Blob , FileList .. etc else if (isArray = S.isArray(input)) { destination = f ? S.filter(input, f) : input.concat(); } else if (isPlainObject = S.isPlainObject(input)) { destination = {}; } // Add a mapping from input (the source object) // to output (the destination object) to memory. // 做标记 input[CLONE_MARKER] = (stamp = S.guid()); // 存储源对象以及克隆后的对象 memory[stamp] = {destination: destination, input: input}; } // If input is an Array object or an Object object, // then, for each enumerable property in input, // add a new property to output having the same name, // and having a value created from invoking the internal structured cloning algorithm recursively // with the value of the property as the 'input' argument and memory as the 'memory' argument. // The order of the properties in the input and output objects must be the same. // clone it if (isArray) { for (var i = 0; i < destination.length; i++) { destination[i] = cloneInternal(destination[i], f, memory); } } else if (isPlainObject) { for (k in input) { if (k !== CLONE_MARKER && (!f || (f.call(input, input[k], k, input) !== FALSE))) { destination[k] = cloneInternal(input[k], f, memory); } } } return destination; } function compareObjects(a, b, mismatchKeys, mismatchValues) { // 两个比较过了,无需再比较,防止循环比较 if (a[COMPARE_MARKER] === b && b[COMPARE_MARKER] === a) { return TRUE; } a[COMPARE_MARKER] = b; b[COMPARE_MARKER] = a; var hasKey = function (obj, keyName) { return (obj !== null && obj !== undefined) && obj[keyName] !== undefined; }; for (var property in b) { if (!hasKey(a, property) && hasKey(b, property)) { mismatchKeys.push("expected has key '" + property + "', but missing from actual."); } } for (property in a) { if (!hasKey(b, property) && hasKey(a, property)) { mismatchKeys.push("expected missing key '" + property + "', but present in actual."); } } for (property in b) { if (property == COMPARE_MARKER) { continue; } if (!S.equals(a[property], b[property], mismatchKeys, mismatchValues)) { mismatchValues.push("'" + property + "' was '" + (b[property] ? (b[property].toString()) : b[property]) + "' in expected, but was '" + (a[property] ? (a[property].toString()) : a[property]) + "' in actual."); } } if (S.isArray(a) && S.isArray(b) && a.length != b.length) { mismatchValues.push('arrays were not the same length'); } delete a[COMPARE_MARKER]; delete b[COMPARE_MARKER]; return (mismatchKeys.length === 0 && mismatchValues.length === 0); } })(KISSY);