面试题答案
一键面试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>
- 合理使用闭包:如果在原型方法中使用闭包,要注意闭包可能会持有对外部变量的引用,导致相关对象无法被回收。尽量在合适的时候释放闭包引用的变量。
- 模块化与作用域:采用模块化开发,合理控制变量的作用域,避免全局变量的滥用,减少意外的引用导致内存泄漏。