說清楚javascript物件導向、原型、繼承

kkkkkiko發表於2019-02-16

這是兩年前寫的筆記,自己都有遺忘,相信也會有人不明白,貼在這裡。
都是紅寶書上的內容,在比較難理解的地方加了一些示例和說明。

es中定義物件為:無需屬性的集合、其屬性可以包含基本值、物件或者函式。

建立物件

Object例項

建立物件最簡單的方法是建立一個Object的例項。

var person =new Object();
person.name="Mike";
person.age=12;
person.job="student";
person.sayName=function(){
};

物件字面量

var person={
      name:"mike",
      age:10,
      job:"student",
      sayName:function(){
        alert(this.name);
      }
 };

Object例項和物件字面量都可以用來建立單個物件,
但是當要建立多個物件就會產生大量重複的程式碼,由此產生了工廠模式。

工廠模式

工廠模式的特點是用函式來封裝建立物件的細節,對外以介面的方式呼叫。

 function createPerson(name,age,job){
      var obj = new Object();
      obj.name=name;
      obj.age=age;
      obj.job=job;
      obj.sayName=function(){
        alert(this.name);
      }
      return obj;
 }
 var person1=createPerson("Mike",12,"student");

工廠模式雖然解決了建立相似物件重複程式碼的問題,
但同時還存在一個問題:如何知道物件的類別?由此產生建構函式模式

建構函式模式

  function CreatePerson(name,age,job){
      this.name=name;
      this.age=age;
      this.job=job;
      this.sayName=function(){
        alert(this.name);
      }
 }
  var person1= new CreatePerson("Mi",13,"student");
  var person2= new CreatePerson("Mike",12,"student");
  alert(person2.constructor==CreatePerson);//true
  alert(person1 instanceof CreatePerson);//true
  alert(person2 instanceof CreatePerson);//true
  alert(person2 instanceof Object);//true  使用instanceof來檢測物件的型別

建構函式沒有返回值,沒有顯示地建立物件。
使用new呼叫建構函式,將建構函式的作用域賦給新物件(this指向新物件)
建構函式模式的特點:使用建構函式建立的物件(例項)有特定的型別。這也是建構函式模式勝過工廠模式的地方

建構函式模式的問題:建構函式中方法,在不同的例項中是不同的funciton例項,也就是說不同例項中有不同function(雖然他們的功能相同),
但是這並不是我們希望看到的,更何況function中還有this(指向例項的作用域),所以完全沒有必要把功能相同function繫結到特定的例項上。
由此產生了原型模式!
大boss來了,接著說下去

原型模式

關於原型的定義,摘自紅寶書:每個函式都有一個prototype(原型)屬性,這個屬性是一個指標,指向一個物件,而這個物件的用途是包含可以由特定型別的所有例項共享的屬性和方法。
好長一句話!有點暈?直接上程式碼:

function Person(){
  }
Person.prototype.name="mike";
Person.prototype.age=12;
Person.prototype.job="Software Engneer";
Person.prototype.sayName=function(){
      alert(this.name);
}

var person1=new Person();
alert(Person1.prototype.isPrototypeOf(person5));//true檢驗例項與原型之間的關係
person1.sayName();//mike
var person2=new Person();
alert(person1.sayName()==person2.sayName());//true


var person6=new Person();
person6.name="ssss";
alert(person6.hasOwnProperty("ssss"));//true


原型鏈查詢屬性或方法:

首先查詢例項屬性:即定義在子類建構函式中或直接新增在例項上的屬性
然後查詢子類原型屬性:包括子類自有的原型屬性和,父類的例項屬性(子類的原型是父類的例項)(這也就是原型鏈繼承的缺點,父類的例項屬性全變成子類的原型屬性,那就是所有子類例項共享的,如果又是引用型別,就出大事了。所以很少單獨使用原型鏈)
最後查詢父類的原型屬性

function SuperType(){
      this.name="super";
      this.job="student";
}
SuperType.prototype.getSuperValue=function(){
      alert(this.name);
}
SuperType.prototype.sex="man";
function SubType(){
      this.age=12;
}
SubType.prototype=new SuperType();
SubType.prototype.getSubValue=function(){
      alert(this.age);
}
//var person=new SubType();
var instance1=new SubType
alert(instance1 instanceof SubType);//true
alert(instance1 instanceof SuperType);//true
alert(SubType.prototype.isPrototypeOf(instance1));//true
alert(SuperType.prototype.isPrototypeOf(instance1));//true  
person.getSuperValue();
alert(person.constructor==SubType);//false子類的例項的原型物件沒有constructor屬性
alert(person instanceof SubType);
alert(person instanceof SuperType);
alert(person.job);
alert(person.constructor);//sub的原型指向super的原型,super的原型的constuctor是superType,所以person的
alert(person.hasOwnProperty("name"));//false
alert("name" in person);//true 說明name是sub的原型屬性。
alert(person.hasOwnProperty("sex"));//false
alert("sex" in person);//true 說明name是sub的原型屬性。,父類中的全部屬性都是子類的原型屬性
var person1=new SuperType();;
alert(person1.constructor);//

解決原型鏈缺點的方法:借用建構函式

借用建構函式

function SuperType(name){
  this.colors=["red","yellow","green"];
  this.name=name;
}
function SubType(){
      SuperType.call(this,"Mike");
      this.age=10;
}
var instance1=new SubType();
instance1.colors.push("aa");
alert(instance1.colors);
alert(instance1 instanceof SubType);//true
alert(instance1 instanceof SuperType);//false
alert(SubType.prototype.isPrototypeOf(instance1));//true
alert(SuperType.prototype.isPrototypeOf(instance1));//false
var instance2=new SubType();
alert(instance2.colors);//["red","yellow","green"];

在新例項的環境中呼叫Super建構函式

var instance3=new SubType();
alert(instance3.hasOwnProperty("name"));
alert(instance3.hasOwnProperty("age"));

在子類中call(借調)父類的建構函式實際上就是為子類新增了例項屬性(原來在父類中的例項屬性)
即父類的例項屬性變成了子類的例項屬性(原型鏈繼承中,父類的例項屬性變成了子類的原型屬性)

組合繼承

function SuperType(name){
  this.colors=["red","yellow","green"];
  this.name=name;
}
SuperType.prototype.sayName=function(){
  alert(this.name);
}

function SubType(name,age){
     SuperType.call(this,"Kiko");
      this.age=age;
}
SubType.prototype=new SuperType();
SubType.prototype.constructor=SubType;
SubType.prototype.sayAge=function(){
      alert(this.age);
}
var instance1=new SubType("Kiko",12);
//instance1.sayName();
//instance1.colors.push("ss");
//alert("colors" in instance1);//有屬性
//alert(instance1.hasOwnProperty("colors"))
//例項屬性 沒有這句話的時候//SuperType.call(this,"Kiko");,,是原型屬性
//有了這句話變成了例項屬性
//alert(instance1.colors);
//instance1.sayAge();
alert(instance1 instanceof SubType);//true
alert(instance1 instanceof SuperType);//true
alert(SubType.prototype.isPrototypeOf(instance1));//true
alert(SuperType.prototype.isPrototypeOf(instance1));//true
alert(Object.keys(SubType.prototype));//colors,name,constructor,sayAge
alert(Object.keys(instance1));//colors,name,age

這兩句說明第一:SubType.prototype=new SuperType();父類的所有屬性都變成了子類的原型屬性
第二:SuperType.call(this,”Kiko”);,在子類的建構函式中重寫了父類的例項屬性,即父類的例項屬性變成了子類的例項屬性
第三:p149:每當程式碼讀取物件中的某個屬性時,都會執行一次搜尋,先查詢例項屬性,再查詢原型屬性
第四:因此,組合繼承中父類中的例項屬性既是子類的例項屬性也是子類的原型屬性,但是在每次使用物件時,查詢到例項屬性就截止了,
所以表現為父類中的例項屬性變成了子類中的例項屬性(例項屬性覆蓋了原型物件上的同名屬性)

var instance2=new SubType(“hihi”,13);
//alert(instance2.colors);

原型式繼承

function object(o){
     function F(){}
      F.prototype=o;
      return new F();
}

var person={
      name:"Mike",
      friends:["kiko","Court","Van"]
};
var anotherPerson=object(person);
alert(anotherPerson.name);//
anotherPerson.friends.push("ss");

var yetAnotherPerson=object(person);
alert(yetAnotherPerson.friends);//["kiko","Court","Van","ss"]

寄生式繼承

function createAnother(original){
      var clone=object(original);
      clone.sayHi=function(){
    alert("hi");
  }
  return clone;
}
var anotherPerson1=createAnother(person);
anotherPerson.sayHi();//hi

使用寄生式繼承來為物件新增函式,會由於不能複用而降低效率,這一點與建構函式模式很類似(同名的方法是不同的function例項)

寄生組合式繼承

組合式的缺點是兩次呼叫超類的建構函式:
一,SubType.prototype=new SuperType();父類的所有屬性都變成了子類的原型屬性
二:SuperType.call(this,”Kiko”);,在子類的建構函式中重寫了父類的例項屬性,即父類的例項屬性變成了子類的例項屬性
寄生組合式目的是為了省掉第一步多繼承的例項屬性,在第一步時只繼承父類的原型屬性,第二步繼承例項屬性

function object(o){
      function F(){}
      F.prototype=o;
      return new F();
}

function inheritPrototype(SubType,SuperType){
      var prototype=object(SuperType.prototype);
      prototype.constructor=SubType;
      SubType.prototype=prototype;
}
function SuperType(name){
      this.name=name;
      this.colors=["red","blue","yellow"];
}
SuperType.prototype.sayName=function(){
      alert(this.name);
}
SuperType.prototype.job="student";

function SubType(name,age){
    SuperType.call(this,name);//(2)繼承例項屬性
      this.age=age;
}
inheritPrototype(SubType,SuperType);//(3)繼承原型屬性
SubType.prototype=new SuperType();//(1)把父類的原型屬性和例項屬性都繼承為原型屬性了
SubType.prototype.sayAge=function(){
      alert(this.age);
}
var instance1=new SubType();
alert("name" in instance1);//有屬性true
alert(instance1.hasOwnProperty("name"));//false不是例項屬性

這兩句話檢驗是否是原型屬性,1true,2false,原型屬性,1ture,2true例項屬性
寄生組合式,name會是例項屬性
組合式,把(3)換成(1)name確實也是例項屬性,那是因為(2)重新呼叫了SuperType,覆蓋了原型中的同名屬性。
可以把(2)去掉,發現name變成了原型屬性。

相關文章