前言
之前,我们已经通过学习对象与构造函数了解到v8 在对象中查找属性,会先查找对象自己的属性,找不到再查找原型上的属性,那么该句中的原型到底所指何物,那就是我们今天探讨的主题。在这篇文章中,我们将从原型的基础概念讲起,逐步深入到原型链的工作原理、 proto 与 prototype 的区别,以及如何利用原型实现继承等核心话题。希望通过这篇文章,能帮你彻底揭开 JavaScript 原型系统的神秘面纱。
prototype(显示原型)
protype是函数天生就拥有的一个属性, 我们可以将一些属性和方法挂载在原型上,在创建实例后,实例就可以调用这个属性和方法,我们可以将一些公用的属性和方法添加在原型上,减少构造函数在执行时的性能开销。听起来是不是晦涩难懂,举个代码例子让你深刻理解:
Array.prototype.abc = function(){
console.log('abc')
}
const arr = [] //new Array()
arr.unshift(1)
console.log(arr)
arr.abc()
由对象的知识我们认识到,v8在创建一个数组开始时,其实是new了一个对象数组,而该对象中存在一个显示存在的属性即unshift函数,插入1,接着我们自己在原型上自己定义了一个abc方法,从而做到为arr这一基本类型添加操作,输出abc。这就是设计显示原型的方便之处,便于扩展对象功能。
作用体现
再举一个用了显示原型便利实现扩展功能的例子,
function Car(color){
// var this = {} //1
this.color = color
this.name = 'su7'
this.long = 5000
this.height = 1000
this.color = color//2
// this.__proto__ = Car.prototype //3__proto__是实例对象的显示原型,prototype是构造函数的隐式原型,两个指向同一个对象
// return this //4
}
const car = new Car('red')
console.log(car)
可以预见,当我们要批量访问car函数时,我们的v8引擎会重复new car函数,使函数内的前几个属性一直被调用,从而失去时间性能。而当我们引入原型时:
Car.prototype.name = 'su7'
Car.prototype.long = 5000
Car.prototype.height = 1000
function Car(color){
// var this = {} //1
this.color = color
this.name = 'su7'
this.long = 5000
this.height = 1000
this.color = color//
}
const car = new Car('red')
car.name = 'benz'
console.log(car)
console.log(car.__proto__)
我们通过在原型上添加属性从而实现了复用,每次new一个car时,这些属性会被“隐藏”在prototype里,可以访问。 通俗的说
prototype 是构造函数的 :制造商的零件库(给所有实例共享用的)
proto(隐式原型)
1.每个对象都有一个隐式的原型__proto__属性,该属性也是一个对象
- v8 在访问对象中的一个属性时,会先访问对象中显示存在的属性,如果不存在,会去对象的隐式原型上查找
- 实例对象的隐式原型 === 构造函数的现实原型
- onstructor 构造器属性,记录该实例对象是由谁创建的,接着看例子 5
Person.prototype.age = function(){
console.log('18岁')
}
function Person(){
this.name = '张三'
}
const p = new Person()
// console.log(p);
p.age()
console.log(p.__proto__===Person.prototype)
可以看到11行打印true,那我们结合代码来拆解原理:
第一步:创建构造函数和原型对象,- p.proto 是实例的「隐形指针」,指向那个仓库 -第 11 行的 === 就是在验证这个指针是否真的指向了仓库 这就是 JavaScript 原型系统的核心原理!即
一表区分:
| 概念 | prototype | proto |
|---|---|---|
| 所属者 | 构造函数(如 Person ) | 实例对象(如 p ) |
| 别称 | 显示原型 | 隐式原型 |
| 作用 | 存放共享属性/方法的仓库 | 指向构造函数的 prototype |
原型链
说完prototype(显示原型)和__proto__(隐式原型),我们聊聊他们之间查找关系,v8在访问对象中的一个属性时,会先访问对象中的显示存在的属性,如果没有,就会去对象的隐式原型上查找,如果还没有,就顺着隐式原型一直往上找,直到找到null ,这个查找关系,就叫做原型链,通俗的讲,- 一层一层往上找 : 自己没有 → 爸爸 → 爷爷 → ... → null
- proto 是链条 :把所有原型对象串起来
- null 是终点 :找到 null 还没找到,就返回 undefined 接下来看一段示例
GrandFather.prototype.house=function(){
console.log('汤臣一品')
}
function GrandFather() {
this.card = '1234567890123456'
}
Father.prototype = new GrandFather()
function Father() { //{card: '1234567890123456'}
this.lastName = '张'
}
Child.prototype = new Father( ) //{lastName: '张'}
function Child() {
this.age = 18
}
//new Object()
const p = new Child() //p.__proto__ = Child.prototype.__proto__ = Object.prototype.__proto__ = null
p.house()
可以看到最后输出的是GrandFather.prototype.house里的函数属性,那么我们来梳理一下逻辑:
console.log(xiaoming.name) // 1. 先找自己 → '小明' ✅
console.log(xiaoming.car) // 2. 自己没有 → 找爸爸 → '特斯拉' ✅
console.log(xiaoming.money) // 3. 爸爸也没有 → 找爷爷 → 1000000 ✅
xiaoming.house() // 4. 继续找 → 爷爷的原型 → '🏠 汤臣一品' ✅
看看原型链结构
console.log(p.proto === Child.prototype) // true
console.log(Child.prototype.proto === Father.prototype) // true
console.log(Father.prototype.proto === GrandFather.prototype) // true
console.log(GrandFather.prototype.proto === Object.prototype) // true(终点前)
console.log(Object.prototype.proto === null) // true(终点!)
接下来看一张原型链图片,理解之后你就能彻底知道这种查找关系背后的逻辑:
过程参考:const f1 = new Foo()
-
f1.proto = Foo.prototype
-
Foo.prototype.proto = Object.prototype
-
Object.prototype.proto = null
-
Foo.proto = Function.prototype
-
Function.prototype.proto = Object.prototype
-
Function.proto = Function.prototype