引言
“原型”(Prototype)是JavaScript这门语言中一个核心且至关重要的概念,但它也常常是初学者乃至一些有一定经验的开发者感到困惑的“拦路虎”。很多人背下了“原型链”的面试题,却未必真正理解其设计精髓。
今天,我们将从最基础的四个概念入手,层层递进,用最清晰的逻辑带你彻底征服JavaScript原型。相信我,看完这篇文章,你会对它有焕然一新的认识。
一、 显示原型 (prototype):构造函数的“蓝图”
之前我们学过数组中的增删改但是你有没有想过这些方法是哪来的呢?
例如:
const arr=[]
arr.push(1)//这是往数组末尾中添加一个数字1,
console.log(arr.push)//我是不是可以打印出这个结果显然是一个函数没错吧
是一个名为push的函数体
想要知道为什么首先我们都知道在js中:万物皆对象, 函数天生拥有一个属性 prototype,它是一个对象
prototype 是函数(通常用作构造函数)才有的属性。那么你就会问了函数要这个原型究竟是干嘛用的呢?
我们举个例子比如说小米工厂造车是不是要有车长,车高这些属性。而且小米支持你自选颜色
function Car(color){
this.lang=4800
this.height=1400
this.weight=1.5
this.name='su-7'
this.color=color
}
const car1=new Car('blue')
console.log(car1);
要执行这段代码,这时候new是不是会创建一个对象并且返回
const this={}
return{}
假如小米工厂要造1万台车上面五行代码是不是要执行1万遍这样不就是会有点浪费时间和内存对吧,所以函数身上的原型属性拥有这样一个作用:将构造函数中的一些固定的属性和方法添加到原型上,在创建实例的时候,就不需要重复执行这些属性和方法了那我们是不是可以把代码这样写
Car.prototype.name='su7'
Car.prototype.lang=4800
Car.prototype.height=1400
Car.prototype.weight=1.5
function Car(color){
this.color=color
}
const Car1=new Car('pink')
console.log(Car1);
来看这时候的运行结果,唉这时候是不是发现没有那些固定属性了但是我们还是可以通过console.log(Car1.name)来找到的,你看不见是藏在了原型里面
这时候来看最开始的问题是不是就是非常简单了,就是官方已经先把这个方法写在了数组原型上,所以可以直接使用这个方法
要点总结:
- 函数天生拥有一个属性 prototype,它是一个对象
- 将构造函数中的一些固定的属性和方法添加到原型上,在创建实例的时候,就不需要重复执行这些属性和方法了
- 添加在原型的属性是可以直接被实例对象访问到的
- 实例对象无法修改 构造函数 原型上的属性值
二、 隐式原型 (proto):实例对象的“血脉”
与构造函数的 prototype 相对应, __proto__ 是每个对象(包括实例对象)都有的属性。
该属性值也是一个对象
来看下面代码
function Car(){
this.name='su7'
}
const car=new Car()
console.log(car.constructor)//继承到的从构造函数的原型上
car是一个实例对象,它是不是显示拥有一个name属性我打印上面的代码但是不执行最后一行代码结果如下
我们直接执行所有代码结果是一个名为car的函数体,所以这个constructor就是让每一个实例对象都可以知道自己是由谁创建出来的2.所以,v8在访问对象中的一个属性时,会先访问该对象中的显示属性,如果找不到,就回去对象的隐式原型中查找,而且我们可以发现实例对象的隐式原型完全等于构造函数的显示原型这个东西就是跟new有关,new干了什么呢
三、 new 运算符:连接一切的“纽带”
new 关键字在背后默默地为我们建立了 prototype 和 __proto__ 之间的桥梁。它主要做了以下四件事:
function Car(){//new function
//const obj={}
//Car.call(obj)//call方法将Car函数中的this=obj
this.name='su7'//3
//obj.__proto__=Car.prototype4
//return obj
}
const car=new Car()//{name:su7}
首先,创建一个空对象,让对象函数中的this 指向一个空对象person.call(obj),然后执行构造函数的代码(等同于往空对象中添加属性值),将这个空对象的隐式原型(proto)赋值成构造函数的显示原型(prototype)最后再返回该对象
关键一步是第2步:obj.__proto__ = Person.prototype;。正是这一步,将实例和构造函数原型连接了起来,使得实例能够访问原型上的属性和方法。
四、 原型链 (Prototype Chain):层层递进的“寻宝图”
v8在访问对象中的属性时,会先访问该对象中的显示属性,如果找不到,就去对象的隐式原型中查找,如果还找不到,就去__proto__.__proto__上找,层层往上,直到找到null为止。这种查找关系被称为 “原型链” 。
V8引擎在查找属性时的完整路径是:
- 对象自身 ->
2. obj.__proto__ (构造函数的 prototype) ->
3.obj.__proto__.__proto__ -> ... -> 最终指向 null。
因为 Person.prototype 本身也是一个对象,它也有 __proto__!、
让我们来看一张经典的图,并结合代码分析:
Grand.prototype.house = function() {
console.log('四合院');
}
function Grand() {
this.card = 10000
}
Parent.prototype = new Grand() // {card: 10000}.__proto__ = Grand.prototype.__proto__ = Object.prototype.__proto__ = null
function Parent() {
this.lastName = '张'
}
Child.prototype = new Parent() // {lastName: '张'}.__proto__ = Parent.prototype
function Child() {
this.age = 18
}
const c = new Child() // {age: 18}.__proto__ = Child.prototype
// console.log(c.card);
// c.house()
console.log(c.toString());
总结与思考
prototype:是函数的属性,是为其未来创建的实例准备的“共享资源库”。__proto__:是所有对象的属性,指向其“生父”的prototype,是构成原型链的链接点。new:是生产线,它创建新对象,并为其正确设置__proto__链接。- 原型链:是查找机制,通过
__proto__层层向上查找,直到null,从而实现属性和方法的继承。
理解原型链,是理解JavaScript面向对象编程、继承、以及类(Class)语法糖基础的关键。希望这篇解析能帮你拨开迷雾,真正掌握它!