「JavaScript深入」彻底搞懂JS原型与原型链


在这里插入图片描述

在这里插入图片描述


一、原因

JavaScript中除了基础类型外的数据类型,都是对象(引用类型)。但是由于其没有类(class)的概念,如何将所有对象联系起来就成了一个问题,于是就有了原型和原型链的概念。

原型链是JavaScript中实现对象属性继承的一种机制。

原型:构造函数会有一个原型对象 prototype,保存了该构造函数,以及定义的方法;而由构造函数生成的实例,则有一个私有属性 [[prototype]],也同样指向该构造函数的 prototype(在很多环境下都可以使用 __proto__ 访问,但现在已经提供了接口,我们可以通过 Object.getPrototypeOf(instance) 访问。
原型链:前面我们提到的构造函数的 prototype,也有私有属性[[prototype]] 指向另一个 prototype,最终指向 Object.prototype,该原型的__proto__ 指向 null

class Teacher {
	constructor(name){
		this.name = name;
	}
	sayHi(){
		console.log(this.name + ": hi")
	}
}

const teacherA = new Teacher("A");

👆 如上代码片段解析
在这里插入图片描述


二、使用class实现继承

因为JS中没有类(class)这个概念,所以JS的设计者使用了构造函数来实现继承机制

ES6中的 class 可以看作只是一个语法糖,它的绝大部分的功能,ES5都可以做到,新的 class 写法只是让原型的写法更加的清晰、更像面向对象编程的语法而已。下文也会进一步的说明。 —— 摘自阮一峰的ES6入门

普通的类

class Student {
  constructor(name, score) {
    this.name = name;
    this.score = score;
  }
  introduce() {
    console.log(`I am ${this.name}, I got ${this.score} in the exam.`);
  }
}
const student = new Student("小明", 59);
console.log("student", student);
student.introduce();

实现继承

class Person {
  constructor(name) {
    this.name = name;
  }
}

class Student extends Person {
  constructor(name, score) {
    super(name);
    this.score = score;
  }
}

三、原型

🌰 依然是上面的例子,当我们在控制台打印 student 时,我们发现其中并没有 introduce 这个方法

在这里插入图片描述

展开 [[Prototype]](或__proto__)后,我们可以看到 introduce 方法,而这个[[Prototype]] 就是 student 这个对象的隐式原型

在这里插入图片描述
Student 类上会有一个属性 prototype,通过验证可知其与student.__proto__ 指向同一地址空间,而 Student.prototype 被称为显式原型

在这里插入图片描述

当我们在一个对象上尝试查找一个属性或方法时,如果找不到对应的属性或方法,就会往它的隐式原型上去找
在这里插入图片描述


四、原型链

🌰 依然是上面的例子

class Person {
  constructor(name) {
    this.name = name;
  }
  sayHi() {
    console.log(`Hi, I am ${this.name}`);
  }
}

class Student extends Person{
  constructor(name, score) {
    super(name);
    this.score = score;
  }
  introduce() {
    console.log(`I am ${this.name}, I got ${this.score} in the exam.`);
  }
}

👇 打印 student 有如下结果

在这里插入图片描述

👇 完整原型链如下

在这里插入图片描述


小结

原型

在JavaScript中,每当定义一个对象(函数也是对象)时候,对象中都会包含一些预定义的属性。其中每个函数对象都有一个 prototype 属性,这个属性指向函数的原型对象,使用原型对象的好处是所有对象实例共享它所包含的属性和方法

原型链

每个实例对象( object )都有一个私有属性(称之为 proto )指向它的构造函数的原型对象(prototype )。该原型对象也有一个自己的原型对象( proto ) ,层层向上直到一个对象的原型对象为 null。根据定义,null 没有原型,并作为这个原型链中的最后一个环节

prototype和proto

  • prototype 是构造函数的属性
  • __proto__ 是每个实例都有的属性,可以访问 [[prototype]] 属性,当然 [[prototype]] 也有 __proto__ 属性
  • 实例的 __proto__ 与其构造函数的 prototype 指向的是同一个对象

在这里插入图片描述

__proto__ 并不是语言本身的特性,这是各大厂商具体实现时添加的私有属性,虽然目前很多现代浏览器的 JS 引擎中都提供了这个私有属性,但依旧不建议在生产中使用该属性,避免对环境产生依赖。生产环境中,我们可以使用 Object.getPrototypeOf 方法来获取实例对象的原型,然后再来为原型添加方法/属性。
——摘自阮一峰的ES6入门


引申

为什么JS不像C++/Java那样用类、指针这种,而是用原型和原型链?

🌰 看代码说输出:

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

const person = new Person("Tom");

console.log(Person.prototype); // {}
console.log(Person.__proto__); // ƒ () { [native code] }
console.log(person.prototype); // undefined
console.log(person.__proto__); // {}

ObjectObject.prototype

复习时突然有点混淆上述两者了,以为 Object 是原型链终点,其实是 Object 的原型,即 Object.prototype

Object 本身是一个构造函数,所以Object.__proto__ 其实指向的是Function.prototype,而Function.prototype.__proto__ 指向了 Object.prototype

在这里插入图片描述

  • 构造函数既有 prototype,指向自己的原型;又有__proto__,指向其继承的构造函数。
  • 构造函数继承原型链的顶端Function,其__proto___指向自己的prototype,如上图,使得 ObjectFunction 在原型链顶端形成了一个类似“环形”的链,第一次遇到时可能难以想明白。

这也解释了我的疑惑,为什么判断类型的方法不能用 Object.toString,而是Object.prototype.toString,二者同名但不是一个方法。

前者沿原型链找到 Function.prototype,调用其 toString 方法,而后者则是Object 原型上定义的方法。

console.log(Object.toString === Object.prototype.toString); // false
console.log(Object.toString === Function.prototype.toString); // true
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值