JavaScript 的数组方法非常强大,可以大致分为 修改原数组、返回新数组、遍历/查找 和 其他辅助 四类。
这些方法会直接改变调用它们的数组。
push(): 在末尾添加元素,返回新长度。pop(): 删除末尾元素,返回该元素。shift(): 删除首位元素,返回该元素。unshift(): 在首位添加元素,返回新长度。splice(start, count, ...items): 最万能的方法,可以实现删除、插入和替换。sort(): 对数组排序(默认按字符串 Unicode 码点)。reverse(): 反转数组。这些方法不会改变原数组,而是返回一个处理后的新数组或结果。
map(fn): 对每个元素执行函数,返回由结果组成的新数组。filter(fn): 过滤掉不符合条件的元素,返回新数组。concat(): 合并两个或多个数组。slice(start, end): 浅拷贝数组的一部分。flat(): 将嵌套数组拉平(扁平化)。用于定位元素或判断数组状态。
| 方法 | 描述 |
|---|---|
indexOf() | 返回元素第一次出现的索引,找不到返回 -1。 |
includes() | 判断数组是否包含某个值,返回布尔值。 |
find(fn) | 返回第一个符合条件的元素。 |
findIndex(fn) | 返回第一个符合条件的索引。 |
every(fn) | 是否所有元素都符合条件? |
some(fn) | 是否至少有一个元素符合条件? |
forEach(fn)单纯的遍历,没有返回值。适合用来执行一些副作用(如打印、修改外部变量)。
reduce(fn, init)这是最强大的方法之一,用于将数组减少(归纳)为单一的值(如求和、构建对象)。
注意: 像
map,filter,reduce这种不会改动原数组的方法,在现代前端开发(尤其是 React/Vue)中非常受欢迎,因为它们遵循函数式编程和不可变性的原则。
[...arr] 或 arr.slice()。Array.isArray(obj),而不是 typeof(后者会返回 "object")。明白,一次性列出 100 个详细答案会变成“长篇小说”,为了让你看得轻松且能快速记住,我将这些题目精简为核心干货版。
你可以把这看作是一份“速查小抄”。
既然你已经有了 Vue 的 100 条题目,JavaScript 作为前端开发的底层基石,这 50 条题目将涵盖从基础语法、异步编程、内存模型到 ES6+ 新特性的完整链路。
既然你已经集齐了 JS 和 Vue 的大招,CSS 作为前端的“门面”,自然也不能落下。CSS 面试题现在的趋势是:不再单纯考属性记忆,而是考布局方案、渲染性能和现代新特性。
display:none, visibility:hidden, opacity:0, clip-path)。content-box vs 怪异盒模型 border-box)。box-sizing: border-box)。flex: 1 是哪三个属性的简写?(flex-grow, flex-shrink, flex-basis)。justify-content 和 align-items 的区别?backdrop-filter: blur())。caret-color)。object-fit?::after 伪元素 + transform: scaleY(0.5)。flex-grow: 1(剩余空间撑开)、flex-shrink: 1(空间不足缩放)、flex-basis: 0%(项目基础大小)。typeof null 是 object(历史 Bug);typeof function 是 function。Number(), String(), Boolean()。+ 运算符(字符串拼接)和 == 比较(转为数字)。typeof NaN === 'number',且 NaN !== NaN。=== 类似,但处理了 NaN === NaN 为 true 和 +0 === -0 为 false。toFixed() 或按比例放大成整数。JSON.parse(JSON.stringify()) 实现。var 和 function 声明会提升至作用域顶部,let/const 不会(TDZ)。let/const 声明前访问该变量会报错。new 指向新对象,箭头函数看外层作用域。this、没有 arguments、不能作为构造函数(无 prototype)。map, reduce)。prototype 属性,指向其原型对象。__proto__ 属性,指向其构造函数的 prototype。Object.prototype.__proto__ 是 null。prototype。function* 定义,通过 yield 暂停执行。setTimeout + Promise 嵌套题练一下。var/let/const、call/apply/bind、Map/Object 的异同。如果你需要对其中任何一条(例如第 28 条的继承或第 32 条的 Event Loop)进行代码演示或深度解析,随时告诉我!
Object.defineProperty 拦截 getter/setter。Dep,Setter 中触发 Watcher 更新。this.$set)。:value 和 @input 事件的语法糖。deep: true 用于监听对象内部变化。props / $emit。provide / inject。include/exclude 属性。<component :is="xxx">。name 属性和结束条件。install 方法。hashchange 事件。pushState,需后端配合避免 404。beforeEach, beforeRouteEnter 等。beforeCreate 之前。ref 处理基础值/对象,reactive 只处理对象。value 改为 modelValue。<script setup> 模式下的 API。onMounted, onUpdated 等需从 vue 引入。<style> 中使用 JS 变量。flush: 'post'。createRouter API,取消了 onReady。Vue.nextTick 变为 import { nextTick }。inserted -> mounted)。v-enter 改为 v-enter-from。defineAsyncComponent。performance API 的集成。onErrorCaptured 和 app.config.errorHandler。微信小程序面试题通常围绕着宿主环境、生命周期、双向绑定机制、性能优化、以及与普通 H5 的区别。
wx:if、自带封装组件)。this.setData(),而非 Vue 的 Proxy 拦截)。onLaunch, onShow, onHide, onError)。onLoad, onShow, onReady, onHide, onUnload)。onLoad 和 onShow 的区别?(onLoad 只加载一次,onShow 每次切换页面都会触发)。created, attached, ready, moved, detached)。lifetimes 字段是什么?(推荐的组件生命周期定义方式)。onPullDownRefresh)。onReachBottom)。reLaunch 和 redirectTo 的区别?onLoad 中接收)。properties 传值,triggerEvent 发射事件)。selectComponent)。bind vs catch)。bindtap 和 catchtap 的区别?(catch 会阻止事件冒泡)。globalData 或使用 MobX/Pinia 插件)。options 中的 addGlobalClass 或 styleIsolation)。data-xxx 属性,在 e.currentTarget.dataset 中获取)。setData 的工作原理及其性能瓶颈?(跨线程通信、频繁调用或大数据量会导致卡顿)。recycle-view 虚拟列表)。wx.request,且必须是 HTTPS)。wx.login 获取 code -> 后端换取 openid/session_key)。wx.requestPayment)。wx.navigateToMiniProgram)。onShareAppMessage)。native(微信客户端)中转,用 JSON 进行通信。这也是为什么频繁 setData 会卡,因为“搬运数据”很累。data;不要频繁调用;更新对象时建议只更新变化的路径(如 this.setData({ 'user.age': 18 }))。code -> 后端拿 code 去微信服务器换 openid -> 后端签发自己的 token 给前端 -> 前端存 storage。toString 或 valueOf在 JS 中,当一个对象与一个原始值进行比较或运算时,会触发隐式类型转换。它会尝试调用对象的 valueOf() 或 toString() 方法。
javascriptlet a = {
value: 1,
// 每次被调用时,返回当前值并自增
toString() {
return this.value++;
}
};
if (a == 1 && a == 2) {
console.log("成功了!");
}
a == 1 时,JS 发现 a 是对象,于是调用 a.toString(),得到 1,此时 value 变成 2。下一次执行 a == 2 时,再次调用,得到 2。===(严格相等)下会失效,因为 === 不会触发隐式转换。Object.defineProperty (支持 ===)如果你面试时考官明确要求必须使用 ===,那么你需要利用 数据劫持,在全局对象(浏览器里是 window)上定义一个属性。
javascriptlet _value = 1;
Object.defineProperty(window, 'a', {
get() {
return _value++;
}
});
if (a === 1 && a === 2) {
console.log("严格相等也成功了!");
}
a 时,都会触发 get 拦截器。第一次读取 a 返回 1 并让内部变量加 1,第二次读取自然就变成了 2。shift 方法(针对 ==)数组也是对象,我们可以巧妙利用数组的 toString 默认会调用 join 的特性。
javascriptlet a = [1, 2];
a.join = a.shift; // 将 join 指向 shift
if (a == 1 && a == 2) {
console.log("数组解法成功!");
}
这类题目的核心原理有两个:
Symbol.toPrimitive -> valueOf -> toString 的特性。Proxy 或 ES5 的 Object.defineProperty 劫持成员访问,实现每次读取返回不同值。这类题目虽然在实际开发中是“毒瘤”代码,但面试官喜欢用它来试探你对 JS 底层机制的掌握程度。你还想看看关于 Proxy 如何实现这个效果的代码吗?
这就是 100 条 Vue 重点题! 建议你先通读一遍,把不理解的概念标出来。 需要我针对其中某一条(比如 Vue 3 的 Patch Flag 到底是什么)给你做一个详细的图解或代码示例吗?
一、基础概念与数据类型
基本类型:String、Number、Boolean、null、undefined、Symbol(ES6)、BigInt(ES2020)
引用类型:Object(包括 Array、Function、Date、RegExp 等)
undefined:变量已声明但未赋值
null:表示空值,是一个可以赋给变量的特殊值
typeof null 返回 “object”,这是历史遗留 bug
==:宽松相等,会进行类型转换
===:严格相等,不会进行类型转换
1 == '1' // true 1 === '1' // false AI写代码 javascript 运行 1 2
var 声明的变量会提升到作用域顶部
只有声明会提升,赋值不会提升
let 和 const 存在暂时性死区,不会提升
一、基础概念与数据类型
基本类型:String、Number、Boolean、null、undefined、Symbol(ES6)、BigInt(ES2020)
引用类型:Object(包括 Array、Function、Date、RegExp 等)
undefined:变量已声明但未赋值
null:表示空值,是一个可以赋给变量的特殊值
typeof null 返回 “object”,这是历史遗留 bug
==:宽松相等,会进行类型转换
===:严格相等,不会进行类型转换
1 == '1' // true 1 === '1' // false AI写代码 javascript 运行 1 2
var 声明的变量会提升到作用域顶部
只有声明会提升,赋值不会提升
let 和 const 存在暂时性死区,不会提升
let、const、var 的区别?
什么是作用域链? 答案:
函数在查找变量时,先从自身作用域查找
找不到则向父级作用域查找,直到全局作用域
这种链式查找关系称为作用域链
函数嵌套函数,内部函数可以访问外部函数的变量
外部函数执行完毕后,其变量仍然被内部函数引用
常见用途:私有变量、函数工厂、模块模式
创建私有变量和方法
实现函数柯里化
模块化开发
缺点:
内存泄漏(如果闭包引用不被释放)
性能考虑(每次创建函数都会创建闭包)
JavaScript 采用词法作用域(静态作用域)
作用域在函数定义时就确定了,而不是调用时
与动态作用域相对
二、原型与继承 10. 什么是原型链? 答案:
每个对象都有 proto 属性,指向其构造函数的 prototype
prototype 也是对象,也有 proto,形成链式结构
查找属性时,沿着原型链向上查找
原型链继承:
function Parent() {} function Child() {} Child.prototype = new Parent() AI写代码 javascript 运行 1 2 3 构造函数继承:
function Child() { Parent.call(this) } AI写代码 javascript 运行 1 2 3 组合继承(最常用):
function Child() { Parent.call(this) } Child.prototype = Object.create(Parent.prototype) Child.prototype.constructor = Child AI写代码 javascript 运行 1 2 3 4 5 Class 继承(ES6):
class Child extends Parent { constructor() { super() } } AI写代码 javascript 运行 1 2 3 4 5
创建一个空对象
将空对象的 proto 指向构造函数的 prototype
将 this 指向这个空对象
执行构造函数
如果构造函数返回对象则返回该对象,否则返回新对象
检查右边构造函数的 prototype 是否在左边对象的原型链上
实现原理:
function myInstanceof(left, right) { let proto = Object.getPrototypeOf(left) while (true) { if (proto === null) return false if (proto === right.prototype) return true proto = Object.getPrototypeOf(proto) } } AI写代码 javascript 运行 1 2 3 4 5 6 7 8 三、函数与 this
普通函数调用:this 指向全局对象(严格模式下为 undefined)
方法调用:this 指向调用该方法的对象
构造函数调用:this 指向新创建的实例
call/apply/bind 调用:this 指向第一个参数
箭头函数:this 指向定义时的上下文,不会改变
func.call(thisArg, arg1, arg2, ...) // 参数逐个传递 func.apply(thisArg, [argsArray]) // 参数作为数组传递 func.bind(thisArg, arg1, arg2, ...) // 返回新函数,不立即执行 AI写代码 javascript 运行 1 2 3
箭头函数没有自己的 this,继承外层
箭头函数没有 arguments 对象
箭头函数不能作为构造函数(不能用 new)
箭头函数没有 prototype 属性
箭头函数不能使用 yield,不能用作生成器
接受函数作为参数
或返回一个函数
例如:map、filter、reduce、bind
异步编程
JavaScript 是单线程的,通过事件循环处理异步
任务分为宏任务和微任务
执行顺序:同步代码 → 微任务 → 宏任务
宏任务:setTimeout、setInterval、I/O
微任务:Promise.then、process.nextTick、MutationObserver
pending:初始状态
fulfilled:操作成功完成
rejected:操作失败
状态一旦改变就不会再变
Promise.resolve(value) // 返回 resolved 状态的 Promise Promise.reject(reason) // 返回 rejected 状态的 Promise Promise.all(iterable) // 所有成功才成功,一个失败就失败 Promise.race(iterable) // 第一个改变状态的 Promise 决定结果 Promise.allSettled(iterable) // 所有 Promise 都完成后返回结果数组 AI写代码 javascript 运行 1 2 3 4 5
代码更简洁,类似同步写法
更好的错误处理(可以使用 try-catch)
更容易调试
避免回调地狱
class MyPromise { constructor(executor) { this.state = 'pending' this.value = undefined this.reason = undefined
const resolve = (value) => { if (this.state === 'pending') { this.state = 'fulfilled' this.value = value } } const reject = (reason) => { if (this.state === 'pending') { this.state = 'rejected' this.reason = reason } } try { executor(resolve, reject) } catch (err) { reject(err) } } then(onFulfilled, onRejected) { // 简化实现,实际更复杂 if (this.state === 'fulfilled') { onFulfilled(this.value) } if (this.state === 'rejected') { onRejected(this.reason) } }
} AI写代码 javascript 运行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 四、ES6+ 新特性
// 数组解构 const [a, b] = [1, 2]
// 对象解构 const { name, age } = { name: 'John', age: 30 }
// 函数参数解构 function foo({ x, y }) { return x + y }
// 交换变量 [a, b] = [b, a] AI写代码 javascript 运行
1 2 3 4 5 6 7 8 9 10 11
// 复制数组 const arr2 = [...arr1]
// 合并数组 const arr3 = [...arr1, ...arr2]
// 函数参数 Math.max(...numbers)
// 对象浅拷贝 const obj2 = { ...obj1 } AI写代码 javascript 运行
1 2 3 4 5 6 7 8 9 10 11
// 支持换行
const str = 第一行 第二行
// 支持表达式
const name = 'John'
const greeting = Hello, ${name}!
// 标签模板 function tag(strings, ...values) { // strings: 模板字符串的静态部分 // values: 表达式的值 } AI写代码 javascript 运行
1 2 3 4 5 6 7 8 9 10 11 12 13
创建唯一的值,避免属性名冲突
可用作对象的私有属性
内置 Symbol 值如 Symbol.iterator、Symbol.toStringTag
Set:值的集合,值唯一
Map:键值对的集合,键可以是任意类型
WeakSet:弱引用集合,只能存对象
WeakMap:弱引用键值对,键只能是对象
五、DOM 与 BOM 28. 事件委托是什么? 答案:
将事件监听器绑定到父元素
利用事件冒泡机制处理子元素事件
优点:减少内存消耗,动态元素也能处理
事件冒泡:从目标元素向上传播到根元素
事件捕获:从根元素向下传播到目标元素
DOM 事件流:捕获 → 目标 → 冒泡
addEventListener 第三个参数控制阶段
event.preventDefault() // 阻止默认行为 event.stopPropagation() // 阻止冒泡 event.stopImmediatePropagation() // 阻止同一事件的其他监听器 AI写代码 javascript 运行 1 2 3 六、性能与安全
防抖:事件触发后延迟执行,如果期间再次触发则重新计时
节流:在一定时间内只执行一次
// 防抖 function debounce(fn, delay) { let timer return function(...args) { clearTimeout(timer) timer = setTimeout(() => fn.apply(this, args), delay) } }
// 节流 function throttle(fn, delay) { let lastTime = 0 return function(...args) { const now = Date.now() if (now - lastTime >= delay) { fn.apply(this, args) lastTime = now } } } AI写代码 javascript 运行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
浏览器同源策略限制
解决方案:
JSONP(仅 GET 请求)
CORS(服务器设置响应头)
代理服务器
postMessage
WebSocket AI写代码 bash 1 2 3 4 5 6 7 8 9
跨站脚本攻击,注入恶意脚本
防范措施:
输入过滤和转义
设置 HttpOnly Cookie
使用 CSP(内容安全策略)
避免内联事件处理
跨站请求伪造,诱导用户发送恶意请求
防范措施:
使用 CSRF Token
验证 Referer 头
设置 SameSite Cookie
验证码
七、算法与数据结构 35. 数组去重的方法? 答案:
// 1. Set [...new Set(array)]
// 2. filter + indexOf array.filter((item, index) => array.indexOf(item) === index)
// 3. reduce array.reduce((acc, cur) => acc.includes(cur) ? acc : [...acc, cur], []) AI写代码 javascript 运行 1 2 3 4 5 6 7 8
// 1. flat arr.flat(Infinity)
// 2. reduce + 递归 function flatten(arr) { return arr.reduce((acc, cur) => Array.isArray(cur) ? acc.concat(flatten(cur)) : acc.concat(cur), []) }
// 3. toString(仅限数字数组) arr.toString().split(',').map(Number) AI写代码 javascript 运行
1 2 3 4 5 6 7 8 9 10 11 12 13 14
function deepClone(obj, map = new WeakMap()) { if (obj === null || typeof obj !== 'object') return obj if (map.has(obj)) return map.get(obj)
const clone = Array.isArray(obj) ? [] : {} map.set(obj, clone) for (let key in obj) { if (obj.hasOwnProperty(key)) { clone[key] = deepClone(obj[key], map) } } return clone
} AI写代码 javascript 运行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 八、 浏览器相关
DNS 解析
TCP 连接
发送 HTTP 请求
服务器处理请求并返回响应
浏览器解析渲染
连接结束
重排:布局改变,需要重新计算
重绘:外观改变,不需要重新布局
优化:避免频繁操作 DOM,使用 transform 和 opacity
意外的全局变量
未清理的定时器
闭包滥用
未解绑的事件监听
DOM 引用未清除
九、模块化 41. CommonJS 和 ES6 Module 的区别?
AMD:异步加载,提前执行(RequireJS)
CMD:异步加载,延迟执行(SeaJS)
现在多用 ES6 Module
其他重要概念
将多参数函数转化为单参数函数序列
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)) } } } } AI写代码 javascript 运行
1 2 3 4 5 6 7 8 9 10 11
将多个函数组合成一个新函数
function compose(...fns) { return function(x) { return fns.reduceRight((acc, fn) => fn(acc), x) } } AI写代码 javascript 运行 1 2 3 4 5
函数最后一步调用另一个函数
ES6 严格模式下支持
可以优化递归性能
可以暂停执行和恢复执行
配合 yield 使用
常用于异步编程(async/await 的基础)
function* gen() { yield 1 yield 2 return 3 } AI写代码 javascript 运行 1 2 3 4 5
Proxy:代理对象,拦截操作
Reflect:操作对象的 API,与 Proxy 对应
const proxy = new Proxy(target, { get(target, prop) { return Reflect.get(target, prop) } }) AI写代码 javascript 运行 1 2 3 4 5
弱引用,不影响垃圾回收
键/值必须是对象
不可遍历
没有 size 属性
ES2020 新增,可选链操作符 ?.
避免访问嵌套对象时的空值错误
const name = obj?.user?.name AI写代码 bash 1
ES2020 新增,空值合并操作符 ??
仅在左侧为 null 或 undefined 时返回右侧
const value = a ?? 'default'
一、JavaScript基础(50题)
题目1:JavaScript中有哪些数据类型?如何判断一个变量的类型?
答案:JavaScript中的数据类型分为基本类型和引用类型。基本类型包括:string、number、boolean、null、undefined、symbol、bigint。引用类型包括:object、array、function等。 javascript 体验AI代码助手 代码解读复制代码console.log(typeof 42); // "number" console.log(typeof "hello"); // "string" console.log(typeof null); // "object" console.log(Object.prototype.toString.call([])); // "[object Array]"
题目2:null和undefined的区别是什么?
答案:
undefined表示变量已声明但未赋值。 null表示一个空对象引用,通常用于显式清空变量。
javascript 体验AI代码助手 代码解读复制代码let a; console.log(a); // undefined let b = null; console.log(b); // null
题目3:如何判断一个变量是否是数组?
答案:使用Array.isArray()方法。 javascript 体验AI代码助手 代码解读复制代码console.log(Array.isArray([])); // true console.log(Array.isArray({})); // false
题目4:如何判断一个变量是否是对象?
答案:使用typeof和Object.prototype.toString.call()。 javascript 体验AI代码助手 代码解读复制代码function isObject(obj) { return obj !== null && typeof obj === 'object'; } console.log(isObject({})); // true console.log(isObject([])); // true console.log(isObject(null)); // false
题目5:如何判断一个变量是否是函数?
答案:使用typeof。 javascript 体验AI代码助手 代码解读复制代码console.log(typeof function() {}); // "function"
题目6:如何判断一个变量是否是正则表达式?
答案:使用Object.prototype.toString.call()。 javascript 体验AI代码助手 代码解读复制代码console.log(Object.prototype.toString.call(/abc/) === "[object RegExp]"); // true
题目7:如何判断一个变量是否是日期对象?
答案:使用Object.prototype.toString.call()。 javascript 体验AI代码助手 代码解读复制代码console.log(Object.prototype.toString.call(new Date()) === "[object Date]"); // true
题目8:如何判断一个变量是否是NaN?
答案:使用isNaN()或Number.isNaN()。 javascript 体验AI代码助手 代码解读复制代码console.log(isNaN(NaN)); // true console.log(Number.isNaN(NaN)); // true
题目9:如何判断一个变量是否是有限数?
答案:使用isFinite()。 javascript 体验AI代码助手 代码解读复制代码console.log(isFinite(42)); // true console.log(isFinite(Infinity)); // false
题目10:如何判断一个变量是否是BigInt?
答案:使用typeof。 javascript 体验AI代码助手 代码解读复制代码console.log(typeof 42n); // "bigint"
题目11:什么是作用域链?JavaScript是如何查找变量的?
答案:作用域链是JavaScript中用于查找变量的一种机制。当访问一个变量时,JavaScript引擎会首先在当前作用域中查找,如果找不到,就会沿着作用域链向上查找,直到全局作用域。 javascript 体验AI代码助手 代码解读复制代码let globalVar = "global"; function outer() { let outerVar = "outer"; function inner() { let innerVar = "inner"; console.log(globalVar); // "global" console.log(outerVar); // "outer" console.log(innerVar); // "inner" } inner(); } outer();
题目12:什么是闭包?闭包有哪些应用场景?
答案:闭包是指函数能够访问其词法作用域中的变量,即使函数在其词法作用域之外执行。 javascript 体验AI代码助手 代码解读复制代码function createCounter() { let count = 0; return function() { count++; console.log(count); }; } const counter = createCounter(); counter(); // 1 counter(); // 2
题目13:闭包会导致内存泄漏吗?如何避免?
答案:闭包可能导致内存泄漏,因为闭包会保留对其词法作用域的引用。避免方法包括:
及时清除不再使用的闭包。 使用弱引用(如WeakMap、WeakSet)。
题目14:如何实现一个私有变量?
答案:使用闭包。 javascript 体验AI代码助手 代码解读复制代码function createPrivateVar() { let privateVar = 0; return { get: function() { return privateVar; }, set: function(value) { privateVar = value; } }; } const obj = createPrivateVar(); obj.set(42); console.log(obj.get()); // 42
题目15:什么是立即执行函数表达式(IIFE)?有什么作用?
答案:IIFE是一个定义后立即执行的函数,用于创建独立的作用域。 javascript 体验AI代码助手 代码解读复制代码(function() { let localVar = "local"; console.log(localVar); // "local" })();
题目16:如何实现一个模块模式?
答案:使用IIFE和闭包。 javascript 体验AI代码助手 代码解读复制代码const module = (function() { let privateVar = 0; return { get: function() { return privateVar; }, set: function(value) { privateVar = value; } }; })(); module.set(42); console.log(module.get()); // 42
题目17:如何实现一个单例模式?
答案:使用IIFE和闭包。 javascript 体验AI代码助手 代码解读复制代码const singleton = (function() { let instance; function init() { return { value: 42 }; } return { getInstance: function() { if (!instance) { instance = init(); } return instance; } }; })(); const instance1 = singleton.getInstance(); const instance2 = singleton.getInstance(); console.log(instance1 === instance2); // true
题目18:如何实现一个工厂模式?
答案:使用函数返回对象。
javascript 体验AI代码助手 代码解读复制代码function createPerson(name, age) {
return {
name,
age,
sayHello: function() {
console.log(Hello, my name is ${this.name});
}
};
}
const person = createPerson("Alice", 25);
person.sayHello(); // "Hello, my name is Alice"
题目19:如何实现一个观察者模式?
答案:使用数组存储观察者,并提供订阅和发布方法。
javascript 体验AI代码助手 代码解读复制代码function Subject() {
this.observers = [];
}
Subject.prototype.subscribe = function(observer) {
this.observers.push(observer);
};
Subject.prototype.unsubscribe = function(observer) {
this.observers = this.observers.filter((obs) => obs !== observer);
};
Subject.prototype.notify = function() {
this.observers.forEach((observer) => observer.update());
};
function Observer(name) {
this.name = name;
}
Observer.prototype.update = function() {
console.log(${this.name} received an update);
};
const subject = new Subject();
const observer1 = new Observer("Observer 1");
const observer2 = new Observer("Observer 2");
subject.subscribe(observer1);
subject.subscribe(observer2);
subject.notify(); // "Observer 1 received an update" "Observer 2 received an update"
题目20:如何实现一个发布-订阅模式?
答案:使用对象存储事件和回调函数。
javascript 体验AI代码助手 代码解读复制代码function PubSub() {
this.events = {};
}
PubSub.prototype.subscribe = function(event, callback) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(callback);
};
PubSub.prototype.publish = function(event, data) {
if (this.events[event]) {
this.events[event].forEach((callback) => callback(data));
}
};
const pubsub = new PubSub();
pubsub.subscribe("event1", (data) => {
console.log(Event1 received: ${data});
});
pubsub.publish("event1", "Hello"); // "Event1 received: Hello"
题目21:如何实现JavaScript中的继承?有哪些方式?
答案:JavaScript中的继承可以通过以下几种方式实现:
原型链继承:通过将子类的原型指向父类的实例来实现继承。 构造函数继承:在子类构造函数中调用父类构造函数。 组合继承:结合原型链继承和构造函数继承。 原型式继承:使用Object.create()方法。 寄生式继承:在原型式继承的基础上增强对象。 寄生组合式继承:结合组合继承和寄生式继承,是目前最常用的继承方式。
javascript 体验AI代码助手 代码解读复制代码// 寄生组合式继承
function Parent(name) {
this.name = name;
}
Parent.prototype.sayHello = function() {
console.log(Hello, ${this.name});
};
function Child(name, age) {
Parent.call(this, name);
this.age = age;
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
const child = new Child("Alice", 10);
child.sayHello(); // "Hello, Alice"
题目22:__proto__和prototype的区别是什么?
答案:
prototype是函数对象的一个属性,指向该函数的原型对象。 __proto__是对象实例的一个属性,指向该对象的原型。
javascript 体验AI代码助手 代码解读复制代码function Person() {} const person = new Person(); console.log(person.proto === Person.prototype); // true
题目23:如何实现一个深拷贝函数?
答案: javascript 体验AI代码助手 代码解读复制代码function deepClone(obj) { if (obj === null || typeof obj !== 'object') { return obj; } let clone = Array.isArray(obj) ? [] : {}; for (let key in obj) { if (obj.hasOwnProperty(key)) { clone[key] = deepClone(obj[key]); } } return clone; }
题目24:什么是原型链?如何避免原型链污染?
答案:原型链是JavaScript中对象继承的机制。避免原型链污染的方法包括:
使用Object.create(null)创建无原型的对象。 避免直接修改Object.prototype。
题目25:如何判断一个对象是否是另一个对象的实例?
答案:使用instanceof。 javascript 体验AI代码助手 代码解读复制代码function Person() {} const person = new Person(); console.log(person instanceof Person); // true
二、ES6+新特性(50题)
题目26:let和const与var的区别是什么?
答案:
var存在变量提升,而let和const不会。 var声明的变量属于函数作用域,而let和const属于块级作用域。 const声明的变量必须初始化,且不能重新赋值。
javascript 体验AI代码助手 代码解读复制代码if (true) { var a = 1; let b = 2; const c = 3; } console.log(a); // 1 console.log(b); // ReferenceError console.log(c); // ReferenceError
题目27:什么是暂时性死区(TDZ)?
答案:在块级作用域中,let和const声明的变量在声明之前不可访问,这种现象称为暂时性死区。 javascript 体验AI代码助手 代码解读复制代码console.log(a); // ReferenceError let a = 1;
题目28:如何实现块级作用域?
答案:使用let或const。 javascript 体验AI代码助手 代码解读复制代码{ let a = 1; console.log(a); // 1 } console.log(a); // ReferenceError
题目29:const声明的对象可以修改吗?
答案:可以修改对象的属性,但不能重新赋值。 javascript 体验AI代码助手 代码解读复制代码const obj = { a: 1 }; obj.a = 2; console.log(obj.a); // 2 obj = {}; // TypeError
题目30:如何冻结一个对象?
答案:使用Object.freeze()。 javascript 体验AI代码助手 代码解读复制代码const obj = { a: 1 }; Object.freeze(obj); obj.a = 2; // 静默失败 console.log(obj.a); // 1
题目31:箭头函数与普通函数的区别是什么?
答案:
箭头函数没有自己的this,它的this继承自外层作用域。 箭头函数不能作为构造函数使用,不能使用new关键字。 箭头函数没有arguments对象,但可以使用rest参数。
javascript 体验AI代码助手 代码解读复制代码const obj = { value: 42, getValue: function() { return this.value; }, getValueArrow: () => { return this.value; } }; console.log(obj.getValue()); // 42 console.log(obj.getValueArrow()); // undefined
题目32:箭头函数能否使用yield关键字?
答案:不能,箭头函数不能用作生成器函数。
题目33:如何实现一个柯里化函数?
答案: javascript 体验AI代码助手 代码解读复制代码function curry(fn) { return function curried(...args) { if (args.length >= fn.length) { return fn.apply(this, args); } else { return function(...moreArgs) { return curried.apply(this, args.concat(moreArgs)); }; } }; } const add = (a, b, c) => a + b + c; const curriedAdd = curry(add); console.log(curriedAdd(1)(2)(3)); // 6
题目34:如何实现一个偏函数?
答案: javascript 体验AI代码助手 代码解读复制代码function partial(fn, ...args) { return function(...moreArgs) { return fn.apply(this, args.concat(moreArgs)); }; } const add = (a, b, c) => a + b + c; const partialAdd = partial(add, 1, 2); console.log(partialAdd(3)); // 6
题目35:如何实现一个惰性函数?
答案: javascript 体验AI代码助手 代码解读复制代码function lazyFunction() { let result; return function() { if (result === undefined) { result = computeExpensiveValue(); } return result; }; }
三、异步编程与事件循环(50题)
题目36:什么是Promise?如何实现一个Promise?
答案:Promise是ES6引入的一种异步编程解决方案,用于处理异步操作。 javascript 体验AI代码助手 代码解读复制代码const promise = new Promise((resolve, reject) => { setTimeout(() => { resolve("Success!"); }, 1000); }); promise.then((result) => { console.log(result); // "Success!" });
题目37:async/await与Promise的关系是什么?
答案:async/await是ES8引入的语法糖,用于简化Promise的使用。 javascript 体验AI代码助手 代码解读复制代码async function fetchData() { const response = await fetch("https://api.example.com/data"); const data = await response.json(); console.log(data); } fetchData();
题目38:如何实现一个Promise.all?
答案: javascript 体验AI代码助手 代码解读复制代码function promiseAll(promises) { return new Promise((resolve, reject) => { let results = []; let completed = 0; promises.forEach((promise, index) => { promise.then((result) => { results[index] = result; completed++; if (completed === promises.length) { resolve(results); } }).catch(reject); }); }); }
题目39:如何实现一个Promise.race?
答案: javascript 体验AI代码助手 代码解读复制代码function promiseRace(promises) { return new Promise((resolve, reject) => { promises.forEach((promise) => { promise.then(resolve).catch(reject); }); }); }
题目40:如何处理Promise的错误?
答案:使用catch或try/catch。 javascript 体验AI代码助手 代码解读复制代码promise.catch((error) => { console.error(error); });
async function fetchData() { try { const response = await fetch("https://api.example.com/data"); const data = await response.json(); console.log(data); } catch (error) { console.error(error); } }
题目41:什么是事件循环(Event Loop)?请描述其工作原理。
答案:事件循环是JavaScript运行时处理异步任务的一种机制。 javascript 体验AI代码助手 代码解读复制代码console.log("Start"); setTimeout(() => { console.log("Timeout"); }, 0); Promise.resolve().then(() => { console.log("Promise"); }); console.log("End"); // 输出顺序:Start -> End -> Promise -> Timeout
题目42:什么是宏任务和微任务?请举例说明。
答案:宏任务包括setTimeout、setInterval、I/O操作等,微任务包括Promise回调、MutationObserver等。 javascript 体验AI代码助手 代码解读复制代码console.log("Start"); setTimeout(() => { console.log("Timeout"); }, 0); Promise.resolve().then(() => { console.log("Promise"); }); console.log("End"); // 输出顺序:Start -> End -> Promise -> Timeout
题目43:如何实现一个异步队列?
答案: javascript 体验AI代码助手 代码解读复制代码class AsyncQueue { constructor() { this.queue = []; this.processing = false; } enqueue(task) { this.queue.push(task); if (!this.processing) { this.process(); } } async process() { this.processing = true; while (this.queue.length > 0) { const task = this.queue.shift(); await task(); } this.processing = false; } } const queue = new AsyncQueue(); queue.enqueue(() => console.log("Task 1")); queue.enqueue(() => console.log("Task 2"));
题目44:如何实现一个异步限流器?
答案: javascript 体验AI代码助手 代码解读复制代码class RateLimiter { constructor(limit, interval) { this.limit = limit; this.interval = interval; this.queue = []; this.count = 0; } enqueue(task) { this.queue.push(task); this.run(); } run() { if (this.count < this.limit && this.queue.length > 0) { const task = this.queue.shift(); this.count++; task().finally(() => { this.count--; setTimeout(() => this.run(), this.interval); }); this.run(); } } } const limiter = new RateLimiter(2, 1000); for (let i = 0; i < 5; i++) { limiter.enqueue(() => new Promise((resolve) => setTimeout(resolve, 500))); }
题目45:如何实现一个异步重试机制?
答案: javascript 体验AI代码助手 代码解读复制代码async function retry(fn, retries, delay) { try { return await fn(); } catch (error) { if (retries === 0) throw error; await new Promise((resolve) => setTimeout(resolve, delay)); return retry(fn, retries - 1, delay); } }
四、性能优化(50题)
题目46:什么是防抖(Debounce)和节流(Throttle)?如何实现?
答案:
防抖:在事件触发后,等待一段时间再执行回调。 节流:在一定时间内只执行一次回调。
javascript 体验AI代码助手 代码解读复制代码// 防抖 function debounce(func, wait) { let timeout; return function(...args) { clearTimeout(timeout); timeout = setTimeout(() => func.apply(this, args), wait); }; }
// 节流 function throttle(func, wait) { let lastTime = 0; return function(...args) { const now = Date.now(); if (now - lastTime >= wait) { func.apply(this, args); lastTime = now; } }; }
题目47:如何实现一个带立即执行选项的防抖函数?
答案: javascript 体验AI代码助手 代码解读复制代码function debounce(func, wait, immediate) { let timeout; return function(...args) { const callNow = immediate && !timeout; clearTimeout(timeout); timeout = setTimeout(() => { timeout = null; if (!immediate) func.apply(this, args); }, wait); if (callNow) func.apply(this, args); }; }
题目48:如何实现一个带取消选项的防抖函数?
答案: javascript 体验AI代码助手 代码解读复制代码function debounce(func, wait) { let timeout; function debounced(...args) { clearTimeout(timeout); timeout = setTimeout(() => func.apply(this, args), wait); } debounced.cancel = function() { clearTimeout(timeout); }; return debounced; }
题目49:如何实现一个带尾调用选项的节流函数?
答案: javascript 体验AI代码助手 代码解读复制代码function throttle(func, wait, trailing = true) { let lastTime = 0; let timeout; return function(...args) { const now = Date.now(); if (now - lastTime >= wait) { if (timeout) { clearTimeout(timeout); timeout = null; } func.apply(this, args); lastTime = now; } else if (trailing && !timeout) { timeout = setTimeout(() => { func.apply(this, args); lastTime = Date.now(); timeout = null; }, wait - (now - lastTime)); } }; }
题目50:如何实现一个带取消选项的节流函数?
答案: javascript 体验AI代码助手 代码解读复制代码function throttle(func, wait) { let lastTime = 0; let timeout; function throttled(...args) { const now = Date.now(); if (now - lastTime >= wait) { if (timeout) { clearTimeout(timeout); timeout = null; } func.apply(this, args); lastTime = now; } else if (!timeout) { timeout = setTimeout(() => { func.apply(this, args); lastTime = Date.now(); timeout = null; }, wait - (now - lastTime)); } } throttled.cancel = function() { clearTimeout(timeout); timeout = null; }; return throttled; }
五、综合题目(50题)
题目51:如何实现一个深拷贝函数?
答案: javascript 体验AI代码助手 代码解读复制代码function deepClone(obj) { if (obj === null || typeof obj !== 'object') { return obj; } let clone = Array.isArray(obj) ? [] : {}; for (let key in obj) { if (obj.hasOwnProperty(key)) { clone[key] = deepClone(obj[key]); } } return clone; }
题目52:如何实现一个函数柯里化?
答案: javascript 体验AI代码助手 代码解读复制代码function curry(fn) { return function curried(...args) { if (args.length >= fn.length) { return fn.apply(this, args); } else { return function(...moreArgs) { return curried.apply(this, args.concat(moreArgs)); }; } }; } const add = (a, b, c) => a + b + c; const curriedAdd = curry(add); console.log(curriedAdd(1)(2)(3)); // 6
题目53:如何实现一个函数记忆化(Memoization)?
答案: javascript 体验AI代码助手 代码解读复制代码function memoize(fn) { const cache = new Map(); return function(...args) { const key = JSON.stringify(args); if (cache.has(key)) { return cache.get(key); } const result = fn.apply(this, args); cache.set(key, result); return result; }; } const factorial = memoize((n) => n <= 1 ? 1 : n * factorial(n - 1)); console.log(factorial(5)); // 120
题目54:如何实现一个函数组合(Compose)?
答案: javascript 体验AI代码助手 代码解读复制代码function compose(...fns) { return function(x) { return fns.reduceRight((acc, fn) => fn(acc), x); }; } const add1 = (x) => x + 1; const mul2 = (x) => x * 2; const addThenMul = compose(mul2, add1); console.log(addThenMul(5)); // 12
题目55:如何实现一个函数管道(Pipe)?
答案: javascript 体验AI代码助手 代码解读复制代码function pipe(...fns) { return function(x) { return fns.reduce((acc, fn) => fn(acc), x); }; } const add1 = (x) => x + 1; const mul2 = (x) => x * 2; const addThenMul = pipe(add1, mul2); console.log(addThenMul(5)); // 12


本文作者:糀飞
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!