最近閱讀了《JS高級程序設(shè)計3》,剛看完第六章面向?qū)ο?,于是將大體的核心概念整理總結(jié)一下。
1.理解對象
屬性類型
ECMAScript中有兩種屬性:數(shù)據(jù)屬性和訪問器屬性。
- 數(shù)據(jù)屬性
configurable:可配置性,表示能否通過delete刪除屬性,默認為false,
(注意:該屬性的修改不可逆一旦把configurable設(shè)為false,則表示該屬性變?yōu)椴豢膳渲玫牧?,此時不可再做任何屬性修改,否則會報錯);
Enumerable: 可遍歷性,表示能否通過for in遍歷屬性,默認為false;
writable: 可修改性,表示能否修改屬性值,默認為false;
value: 表示屬性的數(shù)據(jù)值;
- 訪問器屬性
configurable:可配置性,默認為true
Enumerable: 可遍歷性,表示能否通過for in遍歷屬性,默認為true
get:獲取屬性值的方法,默認為undefined
set:設(shè)置屬性值的方法,默認為undefined
定義屬性
- 定義單個屬性:
Object.defineProperty(對象名,屬性名,屬性定義)
- 定義多個屬性:
Object.defineProperties(對象名,{
屬性名1:屬性定義,
屬性名2:屬性定義,
.......
})
讀取屬性
Object.getOwnPropertyDescriptor(對象名,屬性名) // 返回的是一個對象
2. 創(chuàng)建對象
工廠模式
概念:通過函數(shù)封裝對象共有的屬性和方法,避免創(chuàng)建多個相似對象的問題
示例:
function Person(name) {
var o = new Object();
o.name = name;
o.sayName = function() {
alert(this.name);
};
return o;
}
var person = Person('測試');
問題:工廠模式創(chuàng)建的對象無法得知其類型。
構(gòu)造函數(shù)模式
概念:定義函數(shù),通過new操作符創(chuàng)建對象(任何函數(shù),通過new操作符創(chuàng)建,都是構(gòu)造函數(shù))
示例:
function Person(name) {
this.name = name;
this.sayName = function() {
alert(this.name);
};
}
var person = new Person('測試');
問題:構(gòu)造函數(shù)中定義的方法在每個實例上都會被重新創(chuàng)建一次(函數(shù)也是對象)
原型模式
理解原型對象
只要創(chuàng)建了一個新函數(shù),就會為該函數(shù)創(chuàng)建一個prototype屬性,這個屬性指向函數(shù)的原型對象,每個原型對象都會自帶一個constructor屬性,這個屬性指向該函數(shù)本身。
當(dāng)調(diào)用構(gòu)造函數(shù)創(chuàng)建一個新實例時,該實例將會包含一個指針(內(nèi)部屬性)[[Prototype]],指向構(gòu)造函數(shù)的原型對象。
當(dāng)在對象實例中聲明一個與原型中的屬性名相同的屬性時,這個屬性就會屏蔽原型中對應(yīng)的屬性,但不會修改原型中的屬性,通過delete方法可以刪除對象實例屬性。
上述描述可以舉個例子:
function Person() {}
Person.prototype.name = "測試";
Person.prototype.age = 18;
Person.prototype.job = "學(xué)生";
Person.prototype.sayName = function() {
alert(this.name);
};
var person1 = new Person();
person1.sayname();
我們可以依據(jù)上述的例子畫一個大概的概念圖:

判斷對象之間是否存在原型關(guān)系: isPrototypeOf()
獲取對象的原型: Object.getPrototypeOf(object)
判斷一個屬性是否在實例中: hasOwnProperty(屬性名)
判斷一個屬性是否在原型中或?qū)嵗? name(屬性名) in object(實例)
如何判斷屬性是原型中的屬性?
!object.hasOwnProperty(name) && (name in object)
返回一個包含所有可枚舉屬性的字符串?dāng)?shù)組: Object.keys()
獲取所有屬性(包括不可枚舉屬性): Object.getOwnPropertyNames()
原型的動態(tài)性
1.對原型對象的所有修改都能立即在實例對象上體現(xiàn)出來,即使修改是在聲明實例之后。
function Person() {}
var p = new Person();
Person.prototype.name = '哈哈哈';
console.log(p.name); // 哈哈哈
2.如果重寫了整個原型對象,則切斷了現(xiàn)有原型與原本實例的聯(lián)系,原本實例指向的還是最初的原型(實例指針僅指向原型,不指向構(gòu)造函數(shù))
function Person() {}
var p = new Person();
Person.prototype = {
constructor: Person,
name: '哈哈哈'
};
console.log(p.name); // undefined
原型模式的問題
- 所有實例在默認情況下都取得相同的屬性值。
- 屬性共享,對于引用類型的屬性,實例會共享屬性(當(dāng)其中一個實例修改了原型中的屬性時,其他實例也會一起被影響)。
function Person() {}
Person.prototype = {
constructor: Person,
colors: ["red"]
};
const p1 = new Person();
const p2 = new Person();
p1.colors.push("blue");
console.log(p1.colors); // ["red", "blue"]
console.log(p2.colors); // ["red", "blue"]
組合構(gòu)造函數(shù)和原型模式(最常用)
概念:用構(gòu)造函數(shù)定義實例屬性,用原型定義方法和共享屬性。
示例:
function Person(name) {
this.name = name;
}
Person.prototype = {
constructor: Person, // 重寫原型對象需要指明原型對象的構(gòu)造函數(shù),原因上文有指出
sayName: function() {
alert(this.name);
};
};
var p = new Person("測試");
動態(tài)原型模式
概念:通過在構(gòu)造函數(shù)中判斷某個方法是否已存在,來決定是否需要初始化原型。
示例:
function Person(name) {
this.name = name;
if (typeof this.sayName !== 'function') {
Person.prototype.sayName = function() {
alert(this.name);
};
}
}
var p = new Person("測試");
穩(wěn)妥構(gòu)造函數(shù)(適合在某些安全環(huán)境下工作)
概念:創(chuàng)建對象的實例方法不引用this,也不使用new調(diào)用構(gòu)造函數(shù)
示例:
function Person(name,year,job){
var o = new Object();
// 這里可以添加私有變量和方法
o.sayName = function () {
alert(name); // name值僅能通過sayName()方法訪問
}
return o;
}
var p = Person("測試"); // p是一個穩(wěn)妥對象:沒有公共屬性,方法也不引用this的對象
p.sayName();
3.繼承
原型鏈繼承
示例:
function Father() {
this.colors = ["red"];
}
father.prototype.getFather = function () {
return this.colors;
}
function Son() {}
// 繼承父類型
son.prototype = new father();
const son1 = new Son();
const son2 = new Son();
son1.colors.push("blue");
console.log(son1.getFather()); // ["red", "blue"]
console.log(son2.getFather()); // ["red", "blue"]
問題:
- 包含引用類型值的原型屬性會被所有實例共享,當(dāng)其中一個實例修改了原型中的引用類型屬性時,其他實例也會一起被影響。
- 沒辦法在不影響其他實例的情況下,向父類型的構(gòu)造函數(shù)傳參。
借用構(gòu)造函數(shù)繼承
概念:通過在子函數(shù)內(nèi)部調(diào)用父函數(shù)的構(gòu)造函數(shù)實現(xiàn)繼承。
示例:
function Parent(name) {
this.colors = ["red"];
this.name = name;
}
function Child() {
// 可以向父類型的構(gòu)造函數(shù)傳參
Parent.call(this, '測試');
}
const c = new Child();
c.colors.push('black');
// ["red", "black"]
console.log(c.colors);
const p = new Parent();
// ["red"] 避免了屬性共享的問題
console.log(p.colors);
問題:
- 因為函數(shù)方法都定義在構(gòu)造函數(shù)里,導(dǎo)致方法無法復(fù)用
- 父類型原型對子類型不可見
組合繼承(最常用)
概念:結(jié)合原型鏈繼承和借用構(gòu)造函數(shù)繼承。
示例:
function Parent(name){
this.name = "測試";
this.age = 12;
};
Parent.prototype.say = function() { console.log(this.age) };
// 子類繼承父類
function Child(name){
// 繼承屬性
Parent.call(this, name); // 第二次調(diào)用Parent
this.age = 13;
}
// 繼承方法
Child.prototype = new Parent(); // 第一次調(diào)用Parent
Child.prototype.constructor = Child;
問題:
- 父類型的構(gòu)造函數(shù)會被調(diào)用兩次
原型式繼承
概念:基于已有的對象創(chuàng)建新對象
示例:
/** 1. 不使用Object.create() **/
function createObj(obj) {
funcion F() {}
F.prototype = obj;
return new F();
}
var Person = {
name: '測試'
};
var p = createObj(Person);
/** 2. 使用Object.create() **/
var Person = {
name: '測試'
};
var p = Obejct.create(person, {
name: {
value: '修改測試數(shù)據(jù)'
}
);
問題:就像使用原型模式創(chuàng)建對象一樣,引用類型的屬性會被共享。
寄生式繼承
概念:結(jié)合原型式繼承,在內(nèi)部通過某種方式增強對象。
示例:
function createObj(obj) {
funcion F() {}
F.prototype = obj;
return new F();
}
function createPerson(original) {
// 方式1:
// const clone = createObj(original);
// 方式2:
const clone = Object.create(original);
clone.sayHi = function() {
alert("hi");
};
}
var Person = {
name: '測試'
};
var p = createPerson(Person);
p.sayHi(); // hi
問題:和構(gòu)造函數(shù)模式繼承一樣,因為方法在函數(shù)內(nèi)部定義,導(dǎo)致方法無法復(fù)用。
寄生組合式繼承(最理想)
概念:通過借用構(gòu)造函數(shù)繼承屬性,通過原型鏈的混成形式繼承方法(本質(zhì)就是不再通過調(diào)用父類型的構(gòu)造函數(shù)創(chuàng)建實例來指定子類型的原型,而只需創(chuàng)建一個父類型的原型的副本)。
示例:
function initialPrototype(son, father) {
// 1.創(chuàng)建一個父類型的原型副本(創(chuàng)建對象)
const prototype = Object.create(father.prototype);
// 2.指定副本的構(gòu)造函數(shù)(增強對象)
prototype.constructor = son;
// 3. 將該原型副本賦給子類型的原型(指定對象)
son.prototype = prototype;
}
function Parent(name){
this.name = "測試";
this.age = 12;
};
Parent.prototype.say = function() { console.log(this.age) };
// 子類繼承父類
function Child(name){
// 繼承屬性
Parent.call(this, name);
this.age = 13;
}
// 繼承方法(替換原本的 Child.prototype = new Parent() )
initialPrototype(Child, Parent);