Underscore-分析
0.Ecmascript的版本重要时间点
Ecmascript262-3 1999年,ie5.5及以后完全支持,ff,chrome等全部支持
Ecmascript262-4 因为跨越太大,废弃
Ecmascript262-3.1 后改名ecmascript5 主流浏览器支持,ie8部分支持,ie9全部支持
1.常用ecmascript5原生方法
1.1 Array.isArray(arg)
如果是数组,放回true
if (!Array.isArray) {
Array.prototype.isArray = function(ar) {
return Object.prototype.toString.call(ar) === “[object Array]”
}
}
1.2 Object.keys(obj)
把obj的所有属性枚举放到数组里返回,顺序取决于for in的顺序,Object.keys不枚举原型链中的属性
if (!Object.keys) {
Object.prototype.keys = function(obj) {
var ar = [];
for (var i in obj) {
if (Object.prototype.hasOwnProperty.call(obj,i) {
ar.push(i);
}
}
return ar;
}
}
1.3 FuncProto.bind
fun.bind(thisArg[, arg1[, arg2[, ...]]])
if (!Function.bind) {
Function.prototype.bind = function(context) {
var fn = this,
arg = [];
for (var i = 1, len = arguments.length; i < len; i++) {
arg[i-1] = arguments[i];
}
return function() {
for (var j = 0, len2 = arguments.length; j < len2; j++) {
arg[i++] = arguments[j];
}
fn.apply(context, arg);
}
}
}
1.4 Object.create
Object.create(prototype, [ propertiesObject ])
创建一个对象并返回,让该对象的prototype对象指向参数的对象
if (!Object.prototype.create) {
Object.prototype.create = (function() {
function fn() {
};
var hasown = Object.prototype.hasOwnProperty;
return function(proto, args) {
var obj;
fn.prototype = proto;
obj = new fn();
fn.prototype = null;
if (args) {
for (var i in args) {
if (hasown.call(args, i)) {
obj[i] = args[i];
}
}
}
return obj;
}
}())
}
2. _
构建UnderScore的安全的构造函数
关键点是安全:
var _ = function(obj) {
if (obj instanceof _) return obj;
if (!(this instanceof _)) return new _(obj);
this._wrapped = obj;
};
如果参数obj是通过构造函数_new出来的对象,那么直接返回
如果this不是_的对象,说明可能不是通过new _的调用,所以执行
return new _(obj);
最终执行this._wrapped = obj;
2.1 new操作符的过程
当new ConstructorFn(...) 执行时:
1.一个新对象被创建。它继承自ConstructorFn.prototype(newobj.prototype=foo.prototype).
2.构造函数 ConstructorFn 被执行。执行的时候,相应的传参会被传入,同时上下文(this)会被指定为这个新实例。new ConstructorFn 等同于 new ConstructorFn(), 只能用在不传递任何参数的情况。
3.如果构造函数返回了一个“对象”,那么这个对象会取代整个new出来的结果。如果构造函数没有返回对象,那么new出来的结果为步骤1创建的对象,ps:一般情况下构造函数不返回任何值,不过用户如果想覆盖这个返回值,可以自己选择返回一个普通对象来覆盖。当然,返回数组也会覆盖,因为数组也是对象。
3.baseCreate
创建一个对象,让它的prototype指向参数对象,多用于子类继承父类
首先判断参数是否为对象或函数,如果不是,返回空对象。
然后判断是否存在原生create,如果有,调用。
如果没有,通过ctor函数,让ctort.prototype = prototype;
var result = new ctor();
ctor.prototype = null;
var baseCreate = function(prototype) {
if (!_.isObject(prototype)) return {};
if (nativeCreate) return nativeCreate(prototype);
Ctor.prototype = prototype;
var result = new Ctor;
Ctor.prototype = null;
return result;
};
4.var property
var property = function(key) {
return function(obj) {
return obj == null ? void 0 : obj[key];
};
};
生成安全的取对象属性的方法,防止undefined或null取属性值而报错。
关键点:
obj == null ? void 0 : obj[key];
obj == null 实际上是一个隐式转换,就是当obj是null或undefined的时候。
==的隐式转换规则,详见Ecmascript: http://www.ecma-international.org/ecma-262/5.1/#sec-11.9.1
如果双方类型一致,对比值:
如果是undefined, true
如果是null, true
如果是number,
如果其中一个是NaN,不相等
如果值相等,相等
+0, -0, 0是相等的。
如果是String,
当两个字符串长度一致,而且对应位置字符一致那么相等。
如果是Boolean
当两者都是true或者false时候相等。
如果是Object
当两者的指针指向一个对象时相等。
如果类型不一致,
null, undefined相等
string,boolean和number比较时候会转换成数字再比较
boolean和其他的比较会转换成number,true1 false 0
Object和string或number比较会先调用toString或valueOf,得到值后在比较。
5. optimizeCb
用于优化回调函数:
1) 绑定上下文context,如果有context,返回绑定context的函数,用于后续操作。如果没有context,直接返回
2)通过客户端传入的argCount决定返回函数的参数个数,防止使用arguments对象,因为在一些旧的v8版本中,使用
arguments会导致函数得不到浏览器优化。
6.createreduce
在函数内部创建一个闭包的迭代函数,然后返回一个函数,判断参数,初始化然后调用这个迭代函数。
闭包作用
闭包大致有这么几个作用:避免命名冲突;私有化变量;变量持久化,让函数参数固定值
这里的作用主要就是变量(函数)持久化,好处就是重复调用的时候不需要再重新创建函数,从而提升执行速度。
为什么要用两层闭包呢?第一层闭包持久化iterator
函数,调用reduce
和reduceRight
函数避免重复新建函数。第二层闭包保存keys
,index
,length
这些变量。
7.find
调用情况如下,以findIndex为例,我们一个个分析它的函数
8.filter
对每个元素根据传入的函数进行计算,如果true,将这个元素放入数组,最后返回数组。
内部还是先用optimizeCb进行处理函数的绑定和优化,然后执行each,对每个元素处理。
9.contains
1.调用values把对象的值转换成数组返回
2.调用indexOf >=0 判断是否在给定的数组(或对象中)
3.使用createIndexFinder生成indexOf方法,关键点有:
1)根据开始index和dir值,生成循环的条件
2)判断是否被查找的元素是不是NaN(obj !== obj),如果是的话,对每个元素调用isNaN判断是不是NaN,如果有的话,返回true
10. isNaN
_.isNaN = function(obj) {
return _.isNumber(obj) && obj !== +obj;
};
一般我们判断NaN的方法只需要:a !== a,那么就是NaN了。
而这里是为了同时检验出:new Object(NaN)
这个数的类型(toString)是Number,如果+obj的话就转换成了NaN,满足new Object(NaN) !== NaN
另附对象转换成数字:
When the [[DefaultValue]] internal method of O is called with hint Number, the following steps are taken:
- Let valueOf be the result of calling the [[Get]] internal method of object O with argument "
valueOf
". - If IsCallable(valueOf) is true then,
- Let val be the result of calling the [[Call]] internal method of valueOf, with O as the this value and an empty argument list.
- If val is a primitive value, return val.
- Let toString be the result of calling the [[Get]] internal method of object O with argument "
toString
". - If IsCallable(toString) is true then,
- Let str be the result of calling the [[Call]] internal method of toString, with O as the this value and an empty argument list.
- If str is a primitive value, return str.
- Throw a TypeError exception.
首先调用valueOf,如果可执行,返回值,否则调用toString,返回值。
11.invoke
_.invoke(obj, method)
对obj的每个元素执行method方法,结果组成数组并返回;
如果method是自定义函数,那么执行method,否则,获得eachvalue[method]并执行。
12.shuffle
乱序排列一个数组,几个关键点:
1)返回的是新数组
2)有可能顺序是不变的
3)random的
3)核心是:
if (random !== index) shuffle[index] = shuffle[random]
shuffle[random] = set[index];
13.partial
给函数设置预定义的变量,允许使用占位符。
核心思想是在partial函数中处理参数,然后返回一个闭包函数,把两部分参数组装在一起。
_.throttle(function, wait, [options])
_.after = function(times, func) {
return function() {
if (--times < 1) {
return func.apply(this, arguments);
}
};
};
因为参数也是函数内的一个变量,所以返回的闭包函数对参数的操作也是同一个内存位置,因此直接更新times进行判断。
_.template(templateString, [settings])
var escapes = {
"'": "'",
'\\': '\\',
'\r': 'r',
'\n': 'n',
'\u2028': 'u2028',
'\u2029': 'u2029'
};
2.然后对捕获的内容处理,然后组装成js代码的字符串形式
对所有匹配的正则都做处理,组装成一个大的js代码字符串。
再组装,增加参数定义,常用函数定义等内容在前头。
最后使用new Function创建render函数,函数的参数有两个:
第一个参数是传入的变量对象,如果有setting.variable的话,使用这个字符串作为变量名,然后模板中都是用这个变量名调用和访问,速度会快。
如果没有的话,就命名为obj,然后通过with(obj) {函数体}的方式来使用这个传入的数据。