【问题标题】:Test whether variable declared yet in containing scope测试变量是否在包含范围内声明
【发布时间】:2018-11-11 20:48:39
【问题描述】:

我有一个内部函数,它引用了一个由其包含的外部函数初始化的变量:

function outer() {
    function inner() {
      if (foo) { ... }
    }
    let foo = 'bar';
    inner();  // no error
};

但是,在某些情况下,可能会在执行定义foo 的语句之前调用内部函数。在这种情况下,任何对 foo 的引用都会在内部函数中导致 ReferenceError:

function outer() {
    function inner() {
      if (foo) { ... } // ReferenceError: foo is not defined
    }
    inner();
    let foo = 'bar';
};

这让我感到惊讶,因为foo 具有块作用域,并且在执行内部函数时我位于封闭块内。

更令人惊讶的是,即使尝试使用 typeof 运算符检测这种情况(我一直认为这是测试未定义变量的安全方法)也会导致相同的 ReferenceError:

function outer() {
    function inner() {
      if (typeof foo !== 'undefined') { ... } // ReferenceError: foo is not defined
    }
    inner();
    let foo = 'bar';
};

编辑:我现在明白这种行为是涉及letconst 变量discussedelsewhere 的“时间死区”的结果。但是,我仍在寻找一种干净、安全的方法来处理这种情况。

是否有任何安全的方法来测试块范围的变量(例如,使用 'let' 或 'const' 创建的变量)是否已达到其声明?

【问题讨论】:

  • 使用 this 关键字 => this.foo 来确定变量是否声明。
  • @NattamaiJawaharlalManikandan property this.foo 将与此范围内定义的 variable 完全不同。
  • "是否有任何安全的方法来测试块范围的变量是否已经初始化?" - 不,不是,除了try/@987654334 @。但是你需要这个做什么?要么你想使用这个变量,要么你不想。如果由于您在初始化之前尝试访问它而引发错误,请不要解决它,而只需修复您的错误 - 在最后一行调用 inner()
  • @Bergi 在“真实”代码中需要这个的原因是因为 inner() 是一个错误处理函数,而 outer() 是一个请求处理函数。在 inner() (错误处理函数)中,我想收集由 outer() 收集的所有上下文信息,以便将其写入错误响应。 foo,在现实世界中,是一个将输入值解析为outer()的结果的变量,但在某些情况下,我们在foo被初始化之前就遇到了错误。因此,可能必须在 foo 初始化之前调用 inner()。
  • @MykWillis 你能举例说明你是如何检测错误的吗?是try-catch吗?我不清楚innerfoo 的确切范围是什么,它们不会嵌套在不同的块中吗?同样对于您所描述的内容,我建议您简单地使用 var (或将 let 声明放在您的范围顶部)。

标签: javascript typeof


【解决方案1】:

一种蛮力方法是在foo 初始化之前,在“临时死区”中简单地捕获typeof 引发的异常:

function outer() {
    function inner() {
      let fooExists = false;
      try { fooExists = typeof foo !== 'undefined' } catch(e) {}
      if (fooExists) { /* ... */ }
    }

    inner();   // no error
    let foo = 'bar';
}

也可以使用var 代替let 来解决此问题。因为var 是函数范围的,所以声明将被提升到outer 的顶部,使变量在外部执行时始终可用(尽管未定义):

function outer() {
    // due to hoisting, there is a logical `var foo;` here
    function inner() {
      if (typeof foo !== 'undefined') { /* ... */ }
    }

    inner();   // no error; `foo` has been hoisted
    var foo = 'bar';
}

可以采取类似的方法,将foo 的声明(但不是初始化)放在外部函数的顶部,同时仍然使用let

function outer() {
    let foo;
    function inner() {
      if (typeof foo !== 'undefined') { /* ... */ }
    }

    inner();   // no error
    foo = 'bar';
}

对于问题中给出的示例,最后一个解决方案似乎是最干净的解决方案。很遗憾,在使用const 变量时无法使用。

【讨论】:

    【解决方案2】:
    function outer() {
        function inner() {
          if (foo) { ... } // ReferenceError: foo is not defined
        }
        inner();
        let foo = 'bar';
    };
    

    这让我感到惊讶,因为 foo 具有块作用域,而我是 执行内部函数时在封闭块内。

    由于提升,foo声明 被提升到块的顶部,而不是 初始化 的变量。它就像你写的一样执行:

    function outer() {
        let foo; // declaration only, so still undefined value
        function inner() {
          if (foo) { ... } // ReferenceError: foo is not defined
        }
        inner();
        foo = 'bar'; // Not initialized until after you call inner!
    };
    

    只需将初始化在块中向上移动即可:

    function outer() {
        let foo = 'bar'; // declared and initialized
        function inner() {
          if (foo) { ... } // works as expected
        }
        inner();
    };
    

    【讨论】:

    • foo 的声明显然没有在逻辑上移到 outer() 函数的顶部(因为如果是这样,typeof foo 将毫无例外地返回“未定义”)。如果我使用var 定义foo,这将是的行为,但在let(和const)的情况下显然不是这样。例如,stackoverflow.com/a/31222689/925478
    • 至于将初始化移动到outer()的顶部,不幸的是,在我拥有的实际生产代码中这是不可能的,它基于仅在之后执行的函数调用初始化foo一堆预处理工作。例如,直到函数的最底层,我们才知道它应该初始化的值。
    • “它的执行就像你写的一样” - 这是不正确的。一个会出错,另一个不会。
    • @MykWillis foo 的声明被移动到你的链接以及here 引用的块的顶部(提升)。只是let 作用域变量有一个“临时死区”,它阻止在初始化之前访问它们。
    • @SpencerWieczorek 我的意思是它在逻辑上处理就好像它是按照我展示的方式编写的。换句话说,它被提升了。它抛出错误的事实并不排除提升。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-02-03
    • 2016-09-18
    • 2017-02-12
    • 1970-01-01
    • 1970-01-01
    • 2016-03-20
    • 2015-05-26
    相关资源
    最近更新 更多