本篇博客介紹一下JavaScript中對象和函數的相關概念。
Object
通過前面的學習,我們知道JavaScript中有六種數據類型:String、Number、Boolean、Null、Undefined和Object。其中前五種爲基本數據類型,最後一種爲引用數據類型。
對象屬於一種複合的數據結構,在對象中可以保存多個不同的數據類型的屬性。JS中對象分爲以下三類:
- 內建對象。由ES標準中定義的對象。在任何的ES的實現中都可以使用,如:Math、String、Number、Function;
- 宿主對象。由JS的運行環境提供的對象,目前來講主要指由瀏覽器提供的對象。如:BOM、DOM;
- 自定義對象。有開發人員自己創建的對象。
對象的創建
創建對象有兩種方式:
- 使用new關鍵字創建。
- 使用對象字面量創建一個對象。
注意:對象字面量的屬性名可以加引號,也可以不加,建議不加。如果要使用一些特殊的名字,則必須加引號。
向對象中添加屬性
語法:對象.屬性名 = 屬性值;
讀取對象中的屬性
語法:對象.屬性名;
- 如果讀取對象中沒有的屬性,不會報錯而是會返回undefined。
修改對象的屬性值
語法:對象.屬性名 = 新屬性值;
刪除對象的屬性
語法:delete 對象.屬性;
屬性名和屬性值
屬性名:
- 對象的屬性名不強制要求遵守標識符規範,什麼亂七八糟的名字都可以,但是推薦遵循標識符規範;
- 如果要使用特殊的屬性名,不能採用.操作符而應該採用[]操作符;
屬性值:
- JS對象的屬性值,可以是任意的數據類型。也可以是一個對象。
- 當然也可以是一個函數,因爲JS中函數也是一個對象。如果一個函數作爲一個對象保存,那麼我們稱這個函數是這個對象的方法。
in操作符
- 使用in操作符,可以檢查一個對象中是否含有指定的屬性。如果包含則返回true,否則返回false。
- 語法:
"屬性名" in 對象;
枚舉對象中的屬性
- 我們可以使用for…in語句來枚舉一個對象中的屬性。
基本數據類型和引用數據類型
基本數據類型:
- String、Number、Null、Boolean和Undefined都是基本數據類型;
- 基本數據類型的值直接在棧中存儲,值與值之間是獨立存在,修改一個變量不會影響其他變量;
- 基本數據類型之間使用==進行比較,比較的是兩個變量的值。
引用數據類型:
- Object是引用數據類型;
- 引用數據類型是保存在堆內存中的,每創建一個對象,就會在堆內存中開闢出一個新的空間,而變量保存的是對象的地址;
- 引用數據類型之間使用==進行比較,比較的是對象的內存地址。
Function
- 函數也是一個對象。函數中可以封裝一些功能(代碼),在需要時可以執行這些功能(代碼)。
- 函數中可以保存一些代碼在需要的時候調用,使用
typeof 函數;
返回的是function。
創建一個函數
創建函數有三種方法:
- 方法一:使用構造方法創建一個函數。將代碼以字符串的形式傳入構造方法。
- 方法二:使用函數聲明創建一個函數。
- 方法三:使用函數表達式創建一個函數。
參數和返回值
函數的參數:
- 調用函數時,解析器不會檢查實參的類型,所以要注意,是否有可能會接收到非法的參數。如果有可能接收到非法參數,需要對參數類型進行檢查;
- 解析器也不會檢查實參的數量,多餘的實參不會被賦值。
- 如果實參的數量少於形參,沒有對應實參的形參值爲undefined;
- 函數的實參可以是任意類型。
- 定義形參相當於在函數作用域中聲明瞭變量。
函數的返回值:
- 函數內部可以聲明一個函數。
- 函數返回值可以是任意的數據類型,也可以是一個對象,當然也可以是一個函數。
立即執行函數
- 我們知道函數定義完並不會立即執行,而是在函數被調用時纔會執行。
- 立即執行函數在函數定義完,立即就會被調用。
- 語法:
(function(){})();
- 立即執行函數只能執行一次。
call和apply方法
- 這兩個方法都是函數對象的方法,需要通過函數對象來調用;
- 當對函數對象調用call()和apply()都會調用函數執行;
- 在調用call和apply的時候,可以將一個對象指定爲第一個參數,此時這個對象將會成爲函數執行時的this。
- call方法可以將實參在對象之後依次傳遞,apply方法需要將實參封裝到一個數組中統一傳遞。
arguments
每次調用函數時,瀏覽器每次都會傳遞進兩個隱含的參數:
- 函數的上下文對象this;
- 封裝實參的對象arguments。
arguments是一個類數組對象。不是一個數組對象。
我們在調用函數時,我們所傳遞的實參都會在arguments中保存,我們可以通過arguments.length
來獲取實參的數量;也可以通過下標來使用傳遞來的實參,即使我們不定義形參。
arguments有一個屬性callee,這個屬性對應一個函數對象,就是當前正在執行的函數對象。
作用域
JS中有兩種作用域:
- 全局作用域;
- 函數作用域。
全局作用域
- 直接編寫在script標籤中的JS代碼,都在全局作用域中;
- 全局作用域在頁面打開的創建,頁面關閉時銷燬;
- 全局作用域中有一個全局對象window,我們可以直接使用。它代表的是一個瀏覽器窗口,由瀏覽器窗口創建,在全局作用域中,創建的變量都做爲window對象的屬性保存;創建的函數都會作爲window的方法保存。
- 全局作用域中的變量都是全局變量,在頁面的任意部分都可以訪問到。
變量的聲明提前:
- 變量未創建使用會報錯,屬性不存在使用不會報錯,返回undefined。
- 定義一個變量可以省略var關鍵字。
- 使用var關鍵字聲明的變量會在所有代碼執行之前被聲明;如果不使用var關鍵字,則變量不會被聲明提前。
函數的聲明提前:
- 使用函數聲明形式創建的函數:
function 函數名(){}
,會在所有的代碼執行之前就被創建,所以可以在函數聲明前調用;
- 使用函數表達式創建的函數,不會被聲明提前,不會被提前創建。
函數作用域
- 調用函數時創建函數作用域,函數執行完畢以後,函數作用域銷燬;
- 每調用一次函數就會創建一個新的函數作用域,它們之間相互獨立;
- 在函數作用域中可以訪問到全局作用域變量;
- 全局作用域中無法訪問到函數作用域中的變量。
- 當在函數作用域中操作一個變量時,它會先在自身作用域中尋找,如果有就直接使用,如果沒有則向上一級作用域尋找,直到全局作用域。如果全局作用域也沒有找到,則報錯;
- 如果想要在函數作用域中訪問全局作用域中的變量,可以使用
window.屬性
;
- 函數作用域中也有聲明提前特性。使用var關鍵字聲明的變量,會在函數中所有的代碼執行之前被聲明,函數聲明創建的函數也會在所有代碼執行之前創建;
- 函數作用域中不使用var關鍵字聲明的變量都會成爲全局變量。
this
- 解析器在調用函數時,會向函數內部傳遞一個隱含的參數,這個隱含的參數就是this;
- this指向的是一個對象,這個對象我們稱爲函數執行的上下文對象;
根據函數調用方式的不同,this會指向不同的對象。
- 當我們以函數的形式調用時,this永遠都是window。
- 以方法的形式調用時,this就是調用方法的那個對象。
- 以構造函數的形式調用的時候,this指向新創建的對象。
使用工廠方法創建對象
我們可以通過工廠方法來大批量的創建對象。
下面,我們來看一個例子來具體理解一下如何使用?
構造函數
- 構造函數就是一個普通的函數,創建方式和普通函數沒有區別;
- 構造函數習慣上首字母大寫;
- 構造函數和普通函數的區別就是調用的方式不同,普通函數直接調用,而構造函數需要使用new關鍵字來調用。
下面我們來看一個具體的例子:
構造函數執行流程:
- 立刻創建一個新的對象;
- 將新的對象設置爲函數的this,構造函數中的this指向新對象;
- 執行函數中的代碼;
- 將新創建的對象作爲返回值返回。
使用同一個構造函數構造出來的對象,我們稱爲一類對象。我們將通過一個構造函數創建的對象,稱爲是類的實例。
- 使用instanceof可以檢查一個對象是不是一個類的實例。語法:
對象 instanceof 構造函數;
。
- 所有對象都是Object的後代,任何對象 instanceof Object結果都是true。
原型對象
- 我們所創建的每一個函數,解析器都會向函數中添加一個屬性prototype;這個屬性對應着一個對象,這個對象就是我們所說的原型對象。
我們來具體畫一下示意圖:
- 如果函數作爲普通函數調用,prototype沒有任何作用;當函數以構造函數的方式調用時,它所創建的對象中都會有一個__proto__屬性指向該構造函數的原型對象;
- 我們知道Student構造函數中有prototype屬性指向其原型對象,同樣的使用Student構造出的對象中也有__proto__屬性,該屬性同樣指向Student的原型對象。原型對象相當於一個公共的區域,所有同一個類的實例都可以訪問到這個原型對象。
- 當我們訪問對象的一個屬性或方法時,它會先在對象自身中尋找,如果有則直接使用,如果沒有則會到原型對象中尋找,注意原型對象也是一個對象,也有其原型對象,所以會一直去原型對象中找,直到Object對象的原型,Object對象的原型沒有原型,如果在Object的原型中依然沒有找到,則返回undefined。
原型對象有什麼用呢?
首先,我們再來看一下前面的通過構造函數創建對象的代碼:
使用這種方式來創建對象是有問題的,Student函數中爲每一個對象都添加了info方法。方法是在構造函數內部創建的。構造函數每執行一次,就會創建一個info方法。而這些方法是一模一樣的,這是完全沒有必要的,我們完全可以讓所有的對象共享同一個方法。
如何解決這個問題呢?我們有兩種方法:
- 將info方法寫到全局作用域中;
這種方法雖然可以避免創建多個函數對象,但是將函數定義在全局作用域中會污染全局作用域的命名空間。而且定義在全局作用域中很不安全。 - 使用原型對象,將函數寫到原型對象中。
總結:
- 以後創建構造函數時,可以將這些對象共有的屬性和方法,同一添加到構造函數的原型對象中,這樣不用分別爲每一個對象添加,也不會影響到全局作用域,就可以使每個對象都具有這些屬性和方法了。
in和hasOwnProperty
- 我們可以使用in來檢查對象中是否具有某個屬性,如過對象中沒有,但是原型對象中有,也會返回true;
- 我們可以使用對象的hasOwnProperty()方法來檢查對象自身中是否包含指定屬性,使用該方法只有對象本身包含時才返回true,不包括原型對象中有。
toString
- 當我們在頁面中打印一個對象時,實際上輸出的是對象的toString方法的返回值。
- 如果我們希望在輸出對象時不輸出[object Object],可以爲對象添加一個toString()方法。