2

underscore 0.1.0版本源码阅读

 3 years ago
source link: https://zwkang.com/?p=422
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

underscore 0.1.0版本源码阅读

这篇文章是为之后的underscore现版本的源码做铺垫,大家先感受下早期最先版本

  1. 0.1.0版本足够小
  2. 这个版本已经有将近小10年的历史了
  3. 还是有一些不错的地方。
  4. 大家可以直接阅读代码细节。基本没有调用上的阅读困难

Underscore 0.1.0版本源码分析

// Underscore.js
// (c) 2009 Jeremy Ashkenas, DocumentCloud Inc.
// Underscore is freely distributable under the terms of the MIT license.
// Portions of Underscore are inspired by or borrowed from Prototype.js, 
// Oliver Steele's Functional, And John Resig's Micro-Templating.
// For all details and documentation:
// http://documentcloud.github.com/underscore/
window._ = {

  VERSION : '0.1.0',

  /*------------------------ Collection Functions: ---------------------------*/
  // 集合函数
  // The cornerstone, an each implementation.
  // Handles objects implementing forEach, each, arrays, and raw objects.

  each : function(obj, iterator, context) {
    var index = 0;
    try {
      if (obj.forEach) {
        // 有forEach优先选择forEach
        obj.forEach(iterator, context);
      } else if (obj.length) {
        // 使用自定义迭代器迭代
        for (var i=0; i<obj.length; i++) iterator.call(context, obj[i], i);
      } else if (obj.each) {
        // 如果是对象拥有each方法 // 这里是兼容jq吗?
        obj.each(function(value) { iterator.call(context, value, index++); });
      } else {
        var i = 0;
        for (var key in obj) {
          // 对象属性迭代,可能会获取到对象上可迭代的属性
          var value = obj[key], pair = [key, value];
          pair.key = key;
          pair.value = value;
          // 迭代器绑定上下文且进行迭代
          // 迭代器形容each(function(value,index))
          iterator.call(context, pair, i++);
        }
      }
    } catch(e) {
      if (e != '__break__') throw e;
    }
    return obj;
  },

  // Return the results of applying the iterator to each element. Use Javascript
  // 1.6's version of map, if possible.
  // 
  map : function(obj, iterator, context) {
    // 没有过滤对象其实。。
    // 判断是否存在。map方法
    if (obj && obj.map) return obj.map(iterator, context);

    // map为返回迭代后返回一个数组,所以我们只要push迭代后的值
    // each方法仅仅是定义了一种迭代方法。
    // 后面的几种方法都可以进行复用
    var results = [];
    _.each(obj, function(value, index) {
      results.push(iterator.call(context, value, index));
    });
    return results;
  },

  // Inject builds up a single result from a list of values. Also known as
  // reduce, or foldl.
  // 有点像简化版的reducer
  inject : function(obj, memo, iterator, context) {
    _.each(obj, function(value, index) {
      memo = iterator.call(context, memo, value, index);
    });
    return memo;
  },

  // Return the first value which passes a truth test.
  detect : function(obj, iterator, context) {
    var result;
    // 当有一个完成条件。
    // 则通过try catch来跳出each迭代。// 这里还是有点意思的。
    _.each(obj, function(value, index) {
      if (iterator.call(context, value, index)) {
        result = value;
        // 前面的each try catch的目的在于利用自定义抛出错误令each遍历停止
        throw '__break__';
      }
    });
    return result;
  },

  // Return all the elements that pass a truth test. Use Javascript 1.6's
  // filter(), if it exists.
  // filter
  select : function(obj, iterator, context) {
    if (obj.filter) return obj.filter(iterator, context);
    //如果是数组直接过滤输出
    var results = [];
    _.each(obj, function(value, index) {
      if (iterator.call(context, value, index)) results.push(value);
    });
    //不是数组遍历操作压入数组返回
    return results;
  },

  // Return all the elements for which a truth test fails.
  // 有点像filter,过滤符合条件的
  reject : function(obj, iterator, context) {
    var results = [];
    _.each(obj, function(value, index) {
      // 注意感叹号取反
      if (!iterator.call(context, value, index)) results.push(value);
    });
    return results;
  },

  // Determine whether all of the elements match a truth test. Delegate to
  // Javascript 1.6's every(), if it is present.
  // 有点像every
  all : function(obj, iterator, context) {
    // 设置默认迭代器
    iterator = iterator || function(v){ return v; };
    // 判断是否存在every
    if (obj.every) return obj.every(iterator, context);
    // every即需要所有元素都满足迭代条件。
    var result = true;
    _.each(obj, function(value, index) {
      result = result && !!iterator.call(context, value, index);
      if (!result) throw '__break__';
    });
    return result;
  },

  // Determine if at least one element in the object matches a truth test. Use
  // Javascript 1.6's some(), if it exists.
  // any即some
  // some是只要有值符合条件就
  any : function(obj, iterator, context) {
    iterator = iterator || function(v) { return v; };
    if (obj.some) return obj.some(iterator, context);
    var result = false;
    // 进行迭代,需要迭代器产值均为负值
    _.each(obj, function(value, index) {
      // 赋值语句返回值是右值 
      // if(a = true){console.log(123)}
      // if(d = false){console.log(333)}
      if (result = !!iterator.call(context, value, index)) throw '__break__';
    });
    return result;
  },

  // Determine if a given value is included in the array or object, 
  // based on '==='.
  // 判断一个值包含在数组或对象中时
  include : function(obj, target) {
    if (_.isArray(obj)) return _.indexOf(obj, target) != -1;
    var found = false;
    _.each(obj, function(pair) {
      if (pair.value === target) {
        found = true;
        throw '__break__';
      }
    });
    return found;
  },

  // Invoke a method with arguments on every item in a collection.
  // 借助一个函数对集合内的所有元素进行操作。
  invoke : function(obj, method) {
    var args = _.toArray(arguments).slice(2);
    return _.map(obj, function(value) {
      return (method ? value[method] : value).apply(value, args);
    });
  },

  // Optimized version of a common use case of map: fetching a property.

  // 对数组的值做迭代,返回对应的key值
  pluck : function(obj, key) {
    var results = [];
    _.each(obj, function(value){ results.push(value[key]); });
    return results;
  },
  // 返回一个最大的元素
  // Return the maximum item or (item-based computation).
  max : function(obj, iterator, context) {
    // 这边是如果没有迭代函数的话直接使用Math.max.apply取最大值
    // Math.max.apply({}) === Math.max.apply([]) === -Infinity
    if (!iterator && _.isArray(obj)) return Math.max.apply(Math, obj);
    var result;
    // 进行迭代
    _.each(obj, function(value, index) {
      var computed = iterator ? iterator.call(context, value, index) : value;
      // 对迭代每次的值进行计算。
      if (result == null || computed >= result.computed) result = {value : value, computed : computed};
      // 对初始化以及每一次判断computed大于当前值更新
    });
    return result.value;
  },

  // Return the minimum element (or element-based computation).
  min : function(obj, iterator, context) {
    if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj);
    var result;
    _.each(obj, function(value, index) {
      var computed = iterator ? iterator.call(context, value, index) : value;
      if (result == null || computed < result.computed) result = {value : value, computed : computed};
    });
    return result.value;
  },

  // Sort the object's values by a criteria produced by an iterator.
  sortBy : function(obj, iterator, context) {
    // 根据对象的一些值进行排序
    // 首先我们只要关注每个函数的目的即可
    // map一次遍历,返回多个对象数组。然后根据数组对象排序
    // 并且对象均有值为criteria表示我们迭代函数的值(就是根据什么排序)
    return _.pluck(_.map(obj, function(value, index) {
      return {
        value : value,
        criteria : iterator.call(context, value, index)
      };
    }).sort(function(left, right) {
      var a = left.criteria, b = right.criteria;
      // 判断大于小于等于
      return a < b ? -1 : a > b ? 1 : 0;
    }), 'value');
    // 外面的一层pluck是为了解开map函数的一层打包
  },

  // Use a comparator function to figure out at what index an object should
  // be inserted so as to maintain order. Uses binary search.
  // 这是利用二分查找吧
  // 利用二分查找找出元素应该插入到哪个位置中
  sortedIndex : function(array, obj, iterator) {
    iterator = iterator || function(val) { return val; };
    // 初始化一个迭代器
    var low = 0, high = array.length;//不严谨直接去length
    // 初始化高低位
    // 
    while (low < high) {
      var mid = (low + high) >> 1;
      // 对中位取半(important快捷方法)
      iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid;
    }
    return low;
  },

  // Convert anything iterable into a real, live array.
  // 转换数组(转换一切可迭代的)
  toArray : function(iterable) {
    if (!iterable) return []; //为假值直接返回[]
    if (_.isArray(iterable)) return iterable; //判断是否为数组
    return _.map(iterable, function(val){ return val; });//如果为对象的话,利用map转成数组
  },

  // Return the number of elements in an object.
  // 返回对象的元素数量
  size : function(obj) {
    return _.toArray(obj).length;
  },

  /*-------------------------- Array Functions: ------------------------------*/

  // Get the first element of an array.
  // 返回数组第一个元素
  first : function(array) {
    return array[0];
  },

  // Get the last element of an array.
  // 返回数组最后一个元素
  last : function(array) {
    return array[array.length - 1];
  },

  // Trim out all falsy values from an array.
  //去除假值的数组元素
  //需要传入一个操作函数 false值在两次取反会被去掉
  compact : function(array) {
    return _.select(array, function(value){ return !!value; });
  },

  // Return a completely flattened version of an array.
  //多维数组返回一个一维数组
  // 数组扁平化
  // 开始展现出函数式编程的灵活性了
  flatten : function(array) {
    return _.inject(array, [], function(memo, value) {
      // 这边如果还是数组的话进行一个递归的扁平
      if (_.isArray(value)) return memo.concat(_.flatten(value));
      memo.push(value);
      return memo;
    });
  },

  // Return a version of the array that does not contain the specified value(s).
  // 对传入的数组进行筛选
  // 这里有一个点,我们在传参形参定义了array
  // 而在下面的地方我们可以直接使用array且slice截取arguments

  without : function(array) {
    var values = array.slice.call(arguments, 0);
    return _.select(array, function(value){ return !_.include(values, value); });
  },

  // Produce a duplicate-free version of the array. If the array has already
  // been sorted, you have the option of using a faster algorithm.
  // 唯一的数组。有一个参数可以选择是否排序的数组,是的话会选择最快的算法
  uniq : function(array, isSorted) {
    return _.inject(array, [], function(memo, el, i) {
      if (0 == i || (isSorted ? _.last(memo) != el : !_.include(memo, el))) memo.push(el);
      return memo;
    });
  },

  // Produce an array that contains every item shared between all the 
  // passed-in arrays.
  // 筛选多个元素数组的相同值
  intersect : function(array) {
    var rest = _.toArray(arguments).slice(1);
    // 获得其余多个参数
    // 最外面肯定是一层筛选。
    // 里面做筛选的条件
    return _.select(_.uniq(array), function(item) {
      return _.all(rest, function(other) { 
        // 目的在于取交集
        // 所以我们使用外层的item 对比层的个个数组 据此我们返回同时存在多个数组中的元素
        return _.indexOf(other, item) >= 0;
      });
    });
  },

  // Zip together multiple lists into a single array -- elements that share
  // an index go together.
  zip : function() {
    var args = _.toArray(arguments);
    var length = _.max(_.pluck(args, 'length'));
    // 返回最大数组的长度。
    var results = new Array(length);
    // 创建一个存放点
    for (var i=0; i<length; i++) results[i] = _.pluck(args, String(i));
    // 从对应数组中取出
    // 疑惑,这里有一个小点。那即是当多个数组时,且多个数组长度不一致。会不会导致存入多个undefined值
    return results;
  },

  // If the browser doesn't supply us with indexOf (I'm looking at you, MSIE), 
  // we need this function. Return the position of the first occurence of an 
  // item in an array, or -1 if the item is not included in the array.
  // indexOf的profill
  // 先判断是否支持了indexOf

  indexOf : function(array, item) {
    if (array.indexOf) return array.indexOf(item);
    var length = array.length;
    // 取长度
    for (i=0; i<length; i++) if (array[i] === item) return i;
    // 返回长度
    return -1;
    // 默认返回-1
  },

  /* ----------------------- Function Functions: -----------------------------*/

  // Create a function bound to a given object (assigning 'this', and arguments,
  // optionally). Binding with arguments is also known as 'curry'.

  bind : function(func, context) {
    if (!context) return func;
    // 判断有否需要绑定上下文
    var args = _.toArray(arguments).slice(2);
    // 类数组转数组 且截取除形参的部分
    return function() {
      // 这个arguments应该是再内层的arguments吧
      var a = args.concat(_.toArray(arguments));
      // 然后用apply 传补全的上下层参数。
      // bind 方法就是返回一个绑定上下文的函数
      return func.apply(context, a);
    };
  },

  // Bind all of an object's methods to that object. Useful for ensuring that 
  // all callbacks defined on an object belong to it.
  // bind 多个
  bindAll : function() {
    var args = _.toArray(arguments);
    var context = args.pop();
    // 这里获得一个上下文对象。
    _.each(args, function(methodName) {
      context[methodName] = _.bind(context[methodName], context);
    });
  },

  // Delays a function for the given number of milliseconds, and then calls
  // it with the arguments supplied.
  // 延迟一个数组的执行 可以传入一个数字当延迟毫秒数
  // 以提供的参数调用
  delay : function(func, wait) {
    var args = _.toArray(arguments).slice(2);
    // setTimeout做延迟
    return window.setTimeout(function(){ return func.apply(func, args); }, wait);
  },

  // Defers a function, scheduling it to run after the current call stack has 
  // cleared.
  defer : function(func) {
    return _.delay.apply(_, [func, 1].concat(_.toArray(arguments).slice(1)));
  },

  // Returns the first function passed as an argument to the second, 
  // allowing you to adjust arguments, run code before and after, and 
  // conditionally execute the original function.
  // 返回作为参数传递给第二个的第一个函数,允许你调整参数,在前后运行代码以及有条件地执行原始函数
  wrap : function(func, wrapper) {
    return function() {
      // 内层arguments 与第一个形参的函数所结合
      var args = [func].concat(_.toArray(arguments));
      // wrapper apply调用
      return wrapper.apply(wrapper, args);
    };
  },

  /* ------------------------- Object Functions: ---------------------------- */
  // 一下可以很好的联想到我们的pair对象附加了key value值
  // Retrieve the names of an object's properties.
  keys : function(obj) {
    // 拿到对象中所有属性为key的值
    return _.pluck(obj, 'key');
  },

  // Retrieve the values of an object's properties.
  values : function(obj) {
    // 拿到对象中所有属性为value的值。
    return _.pluck(obj, 'value');
  },

  // Extend a given object with all of the properties in a source object.
  // 简单的for in复制值,这是一层浅复制
  extend : function(destination, source) {
    for (var property in source) destination[property] = source[property];
    return destination;
  },

  // Create a (shallow-cloned) duplicate of an object.
  // 复制一层浅复制
  clone : function(obj) {
    return _.extend({}, obj);
  },

  // Perform a deep comparison to check if two objects are equal.
  // 深判断两个对象是否相等
  isEqual : function(a, b) {
    // Check object identity.
    // 查看数组引用是否一致
    if (a === b) return true;
    // Different types?
    // 是否为相同类型
    var atype = typeof(a), btype = typeof(b);
    if (atype != btype) return false;
    // 简单的==判断
    // Basic equality test (watch out for coercions).
    if (a == b) return true;
    // 也许他们有自己的isEqual方法可以调用
    // One of them implements an isEqual()?
    if (a.isEqual) return a.isEqual(b);
    // 判断类型是否不为对象
    // If a is not an object by this point, we can't handle it.
    if (atype !== 'object') return false;
    // 深层次比对他们的属性个数
    // Nothing else worked, deep compare the contents.
    var aKeys = _.keys(a), bKeys = _.keys(b);
    // Different object sizes?
    if (aKeys.length != bKeys.length) return false;
    // Recursive comparison of contents.
    // 判断属性值
    for (var key in a) if (!_.isEqual(a[key], b[key])) return false;
    return true;
  },

  // Is a given value a DOM element?
  // 判断是否为Dom元素
  // 也可以判断instanceof HTMLElement
  isElement : function(obj) {
    return !!(obj && obj.nodeType == 1);
  },

  // Is a given value a real Array?
  // 判断是否为数组
  // 使用Object.prototype.toString
  isArray : function(obj) {
    return Object.prototype.toString.call(obj) == '[object Array]';
  },

  // Is a given value a Function?
  // 判断是否为函数
  isFunction : function(obj) {
    return typeof obj == 'function';
  },

  // Is a given variable undefined?
  // 判断是否为undefined
  isUndefined : function(obj) {
    return typeof obj == 'undefined';
  },

  /* -------------------------- Utility Functions: -------------------------- */

  // Generate a unique integer id (unique within the entire client session).
  // Useful for temporary DOM ids.

  /**
   * eg
   * var ids = [], i = 0;
     while(i++ < 100) ids.push(_.uniqueId());
     equals(_.uniq(ids).length, ids.length, 'can generate a globally-unique stream of ids');
   */
  uniqueId : function(prefix) {
    // ||0是一次快速将假值初始化为0的操作
    var id = this._idCounter = (this._idCounter || 0) + 1;
    // 然后每次的累加。
    // 那么则是不存在的数
    return prefix ? prefix + id : id;
  },

  // Javascript templating a-la ERB, pilfered from John Resig's 
  // "Secrets of the Javascript Ninja", page 83.
  // 忍者秘籍83页?这么真实的吗
  template : function(str, data) {
    // 这是简单地使用new Function来做的一个`模板引擎`
    // with可以帮助我们注入上下文对象。所以拼接一般选择with
    // 当然还有一些过滤提取操作例如标志位的<% %>提取替换
    var fn = new Function('obj', 
      'var p=[],print=function(){p.push.apply(p,arguments);};' +
      'with(obj){p.push(\'' +
      str
        .replace(/[\r\t\n]/g, " ") 
        .split("<%").join("\t") 
        .replace(/((^|%>)[^\t]*)'/g, "$1\r") 
        .replace(/\t=(.*?)%>/g, "',$1,'") 
        .split("\t").join("');") 
        .split("%>").join("p.push('") 
        .split("\r").join("\\'") 
    + "');}return p.join('');");
    return data ? fn(data) : fn;  
  }

};

  • 后面的模板实现挺亮眼。

  • try catch 设计each的跳出。

  • >> 取半的快捷

  • 早期函数库的代码复用。(有部分也许是不高效)

  • 现在在"新时代",看向"旧时代",API命名设计有些还是颇为相似

  • 源码篇幅较少,加上注释也不过400行左右。整篇阅读下来也没有很大的障碍,就是有复用性相对较高,但是对着test文件看看测试用例也就好了~~~

  • 有任何建议,包括但不限于(写文风格,文章建议)。都可以提出来与我交流。

可以通过这些地方联系我

我的邮箱:[email protected]

Comments

发表评论 取消回复

电子邮件地址不会被公开。 必填项已用*标注

评论

姓名 *

电子邮件 *

站点

在此浏览器中保存我的名字、电邮和网站。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK