js 原型和原型链怎么理解
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 基本都能自己推。
转载请注明出处,版权归原作者所有。