一、原型链
学过java的同学应该都知道,继承是java的重要特点之一,许多面向对象的语言都支持两种继承方式:接口继承和实现继承,接口继承只继承方法签名,而实现继承则继承实际的方法,在js中,由于函数没有签名,因此支持实现继承,而实现继承主要是依靠原型链来实现的,那么,什么是原型链呢?
首先,我们先来回顾一下构造函数,原型和实例之间的关系
当我们创建一个构造函数时,构造函数会获得一个prototype属性,该属性是一个指针,指向一个原型对象,原型对象包含一个constructor属性,该属性也是一个指针,指向构造函数,而当我们创建构造函数的实例时,该实例其实会获得一个[[Prototype]]属性,指向原型对象
function SubType() {}
var instance = new SubType();
比如上面的代码,其中,SubType是构造函数,SubType.prototype是原型对象,instance是实例,这三者的关系可以用下面的图表示
而这个时候呢,如果我们让原型对象等于另一个构造函数的实例,此时的原型对象就会获得一个[[Prototype]]属性,该属性会指向另一个原型对象,如果另一个原型对象又是另一个构造函数的实例,这个原型对象又会获得一个[[Prototype]]属性,该属性又会指向另一个原型对象,如此层层递进,就构成了实例与原型的链条,这就是原型链
我们再看下上面的例子,如果这个时候,我们让SubType.prototype是另一个构造函数的实例,此时会怎么样呢?
function SuperType() {}
function SubType() {}
SubType.prototype = new SuperType();
var instance = new SubType();
最后,要提醒大家的是,所有引用类型默认都继承了Object,这个继承也是通过原型链实现的,因此,其实原型链的顶层就是Object的原型对象啦
二、继承
上面我们弄清了原型链,接下来主要就介绍一些经常会用到的继承方法,具体要用哪一种,还是需要依情况而定的
1、原型链继承
最常见的继承方法就是使用原型链实现继承啦,也就是我们上面所介绍的,接下来,还是看一个实际的例子把
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function() {
return this.property;
}
function SubType() {
ths.subproperty = true;
}
SubType.prototype = new SuperType(); // 实现继承
SubType.prototype.getSubValue = function() {
return this.subprototype;
}
var instance = new SubType();
console.log(instance.getSuperValue()); // true
当调用instance的getSuperValue()方法时,因此在instance实例上找不到该方法,就会顺着原型链先找到SubType.prototype,还是找不到该方法,继续顺着原型链找到SuperType.prototype,终于找到getSuperValue,就调用了该函数,而该函数返回property,该值的查找也是同样的道理,会在SubType.prototype中找到该属性,值为true,所以显示true
存在的问题:通过原型链实现继承时,原型实际上会变成另一个类型实例,而原先的实例属性也会变成原型属性,如果该属性为引用类型时,所有的实例都会共享该属性,一个实例修改了该属性,其它实例也会发生变化,同时,在创建子类型时,我们也不能向超类型的构造函数中传递参数
2、借用构造函数
为了解决原型中包含引用类型值所带来的问题,开发人员开始使用借用构造函数的技术实现继承,该方法主要是通过apply()和call()方法,在子类型构造函数的内部调用超类型构造函数,从而解决该问题
function SuperType() {
this.colors = ["red","blue","green"]
}
function SubType() {
SuperType.call(this); // 实现继承
}
var instance1 = new SubType();
var instance2 = new SubType();
instance2.colors.push("black");
console.log(instance1.colors"); // red,blue,green
console.log(instance2.colors"); // red,blue,green,black
使用这个方法,我们还可以在子类型构造函数中向超类型构造函数传递参数
function SuperType(name) {
this.name = name;
}
function SubType() {
SuperType.call(this,"Nicholas");
this.age = 29;
}
var instance = new SubType();
console.log(instance.name); // Nicholas
console.log(instance.age); // 29
缺点:定义方法时,将会在每个实例上都会重新定义,不能实现函数的复用
3、组合继承
组合继承主要是将原型链和借用构造函数的技术组合到一块,从而发货两者之长的一种继承模式,主要是使用原型链实现对原型属性和方法的基础,通过借用构造函数实现对实例属性的基础,这样,可以通过在原型上定义方法实现函数的复用,又能够保证每个实例都有自己的属性
function SuperType(name) {
this.name = name;
this.colors = ["red","blue","green"]
}
SuperType.prototype.sayName = function() {
console.log(this.name);
}
function SubType(name,age) {
SuperType.call(this,name);
this.age = age;
}
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function() {
console.log(this.age);
}
var instance1 = new SubType("Nicholas", 29);
var instance2 =new SubType("Greg", 27);
instance1.colors.push("black");
console.log(instance1.colors); // red,blue,green,black
console.log(instance2.colors); // red,blue,green
instance1.sayName(); // Nicholas
instance2.sayName(); // 29
instance1.sayAge(); // Greg
instance2.sayAge(); // 27
缺点:无论什么情况下,都会调用两次超类型构造函数,一次是在创建子类型的时候,另一次是在子类型构造函数内部,子类型最终会包含超类型对象的全部实例属性,但是需要在调用子类型构造函数时重写这些属性
4、原型式继承
原型式继承主要的借助原型可以基于已有的对象创建新的对象,基本思想就是创建一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回这个临时类型的一个新实例
function Object(o) {
function F() {}
F.prototype = o;
return new F();
}
从上面的例子我们可以看出,如果我们想创建一个对象,让它继承另一个对象的话,就可以将要被继承的对象当做o传递到Object函数里面去,Object函数里面返回的将会是一个新的实例,并且这个实例继承了o对象
其实,如果我们要使用原型式继承的话,可以直接通过Object.create()方法来实现,这个方法接收两个参数,第一个参数是用作新对象原型的对象,第二个参数是一个为新对象定义额外属性的对象,一般来说,第二个参数可以省略
var person = {
name: "Nicholas",
friends: ["Shelby","Court","Van"]
}
var anotherPerson = Object.create(person, {
name: {
value: "Greg"
}
});
console.log(anotherPerson.name); // Greg
缺点:原型式继承中包含引用类型的属性始终都会共享相应的值
5、寄生式继承
寄生式继承其实和我们前面说的创建对象方法中的寄生构造函数和工程模式很像,创建一个仅用于封装继承过程的函数,该函数在内部以某种方法来增强对象,最后再返回该对象
function createAnother(original) {
var clone = Object(original); // 通过调用函数创建一个新对象
clone.sayHi = function() {
console.log("hi");
}
return clone;
}
var person = {
name: "Nicholas",
friends:["Shelby","Court","Van"]
}
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); // hi
从上面的代码中我们可以看出,原来person是没有包含任何方法的,而通过将person传进去createAnother方法中进行加工,返回的新对象就包含了一个新的方法
缺点:不能实现函数的复用
6、寄生组合式继承
组合继承是js中最经常用到的一种继承方法,而我们前面也已经说了组合继承的缺点,组合继承需要调用两次超类型构造函数,一次是在创建子类型原型的时候,另一次是在子类型构造函数内部,子类型最终会包含超类型对象的全部实例属性,但是我们不得不在调用子类型构造函数时重写这些属性
function SuperType(name) {
this.name = name;
this.colors = ["red","blue","green"]
}
SuperType.prototype.sayName = function() {
console.log(this.name);
}
function SubType(name,age) {
SuperType.call(this,name); // 第二次调用超类型构造函数
this.age = age;
}
SubType.prototype = new SuperType(); // 第一次调用超类型构造函数
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function() {
console.log(this.age);
}
寄生组合式继承就是可以解决上面这个问题,寄生组合式继承主要通过借用构造函数来继承属性,通过原型链的混成形式来继承方法,其实就是不必为了指定子类型的原型而调用超类型的构造函数,只需要超类型原型的一个副本就可以了
function inheritPrototype(subType,SuperType) {
var prototype = Object(SuperType); // 创建对象
prototype.constructor = subType; // 增强对象
subType.prototype = prototype; // 指定对象
}
在上面的例子中,第一步创建了超类型原型的一个副本,第二步为创建的副本添加constructor属性,从而弥补因重写原型而失去的默认的constructor属性,最后一步将副本也就是新对象赋值给子类型的原型,因此,我们可以用这个函数去替换前面说到为子类型原型赋值的语句
function SuperType(name) {
this.name = name;
this.colors = ["red","blue","green"]
}
SuperType.prototype.sayName = function() {
console.log(this.name);
}
function SubType(name,age) {
SuperType.call(this,name);
this.age = age;
}
inheritPrototype(SubType,SuperType);
SubType.prototype.sayAge = function() {
console.log(this.age);
}
7、es6中的继承
我们在前面创建对象中也提到了es6中可以使用Class来创建对象,而同样的道理,在es6中,也新增加了extends实现Class的继承,Class 可以通过extends关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多
class Point {
}
class ColorPoint extends Point {
}
上面这个例子中可以实现ColorPoint类继承Point类,这种简洁的语法确实比我们上面介绍的那些方法要简洁的好多呀
但是呢,使用extends实现继承的时候,还是有几点需要注意的问题,子类在继承父类的时候,子类必须在constructor方法中调用super方法,否则新建实例时会报错,这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法,如果不调用super方法,子类就得不到this对象
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
class ColorPoint extends Point {
constructor(x, y, color) {
this.color = color; // ReferenceError
super(x, y);
this.color = color; // 正确
}
}
let cp = new ColorPoint(25, 8, 'green');
cp instanceof ColorPoint // true
cp instanceof Point // true
对于es6的继承,如果大家想继续了解,可以进行更进一步的学习
来自:https://www.cnblogs.com/Yellow-ice/archive/2019/03/04/10473176.html
原型继承缺点:1、不能由子对象像父对象传递参数,2、对于引用型的属性修改之后会印象其他的实例对象;构造函数继承缺点:1、不能继承父对象原型上的方法 2、每次实例化对象会重新构建函数,浪费内存。
在面向对象语言都会存在继承的概念,在面向对象语言中,继承的特点:继承了父类的属性和方法。那么我们现在主要研究css,css就是在设置属性的。不会牵扯到方法的层面。
1. 原型链继承;2,构造函数继承(对象冒充继承);3,组合继承(原型链继承+构造函数继承);4,原型式继承;5. 寄生组合式继承,为了解决引用共享和超类型无法传参的问题,我们采用一种叫借用构造函数的技术
prototype是构造函数的一个属性,它决定了在构造出来的对象上__proto__属性将是什么样的。如上图所示,理解JavaScript中的继承的关键是要理解母鸡如何产蛋的过程。
JavaScript常用继承方式主要分为(7种):原型链继承、构造函数继承、组合继承、原型式继承、寄生式继承、寄生组合继承以及继承多个对象。原型链继承(核心:将父类的实例作为子类的原型
继承到底是什么?继承(英语:inheritance)是面向对象软件技术当中的一个概念。如果一个类别B“继承自”另一个类别A,就把这个B称为“A的子类”,而把A称为“B的父类别”也可以称“A是B的超类”。继承可以使得子类具有父类别的各种属性和方法
当读取实例的属性时,如果找不到,就会查找与对象关联的原型中的属性,如果还查不到,就去找原型的原型,一直找到最顶层为止。如果让原型对象指向另一个类型的实例.....有趣的事情便发生了.
构造函数,就是专门用来生成实例对象的函数。一个构造函数,可以生成多个实例对象,这些实例对象都有相同的结构。为了与普通函数区别,构造函数名字的第一个字母通常大写。
JavaScript对象继承的方法有很多,这里总结一下几种比较常用的方法。使用call/apply和Object.create()第一种方法使用call或apply方法,改变了 this 的指向而实现继承,将父对象的构造函数绑定在子对象上,即在子对象构造函数中加一行
要搞懂JS继承,我们首先要理解原型链:每一个实例对象都有一个__proto__属性(隐式原型),在js内部用来查找原型链;每一个构造函数都有prototype属性(显示原型),用来显示修改对象的原型
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!