PRE
这是我参与更文挑战的第12天,活动详情查看: 更文挑战
引例
var value = 1
function foo() {
console.log(value)
}
function bar() {
var value = 2
foo()
}
bar() // 1
复制代码
执行上下文 Execution Context
变量或者函数的上下文决定了他们可以访问哪些数据,以及它们的行为。
每个上下文都有一个关联的变量对象,该上下文的所有变量和函数都存在于这个对象上。
全局上下文是最外层的上下文。根据ECMAScript实现的宿主环境,会不一样。
浏览器runtime中,全局上下文就是window对象。
多个上下文层层嵌套,形成作用域链
function A() {
function B() {
function C() {
...
}
}
}
复制代码
在function C中执行的代码,其作用域链为
EC 表示 Execution Context
C EC -> B EC -> A EC -> Global EC
这也是标识符进行寻找的顺序
从实现来看,通过变量对象(VO)绑定相应EC的变量、函数
什么时候会创建新的执行上下文呢?
逻辑上来看,就是能够执行的代码要进行准备的时候
- 全局
- function
- eval
值得注意的是 函数的EC的实现VO在初始化的时候有 arguments 可称为活动对象(AO)
执行过程
处理有两个过程
- 进入执行上下文
- 代码执行
此处注意函数声明提升和变量提升
1
此时foo函数的AO
AO = {
arguments: {
0: 1,
length: 1
},
a: 1,
b: undefined,
c: reference to function c(){},
d: undefined
}
复制代码
2
此时函数的AO
AO = {
arguments: {
0: 1,
length: 1
},
a: 1,
b: 2,
c: 1,
d: function
}
复制代码
由此可见函数提升要比变量提升的优先级要高一些,且不会被变量声明覆盖,但是会被变量赋值之后覆盖。
静态作用域
又叫词法作用域
函数的EC 在进行创建和声明的时候就已经确定了
静态作用域又叫做词法作用域,采用词法作用域的变量叫词法变量。词法变量有一个在编译时静态确定的作用域。词法变量的作用域可以是一个函数或一段代码,该变量在这段代码区域内可见(visibility);在这段区域以外该变量不可见(或无法访问)。词法作用域里,取变量的值时,会检查函数定义时的文本环境,捕捉函数定义时对该变量的绑定。
大多数现在程序设计语言都是采用静态作用域规则,如C/C++、C#、Python、Java、JavaScript……
相反,采用动态作用域的变量叫做动态变量。只要程序正在执行定义了动态变量的代码段,那么在这段时间内,该变量一直存在;代码段执行结束,该变量便消失。这意味着如果有个函数
f
,里面调用了函数g
,那么在执行g
的时候,f
里的所有局部变量都会被g
访问到。而在静态作用域的情况下,g
不能访问f
的变量。动态作用域里,取变量的值时,会由内向外逐层检查函数的调用链,并打印第一次遇到的那个绑定的值。显然,最外层的绑定即是全局状态下的那个值。采用动态作用域的语言有Pascal、Emacs Lisp、Common Lisp(兼有静态作用域)、Perl(兼有静态作用域)。C/C++是静态作用域语言,但在宏中用到的名字,也是动态作用域。
作用域增强
从作用域链的角度来看,就是把with 中的对象,作为EC 变量对象,加入在 {}
语句中的标识符搜索的头部
这两种情况下,都会在作用域链前端添加一个变量对象。对 with 语句来说,会向作用域链前端添
加指定的对象;对 catch 语句而言,则会创建一个新的变量对象,这个变量对象会包含要抛出的错误对象的声明
闭包
返回的匿名函数的作用域链上有name可以拿到
存在对于已经销毁的函数的foo的VO 中 name的饮用
故GC机制不会清除foo中的name所占用的空间
立刻调用的函数表达式(IIFE)
创建函数的方式有两种
-
function declaration
-
function expression
The main difference between a function expression and a function declaration is the function name, which can be omitted in function expressions to create anonymous functions. A function expression can be used as an IIFE (Immediately Invoked Function Expression) which runs as soon as it is defined.
;(function () {
})()
复制代码
类似于函数声明,但由于包含在括号中,会被解释为函数表达式
IIFE + 执行上下文机制可以用来创建隔离的作用域 -> 块级作用域
提两个应用场景
;(function () {
for (var i = 0; i < 3; i++) {
console.log(i)
}
})()
// Uncaught ReferenceError: i is not defined
console.log(i)
复制代码
const lis = document.querySelectorAll('li')
for (var i = 0; i < 3; i++) {
lis[i].addEventListener('click', function () {
console.log(i)
})
}
// 点每一个li都是3
复制代码
const lis = document.querySelectorAll('li')
for (var i = 0; i < 3; i++) {
lis[i].addEventListener(
'click',
(function (frozenI) {
return function () {
console.log(frozenI)
}
})(i)
)
}
复制代码
不过现在有了const let 已经不需要上面的方案了
不过感觉在引入一些包的时候,这样的机制还是在用
比如