你要了解的 JavaScript —— Scope
栏目: JavaScript · 发布时间: 5年前
内容简介:在JavaScript中,Scope(作用域)是一个非常重要的概念,对很多刚接触JavaScript的开发者来说,这个概念理解起来并不容易。本文的目的就是对JavaScript Scope的知识点做一次梳理,希望通过本文能帮助你更好的理解Scope。在JavaScript中,Scope一般译为如果一个变量定义在任何函数或花括号
在JavaScript中,Scope(作用域)是一个非常重要的概念,对很多刚接触JavaScript的开发者来说,这个概念理解起来并不容易。本文的目的就是对JavaScript Scope的知识点做一次梳理,希望通过本文能帮助你更好的理解Scope。
#Scope到底是什么?
在JavaScript中,Scope一般译为 作用域 ,可以通俗易懂的理解为:作用域 限定 了在某个 特定范围 内可以得到的 变量、函数和对象等资源 。这里注意几个关键点,首先作用域起着限定作用,其次它限定在某个特定范围,超过这个范围是不被允许的,最后它规定了可以得到什么,可以得到包括变量、函数声明和对象等。Scope可以分为Global Scope(全局作用域)和 Local Scope(局部作用域)。
#Global Scope
如果一个变量定义在任何函数或花括号 {}
之外,那么这个变量就处于Global Scope之中,这个变量称为全局变量。
// global scope let name = '老王' function alertName() { alert(name) } 复制代码
定义在Global Scope中的变量,可以在代码中的任何位置使用。尽管这样看起来很酷,但是还是不建议这么做。看下面的代码。
代码片段一:
// global scope let name = '老王' let name = '老李' // Uncaught SyntaxError: Identifier 'name' has already been declared 复制代码
代码片段二:
// global scope var name = '老王' var name = '老李' console.log(name) // 老李 复制代码
代码片段一出现了报错,通过 let
或者 const
定义了一个变量,然后再次定义同名变量,则会提示报错信息,这是因为产生了 命名冲突
。尽管代码片段二没有出现报错,但是通过 var
定义了一个变量,然后再次定义同名变量,则变量的值被覆盖了。
事实上,定义在Global Scope中的变量和函数越多,产生命名冲突的风险也就越大。由此可见,定义变量的时候应该尽可能的定义在Local Scope中,而非Global Scope中,避免污染全局命名空间。
#Local Scope
如果一个变量定义在函数或者花括号 {}
之中,那么其只能被一部分特定的代码所使用,我们可以认为它处于Local Scope中,这个变量可以称为局部变量。
Local Scope又包括Function Scope(函数作用域)和Block Scope(块级作用域)。先来看Function Scope。
Function Scope
在很长一段时间里,在JavaScript中声明一个变量,只能通过 var
来声明,在函数中通过 var
声明的变量是作用在Function Scope之中的。
// global scope function showName() { // function scope var name = '老王' if (true) { console.log(name) } } function getName() { // function scope return name } console.log(name) // undefined showName() // 老王 getName() // undefined 复制代码
通过以上代码可以发现,在getName这个函数中,没有声明 name
变量,但是它也无法获取 showName
中的 name
变量,这是因为在没有任何关系的函数之间,函数的Function Scope之间是相互隔离的。同时也可以发现在Global Scope中也是无法获取 showName
中的 name
变量的,这是因为 name
变量被限定在Function Scope之中。既然Global Scope无法获取Function Scope中的变量,那么Function Scope可不可以获取Global Scope中的变量呢?
// global scope function showName() { // function scope if (true) { console.log(name) } } var name = '老王' console.log(name) // 老王 showName() // 老王 复制代码
通过以上代码可知,在JavaScript中,子作用域可以获取父级作用域中的变量和函数声明,反之则不行。
Hoisting(变量提升)
在JavaScript中,有一个特别的现象,在函数开始执行的阶段,JavaScript Engine(JavaScript引擎)会将通过 var
声明的变量提升到函数的最顶端(其实函数的声明也会),这就是所谓的Hoisting。
// global scope name = '老王' age = 40 var name var age 复制代码
以上代码等价于
// global scope var name; var age; name = '老王'; age = 40; 复制代码
值得注意的是变量提升只会提升变量的声明,变量的赋值并不会提升,变量的赋值仍要等到代码执行到赋值语句的位置才会赋值。
// function scope console.log(name) // undefined name = '老王' console.log(name) // 老王 var name console.log(name) // 老王 复制代码
还有一种情况是需要避免出现的,那就是如果定义在函数中的变量没有使用 var
或者 let
和 const
声明,在非严格模式( use strict
)下,变量将提升为全局变量。
// global scope function showName() { console.log(name) name = '老王' console.log(name) } showName() // 老王, 老王 console.log(name) // 老王 name = '老李' showName() // 老李, 老王 console.log(name) // 老李 复制代码
这样 name
变量将可以被随意修改,可能会产品意想不到的bug。
Block Scope
在ES6出现之前,JavaScript并没有严格意义上的Block Scope,这让很多有其他语言开发经验的开发者感到很困惑。除了不推荐使用的 eval
和 with
,只有 try/catch
中的 catch
块拥有Block Scope。
try { alert(age) var name = '老王' } catch(err) { console.log(err) // ReferenceError: age is not defined } console.log(name) // 老王 console.log(err) // Uncaught ReferenceError: err is not defined 复制代码
由此可见, err
变量只限定在 catch
的Block Scope之中,而 try
则没有自己Block Scope。
ES6的发布为广大JavaScript开发者带来了全新的变量声明方式, let
和 const
,其中 let
和 var
一样,都是用来声明变量,而 const
则是用来声明常亮,顾名思义,其值是不可改变的。这里要重点说明的是, let
和 const
是作用于Block Scope的。
// global scope function showName() { // function scope var name1 = '老王' if (true) { // block scope let name2 = '老李' const name3 = '老赵' console.log(name3) // 老赵 } console.log(name1) // 老王 console.log(name2) // ReferenceError: name2 is not defined } showName() 复制代码
通过以上代码发现,在Block Scope之外, console.log
是无法获取 name2
和 name3
的,这就是Block Scope的限制。
JavaScript是自带Garbage collection(垃圾回收)机制的,我们实际开发过程不用特别关心内存回收的问题,因为JavaScript Engine已经帮我们处理好了。但在开发大型应用的时候,性能问题会逐渐凸显,关于性能,其实涉及到很多方面,这里不一一细说,其中有一点是我们可以利用Block Scope来提高垃圾回收的效率。
既然在Block Scope之外,代码是无法获取其中的变量的,Garbage collection一旦发现变量或对象没有被引用,就会将内存及时的回收以备他用。所以利用这个机制,我们可以优化部分代码的书写方式。
// global scope var name = '老王' { // block scope let age = 40 console.log(age) // 40 } console.log(name) // 老王 复制代码
通过加上花括号,来创建一个Block Scope,一旦代码执行结束,Block Scope中的变量将无法获取,那么它就会被及时回收。
接下来我们来看看实际运用中Function Scope和Block Scope的区别。 代码片段一:
// global scope for(var i = 0; i < 10; i++){ setTimeout(function(){ console.log(i); },100); } 复制代码
代码片段二:
// global scope for(let i = 0; i < 10; i++){ setTimeout(function(){ console.log(i); },100); } 复制代码
运行代码片段一,将会打印出10个10。运行代码片段二,会打印出0、1、2、3、4、5、6、7、8、9。这说明了 var
和 let
是的不同的, var
声明的变量会提升,是作用于Global Scope或Function Scope中的,在代码片段一的 setTimeout
开始执行的时候 for
循环已经运行结束了,这个时候 i
的值是10,所以打印出了10个10。而 let
是作用于Block Scope的,每次循环声明的 i
只在当前的Block Scope中有效,所以每次打印出的 i
都不相同。
#Lexical Scope(词法作用域)
Lexical Scope又称为Static Scope(静态作用域),通俗点说就是你在写代码的时候,某个变量的作用域就已经确定好了,你可以通过直接看代码就能够分析出变量的值。
// global scope function getName() { // function scope var name = '老王' return function () { // function scope console.log(name) } } let showName = getName() function getUser() { // function scope var name = '老李' showName() } console.log(showName()) // 老王 getUser() // 老王 复制代码
当代码执行到 getName
内部的 console.log(name)
时候,不管外部是如何调用的,都是通过向上查找到 var name = '老王'
这条声明赋值语句,从而获取 name
变量的值。所以说在函数还未执行之前,就可以根据Static Scope找到变量对应的值,且这种关系是确定的,不会发生改变,这就是词法作用域。
既然有Static Scope,那么也有Dynamic Scope(动态作用域)。动态作用域意味着在代码执行阶段,变量的取值是会根据作用域的不同而发生变化。
// global scope function user1() { // function scope var name = '老王' getName() } function user2() { // function scope var name = '老李' getName() } function getName() { console.log(name) } user1() // undefined user2() // undefined 复制代码
看以上代码, getName
中并没有声明 name
变量,所以执行结果都为 undefined
。如果JavaScript支持动态作用域,那么 user1()
的执行结果将是 老王
, user2()
的执行结果将是 老李
。事实上,JavaScript并没有Dynamic Scope,它只有Lexical Scope,它们的本质区别是,Lexical Scope是作用于代码编写阶段,关心的是函数在哪声明;而Dynamic Scope是作用于代码执行阶段,关心的是函数在哪被调用。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 你了解HTTPS,但你可能不了解X.509
- 你真的了解Mybatis的${}和#{}吗?是否了解应用场景?
- 你所了解的 array_diff_uassoc 真的是你了解的那样吗?
- 图文了解 Kubernetes
- 深入了解 JSONP
- 一文了解 Kubernetes
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。