Javascript代码的执行
本文介绍了块级作用域的在内存上的实现与闭包的实现,在此之前需要简单了解一下JavaScript代码的执行.
编译
经过编译后,会生成两部分内容:执行上下文(Execution context)和可执行代码。
执行上下文
由变量环境和词法环境构成;分为全局执行上下文和函数执行上下文;所有的执行上下文又构成执行栈.
-
词法环境:通过let/const声明的変量存在于词法环境,即块即作用域;代码块內部定义的变量在代码块外部是访问不到的,并且等该代码块中的代码执行完成之后,代码块中定义的变量会被销毁
在词法环境内部,维护了一个小型栈结构函数内let和var声明同一个变量可以吗?暂时性死区
-
变量环境:通过var申明的变量和函数声明存在于此
变量和函数同名时:变量的声明会被忽略;同名的函数,选择最后声明的那个
执行
执行Javascrip 会存在多个执行上下文;通过栈的数据结构来管理的,执行上下文栈,又称调用栈。
执行栈
全局执行上下文,只有一份;函数执行上下文,函数执行结束之后,会被销毁
查找变量
- 从词法环境的栈顶向下查
- 变量环境中查找
- 在外部环境中查找
变量查找是静态的过程——编译阶段就决定,与调用没有关系
外部环境
打印出来是极客邦!!因为一个对象(bar)不是一个执行环境,只有全局执行环境和函数执行环境,在执行13行时,在当前的执行环境没有找到变量,会开始寻找外部环境,这个外部环境是全局执行环境,不是bar!!!
在当前执行环境中没有找到的变量,会向外部环境去查找
外部环境:当前的函数定义的环境,和调用无关
闭包:外函数返回的内函数对外函数中的变量有引用
外部函数已经执行结束
这些变量依然保存在内存,称为外函数的闭包
内存泄漏:被返回的内函数是一个全局变量,那么闭包会一直存在直到页面关闭
如何避免内存泄露:在函数作用域而不是全局作用域调用有用包的函数 等函数销毁后就会回收这块内存
闭包执行情况
对象查找
在上面这个例子中,打印出来的结果是不合常理的,在其他语言中,应该打印出来的是函数所属的对象中的变量
class Bar {
name = "brynn";
getName() {
console.log(name);
}
}
let name = "global environment";
let bar = new Bar();
bar.getName();
"global environment"
比如用c++ch;en inp来复写这段代码
#include <iostream>
using namespace std;
class Bar {
public:
char* name;
Bar(){
name = (char*)"brynn";
}
void getName() {
cout<<name<<endl;
}
} bar ;
char* name = (char*)"gloabal environment";
int main() {
bar.getName();
return 0;
}
"brynn"
所以引入了this
指向:指向调用方法的对象本身/指向window对象(在严格模式下this值是 undefined)
设置函数执行上下文中的this指向
bar{myname:'极客时间',test1;1}
mtObj{}
myObj{}
手写call/apply/bind
- call
testFun.call(alterObj, arg1, arg2, ...)
Function.prototype.myCall = function (alterObj, ...args) {
alterObj = alterObj || (typeof window === "undefined" ? global : window);
// originalObj.testFun.myCall(alterObj, 'argue1', 'argue2');this指向testFun
alterObj.tempFun = this; // 在对象上添加方法
alterObj.tempFun(...args); // 调用方法
delete alterObj.tempFun; // 删除方法
};
- apply
testFun.apply(alterObj, [arg1, arg2, ...])
注意一下展开运算符...:“args”是一个数组,“...args”是数组是的所有元素,“args和[...argus]”代表都是一个数组
Function.prototype.myApply = function (alterObj, [...args]) {
alterObj = alterObj || (typeof window === "undefined" ? global : window);
alterObj.tempFun = this;
alterObj.tempFun(...args);
delete alterObj.tempFun;
};
- bind
testFun.bind(alterObj, arg1, arg2, ...)
Function.prototype.myBind = function (alterObj, ...args) {
alterObj = alterObj || (typeof window === "undefined" ? global : window);
let self = this;
// this指向testFun
return function () {
// 这里面的bind指向alterFun
let addAguments = arguments;
self.call(alterObj, ...args, ...addAguments);
};
};
let originalObj = {
name: "originalBrynn",
testFun: function (m, n) {
console.log(this);
console.log(m, n);
}
};
let alterObj = {
name: "alterBrynn"
};
originalObj.testFun("argue1", "argue2");
let alterFun = originalObj.testFun.myBind(alterObj, "argue1");
alterFun("argue2");
this设计缺陷
- 嵌套函数中的this不会从外层函数中继承,永远是指向最近的调用对象
每一个执行上下文都会创建一个this(即每一个普通函数以及全局执行环境都创建了一个this,由于任何变量的查找都是通过一样的流程,会首先找到最近的this)
let bar = {
name: "brynn",
outerFun: function () {
let self = this;
console.log("外层函数中this指向bar对象", this);
function innerFun() {
console.log("内层函数中this指向全局window/undefined", this);
console.log("用self可以访问同一个对象", self);
}
innerFun();
}
};
bar.outerFun();
解决方法:
1. 声明一个変量self 用来保存this
2. 也可以使用ES6中的箭头函数
2. setTimeout推迟执行不合预期