第六章 面向?qū)ο蟮某绦蛟O(shè)計(jì)(js高級(jí)程序設(shè)計(jì))

Object-Oriented 面向?qū)ο?/p>

理解對(duì)象

  • 對(duì)象屬性分為 【數(shù)據(jù)屬性】 和 【訪問(wèn)器屬性】
  • 對(duì)象屬性中的【數(shù)據(jù)屬性】
    1. configurable 描述該屬性能否通過(guò)delete刪除或被重新定義(為false時(shí)defineProperty也無(wú)法使用了)
    2. enumerable 能否被for-in循環(huán)返回屬性
    3. writable 能否修改屬性的值
    4. value 這個(gè)屬性的數(shù)據(jù)值
  • 利用defineProperty來(lái)定義數(shù)據(jù)屬性


    因?yàn)閣ritable屬性被設(shè)置為false,我嘗試修改xusheng的值,但是無(wú)效
  • 對(duì)象屬性中的的 【訪問(wèn)器屬性】
  1. configurable 描述該屬性能否通過(guò)delete刪除或被重新定義(為false時(shí)defineProperty也無(wú)法使用了)
  2. enumerable 能否被for-in循環(huán)返回屬性
  3. get 在讀取屬性時(shí)調(diào)用的函數(shù)。
  4. set 在寫入屬性時(shí)調(diào)用的函數(shù)。
當(dāng)獲取_name值時(shí),調(diào)用get方法得到name的值。當(dāng)設(shè)置_name值時(shí),其實(shí)時(shí)將name值改為了‘默認(rèn)值’

_ name 前面 _ 的書寫代表只能通過(guò)對(duì)象方法訪問(wèn)的屬性。

  • 定義多個(gè)屬性:defineProperties可以創(chuàng)建多個(gè)對(duì)象屬性


    例子創(chuàng)建了_year和edition兩個(gè)數(shù)據(jù)屬性和year訪問(wèn)器屬性
  • 讀取屬性的特性
    通過(guò)getOwnPropertyDescriptor獲取到的上面例子的屬性描述符
    var descriptor = Object.getOwnPropertyDescriptor(book, "_year");

上述book對(duì)象的_ year屬性的屬性描述符
  • 創(chuàng)建對(duì)象方法:
    1. new Object()
    2. 對(duì)象字面量{ }
      缺點(diǎn):使用同一個(gè)接口創(chuàng)建很多對(duì)象,會(huì)產(chǎn)生大量的重復(fù)代碼。

其他創(chuàng)建對(duì)象的方法

1. 工廠模式
這種模式抽象了創(chuàng)建具體對(duì)象的過(guò)程,用函數(shù)來(lái)封裝以特定接口創(chuàng)建對(duì)象的細(xì)節(jié)。

工廠模式用函數(shù)封裝了創(chuàng)建對(duì)象的過(guò)程

缺點(diǎn):工廠模式雖然解決了創(chuàng)建多個(gè)相似對(duì)象的問(wèn)題,但卻沒(méi)有解決對(duì)象識(shí)別的問(wèn)題(即怎樣知道一個(gè)對(duì)象的類型)。

2. 構(gòu)造函數(shù)模式
構(gòu)造函數(shù)可以用來(lái)創(chuàng)建特定類型的對(duì)象

書中的案例

  • 構(gòu)造函數(shù)開頭字母需要大寫(Person)

  • 在new 構(gòu)造函數(shù)時(shí),程序會(huì)執(zhí)行以下步驟

    1. 創(chuàng)建一個(gè)新對(duì)象
    2. 將構(gòu)造函數(shù)的作用域賦值給新對(duì)象
    3. 執(zhí)行構(gòu)造函數(shù)中的代碼
    4. 返回新對(duì)象
  • 對(duì)象的 constructor 是用來(lái)標(biāo)識(shí)對(duì)象類型的

  • 通過(guò)構(gòu)造函數(shù)創(chuàng)建對(duì)象有一個(gè)constructor屬性,指向原構(gòu)造函數(shù)

  • 創(chuàng)建得對(duì)象是構(gòu)造函數(shù)的實(shí)例


    實(shí)例和構(gòu)造函數(shù)關(guān)系
  • 以上構(gòu)造函數(shù)帶來(lái)的問(wèn)題,相同作用的函數(shù)被反復(fù)復(fù)制,改進(jìn):

sayName其實(shí)做了同一件事,但是卻被創(chuàng)建成了兩個(gè)不同的function,浪費(fèi)
將sayName拎出來(lái)
  • 新的問(wèn)題:全局下又不可能有多個(gè)類似于sayName函數(shù)的東西,會(huì)很亂

3. 原型模式

關(guān)系圖
實(shí)例的sayName均來(lái)自構(gòu)造函數(shù)的原型
構(gòu)造函數(shù)同樣實(shí)現(xiàn)效果

回到問(wèn)題本質(zhì),與構(gòu)造函數(shù)的區(qū)別在哪里?
新對(duì)象的這些屬性和方法是由所有實(shí)例共享(不會(huì)再創(chuàng)建相同作用的函數(shù))的。換句話說(shuō),person1 和 person2 訪問(wèn)的都是同一組屬性和同一個(gè) sayName()函數(shù)

  • 理解原型對(duì)象
我的關(guān)系圖:person1實(shí)例與構(gòu)造函數(shù)沒(méi)有直接關(guān)系,person1實(shí)例可以調(diào)用sayName是因?yàn)樗谠蛯?duì)象上進(jìn)行查找.!注意這里有個(gè)錯(cuò)誤:應(yīng)該是__proto__。兩個(gè)下劃線!
書中關(guān)系圖
  • __proto__ 是兩個(gè)下劃線??!

  • 確定實(shí)例和原型對(duì)象關(guān)系可以用:

  1. isPrototypeOf()判斷對(duì)象是否和原型對(duì)象有關(guān)
  2. Object.getPrototypeOf()獲得對(duì)象的原型對(duì)象

alert(Person.prototype.isPrototypeOf(person1)); //true
alert(Person.prototype.isPrototypeOf(person2)); //true

Object.getPrototypeOf(person1) == Object.getPrototypeOf(person1) //獲得person1對(duì)象指向的原型對(duì)象

  • 當(dāng)調(diào)用person1.sayname時(shí)。解析器會(huì)現(xiàn)在實(shí)例對(duì)象上查找sayname屬性,如果找不到,再在原型對(duì)象上查找sayname屬性。所以實(shí)例上的屬性會(huì)屏蔽掉原型上同名屬性

  • 通過(guò)delate操作符可以刪除實(shí)例上的屬性,從而恢復(fù)原型的訪問(wèn)

  • 利用hasOwnProperty(“屬性名”)可以判斷這個(gè)屬性是來(lái)自于原型對(duì)象還是自己本身

person1實(shí)例上name屬性來(lái)自本身還是來(lái)自于原型對(duì)象上
  • 與hasOwnProperty不同的是,使用in操作符只要能訪問(wèn)到該屬性都會(huì)返回true(無(wú)論是原型對(duì)象上還是實(shí)例對(duì)象上)
alert(person1.hasOwnProperty("name")); //false 
alert("name" in person1); //true name屬性在實(shí)例對(duì)象屬性中
  • hasPrototypeProperty(person1,"name") 實(shí)例對(duì)象要能訪問(wèn)到原型上的屬性才能返回true,即使原型對(duì)象上有屬性name,但是實(shí)例對(duì)象上的name將原型對(duì)象上的name屏蔽,所以依然返回false
var person = new Person(); 
alert(hasPrototypeProperty(person, "name")); //true  
person.name = "Greg"; 
alert(hasPrototypeProperty(person, "name")); //false  實(shí)例對(duì)象要能訪問(wèn)到原型上的屬性才能返回true
  • for in 枚舉屬性
    原則:所有開發(fā)人員定義的屬性都是可以枚舉的屬性,所以對(duì)象實(shí)例屬性和原型對(duì)象屬性都是可以被枚舉的。


    圖中紅色框中屬性都可被枚舉,黃色框中不可被枚舉,因?yàn)樗麄儊?lái)自原型鏈中的Object原型屬性
  • 取得對(duì)象上所有可枚舉的實(shí)例屬性:Object.keys()

  • 使用Object.key()取得對(duì)象上可枚舉的屬性組成的字符串?dāng)?shù)組

var person = new Person()
Object.key(person) //"say"  person實(shí)例對(duì)象上的屬性,原型對(duì)象上的不會(huì)被
console.log(Object.keys(person.__proto__))// "age,job,name,sayName" 原型上的屬性
  • Object.getOwnPropertyNames(object) 可以列出對(duì)象上所有的屬性,即使是不可枚舉的屬性,你可以試著枚舉出繼承的Object對(duì)象上的屬性
console.log(Object.getOwnPropertyNames(person.__proto__.__proto__))
  • 換一種方式定義構(gòu)造函數(shù)原型上的屬性
function Person(){ 
} 
Person.prototype = { 
 constructor : Person,  //如果沒(méi)有這句,constructor 屬性就指不到構(gòu)造函數(shù)了
 name : "Nicholas", 
 age : 29, 
 job: "Software Engineer", 
 sayName : function () { 
 alert(this.name); 
 } 
}; 

此方法帶來(lái)的問(wèn)題:constructor 的[[Enumerable]]值為true可枚舉了

使用Object.defineProperty重設(shè)構(gòu)造函數(shù)

Object.defineProperty(Person.prototype, "constructor", { 
 enumerable: false, 
 value: Person 
}); //此寫法適用于es5
  • 原型的動(dòng)態(tài)性:很好理解,實(shí)例對(duì)象(person1)的prototype屬性指針指向原型對(duì)象,所以在原型對(duì)象上多次修改屬性值,在實(shí)例上也同樣能被正確的訪問(wèn)
var friend = new Person(); 
Person.prototype.sayHi = function(){ 
 alert("hi"); 
}; 
friend.sayHi(); //"hi"(沒(méi)有問(wèn)題?。?

當(dāng)然,你不能重寫原型對(duì)象,切斷與構(gòu)造函數(shù)的聯(lián)系

function Person(){ 
} 
var friend = new Person(); 

Person.prototype = { 
 constructor: Person, 
 name : "Nicholas", 
 age : 29, 
 job : "Software Engineer", 
 sayName : function () { 
 alert(this.name); 
 } 
}; 
friend.sayName(); //error 
在new 構(gòu)造函數(shù)時(shí),構(gòu)造函數(shù)會(huì)為實(shí)例創(chuàng)造指針__proto__指向原型對(duì)象。重寫原型后,舊的實(shí)例依然和舊的原型保持著聯(lián)系,和新的原型沒(méi)有聯(lián)系
  • 原生對(duì)象的原型:原生(Array,String)構(gòu)造函數(shù)都在其原型(prototype)上定義了方法,所以我們可以通過(guò)例如String.substring()來(lái)操作字符串實(shí)例
String.prototype.startsWith() =function(){
  ...//你可以給原生對(duì)象添加屬性,讓所有實(shí)例化的字符串都能訪問(wèn),但是不建議這樣做
}

  • 原型對(duì)象的問(wèn)題:修改原型對(duì)象上的屬性值會(huì)對(duì)其他實(shí)例對(duì)象造成影響(我們有時(shí)候需要它[所有string的實(shí)例都能使用String.substring()],有時(shí)候又不需要它)

4. 組合使用構(gòu)造函數(shù)和原型模式

  • 書中想表達(dá)的意思就是物盡其用,將實(shí)例屬性放到構(gòu)造函數(shù)中定義,將用于共享的屬性和方法放到原型對(duì)象里
function Person(name, age, job){ 
 this.name = name; 
 this.age = age; 
 this.job = job; 
 this.friends = ["Shelby", "Court"]; 
} 
Person.prototype = { 
 constructor : Person, 
 sayName : function(){ 
 alert(this.name); 
 } 
} 

5. 動(dòng)態(tài)原型模式:在構(gòu)造函數(shù)中使用原型模式,并且避免重復(fù)掛載到原型模式
它把所有信息都封裝在了構(gòu)造函數(shù)中,而通過(guò)在構(gòu)造函數(shù)中初始化原型(僅在必要的情況下),又保持了同時(shí)使用構(gòu)造函數(shù)和原型的優(yōu)點(diǎn)
換句話說(shuō),第一次new Person()的時(shí)候,sayName肯定是不存在的,所以會(huì)掛載sayName和其他方法到原型上,而下一次newPerson的時(shí)候,sayName()已經(jīng)在原型上了[注意if判斷的作用],sayName包括其他if語(yǔ)句中的方法都不會(huì)被重復(fù)掛載!

  function Person(name, age, job) {
    //屬性
    this.name = name;
    this.age = age;
    this.job = job; //方法
    if (typeof this.sayName != "function") {
      Person.prototype.sayName = function () {
        alert(this.name);
      };

      Person.prototype.sayAge = function () {
        alert(this.age);
      };
    }
  }

6. 寄生構(gòu)造函數(shù)模式
類似于工廠模式,不過(guò)使用new function()的形式創(chuàng)建

function Person(name, age, job){ 
 var o = new Object(); 
 o.name = name; 
 o.age = age; 
 o.job = job; 
 o.sayName = function(){ 
 alert(this.name); 
 }; 
 return o; 
} 
var friend = new Person("Nicholas", 29, "Software Engineer"); 
friend.sayName(); //"Nicholas" 

這個(gè)模式可以在特殊的情況下用來(lái)為對(duì)象創(chuàng)建構(gòu)造函數(shù)。假設(shè)我們想創(chuàng)建一個(gè)具有額外方法的特殊
數(shù)組。由于不能直接修改 Array 構(gòu)造函數(shù),因此可以使用這個(gè)模式
注意:返回的對(duì)象與構(gòu)造函數(shù)或者與構(gòu)造函數(shù)的原型屬性之間沒(méi)有關(guān)系。 instanceof 不能溯源,所以建議在可以使用其他模式的情況下,不要使用這種模式。

7. 穩(wěn)妥構(gòu)造函數(shù)模式(閉包)

function Person(name, age, job){ 
 
 //創(chuàng)建要返回的對(duì)象
 var o = new Object(); 
 //可以在這里定義私有變量和函數(shù)
 //添加方法
 o.sayName = function(){ 
 alert(name); 
 }; 
 //返回對(duì)象
 return o; 
} 
var friend = Person("Nicholas", 29, "Software Engineer"); 
friend.sayName(); //"Nicholas" 

除了使用 sayName()方法之外,沒(méi)有其他辦法訪問(wèn) name 的值(有點(diǎn)用到了閉包,讓匿名函數(shù)返回私有變量)
同樣和寄生構(gòu)造模式一樣不能溯源,不能使用instanceof

繼承

  • 函數(shù)簽名(或者類型簽名,抑或方法簽名)定義了 函數(shù)或方法的輸入與輸出。JavaScript 是一種類型松散或動(dòng)態(tài)語(yǔ)言。這意味著您不必提前聲明變量的類型。以下是java的函數(shù)簽名
public static void main(String[] args)
  • 繼承分為接口繼承和實(shí)現(xiàn)繼承
    接口繼承:接口是一種特殊的抽象類,即繼承函數(shù)的簽名,故js中沒(méi)有接口繼承
    實(shí)現(xiàn)繼承:實(shí)現(xiàn)繼承是繼承函數(shù)實(shí)際的方法

  • 子類型超類型
    子類型:繼承者
    超類型:被繼承者(下文用父類型)

1.原型鏈

繼承圖解A繼承B

  • 注意此時(shí)的 instance 應(yīng)該是指向B的原型,因?yàn)锳的prototype被b的實(shí)例重寫了。

  • 在實(shí)例中搜索該屬性。如果沒(méi)有找到該屬性,則會(huì)繼續(xù)搜索實(shí)例的原型。在通過(guò)原型鏈實(shí)現(xiàn)繼承的情況下,搜索過(guò)程就得以沿著原型鏈繼續(xù)向上

  • 所有函數(shù)的默認(rèn)原型都是 Object 的實(shí)例:原型鏈的底層永遠(yuǎn)都是Object。因此默認(rèn)原型都會(huì)包含一個(gè)內(nèi)部指針,指向 Object.prototype(也就是繼承于Object原生)。這也是為什么所有自定義類型都會(huì)繼承toString(),valueOf()方法的原因。上圖的B其實(shí)還繼承了原生Object,如下圖。


    默認(rèn)原型都會(huì)指向Object.prototype,
  • 確定原型和實(shí)例的關(guān)系的方式:
    1.instanceof

alert(instance instanceof Object); //true 
alert(instance instanceof SuperType); //true 
alert(instance instanceof SubType); //true
  1. isPrototypeOf
alert(Object.prototype.isPrototypeOf(instance)); //true 
alert(SuperType.prototype.isPrototypeOf(instance)); //true 
alert(SubType.prototype.isPrototypeOf(instance)); //true 
  • 在完成繼承后再添加額外的方法,否則額外的方法會(huì)被繼承覆蓋掉。先.prototype = new Function(),再.prototype.xx=xx.更不能使用.prototype={ }字面量重寫原型
  • 原型繼承帶來(lái)的引用類型的問(wèn)題:原型中包含引用類型,A構(gòu)造函數(shù)的原型繼承于B的實(shí)例,基于A創(chuàng)建M實(shí)例,修改引用類型值,那么B的原型中的值也相應(yīng)會(huì)改變,如果再基于A創(chuàng)建N實(shí)例,那么M,N就會(huì)擁有相同的引用。
    不能向超類型的構(gòu)造函數(shù)傳遞參數(shù)

2.借用構(gòu)造函數(shù)

  • 利用apply或者call在自己的構(gòu)造函數(shù)中調(diào)用別人的構(gòu)造函數(shù)
function SuperType(name){ 
 this.name = name; 
} 
function SubType(){ 
 //繼承了 SuperType,同時(shí)還傳遞了參數(shù)
 SuperType.call(this, "Nicholas"); 
 
 //實(shí)例屬性
 this.age = 29; 
} 
var instance = new SubType(); 
alert(instance.name); //"Nicholas"; 
alert(instance.age); //29 
  • 缺點(diǎn)很明顯:方法都在構(gòu)造函數(shù)中定義,因此函數(shù)復(fù)用就無(wú)從談起。而且父類型原型中的方法子類型也用不了

3.組合繼承

  • 組合繼承是ji中常用的實(shí)現(xiàn)繼承的方式,將原型繼承和構(gòu)造函數(shù)繼承集合
  function f1(name) {
    this.name = name
    this.colors = ['blue', 'red', 'green'] //將需要被繼承的引用類型定義在構(gòu)造函數(shù)中
  }

  f1.prototype.sayName = function () {
    alert(this.name)
  }

  function f2(name, age) {
    f1.call(this, name)  //此時(shí),f2上已經(jīng)繼承f1構(gòu)造函數(shù)中的屬性
    this.age = age
  }

  f2.prototype = new f1(); //此時(shí),f2已經(jīng)繼承f1原型對(duì)象上的屬性

  var F2 = new f2()

  for (const x in F2) {
    console.log(x)//name color age sayname
  }

4.原型式繼承

  • 將一個(gè)對(duì)象作為子對(duì)象的原型對(duì)象實(shí)現(xiàn)繼承
  function object(o) {
    function F() { }
    F.prototype = o; //person對(duì)象被共享到了通過(guò)object創(chuàng)造出來(lái)的對(duì)象的原型上
    return new F();
  }
  var person = {
    name: "Nicholas",
    friends: ["Shelby", "Court", "Van"]
  };
  debugger
  var anotherPerson = object(person);
  anotherPerson.name = "Greg";
  anotherPerson.friends.push("Rob");
  var yetAnotherPerson = object(person);
  yetAnotherPerson.name = "Linda";
  yetAnotherPerson.friends.push("Barbie");
  alert(person.friends); //"Shelby,Court,Van,Rob,Barbie" 
  • Es5規(guī)范化方法:Object.creat()
var person = { 
 name: "Nicholas", 
 friends: ["Shelby", "Court", "Van"] 
}; 
var anotherPerson = Object.create(person); 
anotherPerson.name = "Greg"; 
anotherPerson.friends.push("Rob"); 
 
var yetAnotherPerson = Object.create(person); 
yetAnotherPerson.name = "Linda"; 
yetAnotherPerson.friends.push("Barbie"); 
alert(person.friends); //"Shelby,Court,Van,Rob,Barbie" 

在沒(méi)有必要興師動(dòng)眾地創(chuàng)建構(gòu)造函數(shù),而只想讓一個(gè)對(duì)象與另一個(gè)對(duì)象保持類似的情況下,原型式
繼承是完全可以勝任的.

  • 個(gè)人理解:其實(shí)就是原型鏈繼承的縮寫方案,同樣存在原型鏈繼承引用類型被共享的問(wèn)題

5.寄生式繼承

  function createAnother(original) {
    var clone = Object.create(original); //通過(guò)調(diào)用函數(shù)創(chuàng)建一個(gè)新對(duì)象
    clone.sayHi = function () { //以某種方式來(lái)增強(qiáng)這個(gè)對(duì)象
      alert("hi");
    };
    return clone; //返回這個(gè)對(duì)象
  }
  var person = {
    name: "Nicholas",
    friends: ["Shelby", "Court", "Van"]
  };
  var anotherPerson = createAnother(person);
  anotherPerson.sayHi(); //"hi" 

注意點(diǎn): clone.sayHi 可以給新對(duì)象上添加屬性,和寄生構(gòu)造函數(shù)模式有點(diǎn)像,createAnother存在的意義就是在新的實(shí)例上添加公有屬性

6.寄生組合式繼承

  • 還記得組合繼承模式嗎?通過(guò)f2.call(this)和.protype=new f2()分別調(diào)用了兩次父類型,寄生組合式繼承就是為了解決這個(gè)問(wèn)題。
  • inheritPrototype接受兩個(gè)參數(shù),分別是子類型的構(gòu)造函數(shù)和夫類型的構(gòu)造函數(shù)
function inheritPrototype(subType, superType){ 
 var prototype = Object.creat(superType.prototype); //創(chuàng)建對(duì)象
 prototype.constructor = subType; //增強(qiáng)對(duì)象
 subType.prototype = prototype; //指定對(duì)象
} 
function SuperType(name){ 
 this.name = name; 
 this.colors = ["red", "blue", "green"]; 
} 
SuperType.prototype.sayName = function(){ 
 alert(this.name); 
}; 
function SubType(name, age){ 
 SuperType.call(this, name); 
 this.age = age; 
} 
inheritPrototype(SubType, SuperType);   //SubType.protoype = new SuperType();
SubType.prototype.sayAge = function(){ 
 alert(this.age); 
}; 
  • 只調(diào)用了一次 SuperType 構(gòu)造函數(shù),并且因此避免了在 SubType. prototype 上面創(chuàng)建不必要的、多余的屬性
  • 個(gè)人覺(jué)得是拷貝父類型的prototype到子類型的protype上,inheritPrototype的作用其實(shí)就是SubType.protoype = new SuperType();

ps:終于看完了,真特么累,感覺(jué)迷迷糊糊的,肯定要刷第二遍的~。

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

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

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