JavaScript 中的原型与原型链
- 原型
- 1 函数中 prototype 指向原型对象
- 2 对象中 __proto__ 指向原型对象
- 3 原型对象中 constructor 指向构造函数
- 4 __proto__ 与 [[Prototype]] 的关系
- 5 所有非空类型数据,都具有原型对象
- 6 new运算符做了哪些事情
- 原型链
- 1 举个栗子
- 1.1 直接创建一个对象
- 1.2 数字、字符串、数组等类型数据, 下面以数字为例, 其他类型大同小异
- 1.3 一个复杂的例子
- 2 原型链的作用
原型
1 函数中 prototype 指向原型对象
当我们创建一个函数时,函数都会有一个默认属性
prototype
。该属性指向一个对象, 该对象就被称之为
原型对象
function fun(){
}
fun.prototype // 原型对象
2 对象中 proto 指向原型对象
当函数作为 普通函数 进行调用时,该属性不会有任何作用
当函数被作为 构造函数 进行调用 (使用 new 运算符调用) 时,构建出来的 实例对象 会有一个属性
__proto__
指向 原型对象
function fun(name){
this.name = name
}
fun.prototype // 原型对象
// 函数被作为构造函数进行调用
const obj = new fun('swim')
// 实例对象.__proto__ 指向 构造函数.prototype
obj.__proto__ === fun.prototype // true
3 原型对象中 constructor 指向构造函数
原型对象
默认会有一个特殊的属性constructor
, 该属性又指向了函数本身
function fun(name){
this.name = name
}
fun.prototype // 原型对象
// 原型对象中 constructor 指向构造函数
fun.prototype.constructor === fun // true
4 proto 与 [[Prototype]] 的关系
如果将 实例对象
打印出来, 会发现对象中并不具有 __proto__
属性,恰恰相反有个特殊的 [[Prototype]]
属性。
__proto__
并不是ECMAScript
语法规范的标准, 它只是大部分浏览器厂商实现或说是支持的一个属性, 通过该属性方便我们访问、修改原型对象
遵循
ECMAScript
标准,[[Prototype]]
才是正统,[[Prototype]]
无法被直接修改、引用
从
ECMAScript 6
开始, 可通过Object.getPrototypeOf()
和Object.setPrototypeOf()
来访问、修改原型对象
简单理解:
__proto__
和[[Prototype]]
是同一个东西,__proto__
是非标准的,[[Prototype]]
才是标准的, 但是它们都是指向原型对象
那么问题来了, 我们访问的
__proto__
在哪里呢? 实际上它是被添加在Object.prototype
上, 然后通过原型链
(后面会详细展开说明) 我们就能够访问到该属性
5 所有非空类型数据,都具有原型对象
任何非空数据,本质上都是通过对应的构造函数构建出来的,所以他们都具有 __proto__
属性,指向构造函数的原型对象。
如果需要判断某个值的原型对象,只需要确认该值是通过哪个构造函数构建的即可,只要确认了构造函数,那么该值的 __proto__
必然指向该构造函数的原型对象 prototype
。
// 数字
const num = 1
// 数字是通过 Number 构建的, 那么其原型对象等于 Number.prototype
num.__proto__ === Number.prototype // true
// 字符串
const str = 'str'
// 字符串是通过 String 构建的, 那么其原型对象等于 String.prototype
str.__proto__ === String.prototype // true
// 布尔类型
const bool = false
// 布尔值是通过 Boolean 构建的, 那么其原型对象等于 Boolean.prototype
bool.__proto__ === Boolean.prototype // true
// Symbol
const sym = Symbol('symbol')
// sym 是通过 Symbol 构建的, 那么其原型对象等于 Symbol.prototype
sym.__proto__ === Symbol.prototype // true
// BigInt
const big = BigInt(1)
// big 是通过 BigInt 构建的, 那么其原型对象等于 BigInt.prototype
big.__proto__ === BigInt.prototype // true
// 对象
const obj = { age: 18 }
// 对象是通过 Object 构建的, 那么其原型对象等于 Object.prototype
obj.__proto__ === Object.prototype // true
// 函数
const fun = () => {}
// 函数是通过 Function 构建的, 那么其原型对象等于 Function.prototype
fun.__proto__ === Function.prototype // true
// 数组
const arr = [1, 2, 3]
// 数组是通过 Array 构建的, 那么其原型对象等于 Array.prototype
arr.__proto__ === Array.prototype // true
6 new运算符做了哪些事情
- 创建一个新的空对象
A
- 往空对象挂载
构造函数 Com
的原型对象
: 对象A
创建__proto__
属性, 并将构造函数
的prototype
属性赋值给__proto__
- 执行
构造函数 Com
: 改变构造函数
this
指向, 指向空对象A
, 并执行构造函数
, 往空对象注入属性 - 判断
构造函数
是否返回一个对象?
- 是: 如果
构造函数
也返回了一个对象B
, 则最终new
出来的对象则为返回的对象B
- 否: 最终
new
出来的对象为最初创建的对象A
因此当我们执行
var o = new Foo();
实际上执行的是:
// 1. 创建一个新的空对象 A
let A = {};
// 2. 往空对象挂载, 挂载构造函数 Com 的原型对象: obj.__proto__ === Com.prototype;
Object.setPrototypeOf(A, Com.prototype);
// 3. 执行构造函数: 改变构造函数 this 指向, 指向对象 A, 往 A 注入属性
let B = Com.apply(A, args);
// 4. 判断构造函数是否返回对象: 是则取返回值、否则取最初创建的对象 A
const newObj = B instanceof Object ? res : A;
原型链
原型链是一种对象之间的链接机制,它用于实现对象之间的继承和属性访问。
每个JavaScript对象都有一个原型(prototype),原型是一个对象或null。当你访问一个对象的属性时,如果该对象自身没有该属性,JavaScript 引擎会沿着原型链向上查找,直到找到具有该属性的对象或到达原型链的末尾(即null)。这样,对象可以继承其原型的属性和方法。
根据上文,所有非空数据,都可以通过 __proto__
指向原型对象,如果原型对象非空,那么必然会有 __proto__
指向自己的原型对象,如此一层一层往上追溯,依此类推,就形成了一整条链路,一直到某个原型对象为null,才能达到最后一个链路的最后环节,而原型对象之间这种链路关系被称之为原型链
(prototype chain)
1 举个栗子
1.1 直接创建一个对象
const obj = { age: 18 };
从对象 obj 视角来看:
1. obj 本质上是通过 Object 构建出来的,那么 obj.__proto__ === Object.prototype
2. Object.prototype 的原型对象为 null,因此原型链到此结束
1.2 数字、字符串、数组等类型数据, 下面以数字为例, 其他类型大同小异
let num = 1;
1. num 本质上是通过 Number 构建出来的,那么 num.__proto__ === Number.prototype
2. Number.prototype 本质上是一个对象,是通过 Object 构建出来的,
那么 Number.prototype.__proto__ === Object.prototype
3. Object.prototype 的原型对象为 null,因此原型链到此结束
num.__proto__ === Number.prototype // true
Number.prototype.__proto__ === Object.prototype // true
Object.prototype.__proto__ // null 原型链结束
1.3 一个复杂的例子
function Person(age) {
this.age = age
}
var person = new Person(100)
从对象 person 视角来看:
1. person 是通过 Person 构建出来的, 那么 person.__proto__ 等于 Person.prototype
2. Person.prototype 是个对象, 是通过 Object 构建出来了, 那么Person.prototype.__proto__ 等于 Object.prototype
3. Object.prototype 的 原型对象 为 null, 原型链 到此结束
2 原型链的作用
- 查找属性: 当我们试图访问
对象属性
时, 它会先在当前对象
上进行搜寻, 搜寻没有结果时会继续搜寻该对象的原型对象
, 以及该对象的原型对象
的原型对象
, 依次层层向上搜索, 直到找到一个名字匹配的属性或到达原型链的末尾
function Person(age) {
this.age = age
}
Person.prototype.name = 'klx'
Person.prototype.age = 18
const person = new Person(28)
person // 当前对象: { age: 28 }
person.name // klx, 取自原型对象 Person.prototype
person.age // 28, 取自当前对象
person.toString() // [object Object], 取自原型对象 Object.prototype
person.address // undefined, 沿着原型链找不到 address
-
继承属性和方法:原型链允许对象继承其原型的属性和方法。当你访问一个对象的属性时,如果对象本身没有该属性,它会通过原型链访问原型对象的属性。这样可以实现属性的共享和代码的重用。
-
创建对象关联:通过原型链,你可以将一个对象与另一个对象关联起来,形成一个对象链。这样,一个对象可以通过原型链访问到另一个对象的属性和方法。
-
实现对象的多态性:在原型链中,可以通过在原型对象上定义相同名称的属性或方法,从而实现对象的多态性。当对象在原型链中找到具有相同名称的属性或方法时,会使用最近的那个
JavaScript中的原型链是基于原型继承的概念,它允许对象通过原型链接到其他对象,并继承其属性和方法。这种机制在JavaScript中是一种重要的特性,使得对象之间可以实现继承和共享,提供了灵活性和代码复用的机制。