JavaScript的作者在為JavaScript設(shè)計(jì)面向?qū)ο笙到y(tǒng)時(shí)鞠苟,借鑒了Self和Smalltalk這兩門基于原型的語言,選擇了基于原型的面向?qū)ο笙到y(tǒng)燃逻。
與傳統(tǒng)面向?qū)ο笳Z言的區(qū)別
跟傳統(tǒng)面向?qū)ο笳Z言相比队腐,JavaScript沒有提供傳統(tǒng)面向?qū)ο笳Z言中的類式繼承,而是通過原型委托的方式來實(shí)現(xiàn)對(duì)象與對(duì)象之間的繼承体箕,也沒有在語言層面提供抽象類和接口的支持。
創(chuàng)建對(duì)象
工廠模式
工廠模式是一種軟件工程領(lǐng)域的設(shè)計(jì)模式挑童,這種模式抽象了創(chuàng)建具體對(duì)象的過程。
function createPerson(name, age) {
const o = new Object();
o.name = name;
o.age = age;
o.sayHello = function () {
console.log('My name is ' + this.name);
};
return o;
}
const person1 = createPerson('leeper', 20);
構(gòu)造函數(shù)模式
ECMAScript中的構(gòu)造函數(shù)可以用來創(chuàng)建特定類型的對(duì)象跃须,除了運(yùn)行時(shí)自動(dòng)出現(xiàn)在執(zhí)行環(huán)境中的3原生構(gòu)造函數(shù)站叼,還可以創(chuàng)建自定義的構(gòu)造函數(shù),從而定義自定義對(duì)象類型的屬性和方法菇民。
function Person() {
this.name = name;
this.age = age;
this.sayHello = function () {
console.log('My name is ' + this.name);
};
}
const person1 = new Person('leeper', 20);
構(gòu)造函數(shù)與非構(gòu)造函數(shù)的區(qū)別在于調(diào)用的方式不同尽楔。如果是通過new操作符來調(diào)用,那它就可以作為構(gòu)造函數(shù)第练,否則阔馋,跟普通函數(shù)基本一樣。
使用new操作符會(huì)經(jīng)歷以下4個(gè)步驟:
- 創(chuàng)建一個(gè)新對(duì)象
- 將構(gòu)造函數(shù)的作用域賦給新的對(duì)象(this就指向了這個(gè)新對(duì)象)
- 執(zhí)行構(gòu)造函數(shù)中的代碼(為這個(gè)新對(duì)象添加屬性)
- 返回新對(duì)象
原型模式
創(chuàng)建的每一個(gè)函數(shù)都有一個(gè)prototype(原型)屬性娇掏,這個(gè)屬性是一個(gè)指針呕寝,指向函數(shù)的原型對(duì)象,這個(gè)對(duì)象的用途是包含可以由特定類型的所有實(shí)例共享的屬性和方法婴梧。在默認(rèn)情況下下梢,所有原型對(duì)象都會(huì)自動(dòng)獲得一個(gè)constructor(構(gòu)造函數(shù))屬性,這個(gè)屬性是一個(gè)指向prototype屬性所在函數(shù)指針塞蹭。
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.sayHello = function () {
console.log('My name is ' + this.name);
}
const person = new Person('leeper', 20);
console.log(person instanceof Object); // true
console.log(person instanceof Person); // true
console.log(person.constructor == Person); // true
console.log(person.constructor == Object); // false
調(diào)用構(gòu)造函數(shù)時(shí)會(huì)為實(shí)例添加一個(gè)指向最初原型的[[Prototype]]指針孽江,而把原型修改為另外一個(gè)對(duì)象就等于切斷了構(gòu)造函數(shù)與最初原型之間的聯(lián)系(指向Object):
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype = {
sayHello: function () {
console.log('My name is ' + this.name);
}
}
const person = new Person('leeper', 20);
console.log(person instanceof Object); // true
console.log(person instanceof Person); // true
console.log(person.constructor == Person); // false
console.log(person.constructor == Object); // true
對(duì)于上面的問題,可以手動(dòng)修復(fù):
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype = {
constructor: Person, // 手動(dòng)修復(fù)
sayHello: function () {
console.log('My name is ' + this.name);
}
}
const person = new Person('leeper', 20);
console.log(person instanceof Object); // true
console.log(person instanceof Person); // true
console.log(person.constructor == Person); // true
console.log(person.constructor == Object); // false
總結(jié)一下構(gòu)造函數(shù)番电、原型和實(shí)例的關(guān)系:每個(gè)構(gòu)造函數(shù)都有一個(gè)原型對(duì)象岗屏,原型對(duì)象都包含一個(gè)指向構(gòu)造函數(shù)的指針,而實(shí)例都包含一個(gè)指向原型對(duì)象內(nèi)部指針。
繼承
原型繼承
原型描述事物與事物之間的相似性这刷,可以通過原型描述兩個(gè)實(shí)物之間的相似關(guān)系來復(fù)用代碼婉烟,這種復(fù)用代碼的模式稱為原型繼承。
function Cat() {
}
Cat.prototype.say = function () {
return '喵喵';
}
Cat.prototype.climbTree = function () {
return 'I can climb tree';
}
function Tiger() {
}
Tiger.prototype = new Cat();
// Tiger繼承了Cat的climbTree的方法
// 重寫Cat的say方法
Tiger.prototype.say = function () {
return '嗷';
}
總結(jié)一下原型繼承:
- 要得到一個(gè)對(duì)象崭歧,不是通過實(shí)例化類隅很,而是找到一個(gè)對(duì)象作為原型并克隆它
- 對(duì)象會(huì)記住它的原型
- 如果對(duì)象無法響應(yīng)某個(gè)請(qǐng)求,它會(huì)把這個(gè)請(qǐng)求委托給它自己的原型
類繼承
ES6出了class語法糖率碾,隨之使用類繼承也變得沒那么繁瑣叔营。
class Rectangle {
constructor(length, width) {
this.length = length;
this.width = width;
}
getArea() {
return this.length * this.width;
}
}
class Square extends Rectangle {
constructor(length) {
super(length, length);
}
}
const square = new Square(4);
console.log(square.getArea()); // -> 16
console.log(square.instanceof Square); // -> true
console.log(square.instanceof Rectangle); // -> true
上面相當(dāng)與下面ES6之前代碼:
function Rectangle(length, width) {
this.length = length;
this.width = width;
}
Rectangle.prototype.getArea = function () {
return this.length * this.width;
}
function Square(length) {
Rectangel.call(this, length, length);
}
Square.prototype = Object.create(Rectangle.prototype, {
constructor: {
value: Square,
enumerable: true,
writable: true,
configurable: true
}
});
const square = new Square(4);
console.log(square.getArea()); // -> 16
console.log(square.instanceof Square); // -> true
console.log(square.instanceof Rectangle); // -> true
原型繼承和類繼承
原型繼承和類繼承是兩種認(rèn)知模式,本質(zhì)上都是為了抽象(復(fù)用代碼)所宰。原型繼承的便捷性表現(xiàn)在系統(tǒng)中對(duì)象較少的時(shí)候绒尊,原型繼承不需要構(gòu)造額外的抽象類和接口就可以實(shí)現(xiàn)復(fù)用。