prototype,__proto__,constructor

如何用js实现类的继承和多态?

用好以上三个属性就可以做到

 

1.基本概念

prototype 可以理解为函数原型(类原型),一般来说只对函数(类)设置

__proto__可以理解为对象的原型(链),访问对象的某个属性会依次尝试访问对象本身,对象的原型,原型的原型..

constructor属性可以返回构造函数,用来判断对象类型

非常重要的一点是,对象的原型和类原型是有密切关联的

var F = function(){}
var f = new F()
f.__proto__ === F.prototype ==>true

需要注意__proto__即prototype都应视为对象,或者说一个用来索引属性/方法的表!

又基于__proto__对象属性的访问方式,就可以实现以下操作

var F = function(){}
F.prototype.val = 1
F.prototype.print = function(){
    console.log(this.val)
}

var f = new F
f.print() ==> 1

f
F {}__proto__: F
constructor: ()
print: ()
val: 1

这里对象f本身是没有属性的

f.print()打印1的过程是

首先找到f.__proto__,也就是F.prototype.print函数

再找到f.__proto__.val,也就是F.prototype.val==1,输出

 

2.原型链继承

var C1 = function(argu1){
    this.val = "1"
    this.c1 = "c1"
    this.argu1 = argu1
}
C1.prototype.p = function(){
    console.log("C1 and ", this.val)
}


var C2 = function(argu1){
    this.val = "2"
    this.c2 = "c2"
    
}
C2.prototype = new C1()
C2.prototype.p = function(){
    console.log("C2 and " , this.val)
}


var C3 = function(argu1 , argu2){
    this.val = "3"
    this.c3 = "c3"
    this.argu2 = argu2
}
C3.prototype = new C2()
C3.prototype.p = function(){
    console.log("C3 and " , this.val)
}

var c = new C3()
//常规调用
c.p()
VM2532:30 C3 and 3
//原型调用
c.__proto__.p.call(c)
VM2532:30 C3 and 3
//类原型调用(调用父类的方法)
c.__proto__.__proto__.p.call(c)
VM2532:19 C2 and 3
//类原型调用(调用父类的父类的方法)
c.__proto__.__proto__.__proto__.p.call(c)
VM2532:8 C1 and 3

可以看到随便哪一级父类的方法,我们都可以调用到

核心在于C2和C3在构造函数声明之后对其指定了函数原型!

C2.prototype = new C1()

C3.prototype = new C2()

原型链模式要做好这两点

1.定义时先赋值类的prototype

2.调用时找准对象的__proto__

 

当然调用父类方法也可以使用更直观的方式

C++中可以使用域操作符来确定函数

比如Base::p()/Child::p()

我们js例子中也可以使用

C1.prototype.p.call(c) 替代 c.__proto__.__proto__.__proto__.p.call(c)

C2.prototype.p.call(c) 替代 c.__proto__.__proto__.p.call(c)

这需要付出一个小小的代价:

如果C1/C2的声明不在c上下文中的话,需要引入C1/C2的声明文件(require)

 

3.原型链进阶

以上方法存在两个问题

(new C2).constructor == C1
true
(new C3).constructor == C1
true
(new C2).constructor == C2
false
(new C3).constructor == C3
false

(new C3) instanceof C1
true
(new C3) instanceof C2
true
(new C3) instanceof C3
true
(new C2) instanceof C3
false

通过instanceof判断类型时,父类型和当前类型都可以通过判定,

可以以此确定子类对象与基类的继承关系

但是constructor却统一指向了基类(继承的源头类)

如果想让对象的constructor准确指向其构造函数的话

需要手动做一些修改

另外如果构造函数带参数的话,也需要手动调用父类的构造函数

如以下代码注释中的部分

特别注意手动调用父类构造函数,应该写在构造函数的最前面!

var C1 = function(argu1){
    //this.constructor = C1
    
    this.val = "1"
    this.c1 = "c1"
    this.argu1 = argu1
}
C1.prototype.p = function(){
    console.log("C1 and ", this.val)
}


var C2 = function(argu1){
    //参数调用父类构造函数
    C1.apply(this , arguments)
    //显示定义对象的构造函数(若不做显示定义,contrustor是原始基类C1)
    this.constructor = C2
    
    this.val = "2"
    this.c2 = "c2"
    
}
C2.prototype = new C1()
C2.prototype.p = function(){
    console.log("C2 and " , this.val)
}


var C3 = function(argu1 , argu2){
    //参数调用父类构造函数
    C2.apply(this,arguments)
    //显示定义对象的构造函数(若不做显示定义,contrustor是原始基类C1)
    this.constructor = C3    
    
    this.val = "3"
    this.c3 = "c3"
    this.argu2 = argu2
}
C3.prototype = new C2()
C3.prototype.p = function(){
    console.log("C3 and " , this.val)
}