Git Product home page Git Product logo

underscore's People

Contributors

xlshen avatar

Stargazers

 avatar

Watchers

 avatar  avatar

underscore's Issues

underscore中rest参数实现原理

underscore中rest参数实现原理

核心函数: restArgs方法

源码:

var restArgs = function(func, startIndex) {
    // 如果没有传入startIndex,startIndex默认为func函数形参个数的最后一个,也就说最后一个形参默认为rest对象
    startIndex = startIndex == null ? func.length - 1 : +startIndex;
    return function() {
            // 获取实际参数个数和startIndex大小之差与0的最大值
        var length = Math.max(arguments.length - startIndex, 0),
            // 构建长度为length的rest数组
            rest = Array(length),
            index = 0;

        // 初始化startIndex之后的所有元素为rest数组元素
        for (; index < length; index++) {
            rest[index] = arguments[index + startIndex];
        }
        // call性能加速处理常用0, 1, 2三种情况,剩余情况apply处理
        switch (startIndex) {
            case 0:
                return func.call(this, rest);
            case 1:
                return func.call(this, arguments[0], rest);
            case 2:
                return func.call(this, arguments[0], arguments[1], rest);
        }
        // 如果startIndex超过2,则执行默认的方法:构建args数组,含有所有参数
        var args = Array(startIndex + 1);
        // 构建args数组,结构:[1, 3, 4, [2, 3, 5]]
        for (index = 0; index < startIndex; index++) {
            args[index] = arguments[index];
        }
        // args最后一个参数为rest对象
        args[startIndex] = rest;
        return func.apply(this, args);
    };
};

实例:

  _.restArgs(function(value, rest){
    console.log(value, rest);
  })(1, 2, 3);
  // 1, [2, 3]

解析:

在源码中,_.restArgs = restArgs,也就说直接调用的restArgs方法。该方法目的就是通过分割传入函数的形式参数,达到动态可变参数的效果。
实例中:startIndex没有传值,则默认为func.length - 1;通过比较实Math.max(arguments.length - startIndex, 0)来确定rest数组长度。接下来循环生成rest数组元素,如果startIndex的取值为0 || 1 || 2的话,进行优化加速【优化原理】,否则调用默认执行。
调用默认执行:因为rest对象总是最后一个,则args数组长度为startIndex + 1,初始化args数组元素,最后一个位置留给rest对象,返回执行结果。

总结一下:restArgs函数解决了动态参数分配问题,返回构建好的类似[1, 2, [3, 4, 5]]形式的数组方便其他函数使用

underscore中OOP**—实例对象方法调用实现机制

underscore中OOP**—实例对象方法调用实现机制

默认情况下,underscore的方法都是挂载到_对象下面。那在OOP中怎么实现实例对象方法的调用,也就说如何把_下面方法挂载到_的原型对象中。那就是_.mixin()方法,源码实现:

  // Add your own custom functions to the Underscore object.
  // 混入函数
  // ① 挂载_下面的函数到_.prototype原型上
  // ② 扩展underscroe库函数,参数为对象,方法在对象的属性上
  _.mixin = function(obj) {
      // 遍历对象下面所有方法挂载到_上面和_.prototype上面
      _.each(_.functions(obj), function(name) {
          // 挂载到_上面【此处为扩展,用户可以自定义自己的对象方法混入到_对象上面】
          var func = _[name] = obj[name];
          // 挂载到_.prototype上面
          _.prototype[name] = function() {
              // 第一个参数为OOP的对象,例如:_([1, 2, 3]).each(function(){})中,this._warpped = [1, 2, 3];
              var args = [this._wrapped];
              // arguments为name需要的其他参数,例如:_([1, 2, 3]).each(function(){}, this)中,arguments为function(){}和this
              push.apply(args, arguments);
              // 执行func函数,支持链式操作
              return chainResult(this, func.apply(_, args));
          };
      });
      return _;
  };
  // Add all of the Underscore functions to the wrapper object.
  // 混入_对象
  _.mixin(_);

实例:

  // 自定义对象方法,扩展到_对象
  var _obj = {
    tips: function(obj, msg){
      console.log(obj, msg);
    }
  }
  _.mixin(_obj);
  _.tips([1,2,3], "underscore"); // [1, 2, 3] "underscore"
  _([1,2,3]).tips("underscore"); // [1, 2, 3] "underscore"

解析:
_.mixin()方法将目标对象中的方法扩展到__.prototype对象上,之后可以直接方法调用和OOP实例调用使用。在通过OOP实例调用时,内部函数获取到传入的this._wrapped对象,然后和调用参数一起赋值给args变量,最后在chainResult函数中调用执行。
【注:对于chainResult函数不清楚的移步:underscore链式操作

underscore链式操作

underscore链式操作

涉及函数:_.chain(obj) === _(obj).chain()

实例:

  // 非 OOP 链式调用
  _.chain([1, 2, 3])
  .map(function(a) {return a;})
  .reverse()
  .value(); // [3, 2, 1]

  // OOP 链式调用
  _([1, 2, 3])
  .chain()
  .map(function(a){return a;})
  .first()
  .value(); // 1

解析:

以上两种方式都可以达到链式调用的目的。在underscore内部实现中,这两种方式实现都是用了OOP的实现方法,具体来看实现函数:

  _.chain = function(obj) {
    var instance = _(obj);
    instance._chain = true;
    return instance;
  };

_.chain(obj)通过调用chain()方法将_(obj)先转化为_的实例对象,然后将实例对象添加_chain属性,返回该实例对象。
【注:对于_(obj)不熟悉的可以移步
underscore中_是弄啥的

而对于_(obj).chain()来说,首先通过_(obj)创建_的实例对象,然后该实例调用_对象原型中的chain()方法【该方法通过_.mixin()方法混入到_.prototype中】详见:underscore中OOP**—实例对象方法调用实现机制

_.prototype['chain']方法最后一步:

  // _.mixin()方法中_.prototype[name] = function() {...}最后返回
  return chainResult(this, func.apply(_, args));
  // chainResult实现函数
  var chainResult = function(instance, obj) {
      return instance._chain ? _(obj).chain() : obj;
  };

chainResult(this, func.apply(_, args))方法中this_对象,func.apply(_, args)调用_.chain()方法返回传入对象的实例,在上面例子中就是[1, 2, 3]对象的实例对象,并给该对象添加_chain属性,返回该实例对象。此时调用为chain(_对象, [1, 2, 3]添加了_chain属性的实例对象)_._chain === undefined所以直接返回添加了_chain属性的[1, 2, 3]对象实例。

到这里是不是发现了什么?!Yes,_.chain(obj)_(obj).chain()回到了同一起跑线!

最后再梳理一下其中逻辑:chainResult实现函数通过传入的obj实例和obj对象,判断如果obj实例中含有_chain属性则继续调用_(obj).chain()生成具有_chain属性的obj实例对象;如果没有,则说明obj本身已经有了_chain属性【因为调用该函数的都是在_.prototype[XXX]中,该函数的执行最后必然会调用上面说的chainResult方法,func.apply(this, args)最终返回的一定是带有_chain属性的实例对象】,直接返回obj即可。

underscore中_是弄啥的

underscore中_是弄啥的

_underscore中是以函数形式定义的对象,看看它的定义:

  var _ = function(obj) {
      // 如果参数是"_"的实例,直接返回,相当于容错机制
      if (obj instanceof _) return obj;
      // 否则返回实例new _(obj), 继续调用该函数,下一次进来之后直接保存下一步的变量_wrapped
      if (!(this instanceof _)) return new _(obj);
      // 实例对象_wrapped属性中存储了接受的参数
      this._wrapped = obj;
  };

_是一个函数,支持OOP调用的构造函数
underscore中的属性和方法默认都挂载在_下面:比如 _.each_.map...等

实例:

  // 直接调用
  _.each([1, 2, 3], function(value, index){
    console.log(value); // 1, 2, 3
  });
  // OOP方式调用
  _([1, 2, 3]).each(function(value, index){
    console.log(value); // 1, 2, 3
  });

解析:

第一种直接调用就直接调用_.each()方法即可,因为方法默认都是挂载在_上面。

第二种就厉害了,第一个判断是否是_实例,如果是直接返回该对象,_.chain方法就用到了该判断。
第二个判断this如果不是_实例,生成_对象实例并返回,继而继续从头执行该函数,前两个条件都不满足,执行最后一个,把传入的对象赋值给该实例_wrapped属性,完成操作。

梳理一下:_在underscore中就是定义的一个函数,可以作为OOP中构造函数使用,然后把传入的对象添加到生成的实例对象的_wrapped属性中

underscore中optimizeCb优化函数

underscore中optimizeCb优化函数

optimizeCb内部优化函数接收参数:原始函数、执行上下文、参数个数。
【注:为什么说是内部优化函数,具体可以参考最下方参考文献详解:Why is call so much faster than apply?
简而言之就是callapply快的原因就是在call内部执行中,内部处理所需要的参数都是格式化好的参数,不用特殊处理。
optimizeCb源码:

  // Internal function that returns an efficient (for current engines) version
  // of the passed-in callback, to be repeatedly applied in other Underscore
  // functions.
  // 内部优化方法,参数:函数,执行上下文,参数个数
  // 返回根据传入函数在不同执行上下文,不同参数个数的情况下执行的函数
  var optimizeCb = function(func, context, argCount) {
      // 如果没有指定执行上下文,返回该函数
      if (context === void 0) return func;
      switch (argCount) {
          case 1:
              return function(value) {
                  return func.call(context, value);
              };
              // The 2-parameter case has been omitted only because no current consumers made use of it.
              // 针对下面的null的问题,已经在github提交了pull request(https://github.com/jashkenas/underscore/pull/2732),我认为应该是undefined而非null,具体在下面分析中会提到
          // case void 0:
          case null:
              // 在执行上下文中,没有参数个数,执行下面
              // 在undefined情况下_.each(), _.map()内部用到
          case 3:
              return function(value, index, collection) {
                  return func.call(context, value, index, collection);
              };
              // _.reduce(), _.reduceRight()
          case 4:
              return function(accumulator, value, index, collection) {
                  return func.call(context, accumulator, value, index, collection);
              };
      }
      // 其实不用上面的 switch-case 语句,直接执行下面的 return 函数就行了
      // 理由就是充分利用了call的参数格式化优化
      return function() {
          return func.apply(context, arguments);
      };
  };

实例:

  // _.each()方法在内部调用时首先进行的就是optimizeCb()方法的优化,把_.each()方法源码放到下面
  _.each([1, 2, 3], function(value, index){
    console.log(value, index); // 1, 2, 3
  }, this);
  // _.each()方法源码
  _.each = _.forEach = function(obj, iteratee, context) {
      // 执行函数分发优化策略,即前面的optimizeCb函数,赋值给局部变量iteratee进行代理执行
      iteratee = optimizeCb(iteratee, context);
      // 执行闭包iteratee方法
      var i, length;
      if (isArrayLike(obj)) {
          // 如果是类数组对象
          for (i = 0, length = obj.length; i < length; i++) {
              iteratee(obj[i], i, obj);
          }
      } else {
          // 普通对象
          var keys = _.keys(obj);
          for (i = 0, length = keys.length; i < length; i++) {
              iteratee(obj[keys[i]], keys[i], obj);
          }
      }
      // 返回该对象,链式操作使用
      return obj;
  };

解析:

对于_.each()方法,案例中的contextwindow对象,argCountundefined,如果此处按照null的话则直接执行最后通用返回调用,不会进入case 3的情况,但按照该优化函数设立目的应该是要进入该条件执行,可以参考1.8.2版本[optimizeCb] Combine null and 3 as multi-case for argCount switch statement #2613,已提交pull requestundefined【大家觉得呢???】
按这样理解则执行

  // iteratee(obj[i], i, obj);
  return function(value, index, collection) {
      // context == window,value是每个值,index是索引,collection是传入的数组本身,返回函数执行结果
      return func.call(context, value, index, collection);
  };

总结一下:optimizeCb是针对underscore内部参数确定方法的分类优化函数,原理就是call && apply执行调用

【参考文献】

underscore对象浅拷贝核心

underscore对象浅拷贝核心

核心函数:createAssigner()

源码:

  // An internal function for creating assigner functions.
  // 浅拷贝代码核心
  // 内部调用函数:_.extend(), _.extendOwn(), _.defaults()
  // 1. _.extend = createAssigner(_.allKeys);
  // 2. _.extendOwn = _.assign = createAssigner(_.keys);
  // 3. _.defaults = createAssigner(_.allKeys, true);
  // 文章号:[underscore对象浅拷贝核心](https://github.com/xlshen/underscore/issues/5)
  // keysFunc为_.key或者_.allKeys方法,返回对象的属性名组成的数组
  var createAssigner = function(keysFunc, defaults) {
      // 返回函数闭包,通过传入的对象执行以下函数
      return function(obj) {
          // 获取传入执行函数参数个数
          var length = arguments.length;
          // 如果defaults执行true,再对传入对象包装一下,此时如果obj === null || undefined
          // 则obj也转换为对象,则执行下一步时就不会因为obj为null而返回null
          if (defaults) obj = Object(obj);
          // 如果参数只有原始对象一个或者没有的情况,或者obj值为null的话,直接返回该对象
          // 1. _.extend()和_.extendOwn()调用是defaults为undefined,此时length<2或者obj值为null时,返回该对象
          // 2. _.defaults()调用时,defaults为true,此时length<2时,返回该对象【注:此时obj不可能为null,因为即使原始传入的obj为null,经过上一步操作,null也会转换为Object对象】
          if (length < 2 || obj == null) return obj;
          // _.extendOwn(destination, source1, source2, ...) 外层循环从source1开始取值
          // 外层循环参数取源对象source1,source2...,从source1开始,顺序往后执行
          for (var index = 1; index < length; index++) {
                  // 获取源对象
              var source = arguments[index],
                  // 返回源对象中的属性名数组
                  keys = keysFunc(source),
                  // 源对象属性名数组长度
                  l = keys.length;
              // 内层循环源对象属性名数组
              for (var i = 0; i < l; i++) {
                  // 获取属性名
                  var key = keys[i];
                  // 1. _.extendOwn(), _.extend()执行时defaults === false,直接覆盖目标对象相应的属性值
                  // 2. _.defaults()执行时defaults === true且目标对象当前属性为undefined时,赋值该对象该属性值;如果该对象该属性存在,不执行任何操作
                  if (!defaults || obj[key] === void 0) obj[key] = source[key];
              }
          }
          // 返回目标对象
          return obj;
      };
  };

内部调用函数:_.extend() || _.extendOwn() || _.defaults()

  // Fill in a given object with default properties.
  // 扩展目标对象,但是禁止扩展修改目标对象已经存在的属性键值对
  _.defaults = createAssigner(_.allKeys, true);
  // Extend a given object with all the properties in passed-in object(s).
  // 扩展目标对象,对于源对象及其原型链中所有可枚举属性对目标对象进行扩展修改
  _.extend = createAssigner(_.allKeys);

  // Assigns a given object with all the own properties in the passed-in object(s).
  // (https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/assign)
  // 扩展目标对象,对于源对象仅获取自身可枚举属性扩展修改目标对象,但是源对象原型链属性不进行扩展修改
  _.extendOwn = _.assign = createAssigner(_.keys);

实例:

  var _proto = {name: "xlshen", userName: "13日星期五"};
  var obj = Object.create(_proto, {
    age: {
      value: 20,
      writable: true,
      enumerable: true,
      configurable: true
    }
  });
  _.extend({age: 18}, obj); // {name: "xlshen", age: 20, userName: "13日星期五"}
  _.extendOwn({name: "xlshen"}, {name: "XXX", age: 20}); // {name: "XXX", age: 20}
  _.defaults({name: "xlshen"}, {name: "XXX", age: 20}); // {name: "xlshen", age: 20}

解析:

  • _.extend执行中keysFunc_.allKeys方法,返回的是对象本身及其原型链上所有可枚举属性名组成的数组;执行createAssigner方法,在上例中目标对象为{age: 18}arguments.length2defaultsundefined;继续往下执行,length == 2 && obj != null;继续往下执行,开始依次循环源对象,因源对象从第二个参数开始,所以index取值从1开始,在循环内部,获取源对象source,执行keysFunc方法获取source自身和原型上所有属性组成的数组,得到属性个数l;依次循环属性数组,!defaults始终为true,始终执行obj[key]=source[key]向目标对象赋值操作

  • _.extendOwn_.extend方法唯一不同的就是keysFunc函数,在_.extendOwn中,keysFunc_.keys方法,返回的是对象自身所有可枚举属性组成的数组,剩下的都和_.extend方法一样

  • _.defaults执行和上面两个都略有区别:在_.defaultscreateAssigner函数中keysFunc函数为_.allKeys方法,defaults置为true;执行createAssigner方法,第一步获取传入对象个数;因为defaults始终为true,第二步执行Object包装,此处可以兼容如果传入obj为空或者null的情况;如果参数个数<2则直接返回目标对象【注:此时obj不可能为null,因为即使原始传入的objnull,经过上一步操作,null也会转换为Object对象】;往下和_.extend方法一样,依次执行外层循环、内层循环,在内层循环的最后,因为!defaults始终为false,此处就是判断,如果obj[key] === void 0则赋值,否则如果该属性已经存在目标对象中,则不进行任何操作

_.extend || _.extendOwn || _.defaults方法通过调用createAssigner方法进行对象的浅拷贝扩展

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.