给KISSY源码写注释——lang
这是第二部分,关于KISSY中的lang模块。
相比kissy中主要作为组织代码的方法和函数。
这个模块更多的是工具类型的。
文件下载:kissy-lang.js
/** * @module lang * @author lifesinger@gmail.com */ (function(win, S, undefined) { var doc = document, docElem = doc.documentElement, AP = Array.prototype, indexOf = AP.indexOf, lastIndexOf = AP.lastIndexOf, filter = AP.filter, trim = String.prototype.trim, toString = Object.prototype.toString, encode = encodeURIComponent, decode = decodeURIComponent, HAS_OWN_PROPERTY = 'hasOwnProperty', EMPTY = '', SEP = '&', BRACKET = encode('[]'), REG_TRIM = /^\s+|\s+$/g, REG_ARR_KEY = /^(\w+)\[\]$/, REG_NOT_WHITE = /\S/; S.mix(S, { /** * Determines whether or not the provided object is undefined. */ isUndefined: function(o) { return o === undefined; }, /** * Determines whether or not the provided object is a boolean. */ isBoolean: function(o) { return toString.call(o) === '[object Boolean]'; }, /** * Determines whether or not the provided object is a string. */ isString: function(o) { return toString.call(o) === '[object String]'; }, /** * Determines whether or not the provided item is a legal number. * NOTICE: Infinity and NaN return false. */ // 1/0 的值为infinity,无穷值。 isNumber: function(o) { return toString.call(o) === '[object Number]' && isFinite(o); }, /** * Checks to see if an object is a plain object (created using "{}" or "new Object"). */ isPlainObject: function(o) { // Make sure that DOM nodes and window objects don't pass through. // 这里过滤了大部分的因素,包括函数等。 过滤DOM节点元素 过滤window对象本身 return o && toString.call(o) === '[object Object]' && !o['nodeType'] && !o['setInterval']; }, /** * Checks to see if an object is empty. */ isEmptyObject: function(o) { for (var p in o) { return false; } return true; }, /** * Determines whether or not the provided object is a function. * NOTICE: DOM methods and functions like alert aren't supported. They return false on IE. */ isFunction: function(o) { //return typeof o === 'function'; // Safari 下,typeof NodeList 也返回 function return toString.call(o) === '[object Function]'; }, /** * Determines whether or not the provided object is an array. */ isArray: function(o) { return toString.call(o) === '[object Array]'; }, /** * Removes the whitespace from the beginning and end of a string. */ trim: trim ? function(str) { // 为什么不用isUndefined来判断? return (str == undefined) ? EMPTY : trim.call(str); } : function(str) { return (str == undefined) ? EMPTY : str.toString().replace(REG_TRIM, EMPTY); }, /** * Substitutes keywords in a string using an object/array. * Removes undefined keywords and ignores escaped keywords. */ // \{ 取消对{ 符号的解释,相当于转义{,直接输出。 // 一个简单的字串替换方法。 // 需注意:如果占位符在数据源(o)中找不到数据,会被空值替换。 substitute: function(str, o, regexp) { if(!S.isString(str) || !S.isPlainObject(o)) return str; return str.replace(regexp || /\\?\{([^{}]+)\}/g, function(match, name) { // 如果只写'\'会报错。字符转义,表示"\"。 // * "\"总是会被转义 if (match.charAt(0) === '\\') return match.slice(1); return (o[name] !== undefined) ? o[name] : EMPTY; }); }, /** * Executes the supplied function on each item in the array. * @param object {Object} the object to iterate * @param fn {Function} the function to execute on each item. The function * receives three arguments: the value, the index, the full array. * @param context {Object} (opt) */ // 遍历数据项操作。可操作对象和数组。 // 1、数组类型时,为什么不利用原生的forEach方法? //对于Array对象来说,与原生的 forEach的差别在于 context的设置。// 更正,是一样的。。原生方法中也有context的定义。 // 2、object如果是plainObject,带有length并且值有效,isObj反而是false。object被当做array处理。 // 也就是说,创建的plainObject参数不能有length属性。 // 话说 为什么要根据length 去判断? // 3、如果fn返回了false,则跳出遍历。 // 4、最后返回了原始的object。 // // ps: 因为依赖length判断,所以也支持字符串的遍历。 each: function(object, fn, context) { var key, val, i = 0, length = object.length, isObj = length === undefined || S.isFunction(object); context = context || win; if (isObj) { for (key in object) { if (fn.call(context, object[key], key, object) === false) { break; } } } else { // 极尽压缩之能事 for (val = object[0]; i < length && fn.call(context, val, i, object) !== false; val = object[++i]) { } } return object; }, /** * Search for a specified value within an array. */ indexOf: indexOf ? function(item, arr) { return indexOf.call(arr, item); } : function(item, arr) { for (var i = 0, len = arr.length; i < len; ++i) { if (arr[i] === item) { return i; } } return -1; }, /** * Returns the index of the last item in the array * that contains the specified value, -1 if the * value isn't found. */ lastIndexOf: (lastIndexOf) ? function(item, arr) { return lastIndexOf.call(arr, item); } : function(item, arr) { // for循环隐含了一次 0-1的操作。所以如果找不到就会返回 -1 for (var i = arr.length - 1; i >= 0; i--) { if (arr[i] === item) { break; } } return i; }, /** * Returns a copy of the array with the duplicate entries removed * @param a {Array} the array to find the subset of uniques for * @return {Array} a copy of the array with duplicate entries removed */ // 关于数组的唯一性操作。网上有种方式是通过键值对存储的方式来完成的。 // 但是有许多不足之处,对复杂类型项的筛选都不能很好的满足需求。 // 还是这种遍历比较的方式比较牢靠。 unique: function(a, override) { if(override) a.reverse(); // 默认是后置删除,如果 override 为 true, 则前置删除 var b = a.slice(), i = 0, n, item; while (i < b.length) { item = b[i]; while ((n = S.lastIndexOf(item, b)) !== i) { b.splice(n, 1); } i += 1; } if(override) b.reverse(); // 将顺序转回来 return b; }, /** * Search for a specified value index within an array. */ inArray: function(item, arr) { return S.indexOf(item, arr) > -1; }, /** * Converts object to a true array. */ makeArray: function(o) { if (o === null || o === undefined) return []; if (S.isArray(o)) return o; // The strings and functions also have 'length' // 这里用length判断相对于each来说比较靠谱。 // 主要是利用了Array.prototype.slice方法来对类数组对象进行数组化操作。 // 所以必定是需要length值的,如果length不是number类型,则直接包装成一个数组项返回。 if (typeof o.length !== 'number' || S.isString(o) || S.isFunction(o)) { return [o]; } // 944 line return slice2Arr(o); }, /** * Executes the supplied function on each item in the array. * Returns a new array containing the items that the supplied * function returned true for. * @param arr {Array} the array to iterate * @param fn {Function} the function to execute on each item * @param context {Object} optional context object * @return {Array} The items on which the supplied function * returned true. If no items matched an empty array is * returned. */ filter: filter ? function(arr, fn, context) { return filter.call(arr, fn, context); } : function(arr, fn, context) { var ret = []; S.each(arr, function(item, i, arr) { if (fn.call(context, item, i, arr)) { ret.push(item); } }); return ret; }, /** * Creates a serialized string of an array or object. ** {foo: 1, bar: 2} // -> 'foo=1&bar=2' * {foo: 1, bar: [2, 3]} // -> 'foo=1&bar[]=2&bar[]=3' * {foo: '', bar: 2} // -> 'foo=&bar=2' * {foo: undefined, bar: 2} // -> 'foo=undefined&bar=2' * {foo: true, bar: 2} // -> 'foo=true&bar=2' *
*/ // 作为参数,一层数组是被支持的,嵌套的第二层(以上)数组则会被直接丢弃。 param: function(o, sep) { // 非 plain object, 直接返回空 if (!S.isPlainObject(o)) return EMPTY; sep = sep || SEP; var buf = [], key, val; for (key in o) { val = o[key]; key = encode(key); // val 为有效的非数组值 if (isValidParamValue(val)) { // 原来可以一次性push N个内容的。。学习了。 buf.push(key, '=', encode(val + EMPTY), sep); } // val 为非空数组 else if (S.isArray(val) && val.length) { for (var i = 0, len = val.length; i < len; ++i) { if (isValidParamValue(val[i])) { buf.push(key, BRACKET + '=', encode(val[i] + EMPTY), sep); } // 嵌套的数组被丢弃了 } } // 其它情况:包括空数组、不是数组的 object(包括 Function, RegExp, Date etc.),直接丢弃 } // 每次都添加 sep 分隔符,所以在最后实际上是多了一个sep,将之剔除后合并。 buf.pop(); return buf.join(EMPTY); }, /** * Parses a URI-like query string and returns an object composed of parameter/value pairs. ** 'section=blog&id=45' // -> {section: 'blog', id: '45'} * 'section=blog&tag[]=js&tag[]=doc' // -> {section: 'blog', tag: ['js', 'doc']} * 'tag=ruby%20on%20rails' // -> {tag: 'ruby on rails'} * 'id=45&raw' // -> {id: '45', raw: ''} *
*/ unparam: function(str, sep) { if (typeof str !== 'string' || (str = S.trim(str)).length === 0) return {}; var ret = {}, pairs = str.split(sep || SEP), pair, key, val, m, i = 0, len = pairs.length; for (; i < len; ++i) { pair = pairs[i].split('='); key = decode(pair[0]); // pair[1] 可能包含 gbk 编码的中文,而 decodeURIComponent 仅能处理 utf-8 编码的中文,否则报错 try { val = decode(pair[1] || EMPTY); } catch (ex) { val = pair[1] || EMPTY; } if ((m = key.match(REG_ARR_KEY)) && m[1]) { ret[m[1]] = ret[m[1]] || []; ret[m[1]].push(val); } else { ret[key] = val; } } return ret; }, /** * Executes the supplied function in the context of the supplied * object 'when' milliseconds later. Executes the function a * single time unless periodic is set to true. * @param fn {Function|String} the function to execute or the name of the method in * the 'o' object to execute. * @param when {Number} the number of milliseconds to wait until the fn is executed. * @param periodic {Boolean} if true, executes continuously at supplied interval * until canceled. * @param o {Object} the context object. * @param data [Array] that is provided to the function. This accepts either a single * item or an array. If an array is provided, the function is executed with * one parameter for each array item. If you need to pass a single array * parameter, it needs to be wrapped in an array [myarray]. * @return {Object} a timer object. Call the cancel() method on this object to stop * the timer. */ // 1、fn可以是 o下的方法名。o默认是空对象。 // 2、data可以是类数组对象,内部自动转换为数组。 later: function(fn, when, periodic, o, data) { when = when || 0; o = o || { }; var m = fn, d = S.makeArray(data), f, r; if (S.isString(fn)) { m = o[fn]; } if (!m) { S.error('method undefined'); } f = function() { m.apply(o, d); }; r = (periodic) ? setInterval(f, when) : setTimeout(f, when); return { id: r, interval: periodic, // this 指当前返回的对象 cancel: function() { if (this.interval) { clearInterval(r); } else { clearTimeout(r); } } }; }, /** * Creates a deep copy of a plain object or array. Others are returned untouched. */ clone: function(o) { var ret = o, b, k; // array or plain object if (o && ((b = S.isArray(o)) || S.isPlainObject(o))) { ret = b ? [] : {}; for (k in o) { // hasOwnProperty在数组中也可以使用。并不是我印象中只针对对象的。 // 只是可查的值为数组的下标。 if (o[HAS_OWN_PROPERTY](k)) { ret[k] = S.clone(o[k]); } } } return ret; }, /** * Gets current date in milliseconds. */ now: function() { return new Date().getTime(); }, /** * Evalulates a script in a global context. */ globalEval: function(data) { if (data && REG_NOT_WHITE.test(data)) { // Inspired by code by Andrea Giammarchi // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html var head = doc.getElementsByTagName('head')[0] || docElem, script = doc.createElement('script'); // It works! All browsers support! script.text = data; // Use insertBefore instead of appendChild to circumvent an IE6 bug. // This arises when a base node is used. head.insertBefore(script, head.firstChild); head.removeChild(script); } } }); function isValidParamValue(val) { var t = typeof val; // val 为 null, undefined, number, string, boolean 时,返回 true return val === null || (t !== 'object' && t !== 'function'); } // 将 NodeList 等集合转换为普通数组 function slice2Arr(arr) { return AP.slice.call(arr); } // ie 不支持用 slice 转换 NodeList, 降级到普通方法 // 感觉这里的操作并不合理。 // 在ie下,slice不支持NodeList的转换,但还是支持arguments的转换的。 // 在这里执行了try..catch以后,ie下slice2Arr被重写为循环的方式来操作了。 // 也就是说在ie下不管是对正常的类数组(plainObject或者arguments)还是NodeList都是用循环的方式操作。 // 当然,如果slice方法跟循环遍历的方式的效率是一样的,那倒没什么关系 // 但据我所知,Array.prototype的方式应该效率高点吧。 // PS: ie9 beta支持了。 try { slice2Arr(docElem.childNodes); } catch(e) { slice2Arr = function(arr) { for (var ret = [], i = arr.length - 1; i >= 0; i--) { ret[i] = arr[i]; } return ret; } } })(window, KISSY); /** * NOTES: * * 2010/08 * - 增加 lastIndexOf 和 unique 方法。 * * 2010/06 * - unparam 里的 try catch 让人很难受,但为了顺应国情,决定还是留着。 * * 2010/05 * - 增加 filter 方法。 * - globalEval 中,直接采用 text 赋值,去掉 appendChild 方式。 * * 2010/04 * - param 和 unparam 应该放在什么地方合适?有点纠结,目前暂放此处。 * - param 和 unparam 是不完全可逆的。对空值的处理和 cookie 保持一致。 * */