重新介紹 JavaScript

引言

為什么會(huì)有這一篇“重新介紹”呢赁还?因?yàn)?JavaScript 堪稱世界上被人誤解最深的編程語(yǔ)言。雖然常被嘲為“玩具語(yǔ)言”蹬昌,但在它看似簡(jiǎn)潔的外衣下,還隱藏著強(qiáng)大的語(yǔ)言特性锣尉。 JavaScript 目前廣泛應(yīng)用于眾多知名應(yīng)用中刻炒,對(duì)于網(wǎng)頁(yè)和移動(dòng)開發(fā)者來(lái)說,深入理解 JavaScript 就尤有必要自沧。

先從這門語(yǔ)言的歷史談起是有必要的落蝙。在1995 年 Netscape 一位名為 Brendan Eich 的工程師創(chuàng)造了 JavaScript,隨后在 1996 年初暂幼,JavaScript 首先被應(yīng)用于 Netscape 2 瀏覽器上筏勒。最初的 JavaScript 名為 LiveScript,后來(lái)因?yàn)?Sun Microsystem 的 Java 語(yǔ)言的興起和廣泛使用旺嬉,Netscape 出于宣傳和推廣的考慮管行,將它的名字從最初的 LiveScript 更改為 JavaScript——盡管兩者之間并沒有什么共同點(diǎn)。這便是之后混淆產(chǎn)生的根源邪媳。

幾個(gè)月后捐顷,Microsoft 隨著 IE 3 推出了一個(gè)與之基本兼容的語(yǔ)言 JScript。又幾個(gè)月后雨效,Netscape 將 JavaScript 提交至 Ecma International(一個(gè)歐洲標(biāo)準(zhǔn)化組織)迅涮, ECMAScript 標(biāo)準(zhǔn)第一版便在 1997 年誕生了,隨后在 1999 年以 ECMAScript 第三版的形式進(jìn)行了更新徽龟,從那之后這個(gè)標(biāo)準(zhǔn)沒有發(fā)生過大的改動(dòng)叮姑。由于委員會(huì)在語(yǔ)言特性的討論上發(fā)生分歧,ECMAScript 第四版尚未推出便被廢除据悔,但隨后于 2009 年 12 月發(fā)布的 ECMAScript 第五版引入了第四版草案加入的許多特性传透。第六版標(biāo)準(zhǔn)已經(jīng)于2015年六月發(fā)布。

注意: 為熟悉起見极颓,從這里開始我們將用 “JavaScript” 替代 ECMAScript 朱盐。
與大多數(shù)編程語(yǔ)言不同,JavaScript 沒有輸入或輸出的概念菠隆。它是一個(gè)在宿主環(huán)境(host environment)下運(yùn)行的腳本語(yǔ)言兵琳,任何與外界溝通的機(jī)制都是由宿主環(huán)境提供的。瀏覽器是最常見的宿主環(huán)境骇径,但在非常多的其他程序中也包含 JavaScript 解釋器躯肌,如 Adobe Acrobat、Photoshop既峡、SVG 圖像羡榴、Yahoo! 的 Widget 引擎碧查,以及 Node.js 之類的服務(wù)器端環(huán)境运敢。JavaScript 的實(shí)際應(yīng)用遠(yuǎn)不止這些校仑,除此之外還有 NoSQL 數(shù)據(jù)庫(kù)(如開源的 Apache CouchDB)、嵌入式計(jì)算機(jī)传惠,以及包括 GNOME (注:GNU/Linux 上最流行的 GUI 之一)在內(nèi)的桌面環(huán)境等等迄沫。

概覽

JavaScript 是一種面向?qū)ο蟮膭?dòng)態(tài)語(yǔ)言,它包含類型卦方、運(yùn)算符羊瘩、標(biāo)準(zhǔn)內(nèi)置( built-in)對(duì)象和方法。它的語(yǔ)法來(lái)源于 Java 和 C盼砍,所以這兩種語(yǔ)言的許多語(yǔ)法特性同樣適用于 JavaScript尘吗。需要注意的一個(gè)主要區(qū)別是 JavaScript 不支持類,類這一概念在 JavaScript 通過對(duì)象原型(object prototype)得到延續(xù)(有關(guān) ES6 類的內(nèi)容參考https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Classes)浇坐。另一個(gè)主要區(qū)別是 JavaScript 中的函數(shù)也是對(duì)象睬捶,JavaScript 允許函數(shù)在包含可執(zhí)行代碼的同時(shí),能像其他對(duì)象一樣被傳遞近刘。

先從任何編程語(yǔ)言都不可缺少的組成部分——“類型”開始擒贸。JavaScript 程序可以修改值(value),這些值都有各自的類型觉渴。JavaScript 中的類型包括:

Number(數(shù)字)
String(字符串)
Boolean(布爾)
Function(函數(shù))
Object(對(duì)象)
Symbol (第六版新增)

還有看上去有些…奇怪的 undefined(未定義)類型和 null(空)類型介劫。此外還有Array(數(shù)組)類型,以及分別用于表示日期和正則表達(dá)式的 Date(日期)和 RegExp(正則表達(dá)式)案淋,這三種類型都是特殊的對(duì)象座韵。嚴(yán)格意義上說,F(xiàn)unction(函數(shù))也是一種特殊的對(duì)象踢京。所以準(zhǔn)確來(lái)說回右,JavaScript 中的類型應(yīng)該包括這些:

Number(數(shù)字)
String(字符串)
Boolean(布爾)
Symbol(符號(hào))(第六版新增)
Object(對(duì)象)
Function(函數(shù))
Array(數(shù)組)
Date(日期)
RegExp(正則表達(dá)式)
Null(空)
Undefined(未定義)
JavaScript 還有一種內(nèi)置Error(錯(cuò)誤)類型,這個(gè)會(huì)在之后的介紹中提到漱挚;現(xiàn)在我們先討論下上面這些類型翔烁。

數(shù)字

根據(jù)語(yǔ)言規(guī)范,JavaScript 采用“IEEE 754 標(biāo)準(zhǔn)定義的雙精度64位格式”("double-precision 64-bit format IEEE 754 values")表示數(shù)字旨涝。據(jù)此我們能得到一個(gè)有趣的結(jié)論蹬屹,和其他編程語(yǔ)言(如 C 和 Java)不同,JavaScript 不區(qū)分整數(shù)值和浮點(diǎn)數(shù)值白华,所有數(shù)字在 JavaScript 中均用浮點(diǎn)數(shù)值表示慨默,所以在進(jìn)行數(shù)字運(yùn)算的時(shí)候要特別注意』⌒龋看看下面的例子:

0.1 + 0.2 = 0.30000000000000004

在具體實(shí)現(xiàn)時(shí)厦取,整數(shù)值通常被視為32位整型變量,在個(gè)別實(shí)現(xiàn)(如某些瀏覽器)中也以32位整型變量的形式進(jìn)行存儲(chǔ)管搪,直到它被用于執(zhí)行某些32位整型不支持的操作虾攻,這是為了便于進(jìn)行位操作铡买。

JavaScript 支持標(biāo)準(zhǔn)的算術(shù)運(yùn)算符,包括加法霎箍、減法奇钞、取模(或取余)等等。還有一個(gè)之前沒有提及的內(nèi)置對(duì)象 Math(數(shù)學(xué)對(duì)象)漂坏,用以處理更多的高級(jí)數(shù)學(xué)函數(shù)和常數(shù):

Math.sin(3.5);
var d = Math.PI * (r + r);

你可以使用內(nèi)置函數(shù) parseInt() 將字符串轉(zhuǎn)換為整型景埃。該函數(shù)的第二個(gè)參數(shù)表示字符串所表示數(shù)字的基(進(jìn)制):

parseInt("123", 10); // 123
parseInt("010", 10); //10

如果調(diào)用時(shí)沒有提供第二個(gè)參數(shù)(字符串所表示數(shù)字的基),2013 年以前的 JavaScript 實(shí)現(xiàn)會(huì)返回一個(gè)意外的結(jié)果:

parseInt("010");  //  8
parseInt("0x10"); // 16

這是因?yàn)樽址詳?shù)字 0 開頭顶别,parseInt()函數(shù)會(huì)把這樣的字符串視作八進(jìn)制數(shù)字谷徙;同理,0x開頭的字符串則視為十六進(jìn)制數(shù)字驯绎。

如果想把一個(gè)二進(jìn)制數(shù)字字符串轉(zhuǎn)換成整數(shù)值蒂胞,只要把第二個(gè)參數(shù)設(shè)置為 2 就可以了:

parseInt("11", 2); // 3

JavaScript 還有一個(gè)類似的內(nèi)置函數(shù) parseFloat(),用以解析浮點(diǎn)數(shù)字符串条篷,與parseInt()不同的地方是骗随,parseFloat()只應(yīng)用于解析十進(jìn)制數(shù)字。

單元運(yùn)算符 + 也可以把數(shù)字字符串轉(zhuǎn)換成數(shù)值:

+ "42";   // 42
+ "010";  // 10
+ "0x10"; // 16

如果給定的字符串不存在數(shù)值形式赴叹,函數(shù)會(huì)返回一個(gè)特殊的值 NaN(Not a Number 的縮寫):

parseInt("hello", 10); // NaN

要小心NaN:如果把 NaN 作為參數(shù)進(jìn)行任何數(shù)學(xué)運(yùn)算鸿染,結(jié)果也會(huì)是 NaN:

NaN + 5; //NaN

可以使用內(nèi)置函數(shù) isNaN() 來(lái)判斷一個(gè)變量是否為 NaN:

isNaN(NaN); // true

JavaScript 還有兩個(gè)特殊值:Infinity(正無(wú)窮)和 -Infinity(負(fù)無(wú)窮):

1 / 0; //  Infinity
-1 / 0; // -Infinity

可以使用內(nèi)置函數(shù) isFinite() 來(lái)判斷一個(gè)變量是否是一個(gè)有窮數(shù), 如果類型為Infinity, -Infinity 或 NaN則返回false:

isFinite(1/0); // false 
isFinite(Infinity); // false 
isFinite(NaN); // false 
isFinite(-Infinity); // false 

isFinite(0); // true 
isFinite(2e64); // true 

isFinite("0"); // true
//如果是純數(shù)值類型的檢測(cè)乞巧,則返回false:Number.isFinite("0");

備注: parseInt() 和 parseFloat() 函數(shù)會(huì)嘗試逐個(gè)解析字符串中的字符涨椒,直到遇上一個(gè)無(wú)法被解析成數(shù)字的字符,然后返回該字符前所有數(shù)字字符組成的數(shù)字绽媒。使用運(yùn)算符 "+" 將字符串轉(zhuǎn)換成數(shù)字蚕冬,只要字符串中含有無(wú)法被解析成數(shù)字的字符,該字符串都將被轉(zhuǎn)換成 NaN是辕。請(qǐng)你用這兩種方法分別解析“10.2abc”這一字符串囤热,比較得到的結(jié)果,理解這兩種方法的區(qū)別获三。
字符串

JavaScript 中的字符串是一串Unicode 字符序列旁蔼。這對(duì)于那些需要和多語(yǔ)種網(wǎng)頁(yè)打交道的開發(fā)者來(lái)說是個(gè)好消息。更準(zhǔn)確地說疙教,它們是一串UTF-16編碼單元的序列棺聊,每一個(gè)編碼單元由一個(gè) 16 位二進(jìn)制數(shù)表示。每一個(gè)Unicode字符由一個(gè)或兩個(gè)編碼單元來(lái)表示贞谓。

如果想表示一個(gè)單獨(dú)的字符限佩,只需使用長(zhǎng)度為 1 的字符串。

通過訪問字符串的 長(zhǎng)度(編碼單元的個(gè)數(shù))屬性可以得到它的長(zhǎng)度裸弦。

"hello".length; // 5

這是我們第一次碰到 JavaScript 對(duì)象祟同。我們有沒有提過你可以像 objects 一樣使用字符串作喘?是的,字符串也有methods(方法)能讓你操作字符串和獲取字符串的信息耐亏。

"hello".charAt(0); // "h"
"hello, world".replace("hello", "goodbye"); // "goodbye, world"
"hello".toUpperCase(); // "HELLO"

其他類型

JavaScript 中 null 和 undefined 是不同的徊都,前者表示一個(gè)空值(non-value)沪斟,必須使用null關(guān)鍵字才能訪問广辰,后者是“undefined(未定義)”類型的對(duì)象,表示一個(gè)未初始化的值主之,也就是還沒有被分配的值择吊。我們之后再具體討論變量,但有一點(diǎn)可以先簡(jiǎn)單說明一下槽奕,JavaScript 允許聲明變量但不對(duì)其賦值几睛,一個(gè)未被賦值的變量就是 undefined 類型。還有一點(diǎn)需要說明的是粤攒,undefined 實(shí)際上是一個(gè)不允許修改的常量所森。

JavaScript 包含布爾類型,這個(gè)類型的變量有兩個(gè)可能的值夯接,分別是 true 和 false(兩者都是關(guān)鍵字)焕济。根據(jù)具體需要,JavaScript 按照如下規(guī)則將變量轉(zhuǎn)換成布爾類型:

false盔几、0晴弃、空字符串("")、NaN逊拍、null 和 undefined 被轉(zhuǎn)換為 false
所有其他值被轉(zhuǎn)換為 true

也可以使用 Boolean() 函數(shù)進(jìn)行顯式轉(zhuǎn)換:

Boolean(""); // false
Boolean(234); // true

不過一般沒必要這么做上鞠,因?yàn)?JavaScript 會(huì)在需要一個(gè)布爾變量時(shí)隱式完成這個(gè)轉(zhuǎn)換操作(比如在 if 條件語(yǔ)句中)。所以芯丧,有時(shí)我們可以把轉(zhuǎn)換成布爾值后的變量分別稱為 真值(true values)——即值為 true 和 假值(false values)——即值為 false芍阎;也可以分別稱為“真的”(truthy)和“假的”(falsy)。

JavaScript 支持包括 &&(邏輯與)缨恒、|| (邏輯或)和!(邏輯非)在內(nèi)的邏輯運(yùn)算符能曾。下面會(huì)有所提到。

變量

在 JavaScript 中聲明一個(gè)新變量的方法是使用關(guān)鍵字 var:

var a;
var name = "simon";

如果聲明了一個(gè)變量卻沒有對(duì)其賦值肿轨,那么這個(gè)變量的類型就是 undefined寿冕。

JavaScript 與其他語(yǔ)言的(如 Java)的重要區(qū)別是在 JavaScript 中語(yǔ)句塊(blocks)是沒有作用域的捺癞,只有函數(shù)有作用域该面。因此如果在一個(gè)復(fù)合語(yǔ)句中(如 if 控制結(jié)構(gòu)中)使用 var 聲明一個(gè)變量秦士,那么它的作用域是整個(gè)函數(shù)(復(fù)合語(yǔ)句在函數(shù)中)坟漱。 但是從 ECMAScript Edition 6 開始將有所不同的旺坠, let 和 const 關(guān)鍵字允許你創(chuàng)建塊作用域的變量。

運(yùn)算符

JavaScript的算術(shù)操作符包括 +哆姻、-芙沥、*、/ 和 % ——求余(與模運(yùn)算不同)京办。賦值使用 = 運(yùn)算符掀序,此外還有一些復(fù)合運(yùn)算符,如 += 和 -=惭婿,它們等價(jià)于 x = x op y不恭。

x += 5; // 等價(jià)于 x = x + 5;

可以使用 ++ 和 -- 分別實(shí)現(xiàn)變量的自增和自減。兩者都可以作為前綴或后綴操作符使用财饥。

  • 操作符還可以用來(lái)連接字符串:
"hello" + " world"; // hello world

如果你用一個(gè)字符串加上一個(gè)數(shù)字(或其他值)换吧,那么操作數(shù)都會(huì)被首先轉(zhuǎn)換為字符串。如下所示:

"3" + 4 + 5; // 345
3 + 4 + "5"; // 75

這里不難看出一個(gè)實(shí)用的技巧——通過與空字符串相加钥星,可以將某個(gè)變量快速轉(zhuǎn)換成字符串類型沾瓦。

JavaScript 中的比較操作使用 <、>谦炒、<= 和 >=贯莺,這些運(yùn)算符對(duì)于數(shù)字和字符串都通用。相等的比較稍微復(fù)雜一些宁改。由兩個(gè)“=(等號(hào))”組成的相等運(yùn)算符有類型自適應(yīng)的功能缕探,具體例子如下:

123 == "123" // true
1 == true; // true

如果在比較前不需要自動(dòng)類型轉(zhuǎn)換,應(yīng)該使用由三個(gè)“=(等號(hào))”組成的相等運(yùn)算符:

1 === true; //false
123 === "123"; // false

JavaScript 還支持 != 和 !== 兩種不等運(yùn)算符透且,具體區(qū)別與兩種相等運(yùn)算符的區(qū)別類似撕蔼。

JavaScript 還提供了 位操作符。

控制結(jié)構(gòu)

JavaScript 的控制結(jié)構(gòu)與其他類 C 語(yǔ)言類似秽誊【ň冢可以使用 if 和 else 來(lái)定義條件語(yǔ)句,還可以連起來(lái)使用:

var name = "kittens";
if (name == "puppies") {
  name += "!";
} else if (name == "kittens") {
  name += "!!";
} else {
  name = "!" + name;
}
name == "kittens!!"; // true

JavaScript 支持 while 循環(huán)和 do-while 循環(huán)锅论。前者適合常見的基本循環(huán)操作讼溺,如果需要循環(huán)體至少被執(zhí)行一次則可以使用 do-while:

while (true) {
  // 一個(gè)無(wú)限循環(huán)!
}

var input;
do {
  input = get_input();
} while (inputIsNotValid(input))

JavaScript 的 for 循環(huán)與 C 和 Java 中的相同最易,使用時(shí)可以在一行代碼中提供控制信息怒坯。

for (var i = 0; i < 5; i++) {
  // 將會(huì)執(zhí)行五次
}

&& 和 || 運(yùn)算符使用短路邏輯(short-circuit logic),是否會(huì)執(zhí)行第二個(gè)語(yǔ)句(操作數(shù))取決于第一個(gè)操作數(shù)的結(jié)果藻懒。在需要訪問某個(gè)對(duì)象的屬性時(shí)剔猿,使用這個(gè)特性可以事先檢測(cè)該對(duì)象是否為空:

var name = o && o.getName();

或運(yùn)算可以用來(lái)設(shè)置默認(rèn)值:

var name = otherName || "default";

類似地,JavaScript 也有一個(gè)用于條件表達(dá)式的三元操作符:

var allowed = (age > 18) ? "yes" : "no";

在需要多重分支時(shí)可以使用 基于一個(gè)數(shù)字或字符串的switch 語(yǔ)句:

switch(action) {
    case 'draw':
        drawIt();
        break;
    case 'eat':
        eatIt();
        break;
    default:
        doNothing();
}

如果你不使用 break 語(yǔ)句嬉荆,JavaScript 解釋器將會(huì)執(zhí)行之后 case 中的代碼归敬。除非是為了調(diào)試,一般你并不需要這個(gè)特性,所以大多數(shù)時(shí)候不要忘了加上 break汪茧。

switch(a) {
    case 1: // 繼續(xù)向下
    case 2:
        eatIt();
        break;
    default:
        doNothing();
}

default 語(yǔ)句是可選的椅亚。switch 和 case 都可以使用需要運(yùn)算才能得到結(jié)果的表達(dá)式;在 switch 的表達(dá)式和 case 的表達(dá)式是使用 === 嚴(yán)格相等運(yùn)算符進(jìn)行比較的:

switch(1 + 3){
    case 2 + 2:
        yay();
        break;
    default:
        neverhappens();
}

對(duì)象

JavaScript 中的對(duì)象可以簡(jiǎn)單理解成“名稱-值”對(duì)舱污,不難聯(lián)想 JavaScript 中的對(duì)象與下面這些概念類似:

Python 中的字典
Perl 和 Ruby 中的散列(哈希)
C/C++ 中的散列表
Java 中的 HashMap
PHP 中的關(guān)聯(lián)數(shù)組
這樣的數(shù)據(jù)結(jié)構(gòu)設(shè)計(jì)合理呀舔,能應(yīng)付各類復(fù)雜需求,所以被各類編程語(yǔ)言廣泛采用扩灯。正因?yàn)?JavaScript 中的一切(除了核心類型媚赖,core object)都是對(duì)象,所以 JavaScript 程序必然與大量的散列表查找操作有著千絲萬(wàn)縷的聯(lián)系驴剔,而散列表擅長(zhǎng)的正是高速查找省古。

“名稱”部分是一個(gè) JavaScript 字符串粥庄,“值”部分可以是任何 JavaScript 的數(shù)據(jù)類型——包括對(duì)象丧失。這使用戶可以根據(jù)具體需求,創(chuàng)建出相當(dāng)復(fù)雜的數(shù)據(jù)結(jié)構(gòu)惜互。

有兩種簡(jiǎn)單方法可以創(chuàng)建一個(gè)空對(duì)象:

var obj = new Object();

和:

var obj = {};

這兩種方法在語(yǔ)義上是相同的布讹。第二種更方便的方法叫作“對(duì)象字面量(object literal)”法。這種也是 JSON 格式的核心語(yǔ)法训堆,一般我們優(yōu)先選擇第二種方法描验。

“對(duì)象字面量”也可以用來(lái)在對(duì)象實(shí)例中定義一個(gè)對(duì)象:

var obj = {
    name: "Carrot",
    "for": "Max",
    details: {
        color: "orange",
        size: 12
    }
}

對(duì)象的屬性可以通過鏈?zhǔn)剑╟hain)表示方法進(jìn)行訪問:

obj.details.color; // orange
obj["details"]["size"]; // 12

下面的例子創(chuàng)建了一個(gè)對(duì)象原型,Person坑鱼,和這個(gè)原型的實(shí)例You膘流。

function Person(name, age) {
  this.name = name;
  this.age = age;
}

// 定義一個(gè)對(duì)象
var You = new Person("You", 24); 
// 我們創(chuàng)建了一個(gè)新的 Person,名稱是 "You" 
// ("You" 是第一個(gè)參數(shù), 24 是第二個(gè)參數(shù)..)

完成創(chuàng)建后鲁沥,對(duì)象屬性可以通過如下兩種方式進(jìn)行賦值和訪問:

obj.name = "Simon"
var name = obj.name;

和:

obj["name"] = "Simon";
var name = obj["name"];

這兩種方法在語(yǔ)義上也是相同的呼股。第二種方法的優(yōu)點(diǎn)在于屬性的名稱被看作一個(gè)字符串,這就意味著它可以在運(yùn)行時(shí)被計(jì)算画恰,缺點(diǎn)在于這樣的代碼有可能無(wú)法在后期被解釋器優(yōu)化彭谁。它也可以被用來(lái)訪問某些以預(yù)留關(guān)鍵字作為名稱的屬性的值:

obj.for = "Simon"; // 語(yǔ)法錯(cuò)誤,因?yàn)?for 是一個(gè)預(yù)留關(guān)鍵字
obj["for"] = "Simon"; // 工作正常

注意:從 EcmaScript 5 開始允扇,預(yù)留關(guān)鍵字可以作為對(duì)象的屬性名(reserved words may be used as object property names "in the buff")缠局。 這意味著當(dāng)定義對(duì)象字面量時(shí)不需要用雙引號(hào)了。參見 ES5 Spec.
關(guān)于對(duì)象和原型的詳情參見: Object.prototype.

數(shù)組

JavaScript 中的數(shù)組是一種特殊的對(duì)象考润。它的工作原理與普通對(duì)象類似(以數(shù)字為屬性名狭园,但只能通過[] 來(lái)訪問),但數(shù)組還有一個(gè)特殊的屬性——length(長(zhǎng)度)屬性糊治。這個(gè)屬性的值通常比數(shù)組最大索引大 1唱矛。

創(chuàng)建數(shù)組的傳統(tǒng)方法是:

var a = new Array();
a[0] = "dog";
a[1] = "cat";
a[2] = "hen";
a.length; // 3

使用數(shù)組字面量(array literal)法更加方便:

var a = ["dog", "cat", "hen"];
a.length; // 3

注意,Array.length 并不總是等于數(shù)組中元素的個(gè)數(shù),如下所示:

var a = ["dog", "cat", "hen"];
a[100] = "fox";
a.length; // 101

記滓靖啊:數(shù)組的長(zhǎng)度是比數(shù)組最大索引值多一的數(shù)馆匿。

如果試圖訪問一個(gè)不存在的數(shù)組索引,會(huì)得到 undefined:

typeof(a[90]); // undefined

可以通過如下方式遍歷一個(gè)數(shù)組:

for (var i = 0; i < a.length; i++) {
    // Do something with a[i]
}

遍歷數(shù)組的另一種方法是使用 for...in 循環(huán)燥滑。注意渐北,如果有人向 Array.prototype 添加了新的屬性,使用這樣的循環(huán)這些屬性也同樣會(huì)被遍歷铭拧。所以并不推薦這種方法:

for (var i in a) {
  // Do something with a[i]
}

ECMAScript 5 增加了遍歷數(shù)組的另一個(gè)方法 forEach():

["dog", "cat", "hen"].forEach(function(currentValue, index, array) {
  // Do something with currentValue or array[index]
});

如果想在數(shù)組后追加元素赃蛛,只需要:

a.push(item);

Array(數(shù)組)類自帶了許多方法。查看 array 方法的完整文檔搀菩。

函數(shù)

學(xué)習(xí) JavaScript 最重要的就是要理解對(duì)象和函數(shù)兩個(gè)部分呕臂。最簡(jiǎn)單的函數(shù)就像下面這個(gè)這么簡(jiǎn)單:

function add(x, y) {
    var total = x + y;
    return total;
}

這個(gè)例子包括你需要了解的關(guān)于基本函數(shù)的所有部分。一個(gè) JavaScript 函數(shù)可以包含 0 個(gè)或多個(gè)已命名的變量肪跋。函數(shù)體中的表達(dá)式數(shù)量也沒有限制歧蒋。你可以聲明函數(shù)自己的局部變量。return 語(yǔ)句在返回一個(gè)值并結(jié)束函數(shù)州既。如果沒有使用 return 語(yǔ)句谜洽,或者一個(gè)沒有值的 return 語(yǔ)句,JavaScript 會(huì)返回 undefined吴叶。

已命名的參數(shù)更像是一個(gè)指示而沒有其他作用阐虚。如果調(diào)用函數(shù)時(shí)沒有提供足夠的參數(shù),缺少的參數(shù)會(huì)被 undefined 替代蚌卤。

add(); // NaN
// 不能在 undefined 對(duì)象上進(jìn)行加法操作
你還可以傳入多于函數(shù)本身需要參數(shù)個(gè)數(shù)的參數(shù):

add(2, 3, 4); // 5
// 將前兩個(gè)值相加实束,4被忽略了
這看上去有點(diǎn)蠢。函數(shù)實(shí)際上是訪問了函數(shù)體中一個(gè)名為 arguments 的內(nèi)部對(duì)象逊彭,這個(gè)對(duì)象就如同一個(gè)類似于數(shù)組的對(duì)象一樣咸灿,包括了所有被傳入的參數(shù)。讓我們重寫一下上面的函數(shù)诫龙,使它可以接收任意個(gè)數(shù)的參數(shù):

function add() {
var sum = 0;
for (var i = 0, j = arguments.length; i < j; i++) {
sum += arguments[i];
}
return sum;
}

add(2, 3, 4, 5); // 14
這跟直接寫成 2 + 3 + 4 + 5 也沒什么區(qū)別析显。接下來(lái)創(chuàng)建一個(gè)求平均數(shù)的函數(shù):

function avg() {
var sum = 0;
for (var i = 0, j = arguments.length; i < j; i++) {
sum += arguments[i];
}
return sum / arguments.length;
}
avg(2, 3, 4, 5); // 3.5
這個(gè)很有用,但是卻帶來(lái)了新的問題签赃。avg() 函數(shù)處理一個(gè)由逗號(hào)連接的變量串谷异,但如果想得到一個(gè)數(shù)組的平均值該怎么辦呢?可以這么修改函數(shù):

function avgArray(arr) {
var sum = 0;
for (var i = 0, j = arr.length; i < j; i++) {
sum += arr[i];
}
return sum / arr.length;
}
avgArray([2, 3, 4, 5]); // 3.5
但如果能重用我們已經(jīng)創(chuàng)建的那個(gè)函數(shù)不是更好嗎锦聊?幸運(yùn)的是 JavaScript 允許使用任意函數(shù)對(duì)象的apply() 方法來(lái)調(diào)用該函數(shù)歹嘹,并傳遞給它一個(gè)包含了參數(shù)的數(shù)組。

avg.apply(null, [2, 3, 4, 5]); // 3.5
傳給 apply() 的第二個(gè)參數(shù)是一個(gè)數(shù)組孔庭,它將被當(dāng)作 avg() 的參數(shù)使用尺上,至于第一個(gè)參數(shù) null材蛛,我們將在后面討論。這也正說明一個(gè)事實(shí)——函數(shù)也是對(duì)象卑吭。

JavaScript 允許你創(chuàng)建匿名函數(shù):

var avg = function() {
var sum = 0;
for (var i = 0, j = arguments.length; i < j; i++) {
sum += arguments[i];
}
return sum / arguments.length;
};
這個(gè)函數(shù)在語(yǔ)義上與 function avg() 相同马绝。你可以在代碼中的任何地方定義這個(gè)函數(shù),就像寫普通的表達(dá)式一樣富稻≈腊睿基于這個(gè)特性抚岗,有人發(fā)明出一些有趣的技巧。與 C 中的塊級(jí)作用域類似哪怔,下面這個(gè)例子隱藏了局部變量:

var a = 1;
var b = 2;
(function() {
var b = 3;
a += b;
})();

a; // 4
b; // 2
JavaScript 允許以遞歸方式調(diào)用函數(shù)宣蔚。遞歸在處理樹形結(jié)構(gòu)(比如瀏覽器 DOM)時(shí)非常有用件已。

function countChars(elm) {
if (elm.nodeType == 3) { // 文本節(jié)點(diǎn)
return elm.nodeValue.length;
}
var count = 0;
for (var i = 0, child; child = elm.childNodes[i]; i++) {
count += countChars(child);
}
return count;
}
這里需要說明一個(gè)潛在問題——既然匿名函數(shù)沒有名字元暴,那該怎么遞歸調(diào)用它呢茉盏?在這一點(diǎn)上枢冤,JavaScript 允許你命名這個(gè)函數(shù)表達(dá)式淹真。你可以命名立即調(diào)用的函數(shù)表達(dá)式(IIFES——Immediately Invoked Function Expressions)核蘸,如下所示:

var charsInBody = (function counter(elm) {
if (elm.nodeType == 3) { // 文本節(jié)點(diǎn)
return elm.nodeValue.length;
}
var count = 0;
for (var i = 0, child; child = elm.childNodes[i]; i++) {
count += counter(child);
}
return count;
})(document.body);
如上所提供的函數(shù)表達(dá)式的名稱的作用域僅僅是該函數(shù)自身。這允許引擎去做更多的優(yōu)化祟峦,并且這種實(shí)現(xiàn)更可讀宅楞、友好。該名稱也顯示在調(diào)試器和一些堆棧跟蹤中距淫,節(jié)省了調(diào)試時(shí)的時(shí)間婶希。

需要注意的是 JavaScript 函數(shù)是它們本身的對(duì)象——就和 JavaScript 其他一切一樣——你可以給它們添加屬性或者更改它們的屬性饲趋,這與前面的對(duì)象部分一樣奕塑。

自定義對(duì)象

備注:關(guān)于 JavaScript 中面向?qū)ο缶幊谈敿?xì)的信息,請(qǐng)參考 JavaScript 面向?qū)ο蠛?jiǎn)介盟猖。
在經(jīng)典的面向?qū)ο笳Z(yǔ)言中式镐,對(duì)象是指數(shù)據(jù)和在這些數(shù)據(jù)上進(jìn)行的操作的集合娘汞。與 C++ 和 Java 不同夕玩,JavaScript 是一種基于原型的編程語(yǔ)言燎孟,并沒有 class 語(yǔ)句揩页,而是把函數(shù)用作類。那么讓我們來(lái)定義一個(gè)人名對(duì)象萍程,這個(gè)對(duì)象包括人的姓和名兩個(gè)域(field)尘喝。名字的表示有兩種方法:“名 姓(First Last)”或“姓, 名(Last, First)”朽褪。使用我們前面討論過的函數(shù)和對(duì)象概念缔赠,可以像這樣完成定義:

function makePerson(first, last) {
return {
first: first,
last: last
}
}
function personFullName(person) {
return person.first + ' ' + person.last;
}
function personFullNameReversed(person) {
return person.last + ', ' + person.first
}
s = makePerson("Simon", "Willison");
personFullName(s); // Simon Willison
personFullNameReversed(s); // Willison, Simon
上面的寫法雖然可以滿足要求嗤堰,但是看起來(lái)很麻煩,因?yàn)樾枰谌置臻g中寫很多函數(shù)告匠。既然函數(shù)本身就是對(duì)象后专,如果需要使一個(gè)函數(shù)隸屬于一個(gè)對(duì)象戚哎,那么不難得到:

function makePerson(first, last) {
return {
first: first,
last: last,
fullName: function() {
return this.first + ' ' + this.last;
},
fullNameReversed: function() {
return this.last + ', ' + this.first;
}
}
}
s = makePerson("Simon", "Willison");
s.fullName(); // Simon Willison
s.fullNameReversed(); // Willison, Simon
上面的代碼里有一些我們之前沒有見過的東西:關(guān)鍵字 this型凳。當(dāng)使用在函數(shù)中時(shí)嘱函,this 指代當(dāng)前的對(duì)象实夹,也就是調(diào)用了函數(shù)的對(duì)象亮航。如果在一個(gè)對(duì)象上使用點(diǎn)或者方括號(hào)來(lái)訪問屬性或方法缴淋,這個(gè)對(duì)象就成了 this泄朴。如果并沒有使用“點(diǎn)”運(yùn)算符調(diào)用某個(gè)對(duì)象祖灰,那么 this 將指向全局對(duì)象(global object)局扶。這是一個(gè)經(jīng)常出錯(cuò)的地方。例如:

s = makePerson("Simon", "Willison");
var fullName = s.fullName;
fullName(); // undefined undefined
當(dāng)我們調(diào)用 fullName() 時(shí)莫绣,this 實(shí)際上是指向全局對(duì)象的悠鞍,并沒有名為 first 或 last 的全局變量咖祭,所以它們兩個(gè)的返回值都會(huì)是 undefined么翰。

下面使用關(guān)鍵字 this 改進(jìn)已有的 makePerson函數(shù):

function Person(first, last) {
this.first = first;
this.last = last;
this.fullName = function() {
return this.first + ' ' + this.last;
}
this.fullNameReversed = function() {
return this.last + ', ' + this.first;
}
}
var s = new Person("Simon", "Willison");
我們引入了另外一個(gè)關(guān)鍵字:new硬鞍,它和 this 密切相關(guān)固该。它的作用是創(chuàng)建一個(gè)嶄新的空對(duì)象伐坏,然后使用指向那個(gè)對(duì)象的 this 調(diào)用特定的函數(shù)。注意每瞒,含有 this 的特定函數(shù)不會(huì)返回任何值剿骨,只會(huì)修改 this 對(duì)象本身浓利。new 關(guān)鍵字將生成的 this 對(duì)象返回給調(diào)用方贷掖,而被 new 調(diào)用的函數(shù)成為構(gòu)造函數(shù)渴语。習(xí)慣的做法是將這些函數(shù)的首字母大寫驾凶,這樣用 new 調(diào)用他們的時(shí)候就容易識(shí)別了。

不過這個(gè)改進(jìn)的函數(shù)還是和上一個(gè)例子一樣汇在,單獨(dú)調(diào)用fullName() 時(shí)會(huì)產(chǎn)生相同的問題糕殉。

我們的 Person 對(duì)象現(xiàn)在已經(jīng)相當(dāng)完善了阿蝶,但還有一些不太好的地方黄绩。每次我們創(chuàng)建一個(gè) Person 對(duì)象的時(shí)候爽丹,我們都在其中創(chuàng)建了兩個(gè)新的函數(shù)對(duì)象——如果這個(gè)代碼可以共享不是更好嗎粤蝎?

function personFullName() {
return this.first + ' ' + this.last;
}
function personFullNameReversed() {
return this.last + ', ' + this.first;
}
function Person(first, last) {
this.first = first;
this.last = last;
this.fullName = personFullName;
this.fullNameReversed = personFullNameReversed;
}
這種寫法的好處是初澎,我們只需要?jiǎng)?chuàng)建一次方法函數(shù)碑宴,在構(gòu)造函數(shù)中引用它們延柠。那是否還有更好的方法呢捕仔?答案是肯定的榜跌。

function Person(first, last) {
this.first = first;
this.last = last;
}
Person.prototype.fullName = function() {
return this.first + ' ' + this.last;
}
Person.prototype.fullNameReversed = function() {
return this.last + ', ' + this.first;
}
Person.prototype 是一個(gè)可以被Person的所有實(shí)例共享的對(duì)象钓葫。它是一個(gè)名叫原型鏈(prototype chain)的查詢鏈的一部分:當(dāng)你試圖訪問一個(gè) Person 沒有定義的屬性時(shí)票顾,解釋器會(huì)首先檢查這個(gè) Person.prototype 來(lái)判斷是否存在這樣一個(gè)屬性。所以番刊,任何分配給 Person.prototype 的東西對(duì)通過 this 對(duì)象構(gòu)造的實(shí)例都是可用的芹务。

這個(gè)特性功能十分強(qiáng)大枣抱,JavaScript 允許你在程序中的任何時(shí)候修改原型(prototype)中的一些東西佳晶,也就是說你可以在運(yùn)行時(shí)(runtime)給已存在的對(duì)象添加額外的方法:

s = new Person("Simon", "Willison");
s.firstNameCaps(); // TypeError on line 1: s.firstNameCaps is not a function

Person.prototype.firstNameCaps = function() {
return this.first.toUpperCase()
}
s.firstNameCaps(); // SIMON
有趣的是轿秧,你還可以給 JavaScript 的內(nèi)置函數(shù)原型(prototype)添加?xùn)|西菇篡。讓我們給 String 添加一個(gè)方法用來(lái)返回逆序的字符串:

var s = "Simon";
s.reversed(); // TypeError on line 1: s.reversed is not a function

String.prototype.reversed = function() {
var r = "";
for (var i = this.length - 1; i >= 0; i--) {
r += this[i];
}
return r;
}
s.reversed(); // nomiS
定義新方法也可以在字符串字面量上用(string literal)逸贾。

"This can now be reversed".reversed(); // desrever eb won nac sihT
正如我前面提到的铝侵,原型組成鏈的一部分咪鲜。那條鏈的根節(jié)點(diǎn)是 Object.prototype疟丙,它包括 toString() 方法——將對(duì)象轉(zhuǎn)換成字符串時(shí)調(diào)用的方法享郊。這對(duì)于調(diào)試我們的 Person 對(duì)象很有用:

var s = new Person("Simon", "Willison");
s; // [object Object]

Person.prototype.toString = function() {
return '<Person: ' + this.fullName() + '>';
}
s.toString(); // <Person: Simon Willison>
你是否還記得之前我們說的 avg.apply() 中的第一個(gè)參數(shù) null炊琉?現(xiàn)在我們可以回頭看看這個(gè)東西了苔咪。apply() 的第一個(gè)參數(shù)應(yīng)該是一個(gè)被當(dāng)作 this 來(lái)看待的對(duì)象。下面是一個(gè) new 方法的簡(jiǎn)單實(shí)現(xiàn):

function trivialNew(constructor, ...args) {
var o = {}; // 創(chuàng)建一個(gè)對(duì)象
constructor.apply(o, args);
return o;
}
這并不是 new 的完整實(shí)現(xiàn),因?yàn)樗鼪]有創(chuàng)建原型(prototype)鏈丝里。想舉例說明 new 的實(shí)現(xiàn)有些困難丙者,因?yàn)槟悴粫?huì)經(jīng)常用到這個(gè)械媒,但是適當(dāng)了解一下還是很有用的评汰。在這一小段代碼里被去,...args(包括省略號(hào))叫作剩余參數(shù)(rest arguments)惨缆。如名所示坯墨,這個(gè)東西包含了剩下的參數(shù)捣染。

因此調(diào)用

var bill = trivialNew(Person, "William", "Orange");
可認(rèn)為和調(diào)用如下語(yǔ)句是等效的

var bill = new Person("William", "Orange");
apply() 有一個(gè)姐妹函數(shù)耍攘,名叫 call蕾各,它也可以允許你設(shè)置 this式曲,但它帶有一個(gè)擴(kuò)展的參數(shù)列表而不是一個(gè)數(shù)組检访。

function lastNameCaps() {
return this.last.toUpperCase();
}
var s = new Person("Simon", "Willison");
lastNameCaps.call(s);
// 和以下方式等價(jià)
s.lastNameCaps = lastNameCaps;
s.lastNameCaps();
內(nèi)部函數(shù)

JavaScript 允許在一個(gè)函數(shù)內(nèi)部定義函數(shù)脆贵,這一點(diǎn)我們?cè)谥暗?makePerson() 例子中也見過卖氨。關(guān)于 JavaScript 中的嵌套函數(shù)筒捺,一個(gè)很重要的細(xì)節(jié)是它們可以訪問父函數(shù)作用域中的變量:

function betterExampleNeeded() {
var a = 1;
function oneMoreThanA() {
return a + 1;
}
return oneMoreThanA();
}
如果某個(gè)函數(shù)依賴于其他的一兩個(gè)函數(shù)系吭,而這一兩個(gè)函數(shù)對(duì)你其余的代碼沒有用處肯尺,你可以將它們嵌套在會(huì)被調(diào)用的那個(gè)函數(shù)內(nèi)部则吟,這樣做可以減少全局作用域下的函數(shù)的數(shù)量氓仲,這有利于編寫易于維護(hù)的代碼敬扛。

這也是一個(gè)減少使用全局變量的好方法舔哪。當(dāng)編寫復(fù)雜代碼時(shí)捉蚤,程序員往往試圖使用全局變量缆巧,將值共享給多個(gè)函數(shù)陕悬,但這樣做會(huì)使代碼很難維護(hù)。內(nèi)部函數(shù)可以共享父函數(shù)的變量唯绍,所以你可以使用這個(gè)特性把一些函數(shù)捆綁在一起况芒,這樣可以有效地防止“污染”你的全局命名空間——你可以稱它為“局部全局(local global)”绝骚。雖然這種方法應(yīng)該謹(jǐn)慎使用压汪,但它確實(shí)很有用止剖,應(yīng)該掌握滴须。

閉包

下面我們將看到的是 JavaScript 中必須提到的功能最強(qiáng)大的抽象概念之一:閉包。但它可能也會(huì)帶來(lái)一些潛在的困惑魔市。那它究竟是做什么的呢待德?

function makeAdder(a) {
return function(b) {
return a + b;
}
}
var x = makeAdder(5);
var y = makeAdder(20);
x(6); // ?
y(7); // ?
makeAdder 這個(gè)名字本身應(yīng)該能說明函數(shù)是用來(lái)做什么的:它創(chuàng)建了一個(gè)新的 adder 函數(shù)将宪,這個(gè)函數(shù)自身帶有一個(gè)參數(shù)较坛,它被調(diào)用的時(shí)候這個(gè)參數(shù)會(huì)被加在外層函數(shù)傳進(jìn)來(lái)的參數(shù)上丑勤。

這里發(fā)生的事情和前面介紹過的內(nèi)嵌函數(shù)十分相似:一個(gè)函數(shù)被定義在了另外一個(gè)函數(shù)的內(nèi)部法竞,內(nèi)部函數(shù)可以訪問外部函數(shù)的變量岔霸。唯一的不同是秉剑,外部函數(shù)被返回了,那么常識(shí)告訴我們局部變量“應(yīng)該”不再存在略水。但是它們卻仍然存在——否則 adder 函數(shù)將不能工作渊涝。也就是說跨释,這里存在 makeAdder 的局部變量的兩個(gè)不同的“副本”——一個(gè)是 a 等于5鳖谈,另一個(gè)是 a 等于20缆娃。那些函數(shù)的運(yùn)行結(jié)果就如下所示:

x(6); // 返回 11
y(7); // 返回 27
下面來(lái)說說到底發(fā)生了什么贯要。每當(dāng) JavaScript 執(zhí)行一個(gè)函數(shù)時(shí)崇渗,都會(huì)創(chuàng)建一個(gè)作用域?qū)ο螅╯cope object)宅广,用來(lái)保存在這個(gè)函數(shù)中創(chuàng)建的局部變量乘碑。它和被傳入函數(shù)的變量一起被初始化。這與那些保存的所有全局變量和函數(shù)的全局對(duì)象(global object)類似金拒,但仍有一些很重要的區(qū)別兽肤,第一套腹,每次函數(shù)被執(zhí)行的時(shí)候,就會(huì)創(chuàng)建一個(gè)新的资铡,特定的作用域?qū)ο蟮缳鳎坏诙孕荩c全局對(duì)象(在瀏覽器里面是當(dāng)做 window 對(duì)象來(lái)訪問的)不同的是尖飞,你不能從 JavaScript 代碼中直接訪問作用域?qū)ο螅矝]有可以遍歷當(dāng)前的作用域?qū)ο罄锩鎸傩缘姆椒ā?/p>

所以當(dāng)調(diào)用 makeAdder 時(shí)店雅,解釋器創(chuàng)建了一個(gè)作用域?qū)ο笳鼛в幸粋€(gè)屬性:a,這個(gè)屬性被當(dāng)作參數(shù)傳入 makeAdder 函數(shù)闹啦。然后 makeAdder 返回一個(gè)新創(chuàng)建的函數(shù)沮明。通常 JavaScript 的垃圾回收器會(huì)在這時(shí)回收 makeAdder 創(chuàng)建的作用域?qū)ο螅欠祷氐暮瘮?shù)卻保留一個(gè)指向那個(gè)作用域?qū)ο蟮囊们戏堋=Y(jié)果是這個(gè)作用域?qū)ο蟛粫?huì)被垃圾回收器回收荐健,直到指向 makeAdder 返回的那個(gè)函數(shù)對(duì)象的引用計(jì)數(shù)為零。

作用域?qū)ο蠼M成了一個(gè)名為作用域鏈(scope chain)的鏈琳袄。它類似于原型(prototype)鏈一樣江场,被 JavaScript 的對(duì)象系統(tǒng)使用。

一個(gè)閉包就是一個(gè)函數(shù)和被創(chuàng)建的函數(shù)中的作用域?qū)ο蟮慕M合窖逗。

閉包允許你保存狀態(tài)——所以它們通持贩瘢可以代替對(duì)象來(lái)使用。這里有一些關(guān)于閉包的詳細(xì)介紹滑负。

內(nèi)存泄露

使用閉包的一個(gè)壞處是在张,在 IE 瀏覽器中它會(huì)很容易導(dǎo)致內(nèi)存泄露。JavaScript 是一種具有垃圾回收機(jī)制的語(yǔ)言——對(duì)象在被創(chuàng)建的時(shí)候分配內(nèi)存矮慕,然后當(dāng)指向這個(gè)對(duì)象的引用計(jì)數(shù)為零時(shí)帮匾,瀏覽器會(huì)回收內(nèi)存。宿主環(huán)境提供的對(duì)象都是按照這種方法被處理的痴鳄。

瀏覽器主機(jī)需要處理大量的對(duì)象來(lái)描繪一個(gè)正在被展現(xiàn)的 HTML 頁(yè)面——DOM 對(duì)象瘟斜。瀏覽器負(fù)責(zé)管理它們的內(nèi)存分配和回收。

IE 瀏覽器有自己的一套垃圾回收機(jī)制痪寻,這套機(jī)制與 JavaScript 提供的垃圾回收機(jī)制進(jìn)行交互時(shí)螺句,可能會(huì)發(fā)生內(nèi)存泄露。

在 IE 中橡类,每當(dāng)在一個(gè) JavaScript 對(duì)象和一個(gè)本地對(duì)象之間形成循環(huán)引用時(shí)蛇尚,就會(huì)發(fā)生內(nèi)存泄露。如下所示:

function leakMemory() {
var el = document.getElementById('el');
var o = { 'el': el };
el.o = o;
}
這段代碼的循環(huán)引用會(huì)導(dǎo)致內(nèi)存泄露:IE 不會(huì)釋放被 el 和 o 使用的內(nèi)存顾画,直到瀏覽器被徹底關(guān)閉并重啟后取劫。

這個(gè)例子往往無(wú)法引起人們的重視:一般只會(huì)在長(zhǎng)時(shí)間運(yùn)行的應(yīng)用程序中匆笤,或者因?yàn)榫薮蟮臄?shù)據(jù)量和循環(huán)中導(dǎo)致內(nèi)存泄露發(fā)生時(shí),內(nèi)存泄露才會(huì)引起注意谱邪。

不過一般也很少發(fā)生如此明顯的內(nèi)存泄露現(xiàn)象——通常泄露的數(shù)據(jù)結(jié)構(gòu)有多層的引用(references)炮捧,往往掩蓋了循環(huán)引用的情況。

閉包很容易發(fā)生無(wú)意識(shí)的內(nèi)存泄露惦银。如下所示:

function addHandler() {
var el = document.getElementById('el');
el.onclick = function() {
el.style.backgroundColor = 'red';
}
}
這段代碼創(chuàng)建了一個(gè)元素咆课,當(dāng)它被點(diǎn)擊的時(shí)候變紅,但同時(shí)它也會(huì)發(fā)生內(nèi)存泄露扯俱。為什么书蚪?因?yàn)閷?duì) el 的引用不小心被放在一個(gè)匿名內(nèi)部函數(shù)中。這就在 JavaScript 對(duì)象(這個(gè)內(nèi)部函數(shù))和本地對(duì)象之間(el)創(chuàng)建了一個(gè)循環(huán)引用蘸吓。

這個(gè)問題有很多種解決方法善炫,最簡(jiǎn)單的一種是不要使用 el 變量:

function addHandler(){
document.getElementById('el').onclick = function(){
this.style.backgroundColor = 'red';
};
}
另外一種避免閉包的好方法是在 window.onunload 事件發(fā)生期間破壞循環(huán)引用。很多事件庫(kù)都能完成這項(xiàng)工作库继。注意這樣做將使 Firefox 中的 bfcache 無(wú)法工作。所以除非有其他必要的原因窜醉,最好不要在 Firefox 中注冊(cè)一個(gè)onunload 的監(jiān)聽器宪萄。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市榨惰,隨后出現(xiàn)的幾起案子拜英,更是在濱河造成了極大的恐慌,老刑警劉巖琅催,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件居凶,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡藤抡,警方通過查閱死者的電腦和手機(jī)侠碧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)缠黍,“玉大人弄兜,你說我怎么就攤上這事〈墒剑” “怎么了替饿?”我有些...
    開封第一講書人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)贸典。 經(jīng)常有香客問我视卢,道長(zhǎng),這世上最難降的妖魔是什么廊驼? 我笑而不...
    開封第一講書人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任据过,我火速辦了婚禮惋砂,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蝶俱。我一直安慰自己班利,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開白布榨呆。 她就那樣靜靜地躺著罗标,像睡著了一般。 火紅的嫁衣襯著肌膚如雪积蜻。 梳的紋絲不亂的頭發(fā)上闯割,一...
    開封第一講書人閱讀 51,125評(píng)論 1 297
  • 那天,我揣著相機(jī)與錄音竿拆,去河邊找鬼宙拉。 笑死,一個(gè)胖子當(dāng)著我的面吹牛丙笋,可吹牛的內(nèi)容都是我干的谢澈。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼御板,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼锥忿!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起怠肋,我...
    開封第一講書人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤敬鬓,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后笙各,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體钉答,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年杈抢,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了数尿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡春感,死狀恐怖砌创,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情鲫懒,我是刑警寧澤嫩实,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站窥岩,受9級(jí)特大地震影響甲献,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜颂翼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一晃洒、第九天 我趴在偏房一處隱蔽的房頂上張望慨灭。 院中可真熱鬧,春花似錦球及、人聲如沸氧骤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)筹陵。三九已至,卻和暖如春镊尺,著一層夾襖步出監(jiān)牢的瞬間朦佩,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工庐氮, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留语稠,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓弄砍,卻偏偏與公主長(zhǎng)得像仙畦,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子音婶,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • 單例模式 適用場(chǎng)景:可能會(huì)在場(chǎng)景中使用到對(duì)象议泵,但只有一個(gè)實(shí)例,加載時(shí)并不主動(dòng)創(chuàng)建桃熄,需要時(shí)才創(chuàng)建 最常見的單例模式,...
    Obeing閱讀 2,065評(píng)論 1 10
  • 三型奥、閉包和高階函數(shù) 3.1 閉包 3.1.1 變量的作用域 所謂變量的作用域瞳收,就是變量的有效范圍。通過作用域的劃分...
    梁同學(xué)de自言自語(yǔ)閱讀 1,452評(píng)論 0 6
  • 工廠模式類似于現(xiàn)實(shí)生活中的工廠可以產(chǎn)生大量相似的商品厢汹,去做同樣的事情螟深,實(shí)現(xiàn)同樣的效果;這時(shí)候需要使用工廠模式。簡(jiǎn)單...
    舟漁行舟閱讀 7,750評(píng)論 2 17
  • 第5章 引用類型(返回首頁(yè)) 本章內(nèi)容 使用對(duì)象 創(chuàng)建并操作數(shù)組 理解基本的JavaScript類型 使用基本類型...
    大學(xué)一百閱讀 3,233評(píng)論 0 4
  • 我就是這樣一個(gè)人,會(huì)因?yàn)橐痪湓挻钭郏蛞粋€(gè)故事垢箕。就對(duì)一個(gè)地方產(chǎn)生向往。 2013年兑巾,我在泰國(guó)曼谷条获,正想著接下來(lái)要去哪旅...
    旅途小事閱讀 3,551評(píng)論 0 4