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();

输入后果为:

undefinedJane

这是因为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()); // 输入1console.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及闭包等重要概念。