执行上下文
执行上下文时代码执行时的环境
类型
全局执行上下文 函数执行上下文 eval函数指向上下文
执行栈
当代码第一次执行时会创建一个全局执行环境压入执行栈的栈底,在遇到函数调用时,会把该函数创建的执行栈压入栈顶。当函数执行结束后用,这个执行栈会从栈顶弹出,把控制权交给下一个执行上下文
作用域
词法作用域 :j s的作用域是词法作用域 (静态作用域),是指函数的作用域是在函数定义时就确定好的。
定义:规定如何查找变量,变量在那个范围有用
分类:1.全局作用域 在执行代码最外层 2.函数作用域 在函数里面 3.块级作用域 let const
function a {
}
// a的作用域
a[[scope]] = {
globalContext.VO // 全局变量
}
var let const
- var声明提交 声明的变量会被提到当前作用域的最前端,可以先使用后声明;可重复声明,重复声明就会忽略;不存在块级作用域,在函数内重复赋值会覆盖;函数也存在变量提示,函数声明提升优先于变量声明提升,函数声明会覆盖变量声明
- let const 存在暂时性死区,要先声明后使用;而且不能重复声明;存在块级作用域,只能在最近的代码块{}使用
- const 常量 不能修改值的引用
es3创建执行上下文
创建变量对象
变量对象是引擎实现的不可在js中访,当进入到执行上下文中,这个执行上下文的变量对象会变成活动对象 。变量对象包括:函数的形参,函数声明,变量声明
创建作用域链 :
父级作用域和自身作用域形成的链条,在进入函数时,会把活动对象添加到作用域链的最前端,可以一级一级向上访问查找变量,作用域的最底端是全局作用域,最前端是活动对象。 例如在函数a内部创建函数b 函数b的作用域链包含函数a的作用域,所以函数b可以访问函数a的变量
function a {
function b {}
}
// a的作用域
a[[scope]] = {
globalContext.VO
}
// b的作用域
b[[scope]] = {
aContext.AO // 活动对象
globalContext.VO
}
// 作用域链
Scope [AO].concat([[Scope]])
确定this执行
取决于函数的调用方式
实例
var scope = "global scope";
function checkscope(){
var scope2 = 'local scope';
return scope2;
}
checkscope();
1. 执行全局代码 创建执行栈 全局变量压入栈底
ECStack = [globalContext]
2. 全局上下初始化 包括全局变量 作用域链仅有全局作用域 this指向全局作用域
globalContext = {
VO: [global,scope,checkscope],
Scope:[globalContext.VO],
this: globalContext.vo
}
2. 函数创建 创建checkscope的作用域链scope
checkscope.[[scope]] = [globalContext.VO]
3. 执行checkscope 函数,创建checkscope的执行上下文,压入执行栈
ECStack = [checkscopeContext,globalContext]
4. 进入checkscope checkscope的执行上下文初始化
checkscopeContext = {
AO: {arguments: {length:0},scope:undefined},
Scope:[AO,globalContext.VO],
this:undefined
}
5.执行checkscope 变量赋值
checkscopeContext = {
AO: {arguments: {length:0},scope2:'local scope'},
Scope:[AO,globalContext.VO],
this:undefined
}
6. 函数执行完毕 checkscope从执行栈弹出
ECStack = [globalContext]
es5 创建执行上下文
this 绑定
This 是在运行时绑定的,取决于函数的调用方式而不是编写方式
**默认绑定 **
-
独立函数调用 this 指向全局对象
function func () { var b = 1 console.log(this.b) } var b = 2; func() // 输出2 this指向全局变量
隐式绑定
-
函数作为对象的属性时 被对象直接调用 指向调用的最近对象对象
function func () { var b = 1 console.log(this.b) } var b = 2; var a = { b:3, func:func } a.func(); // 输出3 this指向对象a
如果中间被赋给其他变量 这指向全局对象,参数传递也是一种隐式赋值,传入的函数会被隐式赋值
function func () { var b = 1 console.log(this.b) } var b = 2; var a = { b:3, func:func } var c = a.func c(); // 输出2 this指向对象全局 func(); // 输出2 this指向对象全局 function doFoo(fn) { fn(); } doFoo(a.func) // 输出2 this指向对象全局
原理
参考:https://www.ruanyifeng.com/blog/2018/06/javascript-this.html
var a = {b:2,func:func}
运行时 引擎会现在内存生成一个对象{b:2,func:func的地址}
,函数会单独保存在另一个内存中, 然后把这个函数内存地址复制给变量a.func属性,在把这个整个内存地址赋值给变量a, 这样func就会在两个环境中,在那个环境变量就使用那个环境的值,如下图 黑色的时在全局环境 灰色的是对象a的环境。当直接被对象调用时就去拿灰色的值。
显式绑定 call apply bind 传入的第一次参数时对象 这个对象会绑定到this
function foo() {
console.log( this.a );
}
var obj = {
a: 2
};
foo.call( obj );
new绑定 创建的对象会绑定到传入的构造函数的this
箭头函数 this是根据外层作用域来决定的
- 没有this this由外层作用域确定
- 没有arguments 通过…rest访问
let nums = (...nums) => nuns
- 不能通过new关键字调用 没有[[Construct]]方法 不能作为构造函数
- 没有new.target
- 没有原型
- 没有super
词法环境
标识符和变量的映射
环境记录器 :存储变量和函数声明的实际位置
- 声明式环境记录器 用于函数执行上下文 包括存储变量 函数 参数
- 对象环境记录器 用于全局执行上下文 定义全局上下文中出现的变量和函数的关联
外部环境的引用 :可以访问外部词法环境
语法环境
与词法环境不同的是 词法环境存储的是let const 声明的变量 语法环境存储的是var声明的变量
实例
let a = 20;
const b = 30;
var c;
function multiply(e, f){
var g = 20;
return e*f*g;
}
c = multiply(20, 30);
GlobalContext = {
this:global,
词法环境:{
变量环境记录器: {
Tyepe:Object,
a: uninitialized,
b:uninitialized,
multiply:undefined
},
外部环境的引用: null
},
语法环境: {
变量环境记录器: {
Tyepe:Object,
c:undefined,
},
外部环境的引用: null
}
}
FunctionCOntext = {
this:globalm
词法环境:{
声明式环境记录器: {
Tyepe:Declarative,
Arguments: {0:10,1:20,length:2}
},
外部环境的引用: 外部词法环境
},
语法环境: {
声明式环境记录器: {
Tyepe:Object,
g:undefined,
},
外部环境的引用: 外部语法环境
}
}
闭包
概念
一个函数有权访问另一函数的内存变量,表现形式是:一个函数返回另一个函数,在外部也可以访问内部函数的变量。
原理
在函数内部创建了自己的作用域链,包括了外部的函数变量因此可以访问。
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f;
}
var foo = checkscope();
foo();
es3步骤
-
进入全局环境 创建全局执行上下文 加入执行栈底部
globalContext = { VO: { scope:global scope, checkscope:function foo: undefined } Scope: [globalScope.VO] } ESStack = [globalContext]
-
执行函数 checkscope 创建执行上下文 ,压入执行栈
checkscopeContext = { AO:{ scope:'local scope', f:function } Scope: [checkscopeContext.AO,globalScope.VO] } ESStack = [checkscopeContext,globalContext]
-
执行完毕,从栈中退出
ESStack = [globalContext]
-
执行f 函数,创建执行上下文,压入执行栈
fContext = { AO:{ } Scope: [AO, checkscopeContext.VO, globalContext.V] } ESStack = [fContext,globalContext]
Es5步骤
-
进入全局环境 创建全局执行上下文 加入执行栈底部
GlobalContext = { this:global, 词法环境:{ 变量环境记录器: { Tyepe:Object, checkscope: function, }, 外部环境的引用: null }, 语法环境: { 变量环境记录器: { scope:global scope, foo: undefined }, 外部环境的引用: null } }
-
执行函数 checkscope 创建执行上下文 ,压入执行栈
checkscopeContext = { this:global 词法环境:{ 声明式环境记录器: { Tyepe:Declarative, Arguments: {length:0}, f:function }, 外部环境的引用: 外部词法环境 }, 语法环境: { 声明式环境记录器: { Tyepe:Object, scope:'local scope', }, 外部环境的引用: 外部语法环境 } }
-
执行完毕,从栈中退出
ESStack = [globalContext]
-
执行f 函数,创建执行上下文,压入执行栈
fContext = {
this:globalm
词法环境:{
声明式环境记录器: {
Tyepe:Declarative,
Arguments: {0:10,1:20,length:2}
},
外部环境的引用: 外部词法环境
},
语法环境: {
声明式环境记录器: {
Tyepe:Object,
},
外部环境的引用: 外部语法环境
}
}
经典面试题
for(var i = 0;i<3;i++) {
setTimeout(function() {
console.log(i)
})
}// 闭包常见考题
for(var i = 0;i<3;i++) {
setTimeout(function() {
console.log(i) // 输出 3 3 3 当前的作用域链为Scope: [AO, globalContext.VO] AO中没有会去全局环境中找 就是3
})
}
// 解决办法1:利用闭包
for(var i = 0;i<3;i++) {
(function(i) {
setTimeout(function(){
console.log(i) // 输出 0,1,2 当前的作用域链为Scope: [AO, 匿名函数执行上下文VO,globalContext.VO] AO中没有会去匿名函数执行上下文中找
})
})(i)
}
// 解决办法2:利用es6 let
for(let i = 0;i<3;i++) {
setTimeout(function() {
console.log(i) // 输出 0,1,2 let使得for循环为块级作用域 当前的作用域链为Scope: [AO, 块级作用域,globalContext.VO] AO中没有块级作用域找
})
}
// 变体
var data = []
for(var i = 0;i<3;i++) {
data[i]=function() {
console.log(i) // 输出 3 3 3 当前的作用域链为Scope: [AO, globalContext.VO] AO中没有会去全局环境中找 就是3
}
}
data[0]();
data[1]();
data[2]();