以下为JavaScript中级篇面试考察点总结,具体知识点不会太详细,主要梳理面试核心考察点,为面试做准备。中级JavaScript开发者被期望具备独立解决复杂问题的能力,并对语言的内在机制有深刻的理解。
- 2025前端面试题-JS基础篇
- 2025前端面试题-TS理论篇
- 2025前端面试题-TS实战篇
- 2025前端面试题-Vue3基础篇
- 2025前端面试题-Vue3进阶篇
- 2025前端面试题-React基础篇
- 2025前端面试题-React进阶篇
- 2025前端面试题-React高阶篇
一、 this 指向
四大绑定规则
-
默认绑定 : 独立函数调用,非严格模式下指向全局对象 (
window),严格模式下为undefined。 -
隐式绑定 : 作为对象方法调用,
this指向该对象。 -
显式绑定 : 通过
.call(),.apply(),.bind()强制指定this。 -
new 绑定 : 通过
new调用构造函数,this指向新创建的实例。
箭头函数的 this
箭头函数不创建自己的 this,它会捕获其定义时所在的词法作用域的 this 值。
代码示例:
js
const name = 'window';
const person = {
name: 'Mickey',
regularFunc: function() {
setTimeout(function() {
// 回调函数独立调用,适用默认绑定
console.log('regularFunc this:', this.name); // 'window'
}, 100);
},
arrowFunc: function() {
setTimeout(() => {
// 箭头函数捕获了 arrowFunc 这个方法的 this,即 person 对象
console.log('arrowFunc this:', this.name); // 'Mickey'
}, 100);
}
};
person.regularFunc();
person.arrowFunc();
二、 原型与原型链
这是JavaScript实现继承和属性共享的核心机制。
-
prototype: 每个函数都拥有的属性,指向一个对象,用于存放实例需要共享的方法和属性。 -
__proto__: 每个对象 都拥有的属性,指向其构造函数的prototype。 -
原型链 : 当访问一个对象的属性时,引擎会先在对象自身查找,若未找到,则沿着
__proto__链向上查找,直至链的顶端null。
代码示例 (ES5继承):
js
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() { console.log('Animal speaks'); };
function Dog(name) {
// 1. 借用构造函数继承属性
Animal.call(this, name);
}
// 2. 核心:创建Animal.prototype的副本,作为Dog.prototype
Dog.prototype = Object.create(Animal.prototype);
// 3. 修复constructor指向
Dog.prototype.constructor = Dog;
const myDog = new Dog('Buddy');
myDog.speak(); // 'Animal speaks' - 继承自Animal.prototype
console.log(myDog instanceof Animal); // true
三、 作用域与闭包
作用域
定义了变量和函数的可访问范围。
-
全局作用域: 在代码任何地方都可访问。
-
函数作用域: 变量在声明它们的函数内部及子函数内部可访问。
-
块级作用域 (ES6) : 由
let和const声明的变量,只在{}代码块内可访问。
闭包
指一个函数能够访问并操作其词法作用域(定义时的作用域)中的变量,即使该函数在其词法作用域之外被执行。
闭包的应用 (数据封装):
js
function createCounter() {
let count = 0; // 私有变量,外部无法直接访问
return {
increment: function() {
count++;
},
getValue: function() {
return count;
}
};
}
const counter = createCounter();
counter.increment();
counter.increment();
console.log(counter.getValue()); // 2
// console.log(counter.count); // undefined, 无法直接访问
四、 异步编程 (Promise, async/await)
这是处理非阻塞操作的核心,事件循环 (Event Loop) 是其底层机制。
Promise
一个代表了异步操作最终完成或失败的对象。它有三种状态:
-
pending(进行中) -
fulfilled(已成功) -
rejected(已失败)。
async/await
-
Promise的语法糖,允许以同步的方式编写异步代码,极大提升了可读性。 -
await必须在async函数内部使用,它会暂停async函数的执行,等待Promise解决。
代码示例 (事件循环):
js
console.log('1. Script Start'); // 同步任务
setTimeout(() => {
console.log('5. setTimeout (Macro-task)'); // 宏任务
}, 0);
new Promise((resolve) => {
console.log('2. Promise Constructor'); // 同步任务
resolve();
}).then(() => {
console.log('4. Promise.then (Micro-task)'); // 微任务
});
async function main() {
console.log('3. async function'); // 同步任务
await Promise.resolve(); // await后面的代码被放入微任务队列
console.log('6. After await (Micro-task 2)'); // 微任务
}
main();
// 最终输出: 1, 2, 3, 4, 6, 5
五、 高阶函数
一个函数如果接收函数作为参数,或将函数作为返回值,那么它就是高阶函数。
-
常见内置高阶函数 :
Array.prototype.map,filter,reduce。 -
自定义高阶函数 (函数柯里化 Currying 示例) :
js
// 一个简单的加法函数
function add(x, y) {
return x + y;
}
// 柯里化转换器
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
} else {
return function(...args2) {
return curried.apply(this, args.concat(args2));
}
}
};
}
const curriedAdd = curry(add);
const addFive = curriedAdd(5); // 返回一个新函数,等待接收第二个参数
console.log(addFive(3)); // 8
console.log(curriedAdd(5)(3)); // 8
六、 ES6+ 新特性
1. 解构赋值 (Destructuring Assignment)
解构赋值语法是一种JavaScript表达式,可以将数组中的值或对象中的属性解包成独立的变量。
代码示例:
js
// --- 对象解构 ---
const user = {
id: 42,
is_verified: true,
profile: {
name: 'Mickey',
age: 30
}
};
// 基础解构
const { id, is_verified } = user;
console.log(id); // 42
console.log(is_verified); // true
// 解构并重命名变量
const { id: userID } = user;
console.log(userID); // 42
// 解构不存在的属性并提供默认值
const { last_login = 'N/A' } = user;
console.log(last_login); // 'N/A'
// 深度嵌套解构
const { profile: { name, age } } = user;
console.log(name); // 'Mickey'
console.log(age); // 30
// --- 数组解构 ---
const rgb = [255, 204, 0];
// 基础解构
const [red, green, blue] = rgb;
console.log(red); // 255
// 忽略某些元素
const [r, , b] = rgb;
console.log(r); // 255
console.log(b); // 0
2. 展开/剩余语法
... 语法根据其使用场景,既可以作为"展开语法",也可以作为"剩余语法"。
代码示例:
js
// --- 展开语法 (Spread) ---
// 用于数组:克隆与合并
const arr1 = ['a', 'b'];
const arr2 = [...arr1, 'c', 'd']; // ['a', 'b', 'c', 'd']
const arrClone = [...arr1]; // 创建一个 arr1 的浅拷贝
// 用于对象 (ES2018): 克隆与合并
const obj1 = { x: 1, y: 2 };
const obj2 = { ...obj1, z: 3 }; // { x: 1, y: 2, z: 3 }
// 用于函数调用:将数组元素作为独立参数传入
function sum(x, y, z) {
return x + y + z;
}
const numbers = [1, 2, 3];
console.log(sum(...numbers)); // 6
// --- 剩余语法 (Rest) ---
// 用于函数参数:将不确定的参数收集到一个数组中
function logArguments(firstArg, ...restOfArgs) {
console.log('第一个参数:', firstArg);
console.log('其余参数:', restOfArgs); // restOfArgs 是一个真数组
}
logArguments('hello', 'world', 123, true);
// 输出:
// 第一个参数: hello
// 其余参数: [ 'world', 123, true ]
// 用于数组和对象解构
const [first, ...rest] = [10, 20, 30, 40];
console.log(first); // 10
console.log(rest); // [20, 30, 40]
3. Map/Set 数据结构
-
Map: 键值对的集合,键可以是任意类型的值。解决了传统对象键必须是字符串或Symbol的限制。 -
Set: 值的集合,其中每个值都必须是唯一的。
代码示例:
js
// --- Set ---
// 允许存储任何类型的唯一值,无论是原始值或者是对象引用。
const mySet = new Set();
mySet.add(1);
mySet.add(5);
mySet.add(5); // 重复添加,将被忽略
mySet.add('some text');
const o = {a: 1, b: 2};
mySet.add(o);
console.log(mySet.has(1)); // true
mySet.delete(5);
console.log(mySet.size); // 3
// Set 的一个主要用途是数组去重
const arrayWithDuplicates = [1, 2, 3, 2, 1, 4];
const uniqueArray = [...new Set(arrayWithDuplicates)];
console.log(uniqueArray); // [1, 2, 3, 4]
// --- Map ---
// 键可以是任意类型
const myMap = new Map();
const keyString = 'a string';
const keyObj = {};
const keyFunc = function() {};
myMap.set(keyString, "value associated with 'a string'");
myMap.set(keyObj, 'value associated with keyObj');
myMap.set(keyFunc, 'value associated with keyFunc');
console.log(myMap.get(keyString)); // "value associated with 'a string'"
console.log(myMap.get(keyObj)); // "value associated with keyObj"
console.log(myMap.size); // 3
// Map 的迭代
for (const [key, value] of myMap) {
console.log(`${String(key)} = ${value}`);
}
七、 性能优化基础
1. 防抖 (Debounce)
防抖的核心思想是:在事件被触发n秒后再执行回调,如果在这n秒内事件又被触发,则重新计时。这适用于只需响应用户最终操作状态的场景,如输入框搜索联想。
代码示例:
js
/**
* 防抖函数
* @param {Function} func - 需要执行的回调函数
* @param {number} delay - 延迟时间 (ms)
* @returns {Function} - 经过防抖处理的新函数
*/
function debounce(func, delay) {
let timerId; // 通过闭包维持 timerId
return function(...args) {
// 保存函数调用时的 this 上下文和参数
const context = this;
// 如果存在定时器,则清除它,实现重新计时
clearTimeout(timerId);
// 设置新的定时器
timerId = setTimeout(() => {
// 使用 apply 将保存的上下文和参数传递给原函数
func.apply(context, args);
}, delay);
};
}
// --- 使用场景 ---
// HTML: <input type="text" id="searchInput" />
const searchInput = document.getElementById('searchInput');
function handleSearch(event) {
// 这里的 this 指向 searchInput 元素
console.log(`Searching for: ${this.value}...`);
// 在这里可以发起API请求
}
// 将 handleSearch 函数进行防抖处理,延迟500ms执行
const debouncedSearch = debounce(handleSearch, 500);
// 绑定事件
searchInput.addEventListener('input', debouncedSearch);
2. 节流 (Throttle)
节流的核心思想是:在规定时间内,事件处理函数最多执行一次。这适用于需要以固定频率响应的场景,如scroll滚动加载、resize窗口调整。
代码示例 (定时器版):
js
/**
* 节流函数
* @param {Function} func - 需要执行的回调函数
* @param {number} limit - 时间间隔 (ms)
* @returns {Function} - 经过节流处理的新函数
*/
function throttle(func, limit) {
let inThrottle = false; // 节流阀状态
return function(...args) {
const context = this;
if (!inThrottle) {
// 当节流阀关闭时,执行函数
func.apply(context, args);
// 打开节流阀
inThrottle = true;
// 在 limit 时间后,关闭节流阀,允许下一次执行
setTimeout(() => {
inThrottle = false;
}, limit);
}
};
}
// --- 使用场景 ---
// HTML: <div id="scroll-container" style="height: 300px; overflow: auto;">...内容...</div>
const container = document.getElementById('scroll-container');
function onScroll() {
console.log('Scroll event processed at', new Date().toLocaleTimeString());
}
// 每1秒最多处理一次 scroll 事件
const throttledScroll = throttle(onScroll, 1000);
container.addEventListener('scroll', throttledScroll);
八、 跨域
1. CORS (Cross-Origin Resource Sharing)
CORS 是目前跨域解决方案的标准。它需要后端服务器进行配置。
前端代码 (非常简单):
前端的 fetch 请求与普通请求无异。浏览器会自动处理CORS相关的握手(如发送Preflight请求)。
js
// 前端向 http://api.example.com 发起请求
// 假设前端页面位于 http://app.mydomain.com
fetch('http://api.example.com/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Custom-Header': 'MyValue' // 自定义请求头会触发Preflight
},
body: JSON.stringify({ name: 'Mickey' })
})
.then(response => {
if (!response.ok) throw new Error('Network response was not ok');
return response.json();
})
.then(data => console.log(data))
.catch(error => console.error('CORS Error:', error));
后端代码 (Node.js/Express 示例):
关键在于服务器响应中添加的 Access-Control-* HTTP头。
js
const express = require('express');
const cors = require('cors'); // 使用 cors 中间件会更方便
const app = express();
// --- 手动设置CORS头 (原理) ---
// app.use((req, res, next) => {
// // 允许来自 http://app.mydomain.com 的请求
// res.setHeader('Access-Control-Allow-Origin', 'http://app.mydomain.com');
// // 允许的HTTP方法
// res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
// // 允许的请求头
// res.setHeader('Access-Control-Allow-Headers', 'X-Custom-Header, Content-Type');
// // 允许携带凭证(如cookies)
// res.setHeader('Access-Control-Allow-Credentials', true);
// next();
// });
// --- 使用 cors 中间件 (推荐) ---
const corsOptions = {
origin: 'http://app.mydomain.com',
methods: "GET,POST",
allowedHeaders: "Content-Type,X-Custom-Header",
credentials: true
};
app.use(cors(corsOptions));
// 路由处理
app.post('/data', (req, res) => {
res.json({ message: 'CORS request successful!' });
});
app.listen(3000, () => console.log('API server listening on port 3000'));
2. JSONP (JSON with Padding)
JSONP是一种"hack"方法,利用 <script> 标签不受同源策略限制的特点。它只支持GET请求。
前端代码:
js
/**
* JSONP 动态实现
* @param {string} url - 请求的URL (不含callback参数)
* @param {string} callbackParamName - 服务端接收的回调函数名参数 (通常是'callback')
*/
function jsonpRequest(url, callbackParamName = 'callback') {
return new Promise((resolve, reject) => {
const callbackName = `jsonp_callback_${Date.now()}_${Math.floor(Math.random() * 100000)}`;
// 1. 在全局(window)上创建一个函数,用于接收服务端返回的数据
window[callbackName] = function(data) {
// 4. 清理工作:移除script标签和全局函数
document.body.removeChild(script);
delete window[callbackName];
// 5. Promise 成功
resolve(data);
};
// 2. 创建 script 标签
const script = document.createElement('script');
const separator = url.includes('?') ? '&' : '?';
script.src = `${url}${separator}${callbackParamName}=${callbackName}`;
script.onerror = () => {
// 错误处理
reject(new Error('JSONP request failed.'));
document.body.removeChild(script);
delete window[callbackName];
};
// 3. 将 script 标签插入DOM,浏览器会自动发起GET请求
document.body.appendChild(script);
});
}
// --- 使用场景 ---
jsonpRequest('http://api.example.com/jsonp-data')
.then(data => {
console.log('JSONP Response:', data);
});
// 服务端(api.example.com)的响应内容应为:
// jsonp_callback_1678886400000_12345({"message": "This is a JSONP response"});
3. 代理服务器 (Proxy Server)
代理是目前大型项目中处理跨域最常用、最安全灵活的方案。
前端代码:
前端请求的是同源的后端代理接口,而不是直接请求第三方API。
javascript
// 前端页面位于 http://app.mydomain.com
// 请求同源的后端代理接口 /api/weather
fetch('/api/weather?city=Shanghai')
.then(response => response.json())
.then(data => {
console.log('Weather data from proxy:', data);
})
.catch(error => console.error('Proxy request failed:', error));
后端代理服务器代码 (Node.js/Express 示例):
js
const express = require('express');
const axios = require('axios'); // 使用axios等库方便地在后端发起HTTP请求
const app = express();
// 定义代理接口 /api/weather
app.get('/api/weather', async (req, res) => {
try {
const { city } = req.query;
const apiKey = 'YOUR_THIRD_PARTY_API_KEY';
const externalApiUrl = `https://api.weatherapi.com/v1/current.json?key=${apiKey}&q=${city}`;
// 关键:由后端服务器发起对第三方API的请求
console.log(`Proxying request to: ${externalApiUrl}`);
const apiResponse = await axios.get(externalApiUrl);
// 将从第三方API获取的数据原样返回给前端
res.status(apiResponse.status).json(apiResponse.data);
} catch (error) {
// 错误处理
console.error('Proxy Error:', error.message);
res.status(error.response?.status || 500).json({ message: 'Failed to fetch data from external API.' });
}
});
app.listen(8080, () => console.log('App server with proxy running on port 8080'));
原理:
- 浏览器(客户端) <=> 同源后端服务器 <=> 第三方API服务器。
- 浏览器与后端服务器之间通信遵守同源策略,而后端服务器之间的HTTP请求不受浏览器同源策略的限制。
九、 浏览器兼容性
-
Babel: 将ES6+代码转换为向后兼容的ES5代码。
-
Polyfill : 为旧浏览器提供缺失的原生API的实现(例如
Promise,Array.from)。core-js是最常用的Polyfill库。 -
CSS Autoprefixer: 自动为CSS规则添加浏览器厂商前缀。
-
caniuse.com: 查询特定Web技术在各浏览器版本中兼容性的权威网站。
十、 正则表达式
用于字符串的模式匹配,是处理文本的强大工具。
常用方法:
test()(检查是否匹配)match()(获取匹配结果)replace()(查找并替换)。
示例 (简单邮箱验证) :
js
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$/;
const email1 = 'test@example.com';
const email2 = 'invalid-email';
console.log(emailRegex.test(email1)); // true
console.log(emailRegex.test(email2)); // false
以上是JS中级篇面试考察点的内容,如有错误欢迎评论区指正。