前言:
如今小伙伴们对“前端作用域”可能比较讲究,大家都想要了解一些“前端作用域”的相关知识。那么小编在网上汇集了一些有关“前端作用域””的相关内容,希望同学们能喜欢,看官们快快来了解一下吧!本篇文章小编将带着大家一起学习如何使用新的语法声明变量——let。
你将会学到以下内容:
let基本介绍作用域介绍作用域全局作用域和函数作用域块级作用域var和let的区别重复定义变量的问题提升概念的问题
本篇文章阅读时间预计10分钟。
let介绍
ES6引入了let,用let声明变量,解决了JavaScript没有块级作用域的问题(注:ES3的catch分句会产生块作用域)。
有其它语言背景的比如JAVA,C#开发者来说,这个概念并不难以理解,反而ES6之前,JavaScript没有块级作用域,对于新手而言,使用var声明变量,会让JavaScript不易懂和难以调试,用不好,甚至会有内存泄露的可能性。为什么会这样,主要是没有清楚作用域的概念,接下来我们首先了解下什么是作用域。
作用域
作用域简单的来说,就是一套寻找变量的规则,用于确定在何处以及如何查找变量。说直白点:这些变量在哪里?它们存储在哪里?编译器如何找到它们?ES6代码之前,只有全局作用域或函数作用域。
当一个块或函数嵌套在另一个函数时,就发生了作用域嵌套。如图所示,就有三个嵌套作用域:
全局作用域,其中有一个标识符:foo(整个绿色区域)foo创建的函数作用域,其中有三个标识符:a,bar和b(整个黄色区域)bar创建的函数作用域,其中有一个标识符:c(蓝色区域)
如何在嵌套作用域中寻找变量呢:引擎从当前作用域开始查找变量,如果找不到,就会向上一级继续查找。当抵达最外层全局作用域时,无论找到还是没有找到,查找过程中都会停止。
全局作用域和函数作用域
如何理解全局作用域和函数作用域呢,使用var声明变量时,如果在函数外声明,就是全局变量,任何函数都可以进行使用,这就是全局作用域查找。如果在函数内使用var声明变量,就是函数作用域查找,只能在函数内部进行访问,外部不能进行访问,如下段代码所示:
var a = 12; // 全局作用域都能访问 function myFunction() { console.log(a); // alerts 12 var b = 13; if(true) { var c = 14; // 函数内部可以访问 alert(b); // alerts 13 } alert(c); // alerts 14 } myFunction(); alert(b); // alerts undefined
为什么b变量访问不到?因为变量b是在函数内进行声明的,因此函数执行完后,由于垃圾数据回收机制的存在,引擎认为函数执行完了,变量应该进行销毁。
如果你在函数内忘记写了b标识前忘记写了var,引擎就会自作聪明,在函数外全局作用域为你自动声明变量b,这样在函数外就能访问b变量了(全局作用域)。
因此使用var进行声明时,如果一不小心,你就会声明一个全局作用域的变量,更糟糕的情况还有可能污染一个同名的变量,因此产生的BUG就很难查找。
以下这个例子会更加明显,也是开发者经常会出现的问题,i变量会绑定到外部作用域(函数或全局作用域),污染整个外部作用域:
for(var i=0;i<10;i++){ console.log(i); //依次输出1到9}console.log(i);//10
块级作用域
幸好es6引入了let,避免了有var声明变量的一些问题,让变量和函数不仅可以属于所处的作用域,也可以属于某个代码块(通常是{...}内部),有一点需要强调,在块级作用域定义的变量,块级作用域外是无法访问的,如下段代码所示:
let a = 12; // 全局作用域,可以访问function myFunction() { console.log(a); // alerts 12 let b = 13; if(true) { let c = 14; // this is NOT accessible throughout the function! alert(b); // alerts 13 } alert(c); // alerts undefined {}外,因此无法访问 } myFunction(); alert(b); // alerts undefined {}外,因此无法访问
在for循环体,使用var和let的区别更加明显,一个是在全局作用域进行查找变量,一个是在块级作用域查找变量,块级作用域每一次执行都会产生一个作用域。首先在for循环里,使用var声明变量,如下段代码所示:
for(var i=0;i<5;i++){ setTimeout(function() { console.log(i); }, 1000);}// 输出 5 5 5 5 5
由于JavaScript是单线程,事件循环机制的存在(不太了解事件循环机制的,大家可以查看《JavaScript基础——你真的清楚JavaScript是什么吗?》),主线程执行for循环后,才会执行SetTimeOut里的函数,由于使用var声明的变量,作用域会绑定for循环的上一层作用域,由于for循环执行完后,i的变量自然就等于5,因此setTimeOut在执行内部函数时,查找i变量的值,才会输出5。如图所示变量寻找路径:
将var替换let,将会输出什么结果,如下段代码所示:
for(let i=0;i<5;i++){ setTimeout(function() { console.log(i); }, 1000);}// 输出 0,1,2,3,4
由于块级作用域的存在,每次循环,就会产生一个循环体块级作用域,因此才会达到预期的输出。如图所示变量寻找路径:
var和let的比较重复定义变量问题
用var在同一个作用域重复定义变量,后者将会覆盖前者声明的变量的值,如下段代码所示:
var a = 0;var a = 1;alert(a); // alerts 1function myFunction() { var b = 2; var b = 3; alert(b); // alerts 3}myFunction();
使用let在同一作用域下重复定义变量,将会产生SyntaxError的错误,如下段代码所示:
let a = 0;let a = 1; // SyntaxErrorfunction myFunction() { let b = 2; let b = 3; // SyntaxError if(true) { let c = 4; let c = 5; // SyntaxError }}myFunction();
如果你在嵌套作用域里进行重新定义变量,虽然变量名相同,但是不是同一变量,如下段代码所示:
var a = 1;let b = 2;function myFunction() { var a = 3; // different variable let b = 4; // different variable if(true) { var a = 5; // overwritten let b = 6; // different variable console.log(a); // 5 console.log(b); // 6} console.log(a); // 5 console.log(b); // 4}myFunction();console.log(a);console.log(b);提升概念的问题
初学JavaScript的同学,直觉上会认为编译器会由上到下一行行的执行,其实并不正确,函数声明和变量声明都会被提升(使用var声明变量,let声明变量将不会被提升)。函数首先会被提升,然后才是变量提升。
首先我们看下段函数提升的代码:
bookName("ES8 Concepts");function bookName(name) { console.log("I'm reading " + name);//I'm reading ES8 Concepts}
正常输出,由于函数会提升至执行语句前。在来看以下代码,使用变量的方式声明函数:
bookName("ES8 Concepts"); //TypeError: bookName is not a functionvar bookName = function(name) { console.log("I'm reading " + name);}
为什么会这样呢,JavaScript引擎只会先提升函数,在提升变量声明,引擎将会对上述代码这样调整,代码如下:
var bookName; // 变量声明提升至最上面bookName("ES8 Concepts"); // bookName is not function // because bookName is undefinedbookName = function(name) { // 变量赋值不会被提升 console.log("I'm reading " + name);}
如果使用let替换var声明函数呢?将会有什么提示输出呢?如下段代码所示:
bookName("ES8 Concepts"); // ReferenceError: bookName is not definedlet bookName = function(name) { console.log("I'm reading " + name);}
从中可以看出,使用let声明的变量将不会产生变量声明提升。这样的好处就是,让我们更好的按照由上到下的常规方式书写代码,尽量避免提升问题产生的难以查找的问题。
小节
今天的文章就到这里,从中我们可以看出let可以说是var的进化版,为了避免产生奇奇怪怪的问题,让我们能按照大多数高级语言书写代码的思维方式,在绝大部分情况下,我们应该使用let声明变量,让我们的代码更加易于维护和使用。
本文部分内容参:《你不知道的JavaScript》
多精彩内容,请微信关注“前端达人”公众号!
标签: #前端作用域