原型和原型链

js通过三种方法实现面向对象的设计

  • 构造函数
  • ES6类(语法糖)
  • Object.create()

举个例子:

构造函数以大写开头

const Person = function (firstName, birthYear) {
  console.log(this);//Person {}
}

调用构造函数需要用new关键字

const john = new Person("John", 1999)
console.log(person);//Person {}

这段时间发生了四步

1.这个对象被创建

2.这个构造函数被调用,此时出现this,并且this指向该对象

3.对象被链接到原型

4.函数自动返回这个Person对象

此时在构造函数里加上两行代码

this.firstName = firstName;
this.birthYear = birthYear

此时打印john就会发现是带有这两个参数的Person对象,此时我们就可以通过这个构造函数创建各种不同的实例化对象。

js并没有类,所以我们通过构造函数的方式来模拟类,用与实例化对象。

记住永远不要在构造函数里去声明一个方法,这样的话如果创建一千个对象,那么就会有一千个对象有这个方法的副本。

所以为了解决这个问题,我们会使用原型和原型继承。

原型继承和其他语言的继承是不一样的,OOP的继承是一个类继承另外一个类,子类获取到的方法实际上是对父类方法的复制。而js的原型继承是一个实例继承一个类的属性和方法。

而原型继承中,对象其实是通过行为(方法)链接到原型,委托原型来进行操纵

举个例子:Array.prototype其实是js中创建的所有数组的原型,所以说当你使用map方法的时候,实际上是委托到原型上调用的。

在 JavaScript 中,对象有一个特殊的隐藏属性 [[Prototype]](如规范中所命名的),它要么为 null,要么就是对另一个对象的引用。该对象被称为“原型”

只要是对象就必须有原型。

举个例子,我们先实例化一个对象,这个对象是通过Person构造器实例化出来的

const Person = function (firstName, birthYear) {
    this.firstName = firstName;
    this.birthYear = birthYear
}

const jonas = new Person("jonas", 1999)

上面已经提到过实例化对象时会进行第四步操作,而第三步对象被链接到原型就与接下来要举的例子有关

请先记住__proto__可以看作 是 [[Prototype]]getter/setter

然后再加上这行代码

Person.prototype.calcAge = function () {
    console.log(2037 - this.birthYear);
}

这行代码的意思是在Personprototype属性上添加一个方法用于计算年龄。注意Person.prototype不是Person的原型

那么Person.prototype是谁的原型呢,答案是jonas

首先让我们通过代码来分析

console.log(jonas.__proto__ === Person.prototype)//true

此时控制台打印为true,这说明可能看到这里会觉得疑惑,为什么jonas.__proto__此时不是jonas的属性而是jonas的原型了呢,请回想我刚刚要求记住的那句话:__proto__可以看作 是 [[Prototype]]getter/setter

所以在这个地方jonas.__proto__并不是原型的属性,而是原型本身。

此时可以看到打印jonas.__proto__结果如下,出现了刚刚我们自己添加的calcAge方法

在这里插入图片描述

还可以通过以下代码进行验证

console.log(Person.prototype.isPrototypeOf(jonas));//true

//isPrototypeOf()用于检查一个对象是否存在于另一个对象的原型链中。
console.log(Person.prototype.isPrototypeOf(new Person));//true 并返回Person对象
console.log(Person.prototype.isPrototypeOf(Person)) //false

第二行代码说明了一个结论,也就是说Person.prototypePerson的所有实例化对象的原型,第三行代码也说明了刚刚的结论Person.prototype不是Person的原型

说明了构造函数需要给new出的对象定义原型,而这个原型就储存在构造函数的prototype属性中。也就是此时的Person.prototype,因为只要是对象就必须有原型

回看上面提到的第三步对象被链接到原型,此时jonas__proto__属性就是在此时创建的,此时链接的原型就是Person.prototype

我们还可以在原型上设置属性

Person.prototype.species = 'human';
console.log(jonas.species, jack.species);//human human

但请注意,此时的species并不是jonas的属性,它是jonas原型的属性

以下代码可以验证

console.log(jonas.hasOwnProperty('firstName'));//true
console.log(jonas.hasOwnProperty('species'));//false
//在这行代码中,其实jonas本身是没有hasOwnProperty() 但是js会往上找它的原型有没有
//它的原型是Person.prototype,Person.prototype也没有,就会再往Object.prototype上找

那为什么jonas能够获取到species属性呢,那是因为原型链的作用。当js发现在jonas身上找不到species属性的时候,就会往上找它的原型,从而使用,如果它的原型上没有就再往上找,,直到指向null

所以原型链不是闭合的,它的最终指向是null

下面一张动图可以更清晰的看到,我们在控制台中打印出jonas__proto__属性,也就是 jonas的原型Person.prototype

在这里插入图片描述

也可以清楚的看到原型链的最终指向是null

值得注意的是

console.log(Person.prototype.constructor);//指向Person本身

并且this的指向也不会受原型影响,依旧是哪个对象调用它,它就指向哪个对象

可以做一些相关的题巩固一下: 使用原型

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值