前言,笔者目前已收到网易的offer,但由于学校与大环境的影响,目前拒掉了offer准备考研中,现在将面试准备的笔记上传到博客供大家学习交流,以后还会有补充。笔记来源丰富,转载都附上了链接,如有侵权请告知我删除
this指向详解,思维脑图与代码的结合,让你一篇搞懂this、call、apply。系列(一)
要点与补充:
调用构造函数实现继承:
function Product(name, price) {
this.name = name;
this.price = price;
}
function Food(name, price) {
Product.call(this, name, price); //
this.category = food;
}
var hotDog = new Food('hotDog', 20);
实现call
Function.prototype.setCall = function (obj) {
var obj = obj || window;
obj.fn = this;
var args = [];
for(var i = 1, len = arguments.length; i < len; i++) {
args.push('arguments[' + i + ']');
}
var result = eval('obj.fn(' + args +')');
delete obj.fn;
return result;
};
// 测试一下
var value = 2;
var obj = { value: 1 };
function bar(name, age) {
console.log(this.value);
return {
value: this.value,
name: name,
age: age
}
}
bar.setCall(null); // 2
console.log(bar.setCall(obj, 'yuguang', 18));
bind:返回一个原函数的拷贝,并拥有指定的 this 值
箭头函数的this从其作用链域的上一层获得
结合代码理解:
var name = 'window'
var person1 = {
name: 'person1',
show1: function () {
console.log(this.name)
},
show2: () => console.log(this.name),
show3: function () {
return function () {
console.log(this.name)
}
},
show4: function () {
return () => console.log(this.name)
}
}
var person2 = { name: 'person2' }
person1.show1() //返回person1,this作为对象的属性被调用,this为指向该对象(person1)
person1.show1.call(person2) //返回person2,this作为对象的属性被调用,this通过call绑定为person2
person1.show2() //返回window,箭头函数从自己作用域的上一层继承window
person1.show2.call(person2) //返回window,箭头函数从自己作用域的上一层继承window
person1.show3()() //返回window,person1.show3()返回一个普通函数,this作为普通函数被调用指向全局对象
person1.show3().call(person2) //返回person2,person1.show3()返回一个普通函数,this通过call绑定person2
person1.show3.call(person2)() //返回window,person1.show3的this绑定person2,通过调用返回一个普通函数,console 中的this作为普通函数被调用指向全局对象
person1.show4()() //返回person1,person1.show4()返回一个箭头函数,从作用链域上一层继承this
person1.show4().call(person2) //返回person2,person1.show4()返回一个箭头函数,从作用链域上一层继承this
person1.show4.call(person2)() //返回person2,person2.show4的this绑定person2,通过调用返回一个箭头函数, console中的this从作用链域上一层继承this
从原型到原型链,修炼JavaScript内功这篇文章真的不能错过!系列(二)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cxzJEBDq-1646397181608)(/Users/wangke/Desktop/收集/图片/format,png.png)]
构造函数和普通函数的区别在于,使用new生成实例的函数就是构造函数,直接调用的就是普通函数。
constructor
返回创建实例对象时构造函数的引用。此属性的值是对函数本身的引用,而不是一个包含函数名称的字符串。
模拟一个new
var objectNew = function(){
// 从object.prototype上克隆一个空的对象
var obj = new Object();
// 取得外部传入的构造器,这里是Person
var Constructor = [].shift.call( arguments );
// 更新,指向正确的原型
obj.__proto__ = Constructor.prototype; //知识点,要考、要考、要考
// 借用外部传入的构造器给obj设置属性
var ret = Constructor.apply(obj, arguments);
// 确保构造器总是返回一个对象
return typeof ref === 'object' ? ret : obj;
}
原型
__proto__
,这是一个访问器属性(即 getter 函数和 setter 函数),通过它可以访问到对象的内部[[Prototype]]
(一个对象或 null )。每个引用类型的隐式原型都指向它的构造函数的显式原型。(个人理解:相当于对象与类的关系)Object
构造函数生成的,实例的 __proto__
指向构造函数的 prototype
,可以理解成,Object.prototype()
是所有对象的根对象每个对象拥有一个原型对象,通过 __proto__
指针指向上一个原型 ,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层一层,最终指向 null
。这种关系被称为原型链 (prototype chain),通过原型链一个对象会拥有定义在其他对象中的属性和方法。
补充:
person.constructor === Person.prototype.constructor
(即person.__proto__.construct
) 因为当获取 person.constructor 时,其实 person 中并没有 constructor 属性,当不能读取到constructor 属性时,会从 person 的原型也就是 Person.prototype 中读取,正好原型中有该属性从作用域到作用域链,思维脑图+代码示例让知识点一目了然!系列(三)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QrkP185a-1646397181609)(/Users/wangke/Desktop/收集/图片/作用域与作用链域.png)]
变量的可用性范围
JavaScript 采用是词法作用域(lexical scoping),也就是静态作用域:
与之对应的还有一个动态作用域:
实例分析
var value = 1;
function foo() {
console.log(value);
}
function bar() {
var value = 2;
foo();
}
bar();
bar
函数,函数内部形成了局部作用域;foo
函数,函数foo的作用域内没有value
这个变量,它会向外查找foo
的外部作用域为全局作用域全局作用域与局部作用域
var a = 100;
function fn(){
a = 1000;
console.log('a1-',a);
}
console.log('a2-',a);
fn();
console.log('a3-',a);
// a2- 100 // 在当前作用域下查找变量a => 100
// a1- 1000 // 函数执行时,全局变量a已经被重新赋值
// a3- 1000 // 全局变量a => 1000
function fn(){
var name="余光";
function childFn(){
console.log(name);
}
childFn(); // 余光
}
console.log(name); // name is not defined
当查找变量的时候都发生了什么?
作用域链和原型继承查找时的区别:
作用域嵌套:既然每一个函数就可以形成一个作用域(词法作用域 || 块级作用域),那么当然也会存在多个作用域嵌套的情况,他们遵循这样的查询规则:
JavaScript中的执行上下文,既然遇见了这篇图文并茂的文章,干脆看完吧!(系列四)
定义
执行上下文的类型
JavaScript中的变量对象,简约却不简单(系列五)
当 JavaScript 代码执行一段可执行代码(executable code)时,会创建对应的执行上下文(execution context)。
对于每个执行上下文,都有三个重要属性:变量对象(Variable object,VO)、作用域链(Scope chain)、this
定义:在函数上下文中,我们用活动对象(activation object, AO)来表示变量对象,活动对象和变量对象其实是一个东西:
分类:
变量对象
就是全局对象!活动对象
(activation object,缩写为AO)扮演VO
的角色。执行过程
总结:
比较迷的思考题
console.log(foo);
function foo(){
console.log("foo");
}
var foo = 1;
JavaScript之深入理解立即调用函数表达式(IIFE),你对它的理解,决定了它的出镜率(系列六)
要点:
function
这个关键字,既可以当做语句,也可以当做表达式,如果function出现在行首,一律解析成语句,可以使用()括住,因为JavaScript里括弧()
里面不能包含语句场景:
注意:
JavaScript之闭包,给自己的Js一场重生(系列七)
定义:
解释:
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f;
}
var foo = checkscope();
foo(); // local scope
执行上下文栈和执行上下文的变化情况:
进入全局代码,创建全局执行上下文,全局执行上下文压入执行上下文栈
全局执行上下文初始化
执行 checkscope 函数,创建 checkscope 函数执行上下文,checkscope 执行上下文被压入执行上下文栈
checkscope 执行上下文初始化,创建变量对象、作用域链、this等
checkscope 函数执行完毕,checkscope 执行上下文从执行上下文栈中弹出
执行 f 函数,创建 f 函数执行上下文,f 执行上下文被压入执行上下文栈
f 执行上下文初始化,创建变量对象、作用域链、this等
f 函数执行完毕,f 函数上下文从执行上下文栈中弹出
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ctWPLxlS-1646397181611)(/Users/wangke/Desktop/收集/图片/闭包举例.png)]
f 执行上下文维护了一个作用域链:
f 函数依然可以读取到 checkscopeContext.AO
的值;
当 f 函数引用了 checkscopeContext.AO 中的值的时候,即使 checkscopeContext
被销毁了,JavaScript 依然会让 checkscopeContext.AO
活在内存中;
f 函数依然可以通过 f 函数的作用域链找到它,正是因为 JavaScript 做到了这一点,从而实现了闭包这个概念。
注意:
同一个上下文创建的闭包是共用一个[[scope]]
属性的,某个闭包对其中[[Scope]]
的变量做修改会影响到其他闭包对其变量的读取
思考
var arr = []
for(var i = 0; i < 10; i++){
arr[i] = function () {
console.log(i)
}
}
arr[0](); // 10
arr[1](); // 10
arr[2](); // 10
arr[3](); // 10
var arr = []
for(var i = 0; i < 10; i++){
arr[i] = (function (i) {
return function () {
console.log(i);
}
})(i)
}
arr[0](); // 0
arr[1](); // 1
arr[2](); // 2
arr[3](); // 3
JavaScript中的参数传递(求值策略),ECMAScript中所有函数的参数都是按值传递吗(系列八)
值传递
引用传递
(以下有疑问)
参数的值是调用者传递的对象值的拷贝(copy of value),函数内部改变参数的值不会影响到外面的对象(该参数在外面的值
按引用传递:函数内部对参数的任何改变都是影响该对象在函数外部的值,因为两者引用的是同一个对象,也就是说:这时候参数就相当于外部对象的一个别名。
共享传递不可能去解除引用和改变对象本身,但可以去修改该对象的属性值。
JavaScript中的基本数据类型,地基同样重要(系列九)
原始数据类型值 primitive type,比如Undefined
,Null
,Boolean
,Number
,String
。
栈
中var str = "123hello321";
str.toUpperCase(); // 123HELLO321
console.log(str); // 123hello321
var a = 1;
var b = true;
console.log(a == b); // == 只进行值的比较
console.log(a === b); // === 不仅进行值得比较,还要进行数据类型的比较
引用类型值,也就是对象类型 Object type,比如Object
,Array
,Function
,Date
等。
可变
的。钥匙
存储钥匙地址
的大小是固定的,所以把它存储在栈中对变量性能无任何负面影响。var obj1 = {}; // 新建一个空对象 obj1
var obj2 = {}; // 新建一个空对象 obj2
console.log(obj1 == obj2); // false
console.log(obj1 === obj2); // false
注意:
变量a
可以随时持有任何类型的值
。换个角度来理解就是,JavaScript不做“类型强制”;也就是说,语言引擎不要求变量总是持有与其初始值同类型的值。JavaScript专题(六)类型检测
typeof检测:
注意:typeof null => 'object'
且 typeof function(){} => 'function'
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZAmmKqXQ-1646397181616)(/Users/wangke/Desktop/收集/图片/typeof.png)]
// 基本数学API和属性
typeof Math.LN2 === 'number'; // true Math的属性
typeof Infinity === 'number'; // true 无穷
typeof NaN === 'number'; // true 特殊的数字类型,not a number
// 被强转称数字的其他数据类型
typeof Number('str') === 'number'; // Number('str') => NaN => number
typeof (typeof 1) === 'string'; // typeof always returns a string
typeof String(1) === 'string'; // 强转成字符串
typeof Boolean(1) === 'boolean'; // 强制类型转换
typeof !!(1) === 'boolean'; // two calls of the ! (logical NOT) operator are equivalent to Boolean()
typeof Symbol() === 'symbol'
typeof Symbol('foo') === 'symbol'
typeof undefined === 'undefined';
typeof { name: '余光' } === 'object';
typeof null === 'object'; // true,值得我们注意恰恰是这个null,typeof 对它的处理返回的是object
//typeof检测函数返回的也是object,这是因为从规范上看function实际上是object的一个子类型。
typeof function() {} === 'function';
typeof class C {} === 'function';
null和undefined
null:特指对象的值未设置。它是 JavaScript 基本类型 之一。它不是全局对象的一个属性;在 API 中,null 常在返回类型应是一个对象,但没有关联的值的地方使用。
undefined:表示声明但未被赋值的变量类型,你可以使用undefined和严格相等或不相等操作符来决定一个变量是否拥有值。
当检测 null 或 undefined 时,注意相等 ==
与===
两个操作符的区别 ,前者会执行类型转换
typeof null // "object" (因为一些以前的原因而不是'null')
typeof undefined // "undefined"
null === undefined // false
null == undefined // true
null === null // true
null == null // true
!null //true
isNaN(1 + null) // false
isNaN(1 + undefined) // true
instanceof检测
constructor检测
Object.prototype.toString
对象
;JavaScript专题(七)类型转换
显式类型转换
const str = String(1);
const num = Number("123.3"); //number:123.3
隐式类型转换
const newStr1 = 1 + "str"; // '1str'
const newStr2 = "hello" + [89, 150.156, "mike"]; // 'hello89,150.156,mike'
注意:
true
console.log(Boolean(new Boolean(false))); // true
toString
null
和undefined
之外的任何值都具有toString
方法,通常情况下,它和使用String
方法返回的结果一致。Object.prototype.toString
方法会根据这个对象的[[class]]内部属性,返回由 "[object " 和 class 和 “]” 三个部分组成的字符串。[1, 2, 3, 4].toString(); // "1,2,3,4"
[].toString(); // ""
function func() {
console.log();
}
func.toString(); // "function func () { console.log() }"
valueOf
ToPrimitive//???没弄懂
ToPrimitive
方法,将其转为基本类型,然后再参照 “原始值转字符” 的对应表进行转换。JavaScript专题(一)变量提升与预编译,一起去发现Js华丽的暗箱操作
引擎会在解释JavaScript代码之前首先对其进行编译。编译阶段中的一部分工作就是找到所有的声明,并用合适的作用域将它们关联起来
foo();
var foo; // 1
function foo(){
console.log('余光');
}
foo = function(){
console.log('小李');
}
//解析成以下
function foo(){
console.log('余光');
}
foo(); // 余光
foo = function(){
console.log('小李');
}
var foo
因为是一个重复声明,且优先级低于函数声明
所以它被忽略掉了。
foo();
var foo = function(){
console.log(1);
}
// TypeError: foo is not a function
这段程序中:
总结:
JavaScript专题(二)数组去重,会就要会的理直气壮
双层循环
indexOf includes 只需一层循环
sort排序+push到新数组
filter
键值对
obj
的key
而不管他们的value
数字1
和字符串‘1’
是不同的,但保存到对象中时会发生隐式类型转换,导致去重存在一定的隐患。typeof item + item
拼成字符串作为 key 值来避免这个问题:ES6
set 注意转换:[…new Set(array)]或 Array.from(new Set(arr))
map对象
Map 对象保存键值对,并且能够记住键的原始插入顺序。任何值(对象或者原始值) 都可以作为一个键或一个值。
function unique (arr) {
const newMap = new Map()
return arr.filter((a) => !newMap.has(a) && newMap.set(a, 1));
}
JavaScript专题(三)防抖
//仅作初略了解
目的:降低一个函数的触发频率,以提高性能或避免资源浪费
原理:事件触发n秒无操作后
才执行
JavaScript专题(四)节流
核心:如果你持续触发某个事件,特定的时间间隔内,只执行一次
两种实现方式:
now
;prev
;now - prev > wait
,证明时间区间维护结束,执行指定事件,更新prev
;timer
,记录当前是否在周期内;wait
时间之后再次执行,并清掉定时器;JavaScript专题(五)深浅拷贝
浅拷贝:
深拷贝:
JSON实现
arr
JSON 字符串
值或对象
新字符串
,再通过新字符串
还原为一个新对象
,这中改变数据类型的方式,间接的绕过了拷贝对象引用的过程,也就谈不上影响原始数据。deepCopy实现
var deepCopy = function(obj) {
if (typeof obj !== 'object') return;
var newObj = obj instanceof Array ? [] : {};
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];
}
}
return newObj;
}
ES6基础:let和const
let拒绝提升
经典的问题的for循环问题
const arr = [];
for (var i = 0; i < 10; i++) {
arr[i] = () => { console.log(i) }
}
arr[0](); // 10
arr[1](); // 10
arr[2](); // 10
变量i
是let
声明的,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量,虽然输出的变量都叫i
,但他们已经存在于不同的作用域之中了。let不能在同一个代码块中重复声明
let 声明的变量存在“暂时性死区”
if (true) {
// TDZ开始
tmp = 'abc'; // ReferenceError
console.log(tmp); // ReferenceError
let tmp; // TDZ结束
console.log(tmp); // undefined
tmp = 123;
console.log(tmp); // 123
}
ES6基础:变量的解构赋值
数组解构赋值的作用:
同时赋值多个变量
解构嵌套数组
相同“模式”的不完全解构
let [a, b, c] = [1, 2, 3, 4]; // 1 2 3
let [a, b, c, d] = [1, 2, 3]; // 1 2 3 undefined
let [a, [b, c, [d, e]]] = [1, [2, 3, [4, 5, 6]]]; // 1 2 3 4 5
//可以赋默认值
let [a = true] = [];
a // true
//注意,ES6 内部使用严格相等运算符(===),判断一个位置是否有值。所以,只有当一个数组成员严格等于undefined,默认值才会生效。
let [x = 1] = [undefined];
x // 1
let [x = 1] = [null];
x // null
对象的解构赋值:
let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
baz // "aaa"
let obj = { first: 'hello', last: 'world' };
let { first: f, last: l } = obj; // 起了个别名
f // 'hello'
l // 'world'
函数参数的解构赋值:
解构对象类型参数
function fetch(option){
var name = option.name;
var age = option.age;
var like = option.like;
}
// ES6
function fetch({ name, age, like }) {
// 参数经历了 let { name, age, like } = option
}
解构数组类型参数
const arr = [
[1, 1],
[2, 2],
[3, 3],
[4, 4]
];
const newArr = arr.map(([x, y]) => x + y);
newArr // [ 2, 4, 6, 8 ]
为参数设定默认值,可以避免许多拿不到数据的情况
function func({ name = '余光', age = 23, like = 'FE' }) {
console.log(name, age, like);
}
const options = { name: '余光' }
func(options);
ES6基础:箭头函数
箭头函数和普通函数的区别:
没有this
没有 arguments
arguments
对象,这不一定是件坏事,因为箭头函数可以访问外围函数的 arguments 对象不能通过 new 关键字调用
[[Construct]]
方法,不能被用作构造函数,如果通过 new 的方式调用,会报错ES6基础:Iterator和for…of
迭代器
Iterator
接口的目的,就是为所有数据结构,提供了一种统一的访问机制。当使用 for…of 循环遍历某种数据结构时,该循环会自动去寻找 Iterator 接口for…of
Symbol.iterator
属性,就被视为具有 iterator
接口,就可以用for...of
循环遍历它的成员。for...of
循环内部调用的是数据结构的Symbol.iterator
方法var arr = ["a", "b", "c", "d"];
//for…in 循环读取键名
for (let a in arr) {
console.log(a); // 0 1 2 3
}
//for…of 循环读取键值
for (let a of arr) {
console.log(a); // a b c d
}
//多行字符串
let multiStr = `
模块标题
`;
//嵌入变量
for(var i = 6; i < 10; i++){
var strHtml = `${i}个元素`;
$(".list").append(strHtml);
class Thermostat{
constructor(Fahrenheit ){
this._Fahrenheit = Fahrenheit ;
}
//getter
get temperature(){
return 5/9 * (this._Fahrenheit - 32);
}
// setter
set temperature(Celsius){
this._Fahrenheit = Celsius * 9.0 / 5 + 32;
}
}
const thermos = new Thermostat(76); // Setting in Fahrenheit scale
let temp = thermos.temperature; // 24.44 in Celsius
thermos.temperature = 26;
temp = thermos.temperature; // 26 in Celsius
//export共享代码
ES5:
export const add = (x, y) => {
return x + y;
}
ES6:
const add = (x, y) => {
return x + y;
}
export { add, subtract };
//import复用代码
import { add, subtract } from './math_functions.js';
//或者:
import * as myMathModule from "./math_functions.js";
myMathModule.add(2,3);
myMathModule.subtract(5,3);
//export default
export default function add(x, y) {
return x + y;
}
export default function(x, y) {
return x + y;
}
//default只能有一个,两种方式都可以
import add from "./math_functions.js";
//add具体取什么名字是无所谓的
链接:Promise对象详解
Promise是抽象异步处理对象以及对其进行各种操作的组件。
有三种状态
创建Promise对象分为两步
创建Promise对象
new Promise(function(resolve,reject){
//业务逻辑
//处理结果正确就调用resolve方法
//处理逻辑错误叫调用reject方法
})
调用Promise实例的then()、catch()方法,异步处理完成就执行then方法,处理异常就执行catch方法。
function asyncTest(){
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve('Async Hello world');//使promise对象立即进入onResolved状态,并且将value参数传给then方法
//reject(error); //使promise对象立即进入onRejected状态,并且将error参数传给catch方法
}, 16);
})
}
asyncTest().then(function (value) { //then(fn):该方法是用来注册Promise实例的状态为onFulfilled时的回调函数
console.log(value); // => 'Async Hello world'
}).catch(function (error) { //catch(fn):该方法是用来注册Promise实例的状态为onRejected时的回调函数
console.log(error);
})
注意:then和catch返回的都是一个全新的Promise对象,在执行这两个方法后,不论返回的数据类型是什么,都会被包装成一个全新的Promise对象。返回的数据会传递到下一个链式方法里作为参数。
Promise.all(array):
var promise1 = new Promise(function (resolve){
resolve("promise1 is resolve");
});
var promise2 = new Promise(function (resolve){
resolve("promise2 is resolve");
});
function main(){
return Promise.all([promise1,promise2])
}
main().then(function(value){
console.log(value)
})
//执行结果:
//["promise1 is resolve", "promise2 is resolve"]
Promise.race(array):
// delay毫秒后执行resolve
function timerPromisefy(delay) {
return new Promise(function (resolve) {
setTimeout(function () {
resolve(delay);
}, delay);
});
}
// 任何一个promise变为resolve或reject 的话程序就停止运行
Promise.race([
timerPromisefy(1),
timerPromisefy(32),
timerPromisefy(64),
timerPromisefy(128)
]).then(function (value) {
console.log(value); // => 1
});
async和await的讲解
async function demo() {
let result01 = await sleep(100);
//上一个await执行之后才会执行下一句
let result02 = await sleep(result01 + 100);
let result03 = await sleep(result02 + 100);
// console.log(result03);
return result03;
}
demo().then(result => {
console.log(result);
});
温故知新(六九)const p1 = () => (new Promise((resolve, reject) => { console.log(1); let p2 = new Pr
const p1 = () => (new Promise((resolve, reject) => {
console.log(1);
let p2 = new Promise((resolve, reject) => {
console.log(2);
const timeOut1 = setTimeout(() => {
console.log(3);
resolve(4);
}, 0)
resolve(5);
});
resolve(6);
p2.then((arg) => {
console.log(arg);
});
}));
const timeOut2 = setTimeout(() => {
console.log(8);
const p3 = new Promise(reject => {
reject(9);
}).then(res => {
console.log(res)
})
}, 0)
p1().then((arg) => {
console.log(arg);
});
console.log(10);
答案
1
2
10
5
6
8
9
3
解析:
第一步:浏览器开始将 script 标签包裹的所有代码看作宏任务执行,发现有普通的函数 p1,宏任务 timeOut2,使其进入宏任务队列,微任务 p1.then 进入微任务队列,普通的函数 p1 入栈,输出 1,普通函数 p2 入栈输出 2,有宏任务 timeOut1 入列,微任务 p2.then 入列。
第二步:宏任务执行完之后找微任务,队列是先进先出,then 按照 resolve 处理顺序,先执行的是 p2 中的 resovle 所以执行 p2.then,输出 5,再执行 p1 的 resolve,执行 p1.then,输出 6
第三步:微任务执行完后执行宏任务,由于两个 timeout 等待时间一样,按照队列顺序先执行 timeOut2 ,输出 8,p3.then 微任务入列,宏任务执行完毕后执行微任务 p3.then 输出 9。
第四步:执行 tineOut1,输出 3,由于 p2 的 promise 对象以及被执行过所以 4 不再执行
每日一题(五)async function async1() {console.log(‘async1 start’);await async2();console.log(‘async1 end’)
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(()=>{
console.log('setTimeout');
},0)
async1();
new Promise((resolve)=>{
console.log('promise1');
resolve();
}).then(()=>{
console.log('promise2');
});
console.log('script end');
答案
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
考察的是事件循环和回调队列。注意以下几点:
解析
- 首先,事件循环从宏任务(macrostack)队列开始,这个时候,宏任务队列中,只有一个 script (整体代码)任务。从宏任务队列中取出一个任务来执行。
- 首先执行 console.log(‘script start’),输出 ‘script start’
- 遇到 setTimeout 把 console.log(‘setTimeout’) 放到 macrotask 队列中
- 执行 aync1() 输出 ‘async1 start’ 和 ‘async2’ ,把 console.log(‘async1 end’) 放到 micro 队列中
- 执行到 promise ,输出 ‘promise1’ ,把 console.log(‘promise2’) 放到 micro 队列中
- 执行 console.log(‘script end’),输出 ‘script end’
- macrotask 执行完成会执行 microtask ,把 microtask quene 里面的 microtask 全部拿出来一次性执行完,所以会输出 ‘async1 end’ 和 ‘promise2’
- 开始新一轮的事件循环,去除执行一个 macrotask 执行,所以会输出 ‘setTimeout’
Javascript异步编程的4种方法