国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > JavaScript > 正文

深入理解JavaScript系列(18):面向對象編程之ECMAScript實現

2019-11-20 13:00:44
字體:
來源:轉載
供稿:網友

介紹

本章是關于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依然有值 主站蜘蛛池模板: 县级市| 许昌县| 六枝特区| 景洪市| 和静县| 伊吾县| 光泽县| 咸丰县| 盈江县| 绥棱县| 贡嘎县| 乌兰察布市| 巴林右旗| 巫山县| 泗阳县| 汉沽区| 松江区| 绥化市| 建阳市| 临海市| 读书| 高青县| 随州市| 古田县| 会同县| 洛阳市| 临桂县| 铁岭市| 永靖县| 彩票| 新沂市| 东乡族自治县| 武邑县| 清原| 白玉县| 罗江县| 抚州市| 中牟县| 嘉荫县| 龙游县| 昌平区|