《JavaScript面向物件精要》之一:基本型別和引用型別
原始型別儲存為簡單資料值。引用型別 儲存為物件,其本質是指向記憶體位置的引用。
為了讓開發者能夠把原始型別和引用型別按相同的方式處理,JavaScript/">JavaScript 花費了很大的努力來保證語言的一致性。
其他程式語言用棧存原始型別,用對儲存引用型別。
而 JavaScript 則完全不同:它使用一個變數物件追蹤變數的生存期。
原始值被直接儲存在變數物件內,而引用值則作為一個指標儲存在變數物件內,該指標指向實際物件在記憶體中的儲存位置。
1.2 原始型別
原始型別代表照原樣儲存的一些簡單資料。
JavaScript 共有5種原始型別:
-
boolean布林,值為
true
orfalse
- number數字,值為任何整型或浮點數值
- string字串,值為由單引號或雙引號括住的單個字元或連續字元
- null空型別,僅有一個值:null
- undefined未定義,只有一個值:undefined(undefined 會被賦給一個還沒有初始化的變數)
JavaScript 和許多其他語言一樣,原始型別的變數直接儲存原始值(而不是一個指向物件的指標)。
var color1 = "red"; var color2 = color1; console.log(color1); // "red" console.log(color2); // "red" color1 = "blue"; console.log(color1); // "blue" console.log(color2); // "red" 複製程式碼
鑑別原始型別
鑑別原始型別的最佳方式是使用typeof
操作符。
console.log(typeof "Nicholas"); // "string" console.log(typeof 10);// "number" console.log(typeof true);// "boolean" console.log(typeof undefined);// "undefined" 複製程式碼
至於空型別(null)則有些棘手。
console.log(typeof null); // "object" 複製程式碼
對於 typeof null,結果是"object"。(其實這已被設計和維護 JavaScript 的委員會 TC39 認定是一個錯誤。在邏輯上,你可以認為null
是一個空的物件指標,所以結果為"object",但這還是很令人困惑。)
判斷一個值是否為空型別(null)的最佳方式是直接和null
比較:
console.log(value === null); // true or false 複製程式碼
注意:以上這段程式碼使用了三等號(全等 ===),因為三等號(全等)不會將變數強制轉換為另一種型別。
console.log("5" == 5); // true console.log("5" === 5); // false console.log(undefined == null); // true console.log(undefined === null); // false 複製程式碼
原始方法
雖然字串、數字和布林值是原始型別,但是它們也擁有方法(null 和 undefined 沒有方法)。
var name = "Nicholas"; var lowercaseName = name.toLowerCase(); // 轉為小寫 var count = 10; var fixedCount = count.toFixed(2); // 轉為10.00 var flag = true; var stringFlag = flag.toString(); // 轉為"true" console.log("YIBU".charAt(0)); // 輸出"Y" 複製程式碼
儘管原始型別擁有方法,但它們不是物件。JavaScript 使它們看上去像物件一樣,以此來提高語言上的一致性體驗。
1.3 引用型別
引用型別是指 JavaScript 中的物件,同時也是你在該語言中能找到最接近類的東西。
引用值是引用型別的例項,也是物件的同義詞(後面將用物件指代引用值)。
物件是屬性的無序列表。屬性包含鍵(始終是字串)和值。如果一個屬性的值是函式,它就被稱為方法。
除了函式可以執行以外,一個包含陣列的屬性和一個包含函式的屬性沒有什麼區別。
建立物件
有時候,把 JavaScript 物件想象成雜湊表可以幫助你更好地理解物件結構。
JavaScript 有好幾種方法可以建立物件,或者說例項化物件。第一種是使用new
操作符和建構函式。
建構函式就是通過new
操作符來建立物件的函式——任何函式都可以是建構函式。根據命名規範,JavaScript中的建構函式用首字母大寫來跟非建構函式進行區分。
var object = new Object(); 複製程式碼
因為引用型別不再變數中直接儲存物件,所以本例中的object
變數實際上並不包含物件的例項,而是一個指向記憶體中實際物件所在位置的指標(或者說引用)。這是物件和原始值之間的一個基本差別,原始值是直接儲存在變數中。
當你將一個物件賦值給變數時,實際是賦值給這個變數一個指標。這意味著,將一個變數賦值給另外一個變數時,兩個變數各獲得了一份指標的拷貝,指向記憶體中的同一個物件。
var obj1 = new Object(); var obj2 = obj1; 複製程式碼
物件引用解除
JavaScript 語言有垃圾收集的功能,因此當你使用引用型別時無需擔心記憶體分配。但最好在不使用物件時將其引用解除,讓垃圾收集器對那塊記憶體進行釋放。解除引用的最佳手段是將物件變數設定為null
。
var obj1 = new Object(); // dosomething obj1 = null; // dereference 複製程式碼
新增刪除屬性
在 JavaScript 中,你可以隨時新增和刪除其屬性。
var obj1 = new Object(); var obj2 = obj1; obj1.myCustomProperty = "Awsome!"; console.log(obj2.myCustomProperty); // "Awsome!" 因為 obj1 和 obj2 指向同一個物件。 複製程式碼
1.4 內建型別例項化
內建型別如下:
- Array陣列型別,以數字為索引的一組值的有序列表
- Date日期和時間型別
- Error執行期錯誤型別
- Function函式型別
- Object通用物件型別
- RegExp正則表示式型別
可使用new
來例項化每一個內建引用型別:
var items = new Array(); var now = new Date(); var error = new Error("Something bad happened."); var func = new Function("console.log('HI');"); var object = new Object(); var re = new RegExp(); 複製程式碼
字面形式
內建引用型別有字面形式。字面形式允許你在不需要使用new
操作符和建構函式顯示建立物件的情況下生成引用值。屬性的鍵
可以是識別符號或字串(若含有空格或其他特殊字元)
var book = { name: "Book_name", year: 2016 } 複製程式碼
上面程式碼與下面這段程式碼等價:
var book = new Object(); book.name = "Book_name"; book.year = 2016; 複製程式碼
雖然使用字面形式並沒有呼叫 new Object(),但是 JavaScript 引擎背後做的工作和 new Object() 一樣,除了沒有呼叫建構函式。其他引用型別的字面形式也是如此。
1.5 訪問屬性
可通過.
和中括號
訪問物件的屬性。
中括號[]
在需要動態決定訪問哪個屬性時,特別有用。因為你可以用變數
而不是字串字面形式來指定訪問的屬性。
1.6 鑑別引用型別
函式是最容易鑑別的引用型別,因為對函式使用typeof
操作符時,返回"function"。
function reflect(value){ return value; } console.log(typeof reflect); // "function" 複製程式碼
對其他引用型別的鑑別則較為棘手,因為對於所有非函式的引用型別,typeof
返回object
。為了更方便地鑑別引用型別,可以使用 JavaScript 的instanceof
操作符。
var items = []; var obj = {}; function reflect(value){ return value; } console.log(items instanceof Array); // true; console.log(obj instanceof Object); // true; console.log(reflect instanceof Function); // true; 複製程式碼
instanceof
操作符可鑑別繼承型別。這意味著所有物件都是Oject
的例項,因為所有引用型別都繼承自Object
。
雖然 instanceof 可以鑑別物件型別(如陣列),但是有一個列外。JavaScript 的值可以在同一個網頁的不用框架之間傳來傳去。由於每個網頁擁有它自己的全域性上下文—— Object、Array 以及其他內建型別的版本。所以當你把一個物件(如陣列)從一個框架傳到另外一個框架時,instanceof 就無法識別它。
1.8 原始封裝型別
原始封裝型別有3
種:String、Number 和 Boolean。
當讀取字串、數字或布林值時,原始封裝型別將被自動建立。
var name = "Nicholas"; var firstChar = name.charAt(0); // "N" 複製程式碼
這在背後發生的事情如下:
var name = "Nichola"; var temp = new String(name); var firstChar = temp.charAt(0); temp = null; 複製程式碼
由於第二行把字串當成物件使用,JavaScript 引擎建立了一個字串的實體讓charAt(0)
可以工作。字串物件的存在僅用於該語句並在隨後銷燬(一種被稱為自動打包的過程)。為了測試這一點,試著給字串新增一個屬性看看它是不是物件。
var name = "Nicholas"; name.last = "Zakas"; console.log(name.last); // undefined; 複製程式碼
下面是在 JavaScript 引擎中實際發生的事情:
var name = "Nicholas"; var temp = new String(name); temp.last = "Zakas"; temp = null; // temporary object destroyed var temp = new String(name); console.log(temp.last); temp = null; 複製程式碼
新屬性last
實際上是在一個立刻就被銷燬的臨時物件上而不是字串上新增。之後當你試圖訪問該屬性時,另一個不同的臨時物件被建立,而新屬性並不存在。
雖然原始封裝型別會被自動建立,在這些值上進行instanceof
檢查對應型別的返回值卻是false
。
這是因為臨時物件僅在值被讀取時建立
。instanceof
操作符並沒有真的讀取任何東西,也就沒有臨時物件的建立。
當然你也可以手動建立原始封裝型別。
var str = new String("me"); str.age = 18; console.log(typeof str); // object console.log(str.age); // 18 複製程式碼
如你所見,手動建立原始封裝型別實際會創建出一個object
。這意味著typeof
無法鑑別出你實際儲存的資料的型別。
另外,手動建立原始封裝型別和使用原始值是有一定區別的。所以儘量避免使用。
var found = new Boolean(false); if(found){ console.log("Found"); // 執行到了,儘管物件的值為 false } 複製程式碼
這是因為一個物件(如{}
)在條件判斷語句中總被認為是true
;
1.9 總結
第一章的東西都是我們一些比較熟悉的知識。但是也有一些需要注意的地方:
- 正確區分原始型別和引用型別
-
對於
5
種原始型別都可以用 typeof 來鑑別,而空型別必須直接跟null
進行全等比較。 -
函式也是物件,可用
typeof
鑑別。其它引用型別,可用instanceof
和一個建構函式來鑑別。(當然可以用Object.prototype.toString.call()
鑑別,它會返回 [object Array]之類的)。 -
為了讓原始型別看上去更像引用型別,JavaScript提供了
3
種封裝型別。JavaScript會在背後建立這些物件使得你能夠像使用普通物件那樣使用原始值。但這些臨時物件在使用它們的語句結束時就立刻被銷燬。雖然可手動建立,但不建議。