有关Js变量作用域与闭包的七个问题

2,122 阅读4分钟

1.函数会选择最新的内容吗?

函数 sayHi 使用外部变量。当函数运行时,将使用哪个值?

let name = "John";

function sayHi() {
  alert("Hi, " + name);
}

name = "Pete";

sayHi(); // 会显示什么:"John" 还是 "Pete"?

这种情况在浏览器和服务器端开发中都很常见。一个函数可能被计划在创建之后一段时间后才执行,例如在用户行为或网络请求之后。

因此,问题是:它会接收最新的修改吗?


2.哪些变量可用呢?

下面的 makeWorker 函数创建了另一个函数并返回该函数。可以在其他地方调用这个新函数。

它是否可以从它被创建的位置或调用位置(或两者)访问外部变量?

function makeWorker() {
  let name = "Pete";

  return function() {
    alert(name);
  };
}

let name = "John";

// create a function
let work = makeWorker();

// call it
work(); // 会显示什么?

会显示哪个值?“Pete” 还是 “John”?


3.Counter 是独立的吗?

在这儿我们用相同的 makeCounter 函数创建了两个计数器(counters):counter 和 counter2

它们是独立的吗?第二个 counter 会显示什么?0,1 或 2,3 还是其他?

function makeCounter() {
  let count = 0;

  return function() {
    return count++;
  };
}

let counter = makeCounter();
let counter2 = makeCounter();

alert( counter() ); // 0
alert( counter() ); // 1

alert( counter2() ); // ?
alert( counter2() ); // ? 

4.Counter 对象

这里通过构造函数创建了一个 counter 对象。

它能正常工作吗?它会显示什么呢?

function Counter() {
  let count = 0;

  this.up = function() {
    return ++count;
  };
  this.down = function() {
    return --count;
  };
}

let counter = new Counter();

alert( counter.up() ); // ?
alert( counter.up() ); // ?
alert( counter.down() ); // ?

5.if 内的函数

看看下面这个代码。最后一行代码的执行结果是什么?

let phrase = "Hello";

if (true) {
  let user = "John";

  function sayHi() {
    alert(`${phrase}, ${user}`);
  }
}

sayHi(); 

6.闭包 sum

编写一个像 sum(a)(b) = a+b 这样工作的 sum 函数。

是的,就是这种通过双括号的方式(并不是错误)。

举个例子:

sum(1)(2) = 3
sum(5)(-1) = 4

7.变量可见吗?

下面这段代码的结果会是什么?

let x = 1;

function func() {
  console.log(x); // ?

  let x = 2;
}

func();

P.S. 这个任务有一个陷阱。解决方案并不明显。


答案及解析

1.答案:Pete

函数将从内到外依次在对应的词法环境中寻找目标变量,它使用最新的值。

旧变量值不会保存在任何地方。当一个函数想要一个变量时,它会从自己的词法环境或外部词法环境中获取当前值。


2.答案:Pete.

下方代码中的函数 work() 在其被创建的位置通过外部词法环境引用获取 name

image.png 所以这里的结果是 "Pete"

但如果在 makeWorker() 中没有 let name,那么将继续向外搜索并最终找到全局变量,正如我们可以从上图中看到的那样。在这种情况下,结果将是 "John"


3.答案:0,1。

函数 counter 和 counter2 是通过 makeCounter 的不同调用创建的。

因此,它们具有独立的外部词法环境,每一个都有自己的 count


4.当然行得通。

这两个嵌套函数都是在同一个词法环境中创建的,所以它们可以共享对同一个 count 变量的访问:

function Counter() {
  let count = 0;

  this.up = function() {
    return ++count;
  };

  this.down = function() {
    return --count;
  };
}

let counter = new Counter();

alert( counter.up() ); // 1
alert( counter.up() ); // 2
alert( counter.down() ); // 1

5.答案:error

函数 sayHi 是在 if 内声明的,所以它只存在于 if 中。外部是没有 sayHi 的。


6.了使第二个括号有效,第一个(括号)必须返回一个函数。

就像这样:

function sum(a) {

  return function(b) {
    return a + b; // 从外部词法环境获得 "a"
  };

}

alert( sum(1)(2) ); // 3
alert( sum(5)(-1) ); // 4

7.答案:error

你运行一下试试:

let x = 1;

function func() {
console.log(x); // ReferenceError: Cannot access 'x' before initialization
  let x = 2;
}

func();

在这个例子中,我们可以观察到“不存在”的变量和“未初始化”的变量之间的特殊差异。

你可能已经在 变量作用域,闭包中了解到了,从程序执行进入代码块(或函数)的那一刻起,变量就开始进入“未初始化”状态。它一直保持未初始化状态,直至程序执行到相应的 let 语句。

换句话说,一个变量从技术的角度来讲是存在的,但是在 let 之前还不能使用。

下面的这段代码证实了这一点。

function func() {
// 引擎从函数开始就知道局部变量 x,
  // 但是变量 x 一直处于“未初始化”(无法使用)的状态,直到结束 let(“死区”)
  // 因此答案是 error

  console.log(x); // ReferenceError: Cannot access 'x' before initialization

  let x = 2;
}

变量暂时无法使用的区域(从代码块的开始到 let)有时被称为“死区”。