js中将原型链作为实现继承的主要方法,基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。
先来捋清楚一下实例、构造函数、原型三者的关系:
定义一个构造函数:
function Test() {
//some code
}
var a = Test();
实例a是一个obj对象,在js中所有对象都有一个隐藏属性_proto_,通过a._proto_可以访问到构造函数Test的原型对象。我们创建的每个函数都有一个prototype属性,通过Test.prototype可以访问到原型对象
原型对象是当创建函数时,自动生成的一个特殊对象,默认具有一个constructor属性,指回构造器(注意constructor可以手动被改写)。关于原型对象,也是Object构造器的实例,通过原型链可以访问到Object原型对象上的属性和方法(例如toString()、hasOwnProperty()方法)
在chrome控制台验证:
关于原型对象,可以看到除了constructor属性外,_proto_指向了Object构造函数的原型对象
理解为什么_proto_会指向Object的原型对象呢?
每个对象都有一个__proto__属性,原型链上的对象正是依靠这个__proto__属性连结在一起的,__proto__是否指向实例对象的原型prototype对象的引用。什么是原型链呢?简单来说,访问一个对象的属性时,先在基本属性中查找,如果没有,再沿着__proto__这条链向上找,这就是原型链。所有对象都继承于Object,原型链的顶端就是Object.prototype,Object.prototype.__proto__ = null;
原型链:
var deepPrototypeObj = {
hello: function() {
console.log('hello, my name is '+ this.name + '.');
},
__proto__: null
}
var prototypeObj = {
__proto__: deepPrototypeObj
}
var aaa = {
name: 'tom',
__proto__: prototypeObj
}
var bbb = {
name: 'bob',
__proto__: prototypeObj
}
aaa.hello(); // hello, my name is tom
bbb.hello(); // hello, my name is bob
在上面的例子中,通过直接修改了 __proto__ 属性值,实现了原型继承。但是在实际生产中,用这种方式来改变和继承属性是对性能影响非常严重的,所以并不推荐
代替的方法是使用Object.create()方法
调用 Object.create() 方法会创建一个新对象,同时指定该对象的原型对象为传入的第一个参数
var prototypeObj = {
hello: function() {
console.log('hello, my name is ' + this.name + '.');
}
}
var aaa = Object.create(prototypeObj);
var bbb = Object.create(prototypeObj);
aaa.name = 'tom';
bbb.name = 'bob';
aaa.hello(); // hello, my name is tom
bbb.hello(); // hello, my name is bob
接下来理解一下类式继承
javascript高级程序设计里面的栗子
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function() {
return this.property;
}
function SubType() {
this.subproperty = false;
}
//继承了SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function() {
return this.subproperty;
};
var instance = new SubType();
alert(instance.getSuperValue()); // true
让子函数SubType的原型对象等于父函数SuperType的实例,从而实现了继承
根据这个原理,那么用Object.create()来实现类式继承
// Shape - 父类(superclass)
function Shape() {
this.x = 0;
this.y = 0;
}
// 父类的方法
Shape.prototype.move = function(x, y) {
this.x += x;
this.y += y;
console.info('Shape moved.');
};
// Rectangle - 子类(subclass)
function Rectangle() {
Shape.call(this); // call super constructor.
}
// 子类续承父类
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;
var rect = new Rectangle();
console.log('Is rect an instance of Rectangle?',
rect instanceof Rectangle); // true
console.log('Is rect an instance of Shape?',
rect instanceof Shape); // true
rect.move(1, 1); // Outputs, 'Shape moved.'
这两行代码
// 子类续承父类
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;
使用Object.create()创建了新对象,新对象的原型指向Shape.prototype,再重新让子类原型指向新对象。另外,给子类原型添加constructor属性,指回构造器。整个过程其实还是让子函数的原型对象等于父函数的实例。从而实现继承。
// Rectangle - 子类(subclass)
function Rectangle() {
Shape.call(this); // call super constructor
}
在子类构造器中调用父类构造器,这样就可以继承到父类实例中的属性(x、y)
如果希望继承多个对象,可以使用混入的方式:
function MyClass() {
SuperClass.call(this);
OtherSuperClass.call(this);
}
// 继承一个类
MyClass.prototype = Object.create(SuperClass.prototype);
//混合其他
Object.assign(MyClass.prototype, OtherSuperClass.prototype);
//重新指定constructor
MyClass.prototype.constructor = MyClass;
MyClass.prototype.myMethod = function() {
//do something
};
Object.assign会把OtherSuperClass原型上的函数拷贝到MyClass原型上,使MyClass所有实例都可用OtherSuperClass的方法。