JS高程學(xué)習(xí)-第六章(三)---對(duì)象繼承

對(duì)象繼承

1. 理解原型鏈
  1. 所有引用類型(函數(shù)、對(duì)象、數(shù)組),都存在對(duì)象特性,即可以自由拓展屬性。(除了null以外)

  2. 所有的引用類型(函數(shù)、對(duì)象、數(shù)組),都有一個(gè)__proto__(我們這里稱他為隱形原型)屬性,屬性值是一個(gè)普通的對(duì)象。

  3. 所有函數(shù)都有一個(gè)prototype屬性,屬性值也是一個(gè)普通的函數(shù)

  4. 所有的引用類型(函數(shù)、對(duì)象、數(shù)組),*proto屬性值指向它的構(gòu)造函數(shù)的 prototype(顯性屬性)屬性值。

  5. 當(dāng)試圖得到一個(gè)對(duì)象的某個(gè)屬性時(shí),如果這個(gè)對(duì)象本身沒有這個(gè)屬性,那么會(huì)去它的_proto_*(即他的構(gòu)造函數(shù)的prototype)中尋找。如果沒有,則會(huì)接著往上找,一直上溯到Object.prototype,也就是說所有對(duì)象都繼承Object.prototype的屬性,Object.prototype的原型是null,null沒有任何屬性和方法。

構(gòu)造函數(shù)、原型和實(shí)例的關(guān)系

  1. 每個(gè)構(gòu)造函數(shù)(函數(shù))都有一個(gè)原型對(duì)象 prototype
  2. 原型對(duì)象都包含一個(gè)指向構(gòu)造函數(shù)的指針 construtor
  3. 而實(shí)例都包含一個(gè)指向原型對(duì)象的內(nèi)部指針__proto__
  4. 實(shí)例的 __proto__ 指向構(gòu)造函數(shù)的 prototype

把一個(gè)對(duì)象的__proto__指向另一個(gè)原型對(duì)象,而這個(gè)原型對(duì)象的__proto__又會(huì)指向另一個(gè)原型對(duì)象,這些就會(huì)形成原型鏈

特殊的Function

  • 函數(shù)都是由Function構(gòu)造出來的,F(xiàn)unction作為函數(shù),是由其自身構(gòu)建出來,故Function的原型指針指向其自身的原型對(duì)象。
console.log(Function.__proto__ === Function.prototype); // true
console.log(Function.prototype.constructor === Function);
function Person (name,age){
    this.name = name ; 
    this.age= age; 
    this.class = ['en','math'];
    this.sayName = function(){
        alert(this.name);
    }
}   

Person.prototype.like = 'fruit';

繼承 我們需要繼承什么?

繼承的最終目的 :用最少的代碼 可以實(shí)現(xiàn)繼承公有屬性和方法的同時(shí),擁有自己的屬性和方法

2.原型鏈繼承

原理 讓新實(shí)例的原型等于父類的實(shí)例

優(yōu)點(diǎn) 實(shí)例可繼承的屬性有:實(shí)例的構(gòu)造函數(shù)的屬性,父類構(gòu)造函數(shù)屬性,父類原型的屬性。(新實(shí)例不會(huì)繼承父類實(shí)例的屬性?。?/p>

缺點(diǎn)

1.新實(shí)例無法向父類構(gòu)造函數(shù)傳參。

2.繼承單一。只能繼承一個(gè)父類

3.所有新實(shí)例都會(huì)共享父類原型的屬性。(原型上的屬性是共享的,一個(gè)實(shí)例修改了原型屬性,另一個(gè)實(shí)例的原型屬性也會(huì)被修改!)

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

Per.prototype = new Person();

var per = new Per("la");
var per2 = new Per("la");

per2.class.push('ss') // 

per.class //

per.__proto__ --> (Per.prototype = new Person)
Per.prototype.__proto__-->Person.prototype
Person.prototype.__proto__ -->Object.prototype
Object.prototype.__proto__ --> null

原型鏈繼承

如圖 藍(lán)色鏈為 原型鏈 紅色為構(gòu)造函數(shù)和原型的關(guān)系

3.借用構(gòu)造函數(shù)繼承 偽造對(duì)象 或經(jīng)典繼承

原理 用.call()和.apply()將父類構(gòu)造函數(shù)引入子類函數(shù)(在子類函數(shù)中做了父類函數(shù)的自執(zhí)行(復(fù)制))

優(yōu)點(diǎn)傳遞參數(shù)

1、只繼承了父類構(gòu)造函數(shù)的屬性,沒有繼承父類原型的屬性。

2、解決了原型鏈繼承缺點(diǎn)1、2、3。

3、可以繼承多個(gè)構(gòu)造函數(shù)屬性(call多個(gè))。

4、在子實(shí)例中可向父實(shí)例傳參。

缺點(diǎn)

1、只能繼承父類構(gòu)造函數(shù)的屬性。

2、無法實(shí)現(xiàn)構(gòu)造函數(shù)的復(fù)用。(每次用每次都要重新調(diào)用)

3、每個(gè)新實(shí)例都有父類構(gòu)造函數(shù)的副本,臃腫。(構(gòu)造函數(shù)缺點(diǎn) 所有屬性都綁定在對(duì)應(yīng)的對(duì)象上)

function Con(name){
    Person.call(this,"jer",10)// 調(diào)用了 父構(gòu)造函數(shù) 可以傳參 提高自由度
    // Person2.call(this,"") 多個(gè)構(gòu)造函數(shù)  多繼承
    this.name = name;
}

var con1 = new Con('rr');

console.log(con1 instanceoof Person)

4.組合繼承(組合原型鏈繼承和借用構(gòu)造函數(shù)繼承)(常用)

原理 結(jié)合了兩種模式的優(yōu)點(diǎn),傳參和復(fù)用

優(yōu)點(diǎn)

1、可以繼承父類原型上的屬性,可以傳參,可復(fù)用。

2、每個(gè)新實(shí)例引入的構(gòu)造函數(shù)屬性是私有的。

缺點(diǎn) 調(diào)用了兩次父類構(gòu)造函數(shù)(耗內(nèi)存),

// 子類的構(gòu)造函數(shù)會(huì)代替原型上的那個(gè)父類構(gòu)造函數(shù)(沒有理解)。

function C(name){
    Person.call(this,name);//構(gòu)造函數(shù)繼承屬性
}

C.prototype = new Person();//原型繼承方法  new的時(shí)候調(diào)用第二次 

var per1=new C("aa");

per1.name;   // 構(gòu)造函數(shù)屬性
per1.age;  // 原型屬性

5.原型式繼承

原理 先創(chuàng)建了一個(gè)臨時(shí)性的構(gòu)造函數(shù),然后將傳入的對(duì)象作為個(gè)構(gòu)造函數(shù)的原型,最后返回這個(gè)構(gòu)造函數(shù)的實(shí)例 ,這個(gè)函數(shù)就變成了個(gè)可以隨意增添屬性的實(shí)例或?qū)ο?。object.create()就是這個(gè)原理 。

優(yōu)點(diǎn)類似于復(fù)制一個(gè)對(duì)象,用函數(shù)來包裝。

缺點(diǎn)

1、所有實(shí)例都會(huì)繼承原型上的屬性(共享問題)。

2、無法實(shí)現(xiàn)復(fù)用。(新實(shí)例屬性都是后面添加的) (不能傳參)

function D(obj){
    function F(){}
    F.prototype = obj;
    return new F();
}

var per4 = new Person()
var per5 = D(per4);
console.log(per5.name)


per5 instanceof Person //

//
new 默認(rèn)原型對(duì)象 create 指定原型對(duì)象

Object.create()是Object的內(nèi)置方法,可以創(chuàng)建一個(gè)新對(duì)象,使用現(xiàn)有的對(duì)象來提供新創(chuàng)建的對(duì)象__proto__

Object.create ( proto, [ propertiesObject ] ) 

方法內(nèi)部定義一個(gè)新的空對(duì)象obj
將obj.__proto__的對(duì)象指向傳入的參數(shù)proto
將傳入的對(duì)象屬性復(fù)制到obj并且返回obj


6.寄生式繼承

原理 就是給原型式繼承外面套了個(gè)殼子。

優(yōu)點(diǎn) 傳參

缺點(diǎn) 沒用到原型,無法復(fù)用

function D(obj){
    function F(){}
    F.prototype = obj;
    return new F();
}
var per4 = new Person();

function E(obj,name){
    var sub = D(obj);// 繼承原型
    sub.name = name; //在原來的基礎(chǔ)上加上私有的東西
    return sub;
}
var per6 = E(per4,'ee');

// 給原型式繼承 加個(gè)處理函數(shù)傳參
7.寄生組合式繼承(最理想)

原理

寄生:在函數(shù)內(nèi)返回對(duì)象然后調(diào)用

組合:1、函數(shù)的原型等于另一個(gè)實(shí)例。

? 2、在函數(shù)中用apply或者call引入另一個(gè)構(gòu)造函數(shù),可傳參

優(yōu)點(diǎn) 修復(fù)了組合繼承的問題

缺點(diǎn) 過于繁瑣,故不如組合繼承

// 寄生
function G(obj){
    function F(){};
    F.prototype = obj;
    return new F();
}
// G 是 F實(shí)例的另一種表示
var g1 = G(Person.prototype);

//組合
function Sub(){
    Person.call(this);
}
//重點(diǎn)
Sub.prototype = g1;  //繼承 實(shí)例
g1.constructor = Sub; // 修復(fù) 實(shí)例
var sub1 = new Sub(); 
//sub1 就繼承了繼承 函數(shù)屬性,父類實(shí)例,g1 的函數(shù)屬性 

sub1.age;

8.ES6繼承 (class)(extends)

原理Class之間通過使用extends關(guān)鍵字,這比通過修改原型鏈實(shí)現(xiàn)繼承,要方便清晰很多

新的class寫法只是讓對(duì)象原型的寫法更加清晰、更像面向?qū)ο缶幊痰恼Z法而已。

相當(dāng)于構(gòu)造函數(shù)的另一種寫法
class Point{
    constructor(x,y){
        this.x = x;
        this.y = y;
    }
    toString(){
      console.log(this.x);   
    }
}
class Colorpoint extends Point {
    //這個(gè)就是默認(rèn)方法  使用new 生成實(shí)例時(shí)  會(huì)調(diào)用這個(gè)方法,
    //如果未定義 會(huì)自動(dòng)添加 
    
    constructor(x,y,color){
        
        //子類必須在constructor方法中調(diào)用super方法,否則新建實(shí)例時(shí)會(huì)報(bào)錯(cuò)
        //這是因?yàn)樽宇悰]有自己的this對(duì)象,而是繼承父類的this對(duì)象,然后對(duì)其進(jìn)行加工,如果不調(diào)用super方法,子類就得不到this對(duì)象。
        super(x,y); //調(diào)用父類構(gòu)造函數(shù)(Point.prototype.constructor.call(this,x,y))
        this.color = color
        
        // 隱式返回 this
        // 如果顯示返回對(duì)象 就是該對(duì)象
    }
    toString(){
        //通過 super調(diào)用父類的方法
        return this.color + ' ' + super.toString(); 
    }
}

class A extends B{}
A.__proto__ === B;  //繼承屬性
A.prototype.__proto__ == B.prototype;//繼承方法

typeOf(Colorpoint)
//類的數(shù)據(jù)類型就是函數(shù),類本身就指向構(gòu)造函數(shù)。
//類的所有方法都定義在類的prototype屬性上面。
Colorpoint.prototype.constructor === Colorpoint // true

Object.assign(Colorpoint.prototype, {
  toString(){},
  toValue(){}
});

與 es5 不同之處

1. toString方法是Colorpoint類內(nèi)部定義的方法,它是不可枚舉的。這一點(diǎn)與 ES5 的行為不一致。
2. 必須使用new 調(diào)用
3.不存在變量提升,必須先聲明在使用

與es5 相同之處
1.prototype對(duì)象的constructor屬性,直接指向“類”的本身
2. 與 ES5 一樣,實(shí)例的屬性除非顯式定義在其本身(即定義在this對(duì)象上),否則都是定義在原型上(即定義在class上)。
3.類的所有實(shí)例共享一個(gè)原型對(duì)象。

Colorpoint.hasOwnProperty('x') // true
Colorpoint.hasOwnProperty('color') // true
Colorpoint.hasOwnProperty('toString') // false


“extends” 語法會(huì)設(shè)置兩個(gè)原型:

  1. 在構(gòu)造函數(shù)的 "prototype" 之間設(shè)置原型(為了獲取實(shí)例方法)
  2. 在構(gòu)造函數(shù)之間會(huì)設(shè)置原型(為了獲取靜態(tài)方法)
//繼承對(duì)象
class A extends Object   和   class A 區(qū)別
繼承自對(duì)象                      繼承自函數(shù)
構(gòu)造函數(shù)中需要調(diào)用父類構(gòu)造函數(shù) 

super

  1. 作為函數(shù)使用 調(diào)用父類構(gòu)造函數(shù)

  2. 作為對(duì)象使用 靜態(tài)時(shí)指向父類本身可以調(diào)用父類本身的屬性和方法·指向父類的原型

//動(dòng)態(tài)時(shí)由于super指向父類的原型對(duì)象,所以定義在父類實(shí)例上的方法或?qū)傩?,是無法通過super調(diào)用的。
class A {
  p() {
    return 2;
  }
}

class B extends A {
  constructor() {
    super();//  調(diào)用父類函數(shù)時(shí) 會(huì)綁定子類的this
    console.log(super.p()); // 2
  }
}

let b = new B();

class A {
  constructor() {
    this.p = 2;
  }
}

class B extends A {
  get m() {
    return super.p;
  }
}

let b = new B();
b.m // undefined

// 用在靜態(tài)方法之中,這時(shí)super將指向父類,而不是父類的原型對(duì)象。
class Parent {
  static myMethod(msg) {
    console.log('static', msg);
  }

  myMethod(msg) {
    console.log('instance', msg);
  }
}

class Child extends Parent {
  static myMethod(msg) {
    super.myMethod(msg);
  }

  myMethod(msg) {
    super.myMethod(msg);
  }
}

Child.myMethod(1); // static 1

var child = new Child();
child.myMethod(2); // instance 2

//在子類的靜態(tài)方法中通過super調(diào)用父類的方法時(shí),方法內(nèi)部的this指向當(dāng)前的子類,而不是子類的實(shí)例。

class A {
  constructor() {
    this.x = 1;
  }
  static print() {
    console.log(this.x);
  }
}

class B extends A {
  constructor() {
    super();
    this.x = 2;
  }
  static m() {
    super.print();
  }
}

B.x = 3;
B.m() // 3

  1. 所以如果通過super對(duì)某個(gè)屬性賦值,這時(shí)super就是this,賦值的屬性會(huì)變成子類實(shí)例的屬性。
class A {
  constructor() {
    this.x = 1;
  }
}

class B extends A {
  constructor() {
    super();
    this.x = 2;
    super.x = 3;
    console.log(super.x); // undefined  A.prototype.x
    console.log(this.x); // 3
  }
}

let b = new B();

  1. 由于對(duì)象總是繼承其他對(duì)象的,所以可以在任意一個(gè)對(duì)象中,使用super關(guān)鍵字。
  2. 直接打印 super會(huì)報(bào)錯(cuò) 由于瀏覽器不知道是函數(shù)還是對(duì)象, 所以必須顯式指定是作為函數(shù)、還是作為對(duì)象使用
  3. 箭頭函數(shù)沒有 super
  4. [[HomeObject]] super 的特殊特征

學(xué)習(xí)整理

JS高程學(xué)習(xí)-第六章(一)---認(rèn)識(shí)對(duì)象
JS高程學(xué)習(xí)-第六章(二)---創(chuàng)建對(duì)象
JS高程學(xué)習(xí)-第六章(三)---對(duì)象繼承

最后編輯于
?著作權(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ù)。

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