“为什么构造函数 new 出来的实例,都能用同一个方法?”
这背后就是 “原型 + 原型链” 的复用逻辑。
一、原型是什么?
1.原型是什么:
函数天生拥有一个的属性 prototype,它是一个对象
以构造函数为例,原型像是仓库一样,将构造函数中的一些固定的属性和方法挂载到原型上
这样挂载在原型上的属性是可以直接被实例对象访问到的
类比:构造函数的 “公共储物间”,放所有实例共用的东西
以下代码为例
Person.prototype.sayName = function() {
console.log(this.name);
}
function Person() {
this.name = '张三'
}
const p = new Person()
console.log(p.name); // 张三
p.sayName() // 张三
console.log(p.constructor,Person.prototype.constructor); // Person()
可以看到,即使 p 同样能访问到 sayName 方法,就是因为 Person 的 sayName 方法通过原型链继承给了 p
除此之外呢
原型身上自带 constructor 属性,指向构造函数本身(Person.prototype.constructor === Person)
也可以通过这个属性,找到 p 的爸爸是谁(被谁创建)
2.原型的核心作用
将构造函数中的一些固定的属性和方法挂载到原型上,创建实例对象的时候,就不需要重复执行这些属性和方法了
以下代码为例
Car.prototype.name = 'su7-Ultra'
Car.prototype.lang = 4800
Car.prototype.height = 1400
Car.prototype.weight = 1.5
function Car(color) {
// const this = {} // 1
// this.name = 'su7-Ultra'
// this.lang = 4800
// this.height = 1400
// this.weight = 1.5
this.color = color
// return this // 3
}
const car1 = new Car('pink')
console.log(car1.lang); //4800
这样可以提高代码的复用率
二、隐式原型 [[proto]]
1.隐式原型是什么:
每一个对象都拥有一个 proto 属性,该属性值也是一个对象
v8 在访问对象中的一个属性时,会先访问该对象中的显示属性,如果找不到,就回去对象的隐式原型中查找
实例对象的隐式原型 === 构造函数的显示原型
以下代码为例
Car.prototype.run = function() {
console.log('running');
}
function Car(){ // new Function()
// const obj = {};
// Car.call(obj); // call 方法将 Car 函数中的 this = obj
this.name = 'su7';
// obj.__proto__ = Car.prototype;
// return obj;
}
const car = new Car(); // {name: 'su7'}.__proto__ == Car.prototype
car.run(); // running
注释部分的代码,其实就是 new 真正在干的,顺序也是代码的先后顺序
想必现在你已经知晓了何为 new 以及 原型 prototype 和 隐式原型 [[proto]]
现在来一段略微复杂的逻辑关系
// Array.prototype.abc = function() {}
// const arr = [] // new Array()
// const arr2 = []
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()); // [object Object]
无需赘述,一条一条顺着上找就行了
讲到现在,其实原型链已经会了
2.原型链的基础逻辑:
v8 在访问对象中的属性时,会先访问该对象的显示属性,如果找不到,就去对象的隐式原型上找。如果找不到,就去__proto__.__proto__上找,层层往上,直到找到null为止。这种查找关系被称为原型链。
回过头再来聊聊一些其他的
面试点
1.p1.hasOwnProperty ('say') 为什么是 false?
function Person() {}
Person.prototype.say = function() {};
const p1 = new Person();
//检查say是否是p1自身属性 → false
console.log(p1.hasOwnProperty('say')); // false
//给p1手动加一个自身的say属性
p1.say = function() { console.log('我是p1自己的say') };
console.log(p1.hasOwnProperty('say')); // true
//hasOwnProperty本身是原型链的方法(p1自身没有这个方法)
console.log(p1.hasOwnProperty('hasOwnProperty')); // false
hasOwnProperty() 是 Object.prototype 上的方法,作用是 仅检查属性是否是对象「自身拥有」的,不会去原型链查找;
p1.say 是通过原型链从 Person.prototype 拿到的「共享方法」,并非 p1 自身的属性,所以返回 false。
2.修改原型后,之前的实例会受影响吗?
修改原型对象的属性 / 方法 → 实例受影响;替换整个原型对象 → 旧实例不受影响,新实例受影响。
情况 1:修改原型对象(新增 / 修改原型上的属性)→ 旧实例受影响
function Person() {}
const p1 = new Person(); // 先创建实例
// 修改原型对象的属性
Person.prototype.say = function() { console.log('修改后的say') };
p1.say(); // 输出“修改后的say” → 旧实例能拿到新的原型方法
情况 2:替换整个原型对象 → 旧实例不受影响,新实例受影响
function Person() {}
const p1 = new Person(); // 旧实例
// 替换整个原型对象(指向新的对象)
Person.prototype = {
say: function() { console.log('新原型的say') }
};
const p2 = new Person(); // 新实例
p1.say(); // 报错:p1.say is not a function(旧实例__proto__还是原来的空原型)
p2.say(); // 输出“新原型的say”(新实例__proto__指向新原型)
3.Function.proto === Function.prototype 为什么成立?
JS 中函数是特殊的对象,Function 作为 “函数的构造函数”,存在一个特殊的原型链设计:
所有函数(包括 Function 本身)都是由 Function 构造的(Function = new Function( ));
因此 Function 的隐式原型 proto,必须指向自身的显式原型(prototype),形成一个 “自引用” 的特殊逻辑;
这是 JS 原型链的 “顶层特殊设计”,也是唯一一个构造函数的 proto 指向自身 prototype 的情况。
console.log(Function.__proto__ === Function.prototype); // true
// 延伸:所有函数的__proto__都指向Function.prototype
function fn() {}
console.log(fn.__proto__ === Function.prototype); // true
console.log(Object.__proto__ === Function.prototype); // true(Object也是函数)
// 原型链终点
console.log(Function.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // true
最后,为自检对原型以及原型链的熟悉程度,附上一张图片资源(也是扒的,出处已经找不到了)