2

思考

说到原型,不得不提到原型链,js中不管是对象还是方法(也是对象)都有个隐藏属性_proto_,来表示原型链的下一个指向,一般对象是指向Object.prototype,方法是指向Function.prototype,构造函数new出来的对象指向想构造函数的prototype

原型链的思考

因为原型链的存在,当前对象或者方法可以共用原型链上的上级属性和方法

var obj = {};
obj.toString() //[object Object]

obj对象上是没有toString方法的,因为obj._proto_指向Object.prototype,具体上是调用Object.prototype方法:Object.prototype.toString.apply(obj)

function Foo() {

}
Foo.toString();//function Foo() {}

方法的原型链是Foo->Function.prototype->Object.prototype,所以Foo调用toString方法是引用Function上的toString方法,具体上是调用Function.prototype方法:Function.prototype.toString.apply(obj)

有时候我们为了继承父类(其实js并没有类这个概念),会通过原型继承去继承父类的一些方法,在此之前,先简单叙述下通过构造函数实例化一个对象的过程,比如以下创建一个obj对象,

var obj = new Object();
  • 先创建一个空对象{}
  • 空对象{}._proto_指向Object.prototype
  • Object.apply({})
  • 再执行构造方法里面的代码

所以当es5继承方法时,可以选择原型继承,通过修改prototype的值,如:
ES5的情况:

var Father = function(name) {
    this.name = name;
}

Father.prototype.say = function() {
    return "my name is " + this.name;
}

var Child = function(name) {
    this.name = name;
}

Child.prototype = new Father();

var child = new Child('Nico');
child.say();//my name is Nico

但是在上面原型继承的情况,我们还要对Child的构造函数的constructor做一个声明

Child.prototype.constructor = Child;

因为Child原型是Father实例的一个引用,当想修改Child原型的方法时,会被挂在Father的实例对象上。

ES6的情况:

class Father {
    constructor(name) {
        this.name = name;
    }
    say() {
        return "my name is " + this.name;
    }
}

class Child extends Father {
    constructor(name) {
        super(name)
    }
}

var child = new Child('Nico');
child.say()//my name is Nico

es6的class类中,Child类的原型构造器不用做额外声明,因为,es6的class的constructor指向自身

继承的思考

子类能使用父类的方法

除了上面提及到的原型链继承,还有例如构造函数继承:

function Veticle(name) {
    this.name = name || null

    this.drive = function() {
        console.log(this.name + " drives! --from Veticle ");
    }
}

function Car(name) {
    Veticle.call(this)
    this.name = name;
}

var car = new Car('a car');
car.drive();//a car drives! --from Veticle 

这种方法核心就通过改变构造函数的上下文(context),达到子类能够使用父类方法,但是这种方法不能使用父类原型方法。

除此之外,还可以遍历父类实例继承父类方法:

function Veticle(name) {
    this.name = name || null

    this.drive = function() {
        console.log(this.name + "drives! --from Veticle ");
    }
}

Veticle.prototype.getName = function() {
    return this.name;
}

function Car(name) {
    var veticle = new Veticle();
    for (var p in veticle) {
        console.log(p)
        Car.prototype[p] = veticle[p];
    }
    this.name = name;
}

var car = new Car('a car');
car.getName();//a car

这种方法可以获取实例能调用的所有方法,除了不可枚举(ES6 class方法是不可枚举的)

其实关于继承js已经有个很好的的实现方法叫寄生组合继承,大概原理是用构造函数继承实例属性,用原型继承原型方法,而寄生组合继承是在组合继承的基础上强化,两者的区别是前者避免了两次实例化父类,是目前比较好的es5实现继承的方法:

function Veticle(name) {
    this.name = name || null

    this.drive = function() {
        console.log(this.name + "drives! --from Veticle ");
    }
}

Veticle.prototype.getName = function() {
    return this.name;
}

function Car(name) {
    Veticle.call(this)

    this.name = name || 'car'
}

function inheritProto(subType, superType) {
    var prototype = Object.create(superType.prototype);
    subType.prototype.constructor = subType;
    subType.prototype = prototype;
}

inheritProto(Car, Veticle)

var car = new Car('siip');

总结

    js中有一些构造方法诸如Object、Function、String、Number等等,当当前对象调用方法或获取属性时,会顺着自身对象到构造函数原型,再到Object原型,最后到Null这么一条原型链上查找。原型链上有两个关键词prototype和constructor比较重要,prototype是设置构造函数的原型对象,constructor是声明原型的构造函数,不管是对象还是函数,都有一个隐式属性_proto_用来构成一条完整原型链的指向。
    继承有继承属性和继承方法,很多时候es5实现继承比es6要稍微简单一点,es6的class的原型方法是不可枚举的,有时候,挂载方法时需要通过Object.getOwnPropertyNames方法获取。


NicolaChin
35 声望2 粉丝

互相交流和提高