摘要:在中函數是一等對象,它們不被聲明為任何東西的一部分,而所引用的對象稱為函數上下文并不是由聲明函數的方式決定的,而是由調用函數的方式決定的。更為準確的表述應該為當對象充當函數的調用函數上下文時,函數就充當了對象的方法。
引言:當理解了對象和函數的基本概念,你可能會發現,在JavaScript中有很多原以為理所當然(或盲目接受)的事情開始變得更有意義了。
1.JavaScript對象
大多數面向對象(簡稱OO)語言都定義了某種基本的Object對象類型,其它所有的對象都源于這個對象類型。
JavaScript的基本對象也是作為其它對象的基礎,但是從基本層面上來看,JavaScript的Object對象與其它大部分的OO語言所定義的基本對象很不同。
對象一旦被創建后,它不保存任何數據并且幾乎沒有什么語義。但是這些有限的語義的確又給予了它很大的潛力。
新的對象由new操作符和與其相配的Object構造器來產生。創建一個對象非常簡單:
var shinyAndNew = new Object(); // 還可以更簡單
這個新對象能做什么?似乎什么也沒有——沒有信息,沒有復雜的語義,什么也沒有,一點也不吸引人,直到我們開始向其添加稱為 屬性 的東西。
JavaScript Object的實例(即:對象)就是一組屬性集,每個屬性都由名稱和值構成。屬性的名稱是字符串,屬性值可以是任何JavaScript對象,可以是Number、String、Date、Array、基本的Object,也可以是任何其它的JavaScript對象類型(例如函數)。
這意味著Object實例的主要就是用作容器,包含其它對象的已命名集合。進一步,一個對象屬性可以是另一個Object實例,這個實例又包含其它自己的屬性集,而屬性集也可以包含擁有屬性的對象,以此類推。只要對我們塑造的數據模型有意義,就可以嵌套至任何層次。
舉個栗子:
假設給一輛車(car)添加一個新的屬性以保存車輛的所有者(owner)信息。這個屬性是另一個JavaScript對象,它包含了一些屬性,如所有者姓名和職業:
var owner = new Object();
owner.name = "Roger Shieh";
owner.occupation = "Coder";
car.owner = owner;
為了訪問嵌套的屬性,我們可以編寫如下代碼:
var ownerName = car.owner.name;
提示:出于解釋的目的而創建的所有中間變量都是可以省略的(比如owner)。于是,我們可以利用這一點來使用更加高效和簡潔的方式來聲明對象及其屬性的代碼。
我們使用點操作符(英文的句號字符)來引用對象的屬性。但是事實證明,有一個更加通用的操作符來執行屬性的引用。
通用的屬性引用操作符的格式:
object[propertyNameExpression]
其中propertyNameExpression是JavaScript表達式,其求值的結果作為要引用的屬性名稱的字符串。
例如,下面的3個引用都是等價的:
car.color // 第1種
car["color"] // 第2種
car["c"+"o"+"l"+"o"+"r"] // 第3種
var p = "color"; car[p] // 第4種
對于其名稱并非有效的JavaScript標識符的屬性來說,使用通用的引用操作符是引用這種屬性的唯一方法,例如:
car["a property name that"s rather odd!"]
我們通過new操作符來創建新的實例,并且利用獨立的賦值語句來為每個屬性賦值從而建立對象,這看起來是一件繁瑣的事情。而且,這樣做既枯燥乏味,又冗長易錯,難以在快速檢查代碼的時候把握對象的結構。
幸運的是,我們可以使用更緊湊和更易于閱讀的表示法。參考如下語句:
var car = {
make:"Ford",
year:2014,
purchased:new Date(2014,12,3),
owner:{
name:"Roger Shieh",
occupation:"Coder"
}
};
這種表示法被稱為 JSON(JavaScript Object Notaition,JavaScript對象表示法)。大多數頁面開發者對JSON的偏愛有加,不喜歡通過多個賦值語句來建立對象的方式。
至此,我們看到了兩種保存JavaScript對象的方式:變量和屬性。
這兩種保存引用的方式使用不同的表示法,如下所示:
var aVariable = "Before I teamed up with you, I led quite a normal life.";
someObject.aProperty = "You move that line as you see fit for yourself.";
事實上,這個兩個語句在執行相同的操作。
任何在頂層作用域中生成的引用都隱式地創建在window實例中。
比如下面的語句,如果是在頂層中(也就是在函數的作用域之外)生成的,那么它們都是等價的:
var foo = bar;
window.foo = bar;
foo = bar;
不管使用的是哪種表示法,都會創建一個名為foo的window屬性(如果foo屬性尚未存在),并且將bar賦值給foo。還要注意,因為bar是非限定的,所以將其假定為window的一個屬性。
把頂層作用域認為是window作用域,這可能不會讓我們陷入概念上的煩惱,因為任何位于頂層的未限定的引用都被假定為window的屬性。
重要概念總結如下:
JavaScript對象是屬性的無序集合;
屬性由名稱和值組成;
可以使用字面值來聲明對象;
頂層變量是window的屬性。
2.作為一等公民的函數 當我們談到JavaScript函數是一等對象時,意味著什么?
在許多傳統的OO語言中,對象可以包含數據,還可以擁有方法。在這些語言中,數據和方法通常是不同的概念。
JavaScript可不是這樣子。
JavaScript中的函數也被認為是對象,與定義在JavaScript中任何其它的對象類型一樣,比如String、Number或Date。
和其它對象一樣,函數也是通過JavaScript構造器來定義的(在這種情況下是Function),可以對函數進行如下操作:
把函數賦值給變量;
將函數指定為一個對象的屬性;
把函數作為參數傳遞;
把函數作為函數結果返回;
使用字面值來創建函數。
因為在JavaScript語言中對待函數的方式與對待其它對象的方式相同,所以我們說函數是一等對象。
與對象的其它實例(例如String、Date或Number)一樣,只有在把函數賦值給變量、屬性或參數的時候,函數才能被引用。
我們經常通過字面值表示法來表示Number實例,例如下面的語句:
213;
完全有效,但同時完全無用。Number實例用處不大,除非將其賦值給屬性或對象,或者將其綁定到參數名稱上。否則,我們無法引用散落在內存中的實例。
同樣的規則也適用于Function對象的實例。
思考下面的代碼:
// 這不是創建了名為doSomethingWonderful的函數嗎?
function doSomethingWonderful(){
alert("does something wonderful");
}
盡管這種表示法可能看起來很熟悉,而且被廣泛用來創建頂層函數,但它與通過var來創建window屬性使用的是相同的語法。
function關鍵字自動創建一個Function實例并將其賦值給使用函數“名稱”創建的window屬性,如下所示:
doSomethingWonderful = function(){
alert("does something wonderful");
}
如果看起來覺得奇怪,考慮另一個使用完全相同形式的語句,但這次使用Number的字面值:
aWonderfulNumber = 213;
這個語句不足為奇,它與把函數賦值給頂層變量(window屬性)的語句如出一轍。
函數字面值表示法:由關鍵字function與緊接著的被圓括號所包含的參數列表,以及隨后的函數主體所組成。
記住,在HTML頁面中創建了頂層變量時,會將變量創建為window實例的屬性。因此,下面的語句都是等價的:
function hello(){alert("wow!");}
hello = function(){alert("wow!");}
window.hello = function(){alert("wow!")}
理解這一點很重要:和其它對象類型的實例一樣,Function實例可以賦值給變量、屬性或參數的值。并且就像其它的那些對象類型,無名稱無實體的實例毫無用處,只有將它們賦值給變量、屬性或參數,這樣才能引用它們。
2.1 作為回調的函數。function hello(){alert("Hey,guy!");}
setTimeout(hello, 5000);
當計時器過期時,hello函數會被調用。因為在代碼中setTimeout()方法回調了一個函數,所以該函數被稱為回調函數。
大部分高級JavaScript程序員可能會覺得這段代碼示例很幼稚,因為沒有必要創建hello名稱。除非要在頁面的其它地方調用此函數,否則沒有必要創建window的屬性hello來暫時存儲Function實例,以便將hello作為回調參數來傳遞。
所以,編寫這個片段更簡潔的方式是:
setTimeout(function(){alert("Hey,guy!")},5000);
目前為止,示例中所創建額函數要么是頂層函數(也就是頂層的window屬性),要么是在函數調用中被賦值給參數。我們也可以將Function實例賦值給對象的屬性,此時事情才真正變得有趣起來。
2.2 this到底是什么 OO語言自動提供了從方法中引用對象當前實例的辦法。在Java和C++這樣的語言中,this參數指向當前實例。
JavaScript實現中的this和其它OO語言中的this的差異體現在幾個重要的方面。
在JavaScript中函數是一等對象,它們不被聲明為任何東西的一部分,而this所引用的對象(稱為函數上下文)并不是由聲明函數的方式決定的,而是由調用函數的方式決定的。
在默認情況下,函數調用的上下文(this)是對象,其屬性包含用于調用該函數的引用。
頂層函數是window的屬性,因此當將其作為頂層函數來調用時,其函數上下文就是window對象。
盡管這可能是常見的隱式行為,但是JavaScript提供的辦法可以顯式地控制函數上下文設置為任何想要的內容。通過Function的方法call()或apply()來調用函數,就可以將函數上下文設置為任何想要的內容(是的,作為一等對象,函數甚至擁有Function構造器定義的那些方法)。
提示:使用call()方法來調用函數,指定第一個參數作為函數上下文的對象,而其余參數稱為被調用函數的參數;apply方法的工作方式與此類似,不同的是,它的第二個參數應該成為被調用函數參數的對象數組。
下面的代碼將說明函數上下文的值取決于調用函數的方式:
<span class="coffeescript">What<span class="hljs-string">"s this</span></span>
從上面的示例頁面表明了:函數上下文是基于每個調用來決定的,可以使用任何對象作為函數上下文來調用單個函數。因此,“函數是對象的方法”這個說法是絕對不正確的。
更為準確的表述應該為:
當對象o充當函數f的調用函數上下文時,函數f就充當了對象o的方法。
既然已經理解了函數如何充當對象的方法,下面就來把注意力轉移到另一個高級的函數話題——閉包。
2.3 閉包 閉包就是Function實例,它結合了來自環境的(函數執行所必需的)局部變量。
在聲明函數時,可以在聲明函數時引用函數作用域內任何變量。對于任何有技術背景的開發者來說,這是理所當然的事情。但是使用閉包時,即使在函數聲明之后,已經超出函數作用域(也就是關閉了函數聲明)的情況下,該函數仍然帶有這些變量。
閱讀下面的栗子:
<span class="hljs-attr">Closure</span> <span class="hljs-string">Example</span>
在運行這個頁面時,如果我們不熟悉閉包去查看這段代碼,我們會覺得有點問題,可能會猜測:因為回調會在頁面加載后3秒觸發(在就緒處理器執行完畢很久以后),所以local的值在回調函數的執行期間是未定義的。畢竟,local聲明所在的塊在就緒處理器執行完畢時超出了作用域,對吧?
然而,當我們運行時發現它能正常運行!
</>code
閉包允許回調訪問環境中的值,即使該環境已經超出了作用域。
當就緒處理器執行完畢,local聲明所在的塊超出了作用域,但是函數聲明所創建的閉包(包括local變量)在函數的生命周期內都保持在作用域中。
</>code
注意:在JavaScript中的閉包,其創建方式都是隱式的,而不像其它一些支持閉包的語言那樣需要顯示的語法。這是一把雙刃劍,一方面使得創建閉包很容易,但另一個方面這使得很難在代碼中發現閉包。
無意創建的閉包可能會帶來意料之外的結果。例如,循環引用可能導致內存泄露。內存泄露的典型示例就是創建后引用閉包中變量的DOM元素,這會阻止那些變量的回收。
閉包的另一重要特征是:函數上下文絕不會被包含為閉包的一部分。
閱讀下面這段代碼:
</>code
this.id = `someID`;
$("*").each(function(){
alert(this id);
}); // 這段代碼不會按照我們期望的方式執行
如果需要訪問在外部函數中作為函數上下文的對象,可以采用通常的習慣用法:在局部變量中創建this引用的副本,這個副本將會被包含在閉包中。
于是,考慮如下改變:
</>code
this.id = "someID";
var outer = this;
$("*").each(function(){
alert(this id);
});
局部變量outer被賦值為外部函數的函數上下文的引用,從而成為閉包的一部分,可以在回調函數中訪問此變量。改變后的代碼現在會彈出顯示字符串someID,包裝集($("*"))中有多少個元素就彈出多少次。
當使用jQuery命令(這些命令利用異步的回調函數)來創建優雅的代碼時,我們將會發現閉包是必不可少的,尤其是在Ajax請求和事件處理領域。
參考資料
jQuery實戰(第2版)
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/85427.html
摘要:的理解函數與其他語言函數最大的不同在于,其不介意傳入多少參數以及參數的類型比如函數的形參有兩個,但是調用函數傳入的參數可以寫一個,三個或不寫參數對應等,解析器都可以正常解析,這是因為中參數在內部是以一個數組形式來表示,故而不需要關系傳入參數 ECMAScript function的理解 ECMAScript 函數與其他語言函數最大的不同在于,其不介意傳入多少參數以及參數的類型...
摘要:方法用于刪除原數組的一部分成員,并可以在刪除的位置添加新的數組成員,返回值是被刪除的元素。,這兩個方法,返回一個布爾值,表示判斷數組成員是否符合某種條件。該函數接受三個參數當前成員當前位置和整個數組,然后返回一個布爾值。 數組基礎篇二 數組對象 1.聲明數組的方法 Array是 JavaScript 的原生對象,同時也是一個構造函數,可以用它生成新的數組。 var arr =new A...
摘要:通過設置我們可以將一些屬性鎖定,來防止別人的修改,這是一種防御編程形式,就像語言的內置對象一樣不過的內置對象都可以被隨意更改。可以使用來判斷某一個屬性是否可以枚舉。 對象管理器(defineProperty) 在 JavaScript 里面聲明一個變量,通常我們有三種方式可以實現: let person = {} // 對象字面量 let cat = new Objec...
摘要:對象有狀態對象具有狀態,同一對象可能處于不同狀態之下。中對象獨有的特色對象具有高度的動態性,這是因為賦予了使用者在運行時為對象添改狀態和行為的能力。小結由于的對象設計跟目前主流基于類的面向對象差異非常大,導致有不是面向對象這樣的說法。 筆記說明 重學前端是程劭非(winter)【前手機淘寶前端負責人】在極客時間開的一個專欄,每天10分鐘,重構你的前端知識體系,筆者主要整理學習過程的一些...
閱讀 1323·2023-04-26 03:05
閱讀 781·2021-10-19 11:43
閱讀 3229·2021-09-26 09:55
閱讀 835·2019-08-30 15:56
閱讀 991·2019-08-30 15:44
閱讀 1247·2019-08-30 15:44
閱讀 2728·2019-08-30 14:23
閱讀 3245·2019-08-30 13:13