MST

星途 面试题库

面试题:JavaScript 原型与继承的底层机制及优化

深入探讨 JavaScript 引擎在处理原型与继承时的底层机制,包括原型对象的创建、查找过程以及内存管理。并结合实际项目经验,谈谈如何对基于原型继承的代码进行性能优化,避免可能出现的内存泄漏问题。
33.4万 热度难度
编程语言JavaScript

知识考点

AI 面试

面试题答案

一键面试

1. 原型对象的创建

在 JavaScript 中,每个函数都有一个 prototype 属性,这个属性是一个对象,它就是通过该函数创建的实例的原型对象。例如:

function Person() {}
// Person.prototype 就是原型对象

当使用 new 关键字调用构造函数时,会创建一个新对象,这个新对象的内部 [[Prototype]] (在现代 JavaScript 中可以通过 __proto__ 访问)会被设置为构造函数的 prototype 属性。

2. 查找过程

当访问一个对象的属性时,JavaScript 引擎首先会在对象自身上查找该属性。如果没有找到,就会沿着 [[Prototype]] 链向上查找,直到找到该属性或者到达原型链的顶端(Object.prototype,其 [[Prototype]]null)。例如:

function Animal() {}
Animal.prototype.speak = function() {
  console.log('I can speak');
};

function Dog() {}
Dog.prototype = Object.create(Animal.prototype);

let myDog = new Dog();
myDog.speak(); // 首先在 myDog 自身找 speak 方法,没找到,沿原型链到 Dog.prototype,再到 Animal.prototype 找到并执行

3. 内存管理

  • 对象与原型的引用关系:对象通过 [[Prototype]] 引用其原型对象,而原型对象可能又引用其他对象,形成复杂的引用链。当一个对象不再被任何外部引用时,它以及其原型链上不再被引用的部分会被垃圾回收机制回收。
  • 循环引用:如果在原型链或者对象之间形成循环引用,垃圾回收机制可能无法正确回收相关内存,导致内存泄漏。

4. 性能优化与避免内存泄漏

  • 减少不必要的原型链查找:尽量将频繁访问的属性和方法定义在对象自身,而不是依赖原型链查找。例如,在频繁调用的方法上,直接在构造函数内定义而不是放在原型上。
function Person(name) {
  this.name = name;
  // 频繁调用的方法直接定义在构造函数内
  this.getInfo = function() {
    return `Name: ${this.name}`;
  };
}
  • 事件绑定与解除:在实际项目中,经常会给 DOM 元素绑定事件。如果使用基于原型继承的对象作为事件处理函数的上下文,在 DOM 元素被移除时,要确保解除事件绑定,否则会导致内存泄漏。例如:
<button id="myButton">Click me</button>
<script>
function ButtonHandler() {}
ButtonHandler.prototype.handleClick = function() {
  console.log('Button clicked');
};

let button = document.getElementById('myButton');
let handler = new ButtonHandler();
button.addEventListener('click', handler.handleClick.bind(handler));

// 当按钮被移除时,解除事件绑定
button.parentNode.removeChild(button);
button.removeEventListener('click', handler.handleClick.bind(handler));
</script>
  • 合理使用闭包:如果在原型方法中使用闭包,要注意闭包可能会持有对外部变量的引用,导致相关对象无法被回收。尽量在合适的时候释放闭包引用的变量。
  • 模块化与作用域:采用模块化开发,合理控制变量的作用域,避免全局变量的滥用,减少意外的引用导致内存泄漏。