在JavaScript中,對象其實就是一組鍵值對的組合。
1、字面量對象(Object.Literals)
這是JS中創建對象的最簡單、最常見的方法之一,只需要在花括號內定義屬性及其值,如下所示:
let student = {name: 'Ross', rollno: 1};
// 或用Object構造
let person = new Object();
console.log(person); // {}
person.name = 'lisa';
person.age = 21;
這種方式會創建大量重復代碼。
2、構造函數創建(Constructor Functions)
在構造函數之前,先來說說工廠模式:為了解決對象字面量在創建多個相似對象時,會產生大量重復代碼的問題,于是有了工廠模式。
function createPerson (name, age) {
var o = new Object();
o.name = name;
o.age = age;
o.sayName = function () {
console.log(this.name);
};
return o;
}
var person1 = new createPerson('zhang3', 29);
var person2 = new createPerson('li4', 2);
但是工廠模式也有不足,無法解決對象識別的問題,創建的所有實例都是 Object 類型,不知道是具體誰誰誰的實例。
構造函數創建的方式更多是用來在JS中實現繼承、多態、封裝等特性。構造函數是通過this為對象添加屬性的。
比如,我們需要創建具有相同屬性/結構集的多個實例,this關鍵字是指一個對象,該對象是執行當前代碼位的任何對象。將new 關鍵字與函數名一起使用,將創建一個空對象,并且在該函數內部使用的this關鍵字將保留對該對象的引用。
function Animal (name) {
this.name = name;
this.say = function () {
console.log(this.name)
}
}
let cat = new Animal('Tom'); // Tom
let dog = new Animal('John'); // John
let lion = Animal('amy'); // undefined
//因為這里沒有加new,所有屬性都附加到了Windows對象。無法判斷它是誰的實例,只能判斷它是對象。
構造函數也有缺陷,就是其中的每個方法比如say(),在每次實例化時都會自動重新創建一遍,產生不同的作用域鏈,因此即使是同名函數也是不相等的,這樣會造成資源浪費。比如:
let a1 = new Animal('zz')
let a2 = new Animal('ww');
console.log(a1.say === a2.say); // false
所以就有了原型模式,使用原型模式的好處就是可以讓所有對象實例共享它所包含的屬性和方法。
function Person () {
}
Person.prototype.name = 'zz';
Person.prototype.sayName = function () {
console.log(this.name);
}
var person1 = new Person();
person1.sayName(); // zz
var person2 = new Person();
person2.sayName(); // zz
console.log(person1.sayName === person2.sayName); // true
這里將sayName()方法和所有的屬性都直接添加到了Person的prototype屬性中,構造函數就成了空函數,但是也能調用構造函數創建新對象,新對象的屬性和方法是所有實例共享的,也就是person1和person2訪問都是同一組屬性和同一個sayName()函數。但是原型模式也有缺點,當其中包含引用類型值屬性時會出現問題,如下:
function Person(){
}
Person.prototype = {
constructor: Person,
name: 'zzx',
age: '22',
job: 'Programmer',
friends: ['wc', 'rt'],
sayName: function(){
console.log(this.name);
}
};
var person1 = new Person();
var person2 = new Person();
person1.friends.push('lol');
console.log(person1.friends); //[ 'wc', 'rt', 'lol' ]
console.log(person2.friends); //[ 'wc', 'rt', 'lol' ]
由于數組存在于Person.prototype中,當向數組中添加了一個字符串時,所有的實例都會共享這個數組。
組合使用構造函數和原型模式:是目前最常見的創建自定義類型對象的方式。構造函數用于定義實例屬性,而原型模式用于定義方法和共享的屬性。通過構造函數傳遞參數,這樣每個實例都能擁有自己的屬性值,同時實例還能共享函數的引用,最大限度節省了內存空間。如下:
function Person (name, age) {
this.name = name;
this.age = age;
}
Person.prototype = {
constructore: Person,
sayName: function () {
console.log(this.name)
}
}
let person1 = new Person('king', 11);
let person2 = new Person('kkkk', 12);
console.log(person1.name); // king
console.log(person2.name); // kkkk
console.log(person1.sayName); // king
// 改變一個實例的屬性值
person2.name = 'jing';
// 不影響另一個實例的屬性值
console.log(person1.name); // king
console.log(person2.name); // jing
動態原型模式: 就是將原型對象放在構造函數內部,通過變量進行控制,只在第一次生成實例的時候進行原型的設置。相當于懶漢模式,只在生成實例時設置原型對象,其功能與構造函數和原型模式額混合模式是相同而。這是只有在sayName()不存在的情況下,才會將它添加到原型中。
function Person (name, age, job) {
this.name = name;
this.age = age;
this.job = job;
if (typeof this.sayName != "function") {
Person.prototype.sayName = function () {
console.log(this.name);
};
}
}
var person1 = new Person('zzx', 22, 'Programmer');
person1.sayName(); // zzx
3、Object.create()
ES5的新方法,我們可以使用Object.create()語法創建一個新對象,新對象的原型就是調用create方法時傳入的第一個參數,第二個參數為添加的可枚舉屬性(自身屬性)。如下:
// 例1
var a = {a: 1};
var b = Object.create(a); // b 的原型就是 a
console.log(b); // {}
b.__proto__ === a; // true
// 例2
// 把ross對象的屬性掛到ross對象的原型上
var ross = Object.create(Object.prototype, {
name: {
value: 'ross',
enumerable: true,
writable: true,
configurable: true
},
rollno: {
value: 1,
enumerable: true,
writable: true,
configurable: true
}
});
對于每個屬性,我們都將值、可枚舉、可寫和可配置的屬性設置為true,使用對象文字或構造函數時,這自動為我們完成。
優點: 支持當前所有非微軟版本或者 IE9 以上版本的瀏覽器。允許一次性地直接設置 proto 屬性,以便瀏覽器能更好地優化對象。同時允許通過 Object.create(null)來創建一個沒有原型的對象。
缺點:不支持 IE8 以下的版本;這個慢對象初始化在使用第二個參數的時候有可能成為一個性能黑洞,因為每個對象的描述符屬性都有自己的描述對象。當以對象的格式處理成百上千的對象描述的時候,可能會造成嚴重的性能問題。
4、class創建
class關鍵字是ES6新引入的一個特性,它其實是基于原型和原型鏈實現的一個語法糖。
class Animal {
constructor(name) {
this.name = name
}
}
let cat = new Animal('Tom');
Class 類中的constructor方法就相當于ES5中的構造函數,其實類中的所有方法都定義在了prototype上,prototype對象的constructor屬性也指向class類本身,被所有實例共享。不同的是,class類只能通過new操作符調用,不能像ES5 的構造函數一樣,當成普通函數調用。
constructor方法是類的默認方法,所有的類都有constructor方法,如果constructor方法沒有被顯式定義,js會自動添加一個空的。