前端那些事系列之进阶篇JS(一)

执行上下文

js在执行可执行代码时,会创建对应的执行上下文。在js引擎中用执行上下文栈(**Execution context stack,ECS**)来管理执行上下文。通俗来说,执行上下文就是执行代码的准备过程。

执行上下文栈

js最先解析的是全局代码,所以初始化时会将全局执行上下午压入ECS内,并且只有当整个应用程序结束时ECS才会被清空,所以在程序结束前,ECS底部始终有个全局执行上下文(globalContext)。
当执行一个函数时,就会创建一个函数执行上下文,并且压入ECS中,当函数执行完毕,就会将此函数执行上下文从栈中弹出。

每个执行上下文,都有三个重要属性

  • 变量对象(Variable object ,VO)
  • 作用域链(Scope chain)
  • this
1)变量对象

变量对象是与执⾏上下⽂相关的数据作⽤域,存储了在上下⽂中定义的变量和函数声明

全局上下⽂中全局对象
初始化时是全局对象;
可以通过this引用,在客户端环境中,全局对象就是window;
是由 Object 构造函数实例化的⼀个对象;
预定义的属性是可⽤;(比如可以直接调用Math.random())
作为全局变量的宿主;
函数上下⽂中活动对象(AO)
函数上下⽂的变量对象初始化只包括 Arguments 对象;
只有当进入到一个执行上下文中,这个执行上下文的变量才会被激活。而只有被激活的变量对象上的各种属性才能被访问;
活动对象是在进⼊函数上下⽂时刻被创建的,它通过函数的 arguments 属性初始化。arguments 属性值是 Arguments 对象;
在进⼊执⾏上下⽂时会给变量对象添加形参、函数声明、变量声明等初始的属性值;
在代码执⾏阶段,会再次修改变量对象的属性值;
Tip:在进⼊执⾏上下⽂时,⾸先会处理函数声明,其次会处理变量声明,如果变量名称跟已经声明

的形式参数或函数相同,则变量声明不会⼲扰已经存在的这类属性。

执行过程

执⾏上下⽂的代码会分成两个阶段进⾏处理:进⼊执⾏上下⽂和执⾏

  • 进⼊执⾏上下⽂
    变量对象包括
    1) 函数的所有形参
    2) 函数声明
    3) 变量声明
function foo(a) {
var b = 2;
function c() {}
var d = function() {};
b = 3;
}
foo(1);

AO = {
	arguments: {
		0: 1,
		length: 1
	},
	a: 1,
	b: undefined,
	c: reference to function c(){},
	d: undefined
}
  • 代码执⾏
    在代码执⾏阶段,会顺序执⾏代码,根据代码,修改变量对象的值
    执行完代码后,上面的例子会变成
AO = {
	arguments: {
		0: 1,
		length: 1
	},
	a: 1,
	b: 3,
	c: reference to function c(){},
	d: reference to FunctionExpression "d"
}
2)作⽤域链

当查找变量的时候,会先从当前上下⽂的变量对象中查找,如果没有找到,就会从⽗级(词法
层⾯上的⽗级)执⾏上下⽂的变量对象中查找,⼀直找到全局上下⽂的变量对象,也就是全局对象。
这样构成的链表就叫作用域链。

函数创建

这是因为函数有⼀个内部属性 [[scope]],当函数创建的时候,就会保存所有⽗变量对象到其中,可以理解为 [[scope]] 就是所有⽗变量对象的层级链,举例

function father() {
	function son() {
	...
	}
}

//函数创建时,各⾃的[[scope]]为:
foo.[[scope]] = [
	globalContext.VO
];
bar.[[scope]] = [
	fooContext.AO,
	globalContext.VO
]
函数激活

当函数激活时,进⼊函数上下⽂,创建 VO/AO 后,就会将活动对象添加到作⽤链的前端。这时候执⾏上下⽂的作⽤域链,我们命名为 Scope

Scope = [AO].concat([[Scope]]);

这就是完整的作用域链

举个例子
var scope = "global scope";
	function checkscope(){
	var scope2 = 'local scope';
	return scope2;
}
checkscope();

这个函数的执行过程如下

  1. checkscope 函数被创建,保存作⽤域链到 内部属性[[scope]]
checkscope.[[scope]] = [
	globalContext.VO
];
  1. 执⾏ checkscope 函数,创建 checkscope 函数执⾏上下⽂,checkscope 函数执⾏上下⽂被压⼊执
    ⾏上下⽂栈
ECStack = [
	checkscopeContext,
	globalContext
];
  1. checkscope 函数并不⽴刻执⾏,开始做准备⼯作,第⼀步:复制函数[[scope]]属性创建作⽤域链
checkscopeContext = {
	Scope: checkscope.[[scope]],
}
  1. 第⼆步:⽤ arguments 创建活动对象,随后初始化活动对象,加⼊形参、函数声明、变量声明
checkscopeContext = {
	AO: {
		arguments: {
		length: 0
	},
	scope2: undefined
	},
	Scope: checkscope.[[scope]],
}
  1. 第三步:将活动对象压⼊ checkscope 作⽤域链顶端
checkscopeContext = {
	AO: {
		arguments: {
		length: 0
	},
	scope2: undefined
	},
	Scope: [AO, [[Scope]]]
}
  1. 准备⼯作做完,开始执⾏函数,随着函数的执⾏,修改 AO 的属性值
checkscopeContext = {
	AO: {
		arguments: {
			length: 0
		},
		scope2: 'local scope'
	},
	Scope: [AO, [[Scope]]]
}
  1. 查找到 scope2 的值,返回后函数执⾏完毕,函数上下⽂从执⾏上下⽂栈中弹出
ECStack = [
	globalContext
]
3)this

ECMAScript 的类型分为语⾔类型和规范类型。主要介绍规范类型meta-values中的Reference

Reference

⽤来解释诸如 delete、typeof 以及赋值等操作⾏为的,它是只存在于规范⾥的抽象类型,由三部分组成。

  • base value;(属性所在的对象或者 EnvironmentRecord)
  • referenced name;(属性的名称)
  • strict reference;
    可以通过GetBase或IsPropertyReference来获取Reference 组成部分
    • GetBase 返回对象属性真正的值
    var foo = 1;
    var fooReference = {
    base: EnvironmentRecord,
    name: 'foo',
    strict: false
    };
    GetValue(fooReference) // 1;
    
    • IsPropertyReference 如果 base value 是⼀个对象,就返回true。
如何确定当前this的值
  • 计算 MemberExpression 的结果赋值给 ref
  • 判断 ref 是不是⼀个 Reference 类型
    a. 如果 ref 是 Reference,并且 IsPropertyReference(ref) 是 true, 那么 this 的值为
    GetBase(ref)
    b. 如果 ref 是 Reference,并且 base value 值是 Environment Record, 那么this的值为
    ImplicitThisValue(ref)
    c. 如果 ref 不是 Reference,那么 this 的值为 undefined;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值