Javascript中除了原型链,还有一重要知识环节 —— 上下文环境;通过对执行上下文环境的了解,能够让我们对Javascript内部原理有更深入的理解。
先以执行栈为引入,让大家感受一下:某个
xxx.js代码在执行时,执行栈是怎么运作的,执行栈和上下文环境有什么关系?
执行栈: 一种数据结构栈;在js代码被执行时,已被创建的执行上下文按照被创建的顺序依次被存储(压入)栈中。
【图一】
【图二】
依照上面图一、图二为例子,解释下执行栈的执行过程:
当Js引擎执行到代码文件js-test.js
时,会创建全局执行上下文(Global Excution Context)并压入栈中。对应图二中第1幅图的Global Excution Context被压入栈;
当执行到图一第14行代码时,即执行到函数method_1th,会创建新的函数执行上下文并压入栈中。对应图二中第2幅图的method_1th函数上下文环境(function method_1th() Excution Context)被压入栈。
在执行函数method_1th时,执行到第6行代码method_2th时,会创建当前函数的函数执行上下文并压入栈中。对应图二中第3幅图的method_2th函数上下文环境(function method_2th() Excution Context)被压入栈。
当执行到第7行代码,函数method_2th已执行结束。则执行栈中顶层的method_2th函数上下文被弹出,此时执行控制流程从method_2th函数上下文环境切换到method_1th函数上下文环境。对应图二的第4幅图!
然后,函数method_1th执行结束,即执行到第15行代码时,执行栈中顶层的method_1th函数上下文被弹出,此时执行控制流程从method_1th函数上下文环境切换到Global Excution Context全局执行上下文环境。
由此可见,Javascript中的全局执行上下文和函数执行上下文都是由Js引擎所创建。并且是在Js引擎执行过程中依次创建,而盛放这些上下文的数据结构叫执行栈。
好比Java语言中的Java虚拟机栈,每个方法被执行的同时,都会创建一个栈帧(Stack Frame)。而每一个方法从调用直到执行完成的过程,则对应着一个栈帧在虚拟机栈从入栈到出栈的过程!
下面则是创建执行上下文的过程:
结合上图的思维导图,通过代码环境逐个分析他们的创建过程:
关于 this 的绑定
从执行栈的执行上来解释,在执行第9行代码那一刻之前,全局上下文环境已经创建且被压入了执行栈,此时环境中的this指向全局对象。
当在第9行代码执行时,myFunction函数上下文被创建并压入执行栈中(partialFunc函数定义在了myFunction函数中,不会被执行)且处于栈顶层,此时执行控制流程处在myFunction函数上下文环境中。此时函数partialFunc中的this指向myFunctioin(结合创建执行上下文的过程图可知,this的指向取决于函数是怎么被调用的)。
当执行到第15行代码时,新的函数执行上下文被创建并压入栈中,即partialFunc函数执行上下文。此时执行控制流程处在partialFunc函数上下文环境中。由于this的指向取决于函数是怎么被调用的,而当前函数的执行没有指定任何引用对象,所以当前函数上下文环境的this指向全局对象。
通过对this绑定的分析之后,生出这个疑问:
常用方法call() 和 apply() 中的 this 是绑定到什么对象上?
方法call() 和 apply() 被调用时,即被调用的方式是 this.方法名.call(参数1, 参数2)
和 this.方法名.apply(参数1, 参数2)
。
如果不关注.call() 和 .apply()
,结合关于 this 的绑定进行分析。可以得出结论,此时的方法内部的this指向的是调用方法 (this.方法名) 的 this。
但是,如果方法call() 和 apply() 使用调用方式 —— this.方法名.call(参数1, 参数2)
和 this.方法名.apply(参数1, 参数2)
,那么旨在使用参数1
来更改this
(参数1
来替换this
),具体解释可见下方代码:
bind() 方法中的 this
ECMAScript 5 引入了 Function.prototype.bind
。调用 f.bind(someObject)
会创建一个与 f 具有相同函数体和作用域的函数,但是在这个新函数中,this 将永久地被绑定到了 bind 的第一个参数,无论这个函数是如何被调用的。
因此bind方法
在使用上和call、apply
是一致的。只是bind
是会新建一个新的函数!
关于词法组件的创建
词法组件的创建,这里分为在两种条件环境下:1全局上下文环境; 2函数上下文环境;
创建之后的结构如下图:
由于变量环境和词法环境相似,因此与词法环境的环境结构式一致的。
下面通过变量环境细致的了解下在上图框架结构下的变量环境是如何的!~
关于变量环境组件的创建
结合上图代码,分析:
该变量环境组件的创建过程从代码上来看,该js代码文件中包含了两类上下文环境 —— 因此需要创建全局上下文和函数上下文。
创建全局上下文环境
在全局上下文环境中 this的绑定指向全局对象;使用let、const修饰的变量a和b,以及函数multiply在词法环境中被定义且未被初始化(uninitialized)。
而使用var修饰的变量c,在变量环境中被定义且已被初始化(undefined)。
创建函数上下文环境
从源代码文件的第10行代码 c = multiply(20, 30); 来看,函数上下文环境中this指向的也是全局对象。而在词法环境中使用var修饰的变量g在变量环境中被定义且已被初始化(undefined)。函数上下文环境中的词法环境中,被定义的变量则是函数的参数。
参考文章:
https://juejin.im/post/5b
https://juejin.im/post/585