一、基础概念
- 变量声明方式的区别
var:函数作用域,存在变量提升和暂时性死区。let:块作用域,无变量提升,支持结构化声明。const:块作用域,声明后不可修改,必须初始化。
- JavaScript基本数据类型
- 简单类型(原始类型):
Number,String,Boolean,Undefined,Null,Symbol(ES6新增)。 - 复合类型(引用类型):
Object,Array,Function,Date,RegExp等。
- 简单类型与复合类型的区别
- 简单类型:值直接存储在变量中,不可变。
- 复合类型:存储的是指向堆内存对象的引用,可变。
nullvsundefined
null:表示空指针,主动赋值的结果。undefined:未初始化的变量或不存在的属性,默认值。- 判断:
null == undefined(真),但===(假)。
==vs===
==:类型不同会强制转换后再比较。===:严格比较类型和值。- 使用场景:
===优先,避免类型转换陷阱。
- Hoisting(变量提升)
- 变量声明会被提前到作用域顶部,但初始化不会。
- 示例:
console.log(a); // undefined(变量提升) var a = 1;
this的指向规则
- 全局作用域:指向全局对象(浏览器为
window)。 - 函数调用:普通函数指向全局,严格模式指向
undefined。 - 对象方法:指向调用该方法的对象。
- 箭头函数:无自己的
this,继承自父作用域。
- 箭头函数与普通函数的区别
- 语法简洁,无
function关键字。 - 没有
this绑定(继承父作用域)。 - 不能作为构造函数,没有
arguments对象。
callee和caller的作用
callee:指向当前函数的引用(arguments.callee)。caller:指向调用当前函数的上下文对象。- 示例:
function foo() { console.log(arguments.callee === foo); // true }
二、函数与作用域
- 函数的定义与调用
- 定义:
function,箭头函数,表达式,方法。 - 调用:直接调用、作为参数传递、事件绑定。
- 闭包的定义与应用
- 闭包:函数内部访问外部作用域的变量形成的环境。
- 应用:数据封装、模块化、私有变量。
- 作用域链与原型链
- 作用域链:查找变量的路径(当前作用域 → 父作用域 → ... → 全局)。
- 原型链:对象继承的链条(
__proto__指向父原型)。
-
绑定多个
onclick事件// 方式1:直接赋值(覆盖) btn.onclick = function() { /* 第一个事件 */ }; btn.onclick = function() { /* 第二个事件 */ }; // 方式2:使用addEventListener(支持多事件) btn.addEventListener('click', handler1); btn.addEventListener('click', handler2); -
异步函数与同步函数
- 同步:代码按顺序执行,阻塞主线程。
- 异步:非阻塞,通过回调、Promise、async/await实现。
三、面向对象与原型
- 原型与原型链原理
- 每个对象都有一个
__proto__属性,指向其原型对象。 - 原型链:通过
__proto__逐级查找属性和方法。
- 继承实现方式
- ES5:原型链继承(
Object.create())和构造函数继承。 - ES6:
class语法糖(基于原型链)。
- ES5与ES6继承区别
- ES5:手动管理原型链,易出错(如共享引用)。
- ES6:
class关键字和super关键字,语法更清晰。
instanceof原理及手动实现
- 原理:检查对象的原型链是否包含构造函数的
prototype。 - 手动实现:
function myInstanceof(obj, constructor) { while (obj !== null) { obj = obj.__proto__; if (obj === constructor.prototype) return true; obj = obj.__proto__; } return false; }
-
new操作符的执行过程 -
创建新对象,
this指向它。 -
执行构造函数代码,初始化属性。
-
新对象的
__proto__指向构造函数的prototype。 -
JavaScript面向对象特点
- 基于原型的动态继承。
- 函数是一等公民(可作为值传递)。
- 多态通过原型链实现。
四、异步编程与事件循环
- 异步实现方式
- 回调函数、Promise、Generator、async/await。
-
Promise手写实现
class MyPromise { constructor(executor) { this.state = 'pending'; this.value = undefined; this.handlers = []; executor(this.resolve.bind(this), this.reject.bind(this)); } resolve(val) { if (this.state === 'pending') { this.state = 'fulfilled'; this.value = val; this.handlers.forEach(h => h.onFulfilled(val)); } } reject(err) { if (this.state === 'pending') { this.state = 'rejected'; this.value = err; this.handlers.forEach(h => h.onRejected(err)); } } then(onFulfilled, onRejected) { return new MyPromise((resolve, reject) => { this.handlers.push({ onFulfilled: (val) => { try { resolve(onFulfilled(val)); } catch (e) { reject(e); } }, onRejected: (err) => { try { reject(onRejected(err)); } catch (e) { reject(e); } } }); }); } } -
async/await原理
async函数返回Promise,await暂停函数执行直到Promise解决。- 实现基于Generator和Promise的组合。
- 事件循环机制
- 宏任务(setTimeout、setInterval、I/O)→ 微任务(Promise.then、async/await)→ 渲染。
- 定时器执行差异
console.log(1); // 同步输出 setTimeout(() => console.log(2), 0); // 宏任务 Promise.resolve().then(() => console.log(3)); // 微任务 console.log(4); // 同步输出 // 输出顺序:1 → 4 → 3 → 2
五、DOM与事件
- 事件冒泡与捕获
- 冒泡:事件从目标节点向上触发到根节点。
- 捕获:事件从根节点向下触发到目标节点。
- 阻止冒泡:
event.stopPropagation()。
- 不冒泡的事件
focus,blur,mouseenter,mouseleave,scroll等。
mouseEntervsmouseOver
mouseenter:进入元素时触发(不冒泡)。mouseOver:进入或离开子元素时都可能触发。
- DOM操作方法
- 查询:
document.querySelector(),getElementById() - 添加:
appendChild(),insertBefore() - 删除:
removeChild(),remove() - 修改:
textContent,setAttribute()
- 事件委托实现
// 传统事件委托 document.body.addEventListener('click', function(e) { if (e.target.matches('.btn')) { console.log('按钮被点击'); } });
六、数据操作与存储
- 深拷贝与浅拷贝
- 浅拷贝:
Object.assign(),Array.slice() - 深拷贝:
function deepCopy(obj) { return JSON.parse(JSON.stringify(obj)); }
- 数组去重方法
Set:new Set(arr)→[...new Set(arr)]filter:arr.filter((item, index) => index === firstIndex(item))
-
解构赋值成功条件 需要右侧是可迭代对象(如数组、字符串、Map等):
var [a, b] = { a: 1, b: 2 }; // 需要先转换为数组或类似结构 -
存储机制区别
cookie:小数据量,每次HTTP请求携带。sessionStorage:会话期,浏览器关闭失效。localStorage:持久化存储,跨会话。
七、高级特性与API
-
Proxy能否监听内部变化 是的,通过配置
set陷阱:const obj = new Proxy({}, { set(target, key, value) { console.log(`属性 ${key} 被修改为 ${value}`); target[key] = value; return true; } });vue实现原理
// 创建被代理的目标对象 const target = { value: 123 }; // 定义代理处理器 const handler = { get(target, prop, receiver) { console.log(`读取值: ${prop} => ${target[prop]}`); return Reflect.get(target, prop, receiver); }, set(target, prop, value, receiver) { console.log(`设置值: ${prop} 从 ${target[prop]} → ${value}`); // 只有当新值与旧值不同时才更新 if (value !== target[prop]) { target[prop] = value; // 这里可以触发数据绑定的更新逻辑 updateRelatedProperties(); } return Reflect.set(target, prop, value, receiver); } }; // 创建代理实例 const proxy = new Proxy(target, handler); function updateRelatedProperties() { console.log("相关属性已同步更新"); } // 使用代理对象进行操作 proxy.value = 456; // 输出: // 设置值: value 从 123 → 456 // 相关属性已同步更新 console.log(proxy.value); // 输出: // 读取值: value => 456 // 456实现要点:
- 将基础数值包装在对象中 (
{ value: 123 }),使 Proxy 能够拦截操作 - 在
get陷阱中添加数据读取时的日志/验证逻辑 - 在
set陷阱中添加数据修改时的校验和联动更新逻辑 - 使用
Reflect维持原有的继承链和默认行为 - 通过比较新旧值避免不必要的更新
典型应用场景:
- 双向数据绑定(Vue/React 等框架的核心机制)
- 数据校验和格式化
- 实时统计(如页面访问计数器)
- 版本控制历史记录
- 将基础数值包装在对象中 (
-
防抖与节流实现
- 防抖(Debounce):
function debounce(func, delay) { let timer; return function(...args) { clearTimeout(timer); timer = setTimeout(() => func.apply(this, args), delay); }; } - 节流(Throttle):
function throttle(func, delay) { let lastTime = 0; return function(...args) { if (Date.now() - lastTime >= delay) { func.apply(this, args); lastTime = Date.now(); } }; }
call、apply、bind区别
call(obj, arg1, arg2):立即执行,参数列表。apply(obj, [arg1, arg2]):立即执行,参数数组。bind(obj, arg1, arg2):返回新函数,延迟执行。
八、内存与性能
- 垃圾回收机制
- 标记清除(Mark and Sweep):
- 标记活动对象。
- 回收未标记对象的内存。
- 内存泄漏场景
- 未解除事件监听(如DOM元素删除后未移除)。
- 全局变量或闭包中未释放的大对象。
- 循环引用(如父子节点互相引用)。
九、ES6+新特性
- ES6新增特性
let/const、箭头函数、模板字符串、解构赋值、类、模块化等。
let与const作用域差异
let:块作用域,可重新赋值。const:块作用域,不可修改(但数组/对象内容可变)。
十、其他
- 事件传播机制
- 冒泡阶段:事件从目标节点向上触发。
- 捕获阶段:事件从根节点向下触发。
- 代理机制:通过事件委托优化性能。
- URL参数解析函数
function parseQueryString(url) { const params = new URLSearchParams(new URL(url).search); return Object.fromEntries(params.entries()); }