請配合 簡書側(cè)邊欄文章導(dǎo)航 食用土榴,因為很長
● JS-變量類型和計算
? 1. 值類型有哪些6块仆?
Undefined
、Null
逝薪、Boolean
、Number
曼月、String
容达、Symbol (new in ES 6)
!
? 2. 引用類型有哪些9?
Object 類型
冰沙、Array 類型
侨艾、Date 類型
、RegExp 類型
拓挥、Function 類型
Set
唠梨,Map
,weakSet
侥啤,weakMap
? 3. 手寫深拷貝
function deepClone(target,map = new Map() ){
if(typeof target !=='object' || target ==null ){
//不是引用類型直接返回
return target
}
let cloneTarget = Array.isArray(target) ?[]:{}
//解決循環(huán)引用当叭,利用map開辟一個新的存儲空間茬故, 存儲當(dāng)前對象和拷貝對象之間的關(guān)系
if(map.get(target)){
return target
}
map.set(target,cloneTarget)
for(let key in target){
cloneTarget[key] = deepClone(target[key],map )
}
return cloneTarget
}
? 4. ==和=== 運算符
//除了== null,之外 全部都用===
const obj = {x:100}
//if(obj.a===null || obj.a === undefined ){}
if(obj.a==null){
}
? 5. if語句和邏輯運算
truly 變量 !!a ===false
falsely 變量 !!a ===false
!!0 === false
!!NaN===false
!!''===false
!!null===false
!!undefined===false
!!false===false
? 6. typeof 能判斷哪些類型蚁鳖?
- 識別所有值類型磺芭, null 除外
- 識別函數(shù)
- 判斷是否是引用類型(不可再細分) object array 都是object
● js-原型和原型鏈
call apply bind
**核心:改變函數(shù)運行時的this指向**
-
call、apply與bind的差別
call和apply改變了函數(shù)的this上下文后便執(zhí)行該函數(shù),而bind則是返回改變了上下文后的一個函數(shù)琅攘。
-
call垮庐、apply的區(qū)別
他們倆之間的差別在于參數(shù)的區(qū)別,call和apply的第一個參數(shù)都是要改變上下文的對象坞琴,而call從第二個參數(shù)開始以參數(shù)列表的形式展現(xiàn)哨查,apply則是把除了改變上下文對象的參數(shù)放在一個數(shù)組里面作為它的第二個參數(shù)。
let arr1 = [1, 2, 19, 6]; //例子:求數(shù)組中的最值 console.log(Math.max.call(null, 1,2,19,6)); // 19 console.log(Math.max.call(null, arr1)); // NaN console.log(Math.max.apply(null, arr1)); // 19 直接可以用arr1傳遞進去 function fn() { console.log(this); } // apply方法結(jié)果同下 fn.call(); // 普通模式下this是window剧辐,在嚴(yán)格模式下this是undefined fn.call(null); // 普通模式下this是window寒亥,在嚴(yán)格模式下this是null fn.call(undefined); // 普通模式下this是window,在嚴(yán)格模式下this是undefined
__proto__
是js中 只有對象才有的屬性 稱作
隱式原型
指向的是它的構(gòu)造函數(shù)的
prototype
這個屬性是瀏覽器給提供的
prototype
是js中 只有函數(shù)才有的 的特殊屬性 叫做
原型
也稱作顯式原型
這個屬性是 函數(shù)fn的一個指針荧关,指向一個對象溉奕,這個對象的用途就是包含所有實例共享的屬性和方法(
原型對象
)原型對象也有一個屬性,叫做
constructor
忍啤,這個屬性包含了一個指針加勤,指回原構(gòu)造函數(shù)。這個屬性 JavaScript 標(biāo)準(zhǔn)提供的
對象 沒有 prototype
函數(shù)也是對象 所以也有
__proto__
原型鏈繼承
核心:將父類的實例作為子類的原型
優(yōu)點:父類方法可以復(fù)用
缺點:
- 父類的引用屬性會被所有子類實例共享
- 子類構(gòu)建實例時不能向父類傳遞參數(shù)
//核心
SubClass.prototype = new SuperClass()
// 所有涉及到原型鏈繼承的繼承方式都要修改子類構(gòu)造函數(shù)的指向同波,否則子類實例的構(gòu)造函數(shù)會指向SuperType鳄梅。
SubClass.prototype.constructor = SubClass;
//缺點1:父類的引用屬性會被所有子類實例共享
function SuperType(){
this.colors = ['red','black']
}
function SubClass () {
}
// 將父類的實例作為子類的原型對象
SubClass.prototype = new SuperClass()
SubClass.prototype.constructor = SubClass;
o1 = new SubClass()
o2 = new SubClass()
o1.splice(1, 1, 'yellow');
console.log(o1.colors) // ['red', 'yellow']
console.log(o2.colors) // ['red', 'yellow']
//缺點2:子類構(gòu)建實例時不能向父類傳遞參數(shù)
function SuperClass (color) {
this.color=color
}
function SubClass () {
}
SubClass.prototype = new SuperClass(['red'])
SubClass.prototype.constructor = SubClass;
var o1 = new SubClass()
var o2 = new SubClass()
// 打印o1和o2的color
console.log(o1.color) // ['red']
console.log(o2.__proto__.color)// ['red']
構(gòu)造函數(shù)繼承
核心:將父類構(gòu)造函數(shù)的內(nèi)容復(fù)制給了子類的構(gòu)造函數(shù)。這是所有繼承中唯一一個不涉及到prototype的繼承未檩。
優(yōu)點:和原型鏈繼承完全反過來戴尸。
- 父類的引用屬性不會被共享
- 子類構(gòu)建實例時可以向父類傳遞參數(shù)
缺點:父類的方法不能復(fù)用,子類實例的方法每次都是單獨創(chuàng)建的冤狡。
//核心
SuperClass.apply(this,arguments);
function SuperClass (color) {
this.color=color;
this.say = function () {
alert('hello');
}
}
function SubClass () {
SuperClass.apply(this, arguments)
}
var o1 = new SubClass(['red'])
var o2 = new SubClass(['yellow'])
console.log(o1.color) // ['red']
console.log(o2.color) // ['yellow']
//缺點:父類的方法不能復(fù)用孙蒙,子類實例的方法每次都是單獨創(chuàng)建的。
console.log(o1.say === o2.say)//false
組合繼承
核心:原型式繼承和構(gòu)造函數(shù)繼承的組合悲雳,兼具了二者的優(yōu)點挎峦。
優(yōu)點:
- 父類的方法可以被復(fù)用
- 父類的引用屬性不會被共享
- 子類構(gòu)建實例時可以向父類傳遞參數(shù)
缺點:
調(diào)用了兩次父類的構(gòu)造函數(shù),第一次給子類的原型添加了父類的name, arr屬性合瓢,第二次又給子類的構(gòu)造函數(shù)添加了父類的name, arr屬性浑测,從而覆蓋了子類原型中的同名參數(shù)。這種被覆蓋的情況造成了性能上的浪費。
function SuperClass (color) {
this.color=color;
}
//利用原型鏈繼承要共享的屬性
SuperClass.prototype.say = function () {
console.log(this.color);
}
function SubClass () {
//利用構(gòu)造函數(shù)繼承要獨享的屬性
SuperClass.apply(this, arguments) // 第二次調(diào)用SuperClass
}
SubClass.prototype = new SuperClass() // 第一次調(diào)用SuperClass
SubClass.prototype.constructor = SubClass;
var o1 = new SubClass(['red'])
var o2 = new SubClass(['yellow'])
console.log(o1.color) // ['red']
console.log(o2.color) // ['yellow']
o1.say() // ['red']
o2.say() // ['yellow']
原型式繼承
通過修改原型鏈結(jié)構(gòu)迁央,使得實例能夠使用原型鏈上的所有方法
核心:原型式繼承的object方法本質(zhì)上是對參數(shù)對象的一個淺復(fù)制掷匠。
優(yōu)點:父類方法可以復(fù)用
缺點:
- 父類的引用屬性會被所有子類實例共享
- 子類構(gòu)建實例時不能向父類傳遞參數(shù)
es5的
Object.create()
函數(shù),就是基于原型式繼承的
原型式繼承是
道格拉斯-克羅克福德
2006 年在Prototypal Inheritance in JavaScript
一文中提出的
function clone(o){
function F(){}
F.prototype = o;
return new F();
}
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var o1 = clone(person);
o1.name = "Greg";
o1.friends.push("Rob");
var o2 = clone(person);
o2.name = "Linda";
o2.friends.push("Barbie");
//缺點 父類的引用屬性會被所有子類實例共享
console.log(person.friends);//"Shelby,Court,Van,Rob,Barbie"
寄生式繼承
核心:使用原型式繼承獲得一個目標(biāo)對象的淺復(fù)制,然后增強這個淺復(fù)制的能力岖圈。
優(yōu)缺點:僅提供一種思路讹语,沒什么優(yōu)點
function cloneAndStrengthen(proto){
function F () {}
F.prototype = proto
let f = new F()
f.say = function() {
console.log('I am a person')
}
return f
}
寄生組合繼承
核心:因為是對父類原型的復(fù)制,所以不包含父類的構(gòu)造函數(shù)蜂科,也就不會調(diào)用兩次父類的構(gòu)造函數(shù)造成浪費
優(yōu)點:
解決組合繼承有一個會兩次調(diào)用父類的構(gòu)造函數(shù)造成浪費的缺點
避免了在
SubClass.prototype
上面創(chuàng)建不需要的顽决、多余的屬性。原型鏈還能保持不變导匣;能夠正常使用
instanceof
和isPrototypeOf()
//原型式繼承
function clone(o) {
function F() {}
F.prototype = o
return new F()
}
//寄生式繼承
// 2. 通過原型式繼承和寄生式繼承方法才菠,實現(xiàn)方法共享
//接收子類的構(gòu)造函數(shù),和超類的構(gòu)造函數(shù)
function base(sub, supers) {
//創(chuàng)建一個超類顯式原型的副本贡定,淺拷貝
let prototype = clone(supers.prototype)
//添加 constructor 屬性赋访,從而彌補因重寫原型而失去的默認(rèn)的 constructor 屬性。
prototype.constructor = sub
//將新創(chuàng)建的對象(副本)賦值給子類型的原型缓待。
sub.prototype = prototype
}
function SuperClass(color){
this.color = color
}
SuperClass.prototype.say=function(){
console.log(this.color)
}
function SubClass(){
//1. 通過借用構(gòu)造函數(shù)來繼承屬性蚓耽,解決引用類型共享的問題
SuperClass.apply(this, arguments);
}
base(SubClass,SuperClass)
let o1 = new SubClass(['green']);
console.log(o1)
左側(cè)是組合繼承, 右側(cè)是寄生組合繼承
ES6 Class extends
核心: ES6繼承的結(jié)果和寄生組合繼承相似旋炒,本質(zhì)上步悠,ES6繼承是一種語法糖。但是瘫镇,寄生組合繼承是先創(chuàng)建子類實例this對象鼎兽,然后再對其增強;而ES6先將父類實例對象的屬性和方法铣除,加到this上面(所以必須先調(diào)用super方法)接奈,然后再用子類的構(gòu)造函數(shù)修改this。
class A {}
class B extends A {
constructor() {
super();
}
}
ES6實現(xiàn)繼承的具體原理:
class A {
}
class B {
}
Object.setPrototypeOf = function (obj, proto) {
obj.__proto__ = proto;
return obj;
}
// B 的實例繼承 A 的實例
Object.setPrototypeOf(B.prototype, A.prototype);
// B 繼承 A 的靜態(tài)屬性
Object.setPrototypeOf(B, A);
ES6繼承與ES5繼承的異同:
相同點:本質(zhì)上ES6繼承是ES5繼承的語法糖
不同點:
- ES6繼承中子類的構(gòu)造函數(shù)的原型鏈指向父類的構(gòu)造函數(shù)通孽,ES5中使用的是構(gòu)造函數(shù)復(fù)制,沒有原型鏈指向睁壁。
- ES6子類實例的構(gòu)建背苦,基于父類實例,ES5中不是潘明。
總結(jié)
- ES6 Class extends是ES5繼承的語法糖
- JS的繼承除了構(gòu)造函數(shù)繼承之外都基于原型鏈構(gòu)建的
- 可以用寄生組合繼承實現(xiàn)ES6 Class extends行剂,但是還是會有細微的差別
es6
//class 實際上是函數(shù),可見是語法糖
typeof People // fuction
typeof Student //function
//隱式原型
console.log(xiaoluo.__proto__)
//顯式原型
console.log(xiaoluo.prototype)
//true 引用同一個內(nèi)存地址
console.log(xiaoluo.__proto__===Student.prototype)
每個class 都有顯示原型prototype
每個實例都有隱式原型__proto__
實例的__proto__指向?qū)?yīng)class的prototype
? 1. 如何準(zhǔn)確判斷一個變量是不是數(shù)組钳降?
let a = {}
a instanceof Array
? 2. class的原型本質(zhì)(原型鏈)
prototype 指向一塊內(nèi)存厚宰,這個內(nèi)存里面有共用屬性
proto 指向同一塊內(nèi)存
prototype 和 __proto__
的不同點在于
prototype 是構(gòu)造函數(shù)的屬性,而 proto 是對象的屬性
難點在于……構(gòu)造函數(shù)也是對象!
如果沒有 prototype铲觉,那么共用屬性就沒有立足之地
**如果沒有 __proto__
澈蝙,那么一個對象就不知道自己的共用屬性有哪些
? 3. 手寫 call apply bind
Function.prototype.myCall = function(context = window, ...args) {
context = context || window; // 參數(shù)默認(rèn)值并不會排除null,所以重新賦值
context.fn = this; // this是調(diào)用call的函數(shù)
const result = context.fn(...args);
delete context.fn; // 執(zhí)行后刪除新增屬性
return result;
}
Function.prototype.myApply = function(context = window, args = []) {
context = context || window; // 參數(shù)默認(rèn)值并不會排除null撵幽,所以重新賦值
context.fn = this; // this是調(diào)用call的函數(shù)
const result = context.fn(...args);
delete context.fn;
return result;
}
Function.prototype.myBind = function(context, ...args) {
const _this = this;
return function Bind(...newArgs) {
// 考慮是否此函數(shù)被繼承
if (this instanceof Bind) {
return _this.myApply(this, [...args, ...newArgs])
}
return _this.myApply(context, [...args, ...newArgs])
}
}
● js-作用域和閉包
作用域
核心:作用域是在運行時代碼中的某些特定部分中變量灯荧,函數(shù)和對象的可訪問性
作用域就是一個獨立的地盤,讓變量不會外泄盐杂、暴露出去逗载。也就是說作用域最大的用處就是【隔離變量】,不同作用域下同名變量不會有沖突链烈。
全局作用域 函數(shù)作用域 es6塊級作用域
全局作用域
-
最外層函數(shù) 和在最外層函數(shù)外面定義的變量擁有全局作用域
var outVariable = "我是最外層變量"; //最外層變量 function outFun() { //最外層函數(shù) var inVariable = "內(nèi)層變量"; } console.log(outVariable); //我是最外層變量 outFun(); //內(nèi)層變量 console.log(inVariable); //inVariable is not defined
-
所有末定義直接賦值的變量自動聲明為擁有全局作用域
function outFun2() { variable = "未定義直接賦值的變量"; var inVariable2 = "內(nèi)層變量2"; } outFun2();//要先執(zhí)行這個函數(shù)厉斟,否則根本不知道里面是啥 console.log(variable); //未定義直接賦值的變量 console.log(inVariable2); //inVariable2 is not defined
-
所有 window 對象的屬性擁有全局作用域
- 例如 window.name、window.location强衡、window.top 等等擦秽。
函數(shù)作用域
是指聲明在函數(shù)內(nèi)部的變量,和全局作用域相反食侮,局部作用域一般只在固定的代碼片段內(nèi)可訪問到号涯,
作用域是分層的,內(nèi)層作用域可以訪問外層作用域的變量锯七,反之則不行链快。
function doSomething(){
var blogName="浪里行舟";
function innerSay(){
alert(blogName);
}
innerSay();
}
alert(blogName); //腳本錯誤
innerSay(); //腳本錯誤
值得注意的是:塊語句(大括號“{}”中間的語句),如 if 和 switch 條件語句或 for 和 while 循環(huán)語句眉尸,不像函數(shù)域蜗,它們不會創(chuàng)建一個新的作用域。在塊語句中定義的變量將保留在它們已經(jīng)存在的作用域中噪猾。
if (true) {
// 'if' 條件語句塊不會創(chuàng)建一個新的作用域
var name = 'Hammad'; // name 依然在全局作用域中
}
console.log(name); // logs 'Hammad'
es6塊級作用域
塊級作用域可通過新增命令 let 和 const 聲明霉祸,所聲明的變量在指定塊的作用域外無法被訪問。塊級作用域在如下情況被創(chuàng)建:
- 在一個函數(shù)內(nèi)部
- 在一個代碼塊(由一對花括號包裹)內(nèi)部
let 聲明的語法與 var 的語法一致袱蜡。你基本上可以用 let 來代替 var 進行變量聲明丝蹭,但會將變量的作用域限制在當(dāng)前代碼塊中。塊級作用域有以下幾個特點:
- 聲明變量不會提升到代碼塊頂部
let/const 聲明并不會被提升到當(dāng)前代碼塊的頂部坪蚁,因此你需要手動將 let/const 聲明放置到頂部奔穿,以便讓變量在整個代碼塊內(nèi)部可用。
function getValue(condition) {
if (condition) {
let value = "blue";
return value;
} else {
// value 在此處不可用
return null;
}
// value 在此處不可用
}
- 禁止重復(fù)聲明
如果一個標(biāo)識符已經(jīng)在代碼塊內(nèi)部被定義敏晤,那么在此代碼塊內(nèi)使用同一個標(biāo)識符進行 let 聲明就會導(dǎo)致拋出錯誤贱田。例如:
var count = 30;
let count = 40; // Uncaught SyntaxError: Identifier 'count' has already been declared
閉包
**核心: 「函數(shù)」和「函數(shù)內(nèi)部能訪問到的變量」(也叫環(huán)境)的總和嘴脾,就是一個閉包男摧。**
-
閉包內(nèi)的自由變量的查找,是在函數(shù)定義的地方,向上級作用域查找耗拓,不是在執(zhí)行的地方
作用:用來隱藏變量拇颅,私有變量。不允許直接訪問此變量帆离,所以需要暴露一個訪問器【函數(shù)】蔬蕊,可以讓訪問者【間接訪問】
為什么要嵌套函數(shù)?
因為使用閉包的目的是為了創(chuàng)造局部變量哥谷,所以變量必須放在函數(shù)中岸夯,否則就是全局的變量,無法實現(xiàn)閉包目的-隱藏變量
所以函數(shù)套函數(shù)只是為了造出一個局部變量们妥,跟閉包無關(guān)猜扮。
為什么要return 函數(shù)呢?
因為如果不return 监婶,就沒有辦法使用這個閉包旅赢。return xxx 改成window.xxx =xxx 是一樣的,目的就是為了讓外面可以訪問到這個xxx函數(shù)惑惶,所以return xxx 只是為了使用 xxx煮盼, 也跟閉包無關(guān)。
this
核心:this的值是在函數(shù)執(zhí)行時確認(rèn)的僵控,不是在函數(shù)定義時確認(rèn)的
func.call(context, p1, p2)
才是正常的調(diào)用形式,其他的都是語法糖this 就是你 call 一個函數(shù)時鱼冀,傳入的第一個參數(shù)报破。由于你從來不用 call 形式的函數(shù)調(diào)用,所以你一直不知道千绪。
function func(){
console.log(this)
}
func()
//轉(zhuǎn)換為
func.call(undefined) // 可以簡寫為 func.call()
//按理說打印出來的 this 應(yīng)該就是 undefined 了吧充易,但是瀏覽器里有一條規(guī)則:
//如果你傳的 context 是 null 或 undefined,那么 window 對象就是默認(rèn)的 context(嚴(yán)格模式下默認(rèn) context 是 undefined)
//因此上面的打印結(jié)果是 window荸型。
//如果你希望這里的 this 不是 window盹靴,很簡單:
func.call(obj) // 那么里面的 this 就是 obj 對象了
箭頭函數(shù)
實際上箭頭函數(shù)里并沒有 this,如果你在箭頭函數(shù)里看到 this瑞妇,直接把它當(dāng)作箭頭函數(shù)外面的 this 即可稿静。外面的 this 是什么,箭頭函數(shù)里面的 this 就還是什么踪宠,因為箭頭函數(shù)本身不支持 this。
有人說「箭頭函數(shù)里面的 this 指向箭頭函數(shù)外面的 this」妈嘹,這很傻柳琢,因為箭頭函數(shù)內(nèi)外 this 就是同一個東西,并不存在什么指向不指向。
立即執(zhí)行函數(shù)
什么是立即執(zhí)行函數(shù)柬脸?有什么作用他去?
作用:創(chuàng)建一個獨立的作用域。這個作用域里面的變量倒堕,外面訪問不到(即避免「變量污染」)灾测。
為什么還要用另一對括號把匿名函數(shù)包起來呢?
其實是為了兼容 JS 的語法垦巴。
如果我們不加另一對括號媳搪,直接寫成
function(){alert('我是匿名函數(shù)')}()
瀏覽器會報語法錯誤。想要通過瀏覽器的語法檢查骤宣,必須加點小東西秦爆,比如下面幾種
(function(){alert('我是匿名函數(shù)')} ()) // 用括號把整個表達式包起來
(function(){alert('我是匿名函數(shù)')}) () //用括號把函數(shù)包起來
!function(){alert('我是匿名函數(shù)')}() // 求反,我們不在意值是多少憔披,只想通過語法檢查等限。
+function(){alert('我是匿名函數(shù)')}()
-function(){alert('我是匿名函數(shù)')}()
~function(){alert('我是匿名函數(shù)')}()
void function(){alert('我是匿名函數(shù)')}()
new function(){alert('我是匿名函數(shù)')}()
? 1. this的不同應(yīng)用場景,如何取值芬膝?
? 3. 實際開發(fā)中閉包的應(yīng)用場景望门,舉例說明?
(function(){
var data = {}
return {
set(key,value){
data[key] = value
},
get(key){
return data[key]
}
}
})()
const cache = (function(){
var data = {}
return {
set(key,value){
data[key] = value
},
get(key){
return data[key]
}
}
})()
cache.set('sex',1)
console.log(cache.get('sex'))
● js-異步
event loop 事件循環(huán)/事件輪詢
js 如何執(zhí)行锰霜?
- 從前到后筹误,一行一行執(zhí)行
- 如果某一行執(zhí)行報錯,則停止下面代碼的執(zhí)行
- 先把他同步代碼執(zhí)行完锈遥,再執(zhí)行異步
event loop 過程
- 同步代碼纫事,一行一行放在 call stack(調(diào)用棧) 執(zhí)行
- 遇到異步,會先“記錄”下(web apis)所灸,等待時機(定時丽惶、網(wǎng)絡(luò)請求等)
- 時機到了,就移動到 callback queue(回調(diào) 隊列)
- 如call stack 為空 (即同步代碼執(zhí)行完畢) event loop 開始工作
- 輪詢查找 callback queue爬立, 如有則移動到 call stack 執(zhí)行
- 然后繼續(xù)輪詢查找(永動機一樣)
dom 事件也是使用回調(diào)钾唬,也是基于event loop
Promise
三種狀態(tài)
pending resolved rejected
狀態(tài)的表現(xiàn)和變化
pending ->resolved 或 peding-> rejected
變化不可逆
pending 狀態(tài), 不會觸發(fā)then 和catch
resolved狀態(tài)侠驯,會觸發(fā)后續(xù)的then回調(diào)函數(shù)
rejected狀態(tài)抡秆,會觸發(fā)后續(xù)的catch回調(diào)函數(shù)
then 和catch 對狀態(tài)的影響
then 正常返回 resolved,里面有報錯則返回 rejected
catch 正常返回 resolved吟策,里面有報錯則返回 rejected
先回顧一下 Promise 的基本使用
// 加載圖片
function loadImg(src) {
const p = new Promise(
(resolve, reject) => {
const img = document.createElement('img')
img.onload = () => {
resolve(img)
}
img.onerror = () => {
const err = new Error(`圖片加載失敗 ${src}`)
reject(err)
}
img.src = src
}
)
return p
}
const url = 'https://xxx.jpg'
loadImg(url).then(img => {
console.log(img.width)
return img
}).then(img => {
console.log(img.height)
}).catch(ex => console.error(ex))
三種狀態(tài)
三種狀態(tài) pending resolved rejected
(畫圖表示轉(zhuǎn)換關(guān)系儒士,以及轉(zhuǎn)換不可逆)
// 剛定義時,狀態(tài)默認(rèn)為 pending
const p1 = new Promise((resolve, reject) => {
})
// 執(zhí)行 resolve() 后檩坚,狀態(tài)變成 resolved
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve()
})
})
// 執(zhí)行 reject() 后着撩,狀態(tài)變成 rejected
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
reject()
})
})
// 直接返回一個 resolved 狀態(tài)
Promise.resolve(100)
// 直接返回一個 rejected 狀態(tài)
Promise.reject('some error')
狀態(tài)和 then catch
狀態(tài)變化會觸發(fā) then catch —— 這些比較好理解诅福,就不再代碼演示了
- pending 不會觸發(fā)任何 then catch 回調(diào)
- 狀態(tài)變?yōu)?resolved 會觸發(fā)后續(xù)的 then 回調(diào)
- 狀態(tài)變?yōu)?rejected 會觸發(fā)后續(xù)的 catch 回調(diào)
then catch 會繼續(xù)返回 Promise ,此時可能會發(fā)生狀態(tài)變化M闲稹Cト蟆!
// then() 一般正常返回 resolved 狀態(tài)的 promise
Promise.resolve().then(() => {
return 100
})
// then() 里拋出錯誤薯鳍,會返回 rejected 狀態(tài)的 promise
Promise.resolve().then(() => {
throw new Error('err')
})
// catch() 不拋出錯誤咖气,會返回 resolved 狀態(tài)的 promise
Promise.reject().catch(() => {
console.error('catch some error')
})
// catch() 拋出錯誤,會返回 rejected 狀態(tài)的 promise
Promise.reject().catch(() => {
console.error('catch some error')
throw new Error('err')
})
async/await
核心 :是同步語法挖滤,編寫異步的代碼崩溪,徹底消滅回調(diào)函數(shù)
- 執(zhí)行 async函數(shù),返回的是Promise對象
- await 相當(dāng)于 Promise的then
- try ... catch 可捕獲異常壶辜,代替了Promise的catch
- async/await 是消滅異步回調(diào)的終極武器
- js還是單線程悯舟,還得是有異步,還得是基于event loop
- async/await 只是一個語法糖砸民,但這顆糖真香
有很多 async 的面試題抵怎,例如
- async 直接返回,是什么
- async 直接返回 promise
- await 后面不加 promise
- 等等岭参,需要找出一個規(guī)律
語法介紹
用同步的方式反惕,編寫異步。
function loadImg(src) {
const promise = new Promise((resolve, reject) => {
const img = document.createElement('img')
img.onload = () => {
resolve(img)
}
img.onerror = () => {
reject(new Error(`圖片加載失敗 ${src}`))
}
img.src = src
})
return promise
}
async function loadImg1() {
const src1 = 'http://xxx.png'
const img1 = await loadImg(src1)
return img1
}
async function loadImg2() {
const src2 = 'http://xxx.png'
const img2 = await loadImg(src2)
return img2
}
(async function () {
// 注意:await 必須放在 async 函數(shù)中演侯,否則會報錯
try {
// 加載第一張圖片
const img1 = await loadImg1()
console.log(img1)
// 加載第二張圖片
const img2 = await loadImg2()
console.log(img2)
} catch (ex) {
console.error(ex)
}
})()
和 Promise 的關(guān)系
- async 函數(shù)返回結(jié)果都是 Promise 對象(如果函數(shù)內(nèi)沒返回 Promise 姿染,則自動封裝一下)
async function fn2() {
return new Promise(() => {})
}
console.log( fn2() )
async function fn1() {
return 100
}
console.log( fn1() ) // 相當(dāng)于 Promise.resolve(100)
- await 后面跟 Promise 對象:會阻斷后續(xù)代碼,等待狀態(tài)變?yōu)?resolved 秒际,才獲取結(jié)果并繼續(xù)執(zhí)行
- await 后續(xù)跟非 Promise 對象:會直接返回
(async function () {
const p1 = new Promise(() => {})
await p1
console.log('p1') // 不會執(zhí)行
})()
(async function () {
const p2 = Promise.resolve(100)
const res = await p2
console.log(res) // 100
})()
(async function () {
const res = await 100
console.log(res) // 100
})()
(async function () {
const p3 = Promise.reject('some err')
const res = await p3
console.log(res) // 不會執(zhí)行
})()
- try...catch 捕獲 rejected 狀態(tài)
(async function () {
const p4 = Promise.reject('some err')
try {
const res = await p4
console.log(res)
} catch (ex) {
console.error(ex)
}
})()
總結(jié)來看:
- async 封裝 Promise
- await 處理 Promise 成功
- try...catch 處理 Promise 失敗
異步本質(zhì)
await 是同步寫法悬赏,但本質(zhì)還是異步調(diào)用。
async function async1 () {
console.log('async1 start')
await async2()
console.log('async1 end') // 關(guān)鍵在這一步娄徊,它相當(dāng)于放在 callback 中闽颇,最后執(zhí)行
}
async function async2 () {
console.log('async2')
}
console.log('script start')
async1()
console.log('script end')
即,只要遇到了 await
寄锐,后面的代碼都相當(dāng)于放在 callback 里兵多。
for...of
// 定時算乘法
function multi(num) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(num * num)
}, 1000)
})
}
// // 使用 forEach ,是 1s 之后打印出所有結(jié)果橄仆,即 3 個值是一起被計算出來的
// function test1 () {
// const nums = [1, 2, 3];
// nums.forEach(async x => {
// const res = await multi(x);
// console.log(res);
// })
// }
// test1();
// 使用 for...of 剩膘,可以讓計算挨個串行執(zhí)行
async function test2 () {
const nums = [1, 2, 3];
for (let x of nums) {
// 在 for...of 循環(huán)體的內(nèi)部,遇到 await 會挨個串行計算
const res = await multi(x)
console.log(res)
}
}
test2()
宏任務(wù) macro task 和 微任務(wù) micro task
宏任務(wù): setTimeout盆顾,setInterval怠褐, Ajax, DOM 事件
微任務(wù): Promise async/await
微任務(wù)執(zhí)行時機比宏任務(wù)要早
event loop 和 DOM 渲染
再次回顧 event loop 的過程
- 每一次 call stack 結(jié)束您宪,都會觸發(fā) DOM 渲染(不一定非得渲染奈懒,就是給一次 DOM 渲染的機會>呶隆!?鹋狻)
- 然后再進行 event loop
const $p1 = $('<p>一段文字</p>')
const $p2 = $('<p>一段文字</p>')
const $p3 = $('<p>一段文字</p>')
$('#container')
.append($p1)
.append($p2)
.append($p3)
console.log('length', $('#container').children().length )
alert('本次 call stack 結(jié)束,DOM 結(jié)構(gòu)已更新揖铜,但尚未觸發(fā)渲染')
// (alert 會阻斷 js 執(zhí)行茴丰,也會阻斷 DOM 渲染,便于查看效果)
// 到此天吓,即本次 call stack 結(jié)束后(同步任務(wù)都執(zhí)行完了)贿肩,瀏覽器會自動觸發(fā)渲染,不用代碼干預(yù)
// 另外龄寞,按照 event loop 觸發(fā) DOM 渲染時機汰规,setTimeout 時 alert ,就能看到 DOM 渲染后的結(jié)果了
setTimeout(function () {
alert('setTimeout 是在下一次 Call Stack 物邑,就能看到 DOM 渲染出來的結(jié)果了')
})
宏任務(wù)和微任務(wù)的區(qū)別
- 宏任務(wù):DOM 渲染后再觸發(fā)
- 微任務(wù):DOM 渲染前會觸發(fā)
// 修改 DOM
const $p1 = $('<p>一段文字</p>')
const $p2 = $('<p>一段文字</p>')
const $p3 = $('<p>一段文字</p>')
$('#container')
.append($p1)
.append($p2)
.append($p3)
// // 微任務(wù):渲染之前執(zhí)行(DOM 結(jié)構(gòu)已更新)
// Promise.resolve().then(() => {
// const length = $('#container').children().length
// alert(`micro task ${length}`)
// })
// 宏任務(wù):渲染之后執(zhí)行(DOM 結(jié)構(gòu)已更新)
setTimeout(() => {
const length = $('#container').children().length
alert(`macro task ${length}`)
})
再深入思考一下:為何兩者會有以上區(qū)別溜哮,一個在渲染前,一個在渲染后色解?
- 微任務(wù):ES 語法標(biāo)準(zhǔn)之內(nèi)茂嗓,JS 引擎來統(tǒng)一處理。即科阎,不用瀏覽器有任何關(guān)于述吸,即可一次性處理完,更快更及時锣笨。
- 宏任務(wù):ES 語法沒有蝌矛,JS 引擎不處理,瀏覽器(或 nodejs)干預(yù)處理错英。
面試題:
? 1. 同步和異步的區(qū)別入撒?
同步會阻塞代碼執(zhí)行
異步不會阻塞代碼執(zhí)行,
異步基于js是單線程語言
? 2. 手寫promise 加載一張圖片走趋?
function loadImg(src) {
return new Promise((resolve, reject) => {
let img = document.createElement('img')
img.onload = () => {
resolve(img)
}
img.onerror = () => {
reject(new Error(` 圖片加載失敗 ${src}`))
}
img.src = src
})
}
loadImg('https://xxx.jpg').then(img => {
console.log(img.width)
console.log(img.height)
}).catch(err => console.error(err))
? 3. 描述 event loop 機制 可畫圖衅金?
先回答 event loop的過程
如果 面試官 往深了問 , 則 說明和dom渲染的關(guān)系
微任務(wù)和宏任務(wù) 在 event loop 過程中的不同處理
? 4. 什么是宏任務(wù)和微任務(wù)簿煌,兩者區(qū)別 氮唯?
宏任務(wù): setTimeout,setInterval姨伟, Ajax惩琉, DOM 事件
微任務(wù): Promise async/await
微任務(wù)執(zhí)行時機比宏任務(wù)要早
? 5. Promise的三種狀態(tài),如何變化夺荒?
pending resolved rejected
pending ->resolved 或 peding-> rejected
變化不可逆
? 6. promise then 和catch 的連接瞒渠?
// 第一題
Promise.resolve().then(() => {
console.log(1)
}).catch(() => {
console.log(2)
}).then(() => {
console.log(3)
})
// 1 3
// 第二題
Promise.resolve().then(() => {
console.log(1)
throw new Error('erro1')
}).catch(() => {
console.log(2)
}).then(() => {
console.log(3)
})
// 1 2 3
// 第三題
Promise.resolve().then(() => {
console.log(1)
throw new Error('erro1')
}).catch(() => {
console.log(2)
}).catch(() => { // 注意這里是 catch
console.log(3)
})
// 1 2
? 7. async/await 語法問題
async function fn() {
return 100
}
(async function () {
const a = fn() // ?? // promise
const b = await fn() // ?? // 100
})()
//
(async function () {
console.log('start')
const a = await 100
console.log('a', a)
const b = await Promise.resolve(200)
console.log('b', b)
const c = await Promise.reject(300)
console.log('c', c)
console.log('end')
})() // 執(zhí)行完畢良蒸,打印出那些內(nèi)容?
start 100 200
? 8. async/await 語法問題
console.log(100)
setTimeout(() => {
console.log(200)
})
Promise.resolve().then(() => {
console.log(300)
})
console.log(400)
// 100 400 300 200
? 8. 執(zhí)行順序問題
async function async1 () {
console.log('async1 start')
await async2() // 這一句會同步執(zhí)行伍玖,返回 Promise 嫩痰,其中的 `console.log('async2')` 也會同步執(zhí)行
console.log('async1 end') // 上面有 await ,下面就變成了“異步”窍箍,類似 cakkback 的功能(微任務(wù))
}
async function async2 () {
console.log('async2')
}
console.log('script start')
setTimeout(function () { // 異步串纺,宏任務(wù)
console.log('setTimeout')
}, 0)
async1()
new Promise (function (resolve) { // 返回 Promise 之后,即同步執(zhí)行完成椰棘,then 是異步代碼
console.log('promise1') // Promise 的函數(shù)體會立刻執(zhí)行
resolve()
}).then (function () { // 異步纺棺,微任務(wù)
console.log('promise2')
})
console.log('script end')
// 同步代碼執(zhí)行完之后,屢一下現(xiàn)有的異步未執(zhí)行的邪狞,按照順序
// 1. async1 函數(shù)中 await 后面的內(nèi)容 —— 微任務(wù)
// 2. setTimeout —— 宏任務(wù)
// 3. then —— 微任務(wù)
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout