我得到了一项作业,我有2个解决方案:一个使用this,另一个不使用。

我在jsPerf上测试了这两个解决方案,但有时它说this的版本更快或更另外一个。因此,我认为我不能真正依靠jsPerf。无论如何,性能应该始终是头等大事吗?

顺便说一下,这是问题陈述:


编写一个从许多调用中添加的函数addg直到看到一个空的调用。

类似的东西:

addg(); //未定义

addg(2)(); // 2

addg(2)(7)(); // 9

addg(3)(0)(); // 3

addg(1)(2)(4)(8)(); // 15

addg(5)(4)(-2)(); // 7


这是我的2个解决方案:


this的版本:

 function add(a,b){
  return a+b;
}

function addg(val){
  var numbers = [];

  this.addToCollection = function(val){
    numbers.push(val);

    if(typeof val !== 'number'){  // in case of ()
      var sum = 0, 
          l = numbers.length;

      numbers.forEach(function(num, i){
        if(i === l-1){
          return;
        } else {
          sum = add(sum, num);
        }
      });

//       console.log(numbers);

      if(numbers.length === 1 && numbers[0] === undefined){
        return undefined;
      } else {
        return sum;        
      }

    } else {  // in case of (n)
      return this.addToCollection;  // return the function each time so you can apply it to other numbers in the chain      
    }

  };

  return this.addToCollection(val);
}

console.log(addg());                // undefined
console.log(addg(2)());             // 2
console.log(addg(2)(7)());          // 9
console.log(addg(3)(0)());          // 7
console.log(addg(1)(2)(4)(8)());    // 15
console.log(addg(5)(4)(-2)());      // 7 






不带this的版本:




 function add(a,b){
  return a+b;
}

function addg(val){
  var numbers = [];

  function addValues(val){
    numbers.push(val);

    if(typeof val !== 'number'){  // in case of ()

      var sum = 0, 
          l = numbers.length;

      numbers.forEach(function(num, i){
        if(i === l-1){
          return;
        } else {
          sum = add(sum, num);
        }
      });

//       console.log(numbers);

      if(numbers.length === 1 && numbers[0] === undefined){
        return undefined;
      } else {
        return sum;        
      }

    } else {  // in case of (n)
      return addValues; // return the function each time so you can apply it to other numbers in the chain 
    }
  }

  return addValues(val);

}

console.log(addg());                // undefined
console.log(addg(2)());             // 2
console.log(addg(2)(7)());          // 9
console.log(addg(3)(0)());          // 7
console.log(addg(1)(2)(4)(8)());    // 15
console.log(addg(5)(4)(-2)());      // 7 





我想知道哪个版本更好,为什么。如果您对改进我的代码有任何建议,请随时告诉我。

评论

您在第一个实现中使用此方法是错误的。实际上,如果在严格模式下运行代码,它甚至将无法工作。不处于严格模式时,此===浏览器中的窗口。因此,您仅使用window.addToCollection,这并不是一种特别好的处理方法。如果使用新的SomeFunc(),它将被设置为一个对象。

现在,将数字求和的数组有点过大了,不是吗?只需保持连续运行即可。

太酷了,前段时间我做了一个非常相似的脚本来娱乐,它可能会帮助您缩短代码,因为它对我来说太过复杂了

@FranciscoPresencia。嘿,看起来和我说的差不多:)

@FranciscoPresencia您的代码中有一个小问题。当您调用sum函数时,最后应该有一个空的调用。例如sum(1)(2)(3)(4)(5)()。为此,我看到它正在返回NaN。

#1 楼

最重要的是:


无论如何,性能应该始终是头等大事吗?不要写不必要的事情,而只有在有理由的时候才过度关注性能。在大多数情况下,JS执行时间是无关紧要的,因为您正在等待用户交互,网络请求或其他使JS执行时间不重要的事情。

与性能相比,我更关心编写清晰,表达性和可维护性强的代码。


关于this的用法,您绝对不应该像在第一个示例中那样使用this。您正在渗入全球范围。当addg运行时,this是什么?如果您呼叫obj.addg(),则this将为obj,但是由于您只是呼叫addg(),因此this是全局范围A.K.A。 window(在浏览器中)。

在您的第一个版本中,如果您在控制台中运行它,将会看到:

addg(); 
//undefined
addToCollection;
//function!  Whoops


你想要什么。您不需要在任何地方分配内部的addToCollection函数,因此也不需要。


add函数有点毫无意义。在()的情况下,您要在数字数组中添加undefined,然后再添加特殊逻辑以将其从总和中排除,但相反,您根本不应该添加undefined。仅当numbers.push(val)时才使用typeof val === 'number'

评论


\ $ \ begingroup \ $
哇,我搞砸了:D谢谢您清除我对此的理解。
\ $ \ endgroup \ $
–拉胡尔·德赛(Rahul Desai)
2015年12月4日在3:19



\ $ \ begingroup \ $
作为一种知道何时被滥用的方法,“严格使用”;帮助。
\ $ \ endgroup \ $
– Darkhogg
2015年12月4日13:52



\ $ \ begingroup \ $
最好与新的构造函数一起使用,并且只有在这种情况下才会指向构造函数的实例
\ $ \ endgroup \ $
– Zorgatone
2015年12月6日,12:22

#2 楼

您不需要花哨的逻辑。
这里很好的旧闭包就足够了。
它将存储我们的当前计数。
返回的值是一个新函数,将其添加到计数器并返回自身,从而保留了上下文,直到没有参数为止。
效率高,因为每条链都只创建一个上下文和一个函数,而无论深度如何。 /> eg
function addg ( left ) {
   if ( left === undefined ) return undefined;
   return function addg_chain ( right ) {
      return right === undefined ? left : ( left += right, addg_chain );
   };
}

如果需要此功能,解决方案也很简单:
为每个调用使用新函数,新闭包。
(感谢200_success的支持新代码。)
var nine = addg(5)(4);
var ten = nine(1)(); // 10
var twelve = nine(3)(); // Surprise, it is 13!

每个步骤创建一个新函数显然比重用同一函数要低得多。
所以, d要保留状态,第一个代码应该足够。
尽管如此,闭包仍然比使用对象属性更简单,并且担心left,您不这样认为吗?如果你同意。 :)

一些程序员评论说这些代码很难理解。
我向您保证,不像200_success的解决方案那样将它们放在同一行对我来说也是痛苦的,因为我的代码压缩容限也很高。
我希望在太冗长和太紧凑之间取得平衡。
感谢您的理解。


评论


\ $ \ begingroup \ $
您的第二个解决方案似乎太复杂了。我想出了函数addg(a){return(a === undefined)? a:function(b){return(b === undefined)吗? a:addg(a + b)}}
\ $ \ endgroup \ $
– 200_success
2015年12月4日在7:57

\ $ \ begingroup \ $
这当然是意见,但这些解决方案对我来说似乎难以理解。
\ $ \ endgroup \ $
– Darkhogg
2015年12月4日13:54

\ $ \ begingroup \ $
此解决方案是纯意大利面。
\ $ \ endgroup \ $
– abuzittin gillifirca
2015年12月4日15:43

\ $ \ begingroup \ $
@Darkhogg:这只是意见,您的观点是错误的。 :-)认真的说,如果您采用200_success的解决方案并对其进行正确的格式化,则它具有明显的美感,并且(对于经验丰富的程序员)和建议的替代方案更具可读性。
\ $ \ endgroup \ $
–马尔沃里奥
2015年12月4日在17:51

\ $ \ begingroup \ $
@abuzittingillifirca父母的解决方案或200_success的优雅解决方案几乎没有意大利面。您应该阅读以下内容:steve-yegge.blogspot.com/2008/02/portrait-of-n00b.html
\ $ \ endgroup \ $
–乔纳
2015年12月5日,下午1:31

#3 楼

您在第一个实现中使用this是错误的。实际上,如果在严格模式下运行代码,它甚至将无法工作。如果不在严格模式下,请在浏览器中。因此,您只是使用this === window,这并不是一种特别好的处理方法。

如果使用window.addToCollection运算符调用构造函数,或者使用thisnew或调用.apply(),则.call()将获得有意义的值。否则,它将只是全局对象(在常规模式下)或obj.method()在严格模式下。

我建议这种简化方法是使用函数闭包存储和的状态并返回内部允许链接的功能。这是一种类似于OO的解决方案,其中undefined创建一个函数对象,该函数对象将当前总和存储在私有闭包中,而addg()是返回的函数上的f()是提取总和的一种方式,而f是向其求和的方式,并且支持链接: />



 f(val) 





这在Javascript中使用了一个非常有用的功能,称为“关闭”。当function addg(val) { var sum = 0; // if you really want addg() all by itself to return undefined // instead of 0, you can uncomment this next line // if (typeof val === "undefined") return undefined; function sumNext(val) { if (typeof val === "undefined") { return sum; } else { sum += val; return sumNext; } } return sumNext(val); } log(addg()); log(addg(2)()); log(addg(2)(7)()); log(addg(3)(0)()); log(addg(1)(2)(4)(8)()); log(addg(5)(4)(-2)()); // logging function function log(x) { var div = document.createElement("div"); div.innerHTML = x; document.body.appendChild(div); }返回内部函数addg()时,即使sumNext()已经完成执行,sumNext中的代码仍保留对sum变量的访问。这使您可以在不使用任何全局变量的情况下通过连续调用addg()来私下累积和。无论如何,性能应始终是头等大事吗? />否,性能永远不是重中之重。我倾向于优先处理以下事情:



正确(产生期望的结果)

可靠(总是产生期望的结果)

健壮(适当地处理错误,并且当周围的事物发生变化时不易碎)

可读且可维护(尝试编写别人的代码,甚至在编写代码3年后再使用自己的代码)

适当的性能(大多数代码无需针对性能进行优化)

有时候,您一直在追求性能,但是即使如此,您也不会忘记其优先级。


这是第二个版本,而不是闭包使用返回函数的属性。相同的结果,不同的处理方式。这实际上将sumNext()属性公开为公共属性,而第一个版本将sum隐藏在闭包中。




 sum 





此版本编码为为function addg(val) { function sumNext(val) { if (typeof val === "undefined") { return sumNext.sum; } else { sumNext.sum = (sumNext.sum || 0) + val; return sumNext; } } return sumNext(val); } log(addg()); log(addg(2)()); log(addg(2)(7)()); log(addg(3)(0)()); log(addg(1)(2)(4)(8)()); log(addg(5)(4)(-2)()); // logging function function log(x) { var div = document.createElement("div"); div.innerHTML = x; document.body.appendChild(div); }返回undefined,但可以根据需要轻松修改为返回addg()(对我来说似乎更合逻辑)。

评论


\ $ \ begingroup \ $
typeof val ===“未定义” || !arguments.length是不是!arguments.length部分多余?如果未传递任何参数,则val将是未定义的。
\ $ \ endgroup \ $
– Peter Olson
2015年12月4日下午5:56

\ $ \ begingroup \ $
@PeterOlson-是的,这很多余-我删除了它。
\ $ \ endgroup \ $
– jfriend00
15年12月4日在6:06

\ $ \ begingroup \ $
只是一点点挑剔,您的解决方案是不正确的。 addg()产生0而不是预期的undefined。我更好地喜欢您,我认为这样做是有道理的,但是应该指出并也许解释为什么我会更好。
\ $ \ endgroup \ $
– Tibos
15年12月4日在6:10

\ $ \ begingroup \ $
@Bergi-您所说的副作用似乎是练习的重点。这将创建一个在内部累积总计的功能对象。每次调用时,它都会添加到先前的总数中。任何时候调用它没有值,它都会返回内部存储的总数。就像x(val)是x.add(val)和x()是x.getCurrentSum()一样。您在OP的问题描述中是否看到任何错误或错误的描述?是的,有一种替代设计的行为有所不同,但是我不明白为什么这是必需的或更理想的。
\ $ \ endgroup \ $
– jfriend00
2015年12月4日在15:42

\ $ \ begingroup \ $
@Bergi-我修改了此解决方案的描述,以描述是OO解决方案,其中addg()创建一个函数对象,该函数对象将求和状态存储在私有闭包中,并且基本上有两种方法(一种用于检索当前对象总和和一个加法)。希望可以更清楚地说明这是一种预期的OO设计,该设计的行为与使用两种方法的对象一致,而不是不良的副作用。
\ $ \ endgroup \ $
– jfriend00
2015年12月4日在16:04



#4 楼

我想告诉您这两个版本看起来都不错,但是这里有一些German Overengineering™。 br />
要验证是否传递了值,可以使用arguments.length或检查value是否为undefined
您应该优先选择value === undefined而不是arguments.length,因为它可以帮助限制某些优化。 />
但是现在我们遇到了一个问题:undefined是一个全局变量,任何人都可以随时设置。
您可以在代码之前通过'use strict';或在内部声明var undefined;来解决该问题。
我更喜欢后者,因为它可以运行window.undefined = 5;,这将导致您间接检查并返回5
我仍然建议添加'use strict';,因为它还添加了其他基本检查和安全功能。 />
这是我的简化版本:

 'use strict';
function addg(value) {
    var undefined;

    if(value == undefined)
    {
        return undefined;
    }

    var total = value / 1 || 0;

    var fn = function(value){
        if(value == undefined)
        {
            return total;
        }

        total += value / 1 || 0;

        return fn;
    };

    return fn;
}
 


P练习绳索号是一项练习。


毕竟,我的回答是:都不!简化它。

评论


\ $ \ begingroup \ $
arguments.callee无法在严格模式下工作,通常不建议这样做,因为它限制了解释程序可以执行的某些优化。
\ $ \ endgroup \ $
– jfriend00
2015年12月4日,下午3:09

\ $ \ begingroup \ $
道格拉斯·克罗克福德(Douglas Crockford)建议不要使用参数。
\ $ \ endgroup \ $
–拉胡尔·德赛(Rahul Desai)
2015年12月4日,下午3:13

\ $ \ begingroup \ $
@ jfriend00完全忘记了。我已将其添加到答案中。
\ $ \ endgroup \ $
–伊斯梅尔·米格尔(Ismael Miguel)
2015年12月4日,下午3:14

\ $ \ begingroup \ $
@RahulDesai我很抱歉,但我不知道谁是道格拉斯·克罗克福德。但是,如果您愿意,我可以提供不带参数的更完整版本。只是它极大地简化了您的工作。
\ $ \ endgroup \ $
–伊斯梅尔·米格尔(Ismael Miguel)
2015年12月4日,下午3:15

\ $ \ begingroup \ $
不要做var undefined。这是不必要的,有点令人困惑,并且可以防止实际上不应该发生的情况。
\ $ \ endgroup \ $
–瑞信
2015年12月4日在4:22

#5 楼

只是为了给我戴上帽子,我会建议这样:您需要的只是先前的结果。哪个需要关闭。

从技术上讲,您可能会说调用addg(undefined)不是一个空调用:您正在传递undefined,但它被视为没有传递任何内容。您可以改为:

function addg(value) {
  var sum;

  function adder(value) {
    if(typeof value === 'undefined') return sum;
    sum = (sum || 0) + value;
    return adder;
  };

  return adder(value);
}


技术上,(sum || 0)会在falseNaN和其他错误值上跳闸,例如addg(3)(Number.NaN)(3)()会返回3,由于3 + NaN => NaN,但是NaN被零替换,这意味着您得到0 + 3。因此,如果有问题,请执行以下操作:

function addg(value) {
  var sum;

  function adder(value) {
    if(!arguments.length) return sum;
    sum = (sum || 0) + value;
    return adder;
  };

  return adder.apply(null, [].slice.call(arguments));
}


#6 楼

由于@Retsam在解释this的工作方面做得很好,因此我想展示一种更简单的创建addg的方法,该方法非常快。
一些示例:

function addg( arg ) {
    // If arg cannot be interpreted as a number then process the data collected.
    // Note: Can change to ->
    // if(arg === undefined) {
    // or if need to just look for empty arguments...
    // if(!arguments.length) {
    if(isNaN(arg = +arg)) {
        // Temp storage.
        var sum = addg.sum;
        // Resets the function.
        // Can also do ->
        // addg.sum = undefined;
        delete addg.sum;
        // Returns the sum collected.
        return sum;
    }
    // Checks to see if addg has sum yet.
    addg.sum = (addg.sum) ? addg.sum + arg : arg;
    // Returns the function for more args.
    return addg;
};


评论


\ $ \ begingroup \ $
多次调用addg(1)(这将返回函数)。然后调用addg(1)()。你得到了什么?
\ $ \ endgroup \ $
–章鱼
2015年12月4日23:06

\ $ \ begingroup \ $
@章鱼。您得到所需的行为。这个问题说:“编写一个函数addg,它从许多调用中添加,直到看到一个空的调用为止。”因此,如果您调用函数addg(1)然后addg(1)(),您应该得到2,因为应该将addg累加每次调用,直到发生空调用: :)
\ $ \ endgroup \ $
–tkellehe
2015年12月5日,1:50