Home > Article > Web Front-end > JavaScript detailed analysis of scope chain
This article brings you relevant knowledge about JavaScript, which mainly introduces the relevant content of the scope chain. The scope is a set of rules that is responsible for collecting and maintaining the identities of all declarations. A series of queries composed of identifiers (variables), and a very strict set of rules are implemented to determine the access rights of the currently executing code to these identifiers; let's take a look at it together, I hope it will be helpful to everyone.
[Related recommendations: JavaScript video tutorial, web front-end]
Scope is a set of rules that collects and maintains a sequence of queries consisting of all declared identifiers (variables) and enforces a very strict set of rules that determines the currently executing code access rights to these identifiers.
To explain what the scope is in better understandable terms, it is:
The scope is first a set of rules
The purpose of this rule is to store variables and how to obtain variables with restrictions.
To use an analogy that may not be entirely appropriate, the scope is that there is an international bank, and you deposit the currencies of various countries in your hand into it. When you want to withdraw some money, It has a set of rules that limit you to withdrawing the corresponding currency only where the currency is available. This bank and the rules it formulates are the role of scope on variables.
In JavaScript, the scope used is lexical scope, also called static scope
. It is determined before compilation. JavaScript is essentially a compiled language, but its compilation occurs a few microseconds before the code is executed. Unlike other compiled languages, it is compiled during the construction process, so it looks more like an interpreted language.
Regardless of compilation and interpretation, you only need to understand that lexical scope is static scope. Understanding the meaning of static means that it is determined
when the code is written and defined. That is, the scope in which variables and functions are defined in the code that people read is their scope.
Use a simple example to understand:
var a = 1function foo() { console.log(a) }function bar(b) { var a = 2 console.log(a) foo() function baz() { console.log(b) } return baz }var c = bar(a)c()
For the three defined functions foo, bar, baz and variable a, their scopes have been defined when writing.
Therefore, when the code is executed, the bar function first calls the value of the variable a passed in. When the value of the variable a is output for the first time, it will first ask whether the variable a has been defined in its own scope. If it has been defined, it will ask whether There is a value of a, and the output variable a is 2.
Then start calling the foo function. There is only the value of the output variable a in foo. It also will ask whether its own scope
has been defined. If the variable a is not defined in foo, it will look up to the scope when it was defined.
will ask whether the variable a has been defined. The global scope has defined it and has a value, so the output a is 1. In fact, the scope chain is already involved, but let’s not discuss it for now.
After entering the c function call, which is the baz function, the value of variable b is output in baz. b will ask whether the variable b is defined in its own scope. If baz is not defined, then look up itself. The scope at the time of definition
is whether the variable b has been defined in the bar function scope. bar is actually implicitly defined for variable b in the parameter and assigned a value of 1, so the final output is 1.
This is the static scope. You only need to look at the writing position of variables and functions to determine their scope.
The opposite is Dynamic scope
. In JavaScript, only this points to dynamic scope, which will be covered when reviewing this later.
Assuming that JavaScript is a dynamic scope, look at the code execution process in the above example.
bar is first called and passed in the variable a. When the value of the variable a is output for the first time, the variable a is completely obtained and 2 is output. When calling the foo function, since there is no variable a in its own scope, it will look up from the scope
of the position where it is called. At this time, it is the scope of function bar, so the output a The value is 2.
When the c function is called, that is, when the baz function is called, the variable b does not exist in itself, so it looks for the scope of the position where it is called, which is the global scope, global effect. If variable b is also not defined in the domain, an error will be reported directly.
全局作用域
函数作用域
块级作用域
全局作用域可以理解为所有作用域的祖先作用域, 它包含了所有作用域在其中。也就是最大的范围。反向理解就是除了函数作用域和被{}花括号包裹起来的作用域之外,都属于全局作用域
。
之所以在全局作用域外还需要函数作用域,主要是有几个原因:
用一个例子来展示:
var name = 'xavier'function foo() { var name = 'parker' var age = 18 function bar() { var name = 'coin' return age } return bar() }foo()console.log(age) // 报错
当代码执行时, 最终会报错表示age查找不到。 因为变量age是在foo函数中定义, 属于foo函数作用域中, 验证了第一点外部无法访问内部。
而当代码只执行到foo函数调用时, 其实foo函数有执行过程, 最终是返回了bar函数的调用,返回的结果应该是18。 在对于编写代码的人来说,其实只需要理解一个函数的作用是什么, 然后给一个需要的输入,最后得出一个预期所想的输出,而不需要在意函数内部到底是怎么编写的。验证了第二点只需要最小暴露原则。
在这代码中, 对name变量定义过三次, 但每次都在各自的作用域中而不会产生覆盖的结果。在那个作用域里调用,该作用域就会返回相应的值。这验证了第三点规避命名冲突。
最终bar函数是在foo函数内部定义的,foo函数获取不到bar内部的变量和函数,但是bar函数可以通过作用域链获取到其父作用域也就是foo里的变量与函数。这验证了第四点。
块级作用域在ES6之后才开始普及,对于是var声明的变量是无效的,仅对let和const声明的变量有效。以{}包裹的代码块就会形成块级作用域, 例如if语句, try/catch语句,while/for语句。但声明对象不属于。
let obj = { a: 1, // 这个区域不叫做块级作用域} if (true) { // 这个区域属于块级作用域 var foo = 1 let bar = 2}console.log(foo) // 1console.log(bar) // 报错
用一个大致的类比来形容全局作用域,函数作用域和块级作用域。一个家中所有的范围就称为全局作用域,而家中的各个房间里的范围则是函数作用域, 甚至可能主卧中还配套有单独的卫生间的范围也属于函数作用域,拥有的半开放式厨房则是块级作用域。
假设你要在家中寻找自己的猫,当它在客厅中,也就是全局作用域里,你可以立马找到。但如果猫在房间里,而没发出声音。你在客厅中是无法判断它在哪里,也就是无法找到它。这就是函数作用域。但是如果它在半开放式厨房里,由于未完全封闭,它是能跑出来的,所以你还是能找得到它。 反之你在房间里,如果它也在,那么可以直接找到。但如果你在房间而它在客厅中,则你可以选择开门去客厅寻找,一样也能找到。
上述的过程过于理论化,因而现在通过对于实质的情况也就是内存中的情况来讨论。
之前上一篇说过在ES3中执行上下文都有三大内容:
实际在内存中,对于全局作用域来说,它所涵盖的范围就是全局对象GO。因为全局对象保存了所有关于全局作用域中的变量和方法。
而对于函数来说,当函数被调用时所创建出的函数执行上下文里的活动对象AO所涵盖的范围就是函数作用域, 并且函数本身存在有一个内部属性[[scope]]
, 它是用来保存其父作用域的,而父作用域实际上也是另一个变量对象。
对于块级代码来说,就不能用ES3这套来解释,而是用ES6中词法环境和变量环境来解释。块级代码会创建出块级执行上下文,但块级执行上下文里只存在词法环境,不存在变量环境,因而这词法环境里的环境记录就是块级作用域。
相同的解释对于全局和函数也一样,对于ES6中,它们执行上下文里的词法环境和变量环境的环境记录涵盖的范围就是它们的作用域。
用一段代码来更好的理解:
var a = 'a'function foo() { let b = 'b' console.log(c) }if (true) { let a = 'c' var c = 'c' console.log(a) }foo()console.log(a)
对于这段代码刚编译完准备开始执行,也就是代码创建时,此刻执行上下文栈和内存中的图为:
当开始进行到if语句时,会创建块级执行上下文,并执行完if语句时执行上下文栈和内存图为:
当if语句执行完后, 就会被弹出栈,销毁块级执行上下文。然后开始调用foo函数,创建函数执行上下文,此时执行栈和内存图为:
当foo执行时,变量b被赋值为'b',同时输出c时会在自身环境记录中寻找,但未找到,因而往上通过自身父作用域,也就是全局作用域的环境记录中寻找,找到c的值为'c',输出'c'。
通过上文阐述的各个知识点,作用域链就很好理解了,在ES3中就是执行上下文里其变量对象VO + 自身父作用域
,然后每个执行上下文依次串联出一条链路所形成的就是作用域链。
而在ES6中就是执行上下文里的词法环境里的环境记录+外部环境引用
。外部环境引用依次串联也会形成一条链路,也属于作用域链。
它的作用在于变量的查找路径
。当代码执行时,遇到一个变量就会通过作用域链不断回溯,直到找到该值又或者是到了全局作用域这顶层还是不存在,则会报错。
以及之后关于闭包的产生,也是由于作用域链的存在所导致的。这会在之后的复习里涉及到。
var a = 10let b = 20const c = { d: 30}function foo() { console.log(a) let e = 50 return b + e a = 40}function bar() { console.log(f) var f = 60 let a = 70 console.log(f) return a + c.d}if (a <= 30) { console.log(a) let b = foo() console.log(b) } console.log(b) c.d = bar()console.log(a)console.log(c.d)
【相关推荐:JavaScript视频教程、web前端】
The above is the detailed content of JavaScript detailed analysis of scope chain. For more information, please follow other related articles on the PHP Chinese website!