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
call
和apply
可以指定函數運行時的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點:
-
所有函數都有一個prototype指針,指向原型對象,如圖中的Foo的prototype指針。prototype指針的意義是,當我們使用這個構造函數new出新對象的時候,新對象的
__proto__
指向prototype -
構造函數的prototype所指向的原型對象有一個constructor指針,指回構造函數。如圖中Foo.prototype的constructor指針指向Foo。constructor指針有助於我們找到一個對象的構造函數是誰。
-
__proto__
每個對象都有,js在new一個對象的時候,會將它的__proto__
指向構造函數的prototype指向的那個對象。在上圖中,f1、f2這些實例對象的__proto__
都指向了Foo.prototype。 -
如果一個對象的
__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語言的一大特點就是單線程,也就是說,同一個時間只能做一件事
單線程就意味著,所有任務需要排隊,前一個任務結束,才會執行後一個任務。如果前一個任務耗時很長,後一個任務就不得不一直等著。於是,所有任務可以分成兩種,一種是同步任務,另一種是非同步任務
同步任務指的是,在主線程上排隊執行的任務,只有前一個任務執行完畢,才能執行後一個任務;
非同步任務指的是,不進入主線程、而進入"任務隊列" 的任務,只有"任務隊列"通知主線程,某個非同步任務可以執行了,該任務才會進入主線程執行。
-
所有同步任務都在主線程上執行,形成一個執行棧。
-
主線程之外,還存在一個"任務隊列"。只要非同步任務有了運行結果,就在"任務隊列"之中放置一個事件
-
一旦"執行棧"中的所有同步任務執行完畢,系統就會讀取"任務隊列",看看裡面有哪些事件。那些對應的非同步任務,於是結束等待狀態,進入執行棧,開始執行
-
主線程不斷重覆上面的第三步
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)