思考
说到原型,不得不提到原型链,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方法获取。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。