面试题-JavaScript
2023/3/19大约 10 分钟
一、数据类型与类型检测
1.1 基本数据类型(值类型)
- 特点:在内存中占据固定大小,保存在栈内存中
- 类型:Number(数字)、String(字符串)、Boolean(布尔)、Symbol(符号)、null(空)、undefined(未定义)
1.2 引用数据类型(复杂数据类型)
- 特点:保存在堆内存中,栈内存存储的是对象的变量标识符以及对象在堆内存中的存储地址
- 类型:Object(对象)、Array(数组)、Date(日期)、RegExp(正则表达式)、Function(函数)等
1.3 特殊类型的使用场景
- Symbol:适合作为对象属性名(key),用于定义不需要对外操作和访问的属性
- BigInt:仅在值可能大于 2^53 时使用,避免与 Number 类型相互转换(会损失精度)
1.4 数据类型检测方案
typeof 操作符
console.log(typeof 1); // number
console.log(typeof true); // boolean
console.log(typeof "mc"); // string
console.log(typeof Symbol); // function
console.log(typeof function () {}); // function
console.log(typeof []); // object
console.log(typeof {}); // object
console.log(typeof null); // object (历史遗留问题)
console.log(typeof undefined); // undefined
- 优点:能够快速区分基本数据类型
- 缺点:不能将 Object、Array 和 Null 区分,都返回 object
instanceof 操作符
console.log(1 instanceof Number); // false
console.log(true instanceof Boolean); // false
console.log("str" instanceof String); // false
console.log([] instanceof Array); // true
console.log(function () {} instanceof Function); // true
console.log({} instanceof Object); // true
- 优点:能够区分 Array、Object 和 Function,适合用于判断自定义的类实例对象
- 缺点:无法判断 Number,Boolean,String 基本数据类型
Object.prototype.toString.call()
var toString = Object.prototype.toString;
console.log(toString.call(1)); //[object Number]
console.log(toString.call(true)); //[object Boolean]
console.log(toString.call("mc")); //[object String]
console.log(toString.call([])); //[object Array]
console.log(toString.call({})); //[object Object]
console.log(toString.call(function () {})); //[object Function]
console.log(toString.call(undefined)); //[object Undefined]
console.log(toString.call(null)); //[object Null]
- 优点:精准判断数据类型
- 缺点:写法繁琐,推荐封装后使用
二、变量声明与作用域
2.1 var、let、const 的区别
- 作用域:
- var:没有块级作用域概念,可以跨块访问,不能跨函数访问
- let/const:具有块级作用域,只能在块作用域内访问
- 变量提升:
- var:存在变量提升,可以先使用后声明
- let/const:不存在变量提升,必须先声明后使用
- 重复声明:
- var:允许在相同作用域内重复声明
- let/const:不允许在相同作用域内重复声明
- 全局对象关联:
- var:声明的全局变量与全局对象 GO(window)有映射关系
- let/const:声明的全局变量与全局对象 GO 没有关系
- const 特性:用于定义常量,必须初始化且不能修改
2.2 作用域和作用域链
作用域
- 定义:作用域定义了变量和函数的可见性或可访问性
- 类型:全局作用域、函数作用域(局部作用域)、块作用域
- 作用:隔离变量,避免不同作用域下同名变量的冲突
作用域链
- 定义:变量查找时,从当前作用域开始,逐级向上查找,直到全局作用域,形成的链式结构
- 查找顺序:当前作用域 → 父级作用域 → 全局作用域
2.3 闭包
- 定义:能够读取其他函数内部变量的函数
- 形成条件:
- 函数嵌套
- 内部函数引用外部函数的局部变量
- 用途:
- 读取函数内部的变量
- 保持变量在内存中不被回收
- 应用场景:AJAX 请求回调、setTimeout 延时回调、函数返回匿名函数等
三、函数与 this 指向
3.1 this 的指向规则
- 普通函数调用:this 指向全局对象 window
- 构造函数调用:this 指向新创建的对象
- 对象方法调用:this 指向调用该方法的对象
- 箭头函数调用:没有自己的 this,使用外层作用域的 this
- apply/call/bind 调用:this 指向这些方法的第一个参数
- 回调函数调用:this 通常指向全局对象 window(如 setTimeout、setInterval)
3.2 call、apply、bind 的区别
- call:
fn.call(thisArg, arg1, arg2, ...)
,立即执行函数 - apply:
fn.apply(thisArg, [arg1, arg2, ...])
,立即执行函数 - bind:
fn.bind(thisArg, arg1, arg2, ...)
,返回绑定后的新函数,不立即执行
3.3 箭头函数的特性
- 没有自己的 this,使用外层作用域的 this
- 没有 constructor,不能作为构造函数,不能通过 new 调用
- 没有 new.target 属性
- 不绑定 arguments 对象
- 没有原型属性(Fn.prototype 值为 undefined)
3.4 arguments 对象
- 定义:函数内部的类数组对象,包含传递给函数的所有参数
- 特点:有 length 属性,可以通过索引访问,但没有数组的内置方法(如 forEach、map 等)
- 注意:箭头函数中没有 arguments 对象
四、ES6+ 新特性
4.1 解构赋值
定义:ES6 引入的新特性,允许从数组或对象中提取值并赋值给变量
示例:
// 数组解构 let [a, b, ...rest] = [10, 20, 30, 40, 50]; // 对象解构 let { name, age, gender } = { name: "zs", age: 18, gender: "male" };
4.2 扩展运算符 ...
作用:
- 字符串/数组:语法层面展开元素
- 对象:进行属性拷贝(浅拷贝)
示例:
let str = "hello"; console.log(...str); // h e l l o let arr = [1, 2, 3]; console.log(...arr); // 1 2 3 let obj = { name: "zs", age: 18 }; console.log({ ...obj }); // { name: "zs", age: 18 }
4.3 模板字符串
定义:允许嵌入表达式的字符串,使用反引号(`)定义
示例:
let name = "world"; let str = `Hello ${name}!`; // "Hello world!"
4.4 Set 数据结构
定义:唯一值的集合,每个值在 Set 中只能出现一次
常用方法:
方法/属性 说明 size 返回元素个数 add() 添加新元素 delete() 删除指定元素 has() 判断元素是否存在 clear() 清空所有元素
4.5 Map 数据结构
定义:键值对的集合,能够记住键的原始插入顺序
常用方法:
方法/属性 说明 size 返回元素个数 set(key, value) 设置键值对 get(key) 获取键对应的值 has(key) 判断键是否存在 delete(key) 删除指定键值对
五、对象与原型链
5.1 原型与原型链
- 原型:每个函数对象都有一个 prototype 属性,指向函数的原型对象
- 原型链:通过
__proto__
属性连接起来的链式结构,用于实现继承 - 特性:
- 每个 class 都有显示原型 prototype
- 每个实例都有隐式原型
__proto__
- 实例的
__proto__
指向对应 class 的 prototype
5.2 获取原型的方法
obj.__proto__
(非标准)obj.constructor.prototype
Object.getPrototypeOf(obj)
(推荐)
5.3 new 运算符的实现机制
- 创建一个新的空对象
- 设置原型,将对象的原型设置为函数的 prototype 对象
- 让函数的 this 指向这个对象,执行构造函数的代码
- 判断函数的返回值类型:
- 值类型:返回创建的对象
- 引用类型:返回这个引用类型的对象
六、深浅拷贝
6.1 浅拷贝
- 定义:创建一个新对象,拷贝原始对象属性值的精确副本
- 特点:
- 基本类型:拷贝值
- 引用类型:拷贝内存地址(修改会影响原对象)
- 实现方法:
Object.assign()
Array.prototype.slice()
- 扩展运算符
...
6.2 深拷贝
- 定义:创建一个新对象,从堆内存中开辟新区域存放,修改不影响原对象
- 实现方法:
- JSON 方法:
JSON.parse(JSON.stringify(object))
- 缺点:忽略 undefined、symbol、函数,不能处理正则、Date 对象,不能解决循环引用
递归实现:
function cloneDeep(target, map = new WeakMap()) { if (typeof target === 'object') { let cloneTarget = Array.isArray(target) ? [] : {}; if (map.get(target)) { return target; } map.set(target, cloneTarget); for (const key in target) { cloneTarget[key] = cloneDeep(target[key], map); } return cloneTarget; } else { return target; } }
- JSON 方法:
七、DOM 与 BOM
7.1 DOM 与 BOM 的区别
- DOM(文档对象模型):将文档作为对象,定义处理网页内容的方法和接口
- BOM(浏览器对象模型):将浏览器作为对象,定义与浏览器交互的方法和接口
- 关系:document 对象是 BOM 的 window 对象的子对象
7.2 DOM 操作
创建节点
createDocumentFragment(); // 创建 DOM 片段
createElement(); // 创建元素节点
createTextNode(); // 创建文本节点
添加、移除、替换、插入
appendChild(node); // 添加
removeChild(node); // 移除
replaceChild(new, old); // 替换
insertBefore(new, old); // 插入
查找元素
getElementById(id); // 通过 ID
getElementsByName(name); // 通过 name 属性
getElementsByTagName(tagName); // 通过标签名
getElementsByClassName(className); // 通过类名
querySelector(selectors); // 获取第一个匹配的元素
querySelectorAll(selectors); // 获取所有匹配的元素
属性操作
getAttribute(key); // 获取属性
setAttribute(key, value); // 设置属性
hasAttribute(key); // 判断属性是否存在
removeAttribute(key); // 移除属性
八、事件处理
8.1 事件传播
三个阶段:
- 捕获阶段:从 window 开始,向下传播到目标元素
- 目标阶段:事件到达目标元素
- 冒泡阶段:从目标元素冒泡到 window
addEventListener 第三个参数:
useCapture = false
(默认):事件在冒泡阶段触发useCapture = true
:事件在捕获阶段触发
8.2 事件委托(事件代理)
定义:将子元素的事件委托给父元素处理
原理:利用事件冒泡机制
优点:减少事件监听器数量,提高性能
示例:
document.getElementById("list").addEventListener("click", function (e) { if (e.target && e.target.nodeName == "LI") { console.log("List item clicked!"); } });
8.3 阻止事件
- 阻止事件冒泡:
e.stopPropagation()
- 阻止事件默认行为:
e.preventDefault()
九、异步编程与 Event Loop
9.1 Event Loop 事件循环
定义:JavaScript 实现异步的核心机制
执行顺序:同步任务 → 微任务 → 宏任务
微任务(优先执行):
- Promise.then()
- MutationObserver
- queueMicrotask
宏任务:
- setTimeout
- setInterval
- setImmediate(仅 IE 支持)
- script(整体代码)
- requestAnimationFrame
- UI 渲染
- AJAX 请求
9.2 浏览器中的 Event Loop 流程
- 执行栈执行同步任务,遇到异步任务放入相应队列
- 执行栈为空时,执行所有微任务
- 微任务执行完毕后,执行一个宏任务
- 重复步骤 2-3,形成循环
十、AJAX
10.1 AJAX 基本原理
- 定义:一种异步通信方法,通过 JS 直接向服务器发起 HTTP 请求,根据返回数据更新页面部分内容
- 核心对象:XMLHttpRequest
10.2 AJAX 实现步骤
// 1. 创建 AJAX 对象
var xhr = new XMLHttpRequest();
// 2. 配置请求参数
xhr.open('GET', 'url', true);
// 3. 设置回调函数
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
// 处理响应数据
console.log(xhr.responseText);
}
};
// 4. 发送请求
xhr.send();
十一、实用工具与方法
11.1 常用正则表达式
// 16 进制颜色值
var color = /#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})/g;
// 日期格式 yyyy-mm-dd
var date = /^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/;
// 手机号
var phone = /^1[34578]\d{9}$/g;
// 邮箱
var email = /^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/;
// URL
var urlP = /^((https?|ftp|file):\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/;
// 强密码(包含大小写字母和数字,8-10位)
var pwd = /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,10}$/;
11.2 常用逻辑运算符
&&
(逻辑与):找第一个假值表达式并返回,无假值则返回最后一个真值||
(逻辑或):找第一个真值表达式并返回!!
(双重非):将值强制转换为布尔值
参考
更新日志
2025/9/28 09:03
查看所有更新日志
38e56
-于9a964
-于8b50d
-于