<a name="a1"></a>
# 第三章 直接量和構造函數
JavaScript中的直接量模式更加簡潔、富有表現力,且在定義對象時不容易出錯。本章將對直接量展開討論,包括對象、數組和正則表達式直接量,以及為什么要優先使用它們而不是如`Object()`和`Array()`這些等價的內置構造器函數。本章同樣會介紹JSON格式,JSON是使用數組和對象直接量的形式定義的一種數據轉換格式。本章還會討論自定義構造函數,包括如何強制使用new以確保構造函數的正確執行。
本章還會補充講述一些基礎知識,比如內置包裝對象Number()、String()和Boolean(),以及如何將它們和原始值(數字、字符串和布爾值)比較。最后,快速介紹一下Error()構造函數的用法。
<a name="a2"></a>
## 對象直接量
我們可以將JavaScript中的對象簡單的理解為名值對組成的散列表(hash table),在其他編程語言中被稱作“關聯數組”。其中的值可以是原始值也可以是對象,不管是什么類型,它們都是“屬性”(properties),屬性值同樣可以是函數,這時屬性就被稱為“方法”(methods)。
JavaScript中自定義的對象(用戶定義的本地對象)任何時候都是可變的。內置本地對象的屬性也是可變的。你可以先創建一個空對象,然后在需要時給它添加功能。“對象直接量寫法(object literal notation)”是按需創建對象的一種理想方式。
看一下這個例子:
// start with an empty object
var dog = {};
// add one property
dog.name = "Benji";
// now add a method
dog.getName = function () {
return dog.name;
};
在這個例子中,我們首先定義了一個空對象,然后添加了一個屬性和一個方法,在程序的生命周期內的任何時刻都可以:
1.更改屬性和方法的值,比如:
dog.getName = function () {
// redefine the method to return
// a hardcoded value
return "Fido";
};
2.完全刪除屬性/方法
delete dog.name;
3.添加更多的屬性和方法
dog.say = function () {
return "Woof!";
};
dog.fleas = true;
其實不必每次開始都創建空對象,對象直接量模式可以直接在創建對象時添加功能,就像下面這個例子所展示的:
var dog = {
name: "Benji",
getName: function () {
return this.name;
}
};
> 在本書中多次提到“空對象”(“blank object”和“empty object”)。這只是某種簡稱,要知道JavaScript中根本不存在真正的空對象,理解這一點至關重要。即使最簡單的`{}`對象包含從Object.prototype繼承來的屬性和方法。我們提到的“空(empty)對象”只是說這個對象沒有自己的屬性,不考慮它是否有繼承來的屬性。
<a name="a3"></a>
### 對象直接量語法
如果你從來沒有接觸過對象直接量寫法,第一次碰到可能會感覺怪怪的。但越到后來你就越喜歡它。本質上講,對象直接量語法包括:
- 將對象主體包含在一對花括號內(`{` and `}`)。
- 對象內的屬性或方法之間使用逗號分隔。最后一個名值對后也可以有逗號,但在IE下會報錯,所以盡量不要在最后一個屬性或方法后加逗號。
- 屬性名和值之間使用冒號分隔
- 如果將對象賦值給一個變量,不要忘了在右括號`}`之后補上分號
<a name="a4"></a>
### 通過構造函數創建對象
JavaScript中沒有類的概念,這給JavaScript帶來了極大的靈活性,因為你不必提前知曉關于對象的任何信息,也不需要類的“藍圖”。但JavaScript同樣具有構造函數,它的語法和Java或其他語言中基于類的對象創建非常類似。
你可以使用自定義的構造函數來創建實例對象,也可以使用內置構造函數來創建,比如Object()、Date()、String()等等。
下面這個例子展示了用兩種等價的方法分別創建兩個獨立的實例對象:
// one way -- using a literal
var car = {goes: "far"};
// another way -- using a built-in constructor
// warning: this is an antipattern
var car = new Object();
car.goes = "far";
從這個例子中可以看到,直接量寫法的一個明顯優勢是,它的代碼更少。“創建對象的最佳模式是使用直接量”還有一個原因,它可以強調對象就是一個簡單的可變的散列表,而不必一定派生自某個類。
另外一個使用直接量而不是Object構造函數創建實例對象的原因是,對象直接量不需要“作用域解析”(scope resolution)。因為新創建的實例有可能包含了一個本地的構造函數,當你調用Object()的時候,解析器需要順著作用域鏈從當前作用域開始查找,直到找到全局Object構造函數為止。
<a name="a5"></a>
### 獲得對象的構造器
創建實例對象時能用對象直接量就不要使用new Object()構造函數,但有時你希望能繼承別人寫的代碼,這時就需要了解構造函數的一個“特性”(也是不使用它的另一個原因),就是Object()構造函數可以接收參數,通過參數的設置可以把實例對象的創建委托給另一個內置構造函數,并返回另外一個實例對象,而這往往不是你所希望的。
下面的示例代碼中展示了給new Object()傳入不同的參數:數字、字符串和布爾值,最終得到的對象都是由不同的構造函數生成的:
// Warning: antipatterns ahead
// an empty object
var o = new Object();
console.log(o.constructor === Object); // true
// a number object
var o = new Object(1);
console.log(o.constructor === Number); // true
console.log(o.toFixed(2)); // "1.00"
// a string object
var o = new Object("I am a string");
console.log(o.constructor === String); // true
// normal objects don't have a substring()
// method but string objects do
console.log(typeof o.substring); // "function"
// a boolean object
var o = new Object(true);
console.log(o.constructor === Boolean); // true
Object()構造函數的這種特性會導致一些意想不到的結果,特別是當參數不確定的時候。最后再次提醒不要使用new Object(),盡可能的使用對象直接量來創建實例對象。
<a name="a6"></a>
## 自定義構造函數
除了對象直接量和內置構造函數之外,你也可以通過自定義的構造函數來創建實例對象,正如下面的代碼所示:
var adam = new Person("Adam");
adam.say(); // "I am Adam"
這里用了“類”Person創建了實例,這種寫法看起來很像Java中的實例創建。兩者的語法的確非常接近,但實際上JavaScript中沒有類的概念,Person是一個函數。
Person構造函數是如何定義的呢?看下面的代碼:
var Person = function (name) {
this.name = name;
this.say = function () {
return "I am " + this.name;
};
};
當你通過關鍵字new來調用這個構造函數時,函數體內將發生這些事情:
- 創建一個空對象,將它的引用賦給this,繼承函數的原型。
- 通過this將屬性和方法添加至這個對象
- 最后返回this指向的新對象(如果沒有手動返回其他的對象)
用代碼表示這個過程如下:
var Person = function (name) {
// create a new object
// using the object literal
// var this = {};
// add properties and methods
this.name = name;
this.say = function () {
return "I am " + this.name;
};
//return this;
};
正如這段代碼所示,say()方法添加至this中,結果是,不論何時調用new Person(),在內存中都會創建一個新函數(譯注:所有Person的實例對象中的方法都是獨占一塊內存的)。顯然這是效率很低的,因為所有實例的say()方法是一模一樣的,因此沒有必要“拷貝”多份。最好的辦法是將方法添加至Person的原型中。
Person.prototype.say = function () {
return "I am " + this.name;
};
我們將會在下一章里詳細討論原型和繼承。現在只要記住將需要重用的成員和方法放在原型里即可。
關于構造函數的內部工作機制也會在后續章節中有更細致的討論。這里我們只做概要的介紹。剛才提到,構造函數執行的時候,首先創建一個新對象,并將它的引用賦給this:
// var this = {};
事實并不完全是這樣,因為“空”對象并不是真的空,這個對象繼承了Person的原型,看起來更像:
// var this = Object.create(Person.prototype);
在后續章節會進一步討論Object.create()。
<a name="a7"></a>
### 構造函數的返回值
用new調用的構造函數總是會返回一個對象,默認返回this所指向的對象。如果構造函數內沒有給this賦任何屬性,則返回一個“空”對象(除了繼承構造函數的原型之外,沒有“自己的”屬性)。
盡管我們不會在構造函數內寫return語句,也會隱式返回this。但我們是可以返回任意指定的對象的,在下面的例子中就返回了新創建的that對象。
var Objectmaker = function () {
// this `name` property will be ignored
// because the constructor
// decides to return another object instead
this.name = "This is it";
// creating and returning a new object
var that = {};
that.name = "And that's that";
return that;
};
// test
var o = new Objectmaker();
console.log(o.name); // "And that's that"
我們看到,構造函數中其實是可以返回任意對象的,只要你返回的東西是對象即可。如果返回值不是對象(字符串、數字或布爾值),程序不會報錯,但這個返回值被忽略,最終還是返回this所指的對象。
<a name="a8"></a>
## 強制使用new的模式
我們知道,構造函數和普通的函數無異,只是通過new調用而已。那么如果調用構造函數時忘記new會發生什么呢?漏掉new不會產生語法錯誤也不會有運行時錯誤,但可能會造成邏輯錯誤,導致執行結果不符合預期。這是因為如果不寫new的話,函數內的this會指向全局對象(在瀏覽器端this指向window)。
當構造函數內包含this.member之類的代碼,并直接調用這個函數(省略new),實際會創建一個全局對象的屬性member,可以通過window.member或member訪問到它。這必然不是我們想要的結果,因為我們要努力確保全局命名空間的整潔干凈。
// constructor
function Waffle() {
this.tastes = "yummy";
}
// a new object
var good_morning = new Waffle();
console.log(typeof good_morning); // "object"
console.log(good_morning.tastes); // "yummy"
// antipattern:
// forgotten `new`
var good_morning = Waffle();
console.log(typeof good_morning); // "undefined"
console.log(window.tastes); // "yummy"
ECMAScript5中修正了這種非正常的行為邏輯。在嚴格模式中,this是不能指向全局對象的。如果在不支持ES5的JavaScript環境中,仍然后很多方法可以確保構造函數的行為即便在省略new調用時也不會出問題。
<a name="a9"></a>
### 命名約定
最簡單的選擇是使用命名約定,前面的章節已經提到,構造函數名首字母大寫(MyConstructor),普通函數和方法名首字母小寫(myFunction)。
<a name="a10"></a>
### 使用that
遵守命名約定的確能幫上一些忙,但約定畢竟不是強制,不能完全避免出錯。這里給出了一種模式可以確保構造函數一定會按照構造函數的方式執行。不要將所有成員掛在this上,將它們掛在that上,并返回that。
function Waffle() {
var that = {};
that.tastes = "yummy";
return that;
}
如果要創建簡單的實例對象,甚至不需要定義一個局部變量that,可以直接返回一個對象直接量,就像這樣:
function Waffle() {
return {
tastes: "yummy"
};
}
不管用什么方式調用它(使用new或直接調用),它同都會返回一個實例對象:
var first = new Waffle(),
second = Waffle();
console.log(first.tastes); // "yummy"
console.log(second.tastes); // "yummy"
這種模式的問題是丟失了原型,因此在Waffle()的原型上的成員不會繼承到這些實例對象中。
> 需要注意的是,這里用的that只是一種命名約定,that不是語言的保留字,可以將它替換為任何你喜歡的名字,比如self或me。
<a name="a11"></a>
### 調用自身的構造函數
為了解決上述模式的問題,能夠讓實例對象繼承原型屬性,我們使用下面的方法。在構造函數中首先檢查this是否是構造函數的實例,如果不是,再通過new調用構造函數,并將new的結果返回:
function Waffle() {
if (!(this instanceof Waffle)) {
return new Waffle();
}
this.tastes = "yummy";
}
Waffle.prototype.wantAnother = true;
// testing invocations
var first = new Waffle(),
second = Waffle();
console.log(first.tastes); // "yummy"
console.log(second.tastes); // "yummy"
console.log(first.wantAnother); // true
console.log(second.wantAnother); // true
另一種檢查實例的通用方法是使用arguments.callee,而不是直接將構造函數名寫死在代碼中:
if (!(this instanceof arguments.callee)) {
return new arguments.callee();
}
這里需要說明的是,在任何函數內部都會自行創建一個arguments對象,它包含函數調用時傳入的參數。同時arguments包含一個callee屬性,指向它所在的正在被調用的函數。需要注意,ES5嚴格模式中是禁止使用arguments.callee的,因此最好對它的使用加以限制,并刪除任何你能在代碼中找到的實例(譯注:這里作者的表述很委婉,其實作者更傾向于全面禁止使用arguments.callee)。
<a name="a12"></a>
## 數組直接量
和其他的大多數一樣,JavaScript中的數組也是對象。可以通過內置構造函數Array()來創建數組,類似對象直接量,數組也可以通過直接量形式創建。而且更推薦使用直接量創建數組。
這里的實例代碼給出了創建兩個具有相同元素的數組的兩種方法,使用Array()和使用直接量模式:
// array of three elements
// warning: antipattern
var a = new Array("itsy", "bitsy", "spider");
// the exact same array
var a = ["itsy", "bitsy", "spider"];
console.log(typeof a); // "object", because arrays are objects
console.log(a.constructor === Array); // true
<a name="a13"></a>
### 數組直接量語法
數組直接量寫法非常簡單:整個數組使用方括號括起來,數組元素之間使用逗號分隔。數組元素可以是任意類型,也包括數組和對象。
數組直接量語法簡單直接、高雅美觀。畢竟數組只是從位置0開始索引的值的集合,完全沒必要包含構造器和new運算符的內容(代碼會更多),保持簡單即可。
<a name="a14"></a>
### 有意思的數組構造器
我們對new Array()敬而遠之原因是為了避免構造函數帶來的陷阱。
如果給Array()構造器傳入一個數字,這個數字并不會成為數組的第一個元素,而是設置數組的長度。也就是說,new Array(3)創建了一個長度為3的數組,而不是某個元素是3。如果你訪問數組的任意元素都會得到undefined,因為元素并不存在。下面示例代碼展示了直接量和構造函數的區別:
// an array of one element
var a = [3];
console.log(a.length); // 1
console.log(a[0]); // 3
// an array of three elements
var a = new Array(3);
console.log(a.length); // 3
console.log(typeof a[0]); // "undefined"
或許上面的情況看起來還不算是太嚴重的問題,但當 `new Array()` 的參數是一個浮點數而不是整數時則會導致嚴重的錯誤,這是因為數組的長度不可能是浮點數。
// using array literal
var a = [3.14];
console.log(a[0]); // 3.14
var a = new Array(3.14); // RangeError: invalid array length
console.log(typeof a); // "undefined"
為了避免在運行時動態創建數組時出現這種錯誤,強烈推薦使用數組直接量來代替new Array()。
>有些人用Array()構造器來做一些有意思的事情,比如用來生成重復字符串。下面這行代碼返字符串包含255個空格(請讀者思考為什么不是256個空格)。`var white = new Array(256).join(' ');`
<a name="a15"></a>
### 檢查是不是數組
如果typeof的操作數是數組的話,將返回“object”。
console.log(typeof [1, 2]); // "object"
這個結果勉強說得過去,畢竟數組是一種對象,但對我們用處不大。往往你需要知道一個值是不是真正的數組。你可能見到過這種檢查數組的方法:檢查length屬性、檢查數組方法比如slice()等等。但這些方法非常脆弱,非數組的對象也可以擁有這些同名的屬性。還有些人使用instanceof Array來判斷數組,但這種方法在某些版本的IE里的多個iframe的場景中會出問題(譯注:原因就是在不同iframe中創建的數組不會相互共享其prototype屬性)。
ECMAScript 5定義了一個新的方法Array.isArray(),如果參數是數組的話就返回true。比如:
Array.isArray([]); // true
// trying to fool the check
// with an array-like object
Array.isArray({
length: 1,
"0": 1,
slice: function () {}
}); // false
如果你的開發環境不支持ECMAScript5,可以通過Object.prototype.toString()方法來代替。如調用toString的call()方法并傳入數組上下文,將返回字符串“[object Array]”。如果傳入對象上下文,則返回字符串“[object Object]”。因此可以這樣做:
if (typeof Array.isArray === "undefined") {
Array.isArray = function (arg) {
return Object.prototype.toString.call(arg) === "[object Array]";
};
}
<a name="a16"></a>
## JSON
上文我們剛剛討論過對象和數組直接量,你已經對此很熟悉了,現在我們將目光轉向JSON。JSON(JavaScript Object Notation)是一種輕量級的數據交換格式。很多語言中都實現了JSON,特別是在JavaScript中。
JSON格式及其簡單,它只是數組和對象直接量的混合寫法,看一個JSON字符串的例子:
{"name": "value", "some": [1, 2, 3]}
JSON和對象直接量在語法上的唯一區別是,合法的JSON屬性名均用引號包含。而在對象直接量中,只有屬性名是非法的標識符時采用引號包含,比如,屬性名中包含空格`{"first name": "Dave"}`。
在JSON字符串中,不能使用函數和正則表達式直接量。
<a name="a17"></a>
### 使用JSON
在前面的章節中講到,出于安全考慮,不推薦使用eval()來“粗糙的”解析JSON字符串。最好使用JSON.parse()方法,ES5中已經包含了這個方法,而且在現代瀏覽器的JavaScript引擎中已經內置支持JSON了。對于老舊的JavaScript引擎來說,你可以使用JSON.org所提供的JS文件(http://www.json.org/json2.js)來獲得JSON對象和方法。
// an input JSON string
var jstr = '{"mykey": "my value"}';
// antipattern
var data = eval('(' + jstr + ')');
// preferred
var data = JSON.parse(jstr);
console.log(data.mykey); // "my value"
如果你已經在使用某個JavaScript庫了,很可能庫中提供了解析JSON的方法,就不必再額外引入JSON.org的庫了,比如,如果你已經使用了YUI3,你可以這樣:
// an input JSON string
var jstr = '{"mykey": "my value"}';
// parse the string and turn it into an object
// using a YUI instance
YUI().use('json-parse', function (Y) {
var data = Y.JSON.parse(jstr);
console.log(data.mykey); // "my value"
});
如果你使用的是jQuery,可以直接使用它提供的parseJSON()方法:
// an input JSON string
var jstr = '{"mykey": "my value"}';
var data = jQuery.parseJSON(jstr);
console.log(data.mykey); // "my value"
和JSON.parse()方法相對應的是JSON.stringify()。它將對象或數組(或任何原始值)轉換為JSON字符串。
var dog = {
name: "Fido",
dob:new Date(),
legs:[1,2,3,4]
};
var jsonstr = JSON.stringify(dog);
// jsonstr is now:
// {"name":"Fido","dob":"2010-04-11T22:36:22.436Z","legs":[1,2,3,4]}
<a name="a18"></a>
## 正則表達式直接量
JavaScript中的正則表達式也是對象,可以通過兩種方式創建它們:
- 使用new RegExp()構造函數
- 使用正則表達式直接量
下面的示例代碼展示了創建正則表達式的兩種方法,創建的正則用來匹配一個反斜杠(\):
// regular expression literal
var re = /\\/gm;
// constructor
var re = new RegExp("\\\\", "gm");
顯然正則表達式直接量寫法的代碼更短,且不必強制按照類構造器的思路來寫。因此更推薦使用直接量寫法。
另外,如果使用RegExp()構造函數寫法,還需要考慮對引號和反斜杠進行轉義,正如上段代碼所示的那樣,用了四個反斜杠來匹配一個反斜杠。這會增加正則表達式的長度,而且讓正則變得難于理解和維護。剛開始學習正則表達式不是很容易,所以不要放棄任何一個簡化它們的機會,所以要盡量使用直接量而不是通過構造函數來創建正則。
<a name="a19"></a>
### 正則表達式直接量語法
正則表達式直接量使用兩個斜線包裹起來,正則的主體部分不包括兩端的斜線。在第二個斜線之后可以指定模式匹配的修飾符用以高級匹配,修飾符不需要引號引起來,JavaScript中有三個修飾符:
- g,全局匹配
- m,多行匹配
- i,忽略大小寫的匹配
修飾符可以自由組合,而且順序無關:
var re = /pattern/gmi;
使用正則表達式直接量可以讓代碼更加簡潔高效,比如當調用String.prototype.prelace()方法時,可以傳入正則表達式參數:
var no_letters = "abc123XYZ".replace(/[a-z]/gi, "");
console.log(no_letters); // 123
有一種不得不使用new RegExp()的情形,有時正則表達式是不確定的,直到運行時才能確定下來。
正則表達式直接量和Regexp()構造函數的另一個區別是,正則表達式直接量只在解析時創建一次正則表達式對象(譯注:多次解析同一個正則表達式,會產生相同的實例對象)。如果在循環體內反復創建相同的正則表達式,則每個正則對象的所有屬性(比如lastIndex)只會設置一次(譯注:由于每次創建相同的實例對象,每個循環中的實例對象都是同一個,屬性也自然相同),下面這個例子展示了兩次都返回了相同的正則表達式的情形(譯注:這里作者的表述只是針對ES3規范而言,下面這段代碼在NodeJS、IE6-IE9、FireFox4、Chrome10、Safari5中運行結果和作者描述的不一致,Firefox 3.6中的運行結果和作者描述是一致的,原因可以在ECMAScript5規范第24頁和第247頁找到,也就是說在ECMAScript3規范中,用正則表達式創建的RegExp對象會共享同一個實例,而在ECMAScript5中則是兩個獨立的實例。而最新的Firefox4、Chrome和Safari5都遵循ECMAScript5標準,至于IE6-IE8都沒有很好的遵循ECMAScript3標準,不過在這個問題上反而處理對了。很明顯ECMAScript5的規范更符合開發者的期望)。
function getRE() {
var re = /[a-z]/;
re.foo = "bar";
return re;
}
var reg = getRE(),
re2 = getRE();
console.log(reg === re2); // true
reg.foo = "baz";
console.log(re2.foo); // "baz"
>在ECMAScript5中這種情形有所改變,相同正則表達式直接量的每次計算都會創建新的實例對象,目前很多現代瀏覽器也對此做了糾正(譯注:比如在Firefox4就糾正了Firefox3.6的這種“錯誤”)。
最后需要提一點,不帶new調用RegExp()(作為普通的函數)和帶new調用RegExp()是完全一樣的。
<a name="a20"></a>
## 原始值的包裝對象
JavaScript中有五種原始類型:數字、字符串、布爾值、null和undefined。除了null和undefined之外,其他三種都有對應的“包裝對象”(wrapper objects)。可以通過內置構造函數來生成包裝對象,Number()、String()、和Boolean()。
為了說明數字原始值和數字對象之間的區別,看一下下面這個例子:
// a primitive number
var n = 100;
console.log(typeof n); // "number"
// a Number object
var nobj = new Number(100);
console.log(typeof nobj); // "object"
包裝對象帶有一些有用的屬性和方法,比如,數字對象就帶有toFixed()和toExponential()之類的方法。字符串對象帶有substring()、chatAt()和toLowerCase()等方法以及length屬性。這些方法非常方便,和原始值相比,這讓包裝對象具備了一定優勢。其實原始值也可以調用這些方法,因為原始值會首先轉換為一個臨時對象,如果轉換成功,則調用包裝對象的方法。
// a primitive string be used as an object
var s = "hello";
console.log(s.toUpperCase()); // "HELLO"
// the value itself can act as an object
"monkey".slice(3, 6); // "key"
// same for numbers
(22 / 7).toPrecision(3); // "3.14"
因為原始值可以根據需要轉換成對象,這樣的話,也不必為了用包裝對象的方法而將原始值手動“包裝”成對象。比如,不必使用new String("hi"),直接使用"hi"即可。
// avoid these:
var s = new String("my string");
var n = new Number(101);
var b = new Boolean(true);
// better and simpler:
var s = "my string";
var n = 101;
var b = true;
不得不使用包裝對象的一個原因是,有時我們需要對值進行擴充并保持值的狀態。原始值畢竟不是對象,不能直接對其進行擴充(譯注:比如`1.property = 2`會報錯)。
// primitive string
var greet = "Hello there";
// primitive is converted to an object
// in order to use the split() method
greet.split(' ')[0]; // "Hello"
// attemting to augment a primitive is not an error
greet.smile = true;
// but it doesn't actually work
typeof greet.smile; // "undefined"
在這段示例代碼中,greet只是臨時轉換成了對象,以保證訪問其屬性/方法時不會出錯。另一方面,如果greet通過new String()定義為一個對象,那么擴充smile屬性就會按照期望的那樣執行。對字符串、數字或布爾值的擴充并不常見,除非你清楚自己想要什么,否則不必使用包裝對象。
當省略new時,包裝器將傳給它的參數轉換為原始值:
typeof Number(1); // "number"
typeof Number("1"); // "number"
typeof Number(new Number()); // "number"
typeof String(1); // "string"
typeof Boolean(1); // "boolean"
<a name="a21"></a>
## Error 對象
JavaScript中有很多內置的Error構造函數,比如Error()、SyntaxError(),TypeError()等等,這些“錯誤”通常和throw語句一起使用。這些構造函數創建的錯誤對象包含這些屬性:
**name**
name屬性是指創建這個對象的構造函數的名字,通常是“Errora”,有時會有特定的名字比如“RangeError”
**message**
創建這個對象時傳入構造函數的字符串
錯誤對象還有其他一些屬性,比如產生錯誤的行號和文件名,但這些屬性是瀏覽器自行實現的,不同瀏覽器的實現也不一致,因此出于兼容性考慮,并不推薦使用這些屬性。
另一方面,throw可以拋出任何對象,并不限于“錯誤對象”,因此你可以根據需要拋出自定義的對象。這些對象包含屬性“name”和“message”或其他你希望傳遞給異常處理邏輯的信息,異常處理邏輯由catch語句指定。你可以靈活運用拋出的錯誤對象,將程序從錯誤狀態恢復至正常狀態。
try {
// something bad happened, throw an error
throw {
name: "MyErrorType", // custom error type
message: "oops",
extra: "This was rather embarrassing",
remedy: genericErrorHandler // who should handle it
};
} catch (e) {
// inform the user
alert(e.message); // "oops"
// gracefully handle the error
e.remedy(); // calls genericErrorHandler()
}
通過new調用和省略new調用錯誤構造函數是一模一樣的,他們都返回相同的錯誤對象。
<a name="a22"></a>
## 小結
在本章里,我們討論了多種直接量模式,它們是使用構造函數寫法的替代方案,本章講述了這些內容:
- 對象直接量寫法——一種簡潔優雅的定義對象的方法,名值對之間用逗號分隔,通過花括號包裝起來
- 構造函數——內置構造函數(內置構造函數通常都有對應的直接量語法)和自定義構造函數。
- 一種強制函數以構造函數的模式執行(不管用不用new調用構造函數,都始終返回new出來的實例)的技巧
- 數組直接量寫法——數組元素之間使用逗號分隔,通過方括號括起來
- JSON——是一種輕量級的數據交換格式
- 正則表達式直接量
- 避免使用其他的內置構造函數:String()、Number()、Boolean()以及不同種類的Error()構造器
通常除了Date()構造函數之外,其他的內置構造函數并不常用,下面的表格中對這些構造函數以及它們的直接量語法做了整理。
<table>
<tr>
<td>內置構造函數(不推薦)</td>
<td>直接量語法和原始值(推薦)</td>
</tr>
<tr>
<td>var o = new Object();</td>
<td>var o = {};
</td>
</tr>
<tr>
<td>var a = new Array();
</td>
<td>var a = [];
</td>
</tr>
<tr>
<td>var re = new RegExp("[a-z]","g");
</td>
<td>var re = /[a-z]/g;
</td>
</tr>
<tr>
<td>var s = new String();
</td>
<td>var s = "";
</td>
</tr>
<tr>
<td>var n = new Number();
</td>
<td>var n = 0;
</td>
</tr>
<tr>
<td>var b = new Boolean();
</td>
<td>var b = false;
</td>
</tr>
<tr>
<td>throw new Error("uh-oh");
</td>
<td>throw { name: "Error",message: "uh-oh"};或者throw Error("uh-oh");
</td>
</tr>
</table>