重新介紹 JavaScript(JS 教程)摘要
阿新 • • 發佈:2018-11-13
JavaScript 中 null 和 undefined 是不同的,前者表示一個空值(non-value),必須使用null關鍵字才能訪問,後者是“undefined(未定義)”型別的物件,表示一個未初始化的值,也就是還沒有被分配的值。我們之後再具體討論變數,但有一點可以先簡單說明一下,JavaScript 允許宣告變數但不對其賦值,一個未被賦值的變數就是
物件 JavaScript 中的物件可以簡單理解成“名稱-值”對,不難聯想 JavaScript 中的物件與下面這些概念類似:
- Python 中的字典
- Perl 和 Ruby 中的雜湊(雜湊)
- C/C++ 中的散列表
- Java 中的 HashMap
- PHP 中的關聯陣列
學習 JavaScript 最重要的就是要理解物件和函式兩個部分。最簡單的函式就像下面這個這麼簡單:
關鍵字 this 。當使用在函式中時, this 指代當前的物件,也就是呼叫了函式的物件。如果在一個物件上使用 點或者方括號 來訪問屬性或方法,這個物件就成了 this 。如果並沒有使用“點”運算子呼叫某個物件,那麼 this 將指向全域性物件(global object)。 我們引入了另外一個關鍵字: new ,它和 this 密切相關。它的作用是建立一個嶄新的空物件,然後使用指向那個物件的 this 呼叫特定的函式。注意,含有 this 的特定函式不會返回任何值,只會修改 this 物件本身。 new 關鍵字將生成的 this 物件返回給呼叫方,而被 new 呼叫的函式成為建構函式。習慣的做法是將這些函式的首字母大寫,這樣用 new 呼叫他們的時候就容易識別了。 Person.prototype 是一個可以被 Person 的所有例項共享的物件。它是一個名叫原型鏈(prototype chain)的查詢鏈的一部分:當你試圖訪問一個 Person 沒有定義的屬性時,直譯器會首先檢查這個 Person.prototype 來判斷是否存在這樣一個屬性。所以,任何分配給 Person.prototype 的東西對通過 this 物件構造的例項都是可用的。 原型組成鏈的一部分。那條鏈的根節點是 Object.prototype ,它包括 toString() 方法——將物件轉換成字串時呼叫的方法。
閉包 下面我們將看到的是 JavaScript 中必須提到的功能最強大的抽象概念之一:閉包。但它可能也會帶來一些潛在的困惑。那它究竟是做什麼的呢? function makeAdder ( a ) { return function ( b ) { return a + b ; } } var x = makeAdder ( 5 ); var y = makeAdder ( 20 ); x ( 6 ); // ? y ( 7 ); // ? makeAdder 這個名字本身應該能說明函式是用來做什麼的:它建立了一個新的 adder 函式,這個函式自身帶有一個引數,它被呼叫的時候這個引數會被加在外層函式傳進來的引數上。 這裡發生的事情和前面介紹過的內嵌函式十分相似:一個函式被定義在了另外一個函式的內部,內部函式可以訪問外部函式的變數。唯一的不同是,外部函式被返回了,那麼常識告訴我們區域性變數“應該”不再存在。但是它們卻仍然存在——否則 adder 函式將不能工作。也就是說,這裡存在 makeAdder 的區域性變數的兩個不同的“副本”——一個是 a 等於5,另一個是 a 等於20。那些函式的執行結果就如下所示: x ( 6 ); // 返回 11 y ( 7 ); // 返回 27 下面來說說到底發生了什麼。每當 JavaScript 執行一個函式時,都會建立一個作用域物件(scope object),用來儲存在這個函式中建立的區域性變數。它和被傳入函式的變數一起被初始化。這與那些儲存的所有全域性變數和函式的全域性物件(global object)類似,但仍有一些很重要的區別,第一,每次函式被執行的時候,就會建立一個新的,特定的作用域物件;第二,與全域性物件(在瀏覽器裡面是當做 window 物件來訪問的)不同的是,你不能從 JavaScript 程式碼中直接訪問作用域物件,也沒有可以遍歷當前的作用域物件裡面屬性的方法。 所以當呼叫 makeAdder 時,直譯器建立了一個作用域物件,它帶有一個屬性: a ,這個屬性被當作引數傳入 makeAdder 函式。然後 makeAdder 返回一個新建立的函式。通常 JavaScript 的垃圾回收器會在這時回收 makeAdder 建立的作用域物件,但是返回的函式卻保留一個指向那個作用域物件的引用。結果是這個作用域物件不會被垃圾回收器回收,直到指向 makeAdder 返回的那個函式物件的引用計數為零。 作用域物件組成了一個名為作用域鏈(scope chain)的鏈。它類似於原型(prototype)鏈一樣,被 JavaScript 的物件系統使用。 一個 閉包 就是一個函式和被建立的函式中的作用域物件的組合。
prototype和Object.getPrototypeOf 對於從 Java 或 C++ 轉過來的開發人員來說 JavaScript 會有點讓人困惑,因為它全部都是動態的,都是執行時,而且不存在類(classes)。所有的都是例項(物件)。即使我們模擬出的 “類(classes)”,也只是一個函式物件。 你可能已經注意到我們的 function A 有一個叫做 prototype 的特殊屬性。該特殊屬性可與 JavaScript 的 new 操作符一起使用。對原型物件的引用被複制到新例項的內部 [[Prototype]] 屬性。例如,當執行 var a1 = new A() 時,JavaScript(在記憶體中建立物件之前,並且在執行函式 A() 之前)定義了它)設定 a1.[[Prototype]] = A.prototype 。然後當您訪問例項的屬性時,JavaScript首先會檢查它們是否直接存在於該物件上,如果不存在,則會 [[Prototype]] 中查詢。這意味著你在 prototype 中定義的所有內容都可以由所有例項有效共享,你甚至可以稍後更改部分 prototype ,並在所有現有例項中顯示更改(如果需要)。 像上面的例子中,如果你執行 var a1 = new A(); var a2 = new A(); 那麼 a1.doSomething 事實上會指向 Object.getPrototypeOf(a1).doSomething ,它就是你在 A.prototype.doSomething 中定義的內容。比如: Object.getPrototypeOf(a1).doSomething == Object.getPrototypeOf(a2).doSomething == A.prototype.doSomething 。 簡而言之, prototype 是用於型別的,而 Object.getPrototypeOf() 是用於例項的(instances),兩者功能一致。 [[Prototype]] 看起來就像 遞迴 引用, 如 a1.doSomething , Object.getPrototypeOf(a1).doSomething , Object.getPrototypeOf(Object.getPrototypeOf(a1)).doSomething 等等等, 直到它被找到或 Object.getPrototypeOf 返回 null。 因此,當你執行: var o = new Foo (); JavaScript 實際上執行的是: var o = new Object (); o . __proto__ = Foo . prototype ; Foo . call ( o ); (或者類似上面這樣的),然後當你執行: o . someProp ; 它檢查 o 是否具有 someProp 屬性。如果沒有,它會查詢 Object.getPrototypeOf(o).someProp ,如果仍舊沒有,它會繼續查詢 Object.getPrototypeOf(Object.getPrototypeOf(o)).someProp 。