JavaScript進階

来源:https://www.cnblogs.com/zhendayong/archive/2023/02/08/17102414.html
-Advertisement-
Play Games

javaScript進階 一、作用域 JS的作用域簡單來說就是變數(變數作用於又稱上下文)和函數生效(能被訪問)的區域 1.全局作用域 函數之外聲明的變數,會成為全局變數。 變數在程式的任何地方都能被訪問,表示它是全局變數,window 對象的內置屬性都擁有全局作用域。 自動全局 如果您為尚未聲明的 ...


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

img

2.constructor

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

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

img

在 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__指向的對象)去找,找到了就可以調用

img

圖片第一行告訴了我們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語言的一大特點就是單線程,也就是說,同一個時間只能做一件事
單線程就意味著,所有任務需要排隊,前一個任務結束,才會執行後一個任務。如果前一個任務耗時很長,後一個任務就不得不一直等著。於是,所有任務可以分成兩種,一種是同步任務,另一種是非同步任務

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

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

img

  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代碼的執行順序。

img

進入整體代碼(巨集任務)後,開始第一次迴圈。

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

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)

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 數棧作為袋鼠雲打造的一站式數據開發與治理平臺,從2016年發佈第⼀個版本開始,就始終堅持著以技術為核⼼、安全為底線、提效為⽬標、中台為戰略的思想,堅定不移地⾛國產化信創路線,不斷推進產品功能迭代、技術創新、服務細化和性能升級。 伴隨業務的⻜速發展,數棧設計團隊也啟動了針對數棧產品的體驗升級計劃,從開 ...
  • 輕便式Redis Monitor是面向研發人員的圖形可視化監控工具,借鑒了LEPUS(天兔)監控平臺以及redis-cli info命令輸出的監控指標項,去掉了一些不必要、看不懂的監控項,目前採集了資料庫連接數、QPS、記憶體使用率統計和同步複製延遲時長。 Redis Monitor可以監控單機... ...
  • GreatSQL社區原創內容未經授權不得隨意使用,轉載請聯繫小編並註明來源。 GreatSQL是MySQL的國產分支版本,使用上與MySQL一致。 作者: 葉金榮 文章來源:GreatSQL社區原創 MySQL 8.0版本計劃 MySQL 8.0開始採用快速迭代開發模式,基本上是每隔3個月就發佈一個 ...
  • SqlServer屬於商業資料庫,不可能像Mysql等資料庫一樣,去解析相關的資料庫binlog,從而實現增量數據的回放,結合應用屬性,最後確定採用離線遷移方式,從SqlServer中將表數據全部讀出,然後將數據寫入到pg中,採用此種方案的弊病就是程式端需停止寫入(應用可將部分數據緩存到本地),等待... ...
  • Android 常用 adb 命令總結 adb ( 全稱為Android Debug Bridge)是一個C/S 架構命令行工具,起到調試橋的作用,是連接Android手機與PC端的橋梁,方便我們在電腦上對手機進行操作。adb 命令可用於執行各種設備操作,如安裝和調試應用。 點擊下載adb工具 AD ...
  • 支持日語假名註音的JpTextView 支持自動換行 支持自動寬高 支持一次標記或者總是標記 網上找了一下,發現沒有類型的輪子,就自己造了一個 源碼:https://github.com/toukomine/JpTextView 引入依賴 代碼已上傳 mavenCentral倉庫,在項目根目錄的bu ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 前言 我以前很喜歡封裝組件,什麼東西不喜歡別人的,總喜歡自己搞搞,這讓人很有成就感,雖然是重覆造輪子,但是能從無聊的crud業務中暫時解脫出來,對我來說也算是一種休息,相信有很多人跟我一樣有這個習慣。 這種習慣在獨立開發時無所謂,畢竟沒人 ...
  • react腳手架配置代理總結 方法一 在 package.json 中追加如下配置 "proxy":"<http://localhost:5000>" 說明: 優點:配置簡單,前端請求資源時可以不加任何首碼。 缺點:不能配置多個代理。 工作方式:上述方式配置代理,當請求了3000不存在的資源時,那麼 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...