JavaScript 系列博客之(四) 前言 本篇介紹 JavaScript 中的對象。在第一篇博客中已經說到 JavaScript 是一種‘’對象模型‘’語言。所以可以這樣說,對象是 JavaScript 語言的核心概念,也是最重要的數據類型。 概述 生成方法 在 JavaScript 中聲稱對象 ...
JavaScript 系列博客之(四)
前言
本篇介紹 JavaScript 中的對象。在第一篇博客中已經說到 JavaScript 是一種‘’對象模型‘’語言。所以可以這樣說,對象是 JavaScript 語言的核心概念,也是最重要的數據類型。
概述
生成方法
在 JavaScript 中聲稱對象相當方便,直接定義一個空字典就 ok。想要添加屬性或者方法的話可以在定義結束之後動態添加。註意:對象時無序的複合數據集合。
上面代碼中,大括弧就可以直接定義一個對象,被賦值給變數 a,所以 a 就指向一個對象。該對象為一個空對象,但是會有一些預設的方法,像 constructor 是構造方法,想要動態的添加屬性和方法就是這個方法的功勞。
在這裡添加了一個屬性為name,那麼 name 是鍵名(成員名稱),字元串 musibii 是鍵值(成員的值)。鍵名與鍵值之間用冒號分隔。如果再添加一個屬性,那麼屬性之間使用逗號分隔。
具體生成方法
// 1.單一對象
var obj = {
// 屬性
name: 'Zero',
// 方法
teach: function () {
console.log("教學");
}
};
obj.name | obj.teach()
// 2.構造函數
function Person(name) { // 類似於python中的類一樣來使用
// this代表Person構造函數實例化出的所有具體對象中的某一個
this.name = name;
this.teach = function () {
console.log(this.name + "正在教學");
}
}
// ①通過構造函數實例化出具體對象
// ②通過對象.語法調用屬性與方法
var p1 = new Person("張三");
p1.name // 張三, this指向p1對象
var p2 = new Person("李四");
p2.teach // 李四正在教學, this指向p2對象
// 3.ES6類語法
class Student {
// 需要構造器(構造函數)來完成對象的聲明與初始化
constructor (name) {
// 屬性在構造器中聲明並完成初始化
this.name = name;
}
// 類中規定普通方法
study () {
console.log(this.name + "正在學習");
}
// 類方法
static fn() {
console.log("我是類方法")
}
}
// 類中的普通方法由類的具體實例化對象來調用
// 類中的類方法由類直接來調用(這類型的方法大多是功能性方法,不需要對象的存在)
鍵名
對象的所有鍵名都是字元串(ES6又引入了 Symbol 值也可以作為鍵名:還沒瞭解過),所以加不加引號都可以。如果鍵名是數值,會被自動轉為字元串。如果鍵名不符合標識名的條件(比如第一個字元為數字,或者含有空格或運算符),且也不是數字,則必須加上引號,否則會報錯。
對象的每一個鍵名又稱為‘’屬性‘’,它的鍵值可以是任何數據類型。如果一個屬性的值為函數,通常把這個屬性稱為方法,調用方法和函數一樣。
特別的如果屬性的值指向的還是一個對象,那麼就行成了鏈式引用。對象的屬性之間用逗號分隔,最後一個屬性後面可以加逗號,也可以不加。
對象的引用
如果不同的變數名指向同一個對象,那麼它們都是這個對象的引用,也就是說指向同一個記憶體地址。修改其中一個變數,會影響到其他所有的引用。和 JavaScript 中的基本數據類型不一樣,複合數據類型是傳址傳遞。
a 和 b指向同一個對象,因此為其中任何一個變數添加屬性,另一個變臉都可以讀寫該屬性。如果取消某一個對象的引用,不會影響到其他變數。
這種引用只局限於對象,在之前的博客也提到,兩個變數指向同一個原始類型(基本數據類型)的值,那麼變數只是對值得拷貝(傳值傳遞)。
屬性的操作
屬性的讀取
讀取對象的屬性,有兩種方法,一種是使用點運算符;另一種是使用方括弧運算符。(在 python 中,字典只能通過方括弧取值;對象只可以通過點運算符取值。不過可以通過自定義字典類改寫 getattr 魔術方法改變。)
註意:如果使用方括弧運算符,鍵名必須放在引號裡面,否則會被當做變數處理。
var foo = 'bar';
var obj = {
foo: 1,
bar: 2
};
obj.foo // 1
obj[foo]// 2
上面代碼中,引用對象obj 的 foo 屬性時,如果使用點運算符,foo 就是字元串;如果使用方括弧運算符,但是不使用引號,那麼 foo 就是一個變數,指向字元串 bar。
方括弧運算符內部還可以使用表達式:
obj['hello' + 'world']
obj[3 + 3]
數字鍵可以不加引號,因為會自動轉為字元串。
var obj = {
0.7: 'hello world'
};
obj['0.7'] // 'Hello World'
obj[0.7] // 'Hello World'
上面代碼對象的數字鍵0.7加不加引號都可以,因為會自動轉為字元串。
var obj = {
123: 'Hello musibii'
};
obj.123 // 報錯
obj[123] // 'Hello musibii'
如果對數值鍵名123使用點運算符,會報錯,使用方括弧運算符才是正確的方式。
屬性的賦值
點運算符和方括弧運算符,不僅可以用來讀取值,還可以用來賦值。
JavaScript 允許屬性的後綁定,也就是說可以在任意時刻新增屬性,沒必要在定義對象的時候就把屬性全都定義好。
屬性的查看
查看一個對象的所有屬性使用 Object.keys 方法。
var obj = {
key1: 1,
key2: 2
};
Object.keys(obj); // [key1, key2]
屬性的刪除
delete 命令用於刪除對象的屬性,刪除成功後返回 true。
var obj = {
p: 1;
}
delete obj.p; // true
obj.p // undefined
Object.keys(obj) // []
上述代碼中,delete 命令刪除對象 obj 的 p 屬性。刪除後,再讀取 p 屬性就會返回 undefined,而且 Object.keys 方法的返回值也不再包括該屬性。
註意:刪除一個不存在的屬性,delete 不會報錯而是返回 true。因此不能根據 delete 命令的結果認為某個屬性的存在。(那麼到底哪種方式才可以證明某個屬性的存在與否)
如果刪除屬性時返回 false那就說明該屬性存在,但是不可以刪除。
var obj = Object.defineProperty({}, 'p', {
value: 'musibii',
configurable: false
});
obj.p // 'musibii'
delete obj.p // false
上述代碼中,通過 Object 的defineProperty方法給對象 obj創建了一個屬性,屬性的configurable(可配置) 的值為 false,這樣的一個屬性就是不可以刪除的。
另外需要註意的是,delete 命令只能刪除對象本身的屬性,無法刪除繼承的屬性。
可以看出雖然 delete 命令返回 true,但是刪除的屬性依然存在。但是如果通過 proto 刪除的話就可以刪除。
判斷屬性的存在
in 運算符用於檢出對象是否包含某個屬性(註意,檢查的是鍵名,不是鍵值)。如果包含就返回 true,否則就返回 false。它的左邊是一個字元串,表示屬性名,右邊則是一個對象。
var obj = {p: 1};
'p' in obj //true
'toString' in obj // true
拿上面刪除的 constructor 來說:
in 運算符的一個問題是,它不能識別哪些屬性時對象自身的,哪些屬性是繼承的。就像上面,對象 obj 本身並沒有 toString 屬性,但是 in 運算符會返回 true,因為這個屬性是繼承的。
這時可以通過對象的 hasOwnProperty 方法判斷,是否為對象自身屬性
var obj = {};
if ('toString' in obj) {
console.log(obj.hasOwnProperty('toString')); // fasle
}
屬性遍歷
for...in 迴圈用來遍歷一個對象的所有屬性。
for...in 迴圈有兩個註意點;
- 它遍歷的是對象所有可遍歷的屬性,會跳過不可遍歷的屬性;
- 它不僅遍歷對象自身的屬性,還遍歷繼承的屬性。
如果繼承的屬性是可遍歷的,那麼就會被 for...in 迴圈遍歷到。但是,一般情況下,都是只想遍歷對象自身的屬性,所以使用 for...in 的時候,應該結合使用 hasOwnProperty 方法,在迴圈內部判斷一下,某個屬性是否為對象自身的屬性。
var person = {name: '老張'};
for (var key in person) {
if (person.hasOwnProperty(key)) {
console.log(key);
}
} // name
with 語句
with 語句的格式如下:
with (對象) {
語句;
}
它的作用是操作同一個對象的多個屬性時,提供一些書寫的方便。
註意:如果 with 區塊內部有變數的賦值操作,必須是當前對象已經存在的屬性,否則會創造一個當前作用域的全局變數。
var obj = {};
with (obj) {
p1 = 4,
p2 = 5
}
obj.p1 // undefined
p1 // 4
上面代碼中,對象 obj 並沒有 p1屬性,對 p1賦值等於創造了一個全局變數 p1.正確的寫法應該是,先定義對象 obj 的屬性 p1,然後在 with 區塊內操作它。
這是因為 with 區塊沒有改變作用域,它的內部依然是當前作用域。這造成了with 語句的一個很大的弊病,就是綁定對象不明確。
with (obj) {
console.log(x);
}
單純從上面的代碼塊,根本無法判斷 x 到底是全局變數,還是對象 obj 的一個屬性。這非常不利於代碼的除錯和模塊化,編譯器也無法對這段代碼進行優化,只能留到運行時判斷,這就拖慢了運行速度。因此,建議不要使用 with 語句,可以考慮用一個臨時變數代替 with。
with (obj1.obj2.obj3) {
console.log(p1 + p2);
}
// 可以寫為
var temp = obj1.obj2.obj3;
console.log(temp.p1 + temp.p2);