[深入01] 执行上下文
导航
[深入01] 执行上下文
[深入02] 原型链
[深入03] 继承
[深入04] 事件循环
[深入05] 柯里化 偏函数 函数记忆
[深入06] 隐式转换 和 运算符
[深入07] 浏览器缓存机制(http缓存机制)
[深入08] 前端安全
[深入09] 深浅拷贝
[深入10] Debounce Throttle
[深入11] 前端路由
[深入12] 前端模块化
[深入13] 观察者模式 发布订阅模式 双向数据绑定
[深入14] canvas
[深入15] webSocket
[深入16] webpack
[深入17] http 和 https
[深入18] CSS-interview
[深入19] 手写Promise
[深入20] 手写函数
[部署01] Nginx
[部署02] Docker 部署vue项目
[部署03] gitlab-CI
[源码-webpack01-前置知识] AST抽象语法树
[源码-webpack02-前置知识] Tapable
[源码-webpack03] 手写webpack - compiler简单编译流程
[源码] Redux React-Redux01
[源码] axios
[源码] vuex
[源码-vue01] data响应式 和 初始化渲染
执行上下文的概念
执行上下文是:javaScript代码解析和执行时所在的环境,javascript中运行的所有代码都在执行上下文中执行
需要理解的一些名词
- VO: 变量对象 variable object
- AO: 活动对象 active object
- EC: 执行上下文 execution context
- ECS: 执行上下文栈 execution context stack
- Scope Chain: 作用域链
- Lexical Environment: 词法环境
- Variable Environment: 变量环境
execution:执行
lexical:词汇的
执行上下文的类型
执行上下文分为三种类型:
全局执行上下文:
- js代码运行起来,首先进入该环境。<font color=red >不在任何函数中的js代码都位于全局执行上下文中</font>
- 一个程序中只能存在一个全局上下文,位于执行上下文栈的最底部
- 全局执行上下文会做两件事:(1)创建一个全局对象 (2)将this指向这个全局对象
- 在浏览器环境全局对象是window,在node环境全局对象是global
函数执行上下文:
- 每次调用一个函数,都会生成一个新的函数执行上下文
- <font color=red>每个函数都有自己的函数执行上下文,但只有被调用时才会被创建</font>
- 函数执行上下文的生命周期分为两个阶段,创建阶段和执行阶段
Eval执行上下文:
- eval函数执行时产生的执行上下文,不建议使用
执行上下文栈(函数调用栈)
- 栈是一个后进先出的结构,用于储存 在js代码执行期间产生的所有执行上下文
- 具体过程:
- 在javascript刚开始运行时,首先产生全局执行上下文,压入栈,位于栈底
- 每当发生一个函数被调用,则产生函数执行上下文,压入栈,位于栈顶
- 当这个函数执行完后,会从执行上下文栈中弹出
- 引擎会继续去执行位于栈顶的函数
执行上下文的生命周期
创建阶段:(预编译阶段)
- 执行上下文会创建: <font color=red>变量对象VO(arguments,形参,变量,函数),作用域链,this</font>
执行阶段:
- 变量赋值,函数引用,以及执行其他代码
- 当(执行上下文)执行完毕后,就会出栈,等待被js垃圾回收机制回收
变量对象
<font color=red>变量对象VO </font> ,<font color=red>活动对象AO </font>
变量对象的分类
- 不同执行上下文环境的变量对象有不同的表现
- 全局执行上下文中的 变量对象
- 函数执行上下文中的 变量对象
全局执行上下文中的变量对象
- 全局执行上下文中的变量对象就是:<font color=red>全局对象</font>
- 什么是全局对象
- 进入任何执行上下文之前就建立的对象,只有一份,在程序任何地方都能访问,生命周期终止于程序退出时
- 全局对象初始化:
- 初始化一系列的原始属性:Math、String、Date、parseInt、window等
- 浏览器中,window对象引用全局对象自身,全局环境中this也能引用自身
函数执行上下文中的变量对象
- 在函数执行上下文中,变量对象VO <font color=red>用活动对象AO来表示</font>
- AO是在进入函数执行上下文时(预编译阶段)被创建的,通过argument对象进行初始化
- 复习下执行上下文的生命周期:(1)创建阶段即预编译阶段 (2)执行阶段。
<table><tr><td bgcolor=orange>(1) 函数执行上下文的,进入函数执行上下文阶段(预编译阶段)代码还未真正执行,此时AO被创建,此时包含的属性有:</td></tr></table>
- 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
- 活动对象:
只有成为了活动对象,变量对象的属性和方法才能被访问
变量提升
<table><tr><td bgcolor=orange>优先级: 函数形参 > 函数声明 > 变量声明</table></tr></td> </br>
<table><tr><td bgcolor=yellow>函数名已经存在,则新的覆盖旧的</table></tr></td> </br>
<table><tr><td bgcolor=yellow>变量名已经存在,直接跳过变量声明</table></tr></td> </br>
- 变量提升,实际上是在函数执行上下文中:进入函数执行上下文时,生成的活动对象AO的属性的赋值过程 </br>
变量提升
- 优先级:形参 > 函数声明 > 变量声明
- 函数名已经存在,则新的覆盖旧的
- 变量名已经存在,则跳过变量声明(注意:只是跳过变量的声明,赋值是正常的赋值)
(例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/6844904033308655623
https://juejin.im/post/6844903704466833421
https://juejin.im/post/6844903577899499534
https://juejin.im/post/6844903473528602637
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关键字
- 执行构造函数内部的代码
- 如果构造函数的返回值是一个对象,就返回这个对象。否则返回这个空对象即this对象
- 构造函数中的return
- 构造函数中,如果return后面跟着一个对象,new命令就会返回这个对象
- 构造函数中,如果return后面跟的不是一个对象,或者没有返回值,就是返回this对象
- 普通函数如果用new命令调用,则会返回一个空对象
手写new
- new命令执行构造函数,返回实例对象
- 构造函数中有return,如果后面跟一个对象new命令会返回这个对象,如果后面不是对象,就会返回this对象
- new命令生成的实例对象可以继承构造函数的prototype属性 => 即实例可以访问构造函数的prototype上的属性和方法
- new命令生成的实例可以访问构造函数中的属性和函数
过程:
1. 新建一个空对象
2. 将空对象的隐式原型指向构造函数的显示原型 => 空对象就能访问构造函数的prototype上的属性和方法
3. 将构造函数中的this指向这个空对象 => this上绑定的属性和方法实际上就绑在了空对象上
4. 执行构造函数 => 如果有return,并返回一个对象,就返回这个对象,如果不是对象,就返回这个空对象
5. 如果构造函数的返回值是一个对象,就返回这个对象。否则返回这个空对象即this对象
代码:
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 obj = Object.setPrototypeOf({}, 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')