首页 技术 正文
技术 2022年11月18日
0 收藏 647 点赞 4,265 浏览 5393 个字

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

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

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

相关推荐
python开发_常用的python模块及安装方法
adodb:我们领导推荐的数据库连接组件bsddb3:BerkeleyDB的连接组件Cheetah-1.0:我比较喜欢这个版本的cheeta…
日期:2022-11-24 点赞:878 阅读:8,907
Educational Codeforces Round 11 C. Hard Process 二分
C. Hard Process题目连接:http://www.codeforces.com/contest/660/problem/CDes…
日期:2022-11-24 点赞:807 阅读:5,432
下载Ubuntn 17.04 内核源代码
zengkefu@server1:/usr/src$ uname -aLinux server1 4.10.0-19-generic #21…
日期:2022-11-24 点赞:569 阅读:6,247
可用Active Desktop Calendar V7.86 注册码序列号
可用Active Desktop Calendar V7.86 注册码序列号Name: www.greendown.cn Code: &nb…
日期:2022-11-24 点赞:733 阅读:6,058
Android调用系统相机、自定义相机、处理大图片
Android调用系统相机和自定义相机实例本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显…
日期:2022-11-24 点赞:512 阅读:7,690
Struts的使用
一、Struts2的获取  Struts的官方网站为:http://struts.apache.org/  下载完Struts2的jar包,…
日期:2022-11-24 点赞:671 阅读:4,727