JS的面向?qū)ο缶幊淘O(shè)計

最近閱讀了《JS高級程序設(shè)計3》,剛看完第六章面向?qū)ο?,于是將大體的核心概念整理總結(jié)一下。

1.理解對象

屬性類型

ECMAScript中有兩種屬性:數(shù)據(jù)屬性和訪問器屬性。

  1. 數(shù)據(jù)屬性
configurable:可配置性,表示能否通過delete刪除屬性,默認為false,
(注意:該屬性的修改不可逆一旦把configurable設(shè)為false,則表示該屬性變?yōu)椴豢膳渲玫牧?,此時不可再做任何屬性修改,否則會報錯);

Enumerable: 可遍歷性,表示能否通過for in遍歷屬性,默認為false;

writable: 可修改性,表示能否修改屬性值,默認為false;

value: 表示屬性的數(shù)據(jù)值;
  1. 訪問器屬性
configurable:可配置性,默認為true

Enumerable: 可遍歷性,表示能否通過for in遍歷屬性,默認為true

get:獲取屬性值的方法,默認為undefined

set:設(shè)置屬性值的方法,默認為undefined

定義屬性

  1. 定義單個屬性:
Object.defineProperty(對象名,屬性名,屬性定義)
  1. 定義多個屬性:
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ù)上述的例子畫一個大概的概念圖:

原型.png

判斷對象之間是否存在原型關(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
原型模式的問題
  1. 所有實例在默認情況下都取得相同的屬性值。
  2. 屬性共享,對于引用類型的屬性,實例會共享屬性(當(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"]

問題:

  1. 包含引用類型值的原型屬性會被所有實例共享,當(dāng)其中一個實例修改了原型中的引用類型屬性時,其他實例也會一起被影響。
  2. 沒辦法在不影響其他實例的情況下,向父類型的構(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);

問題:

  1. 因為函數(shù)方法都定義在構(gòu)造函數(shù)里,導(dǎo)致方法無法復(fù)用
  2. 父類型原型對子類型不可見

組合繼承(最常用)

概念:結(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;

問題:

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

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