相关知识点
- 原型链继承
- 构造函数继承
- 组合继承
- 寄生组合继承
- 原型式继承
- 寄生式继承
- class 中的继承
原型链继承
将子类的原型对象指向父类的实例
题目一
function Parent() {
this.name = "Parent";
this.sex = "男";
}
Parent.prototype.getName = function () {
console.log(this.name);
};
function Child() {
this.name = "child";
}
Child.prototype = new Parent();
var child1 = new Child();
console.log(child1); // Child {name: "Child"}
child1.getName(); // child
child1 是通过子类构造函数 Child 生成的对象,自身就有属性 name,并且属性值也是自己的 child。然后子构造函数 Child 它的原型被指向了父类构造函数 Parent 创建出来的匿名实例。这样的话,child1 就可以使用这个匿名实例里的所有属性和方法了,因此,child1.getName() 有效,并且打印出 child。由于 sex、getName 都是 Child 原型对象上的属性,所以并不会表现在 child1 上。
这种方式就叫做原型链继承,将子类的原型对象指向父类的实例。
写个伪代码,方便记忆:
Child.prototype = new Parent();
更加严谨一点的做法其实还有一步:Child.prototype.constructor = Child;
题目二
function Parent() {
this.name = "Parent";
this.sex = "男";
}
Parent.prototype.getSex = function () {
console.log(this.sex);
};
function Child() {
this.name = "Child";
}
Child.prototype = Parent.prototype;
var child1 = new Child();
console.log(child1); // Child {name: "Child"}
child1.getSex(); // undefined
如果原型链继承使用 Child.prototype = Parent.prototype;,child1 上能使用的属性和方法只有 name、getSex,所以 getSex 打印出的会是 undefined,打印出的 child1 只有 name 属性,getSex 为原型上的方法所以并不会表现出来。
题目三
function Parent(name) {
this.name = name;
this.sex = "boy";
this.colors = ["white", "black"];
}
function Child() {
this.feature = ["cute"];
}
var parent = new Parent("parent");
Child.prototype = parent;
var child1 = new Child("child1");
// 相当于给 child1 的实例对象增加 sex
child1.sex = "girl";
child1.colors.push("yellow");
child1.feature.push("sunshine");
var child2 = new Child("child2");
console.log(child1); // Child{ feature: ['cute', 'sunshine'], sex: 'girl' }
console.log(child2); // Child{ feature: ['cute'] }
console.log(child1.name); // parent
console.log(child1.colors); // ["white", "black", "yellow"]
console.log(child2.colors); // ["white", "black", "yellow"]
console.log(parent); // Parent {name: "parent", sex: 'boy', colors: ['white', 'black', 'yellow'] }
child1 在创建完之后,就设置了 sex,并且给 colors 和 feature 都 push 了新的内容。child.sex = 'girl' 这段代码相当于是给 child 这个实例对象增加了一个 sex 属性。相当于是:原本我是没有 sex 这个属性的,我想要获取就得拿原型对象 parent 上的 sex ,但是现在你加了一句 child1.sex 就等于是我自己也有了这个属性了,就不需要你原型上的了,所以并不会影响到原型对象 parent 上。
但是 child1.colors 这里,注意它的操作,它是直接使用了 .push() 的,也就是说我得先找到 colors 这个属性,发现实例对象 parent 上有,然后就拿来用了,之后执行 push 操作,所以这时候改变的是原型对象 parent 上的属性,会影响到后续所有的实例对象。
这里需要注意一下,此时的 colors 和 sex 操作方式不同,所以效果也就不同,sex 使用 = 进行赋值,不管有没有 sex 属性就直接赋值了,而 colors.push 操作是先有 colors 且类型是数组才行,如果你使用的是 child1.colors = ['yellow'] 这样才不会影响 parent )。
而 feature 它是属于 child1 实例自身的属性,它添加还是减少都不会影响到其他实例。
因此 child1 打印出了 feature 和 sex 两个属性。( name 和 colors 属于原型对象上的属性并不会被表现出来)。child2 没有做任何操作,所以它打印出的还是它自身的一个 feature 属性。
child1.name 是原型对象 parent 上的 name,也就是 parent,虽然我们在 new Child 的时候传递了 child1,但它显然是无效的,因为接收 name 属性的是构造函数 Parent,而不是 Child。
child2.colors 由于用的也是原型对象 parent 上的 colors,又由于之前被 child1 给改变了,所以打印出来的会是 ['white', 'black', 'yellow']。
将最后的原型对象 parent 打印出来,name 和 sex 没变,colors 却变了。
总结-原型链继承
- 优点:
- 继承了父类的模板,又继承了父类的原型对象;
- 缺点:
- 如果要给子类的原型上新增属性和方法,就必须放在
Child.prototype = new Parent();语句的后面; - 无法实现多继承,因为已经指定了原型对象了;
- 来自原型对象的所有属性都被共享了,这样如果不小心修改了原型对象中的引用类型属性,那么所有子类创建的实例对象都会受到影响(这点从修改
child1.color可以看出来); - 创建子类时,无法向父类构造函数传参数(这点从
child1.name可以看出来);
- 如果要给子类的原型上新增属性和方法,就必须放在
构造继承(构造函数继承)
在子类构造函数内部使用 call 或 apply 来调用父类构造函数
题目一
function Parnet(name) {
this.name = name;
}
function Child() {
this.sex = "boy";
Parnet.call(this, "child");
}
var child1 = new Child();
console.log(child1); // Child {sex: "boy", name: "child"}
我们使用了 Parent.call(this, 'child') ,.call 函数是会立即执行的,而这里又用了 .call 来改变 Parent 构造函数内的指向,所以可以将它转化为伪代码:
function Child () {
this.sex = 'boy'
// 伪代码
this.name = 'child'
}
可以理解为相当于是直接执行了 Parent 里的代码。
使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类。
构造继承的原理就是:在子类构造函数内部使用 call 或 apply 来调用父类构造函数。
题目二
function Parent(name) {
this.name = name;
}
function Child() {
this.sex = "boy";
Parent.call(this, "good boy");
this.name = "bad boy";
}
var child1 = new Child();
console.log(child1); // Child {sex: "boy", name: "bad boy"}
如果换一下位置:
function Parent(name) {
this.name = name;
}
function Child() {
this.sex = "boy";
this.name = "bad boy";
Parent.call(this, "good boy");
}
var child1 = new Child();
console.log(child1); // Child {sex: "boy", name: "good boy"}
题目三
解决了原型链继承中子类共享父类引用对象的问题。
function Parent(name, sex) {
this.name = name;
this.sex = sex;
this.colors = ["white", "black"];
}
function Child(name, sex) {
Parent.call(this, name, sex);
}
var child1 = new Child("child1", "boy");
child1.colors.push("yellow");
var child2 = new Child("child2", "girl");
console.log(child1); // Child{ name: 'child1', sex: 'boy', colors: ['white', 'black', 'yellow'] }
console.log(child2); // Child{ name: 'child2', sex: 'girl', colors: ['white', 'black'] }
在原型链继承中,子类构造函数创建的实例是会查找到原型链上的 colors 的,而且改动它会影响到其它的实例,这是原型链继承的一大缺点。
而构造函数继承,发现修改 child1.colors 并不会影响其它的实例(child2),使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类。所以现在 child1 和 child2 现在分别有它们各自的 colors 了,就不共享了。
而且这种拷贝属于深拷贝。
构造函数继承优点:解决了原型链继承中子类实例共享父类引用对象的问题,实现多继承,创建子类实例时,可以向父类传递参数。
题目四
function Parent(name) {
this.name = name;
}
Parent.prototype.getName = function () {
console.log(this.name);
};
function Child() {
this.sex = "boy";
Parent.call(this, "good boy");
}
Child.prototype.getSex = function () {
console.log(this.sex);
};
var child1 = new Child();
console.log(child1); // Child {sex: "boy", name: "good boy"}
child1.getSex(); // boy
child1.getName(); // Uncaught TypeError: child1.getName is not a function
构造函数继承的缺点:构造函数只能继承父类的实例属性和方法,不能继承父类原型上的属性和方法。
题目五
function Parent(name) {
this.name = name;
}
function Child() {
this.sex = "boy";
Parent.call(this, "child");
}
var child1 = new Child();
console.log(child1); // Child {sex: "boy", name: "child"}
console.log(child1 instanceof Child); // true
console.log(child1 instanceof Parent); // false
console.log(child1 instanceof Object); // true
第一个 true 很好理解,child1 本身就是 Child 的实例。
第二个输出 false,是因为 child1 只是复制 Child 的属性和方法,并不能继承它。
第三个输出 true 是必然的,实例的原型链如果没有发生改变的话最后都能找到 Object.prototype 。
构造函数继承的第二缺点:实例并不是父类的实例,只是子类的实例。
总结-构造函数继承
- 优点:
- 解决了原型链继承中子类实例共享父类引用对象的问题,实现多继承,创建子类实例时,可以向父类传递参数;
- 缺点:
- 构造继承只能继承父类的实例属性和方法,不能继承父类原型的属性和方法;
- 实例并不是父类的实例,只是子类的实例;
- 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能;
今天就先总结到这里,下一期继续总结后续内容 JS中的继承-2【总结】~~