面试题答案
一键面试原型链和作用域链的协同工作
- 原型链:在JavaScript中,每个对象都有一个
[[Prototype]]
(可通过__proto__
访问,ES6后推荐使用Object.getPrototypeOf()
)属性,它指向该对象的原型对象。当访问对象的属性或方法时,如果对象本身没有该属性或方法,JavaScript会沿着原型链向上查找,直到找到该属性或方法,或者到达原型链的顶端(null
)。原型链主要用于实现继承,通过将一个对象的原型设置为另一个对象,子对象可以访问父对象的属性和方法。 - 作用域链:作用域链是在函数执行上下文创建时构建的。每个函数都有自己的作用域,当函数被调用时,会创建一个执行上下文。执行上下文包含变量对象(VO,函数上下文中是活动对象AO)、作用域链(scope chain)和
this
值。作用域链是由当前执行上下文的变量对象和所有父执行上下文的变量对象组成的链表。当在函数内部查找变量时,JavaScript会首先在当前执行上下文的变量对象中查找,如果找不到,会沿着作用域链向上查找,直到全局执行上下文。 - 协同工作:在多层嵌套函数且涉及原型继承和闭包的场景下,原型链主要负责对象属性和方法的查找,而作用域链负责变量的查找。例如,当在一个内部函数中访问一个变量时,首先在该函数的作用域链中查找;如果访问的是一个对象的方法,会在该对象的原型链中查找。
作用域链与原型链混淆导致的错误及避免方法
- 错误示例:
function Parent() {
this.value = 10;
}
Parent.prototype.getVal = function() {
return this.value;
};
function Child() {
let value = 20;
// 错误:这里本意是调用原型上的getVal方法获取Parent的value,但由于this指向问题和作用域链混淆,导致错误
console.log(this.getVal());
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
let child = new Child();
在上述代码中,Child
函数内部的this.getVal()
调用本意是获取Parent
原型上的getVal
方法并返回Parent
的value
。但由于Child
函数内部this
指向问题(如果不是严格模式,在函数调用时this
指向全局对象或调用该函数的对象,这里不是预期的Child
实例),并且函数内部存在局部变量value
,导致作用域链和原型链混淆,实际执行时可能会出现TypeError
(如果this
不是预期的对象,this.getVal
可能是undefined
)或者返回错误的值。
2. 避免方法:
- 正确使用this
:可以使用bind
、call
或apply
方法明确指定this
的指向。例如在Child
函数内部,可以改为console.log(Parent.prototype.getVal.call(this));
,这样明确指定getVal
方法中的this
指向Child
的实例。
- 注意变量命名:避免在局部作用域中定义与原型链上属性同名的变量,防止作用域链查找干扰原型链查找。如上述Child
函数中,可以将局部变量value
改为其他名字,减少混淆的可能性。