【问题标题】:Lexical Scoping parameters in javascriptjavascript中的词法范围参数
【发布时间】:2013-07-11 23:55:04
【问题描述】:

通过 Javascript Koans 工作,我被以下代码挂断了:

    it("should use lexical scoping to synthesise functions", function () {

function makeMysteryFunction(makerValue)
{
  var newFunction = function doMysteriousThing(param)
  {
    return makerValue + param;
  };
  return newFunction;
}

var mysteryFunction3 = makeMysteryFunction(3);
var mysteryFunction5 = makeMysteryFunction(5);

expect(mysteryFunction3(10) + mysteryFunction5(5)).toBe(FILL_ME_IN);
});

它得到 23,这很好。我感到困惑的是,赋予“mysteryFunction3”变量的参数如何/为什么作为“param”传递给 doMysteriousThing 函数。

如果有一个内部函数和一个外部函数,每个函数都有一个参数,那么在给定指定参数的情况下定义一个等于外部函数的变量,例如:

    var mysteryFunction3 = makeMysterFunction(3);

将使其向外部函数的变量实例发送参数,例如:

   mysteryFunction3(10)

会导致该参数 (10) 被读取为内部函数的参数吗?

【问题讨论】:

  • JavaScript 函数在最初定义它们的变量范围上形成一个闭包。 doMysteriousThing 函数是在传递 makerValue 的变量范围内创建的。这使该函数能够引用makerValue,即使该函数已返回并分配给您的mysteryFunction3。如您所见,它在其返回值return makerValue + param; 中使用它,其中makerValue 再次被传递给makeMysteryFunction()param 被传递给doMysteriousThing()
  • 如果同一个函数没有 3 个不同的名称,可能不会那么混乱。 newFunctiondoMysteriousThingmysteryFunction3 都指的是同一个函数。为了清理它,我会去掉 newFunctiondoMysteriousThing 的名字,只做 return function(param) { return makerValue + param; };

标签: javascript lexical-scope


【解决方案1】:

一开始我也很难理解这一点。这就是我如何通过一击一击达到清晰。这是我在堆栈上的第一篇文章,请原谅我对此啰嗦。

让我们来看一个返回字符串的非常基本的函数:

function fun() {
       return 'Hi!';
    }

如果我们在没有调用括号的情况下记录上述函数,控制台只会记录对该函数的引用:

console.log(fun); //logs ---> [Function: fun] 

如果我们再次记录它,但带有调用括号:

console.log(fun()); //logs ---> Hi!

函数的调用等于它的返回值:

console.log(fun() === 'Hi!'); //logs ---> true

因此,在此基础上,让我们重写我们的函数,在其中声明另一个返回字符串的函数。外部函数将返回内部函数的调用:

function funAgain() {
   function innerFun() {
     return 'Hello there!';
   }
   return innerFun();
}

因此,在 funAgain (innerFun() === 'Hello there!') 的范围内,计算结果为 true,因此当我们将 funAgain 调用记录到控制台时:

console.log(funAgain()); //logs ---> 'Hello there!'

但是如果我们在外部函数的 return 语句中去掉调用 innerFun 的括号呢?

function funAgain() {
  function innerFun() {
    return 'Hello there!';
  }
  return innerFun;
}

console.log(funAgain()); //logs [Function: innerFun]

返回函数 ITSELF。虽然这实际上不是全部,但我们可以想到 (funAgain() === innerFun) 显然,由于范围问题,您实际上无法在实践中运行此比较(innerFun 不能存在于 funAgain 的调用之外) . 但!让我们这样想一下。这意味着如果我们在一个变量中捕获 funAgain 的返回值:

var innerFunCaptured = funAgain();

console.log(innerFunCaptured); // logs [Function: innerFun]

从概念上讲,我们有 (innerFunCaptured === innerFun) ...

既然我们的变量绑定到内部函数,我们可以通过给变量添加括号来调用该内部函数。

console.log(innerFunCaptured()); //logs ---> 'Hello there!'

当我在上面谈论“整个故事”时,我遗漏的是内部函数与变量的绑定是外部函数调用的结果,所以实际上绑定不仅包括 innerFun 本身,还包括它在其中创建的环境包括通过调用外部函数传递的任何潜在参数,这使我们能够...

再次重写外部函数和内部函数,使它们现在具有交互的参数:

function funOnceMore(greetingPartOne) {
  function innerFun(greetingPartTwo) {
    return greetingPartOne + ' ' + greetingPartTwo;
  }
  return innerFun;
}

如果我们用参数记录 funOnceMore 会怎样。

console.log(funOnceMore('Hello')) //logs ---> [Function: innerFun]

再次返回 innerFun 本身。但是我们传递的参数 greetingPartOne 呢?好吧,它通过了,但是由于在 funOnceMore 中从未调用过 innerFun,因此从未以任何有意义的方式使用 greetingPartOne。我们必须弄清楚如何调用 innerFun!答案是:我们需要像上一步那样将它绑定到一个变量。

var innerFunCapturedAgain = funOnceMore('Hello')

现在 innerFunCapturedAgain 保存了 innerFun 和 funOnceMore 的环境以及我们传递给它的参数“Hello”。

所以现在我们可以通过在innerFunCapturedAgain 上加上括号来调用innerFun,这些括号将封装我们传递给innerFun 的greetingPartTwo 的参数。

console.log(innerFunCapturedAgain('there!')) //logs ---> 'Hello there!'

【讨论】:

    【解决方案2】:

    这两个答案都非常有帮助,但我自己正在努力解决这个问题,我认为最好的答案是了解正在发生的事情并对其进行更改以对其有新的认识:

    makeMysteryFunction 创建一个函数,将其参数 (makerValue) 添加到传递给它返回的函数 (mysteryFunctionX) 的参数中。

    所以,帮助我理解的是将数字转换为字符串:

    function makeGreeting(greeting)
    {
      var newFunction = function greet(name)
      {
        return greeting + ' ' + name;
      };
      return newFunction;
    }
    
    var makeGreetingHi = makeGreeting('Hi');
    var makeGreetingHello = makeGreeting('Hello');
    //finally call functions
    makeGreetingHi('Stranger');
    //Hi Stranger
    makeGreetingHello('Friend');
    //Hello Friend
    

    我所做的只是更改函数的名称,并连接字符串而不是添加数字。让公案令人困惑的是函数名称:猜猜这是不良做法的一个很好的例子。练习的重点在于,在我提供的示例中,greet 函数可以访问greeting。不幸的是,这在命名法中丢失了

    【讨论】:

      【解决方案3】:

      我刚刚发现对理解这里到底发生了什么非常有用的东西是添加一个 console.log 来显示“mysteryFunction3”的内容。所以:

      var mysteryFunction3 = makeMysteryFunction(3);
      var mysteryFunction5 = makeMysteryFunction(5);
      
      console.log(mysteryFunction3);
      
      expect(mysteryFunction3(10) + mysteryFunction5(5)).toBe(FILL_ME_IN);
      

      控制台输出是这样的:

      doMysteriousThing(param)
        {
          return makerValue + param;
        }
      

      来自 C# 背景,这太疯狂了。但至少我现在明白了!

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2023-04-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-01-23
        • 2017-12-07
        • 1970-01-01
        相关资源
        最近更新 更多