javascript中作用域以及作用域链

变量及作用域:
变量无非就是两种:全局变量和局部变量。
Javascript语言中,函数内部可以直接读取全局变量,在函数外部无法直接读取函数内的局部变量。

程序设计中作用域的概念:
通常来说,一段程序代码中所用到的名字并不总是有效/可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域。

词法作用域:
词法作用域,也叫静态作用域,它的作用域是指在词法分析阶段就确定了,不会改变。(也就是说预解析的时候就已经确定了作用域,在函数没有运行的时候就已经确定了)

// 词法作用域(静态作用域)
let abc = 123
function fn1 () {
    console.log(abc); // 123
}
function fn2 () {
    let abc = 456
    fn1()
}
fn2()

// 只能输出 123 因为在 fn1 函数定义的时候只能访问全局作用域
// 动态作用域(类似 this,在代码运行的时候才确定了指向)

动态作用域,是在运行时根据程序的流程信息来动态确定的,而不是在写代码时进行静态确定的。(指代码执行的时候确定作用域)

主要区别

  1. 词法作用域是在写代码或者定义时确定的,而动态作用域是在运行时确定的。
  2. 词法作用域关注函数在何处声明,而动态作用域关注函数从何处调用。

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对象的创建。

内部的执行顺序如下:

  1. 查找调用函数的代码。
  2. 执行函数代码之前,先创建执行上下文。
  3. 进入创建阶段:
  4. 初始化作用域链
  5. 创建变量对象
  6. 创建arguments对象,检查上下文,初始化参数名称和值并创建引用的复制
  7. 扫描上下文的函数声明
  8. 扫描上下文的变量声明
  9. 求出上下文内部“this”的值
  10. 激活/代码执行阶段:
  11. 在当前上下文上解释/运行函数代码,并随着代码一行行执行指派变量的值。

在函数的执行上下文中,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

喜欢可以点个赞哦,笔芯 ~

发布了12 篇原创文章 · 获赞 21 · 访问量 342

猜你喜欢

转载自blog.csdn.net/weixin_44691775/article/details/104417932