JavaScript (JS)
是一種編程語言,爲一般用於客戶端(client-side)
的網頁動態腳本,不過,也常經過像Node.js
這樣的包,用於 服務器端(server-side)
。javascript
今天,發一篇關於Js基礎知識點
的文章,爲更多的新人指路。總會有人在你的前方爲你探路,前行之路,你不孤單~ html
先來個目錄結構前端
───一、變量聲明
│ └───JavaScript 的數據類型分類和判斷
│ └───引用類型和值類型
───二、原型與原型鏈(繼承)
│ └───原型和原型鏈
───三、做用域和閉包
│ └───做用域
│ └───什麼是閉包,如何造成?
───四、如何理解同步和異步
│ └───同步 vs 異步
│ └───異步和單線程
│ └───前端異步的場景描述
───五、簡單描述一下對 ES6/ES7 的瞭解
│ └───解構賦值
│ └───箭頭函數
│ └───Promise 對象
│ └───Set 和 Map 數據結構
複製代碼
在 JavaScript 中,共有7種基本類型:java
其中string
、number
、Boolean
、undefined
、Null
、symbol
是6種原始類型。面試
值得注意
的是:原始類型中不包含 Object
。ajax
類型判斷用到哪些方法?編程
一、typeofjson
typeof xxx
獲得的值有如下幾種類型: undefined boolean number string object function symbol
。數組
例如:promise
console.log(typeof 42);
// expected output: "number"
console.log(typeof 'blubber');
// expected output: "string"
console.log(typeof true);
// expected output: "boolean"
console.log(typeof declaredButUndefinedVariable);
// expected output: "undefined";
複製代碼
typeof null
結果是 object
,JavaScript
誕生以來便如此,因爲 null
表明的是空指針(大多數平臺下值爲 0x00),所以,null
的類型標籤是 0
,typeof null
也所以返回 "object"
。typeof [1, 2]
結果是 object
,結果中沒有array
這一項,引用類型除了function
其餘的所有都是 object
typeof Symbol()
用 typeof
獲取 symbol
類型的值獲得的是 symbol
,Symbol
實例是惟一且不可改變的這是 ES6 新增的知識點.二、instanceof
用於實例和構造函數的對應。例如判斷一個變量是不是數組,使用 typeof 沒法判斷,但可 以使用 [1, 2] instanceof Array
來判斷,返回true
。由於, [1, 2] 是數組,它的構造函數就是 Array 。同理:
function Car(make, model, year) {
this.make = make;
this.model = model;
this.year = year;
}
var auto = new Car('Honda', 'Accord', 1998);
console.log(auto instanceof Car);
// expected output: true
console.log([1, 2] instanceof Array);
// expected output: true
複製代碼
除了原始類型,JS 還有引用類型,上面提到的 typeof
識別出來的類型中,只有 object
和 function
是引用類型,其餘都是值類型。
根據
JavaScript
中的變量類型傳遞方式,又分爲值類型和引用類型,值類型變量包括Boolean
、String
、Number
、Undefined
、Null
,引用類型包括了Object
類的全部,如Date
、Array
、Function
等。在參數傳遞方式上,值類型是按值傳遞,引用類型是按共享 傳遞。
// 值類型
var a = 1;
var b = a;
b = 3
console.log(a) // 1
console.log(b) // 3
// a b 都是值類型,二者分別修改賦值,相互之間沒有任何影響。
複製代碼
// 引用類型
var a = {x: 10, y: 20}
var b = a
b.x = 100
b.y = 200
console.log(a) // {x: 100, y: 200}
console.log(b) // {x: 100, y: 200}
複製代碼
a 和 b
都是引用類型。在執行了 b = a
以後,修改 b
的屬性值, a
的也跟着 變化。由於 a 和 b
都是引用類型,指向了同一個內存地址,即二者引用的是同一個值,因 此 b 修改屬性時, a 的值隨之改動。
JavaScript
常被描述爲一種基於原型的語言 (prototype-based language)——每一個對象擁有一個原型對象,對象以其原型爲模板、從原型繼承方法和屬性。原型對象也可能擁有原型,並從中繼承方法和屬性,一層一層、以此類推。這種關係常被稱爲原型鏈 (prototype chain),它解釋了爲什麼一個對象會擁有定義在其餘對象中的屬性和方法。
注意
: 理解對象的原型(能夠經過Object.getPrototypeOf(obj)
或者已被棄用的__proto__
屬性得到)與構造函數的prototype
屬性之間的區別是很重要的。前者是每一個實例上都有的屬性,後者是構造函數的屬性。也就是說,Object.getPrototypeOf(new Foobar())
和Foobar.prototype
指向着同一個對象。
在
javascript
中,函數能夠有屬性。 每一個函數都有一個特殊的屬性叫做原型(prototype) ,正以下面所展現的。請注意,下面的代碼是獨立的一段(在網頁中沒有其餘代碼的狀況下,這段代碼是安全的)。爲了最好的學習體驗,你最好打開一個控制檯 (在Chrome
和Firefox
中,能夠按Ctrl+Shift+I
來打開)切換到"Console"
選項卡, 複製粘貼下面的JavaScript
代碼,而後按回車來運行。
function doSomething(){}
console.log( doSomething.prototype );
// 無論您如何聲明函數,javascript中的函數老是有一個默認的原型屬性
var doSomething = function(){};
console.log( doSomething.prototype );
複製代碼
正如上面所看到的, doSomething
函數有一個默認的原型屬性
,它在控制檯上面呈現了出來. 運行這段代碼以後,控制檯上面應該出現了像這樣的一個對象.
{
constructor: ƒ doSomething(),
__proto__: {
constructor: ƒ Object(),
hasOwnProperty: ƒ `hasOwnProperty`(),
isPrototypeOf: ƒ `isPrototypeOf`(),
propertyIsEnumerable: ƒ `propertyIsEnumerable`(),
toLocaleString: ƒ `toLocaleString`(),
toString: ƒ `toString`(),
valueOf: ƒ `valueOf`()
}
}
複製代碼
如今,咱們能夠添加一些屬性到 doSomething 的原型上面,以下所示:
function doSomething(){}
doSomething.prototype.foo = "bar";
console.log( doSomething.prototype );
複製代碼
輸出:
{
foo: "bar",
constructor: ƒ doSomething(),
__proto__: {
constructor: ƒ Object(),
hasOwnProperty: ƒ `hasOwnProperty`(),
isPrototypeOf: ƒ `isPrototypeOf`(),
propertyIsEnumerable: ƒ `propertyIsEnumerable`(),
toLocaleString: ƒ `toLocaleString`(),
toString: ƒ `toString`(),
valueOf: ƒ `valueOf`()
}
}
複製代碼
而後,咱們可使用 new
運算符來在如今的這個原型基礎之上,建立一個 doSomething
的實例。
function doSomething(){}
doSomething.prototype.foo = "bar"; // add a property onto the prototype
var doSomeInstancing = new doSomething();
doSomeInstancing.prop = "some value"; // add a property onto the object
console.log( doSomeInstancing );
複製代碼
輸出:
{
prop: "some value",
__proto__: {
foo: "bar",
constructor: ƒ doSomething(),
__proto__: {
constructor: ƒ Object(),
hasOwnProperty: ƒ `hasOwnProperty`(),
isPrototypeOf: ƒ `isPrototypeOf`(),
propertyIsEnumerable: ƒ `propertyIsEnumerable`(),
toLocaleString: ƒ `toLocaleString`(),
toString: ƒ `toString`(),
valueOf: ƒ `valueOf`()
}
}
}
複製代碼
就像上面看到的, doSomeInstancing
的 __proto__
屬性就是doSomething.prototype
. 可是這又有什麼用呢? 好吧,當你訪問 doSomeInstancing
的一個屬性, 瀏覽器首先查找 doSomeInstancing
是否有這個屬性. 若是 doSomeInstancing
沒有這個屬性, 而後瀏覽器就會在 doSomeInstancing
的 __proto__
中查找這個屬性(也就是 doSomething.prototype
). 若是 doSomeInstancing
的 __proto__
有這個屬性, 那麼 doSomeInstancing
的 __proto__
上的這個屬性就會被使用. 不然, 若是 doSomeInstancing
的 __proto__
沒有這個屬性, 瀏覽器就會去查找 doSomeInstancing
的 __proto__
的 __proto__
,看它是否有這個屬性. 默認狀況下, 全部函數的原型屬性的 __proto__
就是 window.Object.prototype. 因此 doSomeInstancing 的 __proto__
的 __proto__
(也就是 doSomething.prototype 的 __proto__
(也就是 Object.prototype
)) 會被查找是否有這個屬性. 若是沒有在它裏面找到這個屬性, 而後就會在 doSomeInstancing
的 __proto__
的 __proto__
的 __proto__
裏面查找. 然而這有一個問題: doSomeInstancing
的 __proto__
的 __proto__
的 __proto__
不存在. 最後, 原型鏈上面的全部的 __proto__
都被找完了, 瀏覽器全部已經聲明瞭的 __proto__
上都不存在這個屬性,而後就得出結論,這個屬性是 undefined
.
function doSomething(){}
doSomething.prototype.foo = "bar";
var doSomeInstancing = new doSomething();
doSomeInstancing.prop = "some value";
console.log("doSomeInstancing.prop: " + doSomeInstancing.prop);
console.log("doSomeInstancing.foo: " + doSomeInstancing.foo);
console.log("doSomething.prop: " + doSomething.prop);
console.log("doSomething.foo: " + doSomething.foo);
console.log("doSomething.prototype.prop: " + doSomething.prototype.prop);
console.log("doSomething.prototype.foo: " + doSomething.prototype.foo);
複製代碼
輸出:
doSomeInstancing.prop: some value
doSomeInstancing.foo: bar
doSomething.prop: undefined
doSomething.foo: undefined
doSomething.prototype.prop: undefined
doSomething.prototype.foo: bar
複製代碼
是否是看的頭大了,別擔憂。看看這個:
- 全部的引用類型(數組、對象、函數),都具備對象特性,便可自由擴展屬性(
null
除外)- 全部的引用類型(數組、對象、函數),都有一個
__proto__
屬性,屬性值是一個普通的對象- 全部的函數,都有一個
prototype
屬性,屬性值也是一個普通的對象- 全部的引用類型(數組、對象、函數),
__proto__
屬性值指向它的構造函數的prototype
屬性值
// 要點一:自由擴展屬性
var obj = {}; obj.a = 100;
var arr = []; arr.a = 100;
function fn () {}
fn.a = 100;
// 要點二:__proto__
console.log(obj.__proto__);
console.log(arr.__proto__);
console.log(fn.__proto__);
// 要點三:函數有 prototype
console.log(fn.prototype)
// 要點四:引用類型的 __proto__ 屬性值指向它的構造函數的 prototype 屬性值
console.log(obj.__proto__ === Object.prototype)
複製代碼
原型
// 構造函數
function Foo(name, age) {
this.name = name
}
Foo.prototype.alertName = function () {
alert(this.name)
}
// 建立示例
var f = new Foo('zhangsan')
f.printName = function () {
console.log(this.name)
}
// 測試
f.printName()
f.alertName()
複製代碼
執行 printName
時很好理解,可是執行 alertName
時發生了什麼?這裏再記住一個重點 當 試圖獲得一個對象的某個屬性時,若是這個對象自己沒有這個屬性,那麼會去它的 __proto__
(即它的構造函數的 prototype
)中尋找,所以 f.alertName
就會找到 Foo.prototype.alertName
。
那麼如何判斷這個屬性是否是對象自己的屬性呢?使用 hasOwnProperty
,經常使用的地方是遍 歷一個對象的時候。
var item
for (item in f) {
// 高級瀏覽器已經在 for in 中屏蔽了來自原型的屬性,可是這裏建議你們仍是加上這個判斷,保證程序正常輸出
if (f.hasOwnProperty(item)) {
console.log(item)
}
}
複製代碼
原型鏈
仍是接着上面的示例,若是執行 f.toString() 時,又發生了什麼?
f.printName()
複製代碼
由於 f
自己沒有 toString()
,而且 f.__proto__ (即 Foo.prototype )
中也沒有 toString
。這個問題仍是得拿出剛纔那句話——當試圖獲得一個對象的某個屬性時,若是 這個對象自己沒有這個屬性,那麼會去它的 __proto__
(即它的構造函數的 prototype
) 中尋找。
若是在 f.proto 中沒有找到 toString ,那麼就繼續去 f.proto.proto 中尋 找,由於 f.proto 就是一個普通的對象而已嘛!
f.__proto__
即 Foo.prototype
,沒有找到 toString
,繼續往上找f.__proto__.__proto__
即 Foo.prototype.__proto__
。 Foo.prototype
就是一個普通 的對象,所以 Foo.prototype.__proto__
就是 Object.prototype
,在這裏能夠找到toString
f.toString
最終對應到了 Object.prototype.toString
這樣一直往上找,你會發現是一個鏈式的結構,因此叫作「原型鏈」
。若是一直找到最上 層都沒有找到,那麼就宣告失敗,返回 undefined
。最上層是什麼 ——Object.prototype.__proto__ === null
.原型鏈並非無限的,原型鏈最終指向null。
微信請點擊閱讀原文更好的查看。
做用域和閉包是前端面試中,最可能考查的知識點
做用域就是一個獨立的地盤,讓變量不會外泄、暴露出去。
變量的做用域無非就是兩種:全局變量和局部變量
。
全局做用域:
最外層函數定義的變量擁有全局做用域,即對任何內部函數來講,都是能夠訪問的
var outerVar = "outer";
function fn(){
console.log(outerVar);
}
fn(); // result:outer
複製代碼
局部做用域:
和全局做用域相反,局部做用域通常只在固定的代碼片斷內可訪問到,而對於函數外部是沒法訪問的,最多見的例如函數內部
function fn(){
var innerVar = "inner";
}
fn();
console.log(innerVar); // ReferenceError: innerVar is not defined
複製代碼
這就是爲什麼 jQuery、Zepto 等庫的源碼,全部的代碼都會放在 (function(){....})() 中。由於放在裏面的全部變量,都不會被外泄和暴露,不會污染到外面,不會對其餘的庫 或者 JS 腳本形成影響。這是函數做用域的一個體現。
注意:
ES6 中開始加入了塊級做用域,使用 let
定義變量便可,以下:
if (true) {
let name = 'Tom'
}
console.log(name) // 報錯,由於let定義的name是在if這個塊級做用域
複製代碼
做用域鏈 以下代碼中, console.log(a)
要獲得 a
變量,可是在當前的做用域中沒有定義 a
,一層一層向上尋找,直到找到全局做用域仍是沒找到,就宣佈放棄。這種一層一層的關係,就是 做用域鏈。
var a = 5
function fn() {
var b = 10
console.log(a)
console.log(b)
}
fn()
複製代碼
那麼什麼叫閉包?觀點不少,出現頻率最高的有如下兩個觀點:
function F1() {
var a = 100
return function () {
console.log(a)
}
}
var f1 = F1()
var a = 200
f1()
複製代碼
閉包主要有兩個應用場景:
function F1() {
var a = 100
return function () {
console.log(a)
}
}
function F2(f1) {
var a = 200
console.log(f1())
}
var f1 = F1()
F2(f1)
複製代碼
關於this對象
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
alert(object.getNameFunc()()); // result:The Window
複製代碼
this
對象是在運行時基於函數的執行環境綁定的:在全局函數中,this
等於window
,而當函數被做爲某個對象調用時,this
等於那個對象。不過,匿名函數具備全局性,所以this
對象同常指向window
。
先看下面的栗子,根據程序閱讀起來表達的意思,應該是先打印 100 ,1秒鐘以後打印 200 ,最後打印 300 。可是實際運行根本不是那麼回事。
console.log(100)
setTimeout(function () {
console.log(200)
}, 1000)
console.log(300)
複製代碼
再對比如下程序。先打印 100 ,再彈出 200 (等待用戶確認),最後打印 300 。這個運行 效果就符合預期要求。
console.log(100)
alert(200) // 1秒鐘以後點擊確認
console.log(300)
複製代碼
這倆到底有何區別?—— 第一個示例中間的步驟根本沒有阻塞接下來程序的運行,而第二 個示例卻阻塞了後面程序的運行。前面這種表現就叫作 異步
(後面這個叫作 同步
),即 不會阻塞後面程序的運行。
setTimeout(function(){
a = false;
}, 100)
while(a){
console.log('while執行了')
}
複製代碼
由於JS是單線程的,一次只能作一件事情,因此進入while循環以後,沒有「時間」(線程)去跑定時器了,因此這個代碼跑起來是個死循環!
setTimeout
, setInterval
addEventListener
(click
等等)ajax
和 img
動態加載
ES6
容許按照必定模式,從數組和對象中提取值,對變量進行賦值,這被稱爲解構(Destructuring)。
之前,爲變量賦值,只能直接指定值。
let a = 1;
let b = 2;
let c = 3;
複製代碼
ES6 容許寫成下面這樣。
let [a, b, c] = [1, 2, 3];
複製代碼
賦值的代碼大大減小了,不須要分別把變量a,b,c分別聲明定義和賦值,只須要將變量a,b,c做爲一個數組的元素,而後將數組[1,2,3]賦值給數組[a,b,c]便可,變量a,b,c便可分別獲得對應的值。
一、結構賦值能夠嵌套的
let [ a,b,[ c1,c2 ] ] = [ 1,2,[ 3.1,3.2 ] ];
console.log(c1);// c1的值爲3.1
console.log(c2);// c2的值爲3.2
複製代碼
二、不徹底解構
let [a, b, c] = [1, 2];
console.log(a);// a的值爲1
console.log(b);// b的值爲2
複製代碼
3.解構不成功,變量的值就等於undefined
。
let [a,b,c] = [1,2];
console.log(a);// a的值爲1
console.log(b);// b的值爲2
console.log(c);// 結果:c的值爲undefined
複製代碼
4.解構賦值容許指定默認值
let [foo = true] = [];
foo // true
let [x, y = 'b'] = ['a']; // x='a', y='b'
let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'
複製代碼
注意
,ES6 內部使用嚴格相等運算符(===)
,判斷一個位置是否有值。因此,只有當一個數組成員嚴格等於undefined
,默認值纔會生效。
對象的解構賦值
var { a,b,c } = {"a":1,"c":3,"b":2};
console.log(a);//結果:a的值爲1
console.log(b);//結果:b的值爲2
console.log(c);//結果:c的值爲3
複製代碼
字符串的解構賦值
var [a,b,c,d,e,f] = "我是一隻小鳥";
console.log(a);//我
console.log(b);//是
console.log(c);//一
console.log(d);//只
console.log(e);//小
console.log(f);//鳥
複製代碼
解構賦值的用途
1、交換變量的值
var x = 1;
var y = 2;
[x,y] = [y,x];
複製代碼
2、提取函數返回的多個值
function demo(){
return {"name": "張三","age": 21}
}
var {name,age} = demo();
console.log(name);// 結果:張三
console.log(age);// 結果:21
複製代碼
3、定義函數參數
function demo({a,b,c}){
console.log("姓名:"+ a);
console.log("身高:"+ b);
console.log("體重:"+ c);
}
demo({a:"唐三",b:"1.72m",c:"50kg",d:"8000"});
/* 經過這種寫法, 很方便就能提取JSON對象中想要的參數, 例如案例中,咱們只須要獲取實參中的:a,b,c, 而不須要關其餘的參數,好比:d或者其餘更多的參數。*/
複製代碼
4、提取 JSON 數據
let jsonData = {
id: 42,
status: "OK",
data: [867, 5309]
};
let { id, status, data: number } = jsonData;
console.log(id, status, number);
// 42, "OK", [867, 5309]
複製代碼
5、輸入模塊的指定方法
加載模塊時,每每須要指定輸入哪些方法。解構賦值使得輸入語句很是清晰。
const { SourceMapConsumer, SourceNode } = require("source-map");
複製代碼
歷史上,JavaScript 一直沒有模塊(module)體系,沒法將一個大程序拆分紅互相依賴的小文件,再用簡單的方法拼裝起來。其餘語言都有這項功能,好比 Ruby 的require、Python 的import,甚至就連 CSS 都有@import,可是 JavaScript 任何這方面的支持都沒有,這對開發大型的、複雜的項目造成了巨大障礙。
// CommonJS模塊
let { stat, exists, readFile } = require('fs');
// 等同於
let _fs = require('fs');
let stat = _fs.stat;
let exists = _fs.exists;
let readfile = _fs.readfile;
複製代碼
上面代碼的實質是總體加載fs模塊(即加載fs的全部方法),生成一個對象(_fs),而後再從這個對象上面讀取 3 個方法。這種加載稱爲「運行時加載」,由於只有運行時才能獲得這個對象,致使徹底沒辦法在編譯時作「靜態優化」。
導出Export
:做爲一個模塊,它能夠選擇性地給其餘模塊暴露(提供)本身的屬性和方法,供其餘模塊使用。
導入Import
:做爲一個模塊,能夠根據須要,引入其餘模塊的提供的屬性或者方法,供本身模塊使用。
模塊化實現
//---module-B.js文件---
//導出變量:name
export var name = "模塊化";
複製代碼
模塊B咱們使用關鍵字
export
關鍵字,對外暴露了一個屬性:name
的值爲:字符串「模塊化」
。一個關鍵字,一句代碼就實現了,是否是很簡單。
//---module-A.js文件---
//導入 模塊B的屬性 name
import { name } from "./module-B.js";
console.log(name)
//打印結果:模塊化
複製代碼
模塊A咱們使用關鍵字import
導入了模塊B的name
屬性,而且賦值給變量name
。關鍵字from
的做用是指定你想要引入的模塊,咱們這裏指定的是module-B.js
文件,也就是上面的模塊B
。打印結果:「模塊化」正是模塊B
的對外暴露的屬性。
箭頭函數中的this指向的是定義時的this,而不是執行時的this。
//定義一個對象
var obj = {
x:100,//屬性x
show(){
//延遲500毫秒,輸出x的值
setTimeout(
//不一樣處:箭頭函數
() => { console.log(this.x)},
500
);
}
};
obj.show();//打印結果:100
複製代碼
當定義obj的show( )方法的時候,咱們在箭頭函數編寫
this.x
,此時的this
是指的obj
,因此this.x
指的是obj.x
。而在show()
被調用的時候,this依然指向的是被定義時候所指向的對象,也就是obj對象,故打印出:100。
Promise
是異步編程的一種解決方案,比傳統的解決方案——回調函數和事件——更合理和更強大。它由社區最先提出和實現,ES6 將其寫進了語言標準,統一了用法,原生提供了Promise
對象。
ES6 規定,Promise對象是一個構造函數,用來生成Promise實例。
Promise對象有三種狀態:
pending
:剛剛建立一個Promise實例的時候,表示初始狀態;fulfilled
:resolve方法調用的時候,表示操做成功;rejected
:reject方法調用的時候,表示操做失敗;狀態只能從 初始化 -> 成功 或者 初始化 -> 失敗,不能逆向轉換,也不能在成功fulfilled 和失敗rejected之間轉換。
const pro = new Promise(function(resolve, reject) {
// ... some code
if (/* 異步操做成功 */){
resolve(value);
} else {
reject(error);
}
});
複製代碼
瞭解了Promise的建立和狀態,咱們來學習一個最重要的實例方法:then( )
方法。
pro.then(function (res) {
//操做成功的處理程序
},function (error) {
//操做失敗的處理程序
});
// 參數是兩個函數,第一個用於處理操做成功後的業務,第二個用於處理操做異常後的業務。
複製代碼
catch( )方法
pro.catch(function (error) {
//操做失敗的處理程序
});
複製代碼
之因此可以使用鏈式調用,是由於then方法和catch方法調用後,都會返回promise
對象。
若是你以前一點都沒接觸過Promise的話,如今必定很懵逼,不要緊,下面咱們用一個案例來串聯前面的知識點,演示一下,認真閱讀註釋:
//用new關鍵字建立一個Promise實例
let pro = new Promise(function(resolve,reject){
//假設condition的值爲true
let condition = true;
if(condition){
//調用操做成功方法
resolve('操做成功');
//狀態:pending->fulfilled
}else{
//調用操做異常方法
reject('操做異常');
//狀態:pending->rejected
}
});
//用then處理操做成功,catch處理操做異常
pro.then(function (res) {
//操做成功的處理程序
console.log(res)
}).catch(function (error) {
//操做失敗的處理程序
console.log(error)
});
//控制檯輸出:操做成功
複製代碼
上面案例的註釋十分詳細,串聯起了上面介紹的全部知識點:建立實例,狀態轉換,then方法和catch方法的使用。
因爲咱們設置了變量condition的值爲true,因此執行後控制檯輸出的結果是:「操做成功」。
上面就是Promise用於處理操做異常的這個過程;可是,正如文章開頭講到的,若是多個操做之間層層依賴,咱們用Promise又是怎麼處理的呢?
let pro = new Promise(function(resolve,reject){
if(true){
//調用操做成功方法
resolve('操做成功');
}else{
//調用操做異常方法
reject('操做異常');
}
});
//用then處理操做成功,catch處理操做異常
pro.then(requestA)
.then(requestB)
.then(requestC)
.catch(requestError);
function requestA(){
console.log('請求A成功');
return '請求B,下一個就是你了';
}
function requestB(res){
console.log('上一步的結果:'+res);
console.log('請求B成功');
return '請求C,下一個就是你了';
}
function requestC(res){
console.log('上一步的結果:'+res);
console.log('請求C成功');
}
function requestError(){
console.log('請求失敗');
}
//打印結果:
//請求A成功
//上一步的結果:請求B,下一個就是你了
//請求B成功
//上一步的結果:請求C,下一個就是你了
//請求C成功
複製代碼
案例中,先是建立一個實例,還聲明瞭4個函數,其中三個是分別表明着請求A,請求B,請求C;有了then
方法,三個請求操做不再用層層嵌套了。咱們使用then
方法,按照調用順序,很直觀地完成了三個操做的綁定,而且,若是請求B依賴於請求A的結果,那麼,能夠在請求A的程序用使用return
語句把須要的數據做爲參數,傳遞給下一個請求,案例中咱們就是使用return
實現傳遞參數給下一步操做的。
更直觀的圖解
Promise.all( )方法
Promise.all( )
方法:接受一個數組做爲參數,數組的元素是Promise
實例對象,當參數中的實例對象的狀態都爲fulfilled
時,Promise.all( )
纔會有返回。
//建立實例pro1
let pro1 = new Promise(function(resolve){
setTimeout(function () {
resolve('實例1操做成功');
},5000);
});
//建立實例pro2
let pro2 = new Promise(function(resolve){
setTimeout(function () {
resolve('實例2操做成功');
},1000);
});
Promise.all([pro1,pro2]).then(function(result){
console.log(result);
});
//打印結果:["實例1操做成功", "實例2操做成功"]
複製代碼
Promise.race( )方法
另外一個相似的方法是Promise.race()
方法:它的參數要求跟Promise.all( )
方法同樣,不一樣的是,它參數中的promise實例,只要有一個狀態發生變化(無論是成功fulfilled
仍是異常rejected
),它就會有返回,其餘實例中再發生變化,它也無論了。
//初始化實例pro1
let pro1 = new Promise(function(resolve){
setTimeout(function () {
resolve('實例1操做成功');
},4000);
});
//初始化實例pro2
let pro2 = new Promise(function(resolve,reject){
setTimeout(function () {
reject('實例2操做失敗');
},2000);
});
Promise.race([pro2,pro1]).then(function(result){
console.log(result);
}).catch(function(error){
console.log(error);
});
//打印結果:實例2操做失敗
複製代碼
一樣是兩個實例,實例pro1不變,不一樣的是實例pro2,此次咱們調用的是失敗函數reject。
因爲pro2實例中2000毫秒以後就執行reject方法,早於實例pro1的4000毫秒,因此最後輸出的是:實例2操做失敗。
以上就是對Promise對象的內容講解,上面提到了一個概念:回調地獄;指的是過多地使用回調函數嵌套,使得調試和維護起來極其的不便。
參考:mdn