原型链 与 继承的理解

原型链 与 继承的理解

原型链是前端面试几乎必问的东西,原型链实现了js中的继承。在看完阮一峰老师的博客后,理解了原型链的作用。

new 的由来

关于new的由来大家可以借阅阮一峰老师的博客Javascript继承机制的设计思想

构造函数

在es5之前还没有类的时候,js都是 new 一个构造函数来生成一个构造函数的对象,这边的构造函数相当于java中的class,在es6之后,为了方便js也诞生了 class 关键字。
构造函数和普通的function最直观的区别是 构造函数的函数名是大写的,它张这个样子

function Dog(name){
    this.type = 'dog';
    this.name = name;
}

这边是定义了一个 Dog 的构造函数,那么我们可以使用 new 来创建一只狗

let dog = new Dog('哈士奇');
console.log(dog.name); // 哈士奇

至于构造函数中的this指向问题,这边就不说了,在我的上一篇博客详细的介绍了js中的this指向问题

下面我们了解一下 new 一个对象的中间发生了什么

new 的过程

节选自js高级程序设计(第三版),创建一个实例,一共分为四个步骤

(1)创建一个新对象;

(2)将构造函数的作用域赋给新对象(因此this就指向了这个新对象);

(3)执行构造函数中的代码(为这个新对象添加属性);

(4)返回新对象。

new 的缺点

前面提到了 new 运算符可以实例化一个对象,但是 new 也有一个缺点,就是new出来的两个对象之间没有任何的联系,做不到数据共享,这个缺点有违 new 创建的初衷。下面看一段示例

function Dog(name){
    this.name = name;
    this.type = 'dog';
}

let dogA = new Dog('哈士奇');
let dogB = new Dog('大金毛');

dogA.type = '大型犬';
console.log(dogB.type); // 'dog'

通过 Dog 构造函数实例化出了两个对象,修改其中一个对象的属性,并不会改变另一个对象的属性,这并不是 new 设计的初衷, dogA 和 dogB 两个对象之间无法做到数据共享,就好像没有任何关系一样。

prototype 的引入

为了解决上面所述的问题,new 的创始人引入了 prototype 属性,

这个属性包含一个对象(以下简称"prototype对象"),所有实例对象需要共享的属性和方法,都放在这个对象里面;那些不需要共享的属性和方法,就放在构造函数里面。

实例对象一旦创建,将自动引用prototype对象的属性和方法。也就是说,实例对象的属性和方法,分成两种,一种是本地的,另一种是引用的。

function Dog(name){
    this.name = name;
}
Dog.prototype.type = 'dog';

let dogA = new Dog('哈士奇');
let dogB = new Dog('大金毛');

console.log(dogA.type); // dog
Dog.prototype.type = '大型犬';
console.log(dogA.type, dogB.type); //大型犬 大型犬

type属性是 Dog实例化出的对象所共享的属性,只要修改了 prototype中的type属性,每一个对象的 type 值都会被改变。

构造函数实现继承

构造函数实现继承有很多中实现方式,首先看第一种

call apply 绑定

这种是直接将父构造函数绑定在子构造函数中

function Animal(){
    this.type = 'dog';
}

function Dog(name){
    Animal.call(this);
    this.name = name;
}

let dogA = new Dog('哈士奇');
console.log(dogA.type); // dog

使用prototype进行继承

由于 prototype 属性可以存在对象共享的属性,那么我们可以按照封装的思想,将公共的部分抽离出来,变成一个公共的构造函数,也就是这些对象的父类也是基类,我觉得叫基类更加的合适,因为它存储着每一个对象的公共部分。实现方式只需要将子构造函数的 prototype 属性赋值为父构造函数

function Animal(){
    this.type = 'dog';
}

function Dog(name){
    this.name = name;
}

Dog.prototype = new Animal();
Dog.prototype.constructor = Dog;

let dogA = new Dog('哈士奇');
console.log(dogA.type); // dog
  • 第 9 行:将Dog 的原型属性赋值为Animal的实例
  • 第 10 行:修改 prototype 属性的costructor对象为Dog,这是因为每一个 prototype 对象都包含了它的构造函数对象,在上一行将 Dog 的原型直接赋值为 Animal的构造函数,所以此时Dog的原型对象中的 constructor其实是指向Animal的构造函数的,所以我们这边要修改回来,不然引起原型链的絮乱。

直接继承prototype

第三种方法是对第二种方法的改进。由于Animal对象中,不变的属性都可以直接写入Animal.prototype。所以,我们也可以让Cat()跳过 Animal(),直接继承Animal.prototype。

function Animal(){ };
Animal.prototype.type = 'dog';

function Dog(name){
    this.name = name;
}

Dog.prototype = Animal.prototype;
Dog.prototype.constructor = Dog;

let dogA = new Dog('哈士奇');
console.log(dogA.type); // dog

这种方法相比于第二种方法,优点是效率高,因为他跳过了执行创建一个 Animal 实例,省内存。但是也有缺点,缺点是直接将 Animal 的prototype 属性赋值给了 Dog 的 prototype 这是一个浅拷贝,因为prototype 是一个 object,所以对于任何Dog 的 prototype的修改都会响应在 Animal.prototype;

console.log(Animal.prototype.constructor === Dog); // true

我们在第 9 行修改了Dog.prototype中的 constructor属性,这边打印发现 Animal 的 prototype中的 constructor 也指向了 Dog

利用空对象作为中介

为了解决上面一种方式引起的问题,可以采用空对象作为媒介。

function Animal(){ };
Animal.prototype.type = 'dog';

function Dog(name){
    this.name = name;
}

var Temp = function(){ };
Temp.prototype = Animal.prototype;
Dog.prototype = new Temp();
Dog.prototype.constructor = Dog;

let dogA = new Dog('哈士奇');
console.log(dogA.type); // dog
console.log(Animal.prototype.constructor === Dog); // false

利用空对象作为媒介,空对象几乎不占内存,也不会影响 Animal 的 prototype

拷贝继承

顾名思义,就是将父对象的所有属性和方法,拷贝进子对象。

function Animal(){ };
Animal.prototype.type = 'dog';

function Dog(name){
    this.name = name;
}

function extendOfCopy(Child, Parent){
    let c = Child.prototype;
    let p = Parent.prototype;
    for(var i in p){
        c[i] = p[i];
    }
}

extendOfCopy(Dog, Animal);
let dogA = new Dog('哈士奇');
console.log(dogA.type); // dog
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值