1.js诞生于1997年,Brendan Eich10天设计出来的,当时java很火,为了蹭热度,网景公司将其取名为JavaScript,两者除了语法有点像,啥关系都没有。
2.ECMAScript是一种语言标准,JavaScript是网景公司对ECMAScript标准的一种实现。
3.直接在硬盘上创建好HTML和Javascript文件,然后用浏览器打开,这种方式运行部分js代码没问题,但由于浏览器的安全限制,以file://开头的地址无法执行如联网等js代码,最终,我们还是需要架设一个web服务器,然后以http://开头的地址来正常执行所有js代码。
4.js严格区分大小写。
5.相等运算符:== 会自动转换数据类型再比较,很多时候,会得到非常诡异的结果; === ,不会自动转换数据类型,如果数据类型不一致,返回false,如果一致,再比较。由于这个设计缺陷,不要使用==比较,使用===比较。
6.js数据类型Number、String、Boolean、Null、undefined、symbol、Object
1)Number:数字类型,js不区分整数和浮点数,统一用Number表示,infinite,无限大,当数字计算后超出Number所能表示的最大值时,用infinite表示比如2/0,可以对任何数字调用isFinite()以确保该数不是无穷大,还有个特殊的NaN,表示非数(not a number),一般来说,得出NaN是在类型转换失败时,NaN和自身也不相等,做判断时可用isNaN().
2)String:字符串,由‘’和“”表示,字符串中出现’或“可以使用转义符\进行转义,例如'I\'m'==I'm;多行字符串可以使用反引号``,这是es6新增的字符串模板,在模板中,也能使用${name}来填写变量数据,字符串可以使用下标来获取对应位置的字符,但是字符串是不可更改的,所以使用下标来改变字符串是无效的(s[0]='a'),但不会报错,字符串常用函数:toUpperCase()字符串大写,toLowerCase()字符串转成小写,indexOf()返回指定字符首次出现的位置,没有返回-1,substring()截取区域字符。
3)array:数组,可以包含任意数据类型,并通过索引来访问每个元素。直接给array的length赋值,会改变数组的长度,新增的数组元素是undefined,通过索引赋值时,如果索引超出数组长度,同样会改变数组的长度,其他新增的元素是undefined,数组常用函数:indexof()返回某个元素在数组中首次出现的位置,没有返回-1,slice()类似与字符串的substring,截取指定区域的数组元素,返回一个新的array,slice的起止参数包换开始索引,不包含结束索引,如果slice没有参数,它会从头到尾截取所有参数,利用这一点,可以复制一个数组。push和unshift向数组的结尾或开始添加数个元素,pop和shift删除数组的结尾或开始的第一个元素,sort()排序功能,reverse()反转整个数组,splice()从指定位置删除若干元素,再从该位置添加若干元素。concat()拼接任意数组,join()将数组的元素由指定字符链接起来,返回链接后的字符串。
4)对象是一种无序的集合数据类型,它由若干键值对组成,键值对以逗号隔开,使用 . 或 [ ] 来访问属性字符时要用引,当键名包含特殊号‘’扩起来并且无法使用 . 来访问,只能用 ['xxx'] 来访问,js的对象是一种动态类型,可以自由添加或删除属性:delete删除:delete object['xxx']; 检测对象中是否存在某一属性可以使用in或hasOwnPropert()方法: ’name' in xiaoming 或xiaoming.hasOwnPropert('name'),in不仅判断对象本身它还会判断对象继承到的。
8.变量名不能是数字开头,也不能是js的关键字。
9.strict模式,当一个变量没有通过var声明就被使用,那么该变量就自动被声明为全局变量,同名变量在一个页面会互相冲突覆盖,所以我们需要用var来声明一个非全局变量,把它的返回限制在该变量被申明的函数体内,同名变量在不同的函数体内互不冲突。使用strict模式,会强制通过var申明变量,未使用var申明变量就使用的,将导致运行错误。启用strict模式的方法是在js代码第一行写上 “use strict”; 这是一个字符串,不支持strict模式的浏览器会把它当作一个字符串语句执行,支持strict模式的将开启strict模式运行js。
10.js把null undefinde NaN 0 '' 视为false,其他都为true.
11.for...in..:把对象的所有属性循环出来
var o = {a:1,b:2} ;
for(var key in o){
console.log(key)
};
12.
map:js默认对象表示方式{}是键值对,但键必须是字符串,实际上Number或其他数据类型作为键也是合理的,为解决这个问题,es6引入了新的数据类型Map。map是一组键值对的结构,具有极快的查找速度。
初始化map需要一个二维数组
var m = new Map([['name','God'],['sex','man']]);
m.get('name')//God
,或者直接初始化一个空Map.Map具有以下方法:
var m = new Map();//空map
m.set('name','God')//添加新的key-value
m.has('name');//是否存在key'name',true
m.get('name');//God
m.delete('name')//删除key'name'
m.get('name');//undefined
m.clear();//清空Map
由于一个key只能对应一个value,所以,多次对一个key放入value,后面的值会把前面的值覆盖掉。
13.
Set:Set和数组类似,不存储value。由于key不能重复,所以,在Set中,没有重复的key.。
要创建一个Set,需要提供一个Array作为输入,或者直接创建一个空Set:
var s1=new Set()//空Set
var s2 = new Set([0,1,2,3]);//含1,2,3
重复的元素在set中自动过滤
var s = new Set([0,0,1]);
s;//Set {0,1}
s.size//2
在Set内部,两个NaN
是相等。两个对象总是不相等的。可以用length来检测
四个操作方法:
add(value)
:添加某个值,返回Set结构本身。delete(value)
:删除某个值,返回一个布尔值,表示删除是否成功。has(value)
:返回一个布尔值,表示该值是否为Set
的成员。clear()
:清除所有成员,没有返回值
set内部的元素可以遍历for...of...
14.forEash,和map()方法类似,把元素作用于传入的函数中,但不会返回新的数组,常用于便利数组。
forEash,接收一个函数,每次迭代就自动回调该函数。
var a = [1,2,3];
a.forEash(function(value,index,arr){
// value: 指向当前元素的值
// index: 指向当前索引})
15.for ...of...:循环遍历array Map Set等iterable类型
16.函数就是最基本的一种代码抽象的方式。
17.arguments:当前函数的调用者传入的所有参数,类似数组但不是一个数组。
18 ...rest:当前函数除了指定的参数外,其他多余传入的参数以数组的形式交给变量rest,rest只能写在最后,前面用...标识。
19.变量的作用域在自身定义的函数里,在函数体外不可引用该变量,内部函数可以调用外部函数的变量,在查找函数变量的时候是从自身函数定义开始,由内而外,如果内部函数定义了与外部函数重名的变量,内部函数的变量将屏蔽外部函数的变量。
变量提升:js的函数定义有个特点,会先扫描整个函数体的语句,然后把所有申明的变量提升到函数顶部,但它只会提升变量的声明,不会提升变量的赋值。
全局作用域:不再任何函数内部定义的变量就具有全局作用域。js默认有一个全局对象window,全局作用域的变量实际上是被绑定到window的一个属性。访问alert和访问window.alert()是一个效果。
名字空间:全局变量都会绑定w到indow上,当全局变量使用了同一个名字就会造成命名冲突,减少冲突一个方法是把自己的变量和函数全部绑定到一个全局变量上,例如jq。
局部作用域:变量的作用域实际上是函数内部,在for等语句块中是无法定义具有局部作用域的变量的:
function foo() {
for (var i=0; i<100; i++) {
//
}
i += 100; // 仍然可以引用变量i
}
为解决这个问题,es6引入关键字let,let代替var可以申明一个块级作用域的变量
20.解构赋值:直接对多个变量同时赋值。
对数组元素进行解构赋值,多个变量要用 [ ] 扩起来
例如:
var [x,y,z]=['a','b','c'];//x=a,y=b,z=c
或
var arr = ['a','b','c'];
var [x,y,z]=arr;
console.log(x)//a
如果数组本身还有嵌套,解构赋值时嵌套层次和位置要保持一致
例如:let [x,[y,z]] = ['a',['b','c']];//x=a,y=b,z=c;
解构赋值还可以忽略某些元素
例如:let [,,z] = ['a','b','v'] //z=v;
如果需要从一个对象中取出若干属性,也可以使用解构赋值,便于快速获取对象的指定属性:
例如
var person={
name:'小明',
age:20,
sex:"男"
}
var {name,age}=person;//name=小明,age=20
对对象解构赋值,同样可以对嵌套对象属性进行赋值,之间要对应的层次是一致的:
例如:
var person = {
name: '小明',
age: 20,
gender: 'male',
passport: 'G-12345678',
school: 'No.4 middle school',
address: {
city: 'Beijing',
street: 'No.1 Road',
zipcode: '100001'
}
};
let {name,age,address:{city,zip}}=person;
//name=小明,age=20,city=Beijing,zip=undefined
//address不是变量,是为了让city和zip获得嵌套的address对象的属性
对象属性使用解构赋值时,如果对应属性不存在,变量将被赋值undefined,如果要使用的变量名和属性名不一项可以使用属性名:变量名
例如:
var person = {
name: '小明',
age: 20,
gender: 'male',
passport: 'G-12345678',
school: 'No.4 middle school'
};
// 把passport属性赋值给变量id:
let {name, passport:id} = person;
name; // '小明'
id; // 'G-12345678'
// 注意: passport不是变量,而是为了让变量id获得passport属性:
passport; // Uncaught ReferenceError: passport is not defined
解构赋值还可以使用默认值,这样就避免了不存在的属性返回undefined的问题:
var person = {
name: '小明',
age: 20,
gender: 'male',
passport: 'G-12345678'
};
// 如果person对象没有single属性,默认赋值为true:
var {name, single=true} = person;
name; // '小明'
single; // true
有些时候,如果变量已经被声明了,再次赋值的时候,正确的写法也会报语法错误:
// 声明变量:
var x, y;
// 解构赋值:
{x, y} = { name: '小明', x: 100, y: 200};
// 语法错误: Uncaught SyntaxError: Unexpected token =
这是因为JavaScript引擎把{
开头的语句当作了块处理,于是=
不再合法。解决方法是用小括号括起来:
({x, y} = { name: '小明', x: 100, y: 200});
21.this:谁调用它,它就指向谁。
当我们要改变函数内的this指向时,使用apply或者call
22.apply(obj,array),apply指定函数的this指向,它有两个参数,第一个参数就是需要绑定this的变量,第二个是函数本身的参数
例如:getAge.apply('person',[]);//此时getAge内部的this指向person,参数是空的数组
function getAge() {
var y = new Date().getFullYear();
return y - this.birth;
}
var xiaoming = {
name: '小明',
birth: 1990,
age: getAge
};
xiaoming.age(); // 25
getAge.apply(xiaoming, []); // 25, this指向xiaoming, 参数为空
23.call,类似与apply,唯一的区别是,call的参数是按照顺序传入的,apply参数是按照数组传入的。
Math.max.apply(null, [3, 5, 4]); // 5
Math.max.call(null, 3, 5, 4); // 5
24.map():array的方法,参数是一个函数,函数有三个参数(value,index,arr),,将这个函数作用于数组的每个元素上,返回一个新数组,不改变原数组。
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
arr.map(String); // ['1', '2', '3', '4', '5', '6', '7', '8', '9']
25.filter():array的方法,它是一个实现筛选的高级函数,和map方法类似,接收一个函数,函数有三个参数(value,index,arr),函数作用于数组的每个元素上,不同的是,它根据返回值是false还是true来决定保留还是丢弃该元素。
var arr = [1, 2, 4, 5, 6, 9, 10, 15];
var r = arr.filter(function (x) {
return x % 2 !== 0;
});
r; // [1, 5, 9, 15]
26.reduce():array的方法,参数是一个函数,这个函数必须接收两个值,数组中的值从左到右依次传入这个函数里,最后得出一个值返回出来。
var arr = [1, 3, 5, 7, 9];
arr.reduce(function (x, y) {
return x + y;
}); // 25
27.sort():排序方法,可接受一个函数,排列顺需有函数的返回值-1,0,1排序,sort方法是直接对原数组进行更改的。
var arr = [10, 20, 1, 2]; arr.sort(function (x, y) { if (x < y) { return -1; } if (x > y) { return 1; } return 0; }); console.log(arr); // [1, 2, 10, 20]
28.every():判断数组的所有元素是否符合条件,返回一个布尔值。
var arr = ['Apple', 'pear', 'orange'];
console.log(arr.every(function (s) {
return s.length > 0;
})); // true, 因为每个元素都满足s.length>0console.log(arr.every(function (s) {
return s.toLowerCase() === s;
})); // false, 因为不是每个元素都全部是小写
29:find():查找array元素的方法,接受一个函数,函数作用于数组的每个元素,用于查找符合元素的第一个元素,如果找到了返回这个元素本身,没有找到返回undefined;
var arr = ['Apple', 'pear', 'orange'];
console.log(arr.find(function (s) {
return s.toLowerCase() === s;
})); // 'pear', 因为pear全部是小写console.log(arr.find(function (s) {
return s.toUpperCase() === s;
})); // undefined, 因为没有全部是大写的元素
30:findIndex():和find方法类似,返回符合查找元素的第一个元素的索引。
31:
-
不要使用
new Number()
、new Boolean()
、new String()
创建包装对象; -
用
parseInt()
或parseFloat()
来转换任意类型到number
; -
用
String()
来转换任意类型到string
,或者直接调用某个对象的toString()
方法; -
通常不必把任意类型转换为
boolean
再判断,因为可以直接写if (myVar) {...}
; -
typeof
操作符可以判断出number
、boolean
、string
、function
和undefined
; -
判断
Array
要使用Array.isArray(arr)
; -
判断
null
请使用myVar === null
; -
判断某个全局变量是否存在用
typeof window.myVar === 'undefined'
; -
函数内部判断某个变量是否存在用
typeof myVar === 'undefined'
。
任何对象都有toString()
方法吗?null
和undefined
就没有!确实如此,这两个特殊值要除外,虽然null
还伪装成了object
类型。
number
对象调用toString()
报SyntaxError:
123.toString(); // SyntaxError
遇到这种情况,要特殊处理一下:
123..toString(); // '123', 注意是两个点!
(123).toString(); // '123'
32.generator:es6中提供的一种异步编程解决方法,可以随心所欲交出和恢复函数的执行权,next()恢复执行权,yield交出执行权。generator函数和普通函数写法相似,不过他在function后加一个*号,调用时需要创造一个函数句柄,然后调用next():
function* generator(){
yield "a";
yield "b";
return "c"
}
var g=generator();//创造g对象,指向generator函数句柄
g.next();//g.value=a,g.done=false 第一次调用next(),执行到yield "a",暂缓执行,并返回 a
g.next();//g.value=b,g.done=false 继续上一次的执行,执行到yield "b",暂缓执行,并返回b
g.next();//g.value=c,g.done=true 继续上一次的执行,执行到return,返回 c 和 done:true,表明结束
yield关键字只能在generator函数中使用,起到暂缓执行的作用,每次执行一次next(),相当于指针移动到下一个yield位置。
通过yield标识符和next()方法调用,实现函数的分段执行,yield后跟*,可以调用另一个generator()函数。
出来使用.next()方法遍历迭代器对象外,还可以使用for...of,但它会忽略return返回的值。
function* generator(){
yield "a";
yield "b";
return "c"
}
var g = generator();
for(var i of g){
console.log(i)//a,b
}
33.typeof:获取对象类型,返回一个字符串。
typeof 123 ;//'number'
typeof NaN ;//'number'
typeof 'abc';//'string'
typeof true;//'Boolean'
typeof undefined;//'undefined'
typeof null;//'object'
typeof [1,2];//'object'
typeof {};//'object'
typeof Math.abs;//'function'
34.面向对象编程有两个基本概念:类和实例,js没有类和实例的概念,而是通过原型(prototype)来实现面向对象编程。
js的原型链和java的class区别在于它没有class的概念,所有对象都是实例,所谓的继承关系不过是把一个对象的原型指向另一个对象。
js对每个创建的对象都会设置一个原型,指向它的原型对象,比如:
var arr=[1,2];
arr ----> Array.prototype ----> Object.prototype ----> null
function foo(){
return 0;
}
foo ----> Function.prototype ----> Object.prototype ----> null
35.创建原型的方法
1):Object.create()方法可以传入一个原型对象,并创建出一个基于该原型的新对象。
var Student = {
name: 'Robot',
height: 1.2,
run: function () {
console.log(this.name + ' is running...');
}
};
var xiaoming=Object.create(Student);
xiaoming.name="a"
console.log(xiaoming.name)//a
xiaoming.run();//a is running...
console.log(xiaoming.__proto__===Student)//true
2):先定义一个构造函数,再使用关键字new来调用这个函数,并返回一个对象:
function Student(name){
this.name=name;
}
Student.prototype.say=function(){
console.log(this.name);
}
var xiaoming = new Student('xiaoming');
xiaoming.name//xiaoming
xiaoming.say();//xiaoming
它的原型链:
xiaoming ----> Student.prototype ----> Object.prototype ----> null
3)class,关键字class是从es6开始被正式引入到js中的,使用class的定义包含了构造函数constructor和定义在原型对象上的函数,没有functio关键字
class Student {
constructor(name){
this.name=name;
}
hello(){
console.log("hello!")
}
}
var xiaoming = new Student('小明');
xiaoming.name==小明;
xiaoming.hello();
class继承也是通过class关键字实现的,使用extends来表示原型链对象来自哪里,在子类的构造函数李使用super来调用父类的构造方法
class Student {
constructor(name){
this.name=name;
}
hello(){
console.log("hello!"+this.name)
}
}
class JuniorStudent extends Student{
constructor(name,age){
super(name);//调用父类构造函数
this.age = age;
}
run(){
console.log(this.name+"is running...")
}
}
var xiaoming = new JuniorStudent("小明");
xiaoming.run();
xiaoming.hello();
36.window 表示浏览器的窗口。 有innerWidth和innerHeight这两个属性。可以获取浏览器的内部宽高。
window.innerWidth; window.innerHeight;
对应的还有outerWidth和outerHeight这两个属性,获取浏览器窗口的整体的宽高。 window.outerWidth; window.outerHeight;
37.navigator 表示浏览器信息。 navigator.appName:浏览器名称; navigator.appVersion:浏览器版本 navigator.language:浏览器设置的语言 navigator.platform:操作系统类型; navigator.userAgent:浏览器设定的Use-Agent字符串
38.screen 表示屏幕信息。 screen.width; //屏幕宽度 screen.height; //屏幕宽度 screen.colorDepth; //颜色位数
39.location 表示当前页面的URL信息。 location.href; //获取当前页面URL整体信息
40.document 表示当前的页面信息。 还可以获取当前页面的Cookie信息。 document.cookie;
41.history 表示页面的历史纪录。 但在任何情况下不使用该对象。
42.操作DOM:
直接定位Dom:document.getElementById(); document.querySelector();
返回一组DOM:documet.getElementByClassName(); document.getElementByTagName(); document.querySelectorAll()
获取节点下所有子节点:test.children;
获取父节点:element.parentElement;
获取节点下首个节点和最后一个节点:test.firstElementChild test.lastElementChild;
43.更改DOM:
innerHTML:可以直接更改一个dom节点里的内容和内部结构
innerText:只能更改内容,也不返回隐藏元素的文本
textContent:只能更改内容,返回隐藏元素的文本
44.修改css:dom节点的style属性对应所有的css,可以直接获取或设置。css允许font-size这样的名称,但它并非js有效的属性名,所以需要在js中改写成驼峰式命名fontSize : p.style.fontSize="12px";
45.插入dom:
appendChild:把一个子节点添加到父节点的最后一个子节点 parentElement.appendChild(element);
insertBefore:把一个子节点天骄的父节点的某个指定子节点之前 parentElement.insertBefore(elemeng,referenceElement),子节点会插入到referenceElement之前。
46.删除节点;removeElement,删除子节点,首先获取该节点本身和它的父节点,然后父节点调用removeChild删除子节点,parentElement.remove(element); 删除后的节点虽然不在文档中了,但是它还在内存中,可以随时再次被添加到其他位置
<div>
<p id='first'>First</p>
<p>Second</p>
</div>
var self = document.getElementById('first');
var parent = self.parentElement;
var remove = parent.removeElement(self);
remove === self //true 删除后的节点虽然不在文档树中,但它还在内存中,可以随时再被添加到别的位置。
46.操作文件:<input type="file">
当一个表单包换<input type="file">时,表单的enctype必须是“multipart/form-data”,method必须指定为post,浏览器才能正确的编码并以multipart/form-data格式发送表单的数据。
出于安全考虑,浏览器只允许用户点击<input type="file">来选择本地文件,对value 直接赋值是没有效果的,js也无法获得文件的真实路径。
html5的file API提供了file和fileReader两个对象来获取文件的信息并读取文件
//选择并展示一张图片
var
fileInput = document.getElementById('tset-img-file');
info = document.getElementById('test-file-info');
preview = document.getElementById('text-img-preview');
//监听change事件
fileInput.addEventListener('change',function(){
if(!fileInput.value){
info.innerText='未选择文件';
return;
}
})
//获取file引用
var file = fileInput.files[0];
//获取file的信息
info.innerHTML=`文件名称${file.name},大小${file.size},修改${file.lastModefiedDate}`;
if(file.type != 'image/png'){
alert('不是有效的图片文件');
return;
}
//读取文件
var reader = new FileReader();
//当文件读取完成后,自动调取load函数
reader.onLoad = function(e){
var
data = e.target.result;//base64编码格式的图片
preview.style.backgroundImage='url(' + data + ')';
}
//以dataurl的形式读取文件
reader.readAsDataURL(file);
47.箭头函数:相当于匿名函数,简化了函数定义,箭头函数有两种格式,一种只有一个表达式,连{...}和return都省略了,还有一种包含多条语句,这时候不能省略{...}和return:
// 两个参数:
(x, y) => x * x + y * y
// 无参数:
() => 3.14
// 可变参数:
(x, y, ...rest) => {
var i, sum = x + y;
for (i=0; i<rest.length; i++) {
sum += rest[i];
}
return sum;
}
如果要返回一个对象并且是个单表达式时,函数体要用()括起来 x => ({foo:x})
箭头函数修复了thie的指向,this总是指向词法作用域,也就是外层调用者obj:
var obj = {
birth: 1990,
getAge: function () {
var b = this.birth; // 1990
var fn = () => new Date().getFullYear() - this.birth; // this指向obj对象
return fn();
}
};
obj.getAge(); // 29
48.同步和异步(详情=》https://segmentfault.com/a/1190000007032448)
同步模式,即单线程模式,一次只能执行一个任务,函数调用后要等到函数执行结束,返回执行结果,才能进行下一个任务,如果这个任务执行事件很长,就会导致【线程阻塞】。
var x=true;
while(x);
console.log("死循环阻塞了进程");//不会执行
同步模式中如果请求时间较长,而阻塞了后面代码的执行,体验很不好,对于一些耗时的操作,异步模式更适合。
异步模式:可以一起执行多个任务,函数调用后不会立即分会执行结果,如果任务A的结果返回需要等待一段时间,可以先执行任务B,等到任务A的结果返回后再继续回调。
setTImeOut( () => console.log(1) , 0 ) ;
console.log(2)
-------ouput-------------
2
1
异步任务会在当前脚本的所有同步任务执行完成后再执行,所有即使定时器的时间是0,还是会先输入同步任务的2,再输出异步定时器任务中的1,如果同步代码中还有死循环,那异步任务将不会执行,因为同步任务阻塞了进程。
回调函数是一段可执行的代码段,它以参数的形式传递给其他代码,在其合适的时间执行这段代码。
49.Promise:promise对象用于异步操作,它表示一个尚未完成且预计在未来完成的异步操作,不关心结果如何处理,根据结果的成功或失败,将来在某个时候电泳resolv函数或reject函数
new Promise(function(resolve,reject){
var a = Math.random() *2;
return a>1?resolve('ok'):reject('fail');
})
.then(result => console.log('resolve'))
.catch(reason => console.log('reject'))
Promise一旦新建就会立即执行,无法取消,这也是他的确定之一。
Promise类似构建对象,我们使用new来构建一个Promise对象,Promise接受一个函数作为参数,该函数有两个参数分别是resolve和reject。这两个函数就是回调函数,由js引擎提供。
resolve函数:在异步操作成功时调用,并将异步操作的结果作为参数传递出去。
reject函数:在异步操作失败的时候调用,并将异步操作报错的信息作为参数传递出去。
Promise实例生成后,可以用then和catch方法分别指定resolve状态和reject状态的回调函数。
49:宏任务与微任务与event loop:==》》https://blog.csdn.net/lc237423551/article/details/79902106
常见的宏任务有:script、setTimeout、SetInterval
微任务:premise、process.nextTick
event loop:同步和异步任务分别会进入不同的执行场所,同步任务进入主线程,异步进入event table 并注册异步的回调函数,当异步里的事情完成后,event table会将异步的回调函数移入event queue。主线程的任务执行任务完成后,回去event queue中读取对应的函数,进入主线程。上述过程会不断重复,也就是常说的Event Loop(事件循环)。
只有一个宏任务完成后(包括其中的微任务)才会执行下一个宏任务。