JavaScript創(chuàng)建對(duì)象的幾種方法

在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è)空的。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容