核心:将父类的实例做为子类的原型javascript
首先,要知道构造函数、原型和实例之间的关系:构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个原型对象的指针。前端
function Father(){
this.name = '父类的名字';
}
Father.prototype.getFatherName = function(){
console.log('父类的方法');
}
function Son(){
this.name = '子类的名字';
}
// 若是此时有Son的原型对象有方法或属性,下面Son.prototype = new Father(),因为原型重定向,原型上的方法和属性会丢失
Son.prototype.getAge = function(){
console.log('子类的年龄')
}
Son.prototype = new Father(); // 核心:建立父类的实例,并将该实例赋值给子类的prototype
Son.prototype.getSonName = function(){
console.log('子类的方法');
}
var son = new Son();
son.getFatherName(); // 父类的方法
Son.prototype.__proto__.getFatherName = function(){ // 缺点:若是有多个实例对其父类原型,则会互相影响
console.log('子类改变父类的方法');
}
son.getFatherName(); // 子类改变父类的方法
复制代码
缺点:java
父类使用this声明的属性(私有属性和公有属性)被全部实例共享,在多个实例之间对引用类型数据操做会互相影响。后端
建立子类实例时,没法向父类构造函数传参。函数
核心:使用父类的构造函数来加强子类实例,即复制父类的实例属性给子类性能
function Father(name, age){
this.name = name;
this.age = age;
}
Father.prototype.getFatherName = function(){
console.log('父类的方法');
}
function Son(name, age, job){
Father.call(this,name, age); // 继承自Father
this.job = job;
}
var son = new Son('jacky', 22, '前端开发');
//son.getFatherName(); // Uncaught TypeError: son.getFatherName is not a function
复制代码
优势:ui
缺点:this
核心:组合上述两种方法,用原型链实现对原型属性和方法的继承,用借用构造函数技术来实现实例属性的继承。spa
function Father(name, age){
this.name = name;
this.age = age;
this.sex = 'man';
}
Father.prototype.getFatherName = function(){
console.log('父类的方法')
}
function Son(name, age, job){
Father.call(this,name,age); // 第二次调用:建立子类型实例的时候
this.job = job;
}
Son.prototype = new Father(); // 第一次调用:设置子类型实例的原型的时候
Son.prototype.constructor = Son; // prototype构造器指回本身
var son = new Son('jacky', 22, '前端开发');
son.getFatherName();
console.log(son)
复制代码
优势:prototype
缺点:
拓展:
返回建立实例对象的Object构造函数的引用。
当咱们只有实例对象没有构造函数的引用时: 某些场景下,咱们对实例对象通过多轮导入导出,咱们不知道实例是从哪一个函数中构造出来或者追踪实例的构造函数,较为艰难。(它主要防止一种状况下出错,就是你显式地去使用构造函数。好比,我并不知道instance是由哪一个函数实例化出来的,可是我想clone一个,这时就能够这样——>instance.constructor) 这个时候就能够经过实例对象的constructor属性来获得构造函数的引用
let instance = new sonFn() // 实例化子类
export instance;
// 多轮导入+导出,致使sonFn追踪很是麻烦,或者不想在文件中再引入sonFn
let fn = instance.constructor
复制代码
所以每次重写函数的prototype都应该修正一下constructor的指向,以保持读取constructor指向的一致性
核心:利用一个空对象做为中介,将某个对象直接赋值给空对象构造函数的原型,而后返回这个函数的调用,这个函数就变成了个能够随意增添属性的实例或对象。
/* Object.create() 的实现原理 */
// cloneObject()对传入其中的对象执行了一次浅拷贝,将构造函数F的原型直接指向传入的对象。
function cloneObject(obj){
function F(){}
F.prototype = obj; // 将传进来obj对象做为空函数的prototype
return new F(); // 此对象的原型为被继承的对象, 经过原型链查找能够拿到被继承对象的属性
}
var father = {
name: 'jacky',
age: 22,
courses: ['前端']
}
// var son1 = Object.create(father); // 效果同样
var son1 = cloneObject(father);
son1.courses.push('后端');
var son2 = cloneObject(father);
son2.courses.push('全栈');
console.log(father.courses); // ["前端", "后端", "全栈"]
复制代码
优势:
从已有对象衍生新对象,不须要建立自定义类型
缺点:
与原型链继承同样。多个实例共享被继承对象的属性,存在篡改的可能;也没法传参。
核心:在原型式继承的基础上,建立一个仅用于封装继承过程的函数,该函数在内部以某种形式来作加强对象(增长了一些新的方法和属性),最后返回对象。
使用场景:专门为对象来作某种固定方式的加强。
function createAnother(obj){
var clone = Object.create(obj);
clone.skill = function(){ // 以某种方式来加强这个对象
console.log('run');
};
return clone;
}
var animal = {
eat: 'food',
drink: 'water'
}
var dog = createAnother(animal);
dog.skill();
复制代码
优势:没有建立自定义类型,由于只是套了个壳子增长特定属性/方法返回对象,以达到加强对象的目的
缺点:
同原型式继承:原型链继承多个实例的引用类型属性指向相同,存在篡改的可能,也没法传递参数
核心:结合借用构造函数传递参数和寄生模式实现继承
function Father(name, age){
this.name = name;
this.age = age;
}
Father.prototype.getFatherName = function(){
console.log('父类的方法')
}
function Son(name, age, job){
Father.call(this,name,age); // 借用构造继承: 继承父类经过this声明属性和方法至子类实例的属性上
this.job = job;
}
// 寄生式继承:封装了son.prototype对象原型式继承father.prototype的过程,而且加强了传入的对象。
function inheritPrototype(son,father){
var clone = Object.create(father.prototype); // 原型式继承:浅拷贝father.prototype对象
clone.constructor = son; // 加强对象,弥补因重写原型而失去的默认的constructor 属性
son.prototype = clone; // 指定对象,将新建立的对象赋值给子类的原型
}
inheritPrototype(Son,Father); // 将父类原型指向子类
// 新增子类原型属性
Son.prototype.getSonName = function(){
console.log('子类的方法')
}
var son = new Son('jacky',22,'前端开发');
console.log(son);
复制代码
缺点:
硬要说的话,就是给子类原型添加属性和方法的时候,必定要放在inheritPrototype()方法以后
核心: 类之间经过extends关键字实现继承,清晰方便。 class 仅仅是一个语法糖,它的核心思想仍然是寄生组合式继承。
class Father {
constructor(name, age) {
this.name = name;
this.age = age;
}
skill() {
console.log('父类的技能');
}
}
class Son extends Father {
constructor(name, age, job){
super(name, age); // 调用父类的constructor,只有调用super以后,才可使用this关键字
this.job = job;
}
getInfo() {
console.log(this.name, this.age, this.job);
}
}
let son = new Son('jacky',22,'前端开发');
son.skill(); // 父类的技能
son.getInfo(); // jacky 22 前端开发
复制代码
子类必须在constructor方法中调用super方法,不然新建实例时会报错。这是由于子类本身的this对象,必须先经过父类的构造函数完成塑造,获得与父类一样的实例属性和方法,而后再对其进行加工,加上子类本身的实例属性和方法。若是不调用super方法,子类就得不到this对象。