Scope | - |
window/global Scope | Global Scope |
function Scope | Function Scope |
Block Scope | Block scope (ES6) |
eval Scope | eval scope |
Scope defines a set of rules that define how the engine queries variables based on identifiers in the current scope and nested scopes. On the other hand, the scope chain composed of N scopes determines the value found by the identifier within the function scope.
So we can summarize it as: Scope determines the visibility of variables defined in the current context, that is, the lower-level scope can be accessed. And the scope chain (Scope Chain) also determines how to find the value of the identifier in the current context.
Scope is divided into Lexical Scope and Dynamic Scope. Lexical Scope is literally the Scope defined in the lexical stage. In other words, scope is set when the lexer processes the source code, based on the location of variables and blocks in the source code. JavaScript uses lexical scope.
Access rules for variables:
If variable a is defined inside a function, other variables inside the function have permission to access variable a, but code outside the function does not have access to the variable permissions of a. Therefore, variables in the same scope can access each other, that is, a, b, and c can access each other if they are in the same scope. It's like a mother chicken has a baby. The baby chickens can play with each other, but other chickens can't play with them. Why? Because Mother Chicken doesn’t allow it~ o(^∀^)o.
let a = 1
function foo () {
let b = 1 + a
let c = 2
console.log(b) // 2
}
console.log(c) // error 全局作用无法访问到 c
foo()
If variable a is defined in the global scope (window/global), then the execution code in the local scope under the global scope or Expression can access the value of variable a. A variable with the same name (a) in a local variable will truncate access to the global variable a. (The variable a here is equivalent to the breeder, and the breeder will feed the chickens at the appropriate time. However, in order to save costs, the farmer stipulates that the breeder must feed the chickens nearby. When the breeder 1 leaves the chickens, In the near future, other breeders will not be able to travel all the way across the Yalu River to feed chickens.)
let a = 1
let b = 2
function foo () {
let b = 3
function too () {
console.log(a) // 1
console.log(b) // 3
}
too()
}
foo()
Again, it is emphasized that the javascript scope will strictly limit the accessible range of variables: that is, according to the source code At the location of code and blocks, the nested scope has access to the nested scope. (This rule shows that the entire farm has rules and cannot feed in the opposite direction.)
Scope Chain
The scope chain is composed of the current environment and the upper layer The environment is composed of a series of scopes, which ensures that the current execution environment has orderly access to variables and functions that comply with access permissions.
The above explanation is a bit obscure. For someone like me who has a weak brain, I need to 'read' it several times in my brain to understand it. So what is the scope chain for? Simply put, the scope chain parses the identifier and is responsible for returning the value of the variable that the expression depends on when it is executed. A simpler answer: the scope chain is used to find variables. The scope chain is a series of scopes connected in series.
During function execution, every time a variable is encountered, it will go through an identifier resolution process to determine where to obtain and store the data. This process starts from the head of the scope chain, which is the scope of the currently executing function (from left to right in the figure below), and searches for an identifier with the same name. If found, the value corresponding to the identifier is returned. If not found, continue The next scope in the scope chain is searched. If no scope is found, the identifier is considered undefined. During function execution, each identifier worth parsing must go through this search process.
In order to analyze the problem concretely, we can assume that the scope chain is an array (Scope Array), and the array members are composed of a series of variable objects. We can use the one-way channel of the array, that is, the above figure simulates querying the identifiers in the variable object from left to right, so that we can access the variables in the upper scope. to the topmost level (global scope), and once found, stops the search. Therefore, the inner variable can shield the outer variable with the same name. Think about it if the variables are not searched from the inside out, then the entire language design will become N complicated (we need to design a complex set of rules for baby chickens to find food)
Still the chestnut above:
let a = 1
let b = 2
function foo () {
let b = 3
function too () {
console.log(a) // 1
console.log(b) // 3
}
too()
}
foo()
The nested scope structure is like this:
栗子中,当 javascript 引擎执行到函数 too 时, 全局、函数 foo、函数 too 的上下文分别会被创建。上下文内包含它们各自的变量对象和作用域链(注意: 作用域链包含可访问到的上层作用域的变量对象,在上下文创建阶段根据作用域规则被收集起来形成一个可访问链),我们设定他们的变量对象分别为VO(global),VO(foo), VO(too)。而 too 的作用域链,则同时包含了这三个变量对象,所以 too 的执行上下文可如下表示:
too = {
VO: {...}, // 变量对象
scopeChain: [VO(too), VO(foo), VO(global)], // 作用域链
}
我们可以直接用scopeChain
来表示作用域链数组,数组的第一项scopeChain[0]为作用域链的最前端(当前函数的变量对象),而数组的最后一项,为作用域链的最末端(全局变量对象 window )。所有作用域链的最末端都为全局变量对象。
再举个栗子:
let a = 1
function foo() {
console.log(a)
}
function too() {
let a = 2
foo()
}
too() // 1
这个栗子如果对作用域的特点理解不透彻很容易以为输出是2。但其实最终输出的是 1。 foo() 在执行的时候先在当前作用域内查找变量 a 。然后根据函数定义时的作用域关系会在当前作用域的上层作用域里查找变量标识符 a,所以最后查到的是全局作用域的 a 而不是 foo函数里面的 a 。
变量对象、执行上下文会在后面介绍。
闭包
在 JavaScript 中,函数和函数声明时的词法作用域形成闭包。我们来看个闭包的例子
let a = 1
function foo() {
let a = 2
function too() {
console.log(a)
}
return too
}
foo()() // 2
这是一个闭包的栗子,一个函数执行后返回另一个可执行函数,被返回的函数保留有对它定义时外层函数作用域的访问权。foo()()
调用时依次执行了 foo、too 函数。too 虽然是在全局作用域里执行的,但是too定义在 foo 作用域里面,根据作用域链规则取最近的嵌套作用域的属性 a = 2。
再拿农场的故事做比如。农场主发现还有一种方法会更节约成本,就是让每个鸡妈妈作为家庭成员的‘饲养员’, 从而改变了之前的‘饲养结构’。
关于闭包会在后面的章节里也会有介绍。
从作用域链的结构可以发现,javascript
引擎在查找变量标识符时是依据作用域链依次向上查找的。当标识符所在的作用域位于作用域链的更深的位置,读写的时候相对就慢一些。所以在编写代码的时候应尽量少使用全局代码,尽可能的将全局的变量缓存在局部作用域中。
不加强记忆很容记错作用域与后面将要介绍的执行上下文的区别。代码的执行过程分为编译阶段和解释执行阶段。始终应该记住javascript
作用域在源代码的编码阶段就确定了,而作用域链是在编译阶段被收集到执行上下文的变量对象里的。所以作用域、作用域链都是在当前运行环境内代码执行前就确定了。这里暂且不过多的展开执行上下文的概念,可以关注后续文章。
相信看了本文案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章!
推荐阅读:
PromiseA+的实现步骤详解
EasyCanvas绘图库在Pixeler项目开发中使用实战总结
The above is the detailed content of Analysis of the underlying functions of javascript scope. For more information, please follow other related articles on the PHP Chinese website!