面试题答案
一键面试注意事项和陷阱
- 作用域问题:
- 函数表达式:在函数表达式内部,
this
的指向可能因调用方式而异。例如在事件回调等场景下,如果不注意,this
可能指向全局对象(在浏览器环境中是window
)而不是预期的对象。 - 命名函数:在严格模式下,命名函数内部
this
默认是undefined
,但在非严格模式下也可能指向全局对象,同样会导致与预期不符的行为。
- 函数表达式:在函数表达式内部,
- 函数提升:
- 命名函数:存在函数提升,即在代码执行前,命名函数就已经被声明提升到当前作用域的顶部。这可能导致在函数定义之前就可以调用函数,容易引起逻辑混淆。例如:
func(); // 可以正常调用,输出 'I am a named function'
function func() {
console.log('I am a named function');
}
- 函数表达式:不存在函数提升,只有在执行到定义它的那一行代码时,函数才被创建。如果在定义之前调用,会抛出
ReferenceError
。例如:
func(); // 抛出 ReferenceError: func is not defined
const func = function() {
console.log('I am a function expression');
};
-
内存管理:
- 命名函数:由于函数提升,可能在不需要使用函数的地方也占用了内存空间,尤其是在大型项目中,如果有大量命名函数被提升但未被充分使用,可能会影响性能。
- 函数表达式:只有在执行到定义语句时才会创建函数,相对更节省内存,但如果频繁定义函数表达式,也可能导致内存开销增加。
-
调试困难:
- 函数表达式:由于没有名称,在调试栈中显示的是
anonymous
,不利于定位问题。例如在捕获到一个函数内部的错误时,调试信息可能只显示anonymous function
,难以直接确定具体的函数位置。 - 命名函数:在调试栈中会显示函数名,方便定位错误发生的具体函数。
- 函数表达式:由于没有名称,在调试栈中显示的是
实际项目场景举例及解决方案
- 场景一:事件处理
- 问题描述:在一个 Web 应用中,有一个按钮,点击按钮时需要发起一个异步请求。如果使用函数表达式作为点击事件的回调,可能会遇到
this
指向问题。
- 问题描述:在一个 Web 应用中,有一个按钮,点击按钮时需要发起一个异步请求。如果使用函数表达式作为点击事件的回调,可能会遇到
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<button id="myButton">Click me</button>
<script>
const myButton = document.getElementById('myButton');
myButton.addEventListener('click', function() {
// 这里的 this 指向 window(非严格模式下)
console.log(this);
async function fetchData() {
try {
const response = await fetch('/api/data');
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Error fetching data:', error);
}
}
fetchData();
});
</script>
</body>
</html>
- 解决方案:可以使用箭头函数来避免
this
指向问题,因为箭头函数没有自己的this
,它的this
继承自外层作用域。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<button id="myButton">Click me</button>
<script>
const myButton = document.getElementById('myButton');
myButton.addEventListener('click', () => {
async function fetchData() {
try {
const response = await fetch('/api/data');
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Error fetching data:', error);
}
}
fetchData();
});
</script>
</body>
</html>
- 场景二:模块封装
- 问题描述:在一个模块中,有多个异步函数相互调用。如果使用命名函数,由于函数提升,可能会导致函数调用顺序混乱,难以维护。
// module.js
function func1() {
func2();
console.log('Function 1 executed');
}
function func2() {
console.log('Function 2 executed');
}
export {func1};
- 解决方案:可以将函数定义放在
export
对象中,这样不会产生函数提升,使代码逻辑更清晰。
// module.js
const func1 = function() {
const func2 = function() {
console.log('Function 2 executed');
};
func2();
console.log('Function 1 executed');
};
export {func1};
- 场景三:调试困难
- 问题描述:在一个复杂的异步操作链中,使用了函数表达式,当出现错误时,调试栈显示
anonymous function
,难以定位问题。
- 问题描述:在一个复杂的异步操作链中,使用了函数表达式,当出现错误时,调试栈显示
async function main() {
const result = await new Promise((resolve, reject) => {
setTimeout(() => {
// 假设这里出现错误
throw new Error('Something went wrong');
}, 1000);
});
console.log(result);
}
main().catch(console.error);
- 解决方案:给函数表达式命名,这样在调试栈中就会显示有意义的函数名,方便定位问题。
async function main() {
const result = await new Promise((resolve, reject) => {
const asyncTask = function() {
setTimeout(() => {
throw new Error('Something went wrong');
}, 1000);
};
asyncTask();
});
console.log(result);
}
main().catch(console.error);