面向对象的语言支持两种继承方式,接口继承和实现继承
js无法实现接口继承,只支持实现继承,主要通过原型链来实现。
具体实现方式有以下几种:
- 原型链
- 借用构造函数
- 组合继承
- 原型式继承
- 寄生式继承
- 寄生组合式继承
逐一看下:
原型链
每个函数都有prototype(原型)属性,指向一个原型对象
原型对象都包含一个指向构造函数的指针(constructor)
通过函数实例化的对象中,都有一个指向原型对象的内部指针
通过这种特性,可以让一个对象A的原型对象等于另一个实例化的对象B,对象B的原型对象等于另一个实例化的对象C,这样层层递进,就构成了实例与原型的链条,也就是原型链
function Person() {
this.name = 'wang'
}
Person.prototype.sayName = function () {
console.log(this.name);
}
function Man() {
this.age = 18;
}
//继承:子类的原型等于父类的实例对象
Man.prototype = new Person();
Man.prototype.sayAge = function () {
console.log(this.age);
}
var p1 = new Man();
p1.sayName(); //wang
p1.sayAge(); //18
代码中子类的方法都可以正常使用,这就涉及到了对象的原型搜索机制
当访问一个实例属性时,首先在实例中搜索该属性,如果没有找到,则会继续搜索实例的原型,如果没有找到并且原型对象还有原型,则继续向上搜索,直到搜索到没有原型对象为止,也就是截止到Object,因为js中所有对象都继承自Object。
这也就能解释为什么所有实例都有toString()等公用方法。
对象识别
利用原型链继承实现的子类可以识别属于子类、父类、Object
console.log(p1 instanceof Object); //true
console.log(p1 instanceof Person); //true
console.log(p1 instanceof Man); //true
派生判断
console.log(Object.prototype.isPrototypeOf(p1)); //true
console.log(Person.prototype.isPrototypeOf(p1)); //true
console.log(Man.prototype.isPrototypeOf(p1)); //true
原型链的问题:
- 属性为引用类型时,由于共享机制,操作实例属性会影响其他实例
- 无法在不影响其他实例的情况下,给父类的构造函数传参
function Person() {
this.name = 'wang'
this.friends = ['zhang', 'liu']
}
function Man() {
this.age = 18;
}
//继承:子类的原型等于父类的实例对象
Man.prototype = new Person();
var p1 = new Man();
var p2 = new Man();
console.log(p1.friends); //["zhang", "liu"]
p2.friends.push('li');
console.log(p1.friends); //["zhang", "liu", "li"]
可以看到数组属性受到影响,所以实践中很少单独使用原型链。
借用构造函数
也叫伪造对象或经典继承,为解决原型链不足而生
基本思想:在子类构造函数内部调用父类构造函数
function Person(name) {
this.name = name;
this.friends = ['zhang', 'liu']
}
function Man(name) {
Person.call(this, name)
}
var p1 = new Man('wang');
var p2 = new Man('zhang');
console.log(p1.name); //wang
console.log(p2.name); //zhang
console.log(p1.friends); //["zhang", "liu"]
p2.friends.push('li');
console.log(p2.friends); //["zhang", "liu", "li"]
console.log(p1.friends); //["zhang", "liu"]
可以看出即可以给父类的构造函数传参,引用类型的属性又互不影响
问题
如果仅用借用构造函数,就会碰到构造函数的共性问题,无法将函数复用造成资源浪费,所以借用构造函数的技术也很少单独使用。
组合继承
也叫伪经典继承,将原型链和借用构造函数组合来用,发挥二者之长
使用原型链实现对原型属性和方法的继承
使用借用构造函数实现对实例属性的继承
function Person(name) {
this.name = name;
this.friends = ['zhang', 'liu']
}
Person.prototype.sayName = function () {
console.log(this.name);
}
function Man(name, age) {
//用构造函数继承属性
Person.call(this, name);
this.age = age;
}
//用原型链继承实例
Man.prototype = new Person();
Man.prototype.sayAge = function () {
console.log(this.age);
}
var p1 = new Man('wang', 18);
console.log(p1.name); //wang
p1.sayName() //wang
p1.sayAge(); //18
console.log(p1.friends); //["zhang", "liu"]
console.log(p1.__proto__.friends); //["zhang", "liu"]
代码中用到了2种方式一起实现了继承
通过对象的__proto__属性,可以访问到对象的原型
可以看到实例对象和实例的原型对象同时存在父类属性和方法
组合继承避免了各自的缺陷,融合了优点,成为最常用的继承模式
但也有不足之处:
...
Person.call(this, name); //第二次执行Person()
...
Man.prototype = new Person(); //第一次执行Person()
组合继承会指向两次父类的构造函数,在性能上不够理想
所以可以采用寄生组合式继承优化实现
在不同场景下,还有其他可选择的轻量级继承方式
原型式继承
核心思想:原型可以基于已有对象创建新对象,同时不必创建自定义类型
function object(obj) {
function Fn() { }
Fn.prototype = obj;
return new Fn();
}
在函数内部,先创建了一个临时的构造函数,再将传入的对象作为这个构造函数的原型,最后返回这个临时类型的一个新实例。本质是执行了传入对象的浅复制。
var person = {
name: 'wang',
}
var p1 = object(person);
console.log(p1.name);
在ES5中通过新增Object.create()方法规范了原型式继承
var person = {
name: 'wang',
friends: ['zhang', 'liu']
}
var p1 = Object.create(person);
console.log(p1.friends); //["zhang", "liu"]
var p2 = Object.create(person);
p2.friends.push('li');
console.log(p1.friends); //["zhang", "liu", "li"]
通过Object.create()可实现原型式继承
这种方式适用于让一个对象与另一个对象保持类似的情况,可不用构造函数
问题:可以看出,存在引用类型的属性的共享问题,也就是会被其它实例修改
寄生式继承
实现思路:创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后返回对象
var person = {
name: 'wang',
friends: ['zhang', 'liu']
}
function createObject(obj){
var copy = Object.create(obj);
copy.sayName = function(){
console.log(this.name);
}
return copy;
}
var p1 = createObject(person);
p1.sayName(); //wang
类似寄生构造函数和工厂模式,
适用于:主要考虑对象而不是自定义类型和构造函数的情况
问题:在函数体内部定义方法,会无法使函数复用而降低效率
寄生组合式继承
针对组合式继承模式指向两次父类构造函数的不足,来优化
//实现父类原型拷贝副本给子类
function inheritPrototype(subObj, superObj) {
var prototype = Object.create(superObj.prototype); //创建对象
prototype.constructor = subObj; //增强对象
subObj.prototype = prototype; //指定对象
}
function Person(name) {
this.name = name;
this.friends = ['zhang', 'liu']
}
Person.prototype.sayName = function () {
console.log(this.name);
}
function Man(name, age) {
//用构造函数继承属性,只需执行一次Person()
Person.call(this, name);
this.age = age;
}
//Man.prototype = new Person();
inheritPrototype(Man, Person); //拷贝父类原型的副本,无需执行构造函数
Man.prototype.sayAge = function () {
console.log(this.age);
}
var p1 = new Man('wang', 18);
console.log(p1.name); //wang
p1.sayName() //wang
寄生组合式继承是引用类型最理想的继承方式。
网友评论