js篇

請配合 簡書側(cè)邊欄文章導(dǎo)航 食用土榴,因為很長

● JS-變量類型和計算

? 1. 值類型有哪些6块仆?

值類型

UndefinedNull逝薪、BooleanNumber曼月、String容达、Symbol (new in ES 6)

? 2. 引用類型有哪些9?

引用類型

Object 類型冰沙、Array 類型侨艾、Date 類型RegExp 類型拓挥、Function 類型 Set唠梨,MapweakSet侥啤,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 能判斷哪些類型蚁鳖?

  1. 識別所有值類型磺芭, null 除外
  2. 識別函數(shù)
  3. 判斷是否是引用類型(不可再細分) object array 都是object

● js-原型和原型鏈

es5時代的繼承

一篇文章理解js繼承

JS中的繼承(上)

JS中的繼承(下)

apply、call 的區(qū)別和用途

知乎

call醉箕、apply钾腺、bind使用和區(qū)別

手寫call apply bind

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)建不需要的顽决、多余的屬性。

  • 原型鏈還能保持不變导匣;能夠正常使用 instanceofisPrototypeOf()


//原型式繼承
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-作用域和閉包

作用域

我用了兩個月的時間才理解 let

深入理解JavaScript作用域和作用域鏈

核心:作用域是在運行時代碼中的某些特定部分中變量灯荧,函數(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)建:

  1. 在一個函數(shù)內(nèi)部
  2. 在一個代碼塊(由一對花括號包裹)內(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

閉包

JS 中的閉包是什么?

 **核心: 「函數(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 的值到底是什么带污?一次說清楚

核心: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 過程

  1. 同步代碼纫事,一行一行放在 call stack(調(diào)用棧) 執(zhí)行
  2. 遇到異步,會先“記錄”下(web apis)所灸,等待時機(定時丽惶、網(wǎng)絡(luò)請求等)
  3. 時機到了,就移動到 callback queue(回調(diào) 隊列)
  4. 如call stack 為空 (即同步代碼執(zhí)行完畢) event loop 開始工作
  5. 輪詢查找 callback queue爬立, 如有則移動到 call stack 執(zhí)行
  6. 然后繼續(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末祷蝌,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子帆卓,更是在濱河造成了極大的恐慌巨朦,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件剑令,死亡現(xiàn)場離奇詭異罪郊,居然都是意外死亡,警方通過查閱死者的電腦和手機尚洽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門悔橄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人腺毫,你說我怎么就攤上這事癣疟。” “怎么了潮酒?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵睛挚,是天一觀的道長。 經(jīng)常有香客問我急黎,道長扎狱,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任勃教,我火速辦了婚禮淤击,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘故源。我一直安慰自己污抬,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布绳军。 她就那樣靜靜地躺著印机,像睡著了一般矢腻。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上射赛,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天多柑,我揣著相機與錄音,去河邊找鬼楣责。 笑死顷蟆,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的腐魂。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼逐纬,長吁一口氣:“原來是場噩夢啊……” “哼蛔屹!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起豁生,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤兔毒,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后甸箱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體育叁,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡虾攻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年雄卷,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片冕象。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡豌骏,死狀恐怖龟梦,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情窃躲,我是刑警寧澤计贰,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站蒂窒,受9級特大地震影響躁倒,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜洒琢,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一秧秉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧衰抑,春花似錦福贞、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽完丽。三九已至,卻和暖如春拇舀,著一層夾襖步出監(jiān)牢的瞬間逻族,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工骄崩, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留聘鳞,地道東北人。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓要拂,卻偏偏與公主長得像抠璃,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子脱惰,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容