重学前端学习笔记之JavaScript执行
(一)JavaScript中的事件循环
1、JavaScript引擎存在于浏览器或者node环境中,它们拿到一段JS代码,就会传递给JavaScript引擎,让他们执行。
2、因为宿主环境以及各种API的变化,所以JS代码不是一次执行就可以完成的,而是不断会有js代码需要执行,因此js引擎会长蛀于内存,等待js代码,然后执行它
3、宿主环境发起的任务称为宏任务,js引擎发起的称为微观任务
4、事件循环
事件循环就是js引擎等待宿主环境分配宏观任务的等待行为。
它的实现就是在底层的代码中,大概像下面这样的伪代码
while (true){
r = wait();
execute(r);
}
此外需要注意的是,事件循环是一个独立线程,它主要的任务就是反复的等待-执行,而这个执行的过程,就是一个宏任务。
所以,可以这么理解,宏任务的队列就相当于事件循环
此外,在宏观任务里面,js还会产生异步代码,js必须要保证这些异步代码在一个宏任务中完成,因此,每个宏任务又包括一个微任务队列。(setTimeOut是一个宏任务,promise是一个微任务)
6、promise
promise是js提供的一种标准的异步管理方式管理方式,在进行IO、等待或者其他异步操作的函数,不返回结果,而是返回一个promise,函数的调用可以在合适的时机中选择等待完成这个promise(then)。
function sleep(duration) {
return new Promise((resolve, reject)=>\{
setTimeout(resolve,duration);
\})
}
sleep(1000).then(()=>{console.log(“finished”)});
分析异步执行的顺序
- 首先分析有多少个宏任务(由setTimeOut分隔)
- 在每个宏任务中,分析有多少个微任务
- 根据调用顺序,确定宏任务中微任务的执行顺序
- 根据宏任务的触发规则和调用顺序,确定宏任务的执行顺序
- 确定整个顺序。
7、两个新特性async,await
这两个特性都是基于promise的,async返回的是一个promise
(1)如何定义一个async函数?
在函数function前面加一个async关键字
function sleep(duration) {
return new Promise((resolve, reject)=>\{
setTimeout(resolve,duration);
\})
}
async function foo() {
console.log("a");
await sleep(2000);
console.log("b");
}
用上面只是写一个红绿灯demo
function sleep(duration) {
return new Promise((resolve, reject)=>\{
setTimeout(resolve,duration);
\})
}
async function changeColor(duration,color) {
document.getElementById("traffic-lights").style.backgroundColor = color;
await sleep(duration);
}
async function trafficLights() {
while (true){
await changeColor(3000,"green");
await changeColor(2000,"red");
await changeColor(1000,"yellow");
}
}
trafficLights();
(二)闭包和执行上下文
1、闭包就是一个绑定了执行环境的函数,携带了执行环境。其中环境部分是函数词法环境部分的组成,标识符列表是函数中用到的未声明变量,表达式部分是函数体。
2、执行上下文
js标准把一段代码(包括函数)执行所需的信息定义为执行上下文。
(1)在ES5中,执行上下文包以下部分
- 词法环境,获取变量时使用
- 变量环境,声明变量时使用
- this值
(2)在ES2018中,执行上下文包括以下部分
- 词法环境,this值也绑定在词法环境中,获取变量或者获取this值时使用
- 变量环境,声明变量时使用
- code evaluation state,代码执行状态,用于恢复代码执行位置
- Function执行的任务是函数时使用,表示正在执行的函数
- ScriptOrModule 执行任务是脚本或者模块时使用,表示正在执行的代码
- Realm 使用的基础库和内置对象实例
- Generator:生成器上下文拥有,表示当前的生成器
(3)当执行一段代码的时候,发生了什么事情?
var b = {};
let c = 1;
this.a = 2;
var声明变量到哪里?
声明的变量b表示哪个变量,它的原型是谁?
let将c声明到哪里?this指向哪个对象
这些问题都是属于执行上下文范畴,每次执行代码都会关联到不同的执行上下文
首先回答第一个问题,var声明作用域函数执行的作用域。也就是当前函数所在的环境。
第二个问题,声明的变量b表示的是该作用域的b,它的原型是对应的值的引用类型,比如上面的b是一个对象,那么它的原型就是function对象,如果上面b是一个数字,那么它的原型就是Number,以此类推。
再说一下立即执行函数,通过创建一个函数,并且立即执行,来构造一个新的作用域,从而控制var的范围。常见的立即执行函数的写法:
(function () {
console.log(“a”);
}());
(function () {
console.log("b")
})();
void function () {
console.log("c")
}();
再来回答第三个问题,let声明的作用域是该块级作用域范围,this的值为当前执行上下文的词法环境中的thisModule。
Relam
Relam中包含一组完整的内置对象,而且是复制对象。
(三)函数,在函数的执行机制上面理解
1、函数调用的时候,执行上下文会进行切换
2、函数的分类
(1)普通函数,用function关键字定义的函数
(2)箭头函数,用=>运算符定义的函数
(3)class中定义的方法
class PEOPLE{
say()\{
\}
}
(4)生成器函数,用function*定义的函数
function * foo() {
yield console.log("liang");
}
let liang = foo();
liang.next();
(5)类,用class定义的类实际上也是函数
class Foo{
constructor()\{
\}
}
(6)异步函数,在普通函数和生成器函数、箭头函数前面加async
async function foo() {
console.log("a");
}
async function * foo() {
console.log("a");
}
3、this关键字的行为
(1)普通函数、带async的普通函数、生成器函数、带async的生成器函数
这类的函数是由调用它所使用的引用来决定this的值,调用的时候this的值会发生变化。
当我们获取函数的表达式(使用函数)时,返回的是Reference类型,这个类型由两个部分组成,一个对象和一个属性值,在使用这个函数的时候,此时this的值是这个对象,传入了执行函数的上下文。
function foo() {
console.log(this);
}
foo();//window
当我们像下面这样将这个函数传给一个对象,然后调用该对象的函数,此时this的值会发生变化
function foo() {
console.log(this);
}
let o = {
foo:foo
};
o.foo();//{foo: ƒ}指向的是o
(2)箭头函数、带async的箭头函数
包裹箭头函数的作用域,指向箭头函数执行上下文中的词法环境,无论怎么调用,this的值始终如一。
(3)class定义的函数
等于实例化后的对象,如果将class里面的方法赋给外面的变量,此时外面变量调用该方法的this值为undefined。
4、this关键字的机制
(1)函数拥有一个保存定义该函数时上下文的私有属性[[Enviornment]]
(2)当一个函数执行的时候,会创建一条新的执行环境记录,记录的外层词法环境会被设置为该函数的[[Enviornment]],也就是此时this的值会等于该**[[Enviornment]]**
(3)函数可以访问定义时词法环境,但不能访问执行时词法环境。
(4)js用一个栈来管理上下文,这个栈每一项又包含一个链表,当函数调用时,会入栈一个新的执行上下文,函数调用结束的时候,该执行上下文出栈。
(5)[[thisModel]]私有属性有三个取值
- lexical,表示从上下文中找this,对应箭头函数
- global,当this的值为undefined的时候,取值为全局对象,对应普通函数
- strict,严格按调用时传入的值,可能null或者undefined。(非常有意思的是,方法的行为跟普通函数有差异,class 设计成了默认按 strict 模式)
(6)调用函数创建新的执行上下文中词法环境,会根据[[thisModel]]来标记新记录的[[ThisBindingStatus]],代码执行遇到this的时候,会逐层检查当前词法环境记录中的[[ThisBindingStatus]],直至找到值。
(7)嵌套的箭头函数的this都指向最外层的this。
如下面代码
var o = {};
o.foo = function foo() {
console.log(this);
return ()=>\{
console.log(this);
return ()=>console.log(this);
\}
}
o.foo()()();
5、操作this的内置函数(可以改变this值的函数)
apply,call,bind
(四)js的语句执行机制—Completion
1、js语句执行的完成状态—Completion Record,表示一个语句执行之后的结果,有三个字段。
- [[type]],表示完成的状态,取值有break continue return throw 和normal
- [[vaue]]语句的返回值,如果语句没有,则值为empty,一般只有表达式语句拥有。
- [[target]]表示语句的目标。通常是一个js语句标签。
2、那么在completion record类型下面,js控制语句执行的过程是怎样的?
js语句的分类如下:
(1)普通语句
普通语句执行时(有var会有预处理),从前到后顺序执行,执行后会得到[[type]]的值为normal的Completion Record,js引擎遇到这样的语句,会继续执行下一条语句,只有表达式语句会产生[[value]]。
(2)语句块
- 语句块就是用大括号{}括起来的一组语句,它是一种语句的符合结构,可以嵌套
- 如果一个语句块只含有[[type]]为normal的语句,那么这个语句块里面的语句顺序执行,整个语句块的[[type]]也为normal。
- 如果一个语句块含有[[type]]为非normal的语句,那么整个语句块的[[type]]也非normal。
(3)控制语句
- 控制性语句带有if,switch等关键字,它们会对不同类型的Completion Record产生反应。
- 对语句内部产生影响的有if,switch,while,for,try,对语句外部产生影响的有break,continue,return,throw,两者配合,控制代码执行顺序和逻辑
- 即使try或者catch里面的语句执行完毕,如果有finally,也会执行finally里面的语句。
(4)带标签的语句
JavaScript语句是可以加标签的,如
firstStatement: var i = 1;
作用:与Completion Record中的target相对于,用于跳出多层循环。
outer: while(true) \{
inner: while(true) \{
break outer;
\}
\}
console.log("finished")
(5)因为js语句存在嵌套关系,所以执行过程实际上主要在一个树形结构上完成,树形结构上的每一个节点执行后产生Completion Record,根据语句的结构和Completion Record,js实现了各种分值和跳出逻辑。
还没有评论,来说两句吧...