变量及作用域:
变量无非就是两种:全局变量和局部变量。
Javascript语言中,函数内部可以直接读取全局变量,在函数外部无法直接读取函数内的局部变量。
程序设计中作用域的概念:
通常来说,一段程序代码中所用到的名字并不总是有效/可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域。
词法作用域:
词法作用域,也叫静态作用域,它的作用域是指在词法分析阶段就确定了,不会改变。(也就是说预解析的时候就已经确定了作用域,在函数没有运行的时候就已经确定了)
// 词法作用域(静态作用域)
let abc = 123
function fn1 () {
console.log(abc); // 123
}
function fn2 () {
let abc = 456
fn1()
}
fn2()
// 只能输出 123 因为在 fn1 函数定义的时候只能访问全局作用域
// 动态作用域(类似 this,在代码运行的时候才确定了指向)
动态作用域,是在运行时根据程序的流程信息来动态确定的,而不是在写代码时进行静态确定的。(指代码执行的时候确定作用域)
主要区别:
- 词法作用域是在写代码或者定义时确定的,而动态作用域是在运行时确定的。
- 词法作用域关注函数在何处声明,而动态作用域关注函数从何处调用。
javascript 使用的是词法作用域
作用域链
作用域链:本质上是一个指向变量对象的指针列表(scope chain),它只引用但不实际包含变量对象。
(先去自身的活动对象中找,再到指针列表的下一个指针)
每个执行环境都有一个与之关联的变量对象,执行环境中定义的所有变量和函数都保存在这个变量对象中。(就像全局执行环境中的变量对象就是 window,全局中定义的属性和方法都在 window 对象中)
全局执行环境是最外围的一个执行环境,在Web浏览器中,全局执行环境的变量对象是window对象。
当JavaScript解释器初始化执行代码时,它首先默认进入全局执行环境。
局部执行环境的变量对象,则只在函数执行的过程中存在。
当函数被调用的时候,会创建一个特殊的对象–活动对象。
这个对象中包含形参和arguments对象。活动对象之后会作为局部执行环境的变量对象来使用。
换句话说,活动对象除了变量和函数声明之外,它还存储了形参和arguments对象。
EC(Execution Context) 执行环境(执行上下文)
VO(Variable Object) 变量对象
AO(Activation Object) 活动对象
scope chain 作用域链
EC建立分为两个阶段:进入执行上下文(创建阶段)和执行阶段(激活/执行代码)。
创建阶段解释器扫描传递给函数的参数或arguments,本地函数声明和本地变量声明,并创建EC对象。扫描的结果将完成VO对象的创建。
内部的执行顺序如下:
- 查找调用函数的代码。
- 执行函数代码之前,先创建执行上下文。
- 进入创建阶段:
- 初始化作用域链
- 创建变量对象
- 创建arguments对象,检查上下文,初始化参数名称和值并创建引用的复制
- 扫描上下文的函数声明
- 扫描上下文的变量声明
- 求出上下文内部“this”的值
- 激活/代码执行阶段:
- 在当前上下文上解释/运行函数代码,并随着代码一行行执行指派变量的值。
在函数的执行上下文中,VO是不能直接访问的,我们访问的是AO,而不是VO。
AO是在进入函数的执行上下文时创建的
示例
function fun(i) {
var a = 'hello';
var b = function fb() { };
function c() { }
}
fun(123);
// 当调用fun(123)时,创建EC,像下面这样:
funExecutionContext = {
scopeChain: { ... },
variableObject: {
arguments: {
0: 123,
length: 1
},
i: 123,
c: pointer to function c()
a: undefined,
b: undefined
},
this: { ... }
}
// 执行流进入函数并且激活/代码执行阶段,看下函数执行完成后的样子:
funExecutionContext = {
scopeChain: { ... },
variableObject: {
arguments: {
0: 123,
length: 1
},
i: 123,
c: pointer to function c()
a: ‘hello‘,
b: pointer to function fb()
},
this: { ... }
}
if (true) {
var a = 1;
} else {
var b = 2;
}
alert(a); // 1
alert(b); // undefined 声明提升了
// 虽然这里永远不会被执行,但进入全局执行上下文时,变量对象中已经添加了属性b,值为undefined
作用域和执行环境是两个完全不同的概念,我们试图给他俩分别一个明确的定义,发现很难,因为你很难定义一个概念“是什么”,
但是我们可以搞清楚他们分别起什么作用。另一个令人迷惑的地方就是js中的this关键字。其实执行环境就是this的值。
总的来说,作用域是相对于函数来讲的,因为ES5 里没有块级作用域,只有函数才能形成新的作用域。而且作用域在函数声明时就定义好了(词法作用域)。
作用域里声明的变量和函数,外部无法访问,注意,是外部。而执行环境在函数被调用时才生成。
简而言之,作用域链,就是在当前作用域中如果没有该属性(局部变量)则向上一层作用域中寻找,一直到最上层,也就是window
喜欢可以点个赞哦,笔芯 ~