未加星标

Underscore源码分析系列(1)

字体大小 | |
[前端(javascript) 所属分类 前端(javascript) | 发布者 店小二05 | 时间 2016 | 作者 红领巾 ] 0人收藏点击收藏

Underscore源码分析系列(1)
说明

本文是基于 underscore 1.8.3 来进行源码分析的。

_.VERSION = "1.8.3" ; 立即执行函数

underscore 最外层是一个立即执行函数,如下:

(function () {...}) (); // 立即执行函数

立即执行函数(IIFE)的主要特征是:

1. 使用函数表达式声明一个函数

2. 在其后使用括号直接调用

使用立即函数可以创建一个独立的沙箱似的作用域,它是函数的一种特殊调用方式,利用了 javascript 的函数作用域的概念。这样可以防止其他代码对该函数内部造成影响,而且不会创造全局变量防止污染全局空间。

全局命名空间 var root = ( ( ( ((typeof(self)) == ("object")) // self表示window窗口自身,这是浏览器环境下的全局命名空间 && ((self.self) === (self)) // 如果存在self,判断self是否是自身引用,即window这一对象 ) && (self) // 如果以上都满足,说明全局对象是window,并返回window作为root,这里self即window ) || ( ( ((typeof(global)) == ("object")) // global表示node环境下全局的命名空间 && ((global.global) === (global)) // 如果存在gloabl,判断global是否是自身引用 ) && (global) // 如果以上都满足,说明全局对象是global,并返回global作为root ) ) || (this); // 如果以上都不满足,直接返回this,这里应该处理既不是window这一浏览器环境,也不是global这一node环境的

该段代码主要用于确认环境的全局命名空间,并赋值给变量root。有趣的是,之前代码是这样的:

var root = this;

这引起了笔者的好奇,并进行了一定的研究,首先这一做法应该是出自学习了 lodash 的做法。它有如下两个好处:

向前兼容严格模式,在严格模式下直接使用 this 会得到 undefined 。这是因为 ecma262 第5版中,为了防止人们误将非new出来的对象的this指向全局命名空间,特地将之设置为 undefined 。 用来支持 WebWorker ,在 WebWorker 里可以使用 self 但不能使用 window 。

另一个有趣的地方的是, self 和 global 使用 typeof 判断时使用 == 而非 === ,这样可以进行转义,因为全局命名空间不一定完全等价为对象,这跟浏览器的实现有关。

防冲突

防止与其他库对 _ 的使用冲突是十分重要的。 underscore 做了如下处理。

// 保存原有全局变量“_” var previousUnderscore = root._; // 使用noConflict方法返回自身 _.noConflict = function() { root._ = previousUnderscore; return this; }; 保存原型以及方法的快速引用 var ArrayProto = Array.prototype, ObjProto = Object.prototype; var SymbolProto = ((typeof(Symbol)) !== ("undefined")) ? (Symbol.prototype) : (null); var push = ArrayProto.push, slice = ArrayProto.slice, toString = ObjProto.toString, hasOwnProperty = ObjProto.hasOwnProperty; var nativeIsArray = Array.isArray, nativeKeys = Object.keys, nativeCreate = Object.create;

其中值得注意的是, Symbol 是 ecma262 第6版才正式发布的,所以要先判断是否存在。

对象创建的特殊处理

为了处理 Object.create 的跨浏览器的兼容性, underscore 进行了特殊的处理。我们知道,原型是无法直接实例化的,因此我们先创建一个空对象,然后将其原型指向这个我们想要实例化的原型,最后返回该对象其一个实例。其代码如下:

var Ctor = function() {}; // 用于代理原型转换的空函数 var baseCreate = function(prototype) { if (!(_.isObject(prototype))) return {}; // 如果参数不是对象,直接返回空对象 if (nativeCreate) return nativeCreate(prototype); // 如果原生的对象创建可以使用,返回该方法根据原型创建的对象 // 处理没有原生对象创建的情况 Ctor.prototype = prototype; // 将空函数的原型指向要使用的原型 var result = new Ctor(); // 创建一个实例 Ctor.prototype = null; // 恢复Ctor的原型供下次使用 return result; // 返回该实例 }; 初始化 var _ = function(obj) { if (obj instanceof _) return obj; // 如果参数是underscore的一个实例,就直接返回该参数 if (!(this instanceof _)) return new _(obj); // 实例化 this._wrapped = obj; // 将该实例保存 };

这里是为了不用用户自己实例化 underscore ,并将该实例保存。

if (typeof exports != 'undefined' && !exports.nodeType) { if (typeof module != 'undefined' && !module.nodeType && module.exports) { exports = module.exports = _; } exports._ = _; } else { root._ = _; }

这里笔者翻译一下原有注释。这一部分代码是为了在 node.js 环境下将 underscore 作为一个模块使用,并向后兼容旧版的模块 API ,即 require 。如果在浏览器环境中,则将 underscore 以_暴露到全局。值得注意的是使用 nodeType 来确保 exports 和 module 并不是HTML的元素。

回调处理

underscore 有大量需要回调的函数,因此对于回调进行了特殊的处理。首先存在一个 optimizeCb 函数,它对正常传入的函数进行一层包装处理,这样可以在更好的重复使用,保证上下文即this正确。

var optimizeCb = function(func, context, argCount) { if (context === void 0) return func; // void 0返回undefined,即未传入上下文信息时直接返回相应的函数 switch (argCount == null ? 3 : argCount) { // 如果传入了argCount,那么参数数量为argCount,如果传入等价为null,则为3,包括未传值得情况 // 1个参数的时候,只需要传递当前值 case 1: return function(value) { return func.call(context, value); }; // 并没有2个参数的时候,因为目前并没有用到2个参数的时候 // 3个参数的时候,分别是当前值、当前索引以及整个集合 case 3: return function(value, index, collection) { return func.call(context, value, index, collection); }; // 4个参数的时候,分别是累计值、当前值、当前索引以及整个集合 case 4: return function(accumulator, value, index, collection) { return func.call(context, accumulator, value, index, collection); }; } // 如果都不符合上述的任一条件,直接使用apply调用相关函数 return function() { return func.apply(context, arguments); }; };

接下来,是针对集合迭代的回调处理。

var builtinIteratee; // 设置变量保存内置迭代 var cb = function(value, context, argCount) { if (_.iteratee !== builtinIteratee) return _.iteratee(value, context); // 如果用户修改了迭代器,则使用新的迭代器 if (value == null) return _.identity; // 如果不传value,表示返回等价的自身 if (_.isFunction(value)) return optimizeCb(value, context, argCount); // 如果传入函数,返回该函数的回调 if (_.isObject(value)) return _.matcher(value); // 如果传入对象,寻找匹配的属性值 return _.property(value); // 如果都不是,返回相应的属性访问器 }; // 默认的迭代器,是以无穷argCount为参数调用cb函数。用户可以自行修改。 _.iteratee = builtinIteratee = function(value, context) { return cb(value, context, Infinity); }; 杂项

在开始集合函数编写前还有一些处理。

// 属性访问器生成。 // 通过传入键名,返回可以访问以传入对象为参数,并可以获取该对象相应键值对的函数 var property = function(key) { return function(obj) { return obj == null ? void 0 : obj[key]; }; }; // 额外参数。 // 等价于ES6的rest参数。它将起始索引后的参数放入一个数组中。 var restArgs = function(func, startIndex) { startIndex = startIndex == null ? func.length - 1 : +startIndex; // startIndex为null时,为函数声明时的参数数量减1,即除了第一个参数后的其他参数,否则为传入的startIndex return function() { var length = Math.max(arguments.length - startIndex, 0), // 长度为实际参数数量与起始索引差值,若为负数则取0 rest = Array(length), // 剩余参数是一个length长度的数组 index = 0; // 将startIndex开始的length长度的参数放入rest数组中 for (; index < length; index++) { rest[index] = arguments[index + startIndex]; } // 根据开始索引进行处理 switch (startIndex) { case 0: return func.call(this, rest); // 起始索引为0,表示全部参数作为一个数组调用 case 1: return func.call(this, arguments[0], rest); // 表示将第一个参数以及后续参数的数组,作为两个参数进行调用 case 2: return func.call(this, arguments[0], arguments[1], rest); // 将第一个参数、第二个参数以及后续参数的数组作为三个参数进行调用 } // 将起始索引前的参数保存为一个数组args,并将最后一个值存为之前的rest数组,即[,[]] var args = Array(startIndex + 1); for (index = 0; index < startIndex; index++) { args[index] = arguments[index]; } args[startIndex] = rest; // 将args作为参数调用 return func.apply(this, args); }; }; // 处理类数组对象。 // 类数组对象的长度应当是一个非负整数 var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1; // 表示正无穷+∞ var getLength = property('length'); // 获取length属性访问器 var isArrayLike = function(collection) { var length = getLength(collection); return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX; };

本文前端(javascript)相关术语:javascript是什么意思 javascript下载 javascript权威指南 javascript基础教程 javascript 正则表达式 javascript设计模式 javascript高级程序设计 精通javascript javascript教程

主题: 浏览器HTMLJavaScriptJava冲突变量包装
分页:12
转载请注明
本文标题:Underscore源码分析系列(1)
本站链接:http://www.codesec.net/view/480165.html
分享请点击:


1.凡CodeSecTeam转载的文章,均出自其它媒体或其他官网介绍,目的在于传递更多的信息,并不代表本站赞同其观点和其真实性负责;
2.转载的文章仅代表原创作者观点,与本站无关。其原创性以及文中陈述文字和内容未经本站证实,本站对该文以及其中全部或者部分内容、文字的真实性、完整性、及时性,不作出任何保证或承若;
3.如本站转载稿涉及版权等问题,请作者及时联系本站,我们会及时处理。
登录后可拥有收藏文章、关注作者等权限...
技术大类 技术大类 | 前端(javascript) | 评论(0) | 阅读(90)