前几天我对underscore.js的整体结构做了分析,今天我将针对underscore封装的方法进行具体的分析,代码的一些解释都写在了注释里,那么废话不多说进入今天的正文。

没看过上一篇的可以猛戳这里:underscore.js源码解析(一)

underscore.js源码GitHub地址: https://github.com/jashkenas/underscore/blob/master/underscore.js

本文解析的underscore.js版本是1.8.3

_.each

   _.each = _.forEach = function(obj, iteratee, context) {
//optimizeCb( )是underscore内部用来执行函数的很重要的方法,这个我们后面再聊
iteratee = optimizeCb(iteratee, context);
var i, length;
if (isArrayLike(obj)) {
//判断是否是类数组,一般不会传入类似 {length: 3} 这样的数据
for (i = 0, length = obj.length; i < length; i++) {
iteratee(obj[i], i, obj);
}
} else {
//对象处理,这个_.keys( )我们也后面再聊
var keys = _.keys(obj);
for (i = 0, length = keys.length; i < length; i++) {
iteratee(obj[keys[i]], keys[i], obj);
}
}
return obj;
};

_.each结构很清晰,如果是数组,就遍历数组调用相应的处理方法,如果是对象的话,就遍历对象调用相应的处理方法。

其中判断是否为类数组的代码如下:

   var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
//获取"length"属性
var getLength = property('length');
//判断是否是类数组
var isArrayLike = function(collection) {
var length = getLength(collection);
return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
};

类数组,即拥有 length 属性并且 length 属性值为 Number 类型的元素,例如数组、arguments、HTMLCollection 以及 NodeList 等等,当然 {length: 3} 这种对象也满足条件,但是_.each一般不会传这种值。

这种判断类数组的方法还是可以学习借鉴一下的。

 
接下来我们来聊上面提到的optimizeCb(),它是underscore内部用来执行函数的很重要的方法,并且改变所执行函数的作用域。

optimizeCb

   var optimizeCb = function(func, context, argCount) {
if (context === void 0) return func;
//argCount为函数参数的个数,针对不同参数个数进行不同的处理
switch (argCount == null ? 3 : argCount) {
//为单值的情况,例如times函数
case 1: return function(value) {
return func.call(context, value);
};
//因为2个参数的情况没用被用到,所以在新版中被删除了
//3个参数用于一些迭代器函数,例如map函数
case 3: return function(value, index, collection) {
return func.call(context, value, index, collection);
};
// 4个参数用于reduce和reduceRight函数
case 4: return function(accumulator, value, index, collection) {
return func.call(context, accumulator, value, index, collection);
};
}
return function() {
return func.apply(context, arguments);
};
};

cb和_.iteratee

   var cb = function(value, context, argCount) {
//如果为空,则返回value本身(identity函数就是一个返回本身的函数 )
if (value == null) return _.identity;
//如果为函数,则改变所执行函数的作用域
if (_.isFunction(value)) return optimizeCb(value, context, argCount);
//如果是对象,判断是否匹配(matcher是一个用来判断是否匹配的,我们具体后续再聊)
if (_.isObject(value)) return _.matcher(value);
return _.property(value);
};
// 通过调用cb函数,生成每个元素的回调
_.iteratee = function(value, context) {
return cb(value, context, Infinity);
};

_.keys

   _.keys = function(obj) {
//如果不是对象,返回空数组
if (!_.isObject(obj)) return [];
//如果支持原生的方法,就调用原生的keys方法
if (nativeKeys) return nativeKeys(obj);
var keys = [];
//记录所有属性名
for (var key in obj) if (_.has(obj, key)) keys.push(key);
// IE9以下枚举bug的兼容处理
if (hasEnumBug) collectNonEnumProps(obj, keys);
return keys;
};
获取所有的属性名存在数组当中。
这里的in操作符不仅在对象本身里查找,还会在原型链中查找。_.keys上增加了_.has()判断,将原型上的过滤。
 
其中IE9以下枚举bug兼容处理源码如下:

 //判断是否存在枚举bug
var hasEnumBug = !{toString: null}.propertyIsEnumerable('toString');
//不可枚举的属性如下
var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString', 'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString']; var collectNonEnumProps = function(obj, keys) {
var nonEnumIdx = nonEnumerableProps.length;
var constructor = obj.constructor;
var proto = _.isFunction(constructor) && constructor.prototype || ObjProto; // Constructor单独处理部分.
var prop = 'constructor';
如果对象和keys都存在constructor属性,则把他存入keys数组当中
if (_.has(obj, prop) && !_.contains(keys, prop)) keys.push(prop); while (nonEnumIdx--) {
prop = nonEnumerableProps[nonEnumIdx];
//如果obj对象存在上面数组里那些不可枚举的属性但是不在原型中,并且keys数组里面也没有的话
if (prop in obj && obj[prop] !== proto[prop] && !_.contains(keys, prop)){
//将其添加进来
keys.push(prop);
}
}
};

既然都说到了keys那么顺带着也介绍一下allkeys吧。

_.allKeys

   _.allKeys = function(obj) {
if (!_.isObject(obj)) return [];
var keys = [];
//获取所有的key
for (var key in obj) keys.push(key);
// 依然是IE9以下枚举bug的兼容处理
if (hasEnumBug) collectNonEnumProps(obj, keys);
return keys;
};

其实keys和allKeys代码对比就少了if (_.has(obj, key)),allKeys是获取所有的,包括继承的

在介绍_.matcher之前,需要先介绍一下createAssigner 和_.isMatch。

createAssigner

 var createAssigner = function(keysFunc, defaults) {
return function(obj) {
var length = arguments.length;
//判断是否是对象
if (defaults) obj = Object(obj);
//如果一个参数或者对象为空,则返回这个对象
if (length < 2 || obj == null) return obj;
//从第二个参数开始
for (var index = 1; index < length; index++) {
var source = arguments[index],
//获取对应的keys
//keysFunc只有keys和allKeys两种,在下面_.extend和_.extendOwn中可以看到
keys = keysFunc(source),
l = keys.length;
//进行拷贝处理
for (var i = 0; i < l; i++) {
var key = keys[i];
//defaults是为了对defaults做单独处理而添加的参数,具体的解释_.defaults里做详细分析
//在_.extend和_.extendOwn中default没有传值所以是underfinded,所以下面判断条件横为true,正常进行拷贝处理
if (!defaults || obj[key] === void 0) obj[key] = source[key];
}
}
return obj;
};
};
 //把后面的source拷贝到第一个对象
_.extend = createAssigner(_.allKeys); //把后面的source拷贝到第一个对象(只拷贝实例的)
// (https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/assign)
_.extendOwn = _.assign = createAssigner(_.keys); //跟_.extend相似,只是当key相同,只会去第一次的键值对,不会被后面的覆盖
_.defaults = createAssigner(_.allKeys, true);
把createAssigner中处理defaults的代码拿到这里具体分析
 
if (!defaults || obj[key] === void 0) obj[key] = source[key];

此时defaults参数为true,所以就要看obj[key] === void 0这句了,void 0返回的就是underfinded,那么这里换句通俗的话说就是判断key之前没有出现过相同的值时,才进行拷贝处理,如果后面出现相同的key,将不再进行拷贝操作,只保存第一次的键值对结果。

_.isMatch

 //用来判断该属性是否在对象中 (包括原型链)
_.isMatch = function(object, attrs) {
var keys = _.keys(attrs), length = keys.length;
//判断对象是否为空
if (object == null) return !length;
//判断是否是对象
var obj = Object(object);
for (var i = 0; i < length; i++) {
var key = keys[i];
//如果两者值不等或者不在属性不在对象当中则返回false
if (attrs[key] !== obj[key] || !(key in obj)) return false;
}
return true;
};

_.matcher和_.matches

 // 判断对象是否匹配attrs的属性
_.matcher = _.matches = function(attrs) {
//进行拷贝
attrs = _.extendOwn({}, attrs);
return function(obj) {
//用来判断该属性是否在对象中,上文有提及
return _.isMatch(obj, attrs);
};
};

小结

今天介绍了underscore.js中部分封装函数,其他的会在后面的文章继续一一分析

感谢大家的观看,也希望能够和大家互相交流学习,有什么分析的不对的地方欢迎大家批评指出

参考资料

https://segmentfault.com/a/1190000000531871
http://www.w3cfuns.com/house/17398/note/class/id/bb6dc3cabae6651b94f69bbd562ff370

underscore.js源码解析(二)的更多相关文章

  1. underscore.js源码解析(五)—— 完结篇

    最近公司各种上线,所以回家略感疲惫就懒得写了,这次我准备把剩下的所有方法全部分析完,可能篇幅过长...那么废话不多说让我们进入正题. 没看过前几篇的可以猛戳这里: underscore.js源码解析( ...

  2. underscore.js源码解析(四)

    没看过前几篇的可以猛戳这里: underscore.js源码解析(一) underscore.js源码解析(二) underscore.js源码解析(三) underscore.js源码GitHub地 ...

  3. underscore.js源码解析(三)

    最近工作比较忙,做不到每周两篇了,周末赶着写吧,上篇我针对一些方法进行了分析,今天继续. 没看过前两篇的可以猛戳这里: underscore.js源码解析(一) underscore.js源码解析(二 ...

  4. underscore.js源码解析(一)

    一直想针对一个框架的源码好好的学习一下编程思想和技巧,提高一下自己的水平,但是看过一些框架的源码,都感觉看的莫名其妙,看不太懂,最后找到这个underscore.js由于这个比较简短,一千多行,而且读 ...

  5. underscore.js源码解析【'_'对象定义及内部函数】

    (function() { // Baseline setup // -------------- // Establish the root object, `window` (`self`) in ...

  6. underscore.js源码解析【对象】

    // Object Functions // ---------------- // Keys in IE < 9 that won't be iterated by `for key in . ...

  7. underscore.js源码解析【函数】

    // Function (ahem) Functions // ------------------ // Determines whether to execute a function as a ...

  8. underscore.js源码解析【数组】

    // Array Functions // --------------- // Get the first element of an array. Passing **n** will retur ...

  9. underscore.js源码解析【集合】

    // Collection Functions // -------------------- // The cornerstone, an `each` implementation, aka `f ...

随机推荐

  1. MySQL学习【第八篇索引优化】

    一.建立索引的原则(规范) 1.选择唯一性索引 只要可以创建唯一性索引的,一律创建唯一索引(因为速度快呀) 判断是否能创建唯一索引,用count(列名),count(distinct(列名))一样就能 ...

  2. mysql数据库的系统操作基本操作

    本文主要总结并记录一下简单且常用的mysql 在cmd 窗口中操作的基本命令 命令停止mysql 数据库服务 1.(cmd)命令行 启动:net start mysql 停止:net stop mys ...

  3. Linux系统初学-第一课 虚拟机安装CentOS6.5以及Root密码找回

    Linux系统初学第一课 虚拟机安装CentOS6.5以及Root密码找回 虚拟机安装CentOS6.5 一.安装虚拟机 1-1.安装虚拟机VMware Station,新建虚拟机,选择典型配置. 1 ...

  4. scala 求数组排序后每两个元素的差值

    求数组排序后每两个元素的差值 例如数组 1,5,8,10,2 求得结果为 1,3,3,2 一般什么样的场景会有这种需求呢? 比如 计算一堆数据在一定时间内的计算时延, 或者得到这段时间内数据的平均计算 ...

  5. Shell 变量简介

    1. 概述 概述 知识点又稀又碎, 面试一问就流泪 简单介绍下 shell 下的变量及其基本操作 2. 环境 操作系统 CentOS Linux release 7.5 用户 root 用户 约定 使 ...

  6. AC自动机(简单版)(施工ing)

    声明 想看加强版的戳这里(施工ing,作者正努力中)~ 先贴题目吧哎~   AC自动机加强版  洛谷 P3796 题目: 洛谷 P3808 (数据范围困了我好久 TAT) 反正涉及字符串的算法都很玄学 ...

  7. angular5学习笔记 路由通信

    首先在路由字典中,接收值的组件中加上:/:id 在发送值的组件中,发送值的方式有几种. 第一种:<a routerLink="/detail/1">新闻详情1</ ...

  8. 20155209 2016-2017-2 《Java程序设计》第十周学习总结

    20155209 2016-2017-2 <Java程序设计>第十周学习总结 教材学习内容总结 计算机网络,是指分布在不同地理区域的计算机用通信线路互连起来的一个具有强大功能的网络系统.网 ...

  9. 20155213 实验一《Java开发环境的熟悉》实验报告

    20155213 实验一<Java开发环境的熟悉>实验报告 一. 实验内容及步骤 (一)使用JDK编译.运行简单的java程序 命令行下的程序开发 输入cd Code进入Code文件夹里 ...

  10. 20155231 2016-2017-2 《Java程序设计》第1周学习总结

    20155231 2016-2017-2 <Java程序设计>第1周学习总结 考核方式学习 课前准备 教材学习内容总结 第一章 Java平台概论 了解java 通过学习了解到,java设计 ...