JavaScript面向?qū)ο缶幊?/h2>

JavaScript的面向?qū)ο缶幊毯痛蠖鄶?shù)其他語(yǔ)言如Java、C#的面向?qū)ο缶幊潭疾惶粯印?br> 類和實(shí)例是大多數(shù)面向?qū)ο缶幊陶Z(yǔ)言的基本概念。
不過(guò),在JavaScript中,這個(gè)概念需要改一改。JavaScript不區(qū)分類和實(shí)例的概念,而是通過(guò)原型prototype來(lái)實(shí)現(xiàn)面向?qū)ο缶幊?br> 原型是指 當(dāng)我們想要?jiǎng)?chuàng)阿金xiaoming這個(gè)具體的學(xué)生時(shí),我們并沒(méi)有一個(gè)Student類型可用,那怎么辦,恰好有這么一個(gè)現(xiàn)成的對(duì)象:

var robot = {
    name: 'Robot',
    height: 1.6,
    run: function () {
        console.log(this.name + ' is running...');
    }
};
我們看robot對(duì)象有名字,有身高,還會(huì)跑,有點(diǎn)像小明,干脆就根據(jù)它來(lái)創(chuàng)建小明得了.
于是我們把它改名為Student,然后創(chuàng)建出xiaoming:
var Student = {
    name: 'Robot',
    height: 1.2,
    run: function () {
        console.log(this.name + ' is running...');
    }
};
var xiaoming = {
    name: '小明'
};
xiaoming.__proto__ = Student

注意最后一行代碼把xiaoming的原型指向了對(duì)象Student,看上去xiaoming放佛是從Student繼承下來(lái)的
xiaoming.name; // '小明'
xiaoming.run(); // 小明 is running...
xiaoming有自己的name屬性,但并沒(méi)有定義run()方法。不過(guò),由于小明是從Student繼承而來(lái),只要Student有run()方法,xiaoming也可以調(diào)用:
JavaScript的原型鏈和Java的Class區(qū)別就在,它沒(méi)有“Class”的概念,所有對(duì)象都是實(shí)例,所謂繼承關(guān)系不過(guò)是把一個(gè)對(duì)象的原型指向另一個(gè)對(duì)象而已。
要注意:上述代碼僅用于演示目的,在編寫(xiě)JavaScript代碼時(shí),不要直接用obj.proto去改變一個(gè)對(duì)象的原型,并且,低版本的IE也無(wú)法使用proto
Object.create()方法可以傳入一個(gè)原型對(duì)象,并創(chuàng)建一個(gè)基于該原型的新對(duì)象,但是新對(duì)象什么屬性都沒(méi)有,因此,我們可以編寫(xiě)一函數(shù)來(lái)創(chuàng)建xioaming
原型對(duì)象:

var Student = {
    name:'Robot',
    height:1.2,
    run: function () {
        console.log(this.name + 'is running...')
    }
};
function createStudent(name){
//基于Student原型創(chuàng)建一個(gè)新對(duì)象
var s = Object.create(Student);
//初始化新對(duì)象
s.name = name;
return s;
}
var xiaoming = createStudent('小明');
xiaoming.run();
xiaoming.__proto__ === Student;//true

創(chuàng)建對(duì)象
JavaScript對(duì)每個(gè)創(chuàng)建的對(duì)象都會(huì)設(shè)置一個(gè)原型,指向它的原型對(duì)象。
當(dāng)我們用obj.xxx訪問(wèn)一個(gè)對(duì)象的屬性時(shí),JavaScript引擎先在當(dāng)前對(duì)象上查找該屬性,如果沒(méi)有找到,就到其原型對(duì)象上找,如果還沒(méi)有找到,就一直上溯到Object.prototype對(duì)象,最后,如果還沒(méi)有找到,就只能返回undefined。
容易想到,如果原型鏈很長(zhǎng),那么訪問(wèn)一個(gè)對(duì)象的屬性就會(huì)因?yàn)榛ǜ嗟臅r(shí)間查找而變得更慢,因此要注意不要把原型鏈搞得太長(zhǎng)。

構(gòu)造函數(shù)
除了直接用{...}創(chuàng)建一個(gè)對(duì)象外,JavaScript還可以用一種構(gòu)造函數(shù)的方法來(lái)創(chuàng)建對(duì)象。它的用法是,先定義一個(gè)構(gòu)造函數(shù):

function Student(name) {
    this.name = name;
    this.hello = function () {
        alert('hello, ' + this.name + '!');
    }
}

這不是一個(gè)普通函數(shù)嗎?這確實(shí)是一個(gè)普通函數(shù),但是在JavaScript中,可以用關(guān)鍵字new來(lái)調(diào)用這個(gè)函數(shù),并返回一個(gè)對(duì)象:
var xiaoming = new Student('小明');
xiaoming.name; // '小明'
xiaoming.hello(); // Hello, 小明!
注意: 如果不寫(xiě)new,這就是一個(gè)普通的函數(shù),它返回undefined.但是如果寫(xiě)了new,它就變成了一個(gè)構(gòu)造函數(shù),它綁定的this指向新創(chuàng)建的對(duì)象,并默認(rèn)返回this,
也就是說(shuō),不需要再最后寫(xiě)return this;
新創(chuàng)建的xiaoming的原型鏈?zhǔn)牵簒iaoming ----> Student.prototype ----> Object.prototype ----> null
用new Student()創(chuàng)建的對(duì)象還從原型上獲得了一個(gè)constructor屬性,它指向函數(shù)Student本身.

image.png

紅色箭頭是原型鏈。注意,Student.prototype指向的對(duì)象就是xiaoming、xiaohong的原型對(duì)象,這個(gè)原型對(duì)象自己還有個(gè)屬性constructor,指向Student函數(shù)本身。

另外,函數(shù)Student恰好有個(gè)屬性prototype指向xiaoming、xiaohong的原型對(duì)象,但是xiaoming、xiaohong這些對(duì)象可沒(méi)有prototype這個(gè)屬性,不過(guò)可以用proto這個(gè)非標(biāo)準(zhǔn)用法來(lái)查看。

現(xiàn)在我們就認(rèn)為xiaoming、xiaohong這些對(duì)象“繼承”自Student。

xiaoming.name; // '小明'
xiaohong.name; // '小紅'
xiaoming.hello; // function: Student.hello()
xiaohong.hello; // function: Student.hello()
xiaoming.hello === xiaohong.hello; // false

xiaoming和xiaohong各自的name不同,這是對(duì)的,否則我們無(wú)法區(qū)分誰(shuí)是誰(shuí)了。

xiaoming和xiaohong各自的hello是一個(gè)函數(shù),但它們是兩個(gè)不同的函數(shù),雖然函數(shù)名稱和代碼都是相同的!

如果我們通過(guò)new Student()創(chuàng)建了很多對(duì)象,這些對(duì)象的hello函數(shù)實(shí)際上只需要共享同一個(gè)函數(shù)就可以了,這樣可以節(jié)省很多內(nèi)存。

要讓創(chuàng)建的對(duì)象共享一個(gè)hello函數(shù),根據(jù)對(duì)象的屬性查找原則,我們只要把hello函數(shù)移動(dòng)到xiaoming、xiaohong這些對(duì)象共同的原型上就可以了,也就是Student.prototype:

image.png

修改代碼如下:

function Student(name) {
    this.name = name;
}

Student.prototype.hello = function () {
    alert('Hello, ' + this.name + '!');
};

忘記寫(xiě)new怎么辦

如果一個(gè)函數(shù)被定義為用于創(chuàng)建對(duì)象的構(gòu)造函數(shù),但是調(diào)用時(shí)忘記了寫(xiě)new怎么辦?

在strict模式下,this.name = name將報(bào)錯(cuò),因?yàn)閠his綁定為undefined,在非strict模式下,this.name = name不報(bào)錯(cuò),因?yàn)閠his綁定為window,于是無(wú)意間創(chuàng)建了全局變量name,并且返回undefined,這個(gè)結(jié)果更糟糕。

所以,調(diào)用構(gòu)造函數(shù)千萬(wàn)不要忘記寫(xiě)new。為了區(qū)分普通函數(shù)和構(gòu)造函數(shù),按照約定,構(gòu)造函數(shù)首字母應(yīng)當(dāng)大寫(xiě),而普通函數(shù)首字母應(yīng)當(dāng)小寫(xiě),這樣,一些語(yǔ)法檢查工具如jslint將可以幫你檢測(cè)到漏寫(xiě)的new。

最后,我們還可以編寫(xiě)一個(gè)createStudent()函數(shù),在內(nèi)部封裝所有的new操作。一個(gè)常用的編程模式像這樣:

function Student(props) {
    this.name = props.name || '匿名'; // 默認(rèn)值為'匿名'
    this.grade = props.grade || 1; // 默認(rèn)值為1
}

Student.prototype.hello = function () {
    alert('Hello, ' + this.name + '!');
};

function createStudent(props) {
    return new Student(props || {})
}

這個(gè)createStudent()函數(shù)有幾個(gè)巨大的優(yōu)點(diǎn):一是不需要new來(lái)調(diào)用,二是參數(shù)非常靈活,可以不傳,也可以這么傳:

var xiaoming = createStudent({
    name: '小明'
});

xiaoming.grade; // 1

如果創(chuàng)建的對(duì)象有很多屬性,我們只需要傳遞需要的某些屬性,剩下的屬性可以用默認(rèn)值。由于參數(shù)是一個(gè)Object,我們無(wú)需記憶參數(shù)的順序。如果恰好從JSON拿到了一個(gè)對(duì)象,就可以直接創(chuàng)建出xiaoming。

最后編輯于
?著作權(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)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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