JavaScript進階

2023-02-08 18:00:41

javaScript進階

一、作用域

JS的作用域簡單來說就是變數(變數作用於又稱上下文)和函數生效(能被存取)的區域

1.全域性作用域

函數之外宣告的變數,會成為全域性變數。

變數在程式的任何地方都能被存取,表示它是全域性變數,window 物件的內建屬性都擁有全域性作用域。

自動全域性

如果您為尚未宣告的變數賦值,此變數會自動成為全域性變數

2.函數作用域

在固定的程式碼片段(函數)才能被存取

函數作用域就是一個獨立的地盤,讓變數不會外洩、暴露出去。也就是說作用域最大的用處就是隔離變數,不同作用域下同名變數不會有衝突。

變數取值:到建立 這個變數 的函數的作用域中取值

3.作用域鏈

變數取值到 建立 這個變數 的函數的作用域中取值。

但是如果在當前作用域中沒有查到值,就會向上級作用域去查,直到查到全域性作用域,這麼一個查詢過程形成的鏈條就叫做作用域鏈。

JavaScript 屬於直譯語言,JavaScript 的執行分為:解釋和執行兩個階段,這兩個階段所做的事並不一樣:

解釋階段:
  • 詞法分析
  • 語法分析
  • 作用域規則確定
執行階段:
  • 建立執行上下文
  • 執行函數程式碼
  • 垃圾回收

JavaScript 解釋階段便會確定作用域規則,因此作用域在函數定義時就已經確定了,而不是在函數呼叫時確定,但是執行上下文是函數執行之前建立的。執行上下文最明顯的就是 this 的指向是執行時確定的。而作用域存取的變數是編寫程式碼的結構確定的。

作用域和執行上下文之間最大的區別是:

執行上下文在執行時確定,隨時可能改變;作用域在定義時就確定,並且不會改變

一個作用域下可能包含若干個上下文環境。有可能從來沒有過上下文環境(函數從來就沒有被呼叫過);有可能有過,現在函數被呼叫完畢後,上下文環境被銷燬了;有可能同時存在一個或多個(閉包)。同一個作用域下,不同的呼叫會產生不同的執行上下文環境,繼而產生不同的變數的值

二、this指向

在函數內,this是非常特殊的關鍵詞識別符號,在每個函數的作用域中被自動建立

當函數被呼叫,一個上下文就被建立。上下文包括函數在哪呼叫,誰呼叫的,引數是哪些,等等,上下文中的this,指的就是函數指行期間的this。this的上下文基於函數呼叫的情況。和函數在哪定義無關,但是和函數怎麼呼叫有關

this理解的關鍵:

1:this永遠指向一個物件;

2:this的指向完全取決於函數呼叫的位置;

1、全域性

在全域性上下文(任何函數以外),this指向全域性物件。

console.log(this === window); // true

2、函數

在函數內部時,this由函數怎麼呼叫來確定

//簡單呼叫,即獨立函數呼叫
function f1(){
  return this;
}
//當前呼叫者其實是window  window.f1()
f1() === window;

當函數作為物件方法呼叫時,this指向該物件

var obj = {
	type: 1,
    name: 'Tina',
    age: 18,
    sayHi:function(){
        console.log(this)
        console.log('hi~~')
    }
}
//this === obj
obj.sayHi()

3.建構函式

建構函式的this指向建立的範例物件

function Obj (name,age){
    this.name = name
    this.age = age
    this.sayHai= function(){
        console.log(this)
    }
}

let o = new Obj('Tina',18) //{name: 'tina', age: 18}

建構函式執行過程分為4步:

第一步: 建立一個Object物件範例。
第二步: 將建構函式的執行物件賦給新生成的這個範例。
第三步: 執行建構函式中的程式碼
第四步: 返回新生成的物件範例

function Obj (name){
    this.name = name
    return name
}

let o = new Obj('Tina') // ???

建構函式在new的時候,會預設建立一個空的 { },並且把建構函式的prototype賦值給空物件的__proto__,當建構函式返回值不是物件時,返回值就會預設成建構函式自己建立的物件

4.call和apply

callapply可以指定函數執行時的this

call方法使用的語法規則
函數名稱.call(obj,arg1,arg2...argN);
引數說明:
obj:函數內this要指向的物件,
arg1,arg2...argN :參數列,引數與引數之間使用一個逗號隔開

apply方法使用的語法規則

函數名稱.apply(obj,[arg1,arg2...,argN])
引數說明:
obj :this要指向的物件
[arg1,arg2...argN] : 參數列,要求格式為陣列

function add(c, d){
  return this.a + this.b + c + d;
}

var o = {a:1, b:3};
add.call(o, 5, 7); // 1 + 3 + 5 + 7 = 16
add.apply(o, [10, 20]); // 1 + 3 + 10 + 20 = 34

練習題

var A = {
    name: '張三',
    f: function () {
        console.log('姓名:' + this.name);
    }
};

var B = {
    name: '李四'
};

B.f = A.f;
B.f()   
A.f()  
function foo() {
    console.log(this.a);
}
var obj2 = {
    a: 2,
    fn: foo
};
var obj1 = {
    a: 1,
    o1: obj2
};
obj1.o1.fn();
var o = {
    a:10,
    b:{
        a:12,
        fn:function(){
            console.log(this.a); 
            console.log(this); 
        }
    }
}
var j = o.b.fn;
j();
var x = 3;
var y = 4;
var obj = {
    x: 1,
    y: 6,
    getX: function() {
        var x =5;
        return function() {
            return this.x;
        }();
    },
    getY: function() {
        var y =7;
         return this.y;
    }
}
console.log(obj.getX())
console.log(obj.getY())
 var name="the window";

 var object={
    name:"My Object", 
    getName:function(){ 
       return this.name;
   } 
 }

 object.getName();   

(object.getName)();   

(object.getName=object.getName)();  

三、原型

1.prototype

在JavaScript中,每個函數 都有一個prototype屬性,當一個函數被用作建構函式來建立範例時,這個函數的prototype屬性值會被作為原型賦值給物件範例(也就是設定 範例的__proto__屬性),也就是說,所有範例的原型參照的是函數的prototype屬性。

建構函式使用方式

function Person(name,age){
    this.name = name
    this.age = age
}
var p = new Person('張三',20);

每一個JavaScript物件(除了 null )都具有的一個屬性,叫__proto__,這個屬性會指向該物件的原型

console.log(p.__proto__ === Person.prototype); // true

2.constructor

每個原型都有一個 constructor 屬性指向關聯的建構函式

console.log(Person === p.__proto__.constructor); //true

在 Javascript 語言中,constructor 屬性是專門為 function 而設計的,它存在於每一個 function 的prototype 屬性中。這個 constructor 儲存了指向 function 的一個參照

通過建構函式建立的物件,constructor 指向建構函式,而建構函式本身的constructor ,則指向Function本身,因為所有的函數都是通過new Function()構造的

   function Person() {
       
    }
    var p = new Person()
    console.log(Person.prototype); // Object{} 
    console.log(p.prototype); // undifined
    console.log(p.constructor); //function Person(){}    
    此處的p是通過 Person函數構造出來的,所以p的constructor屬性指向Person
    console.log(Person.constructor); //function Function(){}
    之前提過,每個函數其實是通過new Function()構造的
    console.log({}.constructor); // function Object(){}
    每個物件都是通過new Object()構造的
    console.log(Object.constructor); // function Function() {}
    Object也是一個函數,它是Function()構造的
    console.log([].constructor);  //function Array(){}

函數是物件構造的 物件也是函數構造的,倆者即是函數也是物件,所以為什麼建構函式它是一個函數卻返回一個物件,倆者是互相繼承的關係

    var o1 = new f1();
    typeof o1 //"object"  

prototype的用法

最主要的方法就是將屬性暴露成公用的

程式碼對比:

    function Person(name,age){
        this.name = name;
        this.age = age;
        this.sayHello = function(){
            console.log(this.name + "say hello");
        }
    }
    var girl = new Person("bella",23);
    var boy = new Person("alex",23);
    console.log(girl.name);  //bella
    console.log(boy.name);   //alex
    console.log(girl.sayHello === boy.sayHello);  //false
    function Person(name,age){
        this.name = name;
        this.age = age;
        
    }
    Person.prototype.sayHello=function(){
        console.log(this.name + "say hello");
    }
    var girl = new Person("bella",23);
    var boy = new Person("alex",23);
    console.log(girl.name);  //bella
    console.log(boy.name);   //alex
    console.log(girl.sayHello === boy.sayHello);  //true 

總結:

function Person(){

 }
var person1=new Person()
 
person1.__proto__==Person.prototype

person1.constructor==Person

Person.__proto__==Function.prototype

Person.prototype.constructor==Person

person1.__proto__.constructor==Person

四、原型鏈

在js中,大部分東西都是物件,陣列是物件,函數也是物件,物件更加是物件。不管我們給陣列和函數定義什麼內容,它們總是有一些相同的方法和屬性。比如說valueOf(),toString()等

這說明一個物件所擁有的屬性不僅僅是它本身擁有的屬性,它還會從其他物件中繼承一些屬性。當js在一個物件中找不到需要的屬性時,它會到這個物件的父物件上去找,以此類推,這就構成了物件的原型鏈

function Foo(_name) {
  this.name = _name;
}
Foo.prototype.show = function() {
  console.log('I am ', this.name);
};
var f1 = new Foo('obj1');
var f2 = new Foo('obj2');

f1.show();  //  I am obj1
f2.show();  //  I am obj2
//我們定義的show函數在Foo.prototype中,當我們執行f1.show()時,js發現f1本身沒有show這個屬性,所以它就到f1的原型(也就是__proto__指向的物件)去找,找到了就可以呼叫

圖片第一行告訴了我們4點:

  1. 所有函數都有一個prototype指標,指向原型物件,如圖中的Foo的prototype指標。prototype指標的意義是,當我們使用這個建構函式new出新物件的時候,新物件的__proto__指向prototype

  2. 建構函式的prototype所指向的原型物件有一個constructor指標,指回建構函式。如圖中Foo.prototype的constructor指標指向Foo。constructor指標有助於我們找到一個物件的建構函式是誰。

  3. __proto__每個物件都有,js在new一個物件的時候,會將它的__proto__指向建構函式的prototype指向的那個物件。在上圖中,f1、f2這些範例物件的__proto__都指向了Foo.prototype。

  4. 如果一個物件的__proto__指向了另一個物件,那麼前者就繼承了後者的所有屬性

    function Foo(_name) {
      this.name = _name;
    }
    Foo.prototype.show = function() {
      console.log('I am ', this.name);
    };
    var f1 = new Foo('obj1');
    var f2 = new Foo('obj2');
    
    var obj = {
      type:1
    }
    f1.__proto__ = obj
    console.dir(f1)
    
    f1.show();  //  I am obj1
    f2.show();  //  I am obj2
    

Foo是一個函數,它的建構函式是js內部的function Function(),Function的prototype指向了一個物件Function.prototype,因此Foo的__proto__就指向了Function.prototype

所有的函數都以function Function()為建構函式,因此,所有函數(包括function Function()和function Object())的__proto__都指向Function.prototype這個物件,這個物件中定義了所有函數都共有的方法,比如call()、apply()等。

我們繼續深入下去,Function.prototype這個物件,它就是一個普通的物件,它的建構函式是js內建的function Object(),function Object()的prototype指向Object.prototype,因此Function.prototype.__proto__就指向Object.prototype,這個物件中定義了所有物件共有的屬性,比如我們之前說的hasOwnProperty()和toString()等。

同理,Foo.prototype和其他自定義的物件也是__proto__指向Object.prototype物件

Object.prototype就是原型鏈的終點了,它的__proto__是null,js查詢屬性時,如果到這裡還沒有找到,那就是undefined了

五、閉包

函數和函數內部能存取到的變數加在一起就是一個閉包

常規認為,一個函數巢狀另一個函數,兩個函數中間的環境,叫閉包,但其實這也是製造一個不會被汙染沙箱環境,實質上,由於js函數作用域的存在,所有的函數,都可以是一個閉包

function foo(){
  var num = 1
  function add(){
    num++
    return num
  }
  return add
}

var func = foo()
func()

閉包常常用來間接存取一個變數也可以理解為隱藏一個變數

function foo(){
  var num = 18
  var obj = {
      text:'我是一個字串',
      getNUm:function(){
          return num
      }
  }
  return obj
}

var obj = foo()
var age = obj.getNUm()
console.log(age)

由於 JS 的函數內部可以使用函數外部的變數,而函數外部無法存取到函數內部的變數,所以正好符合了閉包的定義。所以只要懂了 JS 的作用域,自然而然就懂了閉包。

六、事件機制

JavaScript語言的一大特點就是單執行緒,也就是說,同一個時間只能做一件事
單執行緒就意味著,所有任務需要排隊,前一個任務結束,才會執行後一個任務。如果前一個任務耗時很長,後一個任務就不得不一直等著。於是,所有任務可以分成兩種,一種是同步任務,另一種是非同步任務

同步任務指的是,在主執行緒上排隊執行的任務,只有前一個任務執行完畢,才能執行後一個任務;

非同步任務指的是,不進入主執行緒、而進入"任務佇列" 的任務,只有"任務佇列"通知主執行緒,某個非同步任務可以執行了,該任務才會進入主執行緒執行。

  1. 所有同步任務都在主執行緒上執行,形成一個執行棧。

  2. 主執行緒之外,還存在一個"任務佇列"。只要非同步任務有了執行結果,就在"任務佇列"之中放置一個事件

  3. 一旦"執行棧"中的所有同步任務執行完畢,系統就會讀取"任務佇列",看看裡面有哪些事件。那些對應的非同步任務,於是結束等待狀態,進入執行棧,開始執行

  4. 主執行緒不斷重複上面的第三步

    console.log(1);
    setTimeout(function() {
        console.log(2);
    },1000)
    setTimeout(function() {
        console.log(3);
    },0)
    console.log(4);
    /*
        分析:
            同步任務,按照順序一步一步執行
            非同步任務,當讀取到非同步任務的時候,將非同步任務放置到任務佇列
    中,當滿足某種條件或者說指定事情完成了(這裡的是時間分別是達到了0ms和1000ms)當指定
    事件完成了才從任務佇列中註冊到主執行緒的事件佇列,當同步事件完成了,便從
    事件佇列中讀取事件執行。(因為3的事情先完成了,所以先從任務佇列中註冊到
    事件佇列中,所以先執行的是3而不是在前面的2)
    */
    

宏任務與微任務

宏任務:script程式碼,setTimeout,setInterval
微任務:Promise,process.nextTick

不同型別的任務會進入對應的任務佇列。
事件迴圈的順序,決定js程式碼的執行順序。

進入整體程式碼(宏任務)後,開始第一次迴圈。

接著執行所有的微任務。然後再次從宏任務開始,找到其中一個任務佇列執行完畢,再執行所有的微任務

console.log(1);
setTimeout(function() {
    console.log(2)
},1000);
 
new Promise(function(resolve) {
    console.log(3);
    resolve();
}
).then(function() {
    console.log(4)
});
console.log(5);
/*
    為什麼是這樣呢?因為以同步非同步的方式來解釋執行機制是不準確的,更加準確的方式是宏任務和微任務:
    因此執行機制便為:執行宏任務 ===> 執行微任務 ===> 執行另一個宏任務 ===> 不斷迴圈
        即:在一個事件迴圈中,執行第一個宏任務,宏任務執行結束,執行當前事件迴圈中的微任務,
執行完畢之後進入下一個事件迴圈中,或者說執行下一個宏任務
*/

七、ES6

1.let

let 宣告的變數只在 let 命令所在的程式碼塊內有效

{
  let a = 0;
  a   // 0
}
a   // 報錯 ReferenceError: a is not defined

let 只能宣告一次 (var 可以宣告多次)

let a = 1;
let a = 2;
var b = 3;
var b = 4;
a  // Identifier 'a' has already been declared 識別符號「a」已宣告
b  // 4

let 不存在變數提升,var 會變數提升

console.log(a);  //ReferenceError: a is not defined  參照錯誤:未定義a
let a = "apple";
 
console.log(b);  //undefined
var b = "banana";

2.const

const 宣告一個唯讀的常數,一旦宣告,常數的值就不能改變

const PI = "3.1415926";
PI = '123' //Assignment to constant variable 常數變數的賦值

暫時性死區:

var PI = "a";
if(true){
  console.log(PI);  // ReferenceError: PI is not defined
  const PI = "3.1415926";
}

ES6 明確規定,程式碼塊內如果存在 let 或者 const,程式碼塊會對這些命令宣告的變數從塊的開始就形成一個封閉作用域。程式碼塊內,在宣告變數 PI 之前使用它會報錯。

3.解構賦值

解構賦值是對賦值運運算元的擴充套件,是一種針對陣列或者物件進行模式匹配,然後對其中的變數進行賦值處理

基本

let [a, b, c] = [1, 2, 3];
// a = 1
// b = 2
// c = 3

let { foo, bar } = { foo: 'aaa', bar: 'bbb' };
// foo = 'aaa'
// bar = 'bbb'
 
let { baz : foo } = { baz : 'ddd' };
// foo = 'ddd'

可巢狀

let [a, [[b], c]] = [1, [[2], 3]];
// a = 1
// b = 2
// c = 3

let obj = {p: ['hello', {y: 'world'}] };
let {p: [x, { y }] } = obj;
// x = 'hello'
// y = 'world'
let obj = {p: ['hello', {y: 'world'}] };
let {p: [x, { }] } = obj;
// x = 'hello'

可忽略

let [a, , b] = [1, 2, 3];
// a = 1
// b = 3

不完全解構

let [a = 1, b] = []; 
// a = 1, b = undefined

let obj = {p: [{y: 'world'}] };
let {p: [y, x] } = obj;
// x = undefined
// y = 'world'

剩餘運運算元

let [a, ...b] = [1, 2, 3];
//a = 1
//b = [2, 3]

let {a, b, ...rest} = {a: 10, b: 20, c: 30, d: 40};
// a = 10
// b = 20
// rest = {c: 30, d: 40}

解構預設值

let [a = 2] = [undefined]; // a = 2

let {a = 10, b = 5} = {a: 3};
// a = 3; b = 5;
let {a: aa = 10, b: bb = 5} = {a: 3};
// aa = 3; bb = 5;

字串等

在陣列的解構中,解構的目標若為可遍歷物件,皆可進行解構賦值

let [a, b, c, d, e] = 'hello';
// a = 'h'
// b = 'e'
// c = 'l'
// d = 'l'
// e = 'o'

4.Proxy

Proxy 可以對目標物件的讀取、函數呼叫等操作進行攔截,然後進行操作處理。它不直接操作物件,而是像代理模式,通過物件的代理物件進行操作,在進行這些操作時,可以新增一些需要的額外操作

let target = {
    name: 'Tom',
    age: 24
}
let handler = {
    get: function(target, key) {
        console.log('getting '+key);
        return target[key]; // 不是target.key
    },
    set: function(target, key, value) {
        console.log('setting '+key);
        target[key] = value;
    }
}
let proxy = new Proxy(target, handler)
proxy.name     // 實際執行 handler.get
proxy.age = 25 // 實際執行 handler.set
// getting name
// setting age
// 25

5.箭頭函數

箭頭函數提供了一種更加簡潔的函數書寫方式。基本語法是:

引數 => 函數體
var f = v => v;
//等價於
var f = function(a){
 return a;
}
f(1);  //1

當箭頭函數沒有引數或者有多個引數,要用 () 括起來

var f = (a,b) => a+b;
f(6,2);  //8

當箭頭函數函數體有多行語句,用 {} 包裹起來,表示程式碼塊,當只有一行語句,並且需要返回結果時,可以省略 {} , 結果會自動返回

var f = (a,b) => {
 let result = a+b;
 return {};
}
f(6,2);  // 8

當箭頭函數要返回物件的時候,為了區分於程式碼塊,要用 () 將物件包裹起來

// 報錯
var f = (id,name) => {id: id, name: name};
f(6,2);  // SyntaxError: Unexpected token :
 
// 不報錯
var f = (id,name) => ({id: id, name: name});
f(6,2);  // {id: 6, name: 2}

箭頭函數沒有 this、arguments繫結

var func = () => {
  // 箭頭函數裡面沒有 this 物件,
  // 此時的 this 是外層的 this 物件,即 Window 
  console.log(this)
}
func(55)  // Window 
 
var func = () => {    
  console.log(arguments)
}
func(55);  // ReferenceError: arguments is not defined

箭頭函數體中的 this 物件,是定義函數時的物件,而不是使用函數時的物件

function fn(){
  setTimeout(()=>{
    // 定義時,this 繫結的是 fn 中的 this 物件
    console.log(this.a);
  },0)
}
var a = 20;
fn()
// fn 的 this 物件為 window 所以列印20

6.class類

在ES6中,class (類)作為物件的模板被引入,可以通過 class 關鍵字定義類。class 的本質是 function

它可以看作一個語法糖,讓物件原型的寫法更加清晰、更像物件導向程式設計的語法

注意要點:不可重複宣告

class Example {
    constructor(a) {
        this.a = a;
    }
}
let obj = new Example(1)
console.log(obj)

通過 extends 實現類的繼承

class Child extends Example { 
    constructor(a,b) {
        super(b);
        this.b = a;
        this.hi=()=>alert('hi')
    }
 }
 let b = new Child(1,2)
 console.log(b)//{a:2,b:1,hi:()=>alert('hi')}

7.模組

export和import

ES6 的模組化分為匯出(export) @與匯入(import)兩個模組

模組匯入匯出各種型別的變數,如字串,數值,函數,類。

  • 匯出的函數宣告與類宣告必須要有名稱(export default 命令另外考慮)。
  • 不僅能匯出宣告還能匯出參照(例如函數)。
  • export 命令可以出現在模組的任何位置
  • import 命令會提升到整個模組的頭部,首先執行。
/*-----export [test.js]-----*/
let myName = "Tom";
let myAge = 20;
let myfn = function(){
    return "My name is" + myName + "! I'm '" + myAge + "years old."
}
let myClass =  class myClass {
    static a = "yeah!";
}
export { myName, myAge, myfn, myClass }
 
/*-----import [xxx.js]-----*/
import { myName, myAge, myfn, myClass } from "./test.js";
console.log(myfn());// My name is Tom! I'm 20 years old.
console.log(myAge);// 20
console.log(myName);// Tom
console.log(myClass.a );// yeah!

as 的用法

export 命令匯出的介面名稱,須和模組內部的變數有一一對應關係

匯入的變數名,須和匯出的介面名稱相同,順序可以不一致

不同模組匯出介面名稱命名重複, 使用 as 重新定義變數名

/*-----export [test.js]-----*/
let myName = "Tom";
export { myName as exportName }
 
/*-----import [xxx.js]-----*/
import { exportName } from "./test.js";
console.log(exportName);// Tom
//使用 as 重新定義匯出的介面名稱,隱藏模組內部的變數
/*-----export [test1.js]-----*/
let myName = "Tom";
export { myName }
/*-----export [test2.js]-----*/
let myName = "Jerry";
export { myName }
/*-----import [xxx.js]-----*/
import { myName as name1 } from "./test1.js";
import { myName as name2 } from "./test2.js";
console.log(name1);// Tom
console.log(name2);// Jerry

export default

  • 在一個檔案或模組中,export、import 可以有多個,export default 僅有一個。
  • export default 中的 default 是對應的匯出介面變數。
  • 通過 export 方式匯出,在匯入時要加{ },export default 則不需要。
  • export default 向外暴露的成員,可以使用任意變數來接收。
var a = "My name is Tina!";
export default a; // 僅有一個

import b from "./xxx.js"; // 不需要加{}, 使用任意變數接收

8.Promise

Promise 非同步操作有三種狀態:pending(進行中)、fulfilled(已成功)和 rejected(已失敗)。除了非同步操作的結果,任何其他操作都無法改變這個狀態。

Promise 物件只有:從 pending 變為 fulfilled 和從 pending 變為 rejected 的狀態改變。只要處於 fulfilled 和 rejected ,狀態就不會再變了即 resolved(已定型)

const p1 = new Promise(function(resolve,reject){
    resolve('success1');
   // resolve('success2');
}); 
const p2 = new Promise(function(resolve,reject){  
    resolve('success3'); 
   // reject('reject');
});
p1.then(function(value){  
    console.log(value); // success1
});
p2.then(function(value){ 
    console.log(value); // success3
});

無法取消 Promise ,一旦新建它就會立即執行,無法中途取消。

如果不設定回撥函數,Promise 內部丟擲的錯誤,不會反應到外部。

當處於 pending 狀態時,無法得知目前進展到哪一個階段(剛剛開始還是即將完成)

then方法

then 方法接收兩個函數作為引數,第一個引數是 Promise 執行成功時的回撥,第二個引數是 Promise 執行失敗時的回撥,兩個函數只會有一個被呼叫。

在 JavaScript 事件佇列的當前執行完成之前,回撥函數永遠不會被呼叫

const p = new Promise(function(resolve,reject){
  resolve('success');
});
 
p.then(function(value){
  console.log(value);
});
 
console.log('first');
// first
// success

通過 .then 形式新增的回撥函數,不論什麼時候,都會被呼叫。
then 方法將返回一個 resolved 或 rejected 狀態的 Promise 物件用於鏈式呼叫,且 Promise 物件的值就是這個返回值

多次呼叫

const p = new Promise(function(resolve,reject){
  resolve(1);
}).then(function(value){ // 第一個then // 1
  console.log(value);
  return value * 2;
}).then(function(value){ // 第二個then // 2
  console.log(value);
}).then(function(value){ // 第三個then // undefined
  console.log(value);
  return Promise.resolve('resolve'); 
}).then(function(value){ // 第四個then // resolve
  console.log(value);
  return Promise.reject('reject'); 
}).then(function(value){ // 第五個then //reject:reject
  console.log('resolve:' + value);
}, function(err) {
  console.log('reject:' + err);
});

9.async

async 函數返回一個 Promise 物件,可以使用 then 方法新增回撥函數

async function helloAsync(){
    return "helloAsync";
  }
  
console.log(helloAsync())  // Promise {<resolved>: "helloAsync"}
 
helloAsync().then(v=>{
   console.log(v);         // helloAsync
})

async 函數中可能會有 await 表示式,async 函數執行時,如果遇到 await ,那麼await標記的表示式會先執行一遍,將await後面的程式碼加入到微任務中,然後就會跳出整個async函數來執行後面的程式碼

await 關鍵字僅在 async function 中有效

function testAwait(){
   return new Promise((resolve) => {
       setTimeout(function(){
          console.log("testAwait");
          resolve();
       }, 1000);
   });
}
 
async function helloAsync(){
   await testAwait();
   console.log("helloAsync");
 }
helloAsync();
// testAwait
// helloAsync
練習題
const promise = new Promise((resolve, reject) => {
    console.log(1);
    resolve();
    console.log(2);
    reject('error');
})
promise.then(() => {
    console.log(3);
}).catch(e => console.log(e))
console.log(4);
const promise = new Promise((resolve, reject) => {
        setTimeout(() => {
             console.log('once')
             resolve('success')
        }, 1000)
 })
promise.then((res) => {
       console.log(res)
     })
promise.then((res) => {
     console.log(res)
 })
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);
async function async1() {
    console.log(1)
    await async2()
    console.log(2)
}
async function async2() {
    console.log(3)
}
console.log(4)
setTimeout(() => {
    console.log(5)
}, 0);
async1()
new Promise(resolve => {
        console.log(6)
        resolve()
    })
    .then(() => {
        console.log(7)
    })
console.log(8)