# :-: **JS的繼承**
[TOC]
## 1、js 繼承的六種方式:
定義一個父類Person:
~~~
function Person(name){
this.name=name;
}
Person.prototype.age=25;
Person.prototype.show=function(){
console.log(this.name);
}
~~~
**1.原型鏈繼承**
子類的原型是父類的實例。
~~~
function subType(){}
subType.prototype=new Person();
// 測試
let p1=new subType();
p1.name; //undefined,可以訪問,但是name未初始化所以為undefined
p1.age; //25
~~~
特點:父類的構造函數中的屬性和方法,以及原型中的屬性和方法都放入了子類的原型中,都可以訪問得到。
問題:
1.原型中的屬性和方法,在實例中是共享的。構造函數中的屬性和方法在實例中不共享。這說明,父類中的所有屬性,在子類的實例中都將共享,假如父類有一個數組,那么子類的實例將共享這個屬性,當有一個子類實例改變這個數組的項時,所有的實例中的這個屬性都會隨之改變。
2.創建子類實例時,不能向超類型構造函數中傳遞參數。
**2.借用構造函數**
在子類構造函數中調用父類構造函數。
~~~
function subType(name){
Person.call(this,name);
}
// 測試
let p2=new subPerson("zhangsan");
p2.name; //"zhangsan"
p2.age; //undefined
~~~
特點:解決了方法一的問題,所有的子類實例都將不共享屬性和方法,互不影響,并且可以傳值。
問題:
1.構造函數中的方法不能共享復用,每個實例都將擁有相同的方法。
2.父類的原型中的方法和屬性在子類中不可見。
**3.組合繼承**
組合原型鏈方法和借用構造函數方法
~~~
function subType(name){
Person.call(this,name);
}
subType.prototype=new Person();
//測試
let p3=new subType("lisi");
p3.name; //"lisi"
p3.show(); //"lisi"
p3.age; //25
~~~
特點:既可以繼承父類原型中的屬性和方法,又能將父類的構造函數放入子類構造函數中。
缺點:父類的實例屬性在子類的實例和原型中都有,屬性重復了。
**4.原型式繼承**
以一個對象為基礎,創建一個新對象,并將舊對象作為新對象的原型,返回這個新對象的實例。
~~~
function clone(o){
function fn(){};
fn.prototype=o;
return new fn();
}
var subType=clone(superType);
//測試
let person=new Person("wangwu");
var subType=clone(Person);
let p4=new subType();
p4.name; //"wangwu"
p4.age; //25
~~~
特點:要求必須有一個對象可以作為另一個對象的基礎。返回的是一個對象。可以向超類型構造函數中傳遞參數。
缺點:父類的實例屬性不能被繼承。
**5.寄生式繼承**
構造函數中通過某種方式創建父類對象,然后為這個對象添加屬性和函數,并返回這個對象。
~~~
function clone(o){
function fn(){};
fn.prototype=o;
return new fn();
}
function inhire(original){
let obj=clone(original);
obj.sayHi=function(){
alert("Hi");
}
return obj;
}
var suubType=inheir(SuperType);
~~~
特點:創建對象部分不必要固定方式,只要可以返回一個父類對象就可。返回的是一個加入了自定義方法和屬性的對象。
缺點:自定義的方法不能共享復用。
**6.寄生組合式繼承**
使用寄生式繼承來繼承超類型的原型。使用借用構造函數的方法繼承父類的構造函數屬性和方法,并添加自己的構造函數屬性和方法。
~~~
function object(o){
function fn(){};
fn.prototype=o;
return new fn();
}
function inheritPrototype(subType,superType){
let p=object(superType.prototype);
p.constructor=subType;
subType.prototype=p;
}
//子類
function subType(name){
Person.call(this,name);
}
inheritPrototype(subType,Person);
~~~
特點:避免了組合式繼承中原型和構造函數中重復繼承父類的構造函數中的屬性和方法。
## 2、es6 中的class
### *2.1、Class簡述*
**1、ES6中的class可以看作是一個語法糖,它的絕大部分功能,ES5都可以做到,新的class寫法只是讓對象原型的寫法更加清晰,更加面向對象編程的語法而已。**
~~~javascript
class Point { // 定義一個Point類
constructor(x,y){ // 構造方法
this.x=x;
this.y=y;
}
// =>類的所有方法都定義在類的prototype屬性上。
// =>在類的實例上調用方法,其實就是調用原型的方法。
// =>類的內部所有方法都是不可枚舉的
toString(){ // 定義toString方法
return '('+this.x+','+this.y+')';
}
}
// =>Object.assign方法可以方便地一次向類添加多個方法。
Object.assign(Point.prototype,{
toValue1(){ return "添加方法1"},
toValue2(){ return "添加方法2"}
})
console.log(typeof Point); // =>function =>類的數據類型就是函數
console.log(Point === Point.prototype.constructor) // =>true =>類的本身就指向構造函數
var point1=new Point(); // 使用的時候也是直接對類使用new命令,跟構造函數的用法完全一致。
console.log(point1.toString()); // =>(undefined,undefined)
var point2=new Point(1,1); // 使用的時候也是直接對類使用new命令,跟構造函數的用法完全一致。
console.log(point2.toString()); // =>(1,1)
~~~
**2、類和模塊的內部默認使用嚴格模式,所以不需要使用 use strict 指定運行模式。只要將代碼寫在類或模塊之中,那么就只有嚴格模式可用。**
**3、constructor 方法是類的默認方法,通過 new 命令生成對象實例時自動調用該方法。一個類必須有 constructor 方法,如果沒有顯示定義,一個空的 constructor 方法會被默認添加。**
**4、類必須使用 new 來調用,否則就會報錯。**
**5、實例的屬性除非顯示定義在其本身(即 this 對象)上,否則都是定義在原型(即 Class )上。?**
**6、類的實例共享一個原型對象。**
**7、Class表達式**
~~~javascript
// =>Class也可以使用表達式的形式定義。
// =>注意這個類的名字是MyClass而不是Me,Me只在Class的內部代碼可用,指當前類。
const MyClass = class Me{
getClassName(){
return Me.name;
}
}
~~~
**8、類不存在變量提升**
**9、ES6不提供私有方法,不支持私有屬性。目前有一個提案為class加私有屬性。方法是在屬性名前,使用#來表示。**
**10、類的方法內部如果含有 this,它將默認指向類的實例。**
**11、name 屬性返回 class 后面的類名。**
~~~javascript
class Point{
}
console1.log(Point.name); //=>"Point"
~~~
**12、在類的內部可以使用 get 和 set 關鍵字對某個屬性設置存儲函數和取值函數,攔截該屬性的存取行為。**
~~~javascript
class MyClass{
constructor(){
// ...
}
get prop(){
return 'getter';
}
set prop(value){
console.log('setter'+value);
}
}
let inst=new MyClass();
inst.prop=123; // =>setter123
console.log(inst.prop); // =>getter
~~~
**13、類相當于實例中的原型,所有在類中定義的方法都會被實例繼承。如果在一個方法前加上static關鍵字,就表示該方法不會被實例繼承,而是直接通過類調用,稱之為“靜態方法”。**
~~~javascript
class Foo{
static classMethod(){
return 'hello';
}
}
console.log(Foo.classMethod()); // =>hello
let foo=new Foo();
foo.classMethod(); // =>Uncaught TypeError: foo.classMethod is not a function
~~~
**14、父類的靜態方法可以被子類繼承。**
~~~javascript
class Foo{
static classMethod(){
return 'hello';
}
}
class Bar extends Foo{
}
console.log(Bar.classMethod()); // =>hello
~~~
**15、 靜態方法也可以從super對象上調用。**
~~~javascript
class Foo{
static classMethod(){
return 'hello';
}
}
class Bar extends Foo{
static classMethod(){
return super.classMethod()+',too';
}
}
console.log(Bar.classMethod()); // =>hello,too
~~~
**16、靜態屬性指的是Class本身的屬性,即Class.propname,而不是定義在實例對象(this)上的屬性。Class內部只有靜態方法,沒有靜態屬性。正確寫法如下:**
~~~javascriptclass
Foo{
}
Foo.prop=1;
console.log(Foo.prop); // =>1
~~~
**17、Class的實例屬性可以直接用等式寫入類的定義之中。**
**18、Class的靜態屬性只要在實例屬性寫法前面加上 static 關鍵字就可以了。**
**19、new.target屬性用于確定構造函數是怎么調用的。**
### 2.2、Class的繼承
**首先我們要知道一下這些要點:**
**1、Class通過?extends?關鍵字實現繼承。**
**2、super?關鍵字既可以當作函數使用,也可以當作對象使用。**
**? ? ? ? ① super 作為函數調用時表示父類的構造函數,用來新建父類的this對象。**
**? ? ? ? 同時需要注意:**
**? ? ? ? a、子類必須在 constructor 方法中調用 super 方法,否則新建實例時會報錯。**
**? ? ? ? b、如果子類沒有定義 constructor 方法,那么這個方法就會被默認添加。**
**? ? ? ? c、在子類的構造函數中,只有調用 super 方法之后才能夠使用 this 關鍵字,否則會報錯。**
**? ? ? ? d、super?雖然代表了父類的構造函數,但是返回的是子類的實例,即 super 內部的 this 指定的是子類。**
**? ? ? ? e、super () 只能用在子類的構造函數之中,用在其他地方會報錯。**
**? ? ? ? ② super 作為對象時在普通方法中指向父類的原型對象;在靜態方法中指向父類。**
**? ? ? ? 同時需要注意:**
**? ? ? ? a、由于 super 指向父類的原型對象,定義在父類實例上的方法或屬性是無法通過 super 調用的。如果定義在父類的原型上,super 就可以取到。**
**? ? ? ? b、ES6規定,通過 super 調用父類的方法時,super 會綁定子類的 this。**
**? ? ? ? c、如果通過 super 對某個屬性賦值,這時 super 就是 this,賦值的屬性會變成子類實例的屬性。**
**? ? ? ? d、如果 super 作為對象用在靜態方法之中,這時 super 將指向父類,在普通方法之中指向父類的原型對象。**
## 3、面試題
第一題:class 繼承
~~~javascriptclass
class Point{ // 定義了一個名字為Point的類
constructor(x,y){ // constructor是一個構造方法,用來接收參數
this.x=x;
this.y=y;
this.z=1;
this.a=3;
}
toString(){
return "顏色";
}
print(){
console.log(this.z);
}
static myMethod(msg){
console.log('static',msg);
}
myMethod(msg){
console.log('instance',msg);
}
}
class ColorPoint extends Point{
constructor(x,y,color){
super(x,y); // 調用父類的constructor(x,y)
this.z=2;
this.a=4;
super.a=5;
this.color=color;
console.log(super.a);
console.log(this.a);
}
toString(){ // 這是一個類的方法,注意前面沒有function
return this.color+' '+super.toString(); // 調用父類的toString()
}
m(){
super.print();
}
static myMethod(msg){
super.myMethod(msg);
}
myMethod(msg){
super.myMethod(msg);
}
}
let color1=new ColorPoint(1,1,"red");
let color2=new ColorPoint(2,2,"blue");
console.log(color1);
console.log(color1.toString());
color1.m();
ColorPoint.myMethod("你好");
color1.myMethod("你好");
console.log("===================");
console.log(color2);
console.log(color2.toString());
color2.m();
~~~
下面是答案
下面是答案
下面是答案
下面是答案
下面是答案
下面是答案
下面是答案
下面是答案
下面是答案

第二題:js 繼承的繼承 有哪幾種方式 有什么優缺點;