[回炉计划]- javascript七大继承实现
栏目: JavaScript · 发布时间: 6年前
内容简介:当谈到继承时,JavaScript 只有一种结构:对象。每个实例对象(先定义一个父类
javascript
本身不提供一个 class
实现,(即使在 es6
中引入了 class
关键字,但那只是语法糖, javascript
仍然是基于原型的), class
本质上是一个函数。
class Person {}
Person instanceof Function; //true
复制代码
当谈到继承时,JavaScript 只有一种结构:对象。每个实例对象( object
)都有一个私有属性(称之为 __proto__
)指向它的构造函数的原型对象( prototype
)。该原型对象也有一个自己的原型对象( __proto__
) ,层层向上直到一个对象的原型对象为 null
。根据定义, null
没有原型,并作为这个原型链中的最后一个环节。继承与原型链-MDN
几种继承方式
先定义一个父类
function Person(name) {
this.name = name || "小明";
this.color = ["blue", "yellow"];
this.sleep = function() {
console.log(this.name + "正在睡觉");
};
}
Person.prototype.eat = function(food) {
console.log(this.name + "正在吃:" + food);
};
复制代码
1 原型链继承
原型链实现继承的思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。
function Man() {}
Man.prototype = new Person(); //实现了原型链继承
let xiaoLi = new Man();
Person.prototype.happy = function() {
console.log(this.name + "好(。・∀・)ノ゙嗨哦");
};
xiaoLi.sleep(); // '小明正在睡觉'
xiaoLi.happy(); //小明好(。・∀・)ノ゙嗨哦
Man.prototype.name = "小李";
xiaoli.sleep(); // '小李正在睡觉'
xiaoLi.eat("苹果"); // '小李正在吃'
复制代码
原型链继承实现简单,并且父类新增的实例和属性子类都能访问到
缺点:
- 可以在子类中增加实例属性,如果要新增加原型属性和方法需要在 new 父类构造函数的后面
- 无法实现多继承
-
创建子类实例时不能向父类的构造函数传参数
let xiaoLi = new Man('小李')是不行的,必须是通过Man.prototype.name = '小李';实现
2 借助构造函数(经典继承)
function Man(name) {
Person.call(this);
this.name = name || "小亮";
}
let xiaoLi = new Man("小李");
xiaoLi.sleep(); //小李在睡觉
xiaoLi.eat("苹果"); //VM525:1 Uncaught TypeError: xiaoLi.eat is not a function
复制代码
优点:
- 解决了子类构造函数向父类构造函数中传递参数
- 可以实现多继承(call 或者 apply 多个父类)
缺点:
- 方法都在构造函数中定义,无法复用
- 不能继承原型属性/方法,只能继承父类的实例属性和方法
3 组合继承
也叫做伪经典继承,是利用原型链和借用构造函数的技术组合在一起从而发挥两者之长的一种继承模式。思路是使用原型链实现对原型属性和方法的继承,而通过构造函数来实现对实现对实例属性的继承,这样。既通过在原型上定义方法实现复用,又能保证每个实例都有它自己的属性
function Man(name) {
// 继承属性
Person.call(this);
this.name = name || "小亮";
}
// 继承方法
Man.prototype = new Person();
Man.prototype.constructor = Person;
Man.prototype.sayHello = function() {
console.log(this.name + "hello");
};
let xiaoLi = new Man("小李");
xiaoLi.sleep(); //小李在睡觉
xiaoLi.sayHello(); //小李hello
xiaoLi.color.push("black");
console.log(xiaoLi.color); //["blue", "yellow","black"];
let xiaoqiang = new Man("小强");
iaoqiang.sleep(); //小强在睡觉
iaoqiang.sayHello(); //小强hello
console.log(iaoqiang.color); //["blue", "yellow"];
复制代码
优点:融合原型链继承和构造函数的优点,是 JavaScript 中最常用的继承模式。而且 instanceof
和 isPrototypeOf()
能够用于识别基于组合继承创建的对象
xiaoLi instanceof Person; //true 复制代码
4 原型式继承
基于已有的对象创建新对象,同时还不必因此创建自定义类型
function createObj(o) {
function F() {}
F.prototype = o;
return new F();
}
复制代码
上面这段函数就是 es5 Object.create
的模拟实现,将传入的对象作为创建对象的原型
缺点是:包含引用类型的属性值始终都会共享相应的值,这点跟原型链继承一样。 我们来看一个例子
let person = {
name: "小蔡",
like: ["唱", "跳", "rap"]
};
let xiaoLi = createObj(person);
let xiaoWang = createObj(person);
xiaoLi.name = "小李";
console.log(xiaoWang.name); //小蔡
xiaoLi.like.push("篮球");
console.log(xiaoWang.like); //["唱", "跳", "rap","篮球"]
复制代码
注意: 修改 xiaoLi.name
的值,x iaoWang.name
的值并未发生改变,并不是因为 xiaoLi
和 xiaoWang有
独立的 name
值,而是因为 xiaoLi.name = '小李'
,给 xiaoLi
添加了 name
值,并非修改了原型上的 name
值。
5 寄生式继承
封装一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后返回这个对象
function createObj(obj) {
var clone = Object.create(obj);
clone.sayHi = function() {
console.log("hi");
};
return clone;
}
let person = {
name: "小蔡",
like: ["唱", "跳", "rap"]
};
let xiaoLi = createObj(person);
xiaoLi.sayHi(); //hi
复制代码
缺点:跟借用构造函数模式一样,每次创建对象都会创建一遍方法。
6. 寄生组合式继承
组合继承的最大缺点就是他会调用两次父类的构造函数
function Man(name) {
// 继承属性
Person.call(this);
this.name = name || "小亮";
}
// 继承方法
Man.prototype = new Person();
Man.prototype.constructor = Person;
Man.prototype.sayHello = function() {
console.log(this.name + "hello");
};
let xiaoLi = new Man("小李");
复制代码
第一次调用
Man.prototype = new Person(); 复制代码
第二次调用
let xiaoLi = new Man("小李");
复制代码
主要是因为
Parent.call(this, name); //调用夫类构造函数 复制代码
如果我们不使用 Man.prototype = new Person();
,而是间接的让 Man.prototype
访问到 Person.prototype
,就可以避免这种情况
function Person(name) {
this.name = name || "小明";
this.color = ["blue", "yellow"];
}
Person.prototype.sleep = function() {
console.log(this.name + "正在睡觉");
};
function Man(name, age) {
Person.call(this, name);
this.age = age;
}
// 关键的三步
var F = function() {};
F.prototype = Person.prototype;
Man.prototype = new F();
var xiaoLi = new Man("小李");
复制代码
最后封装一下
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
function prototype(child, parent) {
var prototype = object(parent.prototype);
prototype.constructor = child;
child.prototype = prototype;
}
// 使用:
prototype(Child, Parent);
复制代码
简单调用一下
function Person(name) {
this.name = name || "小明";
this.color = ["blue", "yellow"];
}
Person.prototype.sleep = function() {
console.log(this.name + "正在睡觉");
};
function Man(name, age) {
Person.call(this, name);
this.age = age;
}
prototype(Man, Person);
let xiaoXia = new Man("小霞", 22);
xiaoxia.sleep(); //小霞正在睡觉
xiaoxia.age; //22
复制代码
寄生组合继承的高效率在于它只调用了一次父类构造函数,避免在 Person.prototype
上面创建不必要的、多余的属性,与此同时,原型链还能保持不变;因此,还能够正常使用 instanceof
和 isPrototypeOf
。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。
es6 Class 继承
class A {}
class B extends A {
constructor() {
super();
}
}
复制代码
关于 es6 的继承,我们后续再做详细介绍。只需要记住,class 只是语法糖,本质上还是函数。ES6 要求,子类的构造函数必须执行一次 super 函数。在子类的构造函数中,只有调用 super 之后,才可以使用 this 关键字,否则会报错。这是因为子类实例的构建,基于父类实例,只有 super 方法才能调用父类实例。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- [回炉计划]-实现一个图片预加载
- 回炉重造的 Win10 1809 因驱动问题再次暂停部分推送
- 七大查找算法
- 黑客进攻企业网络的七大“通道”
- 2019 年七大安全风险趋势分析
- 2019年七大安全风险趋势分析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Design for Hackers
David Kadavy / Wiley / 2011-10-18 / USD 39.99
Discover the techniques behind beautiful design?by deconstructing designs to understand them The term ?hacker? has been redefined to consist of anyone who has an insatiable curiosity as to how thin......一起来看看 《Design for Hackers》 这本书的介绍吧!