介紹
本章是關于ECMAScript面向對象實現的第2篇,第1篇我們討論的是概論和CEMAScript的比較,如果你還沒有讀第1篇,在進行本章之前,我強烈建議你先讀一下第1篇,因為本篇實在太長了(35頁)。
英文原文:http://dmitrysoshnikov.com/ecmascript/chapter-7-2-oop-ecmascript-implementation/
注:由于篇幅太長了,難免出現錯誤,時刻保持修正中。
在概論里,我們延伸到了ECMAScript,現在,當我們知道它OOP實現時,我們再來準確定義一下:
ECMAScript is an object-oriented programming language supporting delegating inheritance based on prototypes.
ECMAScript是一種面向對象語言,支持基于原型的委托式繼承。
我們將從最基本的數據類型來分析,首先要了解的是ECMAScript用原始值(primitive values)和對象(objects)來區分實體,因此有些文章里說的“在JavaScript里,一切都是對象”是錯誤的(不完全對),原始值就是我們這里要討論的一些數據類型。數據類型
雖然ECMAScript是可以動態轉化類型的動態弱類型語言,它還是有數據類型的。也就是說,一個對象要屬于一個實實在在的類型。
標準規范里定義了9種數據類型,但只有6種是在ECMAScript程序里可以直接訪問的,它們是:Undefined、Null、Boolean、String、Number、Object。
另外3種類型只能在實現級別訪問(ECMAScript對象是不能使用這些類型的)并用于規范來解釋一些操作行為、保存中間值。這3種類型是:Reference、List和Completion。
因此,Reference是用來解釋delete、typeof、this這樣的操作符,并且包含一個基對象和一個屬性名稱;List描述的是參數列表的行為(在new表達式和函數調用的時候);Completion是用來解釋行為break、continue、return和throw語句的。
原始值類型
回頭來看6中用于ECMAScript程序的數據類型,前5種是原始值類型,包括Undefined、Null、Boolean、String、Number、Object。
原始值類型例子:
var a = undefined;
var b = null;
var c = true;
var d = 'test';
var e = 10;
這些值是在底層上直接實現的,他們不是object,所以沒有原型,沒有構造函數。
大叔注:這些原生值和我們平時用的(Boolean、String、Number、Object)雖然名字上相似,但不是同一個東西。所以typeof(true)和typeof(Boolean)結果是不一樣的,因為typeof(Boolean)的結果是function,所以函數Boolean、String、Number是有原型的(下面的讀寫屬性章節也會提到)。
想知道數據是哪種類型用typeof是最好不過了,有個例子需要注意一下,如果用typeof來判斷null的類型,結果是object,為什么呢?因為null的類型是定義為Null的。
alert(typeof null); // "object"
顯示"object"原因是因為規范就是這么規定的:對于Null值的typeof字符串值返回"object“。規范沒有想象解釋這個,但是Brendan Eich (JavaScript發明人)注意到null相對于undefined大多數都是用于對象出現的地方,例如設置一個對象為空引用。但是有些文檔里有些氣人將之歸結為bug,而且將該bug放在Brendan Eich也參與討論的bug列表里,結果就是任其自然,還是把typeof null的結果設置為object(盡管262-3的標準是定義null的類型是Null,262-5已經將標準修改為null的類型是object了)。
Object類型
接著,Object類型(不要和Object構造函數混淆了,現在只討論抽象類型)是描述 ECMAScript對象的唯一一個數據類型。
Object is an unordered collection of key-value pairs.
對象是一個包含key-value對的無序集合
對象的key值被稱為屬性,屬性是原始值和其他對象的容器。如果屬性的值是函數我們稱它為方法 。
例如:
var x = { // 對象"x"有3個屬性: a, b, c
a: 10, // 原始值
b: {z: 100}, // 對象"b"有一個屬性z
c: function () { // 函數(方法)
alert('method x.c');
}
};
alert(x.a); // 10
alert(x.b); // [object Object]
alert(x.b.z); // 100
x.c(); // 'method x.c'
動態性
正如我們在第17章中指出的,ES中的對象是完全動態的。這意味著,在程序執行的時候我們可以任意地添加,修改或刪除對象的屬性。
例如:
var foo = {x: 10};
// 添加新屬性
foo.y = 20;
console.log(foo); // {x: 10, y: 20}
// 將屬性值修改為函數
foo.x = function () {
console.log('foo.x');
};
foo.x(); // 'foo.x'
// 刪除屬性
delete foo.x;
console.log(foo); // {y: 20}
有些屬性不能被修改――(只讀屬性、已刪除屬性或不可配置的屬性)。 我們將稍后在屬性特性里講解。
另外,ES5規范規定,靜態對象不能擴展新的屬性,并且它的屬性頁不能刪除或者修改。他們是所謂的凍結對象,可以通過應用Object.freeze(o)方法得到。
var foo = {x: 10};
// 凍結對象
Object.freeze(foo);
console.log(Object.isFrozen(foo)); // true
// 不能修改
foo.x = 100;
// 不能擴展
foo.y = 200;
// 不能刪除
delete foo.x;
console.log(foo); // {x: 10}
在ES5規范里,也使用Object.preventExtensions(o)方法防止擴展,或者使用Object.defineProperty(o)方法來定義屬性:
var foo = {x : 10};
Object.defineProperty(foo, "y", {
value: 20,
writable: false, // 只讀
configurable: false // 不可配置
});
// 不能修改
foo.y = 200;
// 不能刪除
delete foo.y; // false
// 防治擴展
Object.preventExtensions(foo);
console.log(Object.isExtensible(foo)); // false
// 不能添加新屬性
foo.z = 30;
console.log(foo); {x: 10, y: 20}
內置對象、原生對象及宿主對象
有必要需要注意的是規范還區分了這內置對象、元素對象和宿主對象。
內置對象和元素對象是被ECMAScript規范定義和實現的,兩者之間的差異微不足道。所有ECMAScript實現的對象都是原生對象(其中一些是內置對象、一些在程序執行的時候創建,例如用戶自定義對象)。內置對象是原生對象的一個子集、是在程序開始之前內置到ECMAScript里的(例如,parseInt, Match等)。所有的宿主對象是由宿主環境提供的,通常是瀏覽器,并可能包括如window、alert等。
注意,宿主對象可能是ES自身實現的,完全符合規范的語義。從這點來說,他們能稱為“原生宿主”對象(盡快很理論),不過規范沒有定義“原生宿主”對象的概念。
Boolean,String和Number對象
另外,規范也定義了一些原生的特殊包裝類,這些對象是:
1.布爾對象
2.字符串對象
3.數字對象
這些對象的創建,是通過相應的內置構造器創建,并且包含原生值作為其內部屬性,這些對象可以轉換省原始值,反之亦然。
var c = new Boolean(true);
var d = new String('test');
var e = new Number(10);
// 轉換成原始值
// 使用不帶new關鍵字的函數
с = Boolean(c);
d = String(d);
e = Number(e);
// 重新轉換成對象
с = Object(c);
d = Object(d);
e = Object(e);
此外,也有對象是由特殊的內置構造函數創建: Function(函數對象構造器)、Array(數組構造器) RegExp(正則表達式構造器)、Math(數學模塊)、 Date(日期的構造器)等等,這些對象也是Object對象類型的值,他們彼此的區別是由內部屬性管理的,我們在下面討論這些內容。
字面量Literal
對于三個對象的值:對象(object),數組(array)和正則表達式(regular expression),他們分別有簡寫的標示符稱為:對象初始化器、數組初始化器、和正則表達式初始化器:
// 等價于new Array(1, 2, 3);
// 或者array = new Array();
// array[0] = 1;
// array[1] = 2;
// array[2] = 3;
var array = [1, 2, 3];
// 等價于
// var object = new Object();
// object.a = 1;
// object.b = 2;
// object.c = 3;
var object = {a: 1, b: 2, c: 3};
// 等價于new RegExp("^//d+$", "g")
var re = /^/d+$/g;
注意,如果上述三個對象進行重新賦值名稱到新的類型上的話,那隨后的實現語義就是按照新賦值的類型來使用,例如在當前的Rhino和老版本SpiderMonkey 1.7的實現上,會成功以new關鍵字的構造器來創建對象,但有些實現(當前Spider/TraceMonkey)字面量的語義在類型改變以后卻不一定改變。
var getClass = Object.prototype.toString;
Object = Number;
var foo = new Object;
alert([foo, getClass.call(foo)]); // 0, "[object Number]"
var bar = {};
// Rhino, SpiderMonkey 1.7中 - 0, "[object Number]"
// 其它: still "[object Object]", "[object Object]"
alert([bar, getClass.call(bar)]);
// Array也是一樣的效果
Array = Number;
foo = new Array;
alert([foo, getClass.call(foo)]); // 0, "[object Number]"
bar = [];
// Rhino, SpiderMonkey 1.7中 - 0, "[object Number]"
// 其它: still "", "[object Object]"
alert([bar, getClass.call(bar)]);
// 但對RegExp,字面量的語義是不被改變的。 semantics of the literal
// isn't being changed in all tested implementations
RegExp = Number;
foo = new RegExp;
alert([foo, getClass.call(foo)]); // 0, "[object Number]"
bar = /(?!)/g;
alert([bar, getClass.call(bar)]); // /(?!)/g, "[object RegExp]"
正則表達式字面量和RegExp對象
注意,下面2個例子在第三版的規范里,正則表達式的語義都是等價的,regexp字面量只在一句里存在,并且再解析階段創建,但RegExp構造器創建的卻是新對象,所以這可能會導致出一些問題,如lastIndex的值在測試的時候結果是錯誤的:
for (var k = 0; k < 4; k++) {
var re = /ecma/g;
alert(re.lastIndex); // 0, 4, 0, 4
alert(re.test("ecmascript")); // true, false, true, false
}
// 對比
for (var k = 0; k < 4; k++) {
var re = new RegExp("ecma", "g");
alert(re.lastIndex); // 0, 0, 0, 0
alert(re.test("ecmascript")); // true, true, true, true
}
注:不過這些問題在第5版的ES規范都已經修正了,不管是基于字面量的還是構造器的,正則都是創建新對象。關聯數組
各種文字靜態討論,JavaScript對象(經常是用對象初始化器{}來創建)被稱為哈希表哈希表或其它簡單的稱謂:哈希(Ruby或Perl里的概念), 管理數組(PHP里的概念),詞典 (Python里的概念)等。
只有這樣的術語,主要是因為他們的結構都是相似的,就是使用“鍵-值”對來存儲對象,完全符合“關聯數組 ”或“哈希表 ”理論定義的數據結構。 此外,哈希表抽象數據類型通常是在實現層面使用。
但是,盡管術語上來描述這個概念,但實際上這個是錯誤,從ECMAScript來看:ECMAScript只有一個對象以及類型以及它的子類型,這和“鍵-值”對存儲沒有什么區別,因此在這上面沒有特別的概念。 因為任何對象的內部屬性都可以存儲為鍵-值”對:
var a = {x: 10};
a['y'] = 20;
a.z = 30;
var b = new Number(1);
b.x = 10;
b.y = 20;
b['z'] = 30;
var c = new Function('');
c.x = 10;
c.y = 20;
c['z'] = 30;
// 等等,任意對象的子類型"subtype"
此外,由于在ECMAScript中對象可以是空的,所以"hash"的概念在這里也是不正確的:
Object.prototype.x = 10;
var a = {}; // 創建空"hash"
alert(a["x"]); // 10, 但不為空
alert(a.toString); // function
a["y"] = 20; // 添加新的鍵值對到 "hash"
alert(a["y"]); // 20
Object.prototype.y = 20; // 添加原型屬性
delete a["y"]; // 刪除
alert(a["y"]); // 但這里key和value依然有值
主站蜘蛛池模板:
县级市|
许昌县|
六枝特区|
景洪市|
和静县|
伊吾县|
光泽县|
咸丰县|
盈江县|
绥棱县|
贡嘎县|
乌兰察布市|
巴林右旗|
巫山县|
泗阳县|
汉沽区|
松江区|
绥化市|
建阳市|
临海市|
读书|
高青县|
随州市|
古田县|
会同县|
洛阳市|
临桂县|
铁岭市|
永靖县|
彩票|
新沂市|
东乡族自治县|
武邑县|
清原|
白玉县|
罗江县|
抚州市|
中牟县|
嘉荫县|
龙游县|
昌平区|