JS 原型与原型链:从 “套皮” 到面试必问

0 阅读4分钟

“为什么构造函数 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

最后,为自检对原型以及原型链的熟悉程度,附上一张图片资源(也是扒的,出处已经找不到了)

原型.webp