这是第二部分,关于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 保持一致。
*
*/