JavaScript的作用域是一个十分根底且重要的概念,对于初学者来说,常常会感觉有些凌乱搞不清楚。本文会具体介绍JavaScript作用域,包含全局作用域、函数作用域和块级作用域,以及ES6+新增的let
、const
和block 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引入了两种新的申明形式:let
和const
,它们与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. let
和const
let
和const
都是块级作用域,它们的作用范畴被限度在最近的一对花括号{}
内。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. 总结
了解作用域对于编写可保护的代码十分重要。心愿本文能帮忙你了解全局作用域、函数作用域、块级作用域let
、const
及闭包等重要概念。