首页/文章/八股文

原型与原型链

2026-08-17
691724 分钟
...

第 1 段(10min)回顾与预习:接住昨天的线

1.1 先接昨日(this 与 new)的线

昨天你学了四种 this 绑定,其中 new 绑定讲了 new 的四步:

new Foo() 四步
创建空对象
把空对象的原型指向 Foo.prototype就是今天的主角
this 指向空对象执行函数体this.name = name 写入自有属性
返回这个对象

今天的一切都从第 ② 步长出来foo.__proto__ === Foo.prototype 这个式子,昨天是"new 四步的副产品",今天要把它彻底讲透。

再回忆昨天的一句话:变量看定义处,this 看调用处。今天补上第三句:属性看原型链。三句连起来:

变量 → 看定义处(闭包);this → 看调用处(绑定);属性 → 看原型链(上溯查找)。

1.2 先凭直觉回答 4 个小问题(不用对,先写下答案)

// 问题 1:方法存在哪?每个实例一份还是共享一份?
function Dog(name) { this.name = name; }
Dog.prototype.speak = function () { return '汪汪'; };
const d1 = new Dog('旺财');
const d2 = new Dog('来福');
d1.speak === d2.speak;  // true 还是 false?为什么?

// 问题 2:自己身上没有的属性,从哪冒出来的?
const o = {};
o.toString;  // o 里没写 toString,为什么能访问到?

// 问题 3
function Foo() {}
const foo = new Foo();
foo.constructor === Foo;  // true 还是 false?

// 问题 4
const d = {};
d instanceof Object;      // true 还是 false?instanceof 到底在查什么?

学完第 2、3 段再回来对照。第 1 段只做一件事:把"对象会沿链向上问"这个画面装进脑子。

第 2 段(15min)知识块①:prototype 与原型链

2.1 为什么需要 prototype:为"共享"而生

知识点(细化)

  • 如果每个实例都自己带一份方法,1000 个实例就有 1000 份相同的函数——浪费内存。
  • 解法:把方法放进构造函数的 prototype(一个公共对象),所有实例共享同一份
  • 实例自己身上只放"独有"的数据(自有属性),方法是"借用"的。

例子:先猜后跑

function Dog(name) {
  this.name = name;              // 自有属性:每个实例各有一份
}
Dog.prototype.speak = function () {   // 共享方法:放仓库里
  return `${this.name} 汪汪`;
};

const d1 = new Dog('旺财');
const d2 = new Dog('来福');
console.log(d1.speak());          // 旺财 汪汪
console.log(d1.speak === d2.speak); // true —— 同一份函数!
console.log(d1.name === d2.name);   // false —— 自有属性各一份

类比(共享仓库 / 祖传工具箱):构造函数是"工厂",prototype 是工厂的公共仓库。每个出厂的产品(实例)不各自买一套工具,而是领一张"仓库钥匙"(__proto__)。要用工具(方法)时,先翻自己口袋(自有属性),没有就去仓库借。仓库里永远只放一套工具。

真实场景:电商商品列表 1 万件商品,每件商品实例都共享 getPrice/format 这些方法——放 prototype 上只存一份;若放进构造函数里 this.getPrice = ...,就是 1 万份。这就是 prototype 存在的最大意义。

2.2 属性查找规则:先自己,再沿链上溯

知识点(细化)

  • 访问 obj.xxx 时引擎做的事: 1. 先查 obj自有属性Object.hasOwn(obj, 'xxx')); 2. 没有 → 沿 obj.__proto__(即 [[Prototype]])找; 3. 还没有 → 再沿那层的 __proto__ 继续上溯; 4. 直到 Object.prototype 也没有 → 返回 undefined
  • 这条逐级上溯的路径就是原型链(prototype chain)
  • 链的终点Object.prototype.__proto__ === null,走到 null 就停下。

例子:完整走一遍链

function Dog(name) { this.name = name; }
Dog.prototype.speak = function () { return '汪汪'; };

const d = new Dog('旺财');
console.log(d.name);    // 旺财      ← 第 1 步:自有属性,命中
console.log(d.speak()); // 汪汪      ← 第 2 步:自己/狗名上没有 → 沿链到 Dog.prototype 命中
console.log(d.toString); // [Function] ← 第 3 步:Dog.prototype 没有 → 沿链到 Object.prototype 命中
console.log(d.nothing);  // undefined ← 第 4 步:Object.prototype 也没有 → undefined

// 验证链条
console.log(d.__proto__ === Dog.prototype);           // true
console.log(Dog.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__);              // null —— 终点

类比(家族问话 / 祖传手艺):孙子不会某门手艺,先问爸爸;爸爸不会,去问爷爷;爷爷再不会,到祖宗那里还是没有,那就"没有"。对象查属性同理——一层一层往上问,问到头就说没有(undefined)

真实场景:你每天写的 [1,2].map(...)'abc'.toUpperCase(){}.hasOwnProperty(),全都是沿原型链借来的——数组实例自己没有 map,是 Array.prototype.map;字符串对象自己没有 toUpperCase,是 String.prototype 上的。你能直接调用它们,靠的就是这条链。

2.3 关键分界:[[Prototype]] / __proto__ / getPrototypeOf

知识点(细化)

  • [[Prototype]]:规范内部属性名,真正存"原型是谁"的地方(不在代码里直接写)。
  • __proto__:浏览器提供的便捷读写器(本质是 Object.prototype 上的 getter/setter),不是规范推荐的正规访问方式
  • 正规方式:读用 Object.getPrototypeOf(obj),写用 Object.setPrototypeOf(obj, proto)
  • 三者的值指向同一个东西:d.__proto__ === Object.getPrototypeOf(d) === Dog.prototype

例子

console.log(Object.getPrototypeOf(d) === d.__proto__);       // true
console.log(Object.getPrototypeOf(d) === Dog.prototype);     // true

为什么规范不推荐 __proto__?因为它只是历史遗留的便捷写法,且直接改 __proto__ 可能引发原型污染(第 3 段讲)。面试说"我用 getPrototypeOf/setPrototypeOf"是加分项。

2.4 原型链上的"隐藏机制":new 第②步在做什么

知识点(细化):昨天 new 四步里的第②步"把空对象原型指向 Foo.prototype",代码上就等于:

function Foo() {}
const foo = new Foo();
// new 的内部等价于:创建一个对象,并把它的 [[Prototype]] 指向 Foo.prototype
const manual = {};
Object.setPrototypeOf(manual, Foo.prototype);  // 手动复刻第②步
console.log(manual instanceof Foo);            // true —— 效果一样

意义instanceof 也好、方法共享也好,全都建立在"new 会把实例和构造函数的 prototype 拴在一起"这件事上。

第 3 段(15min)知识块②:__proto__ / prototype / constructor 三兄弟

3.1 三个词,三个角色(先建立"三个角色"画面)

知识点(细化)

名称挂在谁身上是什么一句话

prototype构造函数身上一个普通对象,放共享方法工厂的"公共仓库" __proto__实例对象身上指向其构造函数 prototype 的绳子([[Prototype]])领到的"仓库钥匙" constructorprototype 对象身上默认指回构造函数仓库门上的"铭牌"

核心等式(背下来,三兄弟全靠它)

function Foo() {}
const foo = new Foo();

foo.__proto__ === Foo.prototype;          // true  实例 → 仓库
Foo.prototype.constructor === Foo;        // true  铭牌 → 工厂
foo.constructor === Foo;                  // true  实例沿链找到铭牌

foo.constructor 为什么成立?foo 自己没有 constructor → 沿链找到 Foo.prototype.constructor → 是 Foo这是"链式查找"的又一次实践。

类比(仓库三件套)prototype 是公共仓库(放工具/方法);__proto__ 是发给每个工人的仓库钥匙(指向仓库);constructor 是仓库门上的铭牌(“本仓库归 Foo 工厂所有”)。工人要找人算账,看一眼铭牌就知道自己是哪个工厂的人。

3.2 易错点①:重写 prototype 会"断链"

知识点(细化)

  • Foo.prototype.speak = ... 追加属性:不断链,老实例立即可见。
  • Foo.prototype = { ... } 整体替换:已创建的实例的 __proto__ 仍指向旧仓库,新方法在新仓库里 → 老实例看不见新方法。
  • 整体替换后,Foo.prototype.constructor 也丢了(新对象没有铭牌),要手动补回。

例子:先猜后跑

function Foo() {}
const foo = new Foo();           // 先把实例创建出来
Foo.prototype.say = function () { return 'hi'; };   // 追加 → 老实例能看到
console.log(foo.say());          // 'hi' ✅

Foo.prototype = { other() { return 'new' }; };      // 整体替换 → 断链!
console.log(foo.other);          // undefined —— foo 的钥匙还指着旧仓库
const foo2 = new Foo();          // 新实例才拿新钥匙
console.log(foo2.other());       // 'new' ✅
console.log(foo2.constructor === Foo);  // false —— 新仓库没铭牌,需要手动补

类比:工厂搬家换了新仓库,但老工人的钥匙还是旧的——他进不了新仓库。而且新仓库门口没挂铭牌,得重新挂。

真实场景:这是"猴子补丁 / 重写原型"类库踩坑点;而 class 语法天然规避了这个问题(方法就是方法,不会出现"整体替换"写法)——这也是为什么工程上推荐 class。

3.3 Object.create:自己指定原型造对象

知识点(细化)

  • Object.create(proto)proto 作为原型,创建一个新对象。
  • 等价于手动:const o = {}; Object.setPrototypeOf(o, proto),但 Object.create 更干净。
  • Object.create(null):创建完全没有原型的对象——不是 Object 的实例,没有 toString/hasOwnPropertyObject.getPrototypeOf 返回 null

例子

const base = { greet() { return 'hi'; } };
const child = Object.create(base);
console.log(child.greet());      // 'hi' —— 沿链借到 base 的方法
console.log(child.__proto__ === base); // true

// 纯字典:没有任何继承来的东西
const dict = Object.create(null);
console.log(Object.getPrototypeOf(dict));   // null
console.log(dict.toString);                 // undefined —— 连 toString 都没有
console.log(dict instanceof Object);        // false —— 不是 Object 的实例

类比Object.create(base) = “照着 base 的模板生一个孩子,孩子不会的问他爹 base”;Object.create(null) = “孤儿,没有爹,什么都问不到”。

真实场景(安全):存用户可控的 key(表单字段、URL 参数)时,若用普通 {},恶意 key 可能是 __proto__constructorprototype,赋值会污染原型链(原型污染攻击)。用 Object.create(null) 造"无原型纯字典",这些 key 就只是普通键。事件总线、白名单配置、key-value 映射表常用这个招。更现代的做法是直接 Map(键完全独立、不受原型链影响)。

3.4 易错点②:原型污染(安全)

知识点(细化)

  • 原理:所有对象都沿链访问 Object.prototype。如果攻击者往 Object.prototype 塞了一个属性,所有对象都能看到它
  • 攻击路径:obj.__proto__.isAdmin = true(直接改原型);Object.assign({}, JSON.parse('{"__proto__": {...}}'))(合并不可信数据时触发 __proto__ setter)。
  • 防御:Object.create(null) 纯字典;用 Map;对 key 做白名单校验(['__proto__','prototype','constructor'].includes(key) 就拒绝)。

例子

const polluted = {};
polluted.__proto__.isAdmin = true;   // 攻击:改 Object.prototype
console.log({}.isAdmin);             // true —— 所有对象都"中招"了!

类比Object.prototype 是所有对象的"公共水源"。有人往水源里下毒,所有人喝水都中毒。防御就是"别让不可信的人往公共水源里倒东西"。

第 4 段(10min)知识块③:ES6 class 的本质 + instanceof 原理

4.1 class 只是语法糖:底层仍是原型链

知识点(细化)

  • class 没有引入新的继承机制,底层就是 ES5 那套原型链。
  • class Dog extends Animal 做的事 ≈ Dog.prototype.__proto__ = Animal.prototype(把子类仓库挂到父类仓库下面)。
  • super() ≈ 在子构造函数里调用父构造函数(Animal.call(this, ...))。
  • super.method()Animal.prototype.method.call(this, ...)

例子:class 与 ES5 一一对照

// —— ES5 原型继承 ——
function Animal(name) { this.name = name; }
Animal.prototype.speak = function () { return `${this.name} speaks`; };

function Dog(name) {
  Animal.call(this, name);                 // ≈ super(name)
}
Dog.prototype = Object.create(Animal.prototype);  // ≈ extends(把链接上)
Dog.prototype.constructor = Dog;           // 补回铭牌
Dog.prototype.speak = function () {        // 覆盖方法
  return Animal.prototype.speak.call(this) + ' 汪汪';  // ≈ super.speak()
};

// —— 同样的东西用 class ——
class Animal2 { constructor(name) { this.name = name; } speak() { return `${this.name} speaks`; } }
class Dog2 extends Animal2 {
  constructor(name) { super(name); }               // super() ≈ Animal.call(this, name)
  speak() { return super.speak() + ' 汪汪'; }       // super.speak() ≈ Animal.prototype.speak.call(this)
}

验证两张图是同一张图

const d = new Dog2('旺财');
console.log(d.speak());                  // 旺财 speaks 汪汪
console.log(d instanceof Dog2);          // true
console.log(d instanceof Animal2);       // true —— Dog2.prototype.__proto__ === Animal2.prototype
console.log(Dog2.prototype.__proto__ === Animal2.prototype); // true

类比(礼盒包装)class 是精致的礼盒,里面的东西(原型链)没变,但盒子上写了"使用须知"(见下),比 ES5 那套更不容易用错。面试被问"class 和原型继承有什么关系",答"语法糖 + 背后的链",再加这张对照图,就满分了。

4.2 class 带来的"约束"(它比手写原型严谨在哪)

知识点(细化)

  • 方法不可枚举Object.keys(Dog2.prototype) 是空数组)——手写 Dog.prototype.speak = ... 是可枚举的,会被 for...in 遍历到。
  • class 必须用 new 调用(当普通函数调 Dog2() 会抛 TypeError)。
  • class 内部默认严格模式this 不会悄悄变成全局)。
  • class 方法不可作为构造函数new Dog2.prototype.speak() 报错)。
  • constructor 时,this 初始化为父类构造结果;派生类里super() 才能用 this

例子

class Animal { constructor(name) { this.name = name; } speak() {} }
console.log(Object.keys(Animal.prototype));  // [] —— 方法不可枚举
Animal();      // TypeError: Class constructor cannot be invoked without 'new'

类比(保质期条款):礼盒上印着的"必须冷藏、开封后 3 天内食用"——这些条款(不可枚举/必须 new/严格模式)解决了 ES5 时代"手写原型容易出错、方法被 for…in 枚举出来、忘记 new 导致 this 污染全局"的老问题。

4.3 instanceof:不是在问"是不是你生的",而是在查"血统"

知识点(细化)

  • a instanceof B 的逻辑:B.prototype 是否出现在 a 的原型链上(沿 a.__proto__ 逐级上溯查找)。
  • 所以:d instanceof Animal 为 true,不是因为 d 直接由 Animal 创建,而是因为 Animal.prototype 在 d 的链上(通过 Dog 继承来的)。
  • 边界:左操作数是原始值(1 instanceof Number)→ false(不装箱);右操作数不是函数 → 抛 TypeError
  • 进阶(了解):Symbol.hasInstance 可以自定义 instanceof 行为。

例子:先猜后跑

class Animal {}
class Dog extends Animal {}
const d = new Dog();

console.log(d instanceof Dog);      // true   Dog.prototype 在链上
console.log(d instanceof Animal);   // true   Animal.prototype 也在链上(隔代继承)
console.log(1 instanceof Number);   // false  原始值不参与
Dog instanceof Function;            // true   类本身是函数,Function.prototype 在它的链上

判定流程(背下来)

a instanceof B
a 是原始值? → false
B 不是函数? →  TypeError
沿 a.__proto__ 逐级上溯能找到 === B.prototype 的吗? → 找到 true null false

类比(血统/族谱)instanceof 不是在问"你是不是我亲爹生的",而是在翻你的族谱:你家族谱(原型链)上有没有 XX 这个人。d instanceof Animal 翻族谱时看到"狗的先祖是动物",所以是 true。

真实场景:框架底层大量用 instanceof/constructor类型识别——判断一个值是不是数组(arr instanceof Array)、判断组件实例、判断某个对象属于哪个类。工具库(如 lodash 的 isArray)内部就用 Object.prototype.toString 等原型链技术。

第 5 段(10min)费曼复述:讲给"零基础同学"听

5.1 复述提纲(出声讲一遍,卡壳处回去重看)

  1. 为什么要有原型? —— 方法放公共仓库(prototype),所有实例共享一份,省内存。
  2. 属性怎么找到? —— 先翻自己口袋(自有属性),没有就沿绳子(__proto__)一层层往上问,问到 Object.prototype 还没有就 undefined
  3. 三个词谁是谁? —— prototype 是构造函数身上的仓库;__proto__ 是实例身上的钥匙/绳子;constructor 是仓库门上的铭牌。等式:foo.__proto__ === Foo.prototypeFoo.prototype.constructor === Foo
  4. instanceof 在干嘛? —— 翻族谱:右操作数的 prototype 在不在左操作数的链上。
  5. class 是什么? —— 语法糖礼盒:extends = 把子类仓库挂到父类仓库下,super() = 调父构造器;盒子上还印了"必须 new、方法不可枚举"等条款。

5.2 自测清单(每条能写出例子才算过)

  • 能画出 new Foo()fooFoo.prototypeObject.prototype 的完整链
  • 能说清 prototype/__proto__/constructor 分别挂在谁身上、指向谁
  • 能解释"重写 prototype 为什么断链",以及如何补救
  • 能说出 Object.create(null) 的用途与防污染原理
  • 能把一段 ES5 继承逐行对应到 class 的 constructor/extends/super()
  • 能讲清 instanceof 的底层判断步骤

第 6 段(20min)练习①:ES5 继承 + class 对照(code\es5-inheritance.js)

目标:先自己写,卡住再看提示。写完用验证清单测。 📄 参考实现已放好:code\es5-inheritance.js(含 ES5 版、class 版、逐行对照注释、自测)。写完自己的版本再打开对照。

6.1 分步提示(不要一上来就看)

  • 第 1 步:写父类 Animal:构造函数里 this.name = name(自有属性),Animal.prototype.speak 返回 '${this.name} speaks'
  • 第 2 步:写子类 Dog: 构造函数里 Animal.call(this, name)(≈ super);
  • Dog.prototype = Object.create(Animal.prototype)(≈ extends,把链接上);
  • Dog.prototype.constructor = Dog(补铭牌);
  • 覆盖 Dog.prototype.speak,内部用 Animal.prototype.speak.call(this)(≈ super.speak())。

第 3 步:写等价的 class 版,跑出相同结果。 第 4 步:在注释里逐行标注"这行 ≈ class 的哪个语法"。

6.2 验证清单(每一条都应通过)

new Dog('旺财').speak();        // '旺财 speaks 汪汪'
const d = new Dog('来福');
d instanceof Dog;               // true
d instanceof Animal;            // true —— 证明链接对了
Dog.prototype.constructor === Dog;  // true —— 铭牌补回来了

6.3 常见报错排查

报错/现象原因修法
Animal is not defined子类构造里忘了 Animal.call(this, …)加上父类调用
d instanceof Animal 是 false忘了 Dog.prototype = Object.create(Animal.prototype),链没接上加上并确认写在 new 之前
d.constructor 变成了 Animal重写 prototype 后没补铭牌补 Dog.prototype.constructor = Dog
输出是 undefined speaksthis.name 没写进实例(父类构造没执行)检查子类构造是否 Animal.call(this, name)

第 7 段(20min)练习②③:画关系图 + myInstanceof

7.1 练习②:画 new Foo() 的关系图(code\prototype-chain.md)

先用纸笔或文本画,画完对照参考。要点:四个节点(foo、Foo.prototype、Object.prototype、null)+ 三条连线 + 标注 constructor 归属。

参考图(文本版)

                 ┌──────────────────────────────┐
Object.prototype
                 │  (toString / hasOwnProperty) │
                 └───────────────┬──────────────┘
                     __proto__ ▲ │ (再往上没有了)
                     (指向这里)│
                 ┌───────────────┴──────────────┐
Foo.prototype
constructor ─────► Foo       │  ← 铭牌指向构造函数
                 └───────────────┬──────────────┘
                     __proto__ ▲ │
                 ┌───────────────┴──────────────┐
foo实例对象)           │
name: 'xxx'自有属性)       │
                 └─────────────────────────────┘

关键等式
  foo.__proto__ === Foo.prototype
  Foo.prototype.constructor === Foo
  Foo.prototype.__proto__ === Object.prototype
  Object.prototype.__proto__ === null链的终点

自检:图里能不能回答"foo.toString 从哪来"?(答:自己没有 → Foo.prototype 没有 → Object.prototype 上借来的。)

7.2 练习③:myInstanceof(code\myInstanceof.js)

分步提示

  • 第 1 步:取 obj 的原型:let proto = Object.getPrototypeOf(obj)(比 obj.__proto__ 规范)。
  • 第 2 步while 循环,proto !== null 就继续;每次比较 proto === Ctor.prototype,命中返回 true。
  • 第 3 步:循环结束返回 false。
  • 第 4 步(边界)obj 是原始值 → 返回 false;Ctor 不是函数 → 抛 TypeError
  • 第 5 步:对照参考实现 code\myInstanceof.js,重点看边界处理。

验证清单

myInstanceof([], Array);        // true
myInstanceof([], Object);       // true(数组链上有 Object.prototype)
myInstanceof(Object.create(null), Object); // false(无原型)
myInstanceof(1, Number);        // false(原始值)
myInstanceof({}, 123);          // TypeError

第 8 段(10min)思考提高题:先独立想,再看提示

思考题①:给 Object.prototype 加属性,所有对象都能看到,对工程意味着什么?为什么规范不建议?

提示

  • 先跑一下 Object.prototype.polluted = 1; console.log({}.polluted, [].polluted, function(){}.polluted)——你会发现万物皆中招。这就是共享的代价
  • 三点坏处:① 命名冲突(某个库加的 contains 可能和你的冲突);② 被 for...in 遍历到(如果可枚举,所有对象的遍历都多出一个键);③ 安全(原型污染攻击面)。
  • 规范角度:早期 JS 确实有人这么干(老 polyfill),但现代标准提供 MapObject.hasOwnSymbol 等更干净的替代。给内置原型"打补丁"是 ES5 时代的妥协,现在是反模式。

思考题②:class 相比手写原型链多了哪些约束?解决了 ES5 的什么问题?

提示

  • 回到 4.2 的"保质期条款"列表:不可枚举、必须 new、内部严格模式、方法不可构造。
  • 每个约束对应 ES5 的一个真实痛点:方法被 for...in 枚举出来(Object.keys(Foo.prototype) 全是方法)→ 不可枚举;忘记 new 导致 this 污染全局(Foo() 把 name 写到 window)→ 必须 new + 严格模式;new Foo.prototype.method() 被当构造器乱用 → 不可构造。
  • 结论:class 是用"更多约束"换取"更少意外"——约束不是限制,是保护。

思考题③:Vue3 / React 几乎不用手写原型继承,但底层到处是原型链。为什么框架倾向"组合/闭包"而非"继承"?

提示

  • 原型链继承的缺点:层级越深越难维护(改父类波及所有子类)、无法复用"横向"逻辑(一个类只能 extends 一个父类)、继承关系是静态的(运行时不好换)。
  • 组合/闭包的优点:逻辑以函数为单位自由拼接(useCount()useFetch() 想装几个装几个)、依赖通过参数显式传入(好测试、好替换)、没有"家族图谱"的心智负担。
  • 思考一下 Vue3 的 setup() 里为什么"像写普通函数"就能拥有响应式——因为那是闭包 + 依赖跟踪,根本不依赖 this 和原型链。理解原型链,是为了读懂框架底层(比如 Vue 的响应式对象代理、组件继承),而不是为了鼓励写继承。

📌 今日核心速记卡(可截图/摘抄带走)

原型链 = 对象沿 __proto__ 逐级上溯找属性的路径先自有属性Object.prototypenull

prototype  = 构造函数身上的共享仓库放方法
__proto__  = 实例身上的绳子指向构造函数的 prototype([[Prototype]])
constructor = 仓库门牌Foo.prototype.constructor === Foo

核心等式foo.__proto__ === Foo.prototype
          Foo.prototype.__proto__ === Object.prototype
          Object.prototype.__proto__ === null

instanceof = 查血统B.prototype 在不在 a 的原型链上原始值false非函数RHSTypeError

class = 语法糖礼盒
  extendsDog.prototype = Object.create(Animal.prototype)
  super()    ≈ Animal.call(this, ...)
  super.x()  ≈ Animal.prototype.x.call(this)
  约束必须 new方法不可枚举内部严格模式

Object.create(null) = 无原型纯字典防原型污染
口诀方法放 prototype属性放构造函数给原型打补丁是旧时代的妥协

❌ 常见误区清单(学完自查,哪个说过/想过就划掉)

  • proto 和 prototype 是同一个东西” —— ❌ 一个是实例的绳子,一个是构造函数的仓库
  • “实例身上有 prototype 属性” —— ❌ prototype 挂在构造函数身上
  • “instanceof 判断对象是不是构造函数创建的” —— ❌ 判断的是 prototype 在不在链上
  • “class 引入了全新的继承机制” —— ❌ 底层仍是原型链,只是语法糖 + 约束
  • “Object.create(null) 也是 Object 的实例” —— ❌ 它没有原型,instanceof Object 是 false
  • “重写 prototype 后老实例也能看到新方法” —— ❌ 老实例的钥匙指向旧仓库,断链
  • “所有函数都有 prototype” —— ❌ 箭头函数、方法简写没有(不可构造)
  • “构造函数里 this.name = … 也是共享的” —— ❌ 那是每个实例的自有属性,各一份

题目

① 填空② 判断③ 填空
const foo = new Foo();
foo.__proto__ === ___
prototype 是实例对象的属性。”
对 / 错?
Object.prototype.__proto__
=== ___
④ 问结果⑤ 问结果⑥ 问结果
function F(){}
F.prototype.say = () => 1;
const a = new F();
F.prototype = { say: () => 2 };
a.say(); // ?
Object.create(null)
instanceof Object
// ?
const b = Object.create(null);
Object.getPrototypeOf(b)
// ?
⑦ 补缺⑧ 连线⑨ 实战判断
整体替换 prototype
必须补回哪个属性?
Animal.prototype.speak.call(this)
对应 class 里的哪个关键字?
new Dog('x') instanceof Animal
为 true 的前提是?(一行代码)

📖 参考答案(先做完再展开)

  1. Foo.prototype —— 实例 → 仓库的绳子(2.2/3.1)
  2. prototype 挂在构造函数身上;实例身上的是 __proto__(3.1)
  3. null —— 原型链终点(2.2)
  4. 1 —— 老实例 a 的钥匙还指着旧仓库,新方法 () => 2 在新仓库里,老实例看不见(3.2 断链)
  5. false —— 它没有原型,不是 Object 的实例(3.3)
  6. null —— 无原型对象,getPrototypeOf 返回 null(3.3)
  7. constructor —— 补 Foo.prototype.constructor = Foo(3.2 / 6.1)
  8. super.speak() —— 调父类同名方法(4.1)
  9. Dog.prototype = Object.create(Animal.prototype) —— 让 Animal.prototype 出现在链上(4.3 / 6.1)

如果您觉得这篇文章有帮助,请点个赞吧~

分享文章

相关文章

更多文章 →
八股文2026-08-27
定时器按顺序播放多个音频,切后台音频会乱
本身不会补触发,但 它会被系统级冻结 ——iOS Safari 后台完全停止计时,切回前台后 只补执行一次 (不是不补,而是"缺失的中间状态补不上")。间隔短的不会出大问题, 间隔长的会出现"音频流被压缩" ——切回来后本该播 5 分钟的间隙,实际只过了 3 分钟,结果整段对不上。 核心原则 :定时器只能用来"提醒一次", 真正决定"该不该播"的是绝对时间戳 。 setTimeout 在后台会发生什么(按平台) | 平台 | 后台行为...
面试
八股文2026-08-27
线上项目白屏的原因
白屏的本质是 渲染管线某一环断了 ——可能是资源、JS、接口、路由、样式、兼容性任一环节出问题。排查按"控制台 → 网络 → DOM → 环境"四步定位。 本质(前端类比) 把网页想成一栋楼: 白屏 ≠ 一定是同一种原因——这是面试想听的层次。 六大类原因(按出现频率) | 类别 | 典型表现 | 真实案例 | | : | : | : | | ① 资源加载失败 | DOM 是空的 | 入口 JS 404、CDN 挂了、CSS 阻塞 |...
面试
八股文2026-08-27
背景图就是 1MB 大图,怎么优化
1MB 大背景图优化分 三步 :压缩体积(10 30x)、按需加载(按设备/视口/网速)、渲染期优化(GPU 合成)。背景图跟 不同——它是 CSS,不走浏览器的原生懒加载机制,得手动处理。 背景图 vs 的关键区别(先讲清楚这个) 这是面试官想听的"针对性认知"——背景图不是普通图片,不能套通用方案。 三步优化(按优先级) 第 1 步:压缩体积(最重要,立竿见影) 1MB 的来源一般是这几种 ,对应解决方案: | 原始问题 | 体积来...
面试
八股文2026-08-27
项目里很多图片和视频,怎么优化
图片视频优化分 四层 :网络层(CDN/格式)、加载层(懒加载/预加载)、渲染层(解码/缓存)、业务层(按需/降级)。面试要把这四层都讲到位才算有体系。 四层优化模型 第 1 层:网络层(省钱、省时间) 核心目标:让资源体积小、让用户拿到资源快 | 手段 | 作用 | 关键点 | | : | : | : | | 图片格式 | WebP/AVIF 比 JPEG 小 25 50% | 兼容 fallback | | 视频格式 | H.265...
面试
八股文2026-08-20
Embedding 向量模型:从语义表示到相似度计算
前言 大模型「读懂」文字靠的是 token,但 token 之间只有离散的编号关系,模型并不知道「苹果」和「李子」在语义上很近。要让程序能「理解」两段文字的相似程度,必须先把文本映射成一个 高维向量 ,再用几何方法比较。这一步就是 Embedding。 本篇基于我本地 今天的真实代码,从语义表示讲到余弦相似度,并复盘几个踩过的真实坑。 一、为什么需要 Embedding 传统关键词检索是「字面匹配」: Embedding 做的是「语义匹...
面试
八股文2026-08-19
前端面试100题
前端面试 100 题 适用方向:中高级前端 / React / Vue / Next.js / Nuxt / TypeScript / 工程化 / 实时通信 / Electron / Node.js / AI 应用前端 使用方式:优先掌握“标准回答”,再练“面试官追问”,最后把“结合你的简历怎么答”组织成自己的项目故事。 说明 “结合你的简历怎么答”只使用你简历中已经出现的项目与技术事实。 “标准回答 / 追问”属于通用前端知识总结,用...
面试

评论

请登录后发表评论

去登录
加载评论中...

目录