MDN javascript中級教程

1. Introducing JavaScript objects

2. Client-side web APIs

3. 深入 JavaScript

4. JavaScript 數(shù)據(jù)結(jié)構(gòu)

5. 如何正確判斷相等性

6. Closures


1. Introducing JavaScript objects

在 JavaScript 中唉堪,大多數(shù)事物都是對象, 從作為核心功能的字符串和數(shù)組链方,到建立在 JavaScript 之上的瀏覽器 API侄柔。你甚至可以自己創(chuàng)建對象暂题,將相關(guān)的函數(shù)和變量封裝打包成便捷的數(shù)據(jù)容器。理解這種面向?qū)ο?(object-oriented, OO) 的特性對于進一步學(xué)習(xí) JavaScript 語言知識是必不可少的究珊。這個模塊將幫助你了解“對象”薪者,先詳細介紹對象的設(shè)計思想和語法,再說明如何創(chuàng)建對象剿涮。

鏈接到章節(jié)預(yù)備知識

開始這個模塊之前攻人,你應(yīng)當(dāng)已經(jīng)對 HTML 和 CSS 有所了解。我們建議你通讀 HTML 入門和 CSS 入門模塊悬槽,再開始了解 JavaScript怀吻。

詳細了解 JavaScript 對象之前,你應(yīng)當(dāng)已經(jīng)對 JavaScript 基礎(chǔ)有所熟悉初婆。嘗試這個模塊之前蓬坡,請通讀 JavaScript 第一步和構(gòu)成 JavaScript 的“磚塊”。

注意:如果你使用的電腦/平板/其他設(shè)備上無法創(chuàng)建自己的文件磅叛,你可以使用在線編程網(wǎng)站如 JSBin 或 Thimble屑咳,來試驗文章中的(大多數(shù))代碼。

鏈接到章節(jié)指南

對象基礎(chǔ)
在了解 JavaScript 對象的第一篇文章中弊琴,我們將介紹 JavaScript 對象的語法兆龙,并回顧先前課程中講過的某些 JavaScript 功能。你會發(fā)現(xiàn)敲董,你已經(jīng)在使用的很多功能本質(zhì)上都是對象紫皇。

適合初學(xué)者的面向?qū)ο?JavaScript
了解基礎(chǔ)后,我們將關(guān)注面向?qū)ο?JavaScript (OOJS)腋寨。本文將介紹面向?qū)ο缶幊?(OOP) 的基本理論坝橡,然后講解 JavaScript 如何通過構(gòu)造器 (constructor) 函數(shù)模擬對象類別 (class)、如何創(chuàng)建對象實例 (instance)精置。

對象原型
通過原型 (prototype) 這種機制,JavaScript 中的對象從其他對象繼承功能特性锣杂;這種繼承機制與經(jīng)典的面向?qū)ο缶幊陶Z言不同脂倦。本文將探討這些差別,解釋原型鏈如何工作元莫,并了解如何通過 prototype 屬性向已有的構(gòu)造器添加方法赖阻。

JavaScript 中的繼承
了解了 OOJS 的大多數(shù)細節(jié)之后,本文將介紹如何創(chuàng)建“子”對象類別(構(gòu)造器)并從“父”類別中繼承功能踱蠢。此外火欧,我們還會針對何時何處使用 OOJS 給出建議。

使用 JSON 數(shù)據(jù)
JavaScript Object Notation (JSON) 是一種將結(jié)構(gòu)化數(shù)據(jù)表達為 JavaScript 對象的標(biāo)準(zhǔn)格式茎截,其常用于在網(wǎng)站上表達或傳輸數(shù)據(jù)(比如:從服務(wù)器向客戶端發(fā)送數(shù)據(jù)苇侵,使之顯示在網(wǎng)頁上)。你會經(jīng)常遇到它企锌,因此本文將告訴你如何在 JavaScript 中使用 JSON 數(shù)據(jù)榆浓,包括訪問 JSON 對象中的數(shù)據(jù)條目、編寫自己的 JSON 數(shù)據(jù)等等撕攒。

構(gòu)建對象實戰(zhàn)
在前面的文章中我們了解了 JavaScript 對象基本理論和語法陡鹃,為你打下堅實的基礎(chǔ)烘浦。本文中你需要進行實戰(zhàn)練習(xí),通過構(gòu)建自定義 JavaScript 對象的實踐過程萍鲸,編寫一個有趣而“多彩”的程序——“彩色彈跳球”闷叉。


2. Client-side web APIs

當(dāng)你給網(wǎng)頁或者網(wǎng)頁應(yīng)用編寫客戶端的JavaScript時, 你很快會遇上應(yīng)用程序接口(API )—— 這些編程特性可用來操控網(wǎng)站所基于的瀏覽器與操作系統(tǒng)的不同方面脊阴,或是操控由其他網(wǎng)站或服務(wù)端傳來的數(shù)據(jù)握侧。在這個單元里,我們將一同探索什么是API蹬叭,以及如何使用一些在你開發(fā)中將經(jīng)常遇見的API藕咏。

鏈接到章節(jié)預(yù)備知識

若想深入理解這個單元的內(nèi)容, 你必須能夠以自己的能力較好地學(xué)完之前的幾個章節(jié) (First steps, Building blocks, and JavaScript objects). 這幾部分涉及到了許多簡單的API的使用, 如果沒有它們我們將很難做一些實際的事情秽五。在這個教程中孽查,我們會認為你懂得JavaScript的核心知識,而且我們將更深入地探索常見的網(wǎng)頁API坦喘。

若你知道 HTML 和 CSS 的基本知識盲再,也會對理解這個單元的內(nèi)容大有裨益。

注意:如果你正在使用一臺無法創(chuàng)建你自身文件的電腦瓣铣、平板或其他設(shè)備答朋,你可以嘗試使用一些在線網(wǎng)頁編輯器如JSBin或者Thimble,來嘗試編輯一些代碼示例棠笑。

鏈接到章節(jié)向?qū)?/h6>
Web API簡介

首先, 我們將從一個更高的角度來看這些API —它們是什么梦碗,它們怎么起作用的,你該怎么在自己的代碼中使用它們以及他們是怎么構(gòu)成的蓖救? 我們依舊會再來看一看這些API有哪些主要的種類和他們會有哪些用處洪规。

操作文檔

當(dāng)你在制作WEB頁面和APP時,一個你最經(jīng)常想要做的事就是通過一些方法來操作WEB文檔。這其中最常見的方法就是使用文檔對象模型Document Object Model (DOM)循捺,它是一系列大量使用了 Document object的API來控制HTML和樣式信息斩例。通過這篇文章,我們來看看使用DOM方面的一些細節(jié)从橘, 以及其他一些有趣的API能夠通過一些有趣的方式改變你的環(huán)境念赶。

從服務(wù)器獲取數(shù)據(jù)

在現(xiàn)代網(wǎng)頁及其APP中另外一個很常見的任務(wù)就是與服務(wù)器進行數(shù)據(jù)交互時不再刷新整個頁面,這看起來微不足道恰力,但卻對一個網(wǎng)頁的展現(xiàn)和交互上起到了很大的作用叉谜,在這篇文章里,我們將闡述這個概念踩萎,然后來了解實現(xiàn)這個功能的技術(shù)正罢,例如 XMLHttpRequest 和 Fetch API.(抓取API)。

第三方 API

到目前為止我們所涉及的API都是瀏覽器內(nèi)置的,但并不代表所有翻具。許多大網(wǎng)站如Google Maps, Twitter, Facebook, PayPal等履怯,都提供他們的API給開發(fā)者們?nèi)ナ褂盟麄兊臄?shù)據(jù)(比如在你的博客里展示你分享的推特內(nèi)容)或者服務(wù)(如在你的網(wǎng)頁里展示定制的谷歌地圖或接入Facebook登錄功能)。這篇文章介紹了瀏覽器API和第三方API 的差別以及一些最新的典型應(yīng)用裆泳。

繪制圖形

瀏覽器包含多種強大的圖形編程工具叹洲,從可縮放矢量圖形語言Scalable Vector Graphics (SVG) language,到HTML繪制元素 <canvas> 元素(The Canvas API and WebGL). 這篇文章提供了部分canvas的簡介工禾,以及讓你更深入學(xué)習(xí)的資源运提。

視頻和音頻 API

HTML5能夠通過元素標(biāo)簽嵌入富媒體——<video> and <audio>——而將有自己的API來控制回放,搜索等功能闻葵。本文向您展示了如何創(chuàng)建自定義播放控制等常見的任務(wù)民泵。

客戶端存儲

現(xiàn)代web瀏覽器擁有很多不同的技術(shù),能夠讓你存儲與網(wǎng)站相關(guān)的數(shù)據(jù)槽畔,并在需要時調(diào)用它們栈妆,能夠讓你長期保存數(shù)據(jù)、保存離線網(wǎng)站及其他實現(xiàn)其他功能厢钧。本文解釋了這些功能的基本原理鳞尔。


3. 深入 JavaScript

引言
為什么會有這一篇“重新介紹”呢?因為 JavaScript 堪稱世界上被人誤解最深的編程語言早直。雖然常被嘲為“玩具語言”寥假,但在它看似簡潔的外衣下,還隱藏著強大的語言特性霞扬。 JavaScript 目前廣泛應(yīng)用于眾多知名應(yīng)用中糕韧,對于網(wǎng)頁和移動開發(fā)者來說,深入理解 JavaScript 就尤有必要喻圃。

先從這門語言的歷史談起是有必要的兔沃。在1995 年 Netscape 一位名為 Brendan Eich 的工程師創(chuàng)造了 JavaScript,隨后在 1996 年初级及,JavaScript 首先被應(yīng)用于 Netscape 2 瀏覽器上。最初的 JavaScript 名為 LiveScript额衙,后來因為 Sun Microsystem 的 Java 語言的興起和廣泛使用饮焦,Netscape 出于宣傳和推廣的考慮,將它的名字從最初的 LiveScript 更改為 JavaScript——盡管兩者之間并沒有什么共同點窍侧。這便是之后混淆產(chǎn)生的根源县踢。

幾個月后,Microsoft 隨著 IE 3 推出了一個與之基本兼容的語言 JScript伟件。又幾個月后硼啤,Netscape 將 JavaScript 提交至 Ecma International(一個歐洲標(biāo)準(zhǔn)化組織), ECMAScript 標(biāo)準(zhǔn)第一版便在 1997 年誕生了斧账,隨后在 1999 年以 ECMAScript 第三版的形式進行了更新谴返,從那之后這個標(biāo)準(zhǔn)沒有發(fā)生過大的改動煞肾。由于委員會在語言特性的討論上發(fā)生分歧,ECMAScript 第四版尚未推出便被廢除嗓袱,但隨后于 2009 年 12 月發(fā)布的 ECMAScript 第五版引入了第四版草案加入的許多特性籍救。第六版標(biāo)準(zhǔn)已經(jīng)于2015年六月發(fā)布。

注意: 為熟悉起見渠抹,從這里開始我們將用 “JavaScript” 替代 ECMAScript 蝙昙。

與大多數(shù)編程語言不同,JavaScript 沒有輸入或輸出的概念梧却。它是一個在主機環(huán)境(host environment)下運行的腳本語言奇颠,任何與外界溝通的機制都是由主機環(huán)境提供的。瀏覽器是最常見的主機環(huán)境放航,但在非常多的其他程序中也包含 JavaScript 解釋器烈拒,如 Adobe Acrobat、Photoshop三椿、SVG 圖像缺菌、Yahoo! 的 Widget 引擎,以及 Node.js 之類的服務(wù)器端環(huán)境搜锰。JavaScript 的實際應(yīng)用遠不止這些伴郁,除此之外還有 NoSQL 數(shù)據(jù)庫(如開源的 Apache CouchDB)、嵌入式計算機蛋叼,以及包括 GNOME (注:GNU/Linux 上最流行的 GUI 之一)在內(nèi)的桌面環(huán)境等等焊傅。

鏈接到章節(jié)概覽

JavaScript 是一種面向?qū)ο蟮膭討B(tài)語言,它包含類型狈涮、運算符狐胎、標(biāo)準(zhǔn)內(nèi)置( built-in)對象和方法。它的語法來源于 Java 和 C歌馍,所以這兩種語言的許多語法特性同樣適用于 JavaScript握巢。需要注意的一個主要區(qū)別是 JavaScript 不支持類,類這一概念在 JavaScript 通過對象原型(object prototype)得到延續(xù)(有關(guān) ES6 類的內(nèi)容參考這里Classes)松却。另一個主要區(qū)別是 JavaScript 中的函數(shù)也是對象暴浦,JavaScript 允許函數(shù)在包含可執(zhí)行代碼的同時,能像其他對象一樣被傳遞晓锻。

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

Number(數(shù)字)
String(字符串)
Boolean(布爾)
Function(函數(shù))
Object(對象)
Symbol (第六版新增)
…哦独撇,還有看上去有些…奇怪的 undefined(未定義)類型和 null(空)類型。此外還有Array(數(shù)組)類型,以及分別用于表示日期和正則表達式的 Date(日期)和 RegExp(正則表達式)纷铣,這三種類型都是特殊的對象卵史。嚴(yán)格意義上說,F(xiàn)unction(函數(shù))也是一種特殊的對象关炼。所以準(zhǔn)確來說程腹,JavaScript 中的類型應(yīng)該包括這些:

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

五種簡單(基本)數(shù)據(jù)類型(除Symbol外):
Null
Undefined
Boolean
Number
String
一種復(fù)雜數(shù)據(jù)類型
Object

鏈接到章節(jié)數(shù)字

根據(jù)語言規(guī)范,JavaScript 采用“IEEE 754 標(biāo)準(zhǔn)定義的雙精度64位格式”("double-precision 64-bit format IEEE 754 values")表示數(shù)字社痛。據(jù)此我們能得到一個有趣的結(jié)論见转,和其他編程語言(如 C 和 Java)不同,JavaScript 不區(qū)分整數(shù)值和浮點數(shù)值蒜哀,所有數(shù)字在 JavaScript 中均用浮點數(shù)值表示斩箫,所以在進行數(shù)字運算的時候要特別注意∧於看看下面的例子:

0.1 + 0.2 = 0.30000000000000004
在具體實現(xiàn)時乘客,整數(shù)值通常被視為32位整型變量,在個別實現(xiàn)(如某些瀏覽器)中也以32位整型變量的形式進行存儲淀歇,直到它被用于執(zhí)行某些32位整型不支持的操作易核,這是為了便于進行位操作。

JavaScript 支持標(biāo)準(zhǔn)的算術(shù)運算符浪默,包括加法牡直、減法、取模(或取余)等等纳决。還有一個之前沒有提及的內(nèi)置對象 Math(數(shù)學(xué)對象)碰逸,用以處理更多的高級數(shù)學(xué)函數(shù)和常數(shù):

Math.sin(3.5);
var d = Math.PI * (r + r);
你可以使用內(nèi)置函數(shù) parseInt() 將字符串轉(zhuǎn)換為整型。該函數(shù)的第二個參數(shù)表示字符串所表示數(shù)字的基(進制):

parseInt("123", 10); // 123
parseInt("010", 10); //10
如果調(diào)用時沒有提供第二個參數(shù)(字符串所表示數(shù)字的基)阔加,2013 年以前的 JavaScript 實現(xiàn)會返回一個意外的結(jié)果:

parseInt("010"); // 8
parseInt("0x10"); // 16
這是因為字符串以數(shù)字 0 開頭饵史,parseInt()函數(shù)會把這樣的字符串視作八進制數(shù)字;同理胜榔,0x開頭的字符串則視為十六進制數(shù)字胳喷。

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

parseInt("11", 2); // 3
JavaScript 還有一個類似的內(nèi)置函數(shù) parseFloat()苗分,用以解析浮點數(shù)字符串,與parseInt()不同的地方是牵辣,parseFloat()只應(yīng)用于解析十進制數(shù)字摔癣。

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

  • "42"; // 42
  • "010"; // 10
  • "0x10"; // 16
    如果給定的字符串不存在數(shù)值形式,函數(shù)會返回一個特殊的值 NaN(Not a Number 的縮寫):

parseInt("hello", 10); // NaN
要小心NaN:如果把 NaN 作為參數(shù)進行任何數(shù)學(xué)運算,結(jié)果也會是 NaN:

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

isNaN(NaN); // true
JavaScript 還有兩個特殊值:Infinity(正無窮)和 -Infinity(負無窮):

1 / 0; // Infinity
-1 / 0; // -Infinity
可以使用內(nèi)置函數(shù) isFinite() 來判斷一個變量是否是一個有窮數(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ù)值類型的檢測戴卜,則返回false:Number.isFinite("0");
備注: parseInt() 和 parseFloat() 函數(shù)會嘗試逐個解析字符串中的字符,直到遇上一個無法被解析成數(shù)字的字符琢岩,然后返回該字符前所有數(shù)字字符組成的數(shù)字投剥。使用運算符 "+" 將字符串轉(zhuǎn)換成數(shù)字,只要字符串中含有無法被解析成數(shù)字的字符担孔,該字符串都將被轉(zhuǎn)換成 NaN江锨。請你用這兩種方法分別解析“10.2abc”這一字符串,比較得到的結(jié)果糕篇,理解這兩種方法的區(qū)別啄育。

鏈接到章節(jié)字符串

JavaScript 中的字符串是一串Unicode 字符序列。這對于那些需要和多語種網(wǎng)頁打交道的開發(fā)者來說是個好消息拌消。更準(zhǔn)確地說挑豌,它們是一串UTF-16編碼單元的序列,每一個編碼單元由一個 16 位二進制數(shù)表示墩崩。每一個Unicode字符由一個或兩個編碼單元來表示氓英。

如果想表示一個單獨的字符,只需使用長度為 1 的字符串鹦筹。

通過訪問字符串的 長度(編碼單元的個數(shù))屬性可以得到它的長度铝阐。

"hello".length; // 5
這是我們第一次碰到 JavaScript 對象。我們有沒有提過你可以像 objects 一樣使用字符串盛龄?是的饰迹,字符串也有methods(方法)能讓你操作字符串和獲取字符串的信息。

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

鏈接到章節(jié)其他類型

JavaScript 中 null 和 undefined 是不同的余舶,前者表示一個空值(non-value)啊鸭,必須使用null關(guān)鍵字才能訪問,后者是“undefined(未定義)”類型的對象匿值,表示一個未初始化的值赠制,也就是還沒有被分配的值。我們之后再具體討論變量挟憔,但有一點可以先簡單說明一下钟些,JavaScript 允許聲明變量但不對其賦值,一個未被賦值的變量就是 undefined 類型绊谭。還有一點需要說明的是政恍,undefined 實際上是一個不允許修改的常量。

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

false宗弯、0脯燃、空字符串("")、NaN蒙保、null 和 undefined 被轉(zhuǎn)換為 false
所有其他值被轉(zhuǎn)換為 true
也可以使用 Boolean() 函數(shù)進行顯式轉(zhuǎn)換:

Boolean(""); // false
Boolean(234); // true
不過一般沒必要這么做辕棚,因為 JavaScript 會在需要一個布爾變量時隱式完成這個轉(zhuǎn)換操作(比如在 if 條件語句中)。所以邓厕,有時我們可以把轉(zhuǎn)換成布爾值后的變量分別稱為 真值(true values)——即值為 true 和 假值(false values)——即值為 false逝嚎;也可以分別稱為“真的”(truthy)和“假的”(falsy)。

JavaScript 支持包括 &&(邏輯與)邑狸、|| (邏輯或)和!(邏輯非)在內(nèi)的邏輯運算符懈糯。下面會有所提到。

鏈接到章節(jié)變量

在 JavaScript 中聲明一個新變量的方法是使用關(guān)鍵字 let 单雾、const 和 var:

let 語句聲明一個塊級作用域的本地變量赚哗,并且可選的將其初始化為一個值。

let a;
let name = 'Simon';
下面是使用 let 聲明變量作用域的例子:

// myLetVariable is not visible out here

for (let myLetVariable = 0; myLetVariable < 5; myLetVariable++) {
// myLetVariable is only visible in here
}

// myLetVariable is not visible out here
const 允許聲明一個不可變的常量硅堆。這個常量在定義域內(nèi)總是可見的屿储。

const Pi = 3.14; // 設(shè)置 Pi 的值
Pi = 1; // 將會拋出一個錯誤因為你改變了一個常量的值。
var 是最常見的聲明變量的關(guān)鍵字渐逃。它沒有其他兩個關(guān)鍵字的種種限制够掠。這是因為它是傳統(tǒng)上在 JavaScript 聲明變量的唯一方法。使用 var 聲明的變量在它所聲明的整個函數(shù)都是可見的茄菊。

var a;
var name = "simon";
一個使用 var 聲明變量的語句塊的例子:

// myVarVariable is visible out here

for (var myVarVariable = 0; myVarVariable < 5; myVarVariable++) {
// myVarVariable is visible to the whole function
}

// myVarVariable is visible out here
如果聲明了一個變量卻沒有對其賦值疯潭,那么這個變量的類型就是 undefined。

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

鏈接到章節(jié)運算符

JavaScript的算術(shù)操作符包括 +辽幌、-增淹、*衔掸、/ 和 % ——求余(與模運算不同)靡羡。賦值使用 = 運算符座哩,此外還有一些復(fù)合運算符朝蜘,如 += 和 -=,它們等價于 x = x op y因痛。

x += 5; // 等價于 x = x + 5;
可以使用 ++ 和 -- 分別實現(xiàn)變量的自增和自減次企。兩者都可以作為前綴或后綴操作符使用脯颜。

  • 操作符還可以用來連接字符串:

"hello" + " world"; // hello world
如果你用一個字符串加上一個數(shù)字(或其他值),那么操作數(shù)都會被首先轉(zhuǎn)換為字符串舞蔽。如下所示:

"3" + 4 + 5; // 345
3 + 4 + "5"; // 75
這里不難看出一個實用的技巧——通過與空字符串相加,可以將某個變量快速轉(zhuǎn)換成字符串類型码撰。

JavaScript 中的比較操作使用 <渗柿、>、<= 和 >=脖岛,這些運算符對于數(shù)字和字符串都通用朵栖。相等的比較稍微復(fù)雜一些。由兩個“=(等號)”組成的相等運算符有類型自適應(yīng)的功能柴梆,具體例子如下:

123 == "123" // true
1 == true; // true
如果在比較前不需要自動類型轉(zhuǎn)換陨溅,應(yīng)該使用由三個“=(等號)”組成的相等運算符:

1 === true; //false
123 === "123"; // false
JavaScript 還支持 != 和 !== 兩種不等運算符,具體區(qū)別與兩種相等運算符的區(qū)別類似绍在。

JavaScript 還提供了 位操作符门扇。

鏈接到章節(jié)控制結(jié)構(gòu)

JavaScript 的控制結(jié)構(gòu)與其他類 C 語言類似〕ザ桑可以使用 if 和 else 來定義條件語句臼寄,還可以連起來使用:

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) {
  // 一個無限循環(huán)吉拳!
}

var input;
do {
  input = get_input();
} while (inputIsNotValid(input))
JavaScript 的 for 循環(huán)與 C 和 Java 中的相同,使用時可以在一行代碼中提供控制信息适揉。

for (var i = 0; i < 5; i++) {
  // 將會執(zhí)行五次
}
JavaScript 也還包括其他兩種重要的 for 循環(huán): for...of

for (let value of array) {
  // do something with value
}
和 for...in :

for (let property in object) {
  // do something with object property
}

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

var name = o && o.getName();
或運算可以用來設(shè)置默認值:

var name = otherName || "default";
類似地炼邀,JavaScript 也有一個用于條件表達式的三元操作符:

var allowed = (age > 18) ? "yes" : "no";
在需要多重分支時可以使用 基于一個數(shù)字或字符串的switch 語句:

switch(action) {
case 'draw':
drawIt();
break;
case 'eat':
eatIt();
break;
default:
doNothing();
}
如果你不使用 break 語句,JavaScript 解釋器將會執(zhí)行之后 case 中的代碼吃沪。除非是為了調(diào)試汤善,一般你并不需要這個特性,所以大多數(shù)時候不要忘了加上 break票彪。

switch(a) {
case 1: // 繼續(xù)向下
case 2:
eatIt();
break;
default:
doNothing();
}
default 語句是可選的红淡。switch 和 case 都可以使用需要運算才能得到結(jié)果的表達式;在 switch 的表達式和 case 的表達式是使用 === 嚴(yán)格相等運算符進行比較的:

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

鏈接到章節(jié)對象

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

Python 中的字典
Perl 和 Ruby 中的散列(哈希)
C/C++ 中的散列表
Java 中的 HashMap
PHP 中的關(guān)聯(lián)數(shù)組
這樣的數(shù)據(jù)結(jié)構(gòu)設(shè)計合理在旱,能應(yīng)付各類復(fù)雜需求,所以被各類編程語言廣泛采用推掸。正因為 JavaScript 中的一切(除了核心類型桶蝎,core object)都是對象驻仅,所以 JavaScript 程序必然與大量的散列表查找操作有著千絲萬縷的聯(lián)系,而散列表擅長的正是高速查找登渣。

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

有兩種簡單方法可以創(chuàng)建一個空對象:

var obj = new Object();
和:

var obj = {};
這兩種方法在語義上是相同的。第二種更方便的方法叫作“對象字面量(object literal)”法呻顽。這種也是 JSON 格式的核心語法雹顺,一般我們優(yōu)先選擇第二種方法。

“對象字面量”也可以用來在對象實例中定義一個對象:

var obj = {
name: "Carrot",
"for": "Max",//'for' 是保留字之一廊遍,使用'_for'代替
details: {
color: "orange",
size: 12
}
}
對象的屬性可以通過鏈?zhǔn)剑╟hain)表示方法進行訪問:

obj.details.color; // orange
obj["details"]["size"]; // 12
下面的例子創(chuàng)建了一個對象原型嬉愧,Person,和這個原型的實例喉前,You没酣。

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

// 定義一個對象
var You = new Person("You", 24);
// 我們創(chuàng)建了一個新的 Person,名稱是 "You"
// ("You" 是第一個參數(shù), 24 是第二個參數(shù)..)
完成創(chuàng)建后卵迂,對象屬性可以通過如下兩種方式進行賦值和訪問:

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

// bracket notation
obj['name'] = 'Simon';
var name = obj['name'];
// can use a variable to define a key
var user = prompt('what is your key?')
obj[user] = prompt('what is its value?')
這兩種方法在語義上也是相同的四康。第二種方法的優(yōu)點在于屬性的名稱被看作一個字符串,這就意味著它可以在運行時被計算狭握,缺點在于這樣的代碼有可能無法在后期被解釋器優(yōu)化闪金。它也可以被用來訪問某些以預(yù)留關(guān)鍵字作為名稱的屬性的值:

obj.for = "Simon"; // 語法錯誤,因為 for 是一個預(yù)留關(guān)鍵字
obj["for"] = "Simon"; // 工作正常
注意:從 EcmaScript 5 開始论颅,預(yù)留關(guān)鍵字可以作為對象的屬性名(reserved words may be used as object property names "in the buff")哎垦。 這意味著當(dāng)定義對象字面量時不需要用雙引號了。參見 ES5 Spec.

關(guān)于對象和原型的詳情參見: Object.prototype.

鏈接到章節(jié)數(shù)組

JavaScript 中的數(shù)組是一種特殊的對象恃疯。它的工作原理與普通對象類似(以數(shù)字為屬性名漏设,但只能通過[] 來訪問),但數(shù)組還有一個特殊的屬性——length(長度)屬性今妄。這個屬性的值通常比數(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ù)組中元素的個數(shù)盾鳞,如下所示:

var a = ["dog", "cat", "hen"];
a[100] = "fox";
a.length; // 101
記兹浴:數(shù)組的長度是比數(shù)組最大索引值多一的數(shù)。

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

typeof(a[90]); // undefined
可以通過如下方式遍歷一個數(shù)組:

for (var i = 0; i < a.length; i++) {
// Do something with a[i]
}
遍歷數(shù)組的另一種方法是使用 for...in 循環(huán)乒裆。注意,如果有人向 Array.prototype 添加了新的屬性推励,使用這樣的循環(huán)這些屬性也同樣會被遍歷鹤耍。所以并不推薦這種方法:

for (var i in a) {
// Do something with a[i]
}
ECMAScript 5 增加了遍歷數(shù)組的另一個方法 forEach():

["dog", "cat", "hen"].forEach(function(currentValue, index, array) {
// Do something with currentValue or array[index]
});
如果想在數(shù)組后追加元素肉迫,只需要:

a.push(item);
Array(數(shù)組)類自帶了許多方法。查看 array 方法的完整文檔稿黄。

方法名稱 描述
a.toString() 返回一個包含數(shù)組中所有元素的字符串喊衫,每個元素通過逗號分隔。
a.toLocaleString() 根據(jù)宿主環(huán)境的區(qū)域設(shè)置杆怕,返回一個包含數(shù)組中所有元素的字符串格侯,每個元素通過逗號分隔。
a.concat(item1[, item2[, ...[, itemN]]]) 返回一個數(shù)組财著,這個數(shù)組包含原先 a 和 item1、item2撑碴、……撑教、itemN 中的所有元素。
a.join(sep) 返回一個包含數(shù)組中所有元素的字符串醉拓,每個元素通過指定的 sep 分隔伟姐。
a.pop() 刪除并返回數(shù)組中的最后一個元素。
a.push(item1, ..., itemN) 將 item1亿卤、item2愤兵、……、itemN 追加至數(shù)組 a排吴。
a.reverse() 數(shù)組逆序(會更改原數(shù)組 a)秆乳。
a.shift() 刪除并返回數(shù)組中第一個元素。
a.slice(start, end) 返回子數(shù)組钻哩,以 a[start] 開頭屹堰,以 a[end] 前一個元素結(jié)尾。
a.sort([cmpfn]) 依據(jù) cmpfn 返回的結(jié)果進行排序街氢,如果未指定比較函數(shù)則按字符順序比較(即使元素是數(shù)字)扯键。
a.splice(start, delcount[, item1[, ...[, itemN]]]) 從 start 開始,刪除 delcount 個元素珊肃,然后插入所有的 item荣刑。
a.unshift([item]) 將 item 插入數(shù)組頭部,返回數(shù)組新長度(考慮 undefined)伦乔。
鏈接到章節(jié)函數(shù)
學(xué)習(xí) JavaScript 最重要的就是要理解對象和函數(shù)兩個部分厉亏。最簡單的函數(shù)就像下面這個這么簡單:

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

這個例子包括你需要了解的關(guān)于基本函數(shù)的所有部分。一個 JavaScript 函數(shù)可以包含 0 個或多個已命名的變量烈和。函數(shù)體中的表達式數(shù)量也沒有限制叶堆。你可以聲明函數(shù)自己的局部變量。return 語句在返回一個值并結(jié)束函數(shù)斥杜。如果沒有使用 return 語句虱颗,或者一個沒有值的 return 語句沥匈,JavaScript 會返回 undefined。

已命名的參數(shù)更像是一個指示而沒有其他作用忘渔。如果調(diào)用函數(shù)時沒有提供足夠的參數(shù)高帖,缺少的參數(shù)會被 undefined 替代。

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

add(2, 3, 4); // 5
// 將前兩個值相加畦粮,4被忽略了
這看上去有點蠢散址。函數(shù)實際上是訪問了函數(shù)體中一個名為 arguments 的內(nèi)部對象,這個對象就如同一個類似于數(shù)組的對象一樣宣赔,包括了所有被傳入的參數(shù)预麸。讓我們重寫一下上面的函數(shù),使它可以接收任意個數(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ū)別儒将。接下來創(chuàng)建一個求平均數(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

這個很有用吏祸,但是卻帶來了新的問題。avg() 函數(shù)處理一個由逗號連接的變量串钩蚊,但如果想得到一個數(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)建的那個函數(shù)不是更好嗎?幸運的是 JavaScript 允許使用任意函數(shù)對象的apply() 方法來調(diào)用該函數(shù)砰逻,并傳遞給它一個包含了參數(shù)的數(shù)組鸣驱。

avg.apply(null, [2, 3, 4, 5]); // 3.5
傳給 apply() 的第二個參數(shù)是一個數(shù)組,它將被當(dāng)作 avg() 的參數(shù)使用蝠咆,至于第一個參數(shù) null踊东,我們將在后面討論。這也正說明一個事實——函數(shù)也是對象刚操。

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;
};

這個函數(shù)在語義上與 function avg() 相同递胧。你可以在代碼中的任何地方定義這個函數(shù),就像寫普通的表達式一樣赡茸《衅ⅲ基于這個特性,有人發(fā)明出一些有趣的技巧占卧。與 C 中的塊級作用域類似遗菠,下面這個例子隱藏了局部變量:

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

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

function countChars(elm) {
    if (elm.nodeType == 3) { // 文本節(jié)點
        return elm.nodeValue.length;
    }
    var count = 0;
    for (var i = 0, child; child = elm.childNodes[i]; i++) {
        count += countChars(child);
    }
    return count;
}

這里需要說明一個潛在問題——既然匿名函數(shù)沒有名字辙纬,那該怎么遞歸調(diào)用它呢?在這一點上叭喜,JavaScript 允許你命名這個函數(shù)表達式贺拣。你可以命名立即調(diào)用的函數(shù)表達式(IIFES——Immediately Invoked Function Expressions),如下所示:

var charsInBody = (function counter(elm) {
    if (elm.nodeType == 3) { // 文本節(jié)點
        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ù)表達式的名稱的作用域僅僅是該函數(shù)自身。這允許引擎去做更多的優(yōu)化譬涡,并且這種實現(xiàn)更可讀闪幽、友好。該名稱也顯示在調(diào)試器和一些堆棧跟蹤中涡匀,節(jié)省了調(diào)試時的時間盯腌。

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

鏈接到章節(jié)自定義對象
備注:關(guān)于 JavaScript 中面向?qū)ο缶幊谈敿毜男畔⑼蠊唬垍⒖?JavaScript 面向?qū)ο蠛喗椤?br> 在經(jīng)典的面向?qū)ο笳Z言中,對象是指數(shù)據(jù)和在這些數(shù)據(jù)上進行的操作的集合舌劳。與 C++ 和 Java 不同帚湘,JavaScript 是一種基于原型的編程語言,并沒有 class 語句甚淡,而是把函數(shù)用作類大诸。那么讓我們來定義一個人名對象,這個對象包括人的姓和名兩個域(field)材诽。名字的表示有兩種方法:“名 姓(First Last)”或“姓, 名(Last, First)”。使用我們前面討論過的函數(shù)和對象概念恒傻,可以像這樣完成定義:

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

上面的寫法雖然可以滿足要求脸侥,但是看起來很麻煩,因為需要在全局命名空間中寫很多函數(shù)盈厘。既然函數(shù)本身就是對象睁枕,如果需要使一個函數(shù)隸屬于一個對象,那么不難得到:

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ù)中時外遇,this 指代當(dāng)前的對象,也就是調(diào)用了函數(shù)的對象契吉。如果在一個對象上使用點或者方括號來訪問屬性或方法跳仿,這個對象就成了 this。如果并沒有使用“點”運算符調(diào)用某個對象捐晶,那么 this 將指向全局對象(global object)菲语。這是一個經(jīng)常出錯的地方。例如:

s = makePerson("Simon", "Willison");
var fullName = s.fullName;
fullName(); // undefined undefined

當(dāng)我們調(diào)用 fullName() 時惑灵,this 實際上是指向全局對象的山上,并沒有名為 first 或 last 的全局變量,所以它們兩個的返回值都會是 undefined英支。

下面使用關(guān)鍵字 this 改進已有的 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");

我們引入了另外一個關(guān)鍵字:new佩憾,它和 this 密切相關(guān)。它的作用是創(chuàng)建一個嶄新的空對象,然后使用指向那個對象的 this 調(diào)用特定的函數(shù)妄帘。注意楞黄,含有 this 的特定函數(shù)不會返回任何值,只會修改 this 對象本身寄摆。new 關(guān)鍵字將生成的 this 對象返回給調(diào)用方谅辣,而被 new 調(diào)用的函數(shù)成為構(gòu)造函數(shù)。習(xí)慣的做法是將這些函數(shù)的首字母大寫婶恼,這樣用 new 調(diào)用他們的時候就容易識別了桑阶。

不過這個改進的函數(shù)還是和上一個例子一樣,單獨調(diào)用fullName() 時會產(chǎn)生相同的問題勾邦。

我們的 Person 對象現(xiàn)在已經(jīng)相當(dāng)完善了蚣录,但還有一些不太好的地方。每次我們創(chuàng)建一個 Person 對象的時候眷篇,我們都在其中創(chuàng)建了兩個新的函數(shù)對象——如果這個代碼可以共享不是更好嗎萎河?

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;
}
這種寫法的好處是,我們只需要創(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 是一個可以被Person的所有實例共享的對象达舒。它是一個名叫原型鏈(prototype chain)的查詢鏈的一部分:當(dāng)你試圖訪問一個 Person 沒有定義的屬性時叹侄,解釋器會首先檢查這個 Person.prototype 來判斷是否存在這樣一個屬性。所以趾代,任何分配給 Person.prototype 的東西對通過 this 對象構(gòu)造的實例都是可用的。

這個特性功能十分強大撒强,JavaScript 允許你在程序中的任何時候修改原型(prototype)中的一些東西丈甸,也就是說你可以在運行時(runtime)給已存在的對象添加額外的方法:

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 添加一個方法用來返回逆序的字符串:

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é)點是 Object.prototype顿仇,它包括 toString() 方法——將對象轉(zhuǎn)換成字符串時調(diào)用的方法。這對于調(diào)試我們的 Person 對象很有用:

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

Person.prototype.toString = function() {
    return '<Person: ' + this.fullName() + '>';
}
s.toString(); // <Person: Simon Willison>

你是否還記得之前我們說的 avg.apply() 中的第一個參數(shù) null?現(xiàn)在我們可以回頭看看這個東西了臼闻。apply() 的第一個參數(shù)應(yīng)該是一個被當(dāng)作 this 來看待的對象鸿吆。下面是一個 new 方法的簡單實現(xiàn):

function trivialNew(constructor, ...args) {
    var o = {}; // 創(chuàng)建一個對象
    constructor.apply(o, args);
    return o;
}

這并不是 new 的完整實現(xiàn),因為它沒有創(chuàng)建原型(prototype)鏈述呐。想舉例說明 new 的實現(xiàn)有些困難惩淳,因為你不會經(jīng)常用到這個,但是適當(dāng)了解一下還是很有用的乓搬。在這一小段代碼里思犁,...args(包括省略號)叫作剩余參數(shù)(rest arguments)。如名所示进肯,這個東西包含了剩下的參數(shù)激蹲。

因此調(diào)用

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

var bill = new Person("William", "Orange");
apply() 有一個姐妹函數(shù),名叫 call江掩,它也可以允許你設(shè)置 this学辱,但它帶有一個擴展的參數(shù)列表而不是一個數(shù)組。

function lastNameCaps() {
    return this.last.toUpperCase();
}
var s = new Person("Simon", "Willison");
lastNameCaps.call(s);
// 和以下方式等價
s.lastNameCaps = lastNameCaps;
s.lastNameCaps();

鏈接到章節(jié)內(nèi)部函數(shù)
JavaScript 允許在一個函數(shù)內(nèi)部定義函數(shù)环形,這一點我們在之前的 makePerson() 例子中也見過策泣。關(guān)于 JavaScript 中的嵌套函數(shù)萨咕,一個很重要的細節(jié)是它們可以訪問父函數(shù)作用域中的變量:

function betterExampleNeeded() {
    var a = 1;
    function oneMoreThanA() {
        return a + 1;
    }
    return oneMoreThanA();
}

如果某個函數(shù)依賴于其他的一兩個函數(shù)任洞,而這一兩個函數(shù)對你其余的代碼沒有用處,你可以將它們嵌套在會被調(diào)用的那個函數(shù)內(nèi)部刃鳄,這樣做可以減少全局作用域下的函數(shù)的數(shù)量叔锐,這有利于編寫易于維護的代碼愉烙。

這也是一個減少使用全局變量的好方法。當(dāng)編寫復(fù)雜代碼時蔓肯,程序員往往試圖使用全局變量蔗包,將值共享給多個函數(shù)舟陆,但這樣做會使代碼很難維護吨娜。內(nèi)部函數(shù)可以共享父函數(shù)的變量宦赠,所以你可以使用這個特性把一些函數(shù)捆綁在一起,這樣可以有效地防止“污染”你的全局命名空間——你可以稱它為“局部全局(local global)”铁瞒。雖然這種方法應(yīng)該謹慎使用身辨,但它確實很有用煌珊,應(yīng)該掌握定庵。

鏈接到章節(jié)閉包
下面我們將看到的是 JavaScript 中必須提到的功能最強大的抽象概念之一:閉包。但它可能也會帶來一些潛在的困惑畴博。那它究竟是做什么的呢俱病?

function makeAdder(a) {
    return function(b) {
        return a + b;
    }
}
var x = makeAdder(5);
var y = makeAdder(20);
x(6); // ?
y(7); // ?

makeAdder 這個名字本身應(yīng)該能說明函數(shù)是用來做什么的:它創(chuàng)建了一個新的 adder 函數(shù)庶艾,這個函數(shù)自身帶有一個參數(shù)颖榜,它被調(diào)用的時候這個參數(shù)會被加在外層函數(shù)傳進來的參數(shù)上掩完。

這里發(fā)生的事情和前面介紹過的內(nèi)嵌函數(shù)十分相似:一個函數(shù)被定義在了另外一個函數(shù)的內(nèi)部且蓬,內(nèi)部函數(shù)可以訪問外部函數(shù)的變量。唯一的不同是冯事,外部函數(shù)已經(jīng)返回了昵仅,那么常識告訴我們局部變量“應(yīng)該”不再存在摔笤。但是它們卻仍然存在——否則 adder 函數(shù)將不能工作。也就是說梯澜,這里存在 makeAdder 的局部變量的兩個不同的“副本”——一個是 a 等于5晚伙,另一個是 a 等于20。那些函數(shù)的運行結(jié)果就如下所示:

x(6); // 返回 11
y(7); // 返回 27
下面來說說到底發(fā)生了什么午磁。每當(dāng) JavaScript 執(zhí)行一個函數(shù)時,都會創(chuàng)建一個作用域?qū)ο螅╯cope object)昧辽,用來保存在這個函數(shù)中創(chuàng)建的局部變量。它和被傳入函數(shù)的變量一起被初始化咕痛。這與那些保存的所有全局變量和函數(shù)的全局對象(global object)類似茉贡,但仍有一些很重要的區(qū)別腔丧,第一,每次函數(shù)被執(zhí)行的時候俗壹,就會創(chuàng)建一個新的头滔,特定的作用域?qū)ο罄ぜ欤坏诙缧c全局對象(在瀏覽器里面是當(dāng)做 window 對象來訪問的)不同的是,你不能從 JavaScript 代碼中直接訪問作用域?qū)ο笃仔眨矝]有可以遍歷當(dāng)前的作用域?qū)ο罄锩鎸傩缘姆椒ā?/p>

所以當(dāng)調(diào)用 makeAdder 時屉来,解釋器創(chuàng)建了一個作用域?qū)ο笄芽浚鼛в幸粋€屬性:a账嚎,這個屬性被當(dāng)作參數(shù)傳入 makeAdder 函數(shù)郭蕉。然后 makeAdder 返回一個新創(chuàng)建的函數(shù)召锈。通常 JavaScript 的垃圾回收器會在這時回收 makeAdder 創(chuàng)建的作用域?qū)ο笳撬辏欠祷氐暮瘮?shù)卻保留一個指向那個作用域?qū)ο蟮囊谩=Y(jié)果是這個作用域?qū)ο蟛粫焕厥掌骰厥毡玻钡街赶?makeAdder 返回的那個函數(shù)對象的引用計數(shù)為零琐馆。

作用域?qū)ο蠼M成了一個名為作用域鏈(scope chain)的鏈瘦麸。它類似于原型(prototype)鏈一樣,被 JavaScript 的對象系統(tǒng)使用屠缭。

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

閉包允許你保存狀態(tài)——所以它們通痴篝幔可以代替對象來使用逢并。這里有一些關(guān)于閉包的詳細介紹砍聊。

鏈接到章節(jié)內(nèi)存泄露
使用閉包的一個壞處是玻蝌,在 IE 瀏覽器中它會很容易導(dǎo)致內(nèi)存泄露俯树。JavaScript 是一種具有垃圾回收機制的語言——對象在被創(chuàng)建的時候分配內(nèi)存许饿,然后當(dāng)指向這個對象的引用計數(shù)為零時陋率,瀏覽器會回收內(nèi)存瓦糟。宿主環(huán)境提供的對象都是按照這種方法被處理的菩浙。

瀏覽器主機需要處理大量的對象來描繪一個正在被展現(xiàn)的 HTML 頁面——DOM 對象芍耘。瀏覽器負責(zé)管理它們的內(nèi)存分配和回收。

IE 瀏覽器有自己的一套垃圾回收機制秃殉,這套機制與 JavaScript 提供的垃圾回收機制進行交互時鳄袍,可能會發(fā)生內(nèi)存泄露拗小。

在 IE 中哀九,每當(dāng)在一個 JavaScript 對象和一個本地對象之間形成循環(huán)引用時阅束,就會發(fā)生內(nèi)存泄露息裸。如下所示:

function leakMemory() {
    var el = document.getElementById('el');
    var o = { 'el': el };
    el.o = o;
}

這段代碼的循環(huán)引用會導(dǎo)致內(nèi)存泄露:IE 不會釋放被 el 和 o 使用的內(nèi)存年扩,直到瀏覽器被徹底關(guān)閉并重啟后常遂。

這個例子往往無法引起人們的重視:一般只會在長時間運行的應(yīng)用程序中克胳,或者因為巨大的數(shù)據(jù)量和循環(huán)中導(dǎo)致內(nèi)存泄露發(fā)生時,內(nèi)存泄露才會引起注意笆搓。

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

閉包很容易發(fā)生無意識的內(nèi)存泄露净嘀。如下所示:

function addHandler() {
    var el = document.getElementById('el');
    el.onclick = function() {
        el.style.backgroundColor = 'red';
    }
}

這段代碼創(chuàng)建了一個元素,當(dāng)它被點擊的時候變紅厢漩,但同時它也會發(fā)生內(nèi)存泄露。為什么架谎?因為對 el 的引用不小心被放在一個匿名內(nèi)部函數(shù)中狐树。這就在 JavaScript 對象(這個內(nèi)部函數(shù))和本地對象之間(el)創(chuàng)建了一個循環(huán)引用。

這個問題有很多種解決方法野哭,最簡單的一種是不要使用 el 變量:

function addHandler(){
    document.getElementById('el').onclick = function(){
        this.style.backgroundColor = 'red';
    };
}

另外一種避免閉包的好方法是在 window.onunload 事件發(fā)生期間破壞循環(huán)引用。很多事件庫都能完成這項工作篱蝇。注意這樣做將使 Firefox 中的 bfcache 無法工作零截。所以除非有其他必要的原因涧衙,最好不要在 Firefox 中注冊一個onunload 的監(jiān)聽器。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末撤嫩,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子两踏,更是在濱河造成了極大的恐慌梦染,老刑警劉巖泛粹,帶你破解...
    沈念sama閱讀 212,383評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異们衙,居然都是意外死亡蒙挑,警方通過查閱死者的電腦和手機忆蚀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來欣鳖,“玉大人,你說我怎么就攤上這事师痕。” “怎么了笔横?”我有些...
    開封第一講書人閱讀 157,852評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長厢塘。 經(jīng)常有香客問我晚碾,道長抓半,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,621評論 1 284
  • 正文 為了忘掉前任格嘁,我火速辦了婚禮笛求,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘糕簿。我一直安慰自己探入,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,741評論 6 386
  • 文/花漫 我一把揭開白布冶伞。 她就那樣靜靜地躺著响禽,像睡著了一般。 火紅的嫁衣襯著肌膚如雪贮竟。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,929評論 1 290
  • 那天昔逗,我揣著相機與錄音泽论,去河邊找鬼鹦赎。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 39,076評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,803評論 0 268
  • 序言:老撾萬榮一對情侶失蹤鲸阻,失蹤者是張志新(化名)和其女友劉穎陋守,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,265評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,582評論 2 327
  • 正文 我和宋清朗相戀三年硼补,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瑟啃。...
    茶點故事閱讀 38,716評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡勇边,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情滤否,我是刑警寧澤颈娜,帶...
    沈念sama閱讀 34,395評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響辅愿,放射性物質(zhì)發(fā)生泄漏削锰。R本人自食惡果不足惜饲宿,卻給世界環(huán)境...
    茶點故事閱讀 40,039評論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧黍特,春花似錦造虎、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蚀同。三九已至啡省,卻和暖如春结序,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背泌绣。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留怜庸,地道東北人当犯。 一個月前我還...
    沈念sama閱讀 46,488評論 2 361
  • 正文 我出身青樓,卻偏偏與公主長得像割疾,于是被迫代替她去往敵國和親嚎卫。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,612評論 2 350

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

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,837評論 25 707
  • 關(guān)于Mongodb的全面總結(jié) MongoDB的內(nèi)部構(gòu)造《MongoDB The Definitive Guide》...
    中v中閱讀 31,913評論 2 89
  • 用兩張圖告訴你宏榕,為什么你的 App 會卡頓? - Android - 掘金 Cover 有什么料拓诸? 從這篇文章中你...
    hw1212閱讀 12,704評論 2 59
  • 【0315我在悅讀《干法》媚如雪 【書名】《干法》 【作者】稻盛和夫 【篇目】全書 【收獲】 讀過稻盛和夫的《活法...
    清木木閱讀 642評論 0 3
  • 嘿,你好担扑,boy恰响,我是你的女朋友。 今天我在的城市有很好的陽光涌献,躺在沙發(fā)上曬太陽的時候胚宦,突然有給你寫一封信的沖動。...
    G寵兒閱讀 389評論 0 0