1. 程式人生 > >學習ECMAScript(2017年7月27日)

學習ECMAScript(2017年7月27日)

一元運算子

Delete

delete 運算子刪除對開發者新增的屬性或方法的引用,不能刪除系統的屬性或方法引用。

Void

void 運算子對任何值返回 undefined。該運算子通常用於避免輸出不應該輸出的值。

例如,從 HTML 的 <a> 元素呼叫 JavaScript 函式時。要正確做到這一點,函式不能返回有效值,否則瀏覽器將清空頁面,只顯示函式的結果。例如:

<a href="javascript:window.open('about:blank')">

Click me</a>

如果把這行程式碼放入 HTML 頁面,點選其中的連結,即可看到螢幕上顯示 "[object]"。這是因為 window.open() 方法返回了新開啟的視窗的引用。然後該物件將被轉換成要顯示的字串。要避免這種效果,可以用 void 運算子呼叫 window.open() 函式:<a
href="javascript:void(window.open('about:blank'))">Click me</a>

這使 window.open() 呼叫返回 undefined,它不是有效值,不會顯示在瀏覽器視窗中。提示:請記住,沒有返回值的函式真正返回的都是 undefined。

ECMAScript變數

ECMAScript 中,變數可以存在兩種型別的值,即原始值和引用值。

原始值:儲存在棧(stack)中的簡單資料段,也就是說,它們的值直接儲存在變數訪問的位置。

引用值:儲存在堆(heap)中的物件,也就是說,儲存在變數處的值是一個指標(point),指向儲存物件的記憶體處。

為變數賦值時,ECMAScript 的解釋程式必須判斷該值是原始型別,還是引用型別。要實現這一點,解釋程式則需嘗試判斷該值是否為 ECMAScript 的原始型別之一

,這裡引出下一節內容

ECMAScript原始型別

Undefined型別

Undefined 型別只有一個值,即 undefined。當宣告的變數未初始化時,該變數的預設值是 undefinedvar  oTemp;

alert(oTemp == undefined); 這段程式碼將顯示 "true",說明這兩個值確實相等

undefined 並不同於未定義的值。但是,typeof 運算子並不真正區分這兩種值。考慮下面的程式碼:

var oTemp;

alert(typeof oTemp);  //輸出 "undefined"

alert(typeof oTemp2);  //輸出 "undefined"

前面的程式碼對兩個變數輸出的都是 "undefined",即使只有變數 oTemp2 從未被宣告過。如果對 oTemp2 使用除 typeof 之外的其他運算子的話,會引起錯誤,因為其他運算子只能用於已宣告的變數上

例如,下面的程式碼將引發錯誤:

var oTemp;

alert(oTemp2 == undefined);

當函式無明確返回值時,返回的也是值 "undefined",如下所示:

function testFunc() {}

alert(testFunc() == undefined);  //輸出 "true"

Null型別

另一種只有一個值的型別是 Null,它只有一個專用值 null,即它的字面量。值 undefined 實際上是從值 null 派生來的,因此 ECMAScript 把它們定義為相等的。

alert(null == undefined);  //輸出 "true"

儘管這兩個值相等,但它們的含義不同。undefined 是聲明瞭變數但未對其初始化時賦予該變數的值,null 則用於表示尚未存在的物件(在討論 typeof 運算子時,簡單地介紹過這一點)。如果函式或方法要返回的是物件,那麼找不到該物件時,返回的通常是 null。

Boolean型別

它有兩個值 true 和 false

Number 型別

這種型別既可以表示 32 位的整數,還可以表示 64 位的浮點數。要定義浮點值,必須包括小數點和小數點後的一位數字(例如,用 1.0 而不是 1)。這被看作浮點數字面量。

String型別

String 型別的獨特之處在於,它是唯一沒有固定大小的原始型別。可以用字串儲存 0 或更多的 Unicode 字元,有 16 位整數表示。

typeof運算子

判斷物件是原始值,還是引用值。對變數或值呼叫 typeof 運算子將返回下列值之一:

· undefined - 如果變數是 Undefined 型別的

· boolean - 如果變數是 Boolean 型別的

· number - 如果變數是 Number 型別的

· string - 如果變數是 String 型別的

· object - 如果變數是一種引用型別或 Null 型別的

ECMAScript型別轉換

轉為字串

1、3 種主要的原始型別 Boolean 值、數字和字串都有 toString() 方法,可以把它們的值轉換成字串。

2、也許會問,“字串還有 toString() 方法嗎,這不是多餘嗎?”是的,的確如此,不過 ECMAScript 定義所有物件都有 toString() 方法,無論它是偽物件,還是真物件。因為 String 型別屬於偽物件,所以它一定有 toString() 方法。

3、Boolean 型別的 toString() 方法只是輸出 "true" 或 "false",結果由變數的值決定

4、Number 型別的 toString() 方法比較特殊,它有兩種模式,即預設模式基模式。採用預設模式,toString() 方法只是用相應的字串輸出數字值(無論是整數、浮點數還是科學計數法),在預設模式中,無論最初採用什麼表示法宣告數字,Number 型別的 toString() 方法返回的都是數字的十進位制表示。因此,以八進位制或十六進位制字面量形式宣告的數字輸出的都是十進位制形式的。採用 Number 型別的 toString() 方法的基模式,可以用不同的基輸出數字,例如二進位制的基是 2,八進位制的基是 8,十六進位制的基是 16。

基只是要轉換成的基數的另一種加法而已,它是 toString() 方法的引數:

var iNum = 10;

alert(iNum.toString(2)); //輸出 "1010"

alert(iNum.toString(8)); //輸出 "12"

alert(iNum.toString(16)); //輸出 "A"

轉為數字

ECMAScript 提供了兩種把非數字的原始值轉換成數字的方法,即 parseInt() 和 parseFloat()。前者把值轉換成整數,後者把值轉換成浮點數。只有對 String 型別呼叫這些方法,它們才能正確執行;對其他型別返回的都是 NaN。

在判斷字串是否是數字值前,parseInt() 和 parseFloat() 都會仔細分析該字串。

轉為數字:parseInt()

parseInt() 方法首先檢視位置 0 處的字元,判斷它是否是個有效數字;如果不是,該方法將返回 NaN,不再繼續執行其他操作。但如果該字元是有效數字,該方法將檢視位置 1 處的字元,進行同樣的測試。這一過程將持續到發現非有效數字的字元為止,此時 parseInt() 將把該字元之前的字串轉換成數字。

例如,如果要把字串 "12345red" 轉換成整數,那麼 parseInt() 將返回 12345,因為當它檢查到字元 r 時,就會停止檢測過程。

字串中包含的數字字面量會被正確轉換為數字,比如 "0xA" 會被正確轉換為數字 10。不過,字串 "22.5" 將被轉換成 22,因為對於整數來說,小數點是無效字元。

parseInt() 方法還有基模式,可以把二進位制、八進位制、十六進位制或其他任何進位制的字串轉換成整數。基是由 parseInt() 方法的第二個引數指定的,所以要解析十六進位制的值,需如下呼叫 parseInt() 方法:

var iNum1 = parseInt("AF", 16); //返回 175

當然,對二進位制、八進位制甚至十進位制(預設模式),都可以這樣呼叫 parseInt() 方法:

var iNum1 = parseInt("10", 2); //返回 2

var iNum2 = parseInt("10", 8); //返回 8

var iNum3 = parseInt("10", 10); //返回 10

轉為數字:parseFloat()

parseFloat() 方法與 parseInt() 方法的處理方式相似,從位置 0 開始檢視每個字元,直到找到第一個非有效的字元為止,然後把該字元之前的字串轉換成整數。

不過,對於這個方法來說,第一個出現的小數點是有效字元。如果有兩個小數點,第二個小數點將被看作無效的。parseFloat() 會把這個小數點之前的字元轉換成數字。這意味著字串 "11.22.33" 將被解析成 11.22。

使用 parseFloat() 方法的另一不同之處在於,字串必須以十進位制形式表示浮點數,而不是用八進位制或十六進位制。該方法會忽略前導 0,所以八進位制數 0102 將被解析為 102。對於十六進位制數 0xA,該方法將返回 NaN,因為在浮點數中,x 不是有效字元。

此外,parseFloat() 方法也沒有基模式

強制型別轉換

ECMAScript 中可用的 3 種強制型別轉換如下:

· Boolean(value) - 把給定的值轉換成 Boolean 型;

· Number(value) - 把給定的值轉換成數字(可以是整數或浮點數);

· String(value) - 把給定的值轉換成字串;

強制型別轉換:Boolean()函式

當要轉換的值是至少有一個字元的字串、非 0 數字或物件時,Boolean() 函式將返回 true。如果該值是空字串、數字 0、undefined 或 null,它將返回 false。

可以用下面的程式碼測試 Boolean 型的強制型別轉換:

var b1 = Boolean(""); //false - 空字串

var b2 = Boolean("hello"); //true - 非空字串

var b1 = Boolean(50); //true - 非零數字

var b1 = Boolean(null); //false - null

var b1 = Boolean(0); //false - 零

var b1 = Boolean(new object()); //true - 物件

強制型別轉換:Number()函式

Number() 函式的強制型別轉換與 parseInt() 和 parseFloat() 方法的處理方式相似,只是它轉換的是整個值,而不是部分值。

還記得嗎,parseInt() 和 parseFloat() 方法只轉換第一個無效字元之前的字串,因此 "1.2.3" 將分別被轉換為 "1" 和 "1.2"。

Number() 進行強制型別轉換,"1.2.3" 將返回 NaN,因為整個字串值不能轉換成數字。如果字串值能被完整地轉換,Number() 將判斷是呼叫 parseInt() 方法還是 parseFloat() 方法。

下表說明了對不同的值呼叫 Number() 方法會發生的情況:

用法

結果

Number(false)

0

Number(true)

1

Number(undefined)

NaN

Number(null)

0

Number("1.2")

1.2

Number("12")

12

Number("1.2.3")

NaN

Number(new object())

NaN

Number(50)

50

強制型別轉換:String()函式

String() 是最簡單的,因為它可把任何值轉換成字串。

要執行這種強制型別轉換,只需要呼叫作為引數傳遞進來的值的 toString() 方法,即把 12 轉換成 "12",把 true 轉換成 "true",把 false 轉換成 "false",以此類推。

強制轉換成字串和呼叫 toString() 方法的唯一不同之處在於,對 null 和 undefined 值強制型別轉換可以生成字串而不引發錯誤:

var s1 = String(null); //"null"

var oNull = null;

var s2 = oNull.toString(); //會引發錯誤

ECMAScript引用型別

Object 物件

Object 物件自身用處不大,不過在瞭解其他類之前,還是應該瞭解它。因為 ECMAScript 中的 Object 物件與 Java 中的 java.lang.Object 相似,ECMAScript 中的所有物件都由這個物件繼承而來,Object 物件中的所有屬性和方法都會出現在其他物件中,所以理解了 Object 物件,就可以更好地理解其他物件。

Object 物件具有下列屬性:

constructor

對建立物件的函式的引用(指標)。對於 Object 物件,該指標指向原始的 Object() 函式。

Prototype

對該物件的物件原型的引用。對於所有的物件,它預設返回 Object 物件的一個例項。

Object 物件還具有幾個方法:

hasOwnProperty(property)

判斷物件是否有某個特定的屬性。必須用字串指定該屬性。(例如,o.hasOwnProperty("name"))

IsPrototypeOf(object)

判斷該物件是否為另一個物件的原型。

PropertyIsEnumerable

判斷給定的屬性是否可以用 for...in 語句進行列舉。

ToString()

返回物件的原始字串表示。對於 Object 物件,ECMA-262 沒有定義這個值,所以不同的 ECMAScript 實現具有不同的值。

ValueOf()

返回最適合該物件的原始值。對於許多物件,該方法返回的值都與 ToString() 的返回值相同。

instanceof運算子

在使用 typeof 運算子時採用引用型別儲存值會出現一個問題,無論引用的是什麼型別的物件,它都返回 "object"。ECMAScript 引入了另一個 Java 運算子 instanceof 來解決這個問題。

instanceof 運算子與 typeof 運算子相似,用於識別正在處理的物件的型別。與 typeof 方法不同的是,instanceof 方法要求開發者明確地確認物件為某特定型別。例如:

var oStringObject = new String("hello world");

alert(oStringObject instanceof String); //輸出 "true"

這段程式碼問的是“變數 oStringObject 是否為 String 物件的例項?”oStringObject 的確是 String 物件的例項,因此結果是 "true"。儘管不像 typeof 方法那樣靈活,但是在 typeof 方法返回 "object" 的情況下,instanceof 方法還是很有用的。

ECMAScript面向物件技術

宣告和例項化

Java

JavaScript

new關鍵字

new關鍵字

物件引用

Java

JavaScript

一致

物件銷燬

Java

JavaScript

垃圾回收程式

無用儲存單元收集程式:意味著不必專門銷燬物件來釋放記憶體。當再沒有對物件的引用時,稱該物件被廢除了。執行無用儲存單元收集程式時,所有廢除的物件都被銷燬。每當函式執行完它的程式碼,無用儲存單元收集程式都會執行,釋放所有的區域性變數,還有在一些其他不可預知的情況下,無用儲存單元收集程式也會執行。把物件的所有引用都設定為 null,可以強制性地廢除物件。

物件繫結

所謂繫結(binding),即把物件的介面與物件例項結合在一起的方法。

早繫結(early binding)是指在例項化物件之前定義它的屬性和方法,這樣編譯器或解釋程式就能夠提前轉換機器程式碼。在 Java 和 Visual Basic 這樣的語言中,有了早繫結,就可以在開發環境中使用 IntelliSense(即給開發者提供物件中屬性和方法列表的功能)。ECMAScript 不是強型別語言,所以不支援早繫結。

另一方面,晚繫結(late binding)指的是編譯器或解釋程式在執行前,不知道物件的型別。使用晚繫結,無需檢查物件的型別,只需檢查物件是否支援屬性和方法即可。ECMAScript 中的所有變數都採用晚繫結方法。這樣就允許執行大量的物件操作,而無任何懲罰。

Java

JavaScript

早繫結

晚繫結

物件型別

ECMAScript 中,所有物件並非同等建立的。一般來說,可以建立並使用的物件有三種:本地物件、內建物件和宿主物件。

本地物件:獨立於宿主環境的 ECMAScript 實現提供的物件。簡單來說,本地物件就是 ECMA-262 定義的類(引用型別)。它們包括:Object、Function、Array、String、Boolean、Number、Date、RegExp、Error、EvalError、RangeError、ReferenceError、SyntaxError、TypeError、URIError。

內建物件:由 ECMAScript 實現提供的、獨立於宿主環境的所有物件,在 ECMAScript 程式開始執行時出現。這意味著開發者不必明確例項化內建物件,它已被例項化了。ECMA-262 只定義了兩個內建物件,即 Global 和 Math (它們也是本地物件,根據定義,每個內建物件都是本地物件)。

宿主物件:所有非本地物件都是宿主物件(host object),即由 ECMAScript 實現的宿主環境提供的物件。所有 BOM 和 DOM 物件都是宿主物件。

作用域

Java

JavaScript

public

ECMAScript 中只存在一種作用域 - 公用作用域。ECMAScript 中的所有物件的所有屬性和方法都是公用的。(通俗來講就是public)

private

protected

預設

靜態作用域

Java

JavaScript

static修飾符

嚴格來說,ECMAScript 並沒有靜態作用域。不過,它可以給建構函式提供屬性和方法。還記得嗎,建構函式只是函式。函式是物件,物件可以有屬性和方法。通俗來說,雖然JS沒有靜態作用域的,但可是實現靜態作用域的效果。

this關鍵字

Java

JavaScript

this:代表當前物件

this: 總是指向呼叫該方法的物件,注意引用物件的屬性時,必須使用 this 關鍵字。如果不用物件或 this 關鍵字引用變數,ECMAScript 就會把它看作區域性變數或全域性變數。然後該函式將查詢名為 color 的區域性或全域性變數,但是不會找到。結果如何呢?該函式將在警告中顯示 null。

修改物件

Java

JavaScript

當Class檔案建立完畢之後,只能對屬性值進行修改,無法新增屬性、方法。

通過使用 ECMAScript,不僅可以建立物件,還可以修改已有物件的行為。

prototype 屬性不僅可以定義建構函式的屬性和方法,還可以為本地物件新增屬性和方法。

通俗來講就是,即便物件/類建立完畢,我也可以新增屬性,新能方法,修改現有屬性型別,修改現有方法物件,設定還可以修改Object物件的屬性和方法。

繼承

Java

JavaScript

extends關鍵字

ECMAScript實現繼承的方式不止一種。這是因為 JS中的繼承機制並不是明確規定的,而是通過模仿實現的。這意味著所有的繼承細節並非完全由解釋程式處理。

通俗來講,就是JS沒有繼承這一說法而是通過模仿來實現,這就意味著所有的細節都要開發人員自己來處理

繼承方式一:物件冒充

原理如下:建構函式使用 this 關鍵字給所有屬性和方法賦值(即採用類宣告的建構函式方式)。因為建構函式只是一個函式,所以可使 ClassA 建構函式成為 ClassB 中的一個方法,然後呼叫ClassA的建構函式。ClassB 就會收到 ClassA 的建構函式中定義的屬性和方法。例如,用下面的方式定義 ClassA 和 ClassB:

function ClassA(sColor) {

    this.color = sColor;

    this.sayColor = function () {

        alert(this.color);

    };

}

所有新屬性和新方法都必須在刪除了新方法的程式碼行後定義。否則,可能會覆蓋超類的相關屬性和方法:

function ClassB(sColor, sName) {

    this.newMethod = ClassA;

    this.newMethod(sColor);

    delete this.newMethod;

    this.name = sName;

    this.sayName = function () {

        alert(this.name);

    };

}

解釋:當執行到this.newMethod(sColor);行程式碼時,其實執行的是ClassA構造方法,但ClassA構造方法中的this總是指向呼叫該方法的物件(就是這裡的ClassB),這樣ClassB就獲取到了ClassA所有的屬性、方法,然後delete刪除引用後,sayName就是一個新增的方法,與ClassA中的sayName沒有任何關係。

物件冒充可以支援多重繼承。

缺點:在程式碼書寫中,後面物件會覆蓋前面物件的同名屬性和同名方法,例如:在ClassB中首先持有ClassX,然後持有ClassY,如果存在兩個類 ClassX 和 ClassY 具有同名的屬性或方法,ClassY 會覆蓋掉ClassX中同名的屬性或方法。因為它從後面的類繼承。

繼承方式二:call()方法

原理:ECMAScript 的第三版為 Function 物件加入了兩個方法,即 call() 和 apply()。

舉例:

function sayColor(sPrefix,sSuffix) {

    alert(sPrefix + this.color + sSuffix);

};

var obj = new Object();

obj.color = "blue";

sayColor.call(obj, "The color is ", "a very nice color indeed.");

在這個例子中,函式 sayColor() 在物件外定義,即使它不屬於任何物件,也可以引用關鍵字 this。物件 obj 的 color 屬性等於 blue。呼叫 call() 方法時,第一個引數是 obj,說明應該賦予 sayColor() 函式中的 this 關鍵字值是 obj。第二個和第三個引數是字串。它們與 sayColor() 函式中的引數 sPrefix 和 sSuffix 匹配,最後生成的訊息 "The color is blue, a very nice color indeed." 將被顯示出來。

Call()方法就是呼叫物件的構造方法,第一個引數就是子類物件,其他引數就是父類構造方法中的引數。

繼承方式三:apply() 方法

apply() 方法有兩個引數,用作 this 的物件和要傳遞給函式的引數的陣列。例如:

function sayColor(sPrefix,sSuffix) {

    alert(sPrefix + this.color + sSuffix);

};

var obj = new Object();

obj.color = "blue";

sayColor.apply(obj, new Array("The color is ", "a very nice color indeed."));

這個例子與前面的例子相同,只是現在呼叫的是 apply() 方法。呼叫 apply() 方法時,第一個引數仍是 obj,說明應該賦予 sayColor() 函式中的 this 關鍵字值是 obj。第二個引數是由兩個字串構成的陣列,與 sayColor() 函式中的引數 sPrefix 和 sSuffix 匹配,最後生成的訊息仍是 "The color is blue, a very nice color indeed.",將被顯示出來。

該方法也用於替換前三行的賦值、呼叫和刪除新方法的程式碼:

function ClassB(sColor, sName) {

    //this.newMethod = ClassA;

    //this.newMethod(color);

    //delete this.newMethod;

    ClassA.apply(this, new Array(sColor));

    this.name = sName;

    this.sayName = function () {

        alert(this.name);

    };

}

同樣的,第一個引數仍是 this,第二個引數是隻有一個值 color 的陣列。可以把 ClassB 的整個 arguments 物件作為第二個引數傳遞給 apply() 方法:

function ClassB(sColor, sName) {

    //this.newMethod = ClassA;

    //this.newMethod(color);

    //delete this.newMethod;

    ClassA.apply(this, arguments);

    this.name = sName;

    this.sayName = function () {

        alert(this.name);

    };

}

有超類中的引數順序與子類中的引數順序完全一致時才可以傳遞引數物件。如果不是,就必須建立一個單獨的陣列,按照正確的順序放置引數。此外,還可使用 call() 方法。

繼承方式四:原型鏈

繼承這種形式在 ECMAScript 中原本是用於原型鏈的。上面介紹了定義類的原型方式。原型鏈擴充套件了這種方式,以一種有趣的方式實現繼承機制。

prototype 物件是個模板,要例項化的物件都以這個模板為基礎。總而言之,prototype 物件的任何屬性和方法都被傳遞給那個類的所有例項。原型鏈利用這種功能來實現繼承機制。

如果用原型方式重定義前面例子中的類,它們將變為下列形式:

function ClassA() {

}

ClassA.prototype.color = "blue";

ClassA.prototype.sayColor = function () {

    alert(this.color);

};

function ClassB() {

}

ClassB.prototype = new ClassA();

原型方式的神奇之處在於突出顯示的藍色程式碼行。這裡,把 ClassB 的 prototype 屬性設定成 ClassA 的例項。這很有意思,因為想要 ClassA 的所有屬性和方法,還有比把 ClassA 的例項賦予 prototype 屬性更好的方法嗎?

注意:呼叫 ClassA 的建構函式,沒有給它傳遞引數。這在原型鏈中是標準做法。要確保建構函式沒有任何引數。

與物件冒充相似,子類的所有屬性和方法都必須出現在 prototype 屬性被賦值後,因為在它之前賦值的所有方法都會被刪除。為什麼?因為 prototype 屬性被替換成了新物件,添加了新方法的原始物件將被銷燬。所以,為 ClassB 類新增 name 屬性和 sayName() 方法的程式碼如下:

function ClassB() {

}

ClassB.prototype = new ClassA();

ClassB.prototype.name = "";

ClassB.prototype.sayName = function () {

    alert(this.name);

};

通俗來講:通過prototype屬性更新了模板,這就意味著所有通過老模板建立的物件全部銷戶,重新依據新模板來建立物件,所以,必須在呼叫prototype方法之後再新增屬性、方法。

繼承方式五:混合方式

通俗來講就是:call/apply + 原型鏈

示例:

在上面,我們曾經講解過建立類的最好方式是用建構函式定義屬性,用原型定義方法。這種方式同樣適用於繼承機制,用物件冒充繼承建構函式的屬性,用原型鏈繼承 prototype 物件的方法。用這兩種方式重寫前面的例子,程式碼如下:

function ClassA(sColor) {

    this.color = sColor;

}

ClassA.prototype.sayColor = function () {

    alert(this.color);

};

function ClassB(sColor, sName) {

    ClassA.call(this, sColor);

    this.name = sName;

}

ClassB.prototype = new ClassA();

ClassB.prototype.sayName = function () {

    alert(this.name);

};

在此例子中,繼承機制由兩行突出顯示的藍色程式碼實現。在第一行突出顯示的程式碼中,在 ClassB 建構函式中,用物件冒充繼承 ClassA 類的 sColor 屬性。在第二行突出顯示的程式碼中,用原型鏈繼承 ClassA 類的方法。由於這種混合方式使用了原型鏈,所以 instanceof 運算子仍能正確執行。(這裡,我有問題一直沒想明白,那麼就是ClassB的例項呼叫sayName時,怎麼不提示underfind呢?你都ClassB.prototype = new ClassA();了,後來我想明白了,繼承,是繼承屬性和方法,繼承不能把子類的建構函式給構造沒了,也就說,ClassB.prototype = new ClassA();對ClassB的建構函式沒有影響)