【问题标题】:What is context in _.each(list, iterator, [context])?_.each(list, iterator, [context]) 中的上下文是什么?
【发布时间】:2011-06-24 04:55:33
【问题描述】:

我是 underscore.js 的新手。 _.each() 中的[context] 的用途是什么?应该怎么用?

【问题讨论】:

    标签: javascript functional-programming underscore.js this


    【解决方案1】:

    _.each 的简单使用

    _.each(['Hello', 'World!'], function(word){
        console.log(word);
    });
    <script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>

    这里是simple example,可以使用_.each

    function basket() {
        this.items = [];
        this.addItem = function(item) {
            this.items.push(item);
        };
        this.show = function() {
            console.log('items: ', this.items);
        }
    }
    
    var x = new basket();
    x.addItem('banana');
    x.addItem('apple');
    x.addItem('kiwi');
    x.show();

    输出:

    items:  [ 'banana', 'apple', 'kiwi' ]
    

    而不是这样调用addItem 多次you could use underscore

    _.each(['banana', 'apple', 'kiwi'], function(item) { x.addItem(item); });
    

    这与使用这些项目依次调用addItem 三次相同。基本上,它会迭代您的数组,并为每个项目调用您调用x.addItem(item) 的匿名回调函数。匿名回调函数类似于addItem 成员函数(例如,它需要一个项目)并且有点毫无意义。因此,与其通过匿名函数,不如_.each 避免这种间接调用并直接调用addItem

    _.each(['banana', 'apple', 'kiwi'], x.addItem);
    

    但这不起作用,因为篮子内部的 addItem 成员函数 this 不会引用您创建的 x 篮子。这就是为什么您可以选择将您的篮子x 用作[context]

    _.each(['banana', 'apple', 'kiwi'], x.addItem, x);
    

    使用 _.each 和上下文的完整示例:

    function basket() {
        this.items = [];
        this.addItem = function(item) {
            this.items.push(item);
        };
        this.show = function() {
            console.log('items: ', this.items);
        }
    }
    var x = new basket();
    _.each(['banana', 'apple', 'kiwi'], x.addItem, x);
    x.show();
    <script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>

    简而言之,如果您以任何方式传递给_.each 的回调函数使用this,那么您需要在回调函数中指定this 应该引用的内容。在我的示例中,x 似乎是多余的,但 x.addItem 只是一个函数,可能与 xbasket or any other object, for example 完全无关:

    function basket() {
        this.items = [];
        this.show = function() {
            console.log('items: ', this.items);
        }
    }
    function addItem(item) {
        this.items.push(item);
    };
    
    var x = new basket();
    _.each(['banana', 'apple', 'kiwi'], addItem, x);
    x.show();
    <script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>

    换句话说,你可以在你的回调中绑定一些值到this,或者你也可以像这样直接使用bind

    _.each(['banana', 'apple', 'kiwi'], addItem.bind(x));
    

    此功能如何与一些不同的下划线方法一起使用?

    一般来说,如果某个underscorejs 方法采用回调函数,并且如果您希望在某个对象的某个成员函数(例如使用this 的函数)上调用该回调,那么您可以将该函数绑定到某个对象或将该对象作为[context] 参数传递,这是主要意图。在 underscorejs 文档的顶部,这正是他们所说的:The iteratee is bound to the context object, if one is passed

    【讨论】:

      【解决方案2】:

      上下文允许您在调用时提供参数,从而可以轻松自定义通用的预构建帮助函数。

      一些例子:

      // stock footage:
      function addTo(x){ "use strict"; return x + this; }
      function pluck(x){ "use strict"; return x[this]; }
      function lt(x){ "use strict"; return x < this; }
      
      // production:
      var r = [1,2,3,4,5,6,7,8,9];
      var words = "a man a plan a canal panama".split(" ");
      
      // filtering numbers:
      _.filter(r, lt, 5); // elements less than 5
      _.filter(r, lt, 3); // elements less than 3
      
      // add 100 to the elements:
      _.map(r, addTo, 100);
      
      // encode eggy peggy:
      _.map(words, addTo, "egg").join(" ");
      
      // get length of words:
      _.map(words, pluck, "length"); 
      
      // find words starting with "e" or sooner:
      _.filter(words, lt, "e"); 
      
      // find all words with 3 or more chars:
      _.filter(words, pluck, 2); 
      

      即使从有限的示例中,您也可以看到“额外参数”对于创建可重用代码的强大功能。您通常可以采用低级助手,而不是为每种情况制作不同的回调函数。目标是让您的自定义逻辑捆绑一个动词和两个名词,并使用最少的样板。

      诚然,箭头函数已经消除了泛型纯函数的许多“代码高尔夫”优势,但语义和一致性优势仍然存在。

      我总是将"use strict" 添加到助手中,以便在传递原语时提供本机[].map() 兼容性。否则,它们会被强制转换为对象,这通常仍然有效,但特定于类型会更快更安全。

      【讨论】:

        【解决方案3】:

        正如其他答案中所解释的,context 是在传递给each 的回调中使用的this 上下文。

        我将借助underscore source code的相关方法的源代码来解释这一点

        _.each_.forEach的定义如下:

        _.each = _.forEach = function(obj, iteratee, context) {
          iteratee = optimizeCb(iteratee, context);
        
          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;
        };
        

        这里要注意第二个声明

        iteratee = optimizeCb(iteratee, context);
        

        这里,context 被传递给另一个方法 optimizeCb,然后从它返回的函数被分配给稍后调用的 iteratee

        var optimizeCb = function(func, context, argCount) {
          if (context === void 0) return func;
          switch (argCount == null ? 3 : argCount) {
            case 1:
              return function(value) {
                return func.call(context, value);
              };
            case 2:
              return function(value, other) {
                return func.call(context, value, other);
              };
            case 3:
              return function(value, index, collection) {
                return func.call(context, value, index, collection);
              };
            case 4:
              return function(accumulator, value, index, collection) {
                return func.call(context, accumulator, value, index, collection);
              };
          }
          return function() {
            return func.apply(context, arguments);
          };
        };
        

        从上面optimizeCb的方法定义可以看出,如果context没有通过,那么func就原样返回。如果传递了context,回调函数被调用为

        func.call(context, other_parameters);
                  ^^^^^^^
        

        func 使用call() 调用,用于通过设置this 上下文来调用方法。所以,当thisfunc中使用时,它会引用context

        // Without `context`
        _.each([1], function() {
          console.log(this instanceof Window);
        });
        
        
        // With `context` as `arr`
        var arr = [1, 2, 3];
        _.each([1], function() {
          console.log(this);
        }, arr);
        &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"&gt;&lt;/script&gt;

        您可以将context 视为JavaScript 中forEach 的最后一个可选参数。

        【讨论】:

          【解决方案4】:

          contextthis 在迭代器函数中所指的位置。例如:

          var person = {};
          person.friends = {
            name1: true,
            name2: false,
            name3: true,
            name4: true
          };
          
          _.each(['name4', 'name2'], function(name){
            // this refers to the friends property of the person object
            alert(this[name]);
          }, person.friends);
          

          【讨论】:

            【解决方案5】:

            上下文参数只是在迭代器函数中设置this的值。

            var someOtherArray = ["name","patrick","d","w"];
            
            _.each([1, 2, 3], function(num) { 
                // In here, "this" refers to the same Array as "someOtherArray"
            
                alert( this[num] ); // num is the value from the array being iterated
                                    //    so this[num] gets the item at the "num" index of
                                    //    someOtherArray.
            }, someOtherArray);
            

            工作示例: http://jsfiddle.net/a6Rx4/

            它使用被迭代的数组的每个成员的数字来获取位于someOtherArray 的索引处的项目,它由this 表示,因为我们将它作为上下文参数传递。

            如果不设置上下文,那么this 将引用window 对象。

            【讨论】:

            • 这样做有什么好处?为什么不直接引用someOtherArray[num] 而不是this[num]
            • @csjacobs24:拥有一组无法访问局部变量范围的可重用函数是很常见的。这是一个简单的例子:jsfiddle.net/a6Rx4/745
            • 这个答案确实回答了这个问题,但如果它提供了如何有用的例子会更好。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2019-06-26
            相关资源
            最近更新 更多