JavaScript 面向对象入门到精通

JavaScript 面向对象编程:从入门到精通完全指南


目录

  1. 面向对象基础概念
  2. 对象的创建方式
  3. [this 关键字](#this 关键字 "#3-this-%E5%85%B3%E9%94%AE%E5%AD%97")
  4. 原型与原型链
  5. [构造函数与 new](#构造函数与 new "#5-%E6%9E%84%E9%80%A0%E5%87%BD%E6%95%B0%E4%B8%8E-new")
  6. [ES6 Class 语法](#ES6 Class 语法 "#6-es6-class-%E8%AF%AD%E6%B3%95")
  7. 继承
  8. 封装与私有成员
  9. 多态与抽象
  10. [Mixin 与组合](#Mixin 与组合 "#10-mixin-%E4%B8%8E%E7%BB%84%E5%90%88")
  11. 属性描述符与元编程
  12. [Symbol 与内置协议](#Symbol 与内置协议 "#12-symbol-%E4%B8%8E%E5%86%85%E7%BD%AE%E5%8D%8F%E8%AE%AE")
  13. [Proxy 与 Reflect](#Proxy 与 Reflect "#13-proxy-%E4%B8%8E-reflect")
  14. 设计模式实战
  15. [SOLID 原则在 JS 中的应用](#SOLID 原则在 JS 中的应用 "#15-solid-%E5%8E%9F%E5%88%99%E5%9C%A8-js-%E4%B8%AD%E7%9A%84%E5%BA%94%E7%94%A8")
  16. 性能与最佳实践
  17. 常见陷阱与面试题

1. 面向对象基础概念

面向对象编程(OOP)的四大支柱:

概念 含义 JS 实现手段
封装 隐藏内部细节,暴露接口 闭包、#私有字段、Symbol
继承 复用父类的属性和方法 原型链、extends
多态 同一接口,不同实现 方法重写、鸭子类型
抽象 提取共性,忽略细节 抽象基类模拟、接口约定

JS 的特殊之处 :JavaScript 是基于**原型(prototype)**的语言,而非基于类。ES6 的 class 只是原型机制的语法糖。理解这一点是精通 JS OOP 的关键。


2. 对象的创建方式

2.1 对象字面量

js 复制代码
const user = {
  name: 'Alice',
  age: 25,
  greet() {
    return `Hi, I'm ${this.name}`;
  },
  // 计算属性名
  ['dyn' + 'Key']: 42,
  // getter / setter
  get info() { return `${this.name} (${this.age})`; },
  set info(v) { [this.name, this.age] = v.split(','); }
};

2.2 Object.create

js 复制代码
const animal = {
  speak() { return `${this.name} makes a sound`; }
};

const dog = Object.create(animal, {
  name: { value: 'Rex', writable: true, enumerable: true }
});
dog.speak(); // "Rex makes a sound"

// 创建无原型的纯净对象(适合做字典)
const dict = Object.create(null);

2.3 工厂函数

js 复制代码
function createUser(name, age) {
  let secret = 'hidden'; // 闭包实现私有
  return {
    name,
    age,
    getSecret: () => secret
  };
}

优点 :无需 new,天然私有;缺点:方法不共享,内存占用高。

2.4 构造函数

js 复制代码
function User(name, age) {
  this.name = name;
  this.age = age;
}
User.prototype.greet = function () {
  return `Hi, I'm ${this.name}`;
};
const u = new User('Bob', 30);

3. this 关键字

this 的值在调用时确定,遵循以下优先级(高 → 低):

javascript 复制代码
new 绑定 > 显式绑定 (call/apply/bind) > 隐式绑定 (obj.fn()) > 默认绑定 (window/undefined)
js 复制代码
function show() { return this?.name; }

const obj = { name: 'obj', show };

show();               // undefined(严格模式)/ window.name
obj.show();           // 'obj'        隐式绑定
show.call({ name: 'x' }); // 'x'      显式绑定
new show();           // this 指向新对象

3.1 常见丢失场景

js 复制代码
const fn = obj.show;
fn();                       // this 丢失
setTimeout(obj.show, 0);    // this 丢失

// 解决方案
setTimeout(() => obj.show(), 0);
setTimeout(obj.show.bind(obj), 0);

3.2 箭头函数

箭头函数没有自己的 this ,它捕获定义时所在词法作用域的 this

js 复制代码
class Timer {
  seconds = 0;
  start() {
    setInterval(() => {
      this.seconds++; // 正确指向 Timer实例
    }, 1000);
  }
}

3.3 手写 call / apply / bind

js 复制代码
Function.prototype.myCall = function (ctx, ...args) {
  ctx = ctx ?? globalThis;
  const key = Symbol('fn');
  ctx[key] = this;
  const result = ctx[key](...args);
  delete ctx[key];
  return result;
};

Function.prototype.myBind = function (ctx, ...args) {
  const self = this;
  return function bound(...rest) {
    // 支持 new 调用
    return self.apply(new.target ? this : ctx, [...args, ...rest]);
  };
};

4. 原型与原型链

4.1 三个核心概念

  • prototype函数才有,指向原型对象
  • __proto__(即 [[Prototype]]):所有对象 都有,指向其构造函数的 prototype
  • constructor:原型对象上的属性,指回构造函数
js 复制代码
function Foo() {}
const f = new Foo();

f.__proto__ === Foo.prototype;               // true
Foo.prototype.constructor === Foo;           // true
Foo.prototype.__proto__ === Object.prototype; // true
Object.prototype.__proto__ === null;         // true

Foo.__proto__ === Function.prototype;        // true
Function.prototype.__proto__ === Object.prototype; // true
Function.__proto__ === Function.prototype;   // true(自指)
Object.__proto__ === Function.prototype;     // true

4.2 原型链图

javascript 复制代码
f ──__proto__──▶ Foo.prototype ──__proto__──▶ Object.prototype ──▶ null
                      │
                 constructor
                      ▼
                     Foo ──__proto__──▶ Function.prototype ──▶ Object.prototype

4.3 属性查找

js 复制代码
Object.prototype.z = 3;
Foo.prototype.y = 2;
f.x = 1;

f.x; // 1  自身属性
f.y; // 2  沿原型链查找
f.z; // 3
f.w; // undefined  直到 null 都找不到

4.4 相关 API

js 复制代码
Object.getPrototypeOf(f);            // 推荐替代 __proto__
Object.setPrototypeOf(f, proto);     // 性能差,避免使用
f.hasOwnProperty('x');               // true
Object.hasOwn(f, 'x');               // ES2022 推荐
'y' in f;                            // true(含原型链)
f instanceof Foo;                    // true
Foo.prototype.isPrototypeOf(f);      // true

// 手写 instanceof
function myInstanceof(obj, Ctor) {
  let p = Object.getPrototypeOf(obj);
  while (p) {
    if (p === Ctor.prototype) return true;
    p = Object.getPrototypeOf(p);
  }
  return false;
}

4.5 属性遮蔽(Shadowing)

js 复制代码
const proto = { count: 0 };
const o = Object.create(proto);
o.count++; // 等价于 o.count = o.count + 1,在 o 上创建新属性
proto.count; // 0,未被修改

5. 构造函数与 new

5.1 new 做了什么

  1. 创建空对象 obj
  2. obj.__proto__ = Ctor.prototype
  3. objthis 执行构造函数
  4. 若构造函数返回对象则返回该对象,否则返回 obj
js 复制代码
function myNew(Ctor, ...args) {
  const obj = Object.create(Ctor.prototype);
  const result = Ctor.apply(obj, args);
  return result !== null && typeof result === 'object' ? result : obj;
}

5.2 new.target

js 复制代码
function Shape() {
  if (!new.target) throw new Error('必须使用 new 调用');
  if (new.target === Shape) throw new Error('Shape 是抽象类');
}

5.3 避免忘记 new

js 复制代码
function User(name) {
  if (!(this instanceof User)) return new User(name);
  this.name = name;
}

6. ES6 Class 语法

6.1 完整语法一览

js 复制代码
class Person {
  // 公共实例字段
  name;
  age = 0;

  // 私有字段
  #id;
  static #count = 0;

  // 静态字段
  static species = 'Homo sapiens';

  constructor(name, age) {
    this.name = name;
    this.age = age;
    this.#id = ++Person.#count;
  }

  // 实例方法(挂在 prototype 上)
  greet() {
    return `Hi, I'm ${this.name}`;
  }

  // 私有方法
  #validate() {
    return this.age >= 0;
  }

  // getter / setter
  get id() { return this.#id; }
  get isAdult() { return this.age >= 18; }
  set nickname(v) { this._nick = v.trim(); }

  // 静态方法
  static create(name, age) {
    return new Person(name, age);
  }

  // 静态私有方法
  static #reset() { Person.#count = 0; }

  // 静态块(ES2022)
  static {
    console.log('Class initialized');
  }

  // Symbol 方法
  [Symbol.toPrimitive](hint) {
    return hint === 'number' ? this.age : this.name;
  }

  // 生成器方法
  *[Symbol.iterator]() {
    yield this.name;
    yield this.age;
  }

  // 异步方法
  async load() {
    return await fetch(`/api/${this.#id}`);
  }
}

6.2 Class 与构造函数的区别

特性 构造函数 Class
提升 函数提升 存在 TDZ,不可提前使用
严格模式 需手动开启 默认严格模式
方法可枚举 否(enumerable: false
不用 new 调用 允许 抛出 TypeError
私有成员 闭包模拟 原生 # 语法
js 复制代码
typeof Person; // 'function'
Object.keys(Person.prototype); // [] 方法不可枚举

6.3 类表达式与匿名类

js 复制代码
const Animal = class {
  speak() { return 'sound'; }
};

// 立即实例化
const singleton = new (class {
  constructor() { this.time = Date.now(); }
})();

6.4 字段初始化顺序

js 复制代码
class A {
  x = this.init(); // 字段初始化在 constructor 体之前执行
  constructor() {
    console.log('ctor');
  }
  init() { console.log('field'); return 1; }
}
new A(); // "field" → "ctor"

7. 继承

7.1 ES5 继承演进

js 复制代码
// ① 原型链继承:引用类型共享,无法传参
Child.prototype = new Parent();

// ② 借用构造函数:方法无法复用
function Child() { Parent.call(this); }

// ③ 组合继承:调用两次父构造函数
function Child() { Parent.call(this); }
Child.prototype = new Parent();
Child.prototype.constructor = Child;

// ④ 寄生组合继承(最优 ES5 方案)
function inherit(Child, Parent) {
  Child.prototype = Object.create(Parent.prototype, {
    constructor: { value: Child, writable: true, configurable: true }
  });
  Object.setPrototypeOf(Child, Parent); // 继承静态成员
}

function Parent(name) { this.name = name; }
Parent.prototype.say = function () { return this.name; };

function Child(name, age) {
  Parent.call(this, name);
  this.age = age;
}
inherit(Child, Parent);

7.2 ES6 extends

js 复制代码
class Animal {
  constructor(name) { this.name = name; }
  speak() { return `${this.name} makes a sound`; }
  static kingdom() { return 'Animalia'; }
}

class Dog extends Animal {
  constructor(name, breed) {
    super(name); // 必须在使用 this 前调用
    this.breed = breed;
  }
  speak() {
    return `${super.speak()} --- Woof!`; // 调用父类方法
  }
  static kingdom() {
    return super.kingdom() + ' > Canidae'; // 静态方法中的 super
  }
}

7.3 super 的原理

super 依赖方法的内部槽 [[HomeObject]],因此:

js 复制代码
const obj = {
  __proto__: parent,
  method() { super.method(); }  // ✔ 方法简写有 HomeObject
  // method: function () { super.method(); } // ✘ 语法错误
};

7.4 继承内置类型

js 复制代码
class MyArray extends Array {
  sum() { return this.reduce((a, b) => a + b, 0); }
}
const arr = MyArray.from([1, 2, 3]);
arr.map(x => x * 2) instanceof MyArray; // true(Symbol.species)

class MyError extends Error {
  constructor(msg, code) {
    super(msg);
    this.name = 'MyError';
    this.code = code;
    Error.captureStackTrace?.(this, MyError);
  }
}

7.5 extends null 与 extends 表达式

js 复制代码
class Base extends null {} // 原型为 null 的类

// 动态父类
function withLogging(Base) {
  return class extends Base {
    log(m) { console.log(`[${this.constructor.name}] ${m}`); }
  };
}
class Service extends withLogging(Object) {}

7.6 class 继承的底层原理

js 复制代码
class B extends A {}
// 等价于
Object.setPrototypeOf(B.prototype, A.prototype); // 实例方法继承
Object.setPrototypeOf(B, A);                     // 静态方法继承

8. 封装与私有成员

8.1 方案对比

方案 真正私有 可继承访问 调试友好 性能
_ 约定
闭包 低(方法不共享)
Symbol 半私有
WeakMap 一般
# 字段

8.2 WeakMap 方案

js 复制代码
const _private = new WeakMap();

class Account {
  constructor(balance) {
    _private.set(this, { balance });
  }
  get balance() { return _private.get(this).balance; }
}

8.3 # 私有字段进阶

js 复制代码
class Wallet {
  #balance = 0;

  // 私有字段品牌检查(ES2022)
  static isWallet(obj) {
    return #balance in obj;
  }

  // 私有字段不能通过 this[...] 动态访问,也不会被 Proxy 拦截
  transfer(to, amount) {
    this.#balance -= amount;
    to.#balance += amount; // 同类实例可以访问
  }
}

注意# 字段不会出现在原型链上,子类无法访问父类的私有字段。


9. 多态与抽象

9.1 方法重写与鸭子类型

js 复制代码
class Shape {
  area() { throw new Error('必须实现 area()'); }
  toString() { return `${this.constructor.name}: ${this.area()}`; }
}
class Circle extends Shape {
  constructor(r) { super(); this.r = r; }
  area() { return Math.PI * this.r ** 2; }
}
class Rect extends Shape {
  constructor(w, h) { super(); this.w = w; this.h = h; }
  area() { return this.w * this.h; }
}

[new Circle(1), new Rect(2, 3)].forEach(s => console.log(String(s)));

9.2 模拟抽象类

js 复制代码
class AbstractRepo {
  constructor() {
    if (new.target === AbstractRepo) {
      throw new TypeError('抽象类不可实例化');
    }
    for (const m of ['find', 'save']) {
      if (typeof this[m] !== 'function') {
        throw new TypeError(`必须实现 ${m}()`);
      }
    }
  }
}

9.3 模拟接口

js 复制代码
const Comparable = {
  check(obj) {
    if (typeof obj.compareTo !== 'function') {
      throw new TypeError('未实现 Comparable');
    }
  }
};

TypeScript 是更好的选择,但纯 JS 中可用 JSDoc + 运行时检查。


10. Mixin 与组合

10.1 对象 Mixin

js 复制代码
const Serializable = {
  serialize() { return JSON.stringify(this); }
};
const Validatable = {
  validate() { return Object.values(this).every(v => v != null); }
};

class User {}
Object.assign(User.prototype, Serializable, Validatable);

10.2 类工厂 Mixin(推荐)

js 复制代码
const Timestamped = Base => class extends Base {
  created = Date.now();
};
const Taggable = Base => class extends Base {
  tags = new Set();
  tag(t) { this.tags.add(t); return this; }
};

class Post extends Taggable(Timestamped(Object)) {}
const p = new Post().tag('js');

10.3 组合优于继承

js 复制代码
// 继承:Dog extends Animal(is-a)
// 组合:Dog has Walker, Barker(has-a)

const canWalk = state => ({ walk: () => `${state.name} walks` });
const canBark = state => ({ bark: () => `${state.name} barks` });

function createDog(name) {
  const state = { name };
  return { ...state, ...canWalk(state), ...canBark(state) };
}

何时用继承 :明确的 is-a 关系、需要共享构造逻辑、框架要求(如 React.Component、HTMLElement)。 何时用组合:行为可复用于不相关的类、避免深层继承树、需要运行时动态组合。


11. 属性描述符与元编程

11.1 数据描述符与访问器描述符

js 复制代码
Object.defineProperty(obj, 'key', {
  value: 42,
  writable: false,     // 不可修改
  enumerable: false,   // for-in / Object.keys 不可见
  configurable: false  // 不可删除、不可再修改描述符
});

Object.defineProperty(obj, 'computed', {
  get() { return this._v * 2; },
  set(v) { this._v = v; },
  enumerable: true,
  configurable: true
});

Object.getOwnPropertyDescriptor(obj, 'key');
Object.getOwnPropertyDescriptors(obj);

11.2 对象不可变性

js 复制代码
Object.preventExtensions(o); // 不能添加
Object.seal(o);              // 不能添加、删除
Object.freeze(o);            // 不能添加、删除、修改(浅)

function deepFreeze(o) {
  Object.values(o).forEach(v => typeof v === 'object' && v && deepFreeze(v));
  return Object.freeze(o);
}

11.3 属性遍历方法对比

方法 自身 继承 不可枚举 Symbol
for...in
Object.keys
Object.getOwnPropertyNames
Object.getOwnPropertySymbols
Reflect.ownKeys

12. Symbol 与内置协议

js 复制代码
class Money {
  constructor(amount, currency) {
    this.amount = amount;
    this.currency = currency;
  }

  // 类型转换
  [Symbol.toPrimitive](hint) {
    if (hint === 'number') return this.amount;
    if (hint === 'string') return `${this.amount} ${this.currency}`;
    return this.amount; // default
  }

  // Object.prototype.toString.call(m) → "[object Money]"
  get [Symbol.toStringTag]() { return 'Money'; }

  // 自定义 instanceof
  static [Symbol.hasInstance](obj) {
    return 'amount' in obj && 'currency' in obj;
  }

  // 可迭代
  *[Symbol.iterator]() {
    yield this.amount;
    yield this.currency;
  }

  // 异步迭代
  async *[Symbol.asyncIterator]() { /* ... */ }

  // 派生方法返回的类型(Array.map 等)
  static get [Symbol.species]() { return Array; }
}

const m = new Money(100, 'USD');
+m;         // 100
`${m}`;     // "100 USD"
[...m];     // [100, 'USD']

13. Proxy 与 Reflect

13.1 基础

js 复制代码
const target = { name: 'x' };
const proxy = new Proxy(target, {
  get(t, key, receiver) {
    console.log(`读取 ${String(key)}`);
    return Reflect.get(t, key, receiver);
  },
  set(t, key, value, receiver) {
    if (key === 'age' && typeof value !== 'number') {
      throw new TypeError('age 必须是数字');
    }
    return Reflect.set(t, key, value, receiver);
  },
  has(t, key) { return key.startsWith('_') ? false : key in t; },
  deleteProperty(t, key) { /* ... */ },
  ownKeys(t) { return Reflect.ownKeys(t).filter(k => !k.startsWith('_')); },
  apply(fn, thisArg, args) { /* 函数调用 */ },
  construct(Ctor, args, newTarget) { /* new 操作 */ }
});

13.2 响应式系统(Vue 3 原理)

js 复制代码
const deps = new WeakMap();
let activeEffect = null;

function track(target, key) {
  if (!activeEffect) return;
  let map = deps.get(target) ?? (deps.set(target, new Map()), deps.get(target));
  let set = map.get(key) ?? (map.set(key, new Set()), map.get(key));
  set.add(activeEffect);
}
function trigger(target, key) {
  deps.get(target)?.get(key)?.forEach(fn => fn());
}

function reactive(obj) {
  return new Proxy(obj, {
    get(t, k, r) { track(t, k); return Reflect.get(t, k, r); },
    set(t, k, v, r) {
      const res = Reflect.set(t, k, v, r);
      trigger(t, k);
      return res;
    }
  });
}
function effect(fn) {
  activeEffect = fn;
  fn();
  activeEffect = null;
}

const state = reactive({ count: 0 });
effect(() => console.log('count:', state.count));
state.count++; // 自动打印 count: 1

13.3 Proxy 与私有字段的冲突

js 复制代码
class A { #x = 1; getX() { return this.#x; } }
const p = new Proxy(new A(), {});
p.getX(); // TypeError:this 是 proxy 而非原始对象

// 解决:get 陷阱中绑定原始对象
new Proxy(new A(), {
  get(t, k) {
    const v = Reflect.get(t, k);
    return typeof v === 'function' ? v.bind(t) : v;
  }
});

14. 设计模式实战

14.1 单例

js 复制代码
class Config {
  static #instance;
  static getInstance() {
    return Config.#instance ??= new Config();
  }
  constructor() {
    if (Config.#instance) throw new Error('使用 getInstance()');
  }
}

14.2 观察者 / 发布订阅

js 复制代码
class EventEmitter {
  #listeners = new Map();
  on(evt, fn) {
    (this.#listeners.get(evt) ?? this.#listeners.set(evt, new Set()).get(evt)).add(fn);
    return () => this.off(evt, fn);
  }
  off(evt, fn) { this.#listeners.get(evt)?.delete(fn); }
  once(evt, fn) {
    const off = this.on(evt, (...a) => { off(); fn(...a); });
  }
  emit(evt, ...args) {
    this.#listeners.get(evt)?.forEach(fn => fn(...args));
  }
}

14.3 策略模式

js 复制代码
const strategies = {
  credit: amount => amount * 0.98,
  paypal: amount => amount - 1,
  crypto: amount => amount * 0.995
};
class Checkout {
  constructor(strategy) { this.pay = strategies[strategy]; }
}

14.4 装饰器模式(含 Stage 3 装饰器语法)

js 复制代码
// 函数式装饰
const withRetry = (fn, n = 3) => async (...args) => {
  for (let i = 0; i < n; i++) {
    try { return await fn(...args); } catch (e) { if (i === n - 1) throw e; }
  }
};

// TC39 装饰器(需 Babel / TS 5+)
function log(value, { kind, name }) {
  if (kind === 'method') {
    return function (...args) {
      console.log(`调用 ${name}`);
      return value.apply(this, args);
    };
  }
}
class Api {
  @log
  fetch() {}
}

14.5 工厂 / 抽象工厂

js 复制代码
class ShapeFactory {
  static #registry = new Map();
  static register(type, Ctor) { this.#registry.set(type, Ctor); }
  static create(type, ...args) {
    const Ctor = this.#registry.get(type);
    if (!Ctor) throw new Error(`未知类型 ${type}`);
    return new Ctor(...args);
  }
}
ShapeFactory.register('circle', Circle);

14.6 责任链

js 复制代码
class Handler {
  #next = null;
  setNext(h) { this.#next = h; return h; }
  handle(req) { return this.#next?.handle(req) ?? null; }
}
class AuthHandler extends Handler {
  handle(req) {
    if (!req.user) return 'Unauthorized';
    return super.handle(req);
  }
}

15. SOLID 原则在 JS 中的应用

S --- 单一职责:一个类只有一个变化的理由。

js 复制代码
// ✘ User 既管理数据又负责持久化
// ✔ 拆分为 User + UserRepository

O --- 开闭原则:对扩展开放,对修改关闭(用策略/注册表替代 if-else)。

L --- 里氏替换:子类必须能替换父类而不破坏程序。

js 复制代码
// ✘ Square extends Rectangle 破坏了 setWidth/setHeight 语义

I --- 接口隔离:不强迫类实现用不到的方法(用小 Mixin 替代大基类)。

D --- 依赖倒置:依赖抽象而非具体实现(构造函数注入)。

js 复制代码
class OrderService {
  constructor(repo, mailer) { // 注入依赖而非 new 出来
    this.repo = repo;
    this.mailer = mailer;
  }
}

16. 性能与最佳实践

16.1 V8 隐藏类与内联缓存

js 复制代码
// ✔ 保持属性初始化顺序一致,避免动态增删属性
class Point {
  constructor(x, y) { this.x = x; this.y = y; } // 所有实例同样的形状
}

// ✘ 动态添加导致隐藏类分裂
const p = new Point(1, 2);
p.z = 3;

// ✘ 避免 Object.setPrototypeOf / __proto__ 赋值,会使 IC 失效
// ✘ 避免 delete,改为赋 undefined

16.2 方法定义位置

js 复制代码
class A {
  method() {}          // ✔ 原型上共享,一份内存
  arrow = () => {};    // ✘ 每个实例一份,仅在需要绑定 this 时使用
}

16.3 通用建议

  • 优先 class 语法,避免手写原型
  • 优先组合,慎用深层继承(超过 2-3 层需反思)
  • # 私有字段替代 _ 约定
  • Object.hasOwn 替代 hasOwnProperty
  • Object.freeze 保护常量配置
  • 不要修改内置原型(Array.prototype.xxx = ...
  • 使用 TypeScript 获得接口、抽象类、访问修饰符

17. 常见陷阱与面试题

17.1 陷阱

js 复制代码
// 1. 原型上的引用类型被共享
class A { static list = []; } // 所有子类共享同一个数组

// 2. class 方法作为回调 this 丢失
button.addEventListener('click', this.handleClick);          // ✘
button.addEventListener('click', () => this.handleClick());  // ✔

// 3. 箭头函数不能作为构造函数
const F = () => {}; new F(); // TypeError

// 4. super 之前访问 this
class B extends A { constructor() { this.x = 1; super(); } } // ReferenceError

// 5. 私有字段与 Proxy 不兼容(见 13.3)

// 6. 字段初始化时父类方法已可用,但子类字段尚未初始化
class P { constructor() { this.init(); } }
class C extends P { value = 1; init() { console.log(this.value); } }
new C(); // undefined!

17.2 经典面试题

Q1:输出什么?

js 复制代码
function Foo() {
  getName = function () { console.log(1); };
  return this;
}
Foo.getName = function () { console.log(2); };
Foo.prototype.getName = function () { console.log(3); };
var getName = function () { console.log(4); };
function getName() { console.log(5); }

Foo.getName();        // 2
getName();            // 4(函数声明提升后被表达式覆盖)
Foo().getName();      // 1(Foo() 中修改了全局 getName)
getName();            // 1
new Foo.getName();    // 2(成员访问优先级高于 new 无参)
new Foo().getName();  // 3(new 有参优先级最高,然后访问原型方法)
new new Foo().getName(); // 3

Q2:实现一个支持链式调用和延迟执行的类。

js 复制代码
class Lazy {
  #tasks = [];
  constructor(name) { this.#tasks.push(() => console.log(`Hi ${name}`)); setTimeout(() => this.#run()); }
  sleep(s) { this.#tasks.push(() => new Promise(r => setTimeout(r, s * 1000))); return this; }
  eat(f) { this.#tasks.push(() => console.log(`Eat ${f}`)); return this; }
  async #run() { for (const t of this.#tasks) await t(); }
}
new Lazy('Tom').sleep(1).eat('lunch');

Q3:Object.create(null){} 的区别? 前者没有原型,无 toString/hasOwnProperty 等,适合做纯字典,避免键名冲突和原型污染。

Q4:如何判断一个对象是否由某个类直接实例化?

js 复制代码
obj.constructor === Cls           // 可被篡改
Object.getPrototypeOf(obj) === Cls.prototype  // 更可靠

学习路线总结

javascript 复制代码
入门 ─── 对象字面量 / this / 构造函数 / class 基础
  │
进阶 ─── 原型链 / 继承机制 / 封装 / 多态 / Mixin
  │
精通 ─── 属性描述符 / Symbol 协议 / Proxy 响应式 / 设计模式 / V8 优化
  │
超越 ─── TypeScript 类型系统 / 装饰器 / 架构设计 / 函数式与 OOP 融合

记住核心思想:JavaScript 中一切皆对象,一切继承皆原型链,class 只是语法糖。 掌握这一点,其他所有知识都是它的自然延伸。

相关推荐
一条溺水的鱼1 小时前
一次讲透 JS 闭包 —— 概念、原理、应用和内存泄漏
javascript·面试
吠品1 小时前
STM32最小系统板引脚梳理与配置实操
前端·javascript·vue.js
曹一二1 小时前
前端性能优化场景题:图片懒加载 + 大数据渲染
前端
GISer_Jing1 小时前
Come on,工作总结
前端·ai·前端框架
Hilaku1 小时前
Sass 和 Less 在 2026 年彻底多余了吗?
前端·javascript·程序员
右耳朵猫AI1 小时前
Node.js周刊2026W38 | 进程中断缺陷修复、Copilot 迁至 Rust、Node 新增 VFS
javascript·后端·node.js
百慕大三角1 小时前
AI 写代码最大的风险不是不会写,而是太能写:我给 Coding Agent 加的 4 层工程约束
前端·ai编程·trae
Shao2391 小时前
基于 Rokid AIUI:在眼镜上穿越五千年
javascript
用户921080262861 小时前
从 EventSource 到可复用 SSE Client:我如何实现多实例、双超时与自动重连
前端