【深入01】执行上下文

导航

[深入01] 执行上下文
[深入02] 原型链
[深入03] 继承
[深入04] 事件循环
[深入05] 柯里化 偏函数 函数记忆
[深入06] 隐式转换 和 运算符
[深入07] 浏览器缓存机制(http缓存机制)
[深入08] 前端安全
[深入09] 深浅拷贝
[react] Hooks

执行上下文的概念

执行上下文是:javaScript代码解析和执行时所在的环境,javascript中运行的所有代码都在执行上下文中执行

需要理解的一些名词

  • VO: 变量对象 variable object
  • AO: 活动对象 active object
  • EC: 执行上下文 execution context
  • ECS: 执行上下文栈 execution context stack
  • Scope Chain: 作用域链
  • Lexical Environment: 词法环境
  • Variable Environment: 变量环境
  • execution:执行
  • lexical:词汇的

执行上下文的类型

执行上下文分为三种类型:全局执行上下文,函数执行上下文,eval

全局执行上下文:

  • js代码运行起来,首先进入该环境。不在任何函数中的js代码都位于全局执行上下文中
  • 一个程序中只能存在一个全局上下文,位于执行上下文栈的最底部
  • 全局执行上下文会做两件事:(1)创建一个全局对象 (2)将this指向这个全局对象
  • 在浏览器环境全局对象是window,在node环境全局对象是global

函数执行上下文:

  • 每次调用一个函数,都会生成一个新的函数执行上下文
  • 每个函数都有自己的函数执行上下文,但只有被调用时才会被创建
  • 函数执行上下文的生命周期分为两个阶段,创建阶段和执行阶段

Eval执行上下文:

  • eval函数执行时产生的执行上下文,不建议使用

执行上下文栈(函数调用栈)

  • 栈是一个后进先出的结构,用于储存 在js代码执行期间产生的所有执行上下文
  • 具体过程:
    • 在javascript刚开始运行时,首先产生全局执行上下文,压入栈,位于栈底
    • 每当发生一个函数被调用,则产生函数执行上下文,压入栈,位于栈顶
    • 当这个函数执行完后,会从执行上下文栈中弹出
    • 引擎会继续去执行位于栈顶的函数
【深入01】执行上下文_第1张图片
image

执行上下文的生命周期

创建阶段:(预编译阶段)

  • 执行上下文会创建: 变量对象VO(arguments,形参,变量,函数),作用域链,this

执行阶段:

  • 变量赋值,函数引用,以及执行其他代码
  • 当(执行上下文)执行完毕后,就会出栈,等待被js垃圾回收机制回收

变量对象

变量对象VO ,活动对象AO

变量对象的分类

  • 不同执行上下文环境的变量对象有不同的表现
  • 全局执行上下文中的 变量对象
  • 函数执行上下文中的 变量对象

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

  • 全局执行上下文中的变量对象就是:全局对象
  • 什么是全局对象
    • 进入任何执行上下文之前就建立的对象,只有一份,在程序任何地方都能访问,生命周期终止于程序退出时
  • 全局对象初始化:
    • 初始化一系列的原始属性:Math、String、Date、parseInt、window等
    • 浏览器中,window对象引用全局对象自身,全局环境中this也能引用自身

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

  • 在函数执行上下文中,变量对象VO 用活动对象AO来表示
  • AO是在进入函数执行上下文时(预编译阶段)被创建的,通过argument对象进行初始化
  • 复习下执行上下文的生命周期:(1)创建阶段即预编译阶段 (2)执行阶段。

1) 函数执行上下文的预编译阶段(进入函数执行上下文阶段)代码还未真正执行,此时AO被创建,此时包含的属性有:

  • arguments对象
  • 所有形参
    • 形参名称和形参的值,组成了AO对象的属性。
    • 传递实参,则该形参的值被赋值为实参
    • 没有传递实参,值是undefined
  • 所有函数声明
  • 所有变量声明
  • this
函数执行上下文
- 创建阶段(进入执行上下文阶段,或者说预编译阶段),即进入函数执行上下文时,AO被创建,代码并未真正执行
- 此时AO中的属性有:arguments, 形参,函数声明,变量声明,this
- 代码示例:


function a(name, age) {
    var b = 1
    function c() {}
    var d = function()()
    (function e () {}); // 注意e函数的写法
    var f = function g(){}
}
a('woow') // 注意这里没有传实参age


------------
预编译阶段的AO如下:
AO(a function execution context) = {
    arguments: {
        ......
    },
    name: 'woow',
    age: undefined,
    b: undefined,
    c: reference to function c(){},
    d: undefined
    f: undefined
}
// 注意:
// e函数表达式不在AO中
// g函数不在AO中


------------
执行阶段的AO如下:
AO(a function execution context) = {
    arguments: {
        ......
    },
    name: 'woow',
    age: undefined,
    b: 1,
    c: reference to function c(){},
    d: reference to FunctionExpression "d",
    f: reference to FunctionExpression "f"
}

(2)活动对象AO和变量对象VO的区别:

  • 变量对象:是规范或引擎实现层面的,在js的环境中无法访问,在进入函数执行上下文阶段(预编译阶段),VO才被激活,成为AO
  • 活动对象:只有成为了活动对象,变量对象的属性和方法才能被访问

变量提升

  • 优先级: 函数形参 > 函数声明 > 变量声明
  • 函数名已经存在,则新的覆盖旧的
  • 变量名已经存在,直接跳过变量声明
  • 变量提升,实际上是在函数执行上下文中:进入函数执行上下文时,生成的活动对象AO的属性的赋值过程
变量提升
- 优先级:形参 > 函数声明 > 变量声明 
- 函数名已经存在,则新的覆盖旧的
- 变量名已经存在,则跳过变量声明(注意:只是跳过变量的声明,赋值是正常的赋值)

(例1)
function a(name, age) {
    console.log(name) // -------------------------- 'wang'
    console.log(age) // --------------------------- undefined
    var name = 'woow_wu7'
    console.log(name) // -------------------------- 'woow_wu7'
}
a('wang')

实际执行的代码如下:
function a(name, age) {
    var name = 'wang'
    var age = undefined // ------------------------ 优先级:形参 > 变量声明,所以形参声明并被赋值
    // var name = undefined // -------------------- 变量名已经存在了,跳过变量声明,即这行代码不执行
    console.log(name) // -------------------------- 'wang'
    console.log(age) // --------------------------- undefined, 未传入实参
    name = 'woow_wu7' // -------------------------- 执行阶段被重新赋值
    console.log(name) // -------------------------- 'woow_wu7'
}
a('wang')

(例2)
function a(name, age) {
    console.log(name) // -------------------------- function name(){....}
    console.log(age) // --------------------------- undefined
    var name = 'woow_wu7'
    function name() {
        console.log('name()')
    }
    console.log(name) // --------------------------- 'woow_wu7'
}
a('wang')

实际执行的代码如下:
function a(name, age) {
    var name = 'wang' // -------------------------- 优先级:形参 > 函数声明 > 变量声明
    function name() {...} // ---------------------- 优先级:函数声明 > 变量声明;函数名name已经存在,则新的覆盖旧的
    // var name = undefined // -------------------- 变量名已经存在,则跳过变量声明,相当于该行不执行
    console.log(name) // -------------------------- function name(){....}
    console.log(age) // --------------------------- undefined
    name = 'woow_wu7' // -------------------------- 执行时重新被赋值
    function name() {
        console.log('name()')
    }
    console.log(name) // --------------------------- 'woow_wu7'
}
a('wang')

我的语雀:https://www.yuque.com/woowwu/msyqpd/ph3qtd
https://juejin.im/post/5dc7dbbc6fb9a04aa0014e3f
https://juejin.im/post/5bdfd3e151882516c6432c32
https://juejin.im/post/5ab072c3f265da23884ce1b4
https://juejin.im/post/58ec3cc944d90400576a2cdc
https://www.jianshu.com/p/330b1505e41d

复习

手写new

  • 构造函数
    • 构造函数的首字母通常大写
    • this指向的是实例对象
    • 调用时通过new命令
    • 构造函数也可以接收参数
  • new命令
    • 执行构造函数,返回实例对象
    • new命令本身就可以执行构造函数,所以函数后面的那对括号可以不要。比如:new fn 或者 new fn() 都可以
  • 构造函数调用忘记使用new命令?
    • 构造函数忘记使用new命令调用的话,构造函数就成了普通函数,函数中的this就要在调用时才能确定指向
    • 如何避免构造函数忘记使用new命令调用:
    • (1)在构造函数内使用严格模式'use strict',这样忘记使用new就是报错
    • (2)就是判断是否使用了new,如何判断?
    • !(this instanceof Fubar) => 如果this不是Fubar的实例,就用new命令调用return new Fubar()
  • new命令的原理
    • 创建一个空对象,作为将要返回的实例对象
    • 将这个空对象的原型指向构造函数的prototype属性
    • 将这个空对象赋值给函数内部的this关键字
    • 执行构造函数内部的代码
  • 构造函数中的return
    • 构造函数中,如果return后面跟着一个对象,new命令就会返回这个对象
    • 构造函数中,如果return后面跟的不是一个对象,或者没有返回值,就是返回this对象
    • 普通函数如果用new命令调用,则会返回一个空对象

手写new
- new命令执行构造函数,返回实例对象
- 构造函数中有return,如果后面跟一个对象new命令会返回这个对象,如果后面不是对象,就会返回this对象
- new命令生成的实例对象可以继承构造函数的prototype属性 => 即实例可以访问构造函数的prototype上的属性和方法
- new命令生成的实例可以访问构造函数中的属性和函数


过程:
1. 新建一个空对象
2. 将空对象的隐式原型指向构造函数的显示原型 => 空对象就能访问构造函数的prototype上的属性和方法
3. 将构造函数中的this指向这个空对象 => this上绑定的属性和方法实际上就绑在了空对象上
4. 执行构造函数 => 如果有return,并返回一个对象,就返回这个对象,如果不是对象,就返回这个空对象


代码:
function Constructor(name) {
  this.name = name
  this.age = 20
  return this
}
Constructor.prototype.address = 'chongqing'

function _new() {
  const obj = {}
  // 还可以通过 const obj = new Object()来生成

  const paramsConstructor = Array.prototype.shift.call(arguments)
  // 获取传入new函数的构造函数
  // 等于 [].prototype.shift.call(arguments)
  // 等于 [].prototype.shifig.apply(arguments)

  obj.__proto__ = paramsConstructor.prototype
  // 将空对象的隐式原型指向构造函数的显示原型
  // 这样paramsConstructor.prototype就成了obj的原型对象,obj可以访问原型对象上的属性和方法
  // 注意:Object.create() 该方法可以以参数对象为原型,返回一个实例对象
  // 所以:空对象的生成,加空对象的隐式原型的绑定可以一行代码搞定:const obj = Object.create(paramsConstructor.prototype)

  const res = paramsConstructor.apply(obj, arguments)
  // 将构造函数中的this绑定在空对象上,并执行构造函数
  // 注意:这里arguments是shift后剩下的值,因为apply可以自己转化类数组的对象,所以无需额外操作

  return Object.prototype.toString.call(res).includes('Object') ? res : obj
  // 如果构造函数返回的是一个对象,就返回这个对象,否则返回这个空对象
  // 等于 typeof res === 'object' ? res : obj
}
const res = _new(Constructor, 'woow_wu7')
console.log(res, 'res11')

你可能感兴趣的:(【深入01】执行上下文)