一直对继承这个云里雾里的,看了B站视频结合文档记录一下~
Part1:预备知识
在此之前,你必须掌握以下知识,温故知新啦~
1. 构造函数的属性
举一个简单的构造函数的例子:
function Person(name) {
this.name = name;
this.age = [18];
this.say = function() {
console.log(`你好,我的名字是${name}.`)
}
}
注意:数组和方法都属于"实例引用属性",但是数组强调私有、不共享的。方法需要复用、共享。在构造函数中,一般很少又数组形式的引用属性,大部分都是:基本属性+方法
2. 什么是原型对象
每个函数都有prototype【显式原型】属性,它就是原型对象,通过函数实例化出来的对象有个__proto__【隐式原型】属性,指向原型对象
function Person(name) {
this.name = name;
this.age = [18];
this.say = function() {
console.log(`你好,我的名字是${name}.`)
}
}
let a = new Person('Katrina');
a.__proto__ = Person.prototype;
Person.prototype = {
constructor:Person,
... 其他属性和方法
}
补充:new的过程及实现
- 新建一个空对象
obj - 链接到原型:将
obj 的[[prototype]] 属性指向构造函数的原型,即obj.[[prototype]] = construc.prototype (不懂的可以看图理解一下【图来源于b站大佬】) - 绑定this:把构造函数内部的this绑定到新建的
obj 上,执行构造函数【显然这里需要用call、apply、bind来更改this指向】 - 返回新的对象
function create() {
let obj = {};
let constructor = Array.prototype.shift.call(arguments);
obj.__proto__= constructor.prototype;
let res = constructor.apply(obj,arguments);
return typeof === 'object' ? res : obj;
}
3. 原型对象的作用
原型对象的用途是为每个实例对象存储共享的方法和属性,它仅仅是一个普通的对象而已,并且所有的实例是共享同一个原型对象,因此有别于实例方法或属性,原型对象仅一份,而实例有很多份,且实例属性和方法是独立的。在构造函数中:为了属性(实例基本属性)的私有性以及方法(实例引用属性)的复用共享,提倡:
- 将属性封装在构造函数中
- 将方法定义在原型对象上
function Person(name) {
this.name = name;
}
Person.prototype.say = function() {
console.log('hello')
}
Part2:6种JS继承方式
1. 原型链继承
- 核心:将父类实例作为子类原型
- 优点:方法复用
- 由于方法定义在父类的原型上,复用了父类构造函数的方法
- 缺点:
- 创建子类实例的时候,不能传父类的参数(比如name)
- 子类实例共享了父类构造函数的引用属性
- 无法实现多继承
function Person(name) {
this.name = name || '无名氏';
this.arr = [1];
}
Person.prototype.say = function() {
console.log('hello');
};
function Student(grade) {
this.grade = grade;
};
Student.prototype = new Person();
Student.prototype.constructor = Student;
let s1 = new Student();
let s2 = new Student();
s1.say();
s2.say();
console.log(s1.say()==s2.say())
console.log(s1.name)
console.log(s1.name)
console.log(s1.name == s2.name)
s1.arr.push(2);
console.log(s2.arr);
2. 借用构造函数
- 核心:借用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类
- 优点:实例之间独立
- 创建子类实例,可以向父类构造函数传参数
- 子类实例不共享父类构造函数的引用属性
- 可实现多继承(通过多个call或者apply继承多个父类)
- 缺点:
- 父类方法不能复用
由于方法在父构造函数中定义,导致方法不能复用(因为每次创建子类实例都要创建一次方法) - 子类实例,继承不了父类原型上的属性(因为没有用到原型)
function Person(name) {
this.name = name || '无名氏';
this.arr = [1];
this.say = function() {
console.log('hrllo');
};
};
function Student(name, grade) {
Person.call(this, name);
this.grade = grade;
}
let s1 = new Student('Katrina','研一');
let s2 = new Student('Yang', '研二');
console.log(s1.name, s2.name);
s1.arr.push(2);
console.log(s2.arr);
console.log(s1.say === s2.say);
Person.prototype.walk = function() {
console.log('walk');
};
s1.walk();
3. 组合继承
- 核心:通过调用父类构造函数,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用
- 优点:
- 保留构造函数的优点:创建子类实例,可以向父类构造函数传参数
- 保留原型链的优点:父类的方法定义在父类的原型上,可以实现方法复用
- 不共享父类的引用属性
- 缺点:
- 由于调用了2次父类方法,要记得修复
Student.prototype.constructor 的指向
function Person(name) {
this.name = name || '无名氏';
this.arr = [1];
};
Person.prototype.say = function() {
console.log('hrllo');
};
function Student(name, grade) {
Person.call(this, name);
this.grade = grade;
}
Student.prototype = new Person();
Student.prototype.constructor = Student;
let s1 = new Student('Katrina','研一');
let s2 = new Student('Yang', '研二');
console.log(s1.name, s2.name);
console.log(s1.say === s2.say);
s1.arr.push(2);
console.log(s2.arr);
4. 实例继承
function Person(name) {
this.name = name || '无名氏';
this.arr = [1];
this.say = function() {
console.log('hello');
};
};
function Student(name,grade){
let instance = new Person();
instance.name = name || '无名氏';
this.grade = grade;
return instance
};
let s1 = new Student();
s1.say();
console.log(s1 instanceof Person);
console.log(s1 instanceof Student);
5. 拷贝继承
function Person(name) {
this.name = name || '无名氏';
this.arr = [1];
this.say = function() {
console.log('hello');
};
};
function Student(grade){
let person = new Person();
for (let p in person) {
Student.prototype[p] = person[p];
};
this.grade = grade;
};
let s1 = new Student();
s1.say();
console.log(s1 instanceof Person);
console.log(s1 instanceof Student);
6. 寄生组合继承
function Person(name) {
this.name = name || '无名氏';
this.arr = [1];
};
Person.prototype.say = function() {
console.log('hello');
}
function Student(name,grade){
Person.call(this,name);
this.grade = grade;
};
Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;
let s1 = new Student('Katrina','研一');
let s2 = new Student('Yang','研二');
|