乐趣区

关于程序员:一文彻底搞懂JS作用域

JavaScript 的作用域是一个十分根底且重要的概念,对于初学者来说,常常会感觉有些凌乱搞不清楚。本文会具体介绍 JavaScript 作用域,包含全局作用域、函数作用域和块级作用域,以及 ES6+ 新增的 letconstblock scope等个性,让你彻底搞懂作用域。

1. JavaScript 作用域简介

在 JavaScript 中,作用域是指在代码中定义变量的区域。这个区域定义了变量的可见性和生命周期。JavaScript 有两种次要的作用域类型:全局作用域和函数作用域。

var globalVar = "I'm global!"; // 全局作用域

function someFunction() {
  var functionVar = "I'm local!"; // 函数作用域
  console.log(globalVar); // 能够拜访全局变量
  console.log(functionVar); // 能够拜访函数内的变量
}

console.log(globalVar); // 能够拜访全局变量
console.log(functionVar); // 报错,不能拜访函数内的变量

2. 全局作用域

在代码的任何中央都能拜访到的变量被定义在全局作用域。在浏览器环境中,全局变量被定义在 window 对象上。

3. 函数作用域

在函数外部定义的变量只能在函数外部拜访,这就是函数作用域。这意味着,如果你在一个函数外部定义了一个变量,那么这个变量在函数内部是不可见的。

4. ES6+ 块级作用域

ES6 引入了两种新的申明形式:letconst,它们与var 相比,最大的区别就是它们具备块级作用域。块级作用域是指变量在最近的 {} 代码块内无效。

if (true) {
  var varVar = "I'm var!"; // 函数作用域
  let letVar = "I'm let!"; // 块级作用域
  const constVar = "I'm const!"; // 块级作用域
}

console.log(varVar); // 输入 "I'm var!"
console.log(letVar); // 报错,letVar 未定义
console.log(constVar); // 报错,constVar 未定义

5. letconst

letconst 都是块级作用域,它们的作用范畴被限度在最近的一对花括号 {} 内。let容许你从新赋值,而 const 定义的是一个常量,一旦赋值就不能扭转。

let letVar = "I'm let!";
letVar = "I've changed!"; // 没有问题

const constVar = "I'm const!";
constVar = "I'm trying to change!"; // 报错,不能扭转 const 变量的值

6. 作用域链

当你在一个函数外部尝试拜访一个变量时,JavaScript 会首先在以后的作用域查找。如果没有找到,它会去外层的作用域查找,直到找到为止。这就是作用域链。

var outerVar = "I'm outer!";

function innerFunction() {console.log(outerVar); // 输入 "I'm outer!"
}

innerFunction();

7. 闭包

闭包是 JavaScript 中一个重要的概念。当一个函数可能记住并拜访所在的词法作用域,即便该函数在词法作用域内部执行,这就产生了闭包。

functionouterFunction() {
  var outerVar = "I'm outer!";
  function innerFunction() {console.log(outerVar); // 输入 "I'm outer!"
  }
  return innerFunction;
}

var myFunction = outerFunction();
myFunction(); // 即便在 outerFunction()执行完后,innerFunction()依然能够拜访 outerVar,这就是闭包

8. 作用域深度解读

了解了 JavaScript 作用域的根底后,咱们通过一些小栗子来坚固下这个概念。

练习 1:变量晋升

思考以下代码,试着预测输入后果,并解释起因。

var name = "John";

function sayName() {console.log(name);
  var name = "Jane";
  console.log(name);
}

sayName();

输入后果为:

undefined
Jane

这是因为 JavaScript 存在变量晋升(hoisting)的机制,var name = "Jane";会被晋升到 sayName 函数的顶部,但只晋升变量名,不晋升赋值操作。所以第一个 console.log(name); 打印出的是undefined

练习 2:应用闭包创立公有变量

闭包能够用来创立公有变量,从而实现封装和数据暗藏。思考下如何应用闭包来创立公有变量。

function createCounter() {
  var count = 0;
  return function() {return ++count;};
}

var counter = createCounter();
console.log(counter()); // 输入 1
console.log(counter()); // 输入 2 

在这个例子中,count就是一个公有变量。内部无奈间接拜访 count,只能通过counter 函数来操作。

9. 利用在异步编程中

在异步编程中,作用域起着十分重要的作用。当初思考上面代码:

for (var i = 0; i < 5; i++) {setTimeout(function() {console.log(i);
  }, 1000);
}

这段代码的输入会让很多初学者感到意外,它会打印出五个 5,而不是 0 到 4。这是因为 setTimeout 中的回调函数是在循环完结后才执行的,而此时的 i 曾经变成了 5。

如果咱们想要打印出 0 到 4,咱们能够应用 let 来创立块级作用域:

for (let i = 0; i < 5; i++) {setTimeout(function() {console.log(i);
  }, 1000);
}

或者咱们也能够应用闭包来创立一个新的作用域:

for (var i = 0; i < 5; i++) {(function(i) {setTimeout(function() {console.log(i);
    }, 1000);
  })(i);
}

10. 总结

了解作用域对于编写可保护的代码十分重要。心愿本文能帮忙你了解全局作用域、函数作用域、块级作用域 letconst 及闭包等重要概念。

退出移动版