⭐ 推荐:社区规则条款 V1.0

js 原型和原型链怎么理解

liulian
liulian 初级会员萌新站长
发布于 2026-10-17 02:19 ·1 浏览 ·0 回复
内容摘要

JS 中原型是实例共享属性的对象,prototype 是函数属性,__proto__ 是对象的内部原型指针,二者构成从实例到 Object.prototype 再到 null 的单向原型链;属性访问先查自有属性再沿链查找,new 会创建对象

原型就是「实例共享属性的那个对象」,原型链就是「读属性时沿着 __proto__ 一层层往上找的那条路径」。把 p.__proto__ === Person.prototype、Person.prototype.constructor === Person、Person.prototype.__proto__ === Object.prototype、Object.prototype.__proto__ === null 这几个等式背下来,绝大多数原型相关的面试题都能当场推导出来。

原型和原型链到底怎么定义?

结论:prototype 是函数才有的属性,它指向的那个对象就是该函数 new 出来的实例的原型;__proto__(规范里叫 [[Prototype]])是每个对象都有的内部指针,指向自己的原型。

function Person(name) { this.name = name }
const p = new Person('Tom')

p.__proto__ === Person.prototype                 // true
Person.prototype.constructor === Person          // true
Person.prototype.__proto__ === Object.prototype  // true
Object.prototype.__proto__ === null              // true,链到头了

所以原型链是一条单向链表:p → Person.prototype → Object.prototype → null。还要注意一个常见误读:Person.prototype 不是「Person 的原型」。Person 是函数也是对象,它自己的原型是 Person.__proto__,等于 Function.prototype。

访问一个属性时,JS 引擎是怎么找的?

结论:查找顺序永远是「先自有属性,再沿原型链逐级向上,走到 null 都没有就返回 undefined」。

具体三步:第一步检查实例自己有没有这个属性(Object.hasOwn / hasOwnProperty 那一层);第二步没有就把 [[Prototype]] 当作新起点,重复第一步;第三步走到 Object.prototype.__proto__ === null 就停。

p.name        // 'Tom',p 的自有属性
p.toString()  // 来自 Object.prototype,往上走了两层
p.abc         // undefined,整条链都没有

这正是原型省内存的原因:写 Person.prototype.say = fn,1000 个实例只共享一份函数;而写在构造函数里的 this.x 每个实例各存一份。写属性时有个坑:p.say = fn 不会改原型,它会在 p 上新建一个同名自有属性,把原型上的遮蔽掉。

new 的时候到底发生了什么?

结论:new 干四件事——建空对象、把它的原型指向构造函数的 prototype、执行构造函数并绑定 this、返回这个对象(如果构造函数显式返回一个对象,则返回那个对象)。

function myNew(Ctor, ...args) {
  const obj = Object.create(Ctor.prototype)   // 1 + 2
  const ret = Ctor.apply(obj, args)           // 3
  return ret !== null && typeof ret === 'object' ? ret : obj // 4
}

第四步最容易被忽略:构造函数 return 一个字符串或数字会被无视,仍然返回新对象。

instanceof 和继承是同一件事吗?

结论:是同一套机制。a instanceof B 的判断规则就是「沿 a 的原型链往上找,看有没有哪个节点 === B.prototype」。

function myInstanceof(a, B) {
  let proto = Object.getPrototypeOf(a)
  while (proto) {
    if (proto === B.prototype) return true
    proto = Object.getPrototypeOf(proto)
  }
  return false
}

ES6 的 class Child extends Parent 是语法糖,底层就是把 Child.prototype.__proto__ 设成 Parent.prototype,所以 new Child() instanceof Parent 为 true。读取和修改原型请用 Object.getPrototypeOf / Object.setPrototypeOf / Object.create,别直接写 __proto__——它虽然被 ES6 收进规范,本质是访问器属性,每次读写都有额外开销。

有哪些必须记住的细节和性能提醒?

结论:三个点值得记:Object.create(null) 能造出没有原型的对象;箭头函数没有 prototype 属性、不能用 new;原型链不要拉太长。

Object.create(null) 出来的对象连 toString 都没有,拿来做纯字典(缓存 key、配置表)最干净,不会被原型上的属性污染。箭头函数没有 prototype,new (() => {}) 直接抛 TypeError。原型链每深一层就多一次查找,V8 靠内联缓存加速,频繁 setPrototypeOf 会让已优化的代码退化,所以尽量在对象创建时就定好原型,不要在运行时改。

一句话收束:prototype 属于函数,__proto__ 属于对象,前者是共享属性的载体,后者串成属性查找的路径,终点是 null;new、instanceof、class extends 全部建立在这两点上,把它们串起来,原型相关的题和线上 bug 基本都能自己推。

本文转载自 GJ站长论坛,原文地址:https://www.gj0.com/thread-2215.html
转载请注明出处,版权归原作者所有。
—/ 5暂无评分
还没有评分,来打第一分吧

全部回复 0

还没有回复,来抢沙发~