请选择 进入手机版 | 继续访问电脑版

技术控

    今日:23| 主题:54646
收藏本版 (1)
最新软件应用技术尽在掌握

[其他] Underscore源码分析系列(1)

[复制链接]
曾经的年少轻狂 发表于 2016-10-8 23:09:02
202 4

立即注册CoLaBug.com会员,免费获得投稿人的专业资料,享用更多功能,玩转个人品牌!

您需要 登录 才可以下载或查看,没有帐号?立即注册

x

Underscore源码分析系列(1)

Underscore源码分析系列(1)-1-技术控-underscore,源码

  说明

   本文是基于 underscore 1.8.3 来进行源码分析的。
  1. _.VERSION = "1.8.3" ;
复制代码
立即执行函数

   underscore 最外层是一个立即执行函数,如下:
  1. (function () {...}) (); // 立即执行函数
复制代码
立即执行函数(IIFE)的主要特征是:
  1. 使用函数表达式声明一个函数
  2. 在其后使用括号直接调用
   使用立即函数可以创建一个独立的沙箱似的作用域,它是函数的一种特殊调用方式,利用了 JavaScript 的函数作用域的概念。这样可以防止其他代码对该函数内部造成影响,而且不会创造全局变量防止污染全局空间。
  全局命名空间

  1. var
  2.   root =
  3.   (
  4.     (
  5.       (
  6.         ((typeof(self)) == ("object")) // self表示window窗口自身,这是浏览器环境下的全局命名空间
  7.         &&
  8.         ((self.self) === (self)) // 如果存在self,判断self是否是自身引用,即window这一对象
  9.       )
  10.       &&
  11.       (self) // 如果以上都满足,说明全局对象是window,并返回window作为root,这里self即window
  12.     )
  13.     ||
  14.     (
  15.       (
  16.         ((typeof(global)) == ("object")) // global表示node环境下全局的命名空间
  17.         &&
  18.         ((global.global) === (global)) // 如果存在gloabl,判断global是否是自身引用
  19.       )
  20.       &&
  21.       (global) // 如果以上都满足,说明全局对象是global,并返回global作为root
  22.     )
  23.   )
  24.   ||
  25.   (this); // 如果以上都不满足,直接返回this,这里应该处理既不是window这一浏览器环境,也不是global这一node环境的
复制代码
该段代码主要用于确认环境的全局命名空间,并赋值给变量root。有趣的是,之前代码是这样的:
  1. var root = this;
复制代码
  这引起了笔者的好奇,并进行了一定的研究,首先这一做法应该是出自学习了 lodash 的做法。它有如下两个好处:
  
       
  • 向前兼容严格模式,在严格模式下直接使用 this 会得到 undefined 。这是因为 ecma262 第5版中,为了防止人们误将非new出来的对象的this指向全局命名空间,特地将之设置为 undefined 。   
  • 用来支持 WebWorker ,在 WebWorker 里可以使用 self 但不能使用 window 。  
   另一个有趣的地方的是, selfglobal 使用 typeof 判断时使用 == 而非 === ,这样可以进行转义,因为全局命名空间不一定完全等价为对象,这跟浏览器的实现有关。
  防冲突

   防止与其他库对 _ 的使用冲突是十分重要的。 underscore 做了如下处理。
  1. // 保存原有全局变量“_”
  2. var previousUnderscore = root._;
  3. // 使用noConflict方法返回自身
  4. _.noConflict = function() {
  5.   root._ = previousUnderscore;
  6.   return this;
  7. };
复制代码
保存原型以及方法的快速引用

  1. var
  2.   ArrayProto = Array.prototype,
  3.   ObjProto = Object.prototype;
  4. var
  5.   SymbolProto = ((typeof(Symbol)) !== ("undefined")) ? (Symbol.prototype) : (null);
  6. var
  7.   push = ArrayProto.push,
  8.   slice = ArrayProto.slice,
  9.   toString = ObjProto.toString,
  10.   hasOwnProperty = ObjProto.hasOwnProperty;
  11. var
  12.   nativeIsArray = Array.isArray,
  13.   nativeKeys = Object.keys,
  14.   nativeCreate = Object.create;
复制代码
  其中值得注意的是, Symbolecma262 第6版才正式发布的,所以要先判断是否存在。
  对象创建的特殊处理

   为了处理 Object.create 的跨浏览器的兼容性, underscore 进行了特殊的处理。我们知道,原型是无法直接实例化的,因此我们先创建一个空对象,然后将其原型指向这个我们想要实例化的原型,最后返回该对象其一个实例。其代码如下:
  1. var Ctor = function() {};  // 用于代理原型转换的空函数
  2. var baseCreate = function(prototype) {
  3.   if (!(_.isObject(prototype))) return {}; // 如果参数不是对象,直接返回空对象
  4.   if (nativeCreate) return nativeCreate(prototype); // 如果原生的对象创建可以使用,返回该方法根据原型创建的对象
  5.    
  6.   // 处理没有原生对象创建的情况
  7.   Ctor.prototype = prototype;  // 将空函数的原型指向要使用的原型
  8.   var result = new Ctor();  // 创建一个实例
  9.   Ctor.prototype = null;  // 恢复Ctor的原型供下次使用
  10.   return result;  // 返回该实例
  11. };
复制代码
初始化

  1. var _ = function(obj) {
  2.   if (obj instanceof _) return obj;  // 如果参数是underscore的一个实例,就直接返回该参数
  3.   if (!(this instanceof _)) return new _(obj);  // 实例化
  4.   this._wrapped = obj;  // 将该实例保存
  5. };
复制代码
  这里是为了不用用户自己实例化 underscore ,并将该实例保存。
  1. if (typeof exports != 'undefined' && !exports.nodeType) {
  2.   if (typeof module != 'undefined' && !module.nodeType && module.exports) {
  3.   exports = module.exports = _;
  4.   }
  5.   exports._ = _;
  6. } else {
  7.   root._ = _;
  8. }
复制代码
  这里笔者翻译一下原有注释。这一部分代码是为了在 node.js 环境下将 underscore 作为一个模块使用,并向后兼容旧版的模块 API ,即 require 。如果在浏览器环境中,则将 underscore 以_暴露到全局。值得注意的是使用 nodeType 来确保 exportsmodule 并不是HTML的元素。
  回调处理

   underscore 有大量需要回调的函数,因此对于回调进行了特殊的处理。首先存在一个 optimizeCb 函数,它对正常传入的函数进行一层包装处理,这样可以在更好的重复使用,保证上下文即this正确。
  1. var optimizeCb = function(func, context, argCount) {
  2.   if (context === void 0) return func;  // void 0返回undefined,即未传入上下文信息时直接返回相应的函数
  3.   switch (argCount == null ? 3 : argCount) {  // 如果传入了argCount,那么参数数量为argCount,如果传入等价为null,则为3,包括未传值得情况
  4.     // 1个参数的时候,只需要传递当前值
  5.     case 1: return function(value) {
  6.       return func.call(context, value);  
  7.     };
  8.     // 并没有2个参数的时候,因为目前并没有用到2个参数的时候
  9.    
  10.     // 3个参数的时候,分别是当前值、当前索引以及整个集合
  11.     case 3: return function(value, index, collection) {
  12.       return func.call(context, value, index, collection);
  13.     };
  14.     // 4个参数的时候,分别是累计值、当前值、当前索引以及整个集合
  15.     case 4: return function(accumulator, value, index, collection) {
  16.       return func.call(context, accumulator, value, index, collection);
  17.     };
  18.   }
  19.   // 如果都不符合上述的任一条件,直接使用apply调用相关函数
  20.   return function() {
  21.     return func.apply(context, arguments);
  22.   };
  23. };
复制代码
接下来,是针对集合迭代的回调处理。
  1. (function () {...}) (); // 立即执行函数0
复制代码
杂项

  在开始集合函数编写前还有一些处理。
  1. (function () {...}) (); // 立即执行函数1
复制代码



上一篇:10 Tips in Picking a Technology Stack for Web Developers
下一篇:每日一博|Zookeeper -- 管理分布式环境中的数据
lihdm 发表于 2016-10-9 03:23:09
来自水星的曾经的年少轻狂
回复 支持 反对

使用道具 举报

yiyizhang 发表于 2016-10-12 02:08:19
男女关系处理得好会传出佳话,处理得不好会传出闲话。
回复 支持 反对

使用道具 举报

qcwvw 发表于 2016-11-12 18:09:36
我曾经跟一个人无数次的擦肩而过,衣服都擦破了,也没擦出火花。
回复 支持 反对

使用道具 举报

unixnow 发表于 2016-11-13 10:57:36
你女儿在我手上,我不是你女婿。
回复 支持 反对

使用道具 举报

*滑动验证:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

我要投稿

推荐阅读


回页顶回复上一篇下一篇回列表
手机版/CoLaBug.com ( 粤ICP备05003221号 | 文网文[2010]257号 )

© 2001-2017 Comsenz Inc. Design: Dean. DiscuzFans.

返回顶部 返回列表