JavaScript实现继承的几种方式
JS作为面向对象的弱类型语言,继承也是其非常强大的特性之一
首先定义一个父类
// 定义一个动物类
function Animal (name) {
// 属性
this.name = name || 'Animal';
// 实例方法
this.sleep = function(){
console.log(this.name + '正在睡觉!');
}
}
// 原型方法
Animal.prototype.eat = function(food) {
console.log(this.name + '正在吃:' + food);
};
原型链继承
function Dog(name) {
// 属性
this.name = name || 'Dog';
}
Dog.prototype = new Person();
var dog = new Dog();
console.log(dog.name); // Dog
console.log(dog.eat('bone'));//Dog正在吃:bone
console.log(dog.sleep());//Dog正在睡觉
console.log(dog instanceof Animal); //true
console.log(dog instanceof Dog); //true
优点:
- 非常纯粹的继承关系,实例是子类的实例,也是父类的实例
- 父类新增原型方法/原型属性,子类都能访问到
- 简单,易于实现
缺点:
- 创建子类实例无法向父类构造函数传参
- 继承单一, 无法实现多继承
- 所有子类的实例都会共享父类实例的属性。(原型对象的所有属性被所有实例共享,当其中一个实例修改原型属性时, 其他实例的原型属性也会被改变)。
构造函数继承
function Cat(name, age){
Animal.call(this, name); //复制父类实例属性给子类
this.age = age || 5;
}
var cat = new Cat("Tom", 18);
console.log(cat.name); // Tom
console.log(cat.age); // 18
console.log(cat.sleep()); // Tom正在睡觉
console.log(cat.eat("fish")); // Uncaught TypeError: cat.eat is not a function
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true
优点:
- 只继承了父类构造函数的属性,方法。没有继承父类原型上的属性和方法。
- 解决了原型链继承的缺点
- 可以实现多继承(也就是可以继承多个构造函数属性,在子类构造函数中call多个构造函数)
- 在子实例对象中可以向父实例对象传参
缺点:
- 实例并不是父类的实例,只是子类的实例。
- 只能继承父类的实例属性和方法,不能继承原型属性/方法。
- 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
组合继承(原型链继承+构造函数继承) (常用)
核心:通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用
function Rabbit(name, age) {
Animal.call(this, name); // 构造函数继承, 第二次调用
this.age = age || 5;
}
Rabbit.prototype = new Animal(); //原型继承 第一次调用
Cat.prototype.constructor = Cat; //重点 组合继承也是需要修复构造函数指向的。
var rabbit = new Rabbit("jine");
console.log(rabbit.name); // jine
console.log(rabbit.age); // 5
console.log(rabbit.sleep()); // jine正在睡觉
console.log(rabbit.eat("carrot")); // jine正在吃:carrot
console.log(rabbit instanceof Animal); // true
console.log(rabbit instanceof Rabbit); // false
优点:
- 弥补了构造函数的缺陷。可以继承实例属性/方法,也可以继承原型属性/方法
- 可传参
- 函数可复用
- 每个子类实例引入的构造函数属性是私有的。不存在引用属性共享问题
- 既是子类的实例,也是父类的实例
缺点:
- 调用了两次父类构造函数,生成了两份实例(损耗内存), 子类的构造函数会代替原型上的父类构造函数。
原型式继承
function CreateObject(obj) {
var Fun = function() {};
Fun.prototype = obj; // obj.__proto__===Object.prototype true; Fun.prototype.constructor === Object true
return new Fun();
}
var person = {
name: "xiaoming",
friend: ["lily", "xiaohong"]
}
var person1 = CreateObject(person);
var person2 = CreateObject(person);
//person1.name改变后,person2.name并未改变, 是因为person.name这个属性是基础类型,不是引用类型。
person1.name = "xiaohe";
console.log(person2.name); //"xiaoming"
console.log(person.name); //"xiaoming"
//由于person.friend是一个引用类型,person1.friend修改的时候,会同时改变当前引用类型地址存放的值,导致了person和person2的值改变。
person1.friend.push("xiaozhang");
console.log(person2.friend); // ["lily", "xiaohong", "xiaozhang"]
console.log(person.friend);// ["lily", "xiaohong", "xiaozhang"]
person1.friend = ["xiaowang"]; //将person1的friend属性指向另一个引用地址。
console.log(person2.friend); // ["lily", "xiaohong", "xiaozhang"]
console.log(person.friend);// ["lily", "xiaohong", "xiaozhang"]
//person1.friend引用指向的内存地址和person,person2的不同,所以person2和person的friend属性不会改变。
person1.friend.push("xiaoli");
console.log(person1.friend); // ["xiaowang", "xiaoli"]
console.log(person2.friend); // ["lily", "xiaohong", "xiaozhang"]
console.log(person.friend);// ["lily", "xiaohong", "xiaozhang"]
优点: 类型与一个对象的复制,用函数包装了一层而已
缺点:
- 包含引用类型值的属性始终都会共享相应的值
- 无法实现复用。(新实例属性都是后面添加的)
寄生继承
//这个构造函数与Object.create()的效果是一样的
function CreateObject(obj) {
var Fun = function() {};
Fun.prototype = obj;
return new Fun();
}
var person = {
name: "xiaoming",
friend: ["lily", "xiaohong"]
}
function CreatChild(obj) {
//创建对象,或者用var newObj = Object.create(obj);
var newObj = CreateObject(obj);
newObj.sayName = function() { //增强对象
console.log(this.name);
}
return newObj;
}
var child1 = CreatChild(obj);
child1.sayName(); // xiaoming
child1.name="xiaohua";
child1.sayName(); // xiaohua
优点:没有创建自定义类型, 因为只是套了个壳子返回对象。可以增强对象。 缺点: 没有用到原型, 无法复用属性及方法。
寄生组合继承
// 通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型
function Person(name){
this.name = name;
this.colors = ["red","green","blue"];
}
Person.prototype.sayName = function(){
console.log(this.name);
};
function Child(name,age){
Person.call(this,name); //构造函数继承
this.age = age;
}
(function(){
// 创建超类型原型的一个副本
var anotherPrototype = Object.create(Person.prototype);
// 重设因重写原型而失去的默认的 constructor 属性
anotherPrototype.constructor = Child;
// 将新创建的对象赋值给子类型的原型
Child.prototype = anotherPrototype;
})();
Child.prototype.sayAge = function(){
console.log(this.age);
};
var child1 = new Child("luochen",22);
child1.colors.push("purple");
console.log(child1.colors); // "red,green,blue,purple"
child1.sayName();
child1.sayAge();
var child2 = new SubType("tom",34);
console.log(child2.colors); // "red,green,blue"
child2.sayName();
child2.sayAge();
重点: 修复了组合继承的问题。
实例继承
function Cat(name){
var instance = new Animal();
instance.name = name || 'Tom';
return instance;
}
var cat = new Cat();
console.log(cat.name); // Tom
console.log(cat.sleep()); // Tom正在睡觉
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // false
缺点:
- 实例是父类的实例,不是子类的实例
- 不支持多继承
拷贝继承
function Cat(name){
var animal = new Animal();
for(var p in animal){
Cat.prototype[p] = animal[p];
}
this.name = name || 'Tom';
}
var cat = new Cat();
console.log(cat.name); // "Tom"
console.log(cat.sleep()); // Tom正在睡觉
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true
优点:支持多继承
缺点:
- 效率较低,内存占用高(因为要拷贝父类的属性)
- 无法获取父类不可枚举的方法(不可枚举方法,不能使用for in 访问到)
ES6 Class语法糖继承
class Person {
constructor(name) {
this.name = name||"default name";
}
reName(name) {
this.name = name;
}
}
class Child extends Person {
constructor(name) {
super(name);
}
}
var child1 = new Child();
console.log(child1.name); // default name
child1.reName("Tom");
console.log(child1.name); // Tom
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!