Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

执行上下文 #6

Closed
coconilu opened this issue May 6, 2018 · 0 comments
Closed

执行上下文 #6

coconilu opened this issue May 6, 2018 · 0 comments

Comments

@coconilu
Copy link
Owner

coconilu commented May 6, 2018

前言

前面的一篇博客《JS运行原理》,已经大致讲解过“执行上下文”。这篇博客计划详细说说这个概念。

定义

我们一般把JS引擎运行JS代码分为两步,

  1. 创建执行上下文
  2. 逐行执行代码

函数声明和函数表达式都属于函数定义阶段,只有在函数被执行阶段,新的执行上下文才会被创建。

执行上下文确定了第二步需要用的变量(或者说是寻找变量的途径),如果在执行代码的时候无法确定变量将会报错ReferenceError,不包括变量为undefined和null的情况。
执行上下文主要包括三个重要属性:

  1. 变量对象
  2. 作用域链
  3. this

提醒一下,for或if语句不会创建新的执行上下文,函数才会创建新的执行上下文

这篇博客涉及到的概念

  1. 执行上下文栈
  2. 提升
  3. 变量对象
  4. 作用域链
  5. 计算this的指向
  6. 暂时性死区

执行上下文栈

JS引擎刚开始读取JS文件的时候,会创建一个全局上下文,并把它放到执行上下文栈里,之后如果遇到需要新创建执行上下文,会把它压到栈顶;直到相关的代码执行完毕并返回结果,该执行上下文会被出栈。执行上下文栈是创建作用域链的重要参考。

提升(Hoisting)

提升是相对于var声明的变量和函数声明的,函数表达式并不会被提升。

// 函数声明形如:
// name是函数名,类似变量名可以被调用
function name([param,[, param,[..., param]]]) {
   [statements]
}

// 函数表达式形如:
// 其中的[name]是函数代名词,只是作为函数体的一个本地变量(意味着外部是不可以调用的),主要用于调用自身
var function_expression = function [name]([param1[, param2[, ..., paramN]]]) {
   statements
};

提升的伪逻辑如下:

  1. 提升var变量,如果存在同名变量(包括同名形参),不进行操作;若不存在,则初始化为undefined
  2. 提升函数声明,提升并覆盖已有的同名变量
(function (a, b) {
    console.log(a, b)
    var a;
    function b() {};
    a = 1;
    console.log(a, b)
})(3, 4)

output:
3 [Function: b]
1 [Function: b]

伪代码转换:
(function (a, b) {
    var a=3,b=4;
    b=function () {}

    console.log(a, b)
    a = 1;
    console.log(a, b)
})(3, 4)

变量对象

全局执行上下文的变量对象有:

  1. 全局对象(浏览器环境中是window,nodejs中是global),允许直接访问属性(比如经常使用的new Date,本身就是Window.Date)
  2. 使用或不使用var定义的变量,差别是不使用var定义的变量可以用delete操作符删除
  3. 函数声明

函数执行上下文的变量对象有:

  1. arguments
  2. 实参
  3. 使用var定义的变量
  4. 函数声明

作用域链

当在本执行上下文中的变量对象中没有找到目标变量的话,就会借助作用域来查找变量。
比如:在函数执行上下文中未使用var定义的变量被调用的时候,因为在变量对象中找不到,所以会顺着作用域链查找下一个执行上下文中的变量对象。

作用域链是根据执行上下文栈来确定的。

特殊作用域:

  1. try-catch语句块,临时在作用域链中添加error对象
  2. with语句块,也会临时在作用域链中添加with的对象
  3. eval语句,如果你间接的使用 eval(),比如通过一个引用来调用它,而不是直接的调用 eval ,从 ECMAScript 5 起,它工作在全局作用域下,而不是局部作用域中

计算this的指向

一旦确定了this的指向,当访问this的某个成员变量或函数,如果不存在不会根据执行上下文栈找到上一个this并查询。

1. 全局执行上下文

this指向全局对象,在浏览器环境下是window,在nodejs下是global

2. 函数执行上下文

  1. 作为对象的方法,即使方法是来自该对象的原型链上的方法,this也指向该对象
  2. 作为构造函数,指向正在构建的新对象
  3. 如果是简单调用,就是没有形如fun()而不是obj.fun(),this指向全局对象,但是在严格模式下,会指向undefined
  4. 箭头函数,与封闭词法上下文的this保持一致

这里的封闭词法上下文没有那么高大上,说白了,箭头函数的执行上下文没有定义this属性,根据执行上下文栈找到上一个执行上下文的this,就是箭头函数中的this。下面看一段代码。

var x = 'windows';
function fun1() {
    var fun2 = () => {
        console.log(this.x)
    }
    fun2()
}
fun1();

var obj = {
    x: 'obj',
    showX: fun1
}
obj.showX()

浏览器非严格模式的output:
windows
obj

nodejs,默认就是严格模式:
undefined
obj

fun2的上一个执行上下文就是fun1,所以fun2的this就是fun1的this。第一次调用是简单调用,所以fun1的this指向全局变量(window);第二次调用是作为对象的方法被调用,所以this指向新对象,而新对象里刚好有x变量。

3. 特殊情况

  1. 使用apply和call传递this指向
  2. 使用bind方法绑定this的指向,只对第一次的绑定生效
  3. DOM事件构造函数里的this指向触发事件的DOM元素
  4. 内联事件处理函数里的this指向监听器所绑定的DOM元素

暂时性死区

只有在es6中的let和const才会发生这种情况。

进入一个执行上下文的时候,
let、const声明的变量和形参定义的变量,都会屏蔽掉前一个执行上下文中的同名变量,
且在声明语句之前是不可以使用该变量,
声明语句之后,若该变量没有被赋值,则默认为undefined。

var x = 0;
function f(x = x) { // A行
    console.log(x)
    console.log(y) // B行
    let y = 1;
}
f()

output:
ReferenceError: x is not defined
ReferenceError: y is not defined

上面的代码里A行中的第一个x是形参定义的变量,它会屏蔽掉全局中的x,所以会报错;B行中的y由于是let定义的变量,所以在声明语句前调用将会报ReferenceError错误。

参考链接

http://yanhaijing.com/javascript/2014/04/29/what-is-the-execution-context-in-javascript/
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/eval
https://www.cnblogs.com/snandy/archive/2011/03/19/1988284.html
http://blog.csdn.net/yangbingbinga/article/details/61424363

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant