对象A通过继承B对象,就能直接拥有B对象的所有属性和方法,这对于代码的复用是非常有用的
1.原型链 继承
function Parent() {
this.name = "chu"
}
Parent.prototype.getName = function () {
console.log(this.name)
}
function Child() {}
Child.prototype = new Parent()
Child.prototype.constructor = Child
const child = new Child()
console.log(child.getName()) //chu
问题:
1.引用类型(对象)的属性被所有实例共享,例子
function Parent() {
this.names = ["chu", "chu1"]
}
function Child() {}
Child.prototype = new Parent()
var child1 = new Child()
child1.names.push("yayu")
console.log(child1.names) //["chu", "chu1", "yayu"]
var child2 = new Child()
console.log(child2.names) //["chu", "chu1", "yayu"]
2.在创建 Child 的实例时,不能向 Parent 传参
2.借用构造函数(经典继承 )
就是子类构造函数中使用 apply 或 call 调用父类构造函数
本来,父类构造函数中的 this 是父类的实例,但是这里,在子类构造函数中通过 call(this) 调用了父类构造函数,把上下文修改为了子类实例,相当于把父类实例的属性给子类实例复制了一份
function Parent() {
this.names = ["chu", "chu1"]
}
//子类本身没有实例属性 但是借用了父类实例
function Child() {
Parent.call(this)
}
var child1 = new Child()
child1.names.push("yayu")
console.log(child1.names) //["chu", "chu1", "yayu"]
var child2 = new Child()
console.log(child2.names) // ["chu", "chu1"]
优点:
1.避免了引用类型的属性被所有实例共享
2.可以在Child 中向Parent 传参
function Parent(name) {
this.name = name
}
function Child(name) {
Parent.call(this, name)
}
var child1 = new Child("kevin")
console.log(child1.name)
var child2 = new Child("daisy")
console.log(child2.name)
缺点:方法都在构造函数中定义,每次创建实例都会创建一遍方法
补充:
缺点:父类原型对象中一旦存在父类之前自己定义的方法,那么子类将无法继承这些方法。
只能继承父类的实例属性和方法,不能继承原型属性或方法。
见下图
function Parent1() {
this.name = "parent1";
}
Parent1.prototype.getName = function () {
return this.name;
};
function Child1() {
Parent1.call(this);
this.type = "child1";
}
let child = new Child1();
console.log(child); // 没问题
console.log(child.getName()); // 会报错
3.组合继承
就是原型链继承+借用构造函数
- 使用借用构造函数的方法,复制一份父类实例p的属性到子类实例c上
- 使用原型链的方法,把子类实例挂到原型链上,使得子类实例也能够访问父类原型对象上的属性和方法 ```javascript function Parent(name){ this.name = name this.colors = [“red”,”blue”,”green”] }
Parent.prototype.getName = function(){ console.log(this.name) }
function Child(name,age){ //第二次调用Parent() Parent.call(this,name) this.age = age } //第一次调用Parent() Child.prototype = new Parent() //手动挂上构造器,指向自己的构造函数 Child.prototype.constructor = Child
var child1 = new Child(“kevin”,18) child1.colors.push(“black”) console.log(child1.name) console.log(child1.age) console.log(child1.colors)
var child2 = new Child(“daisy”,20)
优点:融合原型链继承和构造函数的优点<br />缺点:因为使用了原型链,一个子类实例将会持有两份父类实例的数据,一份是 Parent.call(this) 复制到子类实例c上的数据,一份是父类实例原本的数据,位于 c._ _proto__ 上。<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/618347/1611618477032-94d01fc5-18df-47f4-8f9b-442ab8574b4a.png#align=left&display=inline&height=289&margin=%5Bobject%20Object%5D&name=image.png&originHeight=289&originWidth=449&size=19577&status=done&style=none&width=449)
补充:Parent执行了两次,第一次是改变Child的prototype的时候,第二次是通过call方法调用Parent多构造一次就多进行了一次性能开销。
<a name="yg6cP"></a>
## 4.原型式继承
ES5 `Object.create` 的模拟实现,将传入的对象作为创建的对象的原型
创建一个空对象,并把它挂载到另一个对象的原型链上
```javascript
function createObj(o){
function F(){}
F.prototype = o
return new F()
}
缺点:包含引用类型的属性值始终都会共享相应的值,这点跟原型链继承一样
function createObj(o){
function F(){}
F.prototype = o
return new F()
}
var person = {
name:"kevin",
friends:["daisy","kelly"]
}
var person1 = createObj(person)
var person2 = createObj(person)
console.log(person1) //F {}
//注意:修改person1.name的值,person2.name的值并未发生改变,
并不是因为person1和person2有独立的 name 值,而是因为person1.name = 'person1',
给person1添加了 name 值,并非修改了原型上的 name 值。
person1.name = "person1"
console.log(person2.name) //值类型
person1.friends.push("taylor")
console.log(person2.friends) // ["daisy", "kelly", "taylor"]
5.寄生式继承
原型式继承的增强版。使用原型式继承可以获得一份目标对象的浅拷贝,然后利用这个浅拷贝的能力再进行增强,添加一些方法。
创建一个仅用于封装继承过程的函数,该函数在内部以某种形式来做增强对象,最后返回对象。
function createObj(o){
//使用了前面的 createObject 函数,生成了一个子类实例
var clone = Object.create(o)
//先在子类实例上添加一点属性或方法
clone.sayName = function(){
console.log("hi")
}
//再返回
return clone
}
let parent5 = {
name: "parent5",
friends: ["p1", "p2", "p3"],
getName() {
return this.name;
},
};
function clone(original) {
let clone = Object.create(original);
clone.getFriends = function () {
return this.friends;
};
return clone;
}
let person5 = clone(parent5);
console.log(person5.getName());
console.log(person5.getFriends());
6.寄生组合式继承(常用)
借用构造函数继承 + 寄生式继承
组合继承的缺点,会有两份父类实例的数据。这两份数据中,通过Parent.call(this) 复制到子类实例 c 上的这一份是真正需要的,而 c. proto 上的这一份是多余的,是原型链的副作用。
优化:把子类实例添加到父类实例的原型链上,同时又不让父类实例的属性和方法也在原型链上
- 创建一个没有实例属性的父类实例
- 让子类实例绕过父类实例,直接继承父类的原型对象
function Parent(name) {
this.name = name
this.colors = ["red", "blue", "green"]
}
Parent.prototype.getName = function () {
console.log(this.name)
}
function Child(name, age) {
Parent.call(this, name)
this.age = age
}
var child1 = new Child("kevin", 18)
console.log(child1)
封装
function object(o){
function F(){}
F.prototype = o
return new F()
}
function prototype(child,parent){
var prototype = object(parent.property)
prototype.constructor = child
child.prototype = prototype
}
prototype(Child,Parent)
例子
function clone(parent,child){
child.prototype = Object.create(parent.prototype)
child.prototype.constructor = child
}
function Parent6(){
this.name = 'parent6'
this.play = [1,2,3]
}
Parent6.prototype.getName = function(){
return this.name
}
function Child6(){
Parent6.call(this)
this.friends = 'child5'
}
clone(Parent6,Child6)
Child6.prototype.getFriends = function(){
return this.friends
}
let person6 = new Child6()
console.log(person6)
console.log(person6.getName())
console.log(person6.getFriends())
ES6 extends
https://es6.ruanyifeng.com/#docs/class-extends
class Person {
constructor(name) {
this.name = name;
}
// 原型方法
// Person.prototype.getName=function(){}
getName() {
console.log(this.name);
}
}
class Gamer extends Person {
constructor(name, age) {
//子类中存在构造函数 则需要在使用this之前调用super()
super(name);
this.age = age;
}
}
const asuna = new Gamer("Asuna", 20);
asuna.getName();
文章
https://github.com/mqyqingfeng/Blog/issues/16