参透 JavaScript —— 彻底理解原型与原型链

前言

本篇文章主要讲解 JavaScript 中的原型与原型链

函数、构造函数

构造函数其实就是一个普通函数,只是使用 new 操作符调用的函数,我们就称为构造函数

还有一点,构造函数约定规范是首字母大写,用于区分普通函数

构造函数本质上是实例对象的模板new 操作符会改变构造函数内部 this 指向,使创建的对象会拥有其定义的所有属性和方法

比如,一个人的基本信息:姓名、年龄、性别等属性:

js 复制代码
function Person(name, age, gender) {
  this.name = name;
  this.age = age;
  this.gender = gender;
}

const p1 = new Person('张三', 18, '男');
const p2 = new Person('李四', 20, '女');

console.log(p1); // {name: '张三', age: 18, gender: '男'}
console.log(p2); // {name: '李四', age: 20, gender: '女'}

原型对象与prototype

JavaScript 是基于原型实现的面向对象(OOP)编程,这个原型也很好理解

在 《Javascript 高级程序设计》第四版介绍原型时,第一句话就是:每个函数都会创建一个 prototype 属性,这个属性指向的就是原型

那原型其实也是一个对象,我们说每个 JavaScript 对象内部都会有一个 [[prototype]] 属性,它指向该对象的原型,所以原型对象也有原型

并且,在原型对象上定义的属性和方法是所有实例共享的

js 复制代码
function Person(name) {
  this.name = name;
}

Person.prototype.getName = function (){
  console.log(`Hello, I'm ${this.name}`);
}

const p1 = new Person('张三')
const p2 = new Person('李四')

p1.getName() // Hello, I'm 张三
p2.getName() // Hello, I'm 李四

// 在原型对象上定义的属性和方法是所有实例共享的
console.log(p1.getName === p2.getName) // true

原型的作用体现在什么地方呢?

《JavaScript 高级程序设计》第四版有一篇内容介绍了《原始值包装类型》

原始值(string、number、boolean)本身不是对象,没有属性和方法,当你用到某个原始值的方法或属性时,后台会创建相应原始包装类型对象

js 复制代码
const n = 123;
console.log(n.toFixed(2)); // 123.00

// 等价于:
console.log((new Number(n)).toFixed(2));

这里,我们就可以说 n 的原型是 Number.prototype,并且通过原型链机制访问包装类型原型上的方法,在这个例子里是 toFixed 方法

原型的作用表现在:

  • 让所有实例对象共享属性和方法
  • 实现继承(通过原型链查找属性和方法)

constructor

js 复制代码
function Person(name) {
  this.name = name;
}

Person.prototype.getName = function (){
  console.log(`Hello, I'm ${this.name}`);
}

console.log(Person.prototype);

以上面例子为例,打印了 Person.prototype 对象,这个原型对象上除了自定义的内容外(在本例子是 getName),还存在一个 constructor 属性,指回与之关联的函数

事实上每个原型对象默认都有一个 constructor 属性,指回关联的构造函数

在这个例子里,constructor 指向 Person

js 复制代码
//...
console.log(Person.prototype.constructor === Person) // true

构造函数与原型对象的关系是循环引用的:

实例与__proto__

实例是通过 new 操作符创建的对象,并且拥有构造函数定义的所有属性和方法

实例对象上有一个 [[Prototype]] 属性,上面也讲过,这个属性指向原型对象,也称为隐式原型

要访问这个属性,各大浏览器实现了一个 __proto__ 属性,比如

或者使用 Object 对象的 getPrototypeOf 静态方法,访问指定对象的原型对象

js 复制代码
function Person(name) {
  this.name = name;
}

const p1 = new Person('张三')

// 实例对象上的 __proto__ 属性和构造函数上的 prototype 都指向原型对象
console.log(p1.__proto__ === Person.prototype) // true

console.log(Object.getPrototypeOf(p1) === Person.prototype) // true

构造函数、原型对象与实例对象的关系

实例与构造函数之间没有直接的"父子"关系,实例与构造函数原型之间有直接的联系

原型链

理解原型链,其实是理解原型链的行为,原型链是一个不断上溯寻找的过程,也就是说:

当访问一个对象的属性时,先在对象本身查找,如果没有找到,就会沿着 [[Prototype]] 指针向上查找,直到找到该属性为止。如果最终到达最顶层(Object.prototype),依然没有找到该属性,则返回 undefined

再回顾之前讲的《原始值包装类型》的例子,就能明白 n 调用的 toFixed 方法就是沿着指针找到原型 Number.protytype 对象上定义的方法

比如一个基本的例子:

js 复制代码
function Person(name) {
  this.name = name;
}

Person.prototype.age = 18;

const p1 = new Person('张三')

// 在原型链上找到 age 属性,输出 18
console.log(p1.age) // 18

// 沿着原型链找到最顶层依然没有找到
console.log(p1.gender) // undefined

要注意一种情况,如果对象本身和原型对象上都存在某个属性,会优先使用本身的属性,这种情况有个专业术语,叫做属性遮蔽

属性是否存在对象本身,可以使用 Object.prototype.hasOwnProperty 方法判断,返回一个布尔值,参考 MDN - hasOwnProperty

js 复制代码
//...
// 实例对象上有 name 属性,返回 true
console.log(p1.hasOwnProperty("name")) // true

// 实例对象上没有 age 属性,返回 false
console.log(p1.hasOwnProperty("age")) // false

在之前的关系图基础加上原型链,可以是这样的:

总结

本篇文章讲了构造函数、原型对象、实例对象,无非就是在介绍三者的关系,

  • 构造函数通过 prototype 指向原型对象
  • 原型对象通过 constructor 指回构造函数
  • 实例对象通过 __proto__ 指向原型对象

原型链要理解,就是在对象本身上找不到属性时,会沿着指针向上寻找,直到找到属性,找到最顶层依然没有,就返回 undefined

参考资料

参透JavaScript系列

本文已收录至《参透 JavaScript 系列》,全文地址:我的 GitHub 博客 | 掘金专栏

交流讨论

对文章内容有任何疑问、建议,或发现有错误,欢迎交流和指正

相关推荐
切糕师学AI1 天前
前后端分离架构中,Node.js的底层实现原理与线程池饥饿问题解析
前端·vue.js·node.js
妄小闲1 天前
网页设计模板 HTML源码网站模板下载
前端·html
icebreaker1 天前
tailwindcss 究竟比 unocss 快多少?
前端·css·github
卢叁1 天前
Flutter之自定义TabIndicator
前端·flutter
每天吃饭的羊1 天前
state和ref
前端·javascript·react.js
GEO_YScsn1 天前
Vite:Next-Gen Frontend Tooling 的高效之道——从原理到实践的性能革命
前端·javascript·css·tensorflow
GISer_Jing1 天前
滴滴二面(准备二)
前端·javascript·vue·reactjs
ningmengjing_1 天前
webpack打包方式
前端·爬虫·webpack·node.js·逆向
摇滚侠1 天前
Vue3入门到实战,最新版vue3+TypeScript前端开发教程,笔记03
javascript·笔记·typescript
Yuner20001 天前
Webpack开发:从入门到精通
前端·webpack·node.js