JavaScript神奇的面向對象特性(上)

本文寫作時長5小時

OO語言都有一個特點,就是都存在類的概念。通過類自定義類型,創(chuàng)建對象實例。和這些OO語言不同,JavaScript中是沒有類的,又或者說原型對象就是JavaScript中的類。但是沒有類的JavaScript是如何創(chuàng)建對象呢?退一步講,在JavaScript中,對象到底是什么?

理解對象的本質(zhì)

ECMA中關于對象的定義

對象就是無序屬性的集合,屬性的值可以是基本值,函數(shù)或者其他對象

通過JavaScript提供的Ojbect()函數(shù),可以創(chuàng)建出一個簡單的對象實例

var person = new Object();
person.name = "fuckJapan";
person.age = 21;
person.sayHello = function () {
    alert(this.name)
}

創(chuàng)建了一個person對象,person有三個屬性,其中nameage是基本值,sayHello是一個函數(shù)。

這種寫法可以用字面值對象重寫為:

var person = {
    name:"fuckJapan",
    age:21,
    sayHello:function () {
        alert(this.name)
    }
};

這種語法看起來更加 封裝

上面兩種創(chuàng)建對象的方式,有點像手工作坊,每創(chuàng)建一次對象都要重新給對象添加屬性,賦值,不勝其煩。怎么解決?進工廠,上生產(chǎn)線。

工廠模式

工廠模式抽象出創(chuàng)建對象的工程。既然JavaScript中沒有類,所以就用函數(shù)代替,類似這樣:

function createPerson(name, age, job) {
    var obj = new Object();
    obj.name = name;
    obj.age = age;
    obj.job = job;
    obj.sayHello = function () {
        alert(this.name);
    }
    return obj;
}
var person1 = createPerson("fuckJapan1",21,"student");
var person2 = createPerson("fuckJapan2",22,"teacher");

有了生產(chǎn)線,工人們再也不用親自動手了,直接把原料放入函數(shù),duang!一個person,兩個person,統(tǒng)統(tǒng)new出來。工人解放了雙手,提高了生產(chǎn)力,效率蹭蹭上升。

我是誰,我來自哪里?

雖然工廠模式解決了創(chuàng)建多個類似對象的問題,但是卻沒有解決對象識別的問題。怎么辦?

構造函數(shù)模式

構造函數(shù)和普通的函數(shù)沒有任何區(qū)別,只是用new調(diào)用時會產(chǎn)生特殊的效果。另外構造函數(shù)有一些約定的規(guī)定,比如首字母大寫。

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayHello = function () {
        alert(this.name);
    }
}
var person1 = new Person("fuckJapan1",21,"student");
var person2 = new Person("fuckJapan2",22,"teacher");

這種模式中,函數(shù)中并沒有顯示創(chuàng)建對象,而是使用了 this 關鍵字給對象實例添加屬性。在構造函數(shù)的結尾也沒有return。在調(diào)用時,使用了new關鍵字。在用new調(diào)用構造函數(shù)時,一共做了這么幾件事:

  • 分配對象內(nèi)存,創(chuàng)建新對象
  • 將這個對象和this綁定,this指向這個對象
  • 執(zhí)行構造過程并返回對象(在構造函數(shù)末尾返回任何東西都不會起作用)

為什么這種模式解決了對象識別的問題呢?別著急,這是因為在person1person2中還包含一個名為constructor的屬性,這個屬性指向構造函數(shù)Person。從此person1person2不再是三無產(chǎn)品,是可溯源的正品,它們是由一個名為Person的構造工廠生產(chǎn)的。

alert(person1.constructor == Person)//true
alert(person2.constructor == Person)//true
alert(person1.constructor == person2.constructor)//true

alert(person1 instanceof Person)//true
alert(person2 instanceof Person)//true
alert(person1 instanceof Object)//true
alert(person2 instanceof Object)//true

person1person2為什么都是Object類型,這是因為在JavaScript中所有的對象都有一個共同的祖先Object

不走尋常路,把構造函數(shù)當做普通函數(shù)調(diào)用

Person("fuckJapan",23,"student");
alert(window.name);//fuckJapan

由于如果在瀏覽器中執(zhí)行,則構造函數(shù)中的屬性被添加到window對象中

指定作用域

var obj = new Object();
Person.call(obj,"fuckJapanAgain",25,"student");
alert(obj.name);//fuckJapanAgain
alert(obj instanceof Person)//false

這種偷雞摸狗的調(diào)用方法,就會導致生產(chǎn)出來的對象沒有廠家

構造函數(shù)雖然解決了對象的識別問題,但是卻沒有解決對象方法的重復創(chuàng)建問題,person1person2中的sayHello方法是兩個完全不同的方法,這是完全沒有必要的

alert(person1.sayHello == person2.sayHello)//false

補救措施,將方法拿到外面,指向全局函數(shù)

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayHello = sayHello;
}

function sayHello() {
    alert(this.name);
}

暫時解決了方法重復創(chuàng)建的問題,可是,把自己的方法拿到外面這種方式總是不妥的,全局作用域中的函數(shù)卻是特定對象的方法,這太詭異了。而且方法少了還好說,多了就太亂了。

原型模式

每個函數(shù)都有一個名為prototype的屬性,指向函數(shù)的原型對象,類似OC中的類對象。原型對象中保存著創(chuàng)建對象實例共享的方法和屬性。

function Person() {
}
Person.prototype.name = "fuckJapan";
Person.prototype.age = 29;
Person.prototype.job = "student";
Person.prototype.sons = ["son1","son2"];
Person.prototype.sayName = function () {
    alert(this.name);
}
var person1 = new Person();
var person2 = new Person();
alert(person1.name);//fuckJapan
alert(person2.name);//fuckJapan
alert(person1.sayName == person2.sayName);//true

構造函數(shù)變成了空函數(shù),原型對象可以看做是對象的模板,所有對象生成時,默認都有這些屬性和對應的值,其中引用類型是所有對象共享,非引用類型都是獨立的存儲。這樣雖然解決了每個對象實例方法重新創(chuàng)建的問題,但又帶來了新的問題,數(shù)據(jù)紊亂,其實不光這樣一個問題,還有創(chuàng)建出的對象屬性還需要一個個賦值,構造函數(shù)根本沒什么用

person1.name = "fuckJapanAgain";
alert(person1.name);//fuckJapanAgain
alert(person2.name);//fuckJapan

基本類型的并沒有受影響

person1.sons.push("son3");
alert(person1.sons);//son1,son2,son3
alert(person2.sons);//son1,son2,son3

由于引用類型是所有對象共享,所以數(shù)據(jù)亂了

說了這么多了,這個也有問題,那個也有問題,那么到底該怎么做呢?

組合使用構造函數(shù)和原型模式

在構造函數(shù)中創(chuàng)建每個對象需要單獨使用的屬性,比如sons。在原型中創(chuàng)建所有對象共享的屬性,比如sayHello。

對象實例中在構造函數(shù)中創(chuàng)建的屬性,每個都保留單獨的副本,而在原型中創(chuàng)建的引用類型屬性都共享同一份引用。同時這種模式還支持傳遞參數(shù),可謂一舉兩得。

function Person(name,age,job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.sons = ["son1","son2"];
}

Person.prototype = {
    constructor:Person,
    sayHello:function () {
        alert(this.name);
    }
}

var person1 = new Person("fuckJapan1",21,"student");
var person2 = new Person("fuckJapan2",26,"teacher");

person1.sons.push("son3");
alert(person1.sons);//son1,son2,son3
alert(person2.sons);//son1,son2

alert(person1.sayHello == person2.sayHello);//true

所有問題差不多都幾乎是完美解決,這就是JavaScript中神奇的『類』。

除此之外,還有動態(tài)原型模式,寄生構造函數(shù)模式穩(wěn)妥構造函數(shù)模式。讀者自己去研究。

說到面向對象,必須要說的就是繼承,JavaScript中的「類」已經(jīng)很神奇了,作為面向對象特性之一的繼承則更加神奇,各種花式繼承干到你哭。

欲知繼承如何,且聽下回分解...

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

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

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