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