1. 程式人生 > >《JavaScript高階程式設計》筆記第一部分(ECMA)

《JavaScript高階程式設計》筆記第一部分(ECMA)

JavaScript分三部分:

ECMAScript BOM DOM

瀏覽器組成:

shell 核心

主流瀏覽器:

IE				trident
Chrome			webkit/blink
firefox			Gecko
Opera			presto
Safari			webkit

編譯性語言:看完再一次性編譯成01的編譯檔案 優點:快 不足:移植性不好(不跨平臺)

解釋性語言:看一行編譯一行,不需要生成執行檔案 優點:跨平臺 不足:稍微慢

JavaScript引擎是單執行緒的 ECMA制定JavaScript標準

原始值 stack(棧) Number String Boolean undefined null 拷貝關係

引用值 heap(堆) Array function Object data RegExp 棧存放地址,堆存放資料

在HTML中使用JavaScript:

script元素屬性:

  • async(可選):表示應該立即下載指令碼,但不應妨礙頁面中的其他操作。
  • charset(可選):表示通過src屬性指定的程式碼的字符集。
  • defer(可選):表示指令碼可以延遲到文件完全被解析和顯示之後再執行。
  • language(已廢棄):原來用於表示編寫程式碼使用的指令碼語言(如JavaScript、JavaScript1.2或VBScript)。
  • src(可選):表示包含要執行程式碼的外部檔案。 type(可選):可以看成是language的替代屬性。

值:text/javascript和text/ecmascript(不被推薦使用) 伺服器在傳送JavaScript檔案時使用的MIME型別通常是application/x-javascript

延遲指令碼:defer屬性:defer=“defer” 告訴瀏覽器立即下載,但延遲執行。 非同步指令碼:async屬性:async 只適用於外部指令碼檔案,並告訴瀏覽器立即下載檔案。標記位async的指令碼並不保證按照指定他們的先後順序執行。 建議非同步指令碼不要在載入期間修改DOM。 元素:在瀏覽器不支援JavaScript時顯示的一段資訊或內容。

基本概念要點

語法

類似C語言和Java。

區分大小寫

ECMAScript中的一切(變數、函式名和操作符)都區分大小寫。

資料型別

可以使用typeof操作符得知

  • undefined 這個值未定義
  • boolean 這個值是布林值
  • number 這個值是數值
  • object 這個值是物件或null
  • function 這個值是函式
  • String 這個值是字串

操作符

一元操作符

遞增遞減操作符 ++ – 一元加和減操作符

+ -

位操作符

按位非(NOT):~ 按位與(AND):& 按位或(OR):| 按位異或(XOR):^ 左移:<< 有符號右移:>> 無符號右移:>>>

布林操作符

邏輯非:! 邏輯與:&& 邏輯或:||

乘性操作符

乘法:* 無窮:Infinity; Infinity * 0 = NaN; 有一個運算元是NaN,結果是NaN; Infinity * Infinity = Infinity;

除法:/ 有一個運算元是NAN,結果是NaN; Infinity / Infinity = NaN; 0 / 0 = NaN;

求模(餘數):% 被除數是Infinity,除數是有限大的數,結果是NaN; 除數是0,被除數是有限大,結果NaN; Infinity / Infinity = NaN;

加性操作符 加法:+ 一個運算元是NaN,結果是NaN; Infinity + Infinity = Infinity; -Infinity + (-Infinity) = -Infinity; -Infinity + Infinity = NaN; +0 + (+0) = (+0); -0 + (-0) = (-0); +0 + (-0) = (+0);

減法:- Infinity - Infinity = NaN; -Infinity - (-Infinity) = NaN; +0 - (+0) = (+0); -0 - (-0) = (+0);

關係操作符

小於(<)、大於(>)、小於等於(<=)、大於等於(>=)

相等操作符

相等(==)和不相等(!=)

值相等,會自動轉換: 布林值<——數值<——字串 null == undefined;

全等(===)和不全等(!==)

型別和值都相同,是否是同一個引用。 條件操作符:? : ; 賦值操作符:=

*=
/=
%=
+=
-=
<<=
>>=
>>>=

語句

if語句 do-while語句 while語句 for語句 for-in語句 ECMAScript物件的屬性沒有順序,因此,通過for-in迴圈輸出的屬性名的順序是不可預測的。 label語句 使用label語句可以在程式碼中新增標籤,以便將來使用。 語法:label:statement;

start:for (var i=0; i < count; i++){
    alert(i);
}

break和continue語句

with語句

將程式碼的作用域設定到一個特定的物件中。 語法:with (expression) statement;

with(location){
    var qs = search.substring(1);
    var hostName = hostname;
    var url = href;
}

嚴格模式下不允許使用with語句,否則將視為語法錯誤。 switch語句

可以在switch語句中使用任何資料型別。

函式

function 定義引數的數量和使用函式時傳入的引數的數量可以不一樣。 命名的引數只提供便利,但不是必需的。 通過訪問argument物件的length屬性可以獲知有多少個引數傳遞給了函式。 argument的值永遠與對應命名引數的值保持同步。 沒有過載

基本型別

Undefined

Null

Boolean

Number

String

動態的屬性

定義基本型別值和引用型別值的方式是類似的。 引用型別的值。可以新增屬性和方法,基本型別的值則不可以。

複製變數值

在變數物件上建立一個新值,然後把該值複製到為新變數分配的位置上。(相當於建立了一個副本)。 基本型別資料存在棧(steak)裡面,引用資料地址存在棧(steak)裡面,object內容存在堆(hook)裡面,所以多個引用指向同一個object。

傳遞引數

ECMAScript中所有函式的引數都是按值傳遞的(基本型別複製模式)。 訪問變數有按值和按引用兩種方式,而引數只能按值傳遞。

function addTen(num){
    num += 10;
    return num;
}
var count = 20;
var result = addTen(count);
alert(count);       //20,沒有變化
alert(result);      //30
    function setName(obj){
        obj.name = "Nicholas";
    }
    var person = new Object();
    setName(person);
    alert(person.name);     //"Nicholas"
function setName(obj){
    obj.name = "Nicholas";
    obj = new Object();
    obj.name = "Greg";
}
var person = new Object();
setName(person);
slert(person.name);     //"Nicholas"

instanceof

確認物件是什麼型別的物件。

執行環境及作用域

全域性執行環境是最外圍的一個執行環境 當代碼在一個環境中執行時,會建立變數物件的一個作用域鏈。 作用域鏈的用途:保證對執行環境有權訪問的所有變數和函式的有序訪問。 全域性執行環境的變數物件始終都是作用域鏈中的最後一個物件。 每個函式都有自己的執行環境。 在區域性作用域中定義的變數可以在區域性環境中與全域性變數互換使用。

延長作用域鏈

當執行流進入下列任何一個語句時,作用域鏈就會得到加長:(在作用域鏈前端臨時增加一個變數物件,該變數物件會在程式碼執行後被移除) 1.try-catch語句的catch塊; 2.with語句。 JavaScript沒有塊級作用域

if(true){
    var color = "blue";
}
alert(color);       //"blue"
    for(var i=0; i < 10; i++){
        doSomething(i);
    }
    alert(i);           //10

宣告變數

如果初始化變數時沒有使用var宣告,該變數會自動被新增到全域性環境。 查詢識別符號

var color = "blue";
function getColor(){
    return color;
}
alert(getColor());  //"blue"

垃圾收集

JavaScript具有自動垃圾收集機制,執行環境會負責管理程式碼執行過程中使用的記憶體。

標記清除

JavaScript中最常用的垃圾收集方式是標記清除。

當變數進入環境時,就將這個變數標記為”進入環境“。當變數離開環境時,就將其標記為”離開環境“。 另一種不太常見的垃圾收集策略叫做引用計數。

引用計數的含義是跟蹤記錄每個值被引用的次數。 不足:迴圈引用的存在導致物件永遠存在。

myObject.element = null;
element.someObject = null;

管理記憶體

分配給Web瀏覽器的可用記憶體數量通常要比分配給桌面應用程式的少。 一旦資料不再有用,最好通過將其值設定為null來釋放其引用——這個做法叫做解除引用

引用型別

1.Object型別

var person = new Object();
person.name = "Nicholas";
person.age = 29;
    var person = {
        name : "Nicholas",
        "age" : 29,
        5 : true
    };
var person = {};
person.name = "Nicholas";
person.age = 29;

兩種呼叫屬性值的方法:

person["name"];
person.name;

1.通過方括號語法的主要優點是可以通過變數來訪問屬性

var propertyName = "name";
alert(person[propertyName]);

2.屬性名中包含導致語法錯誤的字元,或者屬性名使用的是關鍵字或保留字:

person["first name"] = "Nicholas";

通常,除非必須使用變數來訪問屬性,否則建議使用點表示法。

Array型別

ECMAScript陣列的大小是可以動態調整的。 在使用Array建構函式時也可以省略new操作符。 陣列的項數儲存在其length屬性中。 length屬性不是隻讀的。

檢測陣列

Array.isArray()方法

轉換方法

所有物件都具有toLocaleString()、toString()和valueOf()方法。 使用join()方法,則可以使用不同的分隔符來構建這個字串: join()方法只接收一個引數,即用作分隔符的字串。

var colors = ["red","green","blue"];
alert(colors.join(","));    //red,green,blue
alert(colors.join("||"));   //red||green||blue

棧方法

LIFO(Last-In-First-Out,後進先出) 棧中項的插入(叫做推入)和移除(叫做彈出),只發生在一個位置——棧的頂部。 ECMAScript為陣列專門提供了push()和pop()方法,以實現類似棧的行為。

佇列方法

佇列資料結構的訪問規則是FIFO(First-In-First-Out,先進先出)。 結合使用shift()和push()方法,可以像佇列一樣使用陣列。

四個方法

push()方法:接收任意數量的引數,把它們逐個新增到陣列末尾,並返回修改後陣列的長度。 pop()方法:從陣列末尾移除最後一項,減少陣列的length值,然後返回移除的項。 shift()方法:從陣列中取得第一項並返回該項,同時將陣列長度減1. unshift()方法:在陣列前端新增任意個項並返回新陣列的長度。

反方向佇列

使用unshift()與pop()方法。

重排序方法

reverse()和sort()方法: reverse()方法:反轉陣列項的順序。 sort()方法: 預設:按升序排列陣列項。會呼叫每個陣列項的toString()轉型方法,然後比較得到的字串,以確定如何排序。 sort()方法可以接收一個比較函式作為引數,以便外面指定哪個值位於哪個值的前面。 升序的方法

function compare(value1,value2){
    if(value1 < value2){
        return -1;
    } else if(value1 > value2){
        return 1;
    } else {
        return 0;
    }
}
var values = [0,1,5,10,15];
values.sort(compare);
alert(values);      //0,1,5,10,15

降序

function compare(value1,value2){
    if(value1 < value2){
        return 1;
    } else if(value1 > value2){
        return -1;
    } else {
        return 0;
    }
}

reverse()和sort()方法的返回值是經過排序之後的陣列。

操作方法

ECMAScript為操作已經包含在陣列中的項提供了很多方法。 concat()方法:可以基於當前陣列中的所有項建立一個新陣列。 slice()方法:能夠基於當前陣列中的一或多個項建立一個數組。 只有一個引數: 返回從該引數指定位置開始到當前陣列末尾的所有項。

兩個引數 該方法返回起始和結束位置之間的項——但不包括結束位置的項。 splice()方法(最強大的陣列方法): 主要用途是向陣列的中部插入項。3種方式:

刪除 可以刪除任意數量的項,只需指定2個引數: 要刪除的第一項的位置和要刪除的項數。

插入 可以向指定位置插入任意數量的項,只需3個引數: 起始位置、0(要刪除的項數)、要插入的項 要插入多個項,可以再傳入第四、第五、第六,以至任意多個項。

替換 可以向指定位置插入任意數量的項,且同時刪除任意數量的項,只需指定3個引數: 起始位置、要刪除的項數和要插入的任意數量的項。 插入的項數不必與刪除的項數相等。

位置方法

indexOf()和lastIndexOf() 使用全等操作符

迭代方法

ECMAScript5為陣列定義了5個迭代方法,每個方法都接收兩個引數: 1.要在每一項上執行的函式 2.執行該函式的作用域物件——影響this的值。(可選的) 傳入這些方法中的函式會接收三個引數: 1.陣列項的值 2.該項在陣列中的位置 3.陣列物件本身

5個方法

every():對陣列中的每一項執行給定函式。如果該函式對每一項都返回true,則返回true。 filter():對陣列中的每一項執行給定函式,返回該函式會返回true的項組成的陣列。 forEach():對陣列中的每一項執行給定函式。這個方法沒有返回值。 map():對陣列中的每一項執行給定函式,返回每次函式呼叫的結果組成的陣列。 some():對陣列中的每一項執行給定函式,如果該函式對任一項返回true,則返回true。

歸併方法

reduce()和reduceRight() 這兩個方法都會迭代陣列的所有項,然後構建一個最終返回的值。 reduce()方法:從陣列的第一項開始,逐個遍歷到最後。 reduceRight()方法:從陣列的最後一個開始,向前遍歷到第一項。 接收兩個引數: 1.在每一項上呼叫的函式 2.作為歸併基礎的初始值(可選的) 函式接收4個引數: 1.前一個值 2.當前值 3.項的索引 4.陣列物件 這個函式返回的任何值都會作為第一個引數自動傳給下一項。

var values = [1,2,3,4,5];
var sum = values.reduce(function(prev,cur,index,array){
    return prev + cur;
});
alert(sum);     //15

Date型別

Data型別的兩個方法: Data.parse()

var someData = new Date(Date.parse("May 25,2004"));

等價於

var someDate = new Date("May 25,2004");

Data.UTC()

var y2k = new Date(Date.UTC(2000,0));
//表示GMT時間2000年1月1日午夜零時
var y2k = new Date(2000,0);
//本地時間2000年1月1日午夜零時
var allFives = new Date(Date.UTC(2005,4,5,17,55,55));
//表示GMT時間2005年5月5日下午5:55:55
var allFives = new Date(2005,4,5,17,55,55);
//本地時間2005年5月5日下午5:55:55

ECMAScript5添加了Date.now()方法。

使用+操作符獲取Date物件的時間戳
//獲得開始時間
var start = +new Date();
//呼叫函式
doSomething();
//取得停止時間
var stop = +new Date();
    result = top - start;

日期格式化方法:

toDateString() toTimeString() toLocaleDateString() toLocaleTimeString() toUTCString()

RegExp型別

ECMAScript通過RegExp型別來支援正則表示式。

var expression = / pattern / flags;

三種模式:g、i、m g:表示全域性(global)模式。 模式將被應用於所有字串,而非在發現第一個匹配項時立即停止。 i:表示不區分大小寫(ease-insensitive)模式。 在確定匹配項時忽略模式與字串的大小寫。 m:表示多行(multiline)模式。 在到達一行文字末尾時還會繼續查詢下一行中是否存在與模式匹配的項。

/*
 *匹配字串中所有“at”的例項
 */
var pattern1 = /at/g;

/*
 *匹配第一個“bat”或“cat”,不區分大小寫
 */
var pattern2 = /[ba]at/i;

/*
 *匹配所有以“at”結尾的3個字元的組合,不區分大小寫
 */
var pattern3 = /.at/gi;

正則表示式中的元字元:( [ { \ ^ $ | ) ? * + . ] }

/*
 *匹配第一個“[bc]at”,不區分大小寫
 */
var pattern2 = /\[bc\]at/i;
/*
 *匹配所有“.at”,不區分大小寫
 */
var pattern4 = /\.at/gi;

另一種建立正則表示式的方式是使用RegExp建構函式。

/*
 *匹配第一個"bat"或"cat",不區分大小寫
 */
var pattern1 = /[bc]at/i;

/*
 *與pattern1相同,只不過是使用建構函式建立的
 */
var pattern2 = new RegExp("[bc]at","i");

所有元字元都必須雙重轉義。 ECMAScript5明確規定,使用正則表示式字面量必須像直接呼叫RegExp建構函式一樣,每次都建立新的RegExp例項。

RegExp例項屬性

global:布林值,表示是否設定了g標誌。 ignoreCase:布林值,表示是否設定了i標誌。 lastIndex:整數,表示開始搜尋下一個匹配項的字元位置,從0算起。 multiline:布林值,表示是否設定了m標誌。 source:正則表示式的字串表示,按字面量形式而非傳入建構函式中的字串模式返回。

RegExp例項方法

exec():該方法專門為捕捉組而設計的。 接收一個引數:要應用模式的字串。 返回包含第一個匹配項資訊的陣列,沒有匹配項的情況下返回null。 返回的Array例項包含兩個額外屬性:index和input。 input:表示應用正則表示式的字串。

var text = "mom and dad and bady";
var pattern = /mom( and dad ( and bady)?)?/gi;

var matches = pattern.exec(text);
alert(matches.index);    //0
alert(matches.input);    //"mom and dad and bady"
alert(matches[0]);       //"mam and dad and bady"
alert(matches[1]);       //" and dad and bady"
alert(matches[2]);       //" and bady"

因為整個字串本身與模式匹配,所以返回的陣列matches的index屬性值為0. 在同一個字串上多次呼叫exec()將始終返回第一個匹配項的資訊。 在設定全域性標誌的情況下,每次呼叫exec()則都會在字串中繼續查詢新的匹配項。 RegExp例項繼承的toLocaleString()和toString()方法都會返回正則表示式的字面量,與建立正則表示式的方式無關。

RegExp建構函式屬性

input:lastMatch_ 最近一次要匹配的字串。 lastMatch:& 最近一次的匹配項。 lastParen:+leftContext+ 最近一次匹配的捕獲組。 leftContext:ˋ input字串中lastMatch之前的文字。 multiline:使rightContext* 布林值,表示是否所有表示式都使用多行模式。 rightContext:’ Input字串中lastMatch之後的文字。 9個用於儲存捕獲組的建構函式屬性:$1、$2、$3…$9

Function型別

函式實際上是物件。每個函式都是Function型別的例項,而且都與其他引用型別一樣具有屬性和方法。 函式是物件,函式名是指標。

沒有過載

將函式名想象為指標,也有助於理解為什麼ECMAScript中沒有函式過載的概念。

函式宣告與函式表示式

解析器在向執行環境中載入資料時,對函式宣告和函式表示式並非一視同仁。 函式宣告提升(function declaration hoisting):讀取並將函式宣告新增到執行環境中。

作為值的函式

函式可以作為值來使用。 可以將一個函式作為另一個函式的結果返回。 可以從一個函式中返回另一個函式。

函式內部屬性

在函式內部,有兩個特殊的物件:argument和this。 argument:類陣列物件,包含這傳入函式中的所有引數。有一個名為callee的屬性,該屬性是一個指標,指向擁有這個argument物件的函式。

例項: 實現階乘(消除緊密耦合的現象)

function factorial(num){
    if(num <= 1){
        return 1;
    } else {
        return num * arguments.callee(num-1);
    }
}

this引用的是函式執行環境物件——或者也可以說是this值(當在網頁的全域性作用域中呼叫函式時,this物件引用的就是window)。 ECMAScript5也規範化了另一個函式物件的屬性:caller。 這個屬性中儲存著呼叫當前函式的函式的引用,如果是在全域性作用域中呼叫當前函式,它的值為null。 為了實現更鬆散的耦合,也可以通過arguments.callee.caller來訪問相同的資訊。

函式屬性和方法

每個函式都包含兩個屬性:length和prototype。 length屬性:函式希望接收的命名引數的個數。 對於ECMAScript中的引用型別而言,prototype屬性是儲存它們所有例項方法的真正所在。 在ECMAScript5中,prototype屬性是不可列舉的,因此使用for-in無法發現。

每個函式都包含兩個非繼承而來的方法:apply()和call() 用途:都是在特定的作用域中呼叫函式,實際上等於設定函式體內this物件的值。 apply()方法接收兩個引數: 1.在其中執行函式的作用域 2.引數陣列(可以說Array的例項或arguments物件) call()方法接收引數的方式: 1.this值 2.其餘引數都直接傳遞給函式。 在使用call()方法的情況下,函式必須明確地傳入每一個引數。 這個兩個方法能夠擴充函式賴以執行的作用域。 使用call()(或apply())來擴充作用域的最大好處,就是物件不需要與方法有任何耦合關係。 ECMAScript5還定義了一個方法:bind() 這個方法會建立一個函式的例項,其this值會被繫結到傳給bind()函式的值。

基本包裝型別

3個特殊的引用型別:Boolean、Number和String。 每當讀取一個基本型別值的時候,後臺就會建立一個對應的基本包裝型別的物件,從而讓我們能夠呼叫一些方法來操作這些資料。

var s1 = "some text";
var s2 = s1.substring(2);

(1)建立String型別的一個例項; (2)在例項上呼叫指定的方法; (3)銷燬這個例項。

var s1 = new String("some text");
var s2 = s1.substring(2);
s1 = null;

引用型別與基本包裝型別的主要區別就是物件的生存期。

var s1 = "some text";
s1.color = "red";
alert(s1.color);    //undefined

對基本包裝型別的例項呼叫typeof會返回"object",而且所有基本包裝型別的物件在轉換為布林型別時指都是true。

面對物件的程式設計

ECMA-262對物件的定義: 無序屬性的集合,其屬性可以包含基本值、物件或者函式。

1.理解物件

建立自定義物件的最簡單方式就是建立一個Object的例項,然後再為它新增屬性和方法。 面向物件的程式設計 ECMA-262對物件的定義: 無序屬性的集合,其屬性可以包含基本值、物件或者函式。

var person = new Object();
person.name = "Nicholas";
person.age = 29;
person.job = "Software Engineer";
person.sayName = function(){
 	alert(this.name);
};

用物件字面量語法:

var person = {
 	name: "Nicholas";
 	age: 29;
 	job: "Software Engineer";
 	sayName: function(){
 		alert(this.name);
 	}
};

屬性型別

ECMAScript中有兩種屬性:資料屬性和訪問器屬性。 1.資料屬性 資料屬性包含一個數據值的位置。在這個位置可以讀取和寫入值。資料屬性有4個描述其行為的特性。 [[Configurable]]、[[Enumerable]]、[[Writable]]、[[Value]]。 要修改屬性預設的特性,必須使用ECMAScript5的Object.defineProperty()方法。 這個方法接收三個引數: 屬性所在的物件、 屬性的名字、 一個描述符物件。 描述符物件的屬性必須是:configurable、enumerable、writable和value。

var person = {};
Object.defineProperty(person,"name",{
 	writable: false,
 	value: "Nicholas";
});
alert(person.name);	//"Nicholas"
person.name = "Greg";
alert(person.name);	//"Nicholas"

一旦把屬性定義為不可配置的,就不能再把它變回可配置了。

2.訪問器屬性 訪問器屬性不包含資料值;它們包含了一對兒getter和setter函式。 訪問器屬性4個特性:[[Configurable]]、[[Enumerable]]、[[Get]]、[[Set]]。 訪問器屬性不能直接定義,必須使用Object.defineProperty()來定義。

var book = {
 	_year: 2004;
 	edition: 1;
};
Object.defineProperty(book,"year",{
 	get: function(){
 		return this._year;
 	},
 	set: function(newValue){
 		if(newValue > 2004){
 			this._year = newValue;
 			this.edition += newValue - 2004;
 		}
 	}
});
book.year = 2005;
alert(book.edition);	//2

_year前面的下劃線是一種常用的記號,用來表示只能通過物件方法訪問的屬性。 在defineProperty()方法之前,要建立訪問器屬性的兩個方法:defineGetter()和__defineSetter__()。

var book = {
 	_year: 2004;
 	edition: 1;
};
//定義訪問器的舊有方法
book.__defineGetter__("year",function(){
 	return this._year;
});
book.__defineSetter__("year",function(){
 	if(newValue > 2004) {
 		this._year = newValue;
 		this.edition += newValue - 2004;
 	}
});
book.year = 2005;
alert(book.edition);	//2

定義多個屬性

Object.defineProperties()方法。

var book = {};
Object.defineProperties(book,{
 	_year: {
 		writable: true,
 		value: 2004
 	},
 	edition: {
 		writable: ture;
 		value: 1;
 	},
 	year: {
 		get: function(){
 			return this._year;
 		},
 		set: function(newValue){
 			if (newValue > 2004){
 				this._year = newValue;
 				this.edition += newValue - 2004;
 			}
 		}
 	}
});

這裡的屬性都是在同一時間建立的。

3.讀取屬性的特性 Object.getOwnPropertyDescriptor()方法。返回值是一個物件。 兩個引數: 1.屬性所在的物件, 2.要讀取其描述符的屬性名稱。

var descriptor = Object.getOwnPropertyDescriptor(book,"_year");
alert(descriptor.value);		//2004
alert(descriptor.configurable);		//false;
alert(typeof descriptor.get);		//"undefined"
var descriptor = Object.getOwnPropertyDescriptor(book,"year");
alert(descriptor.value);		//undefined
alert(descriptor.enumberable);		//false
alert(typeof descriptor.get);		//"function"

在JavaScript中,可以針對任何物件——包括DOM和BOM物件,使用Object.getOwnPropertyDescriptor()方法。

2.建立物件

Object建構函式或物件字面量建立單個物件的缺點: 使用同一個介面建立很多物件,會產生大量的重複程式碼。 1.工廠模式 ECMAScript中無法建立類:開發人員發明一種函式,用函式來封裝以特定介面建立物件的細節。

function createPerson(name,age,job){
 	var o = new Object();
 	o.name = name;
 	o.age = age;
 	o.job = job;
 	o.sayName = function(){
 		alert(this.name);
 	};
 	return o;
}
var person1 = createPerson("Nicholas",29,"Software Engineer");
var person2 = createPerson("Grey",27,"Doctor");

2.建構函式模式 建立自定義的建構函式,從而定義自定義物件型別的屬性和方法。

function Person(name, age, job){
 	this.name = name;
 	this.age = age;
 	this.job = job;
 	this.sayName = function(){
 		alert(this.name);
 	};
}
var person1 = new Person("Nicholas", 29 , "Software Engineer");
var person2 = new Person("Grey",27,"Doctor");

Person()函式與createPerson()函式不同: 1.沒有顯式地建立物件; 2.直接將屬性和方法賦給了this物件; 3.沒有return語句。

要建立Person的新例項,必須使用new操作符。以這種方式呼叫建構函式實際上會經歷以下4個步驟: 1.建立一個新物件; 2.將建構函式的作用域賦給新物件(因此this就指向了這個新物件); 3.執行建構函式中的程式碼(為這個新物件新增屬性); 4.返回新物件。

例項的constructor屬性:指向Person。 建構函式模式勝過工廠模式的地方: 建立自定義的建構函式意味著將來可以將它的例項標識為一種特定的型別。

1.將建構函式當作函式 任何函式,只要通過new操作符來呼叫,那它就可以作為建構函式。

//當作建構函式使用
var person = new Person("Nicholas", 29 , "Software Engineer");
person.sayName();	//"Nicholas"
//作為普通函式呼叫
Person("Grey",27,"Doctor"); 		//新增到window
window.sayName(); 			//"Grey"
//在另一個物件的作用域中呼叫
var o = new Object();
Person.call(o, "Kristen", 25, "Nurse");
o.sayName(); 			//"Kristen"

2.建構函式的問題 每個方法都要在每個例項上重新建立一遍。

function Person(name, age, job){
 	this.name = name;
 	this.age = age;
 	this.job = job;
 	this.sayName = new Function("alert(this.name)"); 
 	//與宣告函式在邏輯上是等價的
}

導致不同的作用域鏈和識別符號解析,但建立Function新例項的機制仍然是相同的。 (不同例項上的同名函式是不相等的)

把函式定義轉移到建構函式外部來解決這個問題

function Person(name, age, job){
 	this.name = name;
 	this.age = age;
 	this.job = job;
 	this.sayName = sayName;
}
function sayName(){
 	alert(this.name);
 }
var person1 = new Person("Nicholas", 29 , "Software Engineer");
var person2 = new Person("Grey",27,"Doctor");

新問題: 1.在全域性作用域中定義的函式實際上只能被某個物件呼叫,這讓全域性作用域有點名不副實。 2.如果物件需要定義很多方法,那麼就要定義很多個全域性函式,於是我們這個自定義的引用型別就絲毫沒有封裝性可言了。

3.原型模式 建立的每個函式都有一個prototype(原型)屬性,這個屬性是一個指標,指向一個物件,而這個物件的用途是包含可以由特定型別的所有例項共享的屬性和方法。

function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
 	alert(this.name);
};
var person1 = new Person();
person1.sayName(); 		//"Nicholas"
var person2 = new Person();
person2.sayName(); 		//"Nicholas"
alert(person1.sayName == person2.sayName);		//true

使用同一組屬性和同一個sayName()函式。

1.理解原型物件 在預設情況下,所有原型物件都會自動獲得一個constructor(建構函式)屬性,這個屬性是一個指向prototype屬性所在函式的指標。 例:Person.prototype.constructor指向Person。

proto:這個連線存在於例項與建構函式的原型物件之間,而不是存在於例項與建構函式之間。 [[Prototype]]:當呼叫建構函式建立一個新例項後,該例項的內部所包含的一個指標(內部屬性),指向建構函式的原型物件。 在所有實現中都無法訪問到[[Prototype]]。 isPrototypeOf()方法:用來確定物件之間是否存在這種關係。

alert(Person.prototype.isPrototypeOf(person1)); 		//true

Object.getPrototypeOf()方法:返回[[Prototype]]的值。

可以通過物件例項訪問儲存在原型中的值,但不能通過物件例項重寫原型中的值。 屬性同名將會遮蔽原型中的那個屬性。 使用delete操作符則可以完全刪除例項屬性,從而能夠重新訪問原型中的屬性。

delete person1.name;

使用hasOwnProperty()方法可以檢測一個屬性是存在於例項中,還是存在於原型中。 存在物件例項中:true

1.原型與in操作符

兩種方式使用in操作符:單獨使用 和 在for-in迴圈中使用。

function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
 	alert(this.name);
};
var person1 = new Person();
var person2 = new Person();
alert(person1.hasOwnProperty("name")); 		//false
alert("name" in person1);			//true

無論屬性存在於例項中還是存在於原型中。只要有都會返回true。

//判斷屬性是否是原型中的屬性
function hasPrototypeProperty(object, name){
 	return !object.hasOwnProperty(name) && (name in object);
}

使用for-in迴圈時,返回的是所有能夠通過物件訪問的、可列舉的(enumerated)屬性,包括存在於例項中的屬性,也包括存在於原型中的屬性。([[Enumerable]]標記為false的屬性會在for-in迴圈中返回。)

ECMAScript5的Object.keys()方法:取得物件上所有可列舉的例項屬性。 引數:一個物件。

function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
 	alert(this.name);
};
var keys = Object.keys(Person.prototype);
alert(keys); 			//"name,age,job,sayName"
var p1 = new Person();
p1.name = "Rob";
p1.age = 31;
var p1keys = Object.keys(p1);
alert(p1keys);			//"name,age"

Object.getOwnPropertyNames()方法:得到所有例項屬性,無論它是否可列舉。

var keys = Object.getOwnPropertyNames(Person.prototype);
alert(keys); 			//"constructor,name,age,job,sayName"

3.更簡單的原型語法 用一個包含所有屬性和方法的物件字面量來重寫整個原型物件。

function Person(){
}
Person.prototype = {
 	name : "Nicholas",
 	age : 29,
 	job : "Software Engineer",
 	sayName : function () {
 		alert(this.name);
 	}
};

缺點:constructor屬性不再指向Person了。通過constructor無法確定物件的型別。 注意:此時instanceof操作符還能返回正確的結果。

加強版:

function Person(){
}
Person.prototype = {
 	constructor : Person,
 	name : "Nicholas",
 	age : 29,
 	job : "Software Engineer",
 	sayName : function () {
 		alert(this.name);
 	}
};

不足:導致重設的constructor屬性的[[Enumerable]](可列舉)特性被設定為true。

重設建構函式,只適用於ECMAScript5相容的瀏覽器

Object.defineProperty(Person.prototype, "constructor", {
 	enumerable: false,
 	valus: Person;
});

4.原型的動態性 先建立了例項後修改原型也能夠立即從例項上放映出來。

var friend = new Person();
Person.prototype.sayHi = function(){
 	alert("hi");
};
friend.sayHi(); 		//"hi"(沒有問題!)

原因:例項於原型之間的鬆散連線關係。

重寫整個原型物件的情況: 呼叫建構函式時會為例項新增一個指向最初原型的[[Prototype]]指標,而把原型修改為另一個物件就等於切斷了建構函式與最初原型之間的聯絡。 例項中的指標僅指向原型,而不指向建構函式。

function Person(){
}
var friend = new Person();
Person.prototype = {
 	constructor : Person,
 	name : "Nicholas",
 	age : 29,
 	job : "Software Engineer",
 	sayName : function () {
 		alert(this.name);
 	}
};
friend.sayName(); 		//error

5.原生物件的原型 原型模式的重要性不僅體現在建立自定義型別方面,就連所有原生的引用型別,都採用這種模式建立的。 不建議在產品化的程式中修改原生物件的原型。

6.原型物件的問題 1.省略了為建構函式傳遞初始化引數這一環節,所有例項在預設情況下都將取得相同的屬性值。 2.原型中的所有屬性是被很多例項共享的,對於包含引用型別值的屬性不好。

function Person(){
}
var friend = new Person();
Person.prototype = {
 	constructor : Person,
 	name : "Nicholas",
 	age : 29,
 	job : "Software Engineer",
 	friends : ["Shelby", "Count"],
 	sayName : function () {
 		alert(this.name);
 	}
};
var person1 = new Person();
var person2 = new Person();
person1.friends.push("Van");
alert(person1.friends);			//"Shelby,Count,Van"
alert(person2.friends);			//"Shelby,Count,Van"
alert(person1.friends === person2.friends);		//true

4.組合使用建構函式模式和原型模式 建構函式模式用於定義例項屬性,原型模式用於定義方法和共享的屬性。 每個例項都會有自己的一份例項屬性的副本,同時又共享著對方法的引用。

function Person(name, age, job){
 	this.name = name;
 	this.age = age;
 	this.job = job;
 	this.friends = ["Shelby", "Count"],;
}
Person.prototype = {
 	constructor : Person,
 	sayName : function () {
 		alert(this.name);
 	}
}
var person1 = new Person("Nicholas", 29 , "Software Engineer");
var person2 = new Person("Grey",27,"Doctor");
person1.friends.push("Van");
alert(person1.friends);			//"Shelby,Count,Van"
alert(person2.friends);			//"Shelby,Count"
alert(person1.friends === person2.friends);		//false
alert(person1.sayName === person2.sayName);		//true

這種建構函式與原型混成的模式,是目前在ECMAScript中使用最廣泛、認可度最高的一種建立自定義型別的方法。

5.動態原型模式 把所有資訊都封裝在了建構函式中,而通過在建構函式中初始化原型(僅在必要的情況下)。 可以通過檢查某個應該存在的方法是否有效,來決定是否需要初始化原型。

function Person(name, age, job){
 	//屬性
 	this.name = name;
 	this.age = age;
 	this.job = job;
 	//方法
 	if (typeof this.sayName != "function"){
 		Person.prototype.sayName = function(){
 			alert(this.name);
 		};
 	}
}

if裡的程式碼只會在初次呼叫建構函式時才會執行。

6.寄生建構函式模式 基本思想: 建立一個函式,該函式的作用僅僅是封裝建立物件的程式碼,然後再返回新建立的物件。

function Person(name, age, job){
 	var o = new Object();
 	o.name = name;
 	o.age = age;
 	o.job = job;
 	o.sayName = function () {
 		alert(this.name);
 	};
 	return o;
}
var friend = new Person("Nicholas", 29 , "Software Engineer");
friend.sayName(); 		//"Nicholas"

除了使用new操作符並把使用的包裝函式叫做建構函式之外,這個模式跟工廠模式其實是一模一樣的。 這個模式可以在特殊的情況下用來為物件建立建構函式。

function SpecialArray(){
 	//建立陣列
 	var values = new Array();
 	//新增值
 	values.push.apply(values, arguments);
 	//新增方法
 	values.toPipedString = function(){
 		return this.join("|");
 	};
 	//返回陣列
 	return values;
}
var colors = new SpecialArray("red", "blue", "green");
alert(colors.toPipedString()); 		//"red|blue|green"

說明:返回的物件與建構函式或者與建構函式的原型屬性之間沒有關係,不能依賴instanceof操作符來確定物件型別。

7.穩妥建構函式模式 穩妥物件:沒有公共屬性,其方法不引用this的物件。 應用場景: 1.安全的環境(這些環境中會禁止使用this和new)。 2.防止資料被其他應用程式(如Mashup程式)改動時使用。

與寄生建構函式模式的差異: 1.新建立物件的例項方法不引用this 2.不使用new操作符呼叫建構函式

function Person(name, age, job){
 	//建立要返回的物件
 	var o = new Object();
 	//可以在這裡定義私有變數和函式
 	o.name = name;
 	o.age = age;
 	o.job = job;
 	//新增方法
 	o.sayName = function () {
 		alert(name);
 	};
 	//返回物件
 	return o;
}
var friend = Person("Nicholas", 29 , "Software Engineer");
friend.sayName(); 		//"Nicholas"

3.繼承

由於函式沒有簽名,在ECMAScript中無法實現介面繼承。 ECMAScript只支援實現繼承,而且其實現繼承主要是依靠原型鏈來實現的。

1.原型鏈

基本思想: 利用原型讓一個引用型別繼承另一個引用型別的屬性和方法。 原型物件等於另一個型別的例項。

實現原型鏈有一種基本模式,其程式碼大致如下:

function SuperType() {
 	this.property = true;
}
SuperType.prototype.getSuperValue = function() {
 	return this.property;
};
function SubType() {
 	this.subproperty = false;
}
//繼承SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function() {
 	return this.subproperty;
};
var instance = new SubType();
alert(instance.getSuperValue()); 	//true

新原型不僅具有作為一個SuperType的例項所擁有的全部屬性和方法,而且內部還有一個指標,指向了SuperType的原型。 注意: 1.屬性property在SubType Prototype裡,getSuperValue()方法在SuperType Prototype裡。 2.instance.constructor指向的是SuperType。

1.別忘記預設的原型 所有引用型別預設都繼承了Object,而這個繼承也是通過原型鏈實現的。

2.確定原型和例項的關係 兩種方式: 1.instanceof操作符:測試例項與原型鏈中出現過的建構函式時返回true。 2.isPrototypeof()方法:測試例項與原型鏈中出現過的建構函式時返回true。

3.謹慎地定義方法

function SuperType() {
 	this.property = true;
}
SuperType.prototype.getSuperValue = function() {
 	return this.property;
};
function SubType() {
 	this.subproperty = false;
}
//繼承SuperType
SubType.prototype = new SuperType();
//新增新方法
SubType.prototype.getSubValue = function() {
 	return this.subproperty;
};
//重寫超型別中的方法
SubType.prototype.getSuperValue = function() {
 	return false;
};
var instance = new SubType();
alert(instance.getSuperValue()); 	//false

給原型新增方法的程式碼一定要放在替換原型的語句之後。 必須在用SuperType的例項替換原型之後(原型物件等於另一個型別的例項),再定義這兩個方法。 在通過原型鏈實現繼承時,不能使用物件字面量建立原型方法。因為這樣做就會重寫原型鏈。

function SuperType() {
 	this.property = true;
}
SuperType.prototype.getSuperValue = function() {
 	return this.property;
};
function SubType() {
 	this.subproperty = false;
}
//繼承SuperType
SubType.prototype = new SuperType();
//使用字面量新增新方法,會導致上一行程式碼無效
SubType.prototype = {
 	getSubValue : function (){
 		return this.subproperty;
 	},
 	someOtherMethod : function () {
 		return false;
 	}
};
var instance = new SubType();
alert(instance.getSuperValue()); 	/error!

4.原型鏈的問題 1.最主要的問題來自包含引用型別值的原型。 2.在建立子型別的例項時,不能向超型別的建構函式中傳遞引數。

借用建構函式

借用建構函式(constructor stealing)技術:為了解決原型中包含引用型別值所帶來的問題。(有時候也叫做偽造物件或經典繼承)。 思想:在子型別建構函式的內部呼叫超型別建構函式。

function SuperType() {
	this.colors = ["red","blue","green"];
}
function SubType() {
	//繼承了SuperType
	SuperType.call(this);
}
var instancel = new SubType();
instancel.colors.push("black");
alert(instancel.colors);  
//"red blue,green,black"
var instancel2 = new SubType();
alert(instancel2.colors);
//"red blue,green"

1.傳遞引數 相對於原型鏈而言,借用建構函式有一個很大的優勢,即可以在子型別建構函式中向超型別建構函式傳遞引數。 2.借用建構函式的問題 有和建構函式一樣的問題,沒有函式複用。

組合繼承

combination inheritance:有時候也叫做偽經典繼承,指的是將原型鏈和借用建構函式的技術組合到一塊。

function SuperType(name) {
	this.name = name;
	this.colors = ["red","blue","green"];
}
SuperType.prototype.sayName = function(){
	alert(this.name);
};
function SubType(name,age) {
	//繼承屬性
	SuperType.call(this,name);
	this.age = age;
}
//繼承方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
	alert(this.age);
};
var instancel = new SubType("Nicholas",29);
instancel.colors.push("black");
alert(instancel.colors);  
//"red blue,green,black"
instancel1.sayName();
//"Nicholas"
instancel1.sayAge();
//29
var instancel2 = new SubType("Greg",27);
alert(instancel2.colors);
//"red blue,green"
instancel2.sayName();
//"Greg"
instancel2.sayAge();
//27

原型式繼承

道格拉斯·克羅克福 想法:藉助原型可以基於已有的物件建立新物件,同時還不必因此建立自定義型別。

function object(o) {
	function F(){}
	F.prototype = o;
	return new F();
}

object()對傳入的物件執行了一次淺複製。 ECMAScript5通過新增Object.create()方法規範化了原型式繼承。

var person = {
	name: "Nicholas",
	firends: ["Shelby", "Court", "Van"]
	};
var anotherPerson = Object.create(person, {
	name: {
		value: "Greg"
	}
});
alert(anotherPerson.name); 
//"Greg"

寄生式繼承

parasitic:與原型式繼承緊密相關的一種思路,並且同樣也是由克羅克福推而廣之的。 思路:建立一個僅用於封裝繼承過程的函式,該函式在內部以某種方式來增強物件,最後再像真地是它做了所有工作一樣返回物件。

function createAnother(original) {
	var clone = object(original);		//通過呼叫函式建立一個新物件
	clone.sayHi = function(){		//以某種方式來增強這個物件
		alert("hi");		
	};
	return clone;		//返回這個物件
}

object()函式不是必需的,任何能夠返回新物件的函式都適用於此模式。

寄生組合式繼承

組合繼承是JavaScript最常用的繼承模式。 組合繼承最大是問題是無論在什麼情況下,都會呼叫兩次超型別建構函式:一次是在建立子型別原型的時候,另一次是在子型別建構函式內部。 所謂寄生組合式繼承,即通過建構函式來繼承屬性,通過原型鏈的混合形式來繼承方法。

function inheritPrototype(subType, superType){
	var prototype = object(superType.prototype);		//建立物件
	prototype.constructor = subType;			//增強物件
	subType.prototype = prototype;			//指定物件
	}
  • 第一步,建立超型別原型的一個副本。
  • 第二步,為建立的副本新增constructor屬性,從而彌補重寫原型而失去的預設的constructor屬性。
  • 最後一步,將新建立的物件(即副本)賦值給子型別的原型。