对js原型的思考

思考

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

原型链的思考

由于原型链的存在,当前对象或者方法能够共用原型链上的上级属性和方法es6

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

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

function Foo() {

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

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

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

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

因此当es5继承方法时,能够选择原型继承,经过修改prototype的值,如:
ES5的状况: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作一个声明prototype

Child.prototype.constructor = Child;

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

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指向自身继承

继承的思考

子类能使用父类的方法

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

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方法获取。

相关文章
相关标签/搜索