JavaScript原型对象、构造函数、继承与类详解

JavaScript原型对象、构造函数、继承与类详解

一、5W2H详细解析

What(什么)

深入解析JavaScript中原型对象、实例对象、构造函数之间的关系,以及它们如何延伸到继承、类和封装等概念。这是理解JavaScript面向对象编程的基础。

Why(为什么)

掌握这些核心概念对于深入理解JavaScript语言特性、进行高效的前端开发以及解决复杂的编程问题至关重要。同时这也是前端面试中的高频考点。

Who(谁)

适用于所有希望深入理解JavaScript面向对象编程的开发者,特别是准备前端面试的求职者。

When(何时)

在学习JavaScript进阶知识、准备技术面试或遇到相关编程问题时。

Where(哪里)

在JavaScript开发环境中的任何地方,特别是在处理对象创建、继承和封装时。

How(如何做)

通过理论讲解结合代码示例的方式,从基础概念开始逐步深入到高级应用。

How much(多少成本/资源)

需要一定的时间投入来理解这些概念,但掌握后将大大提升JavaScript编程能力。

二、图解原型和原型链

原型和原型链是 JS 中不可避免需要碰到的知识点,本文使用图片和文字结合的形式缕一缕原型、原型链、实例、构造函数等等概念之间的关系。

Constructor 构造函数

首先我们先写一个构造函数 Person,构造函数一般为了区别普通函数要求首字母大写:

javascript 复制代码
function Person(){}

prototype 原型

原型指的就是一个对象,实例"继承"那个对象的属性。在原型上定义的属性,通过"继承",实例也拥有了这个属性。"继承"这个行为是在 new 操作符内部实现的。

先不说实例,原型与构造函数的关系就是,构造函数内部有一个名为 prototype 的属性,通过这个属性就能访问到原型:

javascript 复制代码
function Person(){}

// Person 就是构造函数,Person.prototype 就是原型
console.log(Person.prototype); // {}

instance 实例

有个构造函数,我们就可以在原型上创建可以"继承"的属性,并通过 new 操作符创建实例

比方说 Person,我们要创建一个 person 实例,那么使用 new 操作符就可以实现,并通过 instanceof 来检查他们之间的关系:

javascript 复制代码
function Person(){}

const person = new Person();

console.log(person instanceof Person); // true

我们在原型上定义一个属性,那么实例上也就可以"继承"这个属性:

javascript 复制代码
function Person(){}

Person.prototype.name = "default name";

const person = new Person();
console.log(person.name); // "default name"

proto 隐式原型

实例通过 proto 访问到原型,所以如果是实例,那么就可以通过这个属性直接访问到原型:

javascript 复制代码
function Person(){}

const person = new Person();

// 实例通过 __proto__ 访问到原型
console.log(person.__proto__); // Person.prototype

所以这两者是等价的:

javascript 复制代码
function Person(){}

const person = new Person();

console.log(person.__proto__ === Person.prototype); // true

constructor 构造函数属性

既然构造函数通过 prototype 来访问到原型,那么原型也应该能够通过某种途径访问到构造函数,这就是 constructor:

javascript 复制代码
function Person(){}

// 原型通过 constructor 属性访问到构造函数
console.log(Person.prototype.constructor === Person); // true

因此两者的关系应该是这样:

javascript 复制代码
function Person(){}

const person = new Person();

// 关系链
console.log(person.__proto__ === Person.prototype); // true
console.log(Person.prototype.constructor === Person); // true
console.log(person.__proto__.constructor === Person); // true

注意这里的 constructor 是原型的一个属性,Person.prototype.constructor 指向的才是真正的构造函数。两者名字不要弄混了。

实例、构造函数、原型之间的关系

这里我们可以看到如果实例想要访问构造函数,那么应当是:

javascript 复制代码
function Person(){}

const person = new Person();

// 实例 -> 原型 -> 构造函数
console.log(person.__proto__.constructor === Person); // true

没有从实例直接访问到构造函数的属性或方法:

javascript 复制代码
function Person(){}

const person = new Person();

// 这样是访问不到的
// person.constructor === Person 也能访问到,但这是通过原型链查找的

实例与原型则是通过上文中提到的 proto 去访问到。

在读取一个实例的属性的过程中,如果属性在该实例中没有找到,那么就会循着 proto 指定的原型上去寻找,如果还找不到,则尝试寻找原型的原型:

javascript 复制代码
function Person(){}

Person.prototype.name = "default name";

const person = new Person();

// 实例上没有 name 属性,通过 __proto__ 在原型上找到
console.log(person.name); // "default name"

// 如果在实例上定义同名属性,会覆盖原型上的属性
person.name = "custom name";
console.log(person.name); // "custom name"

我们把注释删掉,给实例同名属性,可以看到打印出来的属性就指向这个:

javascript 复制代码
function Person(){}

Person.prototype.name = "default name";

const person = new Person();
person.name = "custom name";

console.log(person.name); // "custom name" - 实例属性
console.log(person.__proto__.name); // "default name" - 原型属性

原型链示意图

javascript 复制代码
实例对象 (person)
    ↓ __proto__
构造函数的原型对象 (Person.prototype)
    ↓ __proto__
Object.prototype
    ↓ __proto__
null

原型链

原型同样也可以通过 proto 访问到原型的原型,比方说这里有个构造函数 Person 然后"继承"前者的有一个构造函数 People,然后 new People 得到实例 p

javascript 复制代码
function Person(){}
function People(){}

// 设置继承关系
People.prototype = Object.create(Person.prototype);
People.prototype.constructor = People;

const p = new People();

当访问 p 中的一个非自有属性的时候,就会通过 proto 作为桥梁连接起来的一系列原型、原型的原型、原型的原型的原型直到 Object 构造函数为止。

这个搜索的过程形成的链状关系就是原型链:

javascript 复制代码
p (实例)
  ↓ __proto__
People.prototype
  ↓ __proto__
Person.prototype
  ↓ __proto__
Object.prototype
  ↓ __proto__
null

看到 null 了么,原型链搜索搜到 null 为止,搜不到那访问的这个属性就是不存在的:

javascript 复制代码
function Person(){}
function People(){}

People.prototype = Object.create(Person.prototype);
People.prototype.constructor = People;

const p = new People();

// 搜索路径: p -> People.prototype -> Person.prototype -> Object.prototype -> null
console.log(p.toString); // 找到了 Object.prototype.toString

// 搜索不存在的属性
console.log(p.nonExistentProperty); // undefined

以上,这就是原型、原型链、构造函数、实例、null 之间的关系。

三、完整的技术落地方案

1. 原型对象、实例对象与构造函数详解

基本概念

构造函数(Constructor Function) : 构造函数是用于创建特定类型对象的函数。在JavaScript中,任何函数都可以作为构造函数使用,只要通过new关键字调用。

javascript 复制代码
// 构造函数示例
function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.sayHello = function() {
  console.log(`Hello, my name is ${this.name}`);
};

// 使用构造函数创建实例
const person1 = new Person("Alice", 25);
const person2 = new Person("Bob", 30);

实例对象(Instance Object) : 通过构造函数和new关键字创建的具体对象称为实例对象。

javascript 复制代码
// person1和person2都是实例对象
console.log(person1 instanceof Person); // true
console.log(person2 instanceof Person); // true

原型对象(Prototype Object) : 每个函数都有一个prototype属性,这个属性指向一个对象,这个对象就是原型对象。原型对象包含可以由特定类型的所有实例共享的属性和方法。

javascript 复制代码
// 访问原型对象
console.log(Person.prototype); // 原型对象
console.log(person1.__proto__); // 实例对象通过__proto__指向原型对象
console.log(Person.prototype === person1.__proto__); // true
三者关系图解
scss 复制代码
构造函数(Person) <------constructor-------- 原型对象(Person.prototype)
    |                                    |
    | new                                | 
    |                                    |
    v                                    |
实例对象(person1) <------------------- __proto__
详细代码示例
javascript 复制代码
// 定义构造函数
function Animal(name, type) {
  // 实例属性
  this.name = name;
  this.type = type;
  this.energy = 100;
}

// 原型方法
Animal.prototype.eat = function(food) {
  console.log(`${this.name} is eating ${food}`);
  this.energy += 10;
};

Animal.prototype.sleep = function() {
  console.log(`${this.name} is sleeping`);
  this.energy += 20;
};

Animal.prototype.getInfo = function() {
  return `${this.name} is a ${this.type} with ${this.energy} energy`;
};

// 创建实例对象
const dog = new Animal("Buddy", "Dog");
const cat = new Animal("Whiskers", "Cat");

// 使用方法
dog.eat("bone"); // Buddy is eating bone
cat.sleep(); // Whiskers is sleeping

console.log(dog.getInfo()); // Buddy is a Dog with 110 energy
console.log(cat.getInfo()); // Whiskers is a Cat with 120 energy

// 验证关系
console.log(dog.constructor === Animal); // true
console.log(dog.__proto__ === Animal.prototype); // true
console.log(Animal.prototype.constructor === Animal); // true

原型关系及原型链总结

  1. 构造函数 → 原型对象 :

    • 图中绿色箭头标记为"constructor",表示 Person 构造函数通过其 prototype 属性指向 Person.prototype 原型对象
    • 每个函数在创建时都会自动拥有一个 prototype 属性,指向其原型对象
  2. 原型对象 → 构造函数 :

    • 图中绿色箭头标记为"prototype",表示 Person.prototype 原型对象通过其 constructor 属性指回 Person 构造函数
    • 这形成了一个循环引用关系
  3. 实例 → 原型对象 :

    • 图中绿色箭头标记为" proto "和"proto",表示通过 new Person() 创建的 person 实例通过其内部的 [[Prototype]] 链接(即 proto )指向 Person.prototype
    • 当使用 new 关键字创建对象时,新对象的 proto 会自动链接到构造函数的 prototype

prototype和__proto__属性的作用

  • prototype :

    • 只存在于构造函数上的属性
    • 指向该构造函数的原型对象
    • 用于定义所有实例共享的属性和方法
  • proto :

    • 存在于所有对象上的内部属性(ES6之前是非标准的,现在已标准化)
    • 指向创建该对象的构造函数的原型对象
    • 是对象实例访问原型链的入口点

原型链工作机制

当我们访问一个对象的属性或方法时,JavaScript引擎会:

  1. 首先检查对象自身是否有该属性/方法
  2. 如果没有,则沿着 proto 链向上查找到对象的原型
  3. 如果原型上也没有,则继续沿着原型的 proto 向上查找
  4. 这个查找过程会一直持续到找到属性/方法或到达原型链的末端( Object.prototype.proto === null )

2. 继承详解

原型链继承
javascript 复制代码
// 父类
function Animal(name) {
  this.name = name;
}

Animal.prototype.speak = function() {
  console.log(`${this.name} makes a sound`);
};

// 子类
function Dog(name, breed) {
  Animal.call(this, name); // 调用父类构造函数
  this.breed = breed;
}

// 设置继承关系
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

// 子类特有方法
Dog.prototype.bark = function() {
  console.log(`${this.name} barks`);
};

// 使用
const dog = new Dog("Buddy", "Golden Retriever");
dog.speak(); // Buddy makes a sound
dog.bark(); // Buddy barks
借用构造函数继承
javascript 复制代码
function Parent(name) {
  this.name = name;
  this.hobbies = ["reading", "swimming"];
}

function Child(name, age) {
  Parent.call(this, name); // 借用父类构造函数
  this.age = age;
}

const child1 = new Child("Alice", 10);
const child2 = new Child("Bob", 12);

child1.hobbies.push("dancing");

console.log(child1.hobbies); // ["reading", "swimming", "dancing"]
console.log(child2.hobbies); // ["reading", "swimming"]
组合继承
javascript 复制代码
function Parent(name) {
  this.name = name;
  this.colors = ["red", "blue"];
}

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

function Child(name, age) {
  Parent.call(this, name); // 借用构造函数继承属性
  this.age = age;
}

// 原型链继承方法
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;

Child.prototype.getAge = function() {
  return this.age;
};

const child1 = new Child("Alice", 10);
const child2 = new Child("Bob", 12);

child1.colors.push("green");

console.log(child1.colors); // ["red", "blue", "green"]
console.log(child2.colors); // ["red", "blue"]
console.log(child1.getName()); // Alice
console.log(child2.getName()); // Bob

3. 类(Class)详解

ES6引入了类语法,使JavaScript的面向对象编程更加直观。

基本类定义
javascript 复制代码
class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  
  // 原型方法
  sayHello() {
    console.log(`Hello, my name is ${this.name}`);
  }
  
  // 静态方法
  static getSpecies() {
    return "Homo sapiens";
  }
  
  // Getter
  get description() {
    return `${this.name} is ${this.age} years old`;
  }
  
  // Setter
  set nickname(value) {
    this._nickname = value;
  }
  
  get nickname() {
    return this._nickname || this.name;
  }
}

const person = new Person("Alice", 25);
person.sayHello(); // Hello, my name is Alice
console.log(Person.getSpecies()); // Homo sapiens
console.log(person.description); // Alice is 25 years old
person.nickname = "Ali";
console.log(person.nickname); // Ali
类继承
javascript 复制代码
class Animal {
  constructor(name) {
    this.name = name;
  }
  
  speak() {
    console.log(`${this.name} makes a sound`);
  }
}

class Dog extends Animal {
  constructor(name, breed) {
    super(name); // 调用父类构造函数
    this.breed = breed;
  }
  
  speak() {
    super.speak(); // 调用父类方法
    console.log(`${this.name} barks`);
  }
  
  getInfo() {
    return `${this.name} is a ${this.breed}`;
  }
}

const dog = new Dog("Buddy", "Golden Retriever");
dog.speak(); 
// Buddy makes a sound
// Buddy barks

console.log(dog.getInfo()); // Buddy is a Golden Retriever

4. 封装类详解

javascript 复制代码
class BankAccount {
  constructor(accountNumber, initialBalance) {
    this.accountNumber = accountNumber;
    this._balance = initialBalance; // 私有属性约定
  }
  
  // 存款
  deposit(amount) {
    if (amount > 0) {
      this._balance += amount;
      return this._balance;
    }
    throw new Error("Deposit amount must be positive");
  }
  
  // 取款
  withdraw(amount) {
    if (amount > 0 && amount <= this._balance) {
      this._balance -= amount;
      return this._balance;
    }
    throw new Error("Invalid withdrawal amount");
  }
  
  // 获取余额
  getBalance() {
    return this._balance;
  }
  
  // 转账
  transfer(amount, targetAccount) {
    this.withdraw(amount);
    targetAccount.deposit(amount);
  }
}

// 使用示例
const account1 = new BankAccount("123456", 1000);
const account2 = new BankAccount("789012", 500);

account1.deposit(200);
console.log(account1.getBalance()); // 1200

account1.transfer(300, account2);
console.log(account1.getBalance()); // 900
console.log(account2.getBalance()); // 800

四、技术重点难点列表及其解决办法

1. 原型链理解难点

难点 :理解原型链的查找机制和__proto__prototype的区别。

解决办法

  • 通过可视化图解理解原型链结构
  • 多做代码实验验证原型链查找过程
  • 理解__proto__是实例对象的属性,prototype是构造函数的属性
javascript 复制代码
// 原型链查找示例
function Foo() {}
const foo = new Foo();

console.log(foo.__proto__ === Foo.prototype); // true
console.log(Foo.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // true

2. 继承实现难点

难点:正确实现各种继承方式,避免引用类型属性被共享的问题。

解决办法

  • 理解组合继承的优势和不足
  • 掌握寄生组合式继承的实现方式
  • 使用ES6的class语法简化继承实现
javascript 复制代码
// 寄生组合式继承
function inheritPrototype(subType, superType) {
  const prototype = Object.create(superType.prototype);
  prototype.constructor = subType;
  subType.prototype = prototype;
}

function Parent(name) {
  this.name = name;
  this.colors = ["red", "blue"];
}

Parent.prototype.sayName = function() {
  console.log(this.name);
};

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

inheritPrototype(Child, Parent);

Child.prototype.sayAge = function() {
  console.log(this.age);
};

3. 类与构造函数的区别

难点:理解ES6类与ES5构造函数的区别和联系。

解决办法

  • 理解类本质上仍然是基于原型的语法糖
  • 掌握类的静态方法、getter/setter等特性
  • 理解类必须使用new调用的限制
javascript 复制代码
// 类与构造函数的等价实现
// ES5构造函数
function PersonES5(name) {
  this.name = name;
}

PersonES5.prototype.sayHello = function() {
  console.log(`Hello, ${this.name}`);
};

// ES6类
class PersonES6 {
  constructor(name) {
    this.name = name;
  }
  
  sayHello() {
    console.log(`Hello, ${this.name}`);
  }
}

// 两者创建的对象行为基本一致
const person1 = new PersonES5("Alice");
const person2 = new PersonES6("Bob");

person1.sayHello(); // Hello, Alice
person2.sayHello(); // Hello, Bob

4. 私有属性实现难点

难点:JavaScript中没有真正的私有属性,如何实现封装。

解决办法

  • 使用命名约定(下划线前缀)表示私有属性
  • 使用Symbol创建私有属性
  • 使用ES2022的私有字段语法(#)
javascript 复制代码
// 方法1:命名约定
class MyClass1 {
  constructor() {
    this._privateProp = "private";
  }
}

// 方法2:Symbol
const privateProp = Symbol("private");

class MyClass2 {
  constructor() {
    this[privateProp] = "private";
  }
}

// 方法3:ES2022私有字段(需要现代浏览器支持)
class MyClass3 {
  #privateProp = "private";
  
  getPrivateProp() {
    return this.#privateProp;
  }
}

五、场景或业务的痛难点及其解决方案

1. 对象创建的灵活性需求

痛点:在复杂的业务场景中,需要灵活地创建具有不同属性和方法的对象。

解决方案

  • 使用工厂模式创建对象
  • 结合原型继承实现代码复用
  • 使用ES6类简化对象创建
javascript 复制代码
// 工厂模式示例
class ShapeFactory {
  static createShape(type, ...args) {
    switch(type) {
      case 'circle':
        return new Circle(...args);
      case 'rectangle':
        return new Rectangle(...args);
      default:
        throw new Error('Unknown shape type');
    }
  }
}

class Circle {
  constructor(radius) {
    this.radius = radius;
  }
  
  area() {
    return Math.PI * this.radius ** 2;
  }
}

class Rectangle {
  constructor(width, height) {
    this.width = width;
    this.height = height;
  }
  
  area() {
    return this.width * this.height;
  }
}

// 使用工厂创建对象
const circle = ShapeFactory.createShape('circle', 5);
const rectangle = ShapeFactory.createShape('rectangle', 4, 6);

console.log(circle.area()); // 78.53981633974483
console.log(rectangle.area()); // 24

2. 继承体系的维护困难

痛点:随着业务复杂度增加,继承体系变得复杂,难以维护和扩展。

解决方案

  • 优先使用组合而非继承
  • 合理设计抽象基类
  • 使用接口或混入(mixin)实现多重继承效果
javascript 复制代码
// 混入模式示例
const Flyable = {
  fly() {
    console.log(`${this.name} is flying`);
  }
};

const Swimmable = {
  swim() {
    console.log(`${this.name} is swimming`);
  }
};

class Duck {
  constructor(name) {
    this.name = name;
  }
}

// 扩展Duck类的功能
Object.assign(Duck.prototype, Flyable, Swimmable);

const duck = new Duck("Donald");
duck.fly(); // Donald is flying
duck.swim(); // Donald is swimming

3. 内存优化需求

痛点:大量相似对象的创建导致内存占用过高。

解决方案

  • 使用原型共享方法以节省内存
  • 实现对象池模式复用对象
  • 及时清理不需要的对象引用
javascript 复制代码
// 对象池模式示例
class ObjectPool {
  constructor(createFn, resetFn) {
    this.createFn = createFn;
    this.resetFn = resetFn;
    this.pool = [];
  }
  
  acquire() {
    if (this.pool.length > 0) {
      return this.pool.pop();
    }
    return this.createFn();
  }
  
  release(obj) {
    this.resetFn(obj);
    this.pool.push(obj);
  }
}

// 使用对象池
const particlePool = new ObjectPool(
  () => ({ x: 0, y: 0, vx: 0, vy: 0 }), // 创建函数
  (particle) => { // 重置函数
    particle.x = 0;
    particle.y = 0;
    particle.vx = 0;
    particle.vy = 0;
  }
);

const particle1 = particlePool.acquire();
particle1.x = 10;
particle1.y = 20;

particlePool.release(particle1); // 归还到池中

六、知识延伸与扩展

1. 从原型到现代JavaScript特性

装饰器(Decorators)
javascript 复制代码
// 装饰器示例(需要Babel支持)
function readonly(target, name, descriptor) {
  descriptor.writable = false;
  return descriptor;
}

class Person {
  @readonly
  name = "Alice";
}
代理(Proxy)
javascript 复制代码
// 使用Proxy实现数据绑定
const target = {
  name: "Alice",
  age: 25
};

const proxy = new Proxy(target, {
  get(obj, prop) {
    console.log(`Getting ${prop}`);
    return obj[prop];
  },
  set(obj, prop, value) {
    console.log(`Setting ${prop} to ${value}`);
    obj[prop] = value;
    return true;
  }
});

proxy.name = "Bob"; // Setting name to Bob
console.log(proxy.age); // Getting age \n 25

2. 设计模式与原型

单例模式
javascript 复制代码
class Singleton {
  constructor() {
    if (Singleton.instance) {
      return Singleton.instance;
    }
    
    this.data = [];
    Singleton.instance = this;
  }
  
  addData(item) {
    this.data.push(item);
  }
  
  getData() {
    return this.data;
  }
}

const instance1 = new Singleton();
const instance2 = new Singleton();

console.log(instance1 === instance2); // true
观察者模式
javascript 复制代码
class Subject {
  constructor() {
    this.observers = [];
  }
  
  addObserver(observer) {
    this.observers.push(observer);
  }
  
  removeObserver(observer) {
    const index = this.observers.indexOf(observer);
    if (index > -1) {
      this.observers.splice(index, 1);
    }
  }
  
  notify(data) {
    this.observers.forEach(observer => observer.update(data));
  }
}

class Observer {
  constructor(name) {
    this.name = name;
  }
  
  update(data) {
    console.log(`${this.name} received: ${data}`);
  }
}

// 使用观察者模式
const subject = new Subject();
const observer1 = new Observer("Observer 1");
const observer2 = new Observer("Observer 2");

subject.addObserver(observer1);
subject.addObserver(observer2);

subject.notify("Hello World"); 
// Observer 1 received: Hello World
// Observer 2 received: Hello World

3. 函数式编程与原型

高阶函数与原型方法
javascript 复制代码
// 扩展数组原型以支持函数式编程
Array.prototype.groupBy = function(keyFn) {
  return this.reduce((groups, item) => {
    const key = keyFn(item);
    if (!groups[key]) {
      groups[key] = [];
    }
    groups[key].push(item);
    return groups;
  }, {});
};

const people = [
  { name: "Alice", age: 25, department: "Engineering" },
  { name: "Bob", age: 30, department: "Marketing" },
  { name: "Charlie", age: 35, department: "Engineering" }
];

const grouped = people.groupBy(person => person.department);
console.log(grouped);
// {
//   Engineering: [
//     { name: "Alice", age: 25, department: "Engineering" },
//     { name: "Charlie", age: 35, department: "Engineering" }
//   ],
//   Marketing: [
//     { name: "Bob", age: 30, department: "Marketing" }
//   ]
// }

4. 性能优化与原型

原型方法与实例方法的性能对比
javascript 复制代码
// 性能测试示例
function PerformanceTest() {
  this.instanceMethod = function() {
    return "instance method";
  };
}

PerformanceTest.prototype.prototypeMethod = function() {
  return "prototype method";
};

const test = new PerformanceTest();

// 测试原型方法性能
console.time("Prototype Method");
for (let i = 0; i < 1000000; i++) {
  test.prototypeMethod();
}
console.timeEnd("Prototype Method");

// 测试实例方法性能
console.time("Instance Method");
for (let i = 0; i < 1000000; i++) {
  test.instanceMethod();
}
console.timeEnd("Instance Method");

5. 模块化与原型

ES6模块与类的结合
javascript 复制代码
// person.js
export class Person {
  constructor(name) {
    this.name = name;
  }
  
  greet() {
    return `Hello, I'm ${this.name}`;
  }
}

export class Employee extends Person {
  constructor(name, position) {
    super(name);
    this.position = position;
  }
  
  work() {
    return `${this.name} is working as ${this.position}`;
  }
}

// main.js
import { Person, Employee } from './person.js';

const person = new Person("Alice");
const employee = new Employee("Bob", "Developer");

console.log(person.greet()); // Hello, I'm Alice
console.log(employee.work()); // Bob is working as Developer

6. 错误处理与原型

自定义错误类型
javascript 复制代码
class CustomError extends Error {
  constructor(message, code) {
    super(message);
    this.name = "CustomError";
    this.code = code;
  }
}

class ValidationError extends CustomError {
  constructor(message, field) {
    super(message, "VALIDATION_ERROR");
    this.name = "ValidationError";
    this.field = field;
  }
}

try {
  throw new ValidationError("Invalid email format", "email");
} catch (error) {
  if (error instanceof ValidationError) {
    console.log(`Validation failed for ${error.field}: ${error.message}`);
  }
}

七、面试常见问题与解答

1. 原型链相关问题

Q: 如何判断一个属性是实例属性还是原型属性?

A:

javascript 复制代码
function Person(name) {
  this.name = name;
}

Person.prototype.species = "Homo sapiens";

const person = new Person("Alice");

// 判断方法
console.log(person.hasOwnProperty("name")); // true - 实例属性
console.log(person.hasOwnProperty("species")); // false - 原型属性
console.log("species" in person); // true - 存在于原型链中

Q: instanceof 和 isPrototypeOf 的区别?

A:

javascript 复制代码
function Animal() {}
function Dog() {}

Dog.prototype = Object.create(Animal.prototype);

const dog = new Dog();

console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true
console.log(Animal.prototype.isPrototypeOf(dog)); // true
console.log(Dog.prototype.isPrototypeOf(dog)); // true

2. 继承相关问题

Q: ES6类继承与ES5构造函数继承有什么区别?

A:

javascript 复制代码
// ES5继承
function Parent(name) {
  this.name = name;
}

function Child(name, age) {
  Parent.call(this, name); // 必须手动调用
  this.age = age;
}

Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;

// ES6继承
class ParentES6 {
  constructor(name) {
    this.name = name;
  }
}

class ChildES6 extends ParentES6 {
  constructor(name, age) {
    super(name); // 必须先调用super
    this.age = age;
  }
}

3. 类相关问题

Q: 类的静态方法可以被实例调用吗?

A:

javascript 复制代码
class MyClass {
  static staticMethod() {
    return "static method";
  }
  
  instanceMethod() {
    return "instance method";
  }
}

const instance = new MyClass();

console.log(MyClass.staticMethod()); // "static method"
console.log(instance.instanceMethod()); // "instance method"
// console.log(instance.staticMethod()); // TypeError
相关推荐
T___T4 小时前
Ajax 数据请求详解与实战
javascript·面试
Felix_XXXXL4 小时前
Plugin ‘mysql_native_password‘ is not loaded`
java·后端
onthewaying4 小时前
在Android平台上使用Three.js优雅的加载3D模型
android·前端·three.js
韩立学长4 小时前
【开题答辩实录分享】以《基于SpringBoot在线小说阅读平台》为例进行答辩实录分享
java·spring boot·后端
冴羽4 小时前
能让 GitHub 删除泄露的苹果源码还有 8000 多个相关仓库的 DMCA 是什么?
前端·javascript·react.js
悟能不能悟4 小时前
jsp怎么拿到url参数
java·前端·javascript
程序猿小蒜5 小时前
基于SpringBoot的企业资产管理系统开发与设计
java·前端·spring boot·后端·spring
jzhwolp5 小时前
从基本链表到侵入式链表,体会内核设计思路
c语言·后端·设计模式
Mapmost5 小时前
零代码+三维仿真!实现自然灾害的可视化模拟与精准预警
前端
程序猿_极客5 小时前
JavaScript 的 Web APIs 入门到实战全总结(day7):从数据处理到交互落地的全链路实战(附实战案例代码)
开发语言·前端·javascript·交互·web apis 入门到实战