原型链继承
将父类的实例作为子类的原型
// Shape - 父类(superclass)
function Shape() {
this.name = "123";
this.color = [1,2,3];
}
// 父类的方法
Shape.prototype.getName= function() {
console.log(this.name);
};
// Rectangle - 子类(subclass)
function Rectangle() {
}
Rectangle.prototype = new Shape();
var child = new Rectangle();
child.getName();
缺点:引用类型的属性被所有实例共享
// Shape - 父类(superclass)
function Shape() {
this.name = "123";
this.color = [1,2,3];
}
// 父类的方法
Shape.prototype.getName= function() {
console.log(this.name);
};
// Rectangle - 子类(subclass)
function Rectangle() {
}
Rectangle.prototype = new Shape();
var child = new Rectangle();
var child2 = new Rectangle();
child.getName();
child.color.push('yellow');
console.log(child.color); //打印输出 [1, 2, 3, "yellow"]
console.log(child2.color); //打印输出 [1, 2, 3, "yellow"]
缺点2:无法在不影响其它实例的前提下向父类传递参数
// Shape - 父类(superclass)
function Shape(color) {
this.name = "123";
this.color = color;
}
// 父类的方法
Shape.prototype.getName= function() {
console.log(this.name);
};
// Rectangle - 子类(subclass)
function Rectangle() {
}
Rectangle.prototype = new Shape([4,5,6])
var child = new Rectangle();
var child2 = new Rectangle();
console.log(child.color);
console.log(child2.__proto__.color);
构造函数继承
复制父类构造函数内的属性
// Shape - 父类(superclass)
function Shape(age) {
this.name = "123";
this.color = [1,2,3];
this.age = age;
this.setName = function () {
this.name = "456";
}
}
// 父类的方法
Shape.prototype.getName= function() {
console.log(this.name);
};
// Rectangle - 子类(subclass)
function Rectangle(age) {
//向父类传递age参数
Shape.call(this,age) //或使用 Shape.apply(this) 差别在于传参方式不同
}
var child = new Rectangle(10);
var child2 = new Rectangle(18);
//避免了引用类型的属性被所有实例共享
child.color.push('yellow');
console.log(child.color); //输出打印 [1, 2, 3, "yellow"]
console.log(child2.color); //输出打印 [1, 2, 3]
//只能继承父类的实例属性和方法,
child.setName();
console.log(child.name); //输出打印 456
console.log(child2.name); //输出打印 123
//无法实现复用,每个子类都有父类实例函数的副本,影响性能
console.log(child);
console.log(child2);
child.getName(); //不能继承原型属性/方法 报错
优点:1.避免了引用类型的属性被所有实例共享 2.可以向父类传递参数
缺点:1.只能继承父类的实例属性和方法,不能继承原型属性/方法 2.无法实现复用,每个子类都有父类实例函数的副本,影响性能
组合继承
结合原型链继承和构造函数继承通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用
// Shape - 父类(superclass)
function Shape(age) {
this.name = "123";
this.color = [1,2,3];
this.age = age;
}
// 父类的方法
Shape.prototype.getName= function() {
console.log(this.name);
};
// Rectangle - 子类(subclass)
function Rectangle(age) {
//向父类传递age参数
Shape.call(this,age) //或使用 Shape.apply(this) 差别在于传参方式不同
}
Rectangle.prototype = new Shape();
var child = new Rectangle(10);
var child2 = new Rectangle(18);
child.color.push('yellow');
console.log(child.color); //输出打印 [1, 2, 3, "yellow"]
console.log(child2.color); //输出打印 [1, 2, 3]
console.log(child);
console.log(child2);
child.getName();
优点:融合原型链继承和构造函数的优点,是JavaScript中最常用的继承模式
缺点:调用了两次父类构造函数,父类中的实例属性和方法既存在于子类的实例中,又存在于子类的原型中,不过仅是内存占用,因此,在使用子类创建实例对象时,其原型中会存在两份相同的属性/方法
组合继承(优化1)
让子类原型引用父类原型(剔除已有的父类的实例属性和方法,只要父类的原型属性/方法,同时减少了一次父类函数调用)
// Shape - 父类(superclass)
function Shape(age) {
this.name = "123";
this.color = [1,2,3];
this.age = age;
}
// 父类的方法
Shape.prototype.getName= function() {
console.log(this.name);
};
// Rectangle - 子类(subclass)
function Rectangle(age) {
//向父类传递age参数
Shape.call(this,age) //或使用 Shape.apply(this) 差别在于传参方式不同
}
Rectangle.prototype = Shape.prototype; //这里让子类原型引用父类原型
var child = new Rectangle(10);
var child2 = new Rectangle(18);
child.color.push('yellow');
console.log(child.color); //输出打印 [1, 2, 3, "yellow"]
console.log(child2.color); //输出打印 [1, 2, 3]
console.log(child);
console.log(child2);
child.getName();
缺点:如上图发现Rectangle的原型的构造器constructor成了Shape,按照我们的理解应该是Rectangle,这就造成了构造器紊乱,
组合继承(优化2)
指定子类原型构造器constructor为自身
// Shape - 父类(superclass)
function Shape(age) {
this.name = "123";
this.color = [1,2,3];
this.age = age;
}
// 父类的方法
Shape.prototype.getName= function() {
console.log(this.name);
};
// Rectangle - 子类(subclass)
function Rectangle(age) {
//向父类传递age参数
Shape.call(this,age) //或使用 Shape.apply(this) 差别在于传参方式不同
}
Rectangle.prototype = Shape.prototype; //这里让子类原型引用父类原型
Rectangle.prototype.constructor = Rectangle; //让子类原型构造器指向自身
var child = new Rectangle(10);
var child2 = new Rectangle(18);
child.color.push('yellow');
console.log(child.color); //输出打印 [1, 2, 3, "yellow"]
console.log(child2.color); //输出打印 [1, 2, 3]
console.log(child);
console.log(child2);
child.getName();
//这时我们打印子类构造器能正确打印结果
console.log(Rectangle.prototype.constructor) //Rectangle
//但是我们打印父类构造器出现了紊乱 也指向了子类Rectangle 因为父类原型是引用类型 当使用赋值操作时子类原型引用的是父类原型所指向的内存地址,当我们改变子类的原型constructor时同时也改变的父类的原型 constructor
console.log(Shape.prototype.constructor) // Rectangle
缺点:因为引用类型的特性,导致在修改子类的原型构造器同时也改变的父类的原型构造器
组合继承(优化3
设置一个中间对象,将中间对象的原型指向父类的原型
// Shape - 父类(superclass)
function Shape(age) {
this.name = "123";
this.color = [1,2,3];
this.age = age;
}
// 父类的方法
Shape.prototype.getName= function() {
console.log(this.name);
};
// Rectangle - 子类(subclass)
function Rectangle(age) {
//向父类传递age参数
Shape.call(this,age) //或使用 Shape.apply(this) 差别在于传参方式不同
}
//设置中间对象(方式一)
function f(){}
f.prototype = Shape.prototype;
Rectangle.prototype = new f();
//上面三行代码也可以简化成(方式二)
//Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle
var child = new Rectangle(10);
var child2 = new Rectangle(18);
child.color.push('yellow');
console.log(child.color); //输出打印 [1, 2, 3, "yellow"]
console.log(child2.color); //输出打印 [1, 2, 3]
console.log(child);
console.log(child2);
child.getName();
ES6实现继承
class Shape {
constructor (age) {
this.name = "123";
this.color = [1,2,3];
this.age = age;
}
getName () {
console.log(this.name)
}
}
class Rectangle extends Shape {
constructor (age) {
super(age)
}
}
const o1 = new Rectangle(10);
const o2 = new Rectangle(18);
o1.color.push(4)
console.log(o1.color)
console.log(o2.color)
o1.getName()
console.log(o1)
console.log(new Rectangle instanceof Shape)
相关资料
new 运算符
constructor
Object.create()
Object.assign()
深入理解JavaScript原型链与继承