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函数,调用reducereduceRight函数避免重复新建函数。第二层闭包保存keys,index,length这些变量。

 

7.find

调用情况如下,以findIndex为例,我们一个个分析它的函数

createPredicateIndexFinder(1)
类似于createReduce,传入一个用于表示方向的参数,初始化
迭代的长度,起点位置,迭代器,然后执行,如果迭代器返回true,表示找到了,返回index。否则返回-1
然后看生成迭代器的cb函数
cb
是一个所有回调函数的入口函数,会根据传入的值类型进行判断,然后优化。
如果传入的value是方法,调用optimizeCb优化。返回一个迭代器,参数是value, index, obj
 

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:

  1. Let valueOf be the result of calling the [[Get]] internal method of object O with argument "valueOf".
  2. If IsCallable(valueOf) is true then,
    1. Let val be the result of calling the [[Call]] internal method of valueOf, with O as the this value and an empty argument list.
    2. If val is a primitive value, return val.
  3. Let toString be the result of calling the [[Get]] internal method of object O with argument "toString".
  4. If IsCallable(toString) is true then,
    1. Let str be the result of calling the [[Call]] internal method of toString, with O as the this value and an empty argument list.
    2. If str is a primitive value, return str.
  5. 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函数中处理参数,然后返回一个闭包函数,把两部分参数组装在一起。

_.partial = function(func) {
  var args = slice.call(arguments, 1);
  return function() {
    return func.apply(this, args.concat(slice.call(arguments)));
  };
};
可以把固定的操作和参数定义成fn,
var fn = function(myargs1,myargs2..);
然后使用
var mydefin = partial(fn, args);返回一个函数
 
14.memorize
缓存耗费长时间的函数的结果
通过var fab = memorize(fn);
获得一个执行函数,每次执行的时候看是否有关键字,如果
 
15.throttle
节流阀函数,_.throttle(function, wait, [options]) 
如果有很短时间内的多次调用,只在大于等于wait时间时候调用
 
 
15.after和before的技巧

_.after = function(times, func) {
return function() {
if (--times < 1) {
return func.apply(this, arguments);
}
};
};

因为参数也是函数内的一个变量,所以返回的闭包函数对参数的操作也是同一个内存位置,因此直接更新times进行判断。

 
16. Infinity的妙用
用于设置无穷大或小的值,用于查找最大(小)值时候的初始化值:
var max = -Infinity;
if (value > max) {
  max = value;
}
 
17.eq
关键点
+0和-0如何区分:
1/+0 = Infinity
1/-0 =  -Infinity
 
18. 双叹号作用
用于返回true或false
比如:
var a
此时a是undefined ,而!!a是false
 
 19.template函数
_.template(templateString, [settings]) 
整体思路:
在templateString里设置
三个关键正则参数:
interpolate   插入内容,默认正则是       /<%=([\s\S]+?)%>/g
escape       插入转义后内容,默认正则是       /<%-([\s\S]+?)%>/g
evaluate    只执行一次不返回值,默认正则是       /<%([\s\S]+?)%>/g 
 
填充选项对象,如果自定义的选项没有上面的三个属性,用默认填充。
创建这三个关键字的正则,对输入的模板字符串进行全局的替换,
1.先对非捕获的内容进行转义

var escapes = {
"'": "'",
'\\': '\\',
'\r': 'r',
'\n': 'n',
'\u2028': 'u2028',
'\u2029': 'u2029'
};

2.然后对捕获的内容处理,然后组装成js代码的字符串形式

对所有匹配的正则都做处理,组装成一个大的js代码字符串。

再组装,增加参数定义,常用函数定义等内容在前头。

最后使用new Function创建render函数,函数的参数有两个:

第一个参数是传入的变量对象,如果有setting.variable的话,使用这个字符串作为变量名,然后模板中都是用这个变量名调用和访问,速度会快。

如果没有的话,就命名为obj,然后通过with(obj) {函数体}的方式来使用这个传入的数据。

 

 
posted @ 2016-05-17 17:04  mylifeholdon  阅读(443)  评论(0编辑  收藏  举报