1. 程式人生 > >重新介紹 JavaScript(JS 教程)摘要

重新介紹 JavaScript(JS 教程)摘要



JavaScript 中  null  和  undefined  是不同的,前者表示一個空值(non-value),必須使用null關鍵字才能訪問,後者是“undefined(未定義)”型別的物件,表示一個未初始化的值,也就是還沒有被分配的值。我們之後再具體討論變數,但有一點可以先簡單說明一下,JavaScript 允許宣告變數但不對其賦值,一個未被賦值的變數就是 
undefined  型別。還有一點需要說明的是, undefined  實際上是一個不允許修改的常量。

物件 JavaScript 中的物件可以簡單理解成“名稱-值”對,不難聯想 JavaScript 中的物件與下面這些概念類似:
  • Python 中的字典
  • Perl 和 Ruby 中的雜湊(雜湊)
  • C/C++ 中的散列表
  • Java 中的 HashMap
  • PHP 中的關聯陣列
這樣的資料結構設計合理,能應付各類複雜需求,所以被各類程式語言廣泛採用。正因為 JavaScript 中的一切(除了核心型別,core object)都是物件,所以 JavaScript 程式必然與大量的散列表查詢操作有著千絲萬縷的聯絡,而散列表擅長的正是高速查詢。
“名稱”部分是一個 JavaScript 字串,“值”部分可以是任何 JavaScript 的資料型別——包括物件。這使使用者可以根據具體需求,創建出相當複雜的資料結構。 有兩種簡單方法可以建立一個空物件: var obj = new Object (); 和: var obj = {}; 這兩種方法在語義上是相同的。第二種更方便的方法叫作“物件字面量(object literal)”法。這種也是 JSON 格式的核心語法,一般我們優先選擇第二種方法。

學習 JavaScript 最重要的就是要理解物件和函式兩個部分。最簡單的函式就像下面這個這麼簡單:
function add ( x , y ) { var total = x + y ; return total ; } 這個例子包括你需要了解的關於基本函式的所有部分。一個 JavaScript 函式可以包含 0 個或多個已命名的變數。函式體中的表示式數量也沒有限制。你可以宣告函式自己的區域性變數。 return 語句在返回一個值並結束函式。如果沒有使用  return  語句,或者一個沒有值的  return  語句,JavaScript 會返回  undefined

關鍵字  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