溫習JS及相關常用的ES6新語法(二)
generator
generator(生成器)是ES6標準引入的新的資料型別。一個generator看上去像一個函式,但可以返回多次。
函式在執行過程中,如果沒有遇到return語句(函式末尾如果沒有return,就是隱含的return undefined;),控制權無法交回被呼叫的程式碼。
generator跟函式很像,定義如下:
function* foo(x) {
yield x + 1;
yield x + 2;
return x + 3;
}
generator和函式不同的是,generator由function*定義(注意多出的*號),並且,除了return語句,還可以用yield返回多次。
generator就是能夠返回多次的“函式”?返回多次有啥用?
還是舉個栗子吧。
我們以一個著名的斐波那契數列為例,它由0,1開頭(這個數列從第3項開始,每一項都等於前兩項之和):
0 1 1 2 3 5 8 13 21 34 ...
要編寫一個產生斐波那契數列的函式,可以這麼寫:
function fib(max) {
var
t,
a = 0,
b = 1,
arr = [0, 1];
while (arr.length < max) {
[a, b] = [b, a + b]; //解構賦值
arr.push(b);
}
return arr;
}
// 測試:
fib(5); // [0, 1, 1, 2, 3]
fib(10); // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
函式只能返回一次,所以必須返回一個Array。但是,如果換成generator,就可以一次返回一個數,不斷返回多次。用generator改寫如下:
function* fib(max) {
var
t,
a = 0,
b = 1,
n = 0;
while (n < max) {
yield a;
[a, b] = [b, a + b];
n ++;
}
return;
}
直接呼叫試試:
fib(5); // fib {[[GeneratorStatus]]: "suspended", [[GeneratorReceiver]]: Window}
直接呼叫一個generator和呼叫函式不一樣,fib(5)僅僅是建立了一個generator物件,還沒有去執行它。
呼叫generator物件有兩個方法,一是不斷地呼叫generator物件的next()方法:
var f = fib(5);
f.next(); // {value: 0, done: false}
f.next(); // {value: 1, done: false}
f.next(); // {value: 1, done: false}
f.next(); // {value: 2, done: false}
f.next(); // {value: 3, done: false}
f.next(); // {value: undefined, done: true}
next()方法會執行generator的程式碼,然後,每次遇到yield x;就返回一個物件{value: x, done: true/false},然後“暫停”。返回的value就是yield的返回值,done表示這個generator是否已經執行結束了。如果done為true,則value就是return的返回值。
當執行到done為true時,這個generator物件就已經全部執行完畢,不要再繼續呼叫next()了。
第二個方法是直接用for … of迴圈迭代generator物件,這種方式不需要我們自己判斷done:
'use strict'
function* fib(max) {
var
t,
a = 0,
b = 1,
n = 0;
while (n < max) {
yield a;
[a, b] = [b, a + b];
n ++;
}
return;
}
for (var x of fib(10)) {
console.log(x); // 依次輸出0, 1, 1, 2, 3, ...
}
generator和普通函式相比,有什麼用?
因為generator可以在執行過程中多次返回,所以它看上去就像一個可以記住執行狀態的函式,利用這一點,寫一個generator就可以實現需要用面向物件才能實現的功能。例如,用一個物件來儲存狀態,得這麼寫:
var fib = {
a: 0,
b: 1,
n: 0,
max: 5,
next: function () {
var
r = this.a,
t = this.a + this.b;
this.a = this.b;
this.b = t;
if (this.n < this.max) {
this.n ++;
return r;
} else {
return undefined;
}
}
};
用物件的屬性來儲存狀態,相當繁瑣。
generator還有另一個巨大的好處,就是把非同步回撥程式碼變成“同步”程式碼。這個好處要等到後面學了AJAX以後才能體會到。
沒有generator之前的黑暗時代,用AJAX時需要這麼寫程式碼:
ajax('http://url-1', data1, function (err, result) {
if (err) {
return handle(err);
}
ajax('http://url-2', data2, function (err, result) {
if (err) {
return handle(err);
}
ajax('http://url-3', data3, function (err, result) {
if (err) {
return handle(err);
}
return success(result);
});
});
});
回撥越多,程式碼越難看。
有了generator的美好時代,用AJAX時可以這麼寫:
try {
r1 = yield ajax('http://url-1', data1);
r2 = yield ajax('http://url-2', data2);
r3 = yield ajax('http://url-3', data3);
success(r3);
}
catch (err) {
handle(err);
}
看上去是同步的程式碼,實際執行是非同步的。
例子:
要生成一個自增的ID,可以編寫一個next_id()函式:
var current_id = 0;
function next_id() {
current_id ++;
return current_id;
}
由於函式無法儲存狀態,故需要一個全域性變數current_id來儲存數字。
閉包的方式:
function xxa() {
var a=0;
return function(){
a++;
console.log(a);
};
}
var fc=xxa();
fc(); //1
fc(); //2
fc(); //3
fc(); //4
不用閉包,試用generator改寫:
function* next_id() {
var a = 1;
while(1) yield a++;
}
標準物件
在JavaScript的世界裡,一切都是物件。
但是某些物件還是和其他物件不太一樣。為了區分物件的型別,我們用typeof操作符獲取物件的型別,它總是返回一個字串:
typeof 123; // 'number'
typeof NaN; // 'number'
typeof 'str'; // 'string'
typeof true; // 'boolean'
typeof undefined; // 'undefined'
typeof Math.abs; // 'function'
typeof null; // 'object'
typeof []; // 'object'
typeof {}; // 'object'
可見,number、string、boolean、function和undefined有別於其他型別。特別注意null的型別是object,Array的型別也是object,如果我們用typeof將無法區分出null、Array和通常意義上的object——{}。
包裝物件
除了這些型別外,JavaScript還提供了包裝物件,熟悉Java的小夥伴肯定很清楚int和Integer這種曖昧關係。
number、boolean和string都有包裝物件。沒錯,在JavaScript中,字串也區分string型別和它的包裝型別。包裝物件用new建立
雖然包裝物件看上去和原來的值一模一樣,顯示出來也是一模一樣,但他們的型別已經變為object了!所以,包裝物件和原始值用===比較會返回false:
typeof new Number(123); // 'object'
new Number(123) === 123; // false
typeof new Boolean(true); // 'object'
new Boolean(true) === true; // false
typeof new String('str'); // 'object'
new String('str') === 'str'; // false
所以閒的蛋疼也不要使用包裝物件!尤其是針對string型別!!!
如果我們在使用Number、Boolean和String時,沒有寫new會發生什麼情況?
此時,Number()、Boolean和String()被當做普通函式,把任何型別的資料轉換為number、boolean和string型別(注意不是其包裝型別):
var n = Number('123'); // 123,相當於parseInt()或parseFloat()
typeof n; // 'number'
var b = Boolean('true'); // true
typeof b; // 'boolean'
var b2 = Boolean('false'); // true! 'false'字串轉換結果為true!因為它是非空字串!
var b3 = Boolean(''); // false
var s = String(123.45); // '123.45'
typeof s; // 'string'
總結一下,有這麼幾條規則需要遵守:
- 不要使用new Number()、new Boolean()、new String()建立包裝物件;
- 用parseInt()或parseFloat()來轉換任意型別到number;
- 用String()來轉換任意型別到string,或者直接呼叫某個物件的toString()方法;
- 通常不必把任意型別轉換為boolean再判斷,因為可以直接寫if (myVar) {…};
- typeof操作符可以判斷出number、boolean、string、function和undefined;
- 判斷Array要使用Array.isArray(arr);
- 判斷null請使用myVar === null;
- 判斷某個全域性變數是否存在用typeof window.myVar === ‘undefined’;
- 函式內部判斷某個變數是否存在用typeof myVar === ‘undefined’。
Date
在JavaScript中,Date物件用來表示日期和時間。
要獲取系統當前時間,用:
var now = new Date();
now; // Wed Jun 24 2015 19:49:22 GMT+0800 (CST)
now.getFullYear(); // 2015, 年份
now.getMonth(); // 5, 月份,注意月份範圍是0~11,5表示六月
now.getDate(); // 24, 表示24號
now.getDay(); // 3, 表示星期三
now.getHours(); // 19, 24小時制
now.getMinutes(); // 49, 分鐘
now.getSeconds(); // 22, 秒
now.getMilliseconds(); // 875, 毫秒數
now.getTime(); // 1435146562875, 以number形式表示的時間戳
時區
Date物件表示的時間總是按瀏覽器所在時區顯示的,不過我們既可以顯示本地時間,也可以顯示調整後的UTC時間:
var d = new Date(1435146562875);
d.toLocaleString(); // '2015/6/24 下午7:49:22',本地時間(北京時區+8:00),顯示的字串與作業系統設定的格式有關
d.toUTCString(); // 'Wed, 24 Jun 2015 11:49:22 GMT',UTC時間,與本地時間相差8小時
那麼在JavaScript中如何進行時區轉換呢?實際上,只要我們傳遞的是一個number型別的時間戳,我們就不用關心時區轉換。任何瀏覽器都可以把一個時間戳正確轉換為本地時間。
時間戳是個什麼東西?時間戳是一個自增的整數,它表示從1970年1月1日零時整的GMT時區開始的那一刻,到現在的毫秒數。假設瀏覽器所在電腦的時間是準確的,那麼世界上無論哪個時區的電腦,它們此刻產生的時間戳數字都是一樣的,所以,時間戳可以精確地表示一個時刻,並且與時區無關。
面向物件程式設計
JavaScript的面向物件程式設計和大多數其他語言如Java、C#的面向物件程式設計都不太一樣。如果你熟悉Java或C#,很好,你一定明白麵向物件的兩個基本概念:
類:類是物件的型別模板,例如,定義Student類來表示學生,類本身是一種型別,Student表示學生型別,但不表示任何具體的某個學生;
例項:例項是根據類建立的物件,例如,根據Student類可以創建出xiaoming、xiaohong、xiaojun等多個例項,每個例項表示一個具體的學生,他們全都屬於Student型別。
所以,類和例項是大多數面向物件程式語言的基本概念。
不過,在JavaScript中,這個概念需要改一改。JavaScript不區分類和例項的概念,而是通過原型(prototype)來實現面向物件程式設計。
原型是指當我們想要建立xiaoming這個具體的學生時,我們並沒有一個Student型別可用。那怎麼辦?恰好有這麼一個現成的物件:
var robot = {
name: 'Robot',
height: 1.6,
run: function () {
console.log(this.name + ' is running...');
}
};
我們看這個robot物件有名字,有身高,還會跑,有點像小明,乾脆就根據它來“建立”小明得了!
於是我們把它改名為Student,然後創建出xiaoming:
var Student = {
name: 'Robot',
height: 1.2,
run: function () {
console.log(this.name + ' is running...');
}
};
var xiaoming = {
name: '小明'
};
xiaoming.__proto__ = Student;
注意最後一行程式碼把xiaoming的原型指向了物件Student,看上去xiaoming彷彿是從Student繼承下來的:
xiaoming.name; // '小明'
xiaoming.run(); // 小明 is running...
xiaoming有自己的name屬性,但並沒有定義run()方法。不過,由於小明是從Student繼承而來,只要Student有run()方法,xiaoming也可以呼叫:
JavaScript的原型鏈和Java的Class區別就在,它沒有“Class”的概念,所有物件都是例項,所謂繼承關係不過是把一個物件的原型指向另一個物件而已。
如果你把xiaoming的原型指向其他物件:
var Bird = {
fly: function () {
console.log(this.name + ' is flying...');
}
};
xiaoming.__proto__ = Bird;
現在xiaoming已經無法run()了,他已經變成了一隻鳥:
xiaoming.fly(); // 小明 is flying...
在JavaScrip程式碼執行時期,你可以把xiaoming從Student變成Bird,或者變成任何物件。
請注意,上述程式碼僅用於演示目的。在編寫JavaScript程式碼時,不要直接用obj.__proto__
去改變一個物件的原型,並且,低版本的IE也無法使用__proto__
。Object.create()方法可以傳入一個原型物件,並建立一個基於該原型的新物件,但是新物件什麼屬性都沒有,因此,我們可以編寫一個函式來建立xiaoming:
// 原型物件:
var Student = {
name: 'Robot',
height: 1.2,
run: function () {
console.log(this.name + ' is running...');
}
};
function createStudent(name) {
// 基於Student原型建立一個新物件:
var s = Object.create(Student);
// 初始化新物件:
s.name = name;
return s;
}
var xiaoming = createStudent('小明');
xiaoming.run(); // 小明 is running...
xiaoming.__proto__ === Student; // true
這是建立原型繼承的一種方法,JavaScript還有其他方法來建立物件
建立物件
JavaScript對每個建立的物件都會設定一個原型,指向它的原型物件。
當我們用obj.xxx訪問一個物件的屬性時,JavaScript引擎先在當前物件上查詢該屬性,如果沒有找到,就到其原型物件上找,如果還沒有找到,就一直上溯到Object.prototype物件,最後,如果還沒有找到,就只能返回undefined。
例如,建立一個Array物件:
var arr = [1, 2, 3];
其原型鏈是:
arr ----> Array.prototype ----> Object.prototype ----> null
函式也是一個物件,它的原型鏈是:
foo ----> Function.prototype ----> Object.prototype ----> null
很容易想到,如果原型鏈很長,那麼訪問一個物件的屬性就會因為花更多的時間查詢而變得更慢,因此要注意不要把原型鏈搞得太長。
建構函式
除了直接用{ … }建立一個物件外,JavaScript**還可以用一種建構函式的方法來建立物件**。它的用法是,先定義一個建構函式:
function Student(name) {
this.name = name;
this.hello = function () {
alert('Hello, ' + this.name + '!');
}
}
這是一個普通函式,但是在JavaScript中,可以用關鍵字new來呼叫這個函式,並返回一個物件:
var xiaoming = new Student('小明');
xiaoming.name; // '小明'
xiaoming.hello(); // Hello, 小明!
注意,如果不寫new,這就是一個普通函式,它返回undefined。但是,如果寫了new,它就變成了一個建構函式,它繫結的this指向新建立的物件,並預設返回this,也就是說,不需要在最後寫return this;。
新建立的xiaoming的原型鏈是:
xiaoming ----> Student.prototype ----> Object.prototype ----> null
用new Student()建立的物件還從原型上獲得了一個constructor屬性,它指向函式Student本身:
xiaoming.constructor === Student.prototype.constructor; // true
Student.prototype.constructor === Student; // true
Object.getPrototypeOf(xiaoming) === Student.prototype; // true
xiaoming instanceof Student; // true
用一張圖來表示這些亂七八糟的關係就是:
紅色箭頭是原型鏈。注意,Student.prototype指向的物件就是xiaoming、xiaohong的原型物件,這個原型物件自己還有個屬性constructor,指向Student函式本身。但是xiaoming、xiaohong這些物件可沒有prototype這個屬性,不過可以用__proto__
這個非標準用法來檢視。
現在我們就認為xiaoming、xiaohong這些物件“繼承”自Student
不過還有一個小問題:
xiaoming和xiaohong各自的name不同,這是對的,否則我們無法區分誰是誰了。
xiaoming和xiaohong各自的hello是一個函式,但它們是兩個不同的函式,雖然函式名稱和程式碼都是相同的!
如果我們通過new Student()建立了很多物件,這些物件的hello函式實際上只需要共享同一個函式就可以了,這樣可以節省很多記憶體。
要讓建立的物件共享一個hello函式,根據物件的屬性查詢原則,我們只要把hello函式移動到xiaoming、xiaohong這些物件共同的原型上就可以了,也就是Student.prototype:
修改程式碼如下:
function Student(name) {
this.name = name;
}
Student.prototype.hello = function () {
alert('Hello, ' + this.name + '!');
};
用new建立基於原型的JavaScript的物件就是這麼簡單!
為了區分普通函式和建構函式,按照約定,建構函式首字母應當大寫,而普通函式首字母應當小寫,這樣,一些語法檢查工具如jslint將可以幫你檢測到漏寫的new。
最後,我們還可以編寫一個createStudent()函式,在內部封裝所有的new操作。一個常用的程式設計模式像這樣:
function Student(props) {
this.name = props.name || '匿名'; // 預設值為'匿名'
this.grade = props.grade || 1; // 預設值為1
}
Student.prototype.hello = function () {
alert('Hello, ' + this.name + '!');
};
function createStudent(props) {
return new Student(props || {})
}
這個createStudent()函式有幾個巨大的優點:一是不需要new來呼叫,二是引數非常靈活,可以不傳,也可以這麼傳:
var xiaoming = createStudent({
name: '小明'
});
xiaoming.grade; // 1
如果建立的物件有很多屬性,我們只需要傳遞需要的某些屬性,剩下的屬性可以用預設值。由於引數是一個Object,我們無需記憶引數的順序。如果恰好從JSON拿到了一個物件,就可以直接創建出xiaoming。
原型繼承
在傳統的基於Class的語言如Java、C++中,繼承的本質是擴充套件一個已有的Class,並生成新的Subclass。
由於這類語言嚴格區分類和例項,繼承實際上是型別的擴充套件。但是,JavaScript由於採用原型繼承,我們無法直接擴充套件一個Class,因為根本不存在Class這種型別。
但是辦法還是有的
現在,我們要基於之前的Student擴展出PrimaryStudent,可以先定義出PrimaryStudent:
function PrimaryStudent(props) {
// 呼叫Student建構函式,繫結this變數:
Student.call(this, props);
this.grade = props.grade || 1;
}
但是,呼叫了Student建構函式不等於繼承了Student,PrimaryStudent建立的物件的原型是:
new PrimaryStudent() ----> PrimaryStudent.prototype ----> Object.prototype ----> null
必須想辦法把原型鏈修改為:
new PrimaryStudent() ----> PrimaryStudent.prototype ----> Student.prototype ----> Object.prototype ----> null
這樣,原型鏈對了,繼承關係就對了。新的基於PrimaryStudent建立的物件不但能呼叫PrimaryStudent.prototype定義的方法,也可以呼叫Student.prototype定義的方法。
但是:
PrimaryStudent.prototype = Student.prototype;
是不行的!如果這樣的話,PrimaryStudent和Student共享一個原型物件,那還要定義PrimaryStudent幹啥?
我們必須藉助一箇中間物件來實現正確的原型鏈,這個中間物件的原型要指向Student.prototype。為了實現這一點,參考道爺(就是發明JSON的那個道格拉斯)的程式碼,中間物件可以用一個空函式F來實現:
// PrimaryStudent建構函式:
function PrimaryStudent(props) {
Student.call(this, props);
this.grade = props.grade || 1;
}
// 空函式F:
function F() {
}
// 把F的原型指向Student.prototype:
F.prototype = Student.prototype;
// 把PrimaryStudent的原型指向一個新的F物件,F物件的原型正好指向Student.prototype:
PrimaryStudent.prototype = new F();
// 把PrimaryStudent原型的建構函式修復為PrimaryStudent:
PrimaryStudent.prototype.constructor = PrimaryStudent;
// 繼續在PrimaryStudent原型(就是new F()物件)上定義方法:
PrimaryStudent.prototype.getGrade = function () {
return this.grade;
};
// 建立xiaoming:
var xiaoming = new PrimaryStudent({
name: '小明',
grade: 2
});
xiaoming.name; // '小明'
xiaoming.grade; // 2
// 驗證原型:
xiaoming.__proto__ === PrimaryStudent.prototype; // true
xiaoming.__proto__.__proto__ === Student.prototype; // true
// 驗證繼承關係:
xiaoming instanceof PrimaryStudent; // true
xiaoming instanceof Student; // true
用一張圖來表示新的原型鏈:
注意,函式F僅用於橋接,我們僅建立了一個new F()例項,而且,沒有改變原有的Student定義的原型鏈。
如果把繼承這個動作用一個inherits()函式封裝起來,還可以隱藏F的定義,並簡化程式碼:
function inherits(Child, Parent) {
var F = function () {};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
}
這個inherits()函式可以複用:
function Student(props) {
this.name = props.name || 'Unnamed';
}
Student.prototype.hello = function () {
alert('Hello, ' + this.name + '!');
}
function PrimaryStudent(props) {
Student.call(this, props);
this.grade = props.grade || 1;
}
// 實現原型繼承鏈:
inherits(PrimaryStudent, Student);
// 繫結其他方法到PrimaryStudent原型:
PrimaryStudent.prototype.getGrade = function () {
return this.grade;
};
JavaScript的原型繼承實現方式就是:
- 定義新的建構函式,並在內部用call()呼叫希望“繼承”的建構函式,並繫結this;
- 藉助中間函式F實現原型鏈繼承,最好通過封裝的inherits函式完成;
- 繼續在新的建構函式的原型上定義新方法。
class繼承
在上面我們看到了JavaScript的物件模型是基於原型實現的,特點是簡單,缺點是理解起來比傳統的類-例項模型要困難,最大的缺點是繼承的實現需要編寫大量程式碼,並且需要正確實現原型鏈。
有沒有更簡單的寫法?有!
新的關鍵字class從ES6開始正式被引入到JavaScript中。class的目的就是讓定義類更簡單。
我們先回顧用函式實現Student的方法:
function Student(name) {
this.name = name;
}
Student.prototype.hello = function () {
alert('Hello, ' + this.name + '!');
}
如果用新的class關鍵字來編寫Student,可以這樣寫:
class Student {
constructor(name) {
this.name = name;
}
hello() {
alert('Hello, ' + this.name + '!');
}
}
比較一下就可以發現,class的定義包含了建構函式constructor和定義在原型物件上的函式hello()(注意沒有function關鍵字),這樣就避免了Student.prototype.hello = function () {…}這樣分散的程式碼。
最後,建立一個Student物件程式碼和前面完全一樣:
var xiaoming = new Student('小明');
xiaoming.hello();
用class定義物件的另一個巨大的好處是繼承更方便了。想一想我們從Student派生一個PrimaryStudent需要編寫的程式碼量。現在,原型繼承的中間物件,原型物件的建構函式等等都不需要考慮了,直接通過extends來實現:
class PrimaryStudent extends Student {
constructor(name, grade) {
super(name); // 記得用super呼叫父類的構造方法!
this.grade = grade;
}
myGrade() {
alert('I am at grade ' + this.grade);
}
}
注意PrimaryStudent的定義也是class關鍵字實現的,而extends則表示原型鏈物件來自Student。子類的建構函式可能會與父類不太相同,例如,PrimaryStudent需要name和grade兩個引數,並且需要通過super(name)來呼叫父類的建構函式,否則父類的name屬性無法正常初始化。
PrimaryStudent已經自動獲得了父類Student的hello方法,我們又在子類中定義了新的myGrade方法。
ES6引入的class和原有的JavaScript原型繼承有什麼區別呢?實際上它們沒有任何區別,class的作用就是讓JavaScript引擎去實現原來需要我們自己編寫的原型鏈程式碼。簡而言之,用class的好處就是極大地簡化了原型鏈程式碼。
你一定會問,class這麼好用,能不能現在就用上?
現在用還早了點,因為不是所有的主流瀏覽器都支援ES6的class。如果一定要現在就用上,就需要一個工具把class程式碼轉換為傳統的prototype程式碼,可以試試Babel這個工具。
promise
在JavaScript的世界中,所有程式碼都是單執行緒執行的。
由於這個“缺陷”,導致JavaScript的所有網路操作,瀏覽器事件,都必須是非同步執行。非同步執行可以用回撥函式實現:
function callback() {
console.log('Done');
}
console.log('before setTimeout()');
setTimeout(callback, 1000); // 1秒鐘後呼叫callback函式
console.log('after setTimeout()');
觀察上述程式碼執行,在Chrome的控制檯輸出可以看到:
before setTimeout()
after setTimeout()
(等待1秒後)
Done
可見,非同步操作會在將來的某個時間點觸發一個函式呼叫。
AJAX就是典型的非同步操作。比如:
把回撥函式success(request.responseText)和fail(request.status)寫到一個AJAX操作裡很正常,但是不好看,而且不利於程式碼複用。
有沒有更好的寫法?比如寫成這樣:
var ajax = ajaxGet('http://...');
ajax.ifSuccess(success)
.ifFail(fail);
這種鏈式寫法的好處在於,先統一執行AJAX邏輯,不關心如何處理結果,然後,根據結果是成功還是失敗,在將來的某個時候呼叫success函式或fail函式。
這種“承諾將來會執行”的物件在JavaScript中稱為Promise物件。
Promise有各種開源實現,在ES6中被統一規範,由瀏覽器直接支援。
我們先看一個最簡單的Promise例子:生成一個0-2之間的隨機數,如果小於1,則等待一段時間後返回成功,否則返回失敗:
function test(resolve, reject) {
var timeOut = Math.random() * 2;
log('set timeout to: ' + timeOut + ' seconds.');
setTimeout(function () {
if (timeOut < 1) {
log('call resolve()...');
resolve('200 OK');
}
else {
log('call reject()...');
reject('timeout in ' + timeOut + ' seconds.');
}
}, timeOut * 1000);
}
這個test()函式有兩個引數,這兩個引數都是函式,如果執行成功,我們將呼叫resolve(‘200 OK’),如果執行失敗,我們將呼叫reject(‘timeout in ’ + timeOut + ’ seconds.’)。可以看出,test()函式只關心自身的邏輯,並不關心具體的resolve和reject將如何處理結果。
有了執行函式,我們就可以用一個Promise物件來執行它,並在將來某個時刻獲得成功或失敗的結果:
var p1 = new Promise(test);
var p2 = p1.then(function (result) {
console.log('成功:' + result);
});
var p3 = p2.catch(function (reason) {
console.log('失敗:' + reason);
});
變數p1是一個Promise物件,它負責執行test函式。由於test函式在內部是非同步執行的,當test函式執行成功時,我們告訴Promise物件:
// 如果成功,執行這個函式:
p1.then(function (result) {
console.log('成功:' + result);
});
當test函式執行失敗時,我們告訴Promise物件:
p2.catch(function (reason) {
console.log('失敗:' + reason);
});
Promise物件可以串聯起來,所以上述程式碼可以簡化為:
new Promise(test).then(function (result) {
console.log('成功:' + result);
}).catch(function (reason) {
console.log('失敗:' + reason);
});
實際測試一下,看看Promise是如何非同步執行的:
new Promise(function (resolve, reject) {
log('start new Promise...');
var timeOut = Math.random() * 2;
log('set timeout to: ' + timeOut + ' seconds.');
setTimeout(function () {
if (timeOut < 1) {
log('call resolve()...');
resolve('200 OK');
}
else {
log('call reject()...');
reject('timeout in ' + timeOut + ' seconds.');
}
}, timeOut * 1000);
}).then(function (r) {
log('Done: ' + r);
}).catch(function (reason) {
log('Failed: ' + reason);
});
可見Promise最大的好處是在非同步執行的流程中,把執行程式碼和處理結果的程式碼清晰地分離了:
Promise還可以做更多的事情,比如,有若干個非同步任務,需要先做任務1,如果成功後再做任務2,任何任務失敗則不再繼續並執行錯誤處理函式。
要序列執行這樣的非同步任務,不用Promise需要寫一層一層的巢狀程式碼。有了Promise,我們只需要簡單地寫:
job1.then(job2).then(job3).catch(handleError);
其中,job1、job2和job3都是Promise物件。
相關推薦
溫習JS及相關常用的ES6新語法(二)
generator generator(生成器)是ES6標準引入的新的資料型別。一個generator看上去像一個函式,但可以返回多次。 函式在執行過程中,如果沒有遇到return語句(函式末尾如果沒有return,就是隱含的return undefined
溫習JS及相關常用的ES6新語法(一)
Map和Set JavaScript的預設物件表示方式{}可以視為其他語言中的Map或Dictionary的資料結構,即一組鍵值對。 但是JavaScript的物件有個小問題,就是鍵必須是字串。但實際上Number或者其他資料型別作為鍵也是非常合理的。 為
ES6常用的新語法學習筆記
簡介 ES6是 JavaScript 語言的下一代標準,已經在 2015 年 6 月正式釋出了。它的目標,是使得 JavaScript 語言可以用來編寫複雜的大型應用程式,成為企業級開發語言。 1.let和const let let和var 差不多,都是用來宣告變數,但是let的宣告
總結常見的ES6新語法特性。
等號 模塊 不能 文件 import 處理程序 定義 ogl 進行 前言 ES6是即將到來的新版本JavaScript語言的標準,他給我們帶來了更“甜”的語法糖(一種語法,使得語言更容易理解和更具有可讀性,也讓我們編寫代碼更加簡單快捷),如箭頭函數(=>)、class
ES6新語法之---對象字面量擴展、模板字符串(5)
ons 可靠的 小數 為我 寫法 define 當前 BE dde 這節課學習ES6中對象字面量擴展和新增模板字符串 第一部分:對象字面量擴展 1.簡潔寫法 ES6對於對象字面量屬性提供了簡寫方式。 1.1:屬性簡寫 //傳統寫法
ES6新語法概覽
Freelance last num cep sse 恢復 top book get 簡介 ES6是JavaScript語言的新一代標準,加入了一些新的功能和語法,正式發布於2015年6月,亦稱ES2015;該標準由ECMA(歐洲計算機制造聯合會)的第39號技術專家委員會(
javascript es6新語法
es6 一些新的語法與用法 1: let新的方式定義變數 相比於之前的var宣告變數 有兩個好處 1.1 : 之前用var 定義變數 只有函式才會起到作用域的左右 在{ } 物件這樣的作用域裡面不起左右 而用let則會在{ } 裡面也會開啟一個作用域 1.2 : 沒有變數的提升 2:
javascript es6新語法
es6 一些新的語法與用法 1: let新的方式定義變數 相比於之前的var宣告變數 有兩個好處 1.1 : 之前用var 定義變數 只有函式才會起到作用域的左右 在{ } 物件這樣的作用域裡面不起左右 而用let則會在{ } 裡面也會開啟一個作用域 1.2
Es6新語法
目錄 1.變數宣告 1.1 let 宣告的變數是區域性變數,只在範圍內有效 1.2 const 修飾的變數不能自增 2. 解構表示式 2.1 陣列 &nb
Java中String,StringBuffer,StringBuilder的區別及相關常用函式
今天在刷演算法題的時候,題目上傳入的引數型別是StringBuffer,而需要返回的卻是String型別。其中我在寫的時候用了兩種不同的寫法產生的結果相同,但是速度卻不同。所以在網上查找了一些資料。下面做一個String,StringBuffer,StringBuilder的區別及常用函式介
【ES6新語法】--import()
import()方法是用來代替require,實現動態載入;例項: 要使用import()+angular路由實現動態載入, 構建工具: webpack 1、要使用import() 需要使用babel進行轉換,依賴babel-loaderbabel-corebabel-
CURL模擬表單post提交及相關常用引數的使用(包括提交表單同時上傳檔案)
<form action="doLogin" method="post"> <input type="text" name="username" value="admin"/> <input type="password" nam
03-es6 新語法 Promise 的了解和基本使用
res spa The div json $.ajax con err 參數 // Promise // 1.說明 : Promise 是 es6 中提出的新語法 // 2.作用 : 用 ‘用戶編寫同步代碼‘ 的方式 ‘處理異步‘ 的一種解決方案 // 3.
從零開始學 Web 之 ES6(四)ES6基礎語法二
一、Promise Promise是一個物件,代表了未來某個將要發生的事件(,這個事件通常是一個非同步操作) 有了Promise物件, 可以將非同步操作以同步的流程表達出來, 避免了層層巢狀的回撥函式(俗稱'回撥地獄')。 ES6的Promise是一個建構函式, 用來生成promise例項。 1、prom
python序列之元組的概念及相關函式的總結(二)
接著上篇沒寫完的,上篇沒看的建議從上篇開始看,是按順序寫的. 這裡引入一道題,方便大家理解;Write a Python program to convert a tuple to a string.(用python將元組轉化成字串) 程式碼: tup = ('e',
熟悉JS中的常用選擇器及屬性、方法的調用
model bower 文本 font [1] 選擇 val yellow width 選擇器、屬性及方法調用的配合使用: <style> #a{ width: 200px; he
js常用刷新頁面的操作
自動刷新 meta his http navi cati 自動 href 一次 一、手動刷新 1,history.go(0) 2,location.reload() 3,location=location 4,location.assign(location) 5,doc
[js高手之路] es6系列教程 - Map詳解以及常用api
.com size style 系列教程 image clear rsquo images div ECMAScript 6中的Map類型是一種存儲著許多鍵值對的有序列表。鍵值對支持所有的數據類型. 鍵 0 和 ‘0’會被當做兩個不同的鍵,不會發生強
關於ES6 Class語法相關總結
定義類 成對 new source ret str 創建 方法 () 其實關於ES6,網上已經有很多的資料可供查詢,教程可參考阮一峰大神的ES6入門,本文只是對Class這一語法做一個總結: 一、Class基本語法 constructor方法 constructor是類的默
npm-node相關 及 git常用命令
I. 臨時使用npm源 // 臨時從淘寶的映象源安裝node-sass npm --registry https://registry.npm.taobao.org install node-sass II. 持久使用npm源 // 設定npm的源為淘寶的映象源 npm c