首先,定义一个类
function Animal (name) {
// 属性
this.name = name || 'Animal';
// 实例方法
this.sleep = function(){
console.log(this.name + '正在睡觉');
}
}
// 原型方法
Animal.prototype.eat = function(food) {
console.log(this.name + '正在吃:' + food);
};
1. 原型链继承
思想: 利用原型链来实现继承,超类的一个实例作为子类的原型
function Dog(){ }
Dog.prototype = new Animal();
Dog.prototype.name = '狗';
var dog = new Dog();
console.log(dog.name); // 狗
dog.eat('狗粮'); // 狗正在吃:狗粮
dog.sleep(); // 狗正在睡觉
console.log(dog instanceof Animal); // true
console.log(dog instanceof Dog); // true
特点:
- 非常纯粹的继承关系,实例是子类的实例,也是父类的实例;
- 父类新增原型方法/原型属性,子类都能访问到;
- 简单,易于实现;
缺点:
- 可以在Dog构造函数中,为Dog实例增加实例属性。如果要新增原型属性和方法,则必须放在new Animal()这样的语句之后执行;
- 无法实现多继承;
- 来自原型对象的所有属性被所有实例共享;
- 创建子类实例时,无法向父类构造函数传参;
2. 构造函数继承
思想: 通过使用call、apply方法在新创建的对象上执行构造函数,用父类的构造函数来增加子类的实例
function Dog(name){
Animal.call(this);
this.name = name || '汪汪';
}
var dog = new Dog();
console.log(dog.name); // 汪汪
dog.sleep(); // 汪汪正在睡觉
console.log(dog instanceof Animal); // false
console.log(dog instanceof Dog); // true
特点:
- 解决了方法1中,子类实例共享父类引用属性的问题;
- 创建子类实例时,可以向父类传递参数;
- 可以实现多继承(call多个父类对象);
缺点:
- 实例并不是父类的实例,只是子类的实例;
- 只能继承父类的实例属性和方法,不能继承原型属性/方法;
- 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能;
3. 实例继承
思想: 为父类实例添加新特性,作为子类实例返回
function Dog(name){
var instance = new Animal();
instance.name = name || '汪汪';
return instance;
}
var dog = new Dog();
console.log(dog.name); // 汪汪
dog.sleep(); // 汪汪正在睡觉
console.log(dog instanceof Animal); // true
console.log(dog instanceof Dog); // false
特点:
- 不限制调用方式,不管是new 子类()还是子类(),返回的对象具有相同的效果;
缺点:
- 实例是父类的实例,不是子类的实例;
- 不支持多继承;
4. 拷贝继承
function Dog(name){
var animal = new Animal();
for(var p in animal){
Dog.prototype[p] = animal[p];
}
Dog.prototype.name = name || '汪汪';
}
var dog = new Dog();
console.log(dog.name); // '汪汪'
dog.sleep(); // 汪汪正在睡觉
console.log(dog instanceof Animal); // false
console.log(dog instanceof Dog); // true
特点:
- 支持多继承;
缺点:
- 效率较低,内存占用高(因为要拷贝父类的属性);
- 无法获取父类不可枚举的方法(不可枚举方法,不能使用for in 访问到);
5. 原型式继承
思想: 采用原型式继承并不需要定义一个类!!!,传入参数obj,生成一个继承obj对象的对象
var animal = {
name: "汪汪",
age: 2,
}
function F(o) {
function C() {}
C.prototype = o;
return new C();
}
var dog = F(animal)
console.log(dog.name); // 汪汪
console.log(dog.age); // 2
特点:
- 直接通过对象生成一个继承该对象的对象;
缺点:
- 不是类式继承,而是原型式基础,缺少了类的概念;
6. 组合继承(推荐)
思想: 利用构造继承和原型链组合
function Dog(name){
Animal.call(this);
this.name = name || '汪汪';
}
Dog.prototype = new Animal();
Dog.prototype.constructor = Dog;
var dog = new Dog();
console.log(dog.name); // 汪汪
dog.sleep(); // 汪汪正在睡觉
console.log(dog instanceof Animal); // true
console.log(dog instanceof Dog); // true
特点:
- 弥补了方式2的缺陷,可以继承实例属性/方法,也可以继承原型属性/方法;
- 既是子类的实例,也是父类的实例;
- 不存在引用属性共享问题;
- 可传参;
- 函数可复用;
缺点:
- 调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了);
7. 寄生式继承
思想: 原型式+工厂模式 ,解决了组合继承两次调用构造函数的问题; 创建一个仅仅用于封装继承过程的函数,然后在内部以某种方式增强对象,最后返回对象;
//临时中转函数
function obj(o) {
function Animal() {}
Animal.prototype = o;
return new Animal();
}
//寄生函数
function create(o){
var Dog = obj(o);
// 可以对f进行扩展
Dog.sleep = function(){
console.log(this.name + '在睡觉')
}
return Dog;
}
var mydog = {
name: '汪汪',
age: 1,
};
var dog = create(mydog);
console.log(dog.name); // 汪汪
dog.sleep(); // 汪汪在睡觉
特点:
- 原型式继承的一种拓展
缺点:
- 依旧没有类的概念
8. 寄生组合继承(推荐)
思想: 结合寄生式继承和组合式继承,完美实现不带两份超类属性的继承方式
// 该实现没有修复constructor
function Dog(name){
Animal.call(this);
this.name = name || '汪汪';
}
(function(){
// 创建一个没有实例方法的类
var Super = function(){};
Super.prototype = Animal.prototype;
//将实例作为子类的原型
Dog.prototype = new Super();
})();
var dog = new Dog();
console.log(dog.name); // 汪汪
dog.sleep(); // 汪汪正在睡觉
console.log(dog instanceof Animal); // true
console.log(dog instanceof Dog); // true
补充:
// 定义一个类
function Animal (name) {
// 属性
this.name = name || 'Animal';
// 实例方法
this.sleep = function(){
console.log(this.name + '正在睡觉');
}
//实例引用属性
this.features = [];
}
function Dog(name){}
Dog.prototype = new Animal();
var tom = new Dog('Tom');
var kissy = new Dog('Kissy');
console.log(tom.name); // "Animal"
console.log(kissy.name); // "Animal"
console.log(tom.features); // []
console.log(kissy.features); // []
tom.name = 'Tom-New Name';
tom.features.push('eat');
//针对父类实例值类型成员的更改,不影响
console.log(tom.name); // "Tom-New Name"
console.log(kissy.name); // "Animal"
//针对父类实例引用类型成员的更改,会通过影响其他子类实例
console.log(tom.features); // ['eat']
console.log(kissy.features); // ['eat']
// 原因分析:
// 1. 执行tom.features.push,首先找tom对象的实例属性(找不到),那么去原型对象中找,也就是Animal的实例。发现有,那么就直接在这个对象的features属性中插入值。
// 2. 在console.log(kissy.features); 的时候。同上,kissy实例上没有,那么去原型上找。
// 3. 刚好原型上有,就直接返回,但是注意,这个原型对象中features属性值已经变化了。
特点:
- 堪称完美;
缺点:
- 实现较为复杂;
9. ES6中的继承(强烈推荐)
思想: ES6中通过 Class ‘类’ 这个语法糖实现继承和Java等面向对象的语言在实现继承上已经非常相似,当然只是语法层面相似,本质当然依旧是通过原型实现的。
ES6实现继承是通过关键字extends、super来实现继承,和面向对象语言Java一样。
class Animal{
constructor(name,age){
this.name = name || 'Animal';
this.age = age
this.hobbies = ['睡觉','吃饭']
}
/* 下面的写法就等同于把方法挂在原型上 */
static say(){ // 加了static 静态方法,只给类用的方法
console.log('你好');
} // 方法和方法之间不用加逗号
getname(){
console.log(this.name);
}
}
class Dog extends Animal{
/*
在子类constructor中添加属性的小技巧
专属于子类的属性写在参数的前面,父类的属性放在后面
这样一来,就能直接使用...arg
...arg
把函数中的多余的参数放入数组中体现出来。
*/
constructor(food,...arg){
super(...arg); // 等同于调用父类,把多余的参数(和父类一样的属性)放到super中,达到继承父类属性的目的
/*
在继承里,写constructor必须写super
super下面才能使用this,super有暂存死区(super上面不能使用this,用了就报错)
*/
this.food = food
}
static sayState(){
console.log("我在睡觉")
}
sayAge(){
console.log("我今年" + this.age + '岁');
}
sayFood(){
console.log('我在吃'+this.food)
}
}
var dog1 = new Dog('骨头',"汪汪",2);
Animal.say() // 你好
Dog.sayState(); // 我在睡觉
dog1.getname(); // 汪汪
dog1.sayAge(); // 我今年2岁
dog1.sayFood(); // 我在吃骨头
var dog2 = new Dog('狗粮',"旺财",1);
dog2.hobbies.push("玩耍");
dog2.getname(); // 旺财
dog2.sayAge(); // 我今年1岁
dog2.sayFood(); // 我在吃狗粮
console.log(dog1.hobbies) // ['睡觉','吃饭']
console.log(dog2.hobbies) // ['睡觉','吃饭','玩耍']
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/141449.html