1 /**
  2  * @fileOverview   lang
  3  * @author  lifesinger@gmail.com, yiminghe@gmail.com
  4  * @description this code can run in any ecmascript compliant environment
  5  */
  6 (function (S, undefined) {
  7 
  8     function hasOwnProperty(o, p) {
  9         return Object.prototype.hasOwnProperty.call(o, p);
 10     }
 11 
 12     var TRUE = true,
 13         FALSE = false,
 14         OP = Object.prototype,
 15         toString = OP.toString,
 16         AP = Array.prototype,
 17         indexOf = AP.indexOf,
 18         lastIndexOf = AP.lastIndexOf,
 19         filter = AP.filter,
 20         every = AP.every,
 21         some = AP.some,
 22     //reduce = AP.reduce,
 23         trim = String.prototype.trim,
 24         map = AP.map,
 25         EMPTY = '',
 26         HEX_BASE = 16,
 27         CLONE_MARKER = '__~ks_cloned',
 28         COMPARE_MARKER = '__~ks_compared',
 29         STAMP_MARKER = '__~ks_stamped',
 30     // IE doesn't include non-breaking-space (0xa0) in their \s character
 31     // class (as required by section 7.2 of the ECMAScript spec), we explicitly
 32     // include it in the regexp to enforce consistent cross-browser behavior.
 33         RE_TRIM = /^[\s\xa0]+|[\s\xa0]+$/g,
 34         encode = encodeURIComponent,
 35         decode = decodeURIComponent,
 36         SEP = '&',
 37         EQ = '=',
 38     // [[Class]] -> type pairs
 39         class2type = {},
 40     // http://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet
 41         htmlEntities = {
 42             '&':'&',
 43             '>':'>',
 44             '<':'<',
 45             '`':'`',
 46             '/':'/',
 47             '"':'"',
 48             ''':"'"
 49         },
 50         reverseEntities = {},
 51         escapeReg,
 52         unEscapeReg,
 53     // - # $ ^ * ( ) + [ ] { } | \ , . ?
 54         escapeRegExp = /[\-#$\^*()+\[\]{}|\\,.?\s]/g;
 55     (function () {
 56         for (var k in htmlEntities) {
 57             if (hasOwnProperty(htmlEntities, k)) {
 58                 reverseEntities[htmlEntities[k]] = k;
 59             }
 60         }
 61     })();
 62 
 63     function getEscapeReg() {
 64         if (escapeReg) {
 65             return escapeReg
 66         }
 67         var str = EMPTY;
 68         S.each(htmlEntities, function (entity) {
 69             str += entity + '|';
 70         });
 71         str = str.slice(0, -1);
 72         return escapeReg = new RegExp(str, "g");
 73     }
 74 
 75     function getUnEscapeReg() {
 76         if (unEscapeReg) {
 77             return unEscapeReg
 78         }
 79         var str = EMPTY;
 80         S.each(reverseEntities, function (entity) {
 81             str += entity + '|';
 82         });
 83         str += '&#(\\d{1,5});';
 84         return unEscapeReg = new RegExp(str, "g");
 85     }
 86 
 87 
 88     function isValidParamValue(val) {
 89         var t = typeof val;
 90         // If the type of val is null, undefined, number, string, boolean, return true.
 91         return val == null || (t !== 'object' && t !== 'function');
 92     }
 93 
 94     S.mix(S,
 95         /**
 96          * @lends KISSY
 97          */
 98         {
 99 
100             /**
101              * stamp a object by guid
102              * @param {Object} o object needed to be stamped
103              * @param {Boolean} [readOnly] while set marker on o if marker does not exist
104              * @param {String} [marker] the marker will be set on Object
105              * @return guid associated with this object
106              */
107             stamp:function (o, readOnly, marker) {
108                 if (!o) {
109                     return o
110                 }
111                 marker = marker || STAMP_MARKER;
112                 var guid = o[marker];
113                 if (guid) {
114                     return guid;
115                 } else if (!readOnly) {
116                     try {
117                         guid = o[marker] = S.guid(marker);
118                     }
119                     catch (e) {
120                         guid = undefined;
121                     }
122                 }
123                 return guid;
124             },
125 
126             /**
127              * empty function
128              */
129             noop:function () {
130             },
131 
132             /**
133              * Determine the internal JavaScript [[Class]] of an object.
134              */
135             type:function (o) {
136                 return o == null ?
137                     String(o) :
138                     class2type[toString.call(o)] || 'object';
139             },
140 
141             /**
142              * whether o === null
143              * @param o
144              */
145             isNull:function (o) {
146                 return o === null;
147             },
148 
149             /**
150              * whether o === undefined
151              * @param o
152              */
153             isUndefined:function (o) {
154                 return o === undefined;
155             },
156 
157             /**
158              * Checks to see if an object is empty.
159              */
160             isEmptyObject:function (o) {
161                 for (var p in o) {
162                     if (p !== undefined) {
163                         return FALSE;
164                     }
165                 }
166                 return TRUE;
167             },
168 
169             /**
170              * Checks to see if an object is a plain object (created using "{}"
171              * or "new Object()" or "new FunctionClass()").
172              */
173             isPlainObject:function (o) {
174                 /**
175                  * note by yiminghe
176                  * isPlainObject(node=document.getElementById("xx")) -> false
177                  * toString.call(node) : ie678 == '[object Object]',other =='[object HTMLElement]'
178                  * 'isPrototypeOf' in node : ie678 === false ,other === true
179                  * refer http://lifesinger.org/blog/2010/12/thinking-of-isplainobject/
180                  */
181                 return o && toString.call(o) === '[object Object]' && 'isPrototypeOf' in o;
182             },
183 
184 
185             /**
186              * Checks to see whether two object are equals.
187              * @param a 比较目标1
188              * @param b 比较目标2
189              * @returns {Boolean} a.equals(b)
190              */
191             equals:function (a, b, /*internal use*/mismatchKeys, /*internal use*/mismatchValues) {
192                 // inspired by jasmine
193                 mismatchKeys = mismatchKeys || [];
194                 mismatchValues = mismatchValues || [];
195 
196                 if (a === b) {
197                     return TRUE;
198                 }
199                 if (a === undefined || a === null || b === undefined || b === null) {
200                     // need type coercion
201                     return a == null && b == null;
202                 }
203                 if (a instanceof Date && b instanceof Date) {
204                     return a.getTime() == b.getTime();
205                 }
206                 if (S.isString(a) && S.isString(b)) {
207                     return (a == b);
208                 }
209                 if (S.isNumber(a) && S.isNumber(b)) {
210                     return (a == b);
211                 }
212                 if (typeof a === "object" && typeof b === "object") {
213                     return compareObjects(a, b, mismatchKeys, mismatchValues);
214                 }
215                 // Straight check
216                 return (a === b);
217             },
218 
219             /**
220              * Creates a deep copy of a plain object or array. Others are returned untouched.
221              * @param input
222              * @param {Function} [filter] filter function
223              * @returns the new cloned object
224              * @see http://www.w3.org/TR/html5/common-dom-interfaces.html#safe-passing-of-structured-data
225              */
226             clone:function (input, filter) {
227                 // 稍微改改就和规范一样了 :)
228                 // Let memory be an association list of pairs of objects,
229                 // initially empty. This is used to handle duplicate references.
230                 // In each pair of objects, one is called the source object
231                 // and the other the destination object.
232                 var memory = {},
233                     ret = cloneInternal(input, filter, memory);
234                 S.each(memory, function (v) {
235                     // 清理在源对象上做的标记
236                     v = v.input;
237                     if (v[CLONE_MARKER]) {
238                         try {
239                             delete v[CLONE_MARKER];
240                         } catch (e) {
241                             S.log("delete CLONE_MARKER error : ");
242                             v[CLONE_MARKER] = undefined;
243                         }
244                     }
245                 });
246                 memory = null;
247                 return ret;
248             },
249 
250             /**
251              * Removes the whitespace from the beginning and end of a string.
252              * @function
253              */
254             trim:trim ?
255                 function (str) {
256                     return str == null ? EMPTY : trim.call(str);
257                 } :
258                 function (str) {
259                     return str == null ? EMPTY : str.toString().replace(RE_TRIM, EMPTY);
260                 },
261 
262             /**
263              * Substitutes keywords in a string using an object/array.
264              * Removes undefined keywords and ignores escaped keywords.
265              * @param {String} str template string
266              * @param {Object} o json data
267              * @param {RegExp} [regexp] to match a piece of template string
268              */
269             substitute:function (str, o, regexp) {
270                 if (!S.isString(str)
271                     || !S.isPlainObject(o)) {
272                     return str;
273                 }
274 
275                 return str.replace(regexp || /\\?\{([^{}]+)\}/g, function (match, name) {
276                     if (match.charAt(0) === '\\') {
277                         return match.slice(1);
278                     }
279                     return (o[name] === undefined) ? EMPTY : o[name];
280                 });
281             },
282 
283             /**
284              * Executes the supplied function on each item in the array.
285              * @param object {Object} the object to iterate
286              * @param fn {Function} the function to execute on each item. The function
287              *        receives three arguments: the value, the index, the full array.
288              * @param {Object} [context]
289              */
290             each:function (object, fn, context) {
291                 if (object) {
292                     var key,
293                         val,
294                         i = 0,
295                         length = object && object.length,
296                         isObj = length === undefined || S.type(object) === 'function';
297 
298                     context = context || null;
299 
300                     if (isObj) {
301                         for (key in object) {
302                             // can not use hasOwnProperty
303                             if (fn.call(context, object[key], key, object) === FALSE) {
304                                 break;
305                             }
306                         }
307                     } else {
308                         for (val = object[0];
309                              i < length && fn.call(context, val, i, object) !== FALSE; val = object[++i]) {
310                         }
311                     }
312                 }
313                 return object;
314             },
315 
316             /**
317              * Search for a specified value within an array.
318              * @param item individual item to be searched
319              * @function
320              * @param {Array} arr the array of items where item will be search
321              * @returns {number} item's index in array
322              */
323             indexOf:indexOf ?
324                 function (item, arr) {
325                     return indexOf.call(arr, item);
326                 } :
327                 function (item, arr) {
328                     for (var i = 0, len = arr.length; i < len; ++i) {
329                         if (arr[i] === item) {
330                             return i;
331                         }
332                     }
333                     return -1;
334                 },
335 
336             /**
337              * Returns the index of the last item in the array
338              * that contains the specified value, -1 if the
339              * value isn't found.
340              * @function
341              * @param item individual item to be searched
342              * @param {Array} arr the array of items where item will be search
343              * @returns {number} item's last index in array
344              */
345             lastIndexOf:(lastIndexOf) ?
346                 function (item, arr) {
347                     return lastIndexOf.call(arr, item);
348                 } :
349                 function (item, arr) {
350                     for (var i = arr.length - 1; i >= 0; i--) {
351                         if (arr[i] === item) {
352                             break;
353                         }
354                     }
355                     return i;
356                 },
357 
358             /**
359              * Returns a copy of the array with the duplicate entries removed
360              * @param a {Array} the array to find the subset of unique for
361              * @param [override] {Boolean}
362              *        if override is true, S.unique([a, b, a]) => [b, a]
363              *        if override is false, S.unique([a, b, a]) => [a, b]
364              * @return {Array} a copy of the array with duplicate entries removed
365              */
366             unique:function (a, override) {
367                 var b = a.slice();
368                 if (override) {
369                     b.reverse();
370                 }
371                 var i = 0,
372                     n,
373                     item;
374 
375                 while (i < b.length) {
376                     item = b[i];
377                     while ((n = S.lastIndexOf(item, b)) !== i) {
378                         b.splice(n, 1);
379                     }
380                     i += 1;
381                 }
382 
383                 if (override) {
384                     b.reverse();
385                 }
386                 return b;
387             },
388 
389             /**
390              * Search for a specified value index within an array.
391              * @param item individual item to be searched
392              * @param {Array} arr the array of items where item will be search
393              * @returns {Boolean} the item exists in arr
394              */
395             inArray:function (item, arr) {
396                 return S.indexOf(item, arr) > -1;
397             },
398 
399             /**
400              * Executes the supplied function on each item in the array.
401              * Returns a new array containing the items that the supplied
402              * function returned true for.
403              * @function
404              * @param arr {Array} the array to iterate
405              * @param fn {Function} the function to execute on each item
406              * @param [context] {Object} optional context object
407              * @return {Array} The items on which the supplied function
408              *         returned true. If no items matched an empty array is
409              *         returned.
410              */
411             filter:filter ?
412                 function (arr, fn, context) {
413                     return filter.call(arr, fn, context || this);
414                 } :
415                 function (arr, fn, context) {
416                     var ret = [];
417                     S.each(arr, function (item, i, arr) {
418                         if (fn.call(context || this, item, i, arr)) {
419                             ret.push(item);
420                         }
421                     });
422                     return ret;
423                 },
424 
425 
426             /**
427              * Executes the supplied function on each item in the array.
428              * Returns a new array containing the items that the supplied
429              * function returned for.
430              * @function
431              * @param arr {Array} the array to iterate
432              * @param fn {Function} the function to execute on each item
433              * @param [context] {Object} optional context object
434              * @see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/map
435              * @return {Array} The items on which the supplied function
436              *         returned
437              */
438             map:map ?
439                 function (arr, fn, context) {
440                     return map.call(arr, fn, context || this);
441                 } :
442                 function (arr, fn, context) {
443                     var len = arr.length,
444                         res = new Array(len);
445                     for (var i = 0; i < len; i++) {
446                         var el = S.isString(arr) ? arr.charAt(i) : arr[i];
447                         if (el
448                             ||
449                             //ie<9 in invalid when typeof arr == string
450                             i in arr) {
451                             res[i] = fn.call(context || this, el, i, arr);
452                         }
453                     }
454                     return res;
455                 },
456 
457 
458             /**
459              * Executes the supplied function on each item in the array.
460              * Returns a value which is accumulation of the value that the supplied
461              * function returned.
462              *
463              * @param arr {Array} the array to iterate
464              * @param callback {Function} the function to execute on each item
465              * @param initialValue {number} optional context object
466              * @see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/array/reduce
467              * @return {Array} The items on which the supplied function returned
468              */
469             reduce:/*
470              NaN ?
471              reduce ? function(arr, callback, initialValue) {
472              return arr.reduce(callback, initialValue);
473              } : */function (arr, callback, initialValue) {
474                 var len = arr.length;
475                 if (typeof callback !== "function") {
476                     throw new TypeError("callback is not function!");
477                 }
478 
479                 // no value to return if no initial value and an empty array
480                 if (len === 0 && arguments.length == 2) {
481                     throw new TypeError("arguments invalid");
482                 }
483 
484                 var k = 0;
485                 var accumulator;
486                 if (arguments.length >= 3) {
487                     accumulator = arguments[2];
488                 }
489                 else {
490                     do {
491                         if (k in arr) {
492                             accumulator = arr[k++];
493                             break;
494                         }
495 
496                         // if array contains no values, no initial value to return
497                         k += 1;
498                         if (k >= len) {
499                             throw new TypeError();
500                         }
501                     }
502                     while (TRUE);
503                 }
504 
505                 while (k < len) {
506                     if (k in arr) {
507                         accumulator = callback.call(undefined, accumulator, arr[k], k, arr);
508                     }
509                     k++;
510                 }
511 
512                 return accumulator;
513             },
514 
515             /**
516              * Tests whether all elements in the array pass the test implemented by the provided function.
517              * @function
518              * @param arr {Array} the array to iterate
519              * @param callback {Function} the function to execute on each item
520              * @param [context] {Object} optional context object
521              * @returns {Boolean} whether all elements in the array pass the test implemented by the provided function.
522              */
523             every:every ?
524                 function (arr, fn, context) {
525                     return every.call(arr, fn, context || this);
526                 } :
527                 function (arr, fn, context) {
528                     var len = arr && arr.length || 0;
529                     for (var i = 0; i < len; i++) {
530                         if (i in arr && !fn.call(context, arr[i], i, arr)) {
531                             return FALSE;
532                         }
533                     }
534                     return TRUE;
535                 },
536 
537             /**
538              * Tests whether some element in the array passes the test implemented by the provided function.
539              * @function
540              * @param arr {Array} the array to iterate
541              * @param callback {Function} the function to execute on each item
542              * @param [context] {Object} optional context object
543              * @returns {Boolean} whether some element in the array passes the test implemented by the provided function.
544              */
545             some:some ?
546                 function (arr, fn, context) {
547                     return some.call(arr, fn, context || this);
548                 } :
549                 function (arr, fn, context) {
550                     var len = arr && arr.length || 0;
551                     for (var i = 0; i < len; i++) {
552                         if (i in arr && fn.call(context, arr[i], i, arr)) {
553                             return TRUE;
554                         }
555                     }
556                     return FALSE;
557                 },
558 
559             /**
560              * Creates a new function that, when called, itself calls this function in the context of the provided this value,
561              * with a given sequence of arguments preceding any provided when the new function was called.
562              * @see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind
563              * @param {Function} fn internal called function
564              * @param {Object} obj context in which fn runs
565              * @param {...} arg1 extra arguments
566              * @returns {Function} new function with context and arguments
567              */
568             bind:function (fn, obj, arg1) {
569                 var slice = [].slice,
570                     args = slice.call(arguments, 2),
571                     fNOP = function () {
572                     },
573                     bound = function () {
574                         return fn.apply(this instanceof fNOP ? this : obj,
575                             args.concat(slice.call(arguments)));
576                     };
577                 fNOP.prototype = fn.prototype;
578                 bound.prototype = new fNOP();
579                 return bound;
580             },
581 
582             /**
583              * Gets current date in milliseconds.
584              * @function
585              * @see  https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Date/now
586              * http://j-query.blogspot.com/2011/02/timing-ecmascript-5-datenow-function.html
587              * http://kangax.github.com/es5-compat-table/
588              */
589             now:Date.now || function () {
590                 return +new Date();
591             },
592             /**
593              * frequently used in taobao cookie about nick
594              */
595             fromUnicode:function (str) {
596                 return str.replace(/\\u([a-f\d]{4})/ig, function (m, u) {
597                     return  String.fromCharCode(parseInt(u, HEX_BASE));
598                 });
599             },
600 
601 
602             ucfirst:function (s) {
603                 s += "";
604                 return s.charAt(0).toUpperCase() + s.substring(1);
605             },
606 
607             /**
608              * get escaped string from html
609              * @see   http://yiminghe.javaeye.com/blog/788929
610              *        http://wonko.com/post/html-escaping
611              * @param str {string} text2html show
612              */
613             escapeHTML:function (str) {
614                 return str.replace(getEscapeReg(), function (m) {
615                     return reverseEntities[m];
616                 });
617             },
618 
619             /**
620              * get escaped regexp string for construct regexp
621              * @param str
622              */
623             escapeRegExp:function (str) {
624                 return str.replace(escapeRegExp, '\\$&');
625             },
626 
627             /**
628              * un-escape html to string
629              * @param str {string} html2text
630              */
631             unEscapeHTML:function (str) {
632                 return str.replace(getUnEscapeReg(), function (m, n) {
633                     return htmlEntities[m] || String.fromCharCode(+n);
634                 });
635             },
636             /**
637              * Converts object to a true array.
638              * @param o {object|Array} array like object or array
639              * @return {Array} native Array
640              */
641             makeArray:function (o) {
642                 if (o == null) {
643                     return [];
644                 }
645                 if (S.isArray(o)) {
646                     return o;
647                 }
648 
649                 // The strings and functions also have 'length'
650                 if (typeof o.length !== 'number'
651                     // form.elements in ie78 has nodeName "form"
652                     // then caution select
653                     // || o.nodeName
654                     // window
655                     || o.alert
656                     || S.isString(o)
657                     || S.isFunction(o)) {
658                     return [o];
659                 }
660                 var ret = [];
661                 for (var i = 0, l = o.length; i < l; i++) {
662                     ret[i] = o[i];
663                 }
664                 return ret;
665             },
666             /**
667              * Creates a serialized string of an array or object.
668              * @example
669              * <code>
670              * {foo: 1, bar: 2}    // -> 'foo=1&bar=2'
671              * {foo: 1, bar: [2, 3]}    // -> 'foo=1&bar=2&bar=3'
672              * {foo: '', bar: 2}    // -> 'foo=&bar=2'
673              * {foo: undefined, bar: 2}    // -> 'foo=undefined&bar=2'
674              * {foo: true, bar: 2}    // -> 'foo=true&bar=2'
675              * </code>
676              * @param {Object} o json data
677              * @param {String} [sep='&'] separator between each pair of data
678              * @param {String} [eq='='] separator between key and value of data
679              * @param {Boolean} [arr=true] whether add '[]' to array key of data
680              * @return {String}
681              */
682             param:function (o, sep, eq, arr) {
683                 if (!S.isPlainObject(o)) {
684                     return EMPTY;
685                 }
686                 sep = sep || SEP;
687                 eq = eq || EQ;
688                 if (S.isUndefined(arr)) {
689                     arr = TRUE;
690                 }
691                 var buf = [], key, val;
692                 for (key in o) {
693                     if (hasOwnProperty(o, key)) {
694                         val = o[key];
695                         key = encode(key);
696 
697                         // val is valid non-array value
698                         if (isValidParamValue(val)) {
699                             buf.push(key, eq, encode(val + EMPTY), sep);
700                         }
701                         // val is not empty array
702                         else if (S.isArray(val) && val.length) {
703                             for (var i = 0, len = val.length; i < len; ++i) {
704                                 if (isValidParamValue(val[i])) {
705                                     buf.push(key,
706                                         (arr ? encode("[]") : EMPTY),
707                                         eq, encode(val[i] + EMPTY), sep);
708                                 }
709                             }
710                         }
711                         // ignore other cases, including empty array, Function, RegExp, Date etc.
712                     }
713                 }
714                 buf.pop();
715                 return buf.join(EMPTY);
716             },
717 
718             /**
719              * Parses a URI-like query string and returns an object composed of parameter/value pairs.
720              * @example
721              * <code>
722              * 'section=blog&id=45'        // -> {section: 'blog', id: '45'}
723              * 'section=blog&tag=js&tag=doc' // -> {section: 'blog', tag: ['js', 'doc']}
724              * 'tag=ruby%20on%20rails'        // -> {tag: 'ruby on rails'}
725              * 'id=45&raw'        // -> {id: '45', raw: ''}
726              * </code>
727              * @param {String} str param string
728              * @param {String} [sep='&'] separator between each pair of data
729              * @param {String} [eq='='] separator between key and value of data
730              * @returns {Object} json data
731              */
732             unparam:function (str, sep, eq) {
733                 if (!S.isString(str) || !(str = S.trim(str))) {
734                     return {};
735                 }
736                 sep = sep || SEP;
737                 eq = eq || EQ;
738                 var ret = {},
739                     pairs = str.split(sep),
740                     pair, key, val,
741                     i = 0, len = pairs.length;
742 
743                 for (; i < len; ++i) {
744                     pair = pairs[i].split(eq);
745                     key = decode(pair[0]);
746                     try {
747                         val = decode(pair[1] || EMPTY);
748                     } catch (e) {
749                         S.log(e + "decodeURIComponent error : " + pair[1], "error");
750                         val = pair[1] || EMPTY;
751                     }
752                     if (S.endsWith(key, "[]")) {
753                         key = key.substring(0, key.length - 2);
754                     }
755                     if (hasOwnProperty(ret, key)) {
756                         if (S.isArray(ret[key])) {
757                             ret[key].push(val);
758                         } else {
759                             ret[key] = [ret[key], val];
760                         }
761                     } else {
762                         ret[key] = val;
763                     }
764                 }
765                 return ret;
766             },
767             /**
768              * Executes the supplied function in the context of the supplied
769              * object 'when' milliseconds later. Executes the function a
770              * single time unless periodic is set to true.
771              * @param fn {Function|String} the function to execute or the name of the method in
772              *        the 'o' object to execute.
773              * @param when {Number} the number of milliseconds to wait until the fn is executed.
774              * @param {Boolean} [periodic] if true, executes continuously at supplied interval
775              *        until canceled.
776              * @param {Object} [context] the context object.
777              * @param [data] that is provided to the function. This accepts either a single
778              *        item or an array. If an array is provided, the function is executed with
779              *        one parameter for each array item. If you need to pass a single array
780              *        parameter, it needs to be wrapped in an array [myarray].
781              * @return {Object} a timer object. Call the cancel() method on this object to stop
782              *         the timer.
783              */
784             later:function (fn, when, periodic, context, data) {
785                 when = when || 0;
786                 var m = fn,
787                     d = S.makeArray(data),
788                     f,
789                     r;
790 
791                 if (S.isString(fn)) {
792                     m = context[fn];
793                 }
794 
795                 if (!m) {
796                     S.error('method undefined');
797                 }
798 
799                 f = function () {
800                     m.apply(context, d);
801                 };
802 
803                 r = (periodic) ? setInterval(f, when) : setTimeout(f, when);
804 
805                 return {
806                     id:r,
807                     interval:periodic,
808                     cancel:function () {
809                         if (this.interval) {
810                             clearInterval(r);
811                         } else {
812                             clearTimeout(r);
813                         }
814                     }
815                 };
816             },
817 
818             /**
819              * test whether a string start with a specified substring
820              * @param {String} str the whole string
821              * @param {String} prefix a specified substring
822              * @returns {Boolean} whether str start with prefix
823              */
824             startsWith:function (str, prefix) {
825                 return str.lastIndexOf(prefix, 0) === 0;
826             },
827 
828             /**
829              * test whether a string end with a specified substring
830              * @param {String} str the whole string
831              * @param {String} suffix a specified substring
832              * @returns {Boolean} whether str end with suffix
833              */
834             endsWith:function (str, suffix) {
835                 var ind = str.length - suffix.length;
836                 return ind >= 0 && str.indexOf(suffix, ind) == ind;
837             },
838 
839             /**
840              * Throttles a call to a method based on the time between calls.
841              * @param {function} fn The function call to throttle.
842              * @param {object} [context] context fn to run
843              * @param {Number} [ms] The number of milliseconds to throttle the method call.
844              *              Passing a -1 will disable the throttle. Defaults to 150.
845              * @return {function} Returns a wrapped function that calls fn throttled.
846              */
847             throttle:function (fn, ms, context) {
848                 ms = ms || 150;
849 
850                 if (ms === -1) {
851                     return (function () {
852                         fn.apply(context || this, arguments);
853                     });
854                 }
855 
856                 var last = S.now();
857 
858                 return (function () {
859                     var now = S.now();
860                     if (now - last > ms) {
861                         last = now;
862                         fn.apply(context || this, arguments);
863                     }
864                 });
865             },
866 
867             /**
868              * buffers a call between a fixed time
869              * @param {function} fn
870              * @param {object} [context]
871              * @param {Number} ms
872              * @return {function} Returns a wrapped function that calls fn buffered.
873              */
874             buffer:function (fn, ms, context) {
875                 ms = ms || 150;
876 
877                 if (ms === -1) {
878                     return function () {
879                         fn.apply(context || this, arguments);
880                     };
881                 }
882                 var bufferTimer = null;
883 
884                 function f() {
885                     f.stop();
886                     bufferTimer = S.later(fn, ms, FALSE, context || this, arguments);
887                 }
888 
889                 f.stop = function () {
890                     if (bufferTimer) {
891                         bufferTimer.cancel();
892                         bufferTimer = 0;
893                     }
894                 };
895 
896                 return f;
897             }
898 
899         });
900 
901     // for idea ..... auto-hint
902     S.mix(S,
903         /**
904          * @lends KISSY
905          */
906         {
907             /**
908              * test whether o is boolean
909              * @function
910              * @param  o
911              * @returns {Boolean}
912              */
913             isBoolean:isValidParamValue,
914             /**
915              * test whether o is number
916              * @function
917              * @param  o
918              * @returns {Boolean}
919              */
920             isNumber:isValidParamValue,
921             /**
922              * test whether o is String
923              * @function
924              * @param  o
925              * @returns {Boolean}
926              */
927             isString:isValidParamValue,
928             /**
929              * test whether o is function
930              * @function
931              * @param  o
932              * @returns {Boolean}
933              */
934             isFunction:isValidParamValue,
935             /**
936              * test whether o is Array
937              * @function
938              * @param  o
939              * @returns {Boolean}
940              */
941             isArray:isValidParamValue,
942             /**
943              * test whether o is Date
944              * @function
945              * @param  o
946              * @returns {Boolean}
947              */
948             isDate:isValidParamValue,
949             /**
950              * test whether o is RegExp
951              * @function
952              * @param  o
953              * @returns {Boolean}
954              */
955             isRegExp:isValidParamValue,
956             /**
957              * test whether o is Object
958              * @function
959              * @param  o
960              * @returns {Boolean}
961              */
962             isObject:isValidParamValue
963         });
964 
965     S.each('Boolean Number String Function Array Date RegExp Object'.split(' '),
966         function (name, lc) {
967             // populate the class2type map
968             class2type['[object ' + name + ']'] = (lc = name.toLowerCase());
969 
970             // add isBoolean/isNumber/...
971             S['is' + name] = function (o) {
972                 return S.type(o) == lc;
973             }
974         });
975 
976     function cloneInternal(input, f, memory) {
977         var destination = input,
978             isArray,
979             isPlainObject,
980             k,
981             stamp;
982         if (!input) {
983             return destination;
984         }
985 
986         // If input is the source object of a pair of objects in memory,
987         // then return the destination object in that pair of objects .
988         // and abort these steps.
989         if (input[CLONE_MARKER]) {
990             // 对应的克隆后对象
991             return memory[input[CLONE_MARKER]].destination;
992         } else if (typeof input === "object") {
993             // 引用类型要先记录
994             var constructor = input.constructor;
995             if (S.inArray(constructor, [Boolean, String, Number, Date, RegExp])) {
996                 destination = new constructor(input.valueOf());
997             }
998             // ImageData , File, Blob , FileList .. etc
999             else if (isArray = S.isArray(input)) {
1000                 destination = f ? S.filter(input, f) : input.concat();
1001             } else if (isPlainObject = S.isPlainObject(input)) {
1002                 destination = {};
1003             }
1004             // Add a mapping from input (the source object)
1005             // to output (the destination object) to memory.
1006             // 做标记
1007             input[CLONE_MARKER] = (stamp = S.guid());
1008             // 存储源对象以及克隆后的对象
1009             memory[stamp] = {destination:destination, input:input};
1010         }
1011         // If input is an Array object or an Object object,
1012         // then, for each enumerable property in input,
1013         // add a new property to output having the same name,
1014         // and having a value created from invoking the internal structured cloning algorithm recursively
1015         // with the value of the property as the "input" argument and memory as the "memory" argument.
1016         // The order of the properties in the input and output objects must be the same.
1017 
1018         // clone it
1019         if (isArray) {
1020             for (var i = 0; i < destination.length; i++) {
1021                 destination[i] = cloneInternal(destination[i], f, memory);
1022             }
1023         } else if (isPlainObject) {
1024             for (k in input) {
1025                 if (hasOwnProperty(input, k)) {
1026                     if (k !== CLONE_MARKER &&
1027                         (!f || (f.call(input, input[k], k, input) !== FALSE))) {
1028                         destination[k] = cloneInternal(input[k], f, memory);
1029                     }
1030                 }
1031             }
1032         }
1033 
1034         return destination;
1035     }
1036 
1037     function compareObjects(a, b, mismatchKeys, mismatchValues) {
1038         // 两个比较过了,无需再比较,防止循环比较
1039         if (a[COMPARE_MARKER] === b && b[COMPARE_MARKER] === a) {
1040             return TRUE;
1041         }
1042         a[COMPARE_MARKER] = b;
1043         b[COMPARE_MARKER] = a;
1044         var hasKey = function (obj, keyName) {
1045             return (obj !== null && obj !== undefined) && obj[keyName] !== undefined;
1046         };
1047         for (var property in b) {
1048             if (hasOwnProperty(b, property)) {
1049                 if (!hasKey(a, property) && hasKey(b, property)) {
1050                     mismatchKeys.push("expected has key '" + property + "', but missing from actual.");
1051                 }
1052             }
1053         }
1054         for (property in a) {
1055             if (hasOwnProperty(a, property)) {
1056                 if (!hasKey(b, property) && hasKey(a, property)) {
1057                     mismatchKeys.push("expected missing key '" + property + "', but present in actual.");
1058                 }
1059             }
1060         }
1061         for (property in b) {
1062             if (hasOwnProperty(b, property)) {
1063                 if (property == COMPARE_MARKER) {
1064                     continue;
1065                 }
1066                 if (!S.equals(a[property], b[property], mismatchKeys, mismatchValues)) {
1067                     mismatchValues.push("'" + property + "' was '" + (b[property] ? (b[property].toString()) : b[property])
1068                         + "' in expected, but was '" +
1069                         (a[property] ? (a[property].toString()) : a[property]) + "' in actual.");
1070                 }
1071             }
1072         }
1073         if (S.isArray(a) && S.isArray(b) && a.length != b.length) {
1074             mismatchValues.push("arrays were not the same length");
1075         }
1076         delete a[COMPARE_MARKER];
1077         delete b[COMPARE_MARKER];
1078         return (mismatchKeys.length === 0 && mismatchValues.length === 0);
1079     }
1080 
1081 })(KISSY, undefined);
1082