1. JavaScript 数据类型
- 基本类型:
String,Number,Boolean,Undefined,Null,Symbol。这些类型的数据是不可变的,存储在栈中。- NaN:是Number(数值)类型,但它代表的是一个“不是数字”(Not-A-Number)值,不会抛出错误。
- 引用类型:
Object,Array,Funtion。这些类型的数据可以修改它们的内容,且是通过引用传递的,存储在堆中。
2. 判断数据的类型
-
typeof运算符- 返回数据类型,是字符串。
typeof '123' // "string" typeof 123 // "number" typeof true // "boolean" typeof undefined // "undefined" typeof null // "object"(特殊情况) typeof Symbol() // "symbol" -
instanceof运算符- 判断对象是否是某个构造函数的实例,是布尔值。(不能判断原始类型)
const arr = [1, 2, 3]; console.log(arr instanceof Array); // true console.log(arr instanceof Object); // true,Array 本身是 Object 的子类 function Person(name) { this.name = name; } const p = new Person('Alice'); console.log(Person instanceof Function); // true console.log(Person instanceof Object); // true console.log(p instanceof Person); // true console.log(p instanceof Object); // true -
Object.prototype.toString.call()方法- 精确判断所有类型(推荐)。
Object.prototype.toString.call([]); // "[object Array]" Object.prototype.toString.call(null); // "[object Null]" -
constructor属性- 判断对象由哪个构造函数创建。
const arr = [1, 2, 3]; console.log(arr.constructor === Array); // true
3. null 和 undefined 的区别
- null:是一个指示“无”或“空”的特殊值。
null被认为是一个对象类型。转换为数字时为0。 - undefined:是表示“变量未定义”或“未初始化”的特殊值,表示变量声明后尚未赋值。
undefined转换为数字时为NaN。
4. "==" 与 "===" 的区别
==:执行类型转换后再比较值。- 例如:
'1' == 1会返回true,因为类型会被自动转换成相同的类型。
- 例如:
===:不进行类型转换,只有类型和值都相等时才返回true。- 例如:
'1' === 1会返回false,因为类型不同。
- 例如:
'' == '0' // false
0 == '' // true
0 == '0' // true
false == 'false' // false
false == '0' // true
false == undefined // false
false == null // false
null == undefined // true
' \t\r\n' == 0 // true
5. 字符串常用方法
- 增:
- 使用
+或${}拼接字符串。 concat()将多个字符串合并。
- 使用
let stringValue = "hello ";
let result = stringValue.concat("world");
console.log(result); // "hello world"
console.log(stringValue); // "hello"
- 删:
slice(),substr(), 和substring()提取字符串的子串。
let stringValue = "hello world";
console.log(stringValue.slice(3)); // "lo world"
console.log(stringValue.substr(3)); // "lo world"
console.log(stringValue.substring(3)); // "lo world"
console.log(stringValue.slice(3, 7)); // "lo w"
console.log(stringValue.substr(3, 7)); // "lo worl"
console.log(stringValue.substring(3, 7)); // "lo w"
-
改:
trim(),trimLeft(),trimRight():去除字符串两端的空白。repeat():重复字符串指定次数。padStart(),padEnd():在字符串的两端填充指定字符,直到字符串达到指定长度。toUpperCase(),toLowerCase():大小写转化。
-
查:
charAt():返回指定索引位置的字符。indexOf():返回字符所在的索引位置。includes(),startsWith(),endsWith():检查字符串是否包含,或以某个字符串开头或结尾。
6. 数组常用方法
-
增:
push():向数组末尾添加元素,返回新数组的长度。unshift():向数组开头添加元素,返回新数组长度。concat():连接多个数组,返回一个新的数组,不修改原数组。
-
删:
pop():移除数组末尾的元素,返回该元素。shift():移除数组开头的元素,返回该元素。slice():返回数组的一个子数组,不修改原数组。
-
改:
splice():用于添加、删除或替换数组元素,返回操作元素。
-
查:
indexOf():返回元素首次出现的索引,找不到返回 -1。includes():判断数组中是否存在某个元素。find():返回第一个符合条件的元素。
-
排序:
sort():根据指定的排序规则对数组进行排序,默认按字符编码排序,可以传入比较函数来进行自定义排序。reverse():反转数组的顺序。
-
迭代:
forEach():遍历数组,适合执行副作用操作。map():对数组的每个元素进行转换,返回新数组。filter():过滤符合条件的数组项,返回新数组。reduce():将数组中的所有元素按照某种规则累加/组合,返回单个值。
-
转换:
join():将数组转换为一个字符串,使用指定的分隔符连接各个元素。toString():将数组转换为一个字符串,元素之间以逗号分隔。from():将类数组对象或可迭代对象转换为数组。
7. 数组的特殊操作(扁平化、去重、乱序)
扁平化:将多维数组“拉平”为一维或指定深度的数组。
flat()
const arr = [1, [2, [3, [4]]]];
console.log(arr.flat(2)); // [1, 2, 3, [4]]
console.log(arr.flat(Infinity)); // [1, 2, 3, 4]
flat(depth)参数控制展开深度,参数为Infinity可完全扁平化。
- 递归
function flatten(arr) {
return arr.reduce((acc, val) =>
Array.isArray(val) ? acc.concat(flatten(val)) : acc.concat(val), []);
}
数组去重
Set和[...]
const arr = [1, 2, 2, 3];
const unique = [...new Set(arr)];
console.log(unique); // [1, 2, 3]
filter和indexOf
const unique = arr.filter((item, index) => arr.indexOf(item) === index);
数组乱序
sort()+Math.random()
const arr = [1, 2, 3, 4, 5];
const shuffled = arr.sort(() => Math.random() - 0.5);
8. 原型与原型链
-
原型(Prototype):每个对象都有一个内部属性
[[Prototype]](可通过__proto__访问),指向另一个对象。对象可以通过原型继承属性和方法。 -
原型链(Prototype Chain):对象的原型也有自己的原型,层层向上形成链式结构。
- 当访问属性或方法时,会沿原型链逐级查找,直到找到或到达链顶(
null)。
- 当访问属性或方法时,会沿原型链逐级查找,直到找到或到达链顶(
9. 作用域与作用域链
-
作用域(Prototype):变量可以被访问的范围。
- 全局作用域:在代码任何地方都能访问到的变量,比如最外层声明的变量。
- 函数作用域:在函数内部声明的变量,只能在函数内部访问。
- 块级作用域(ES6引入):由一对
{}包裹起来的范围(比如if、for里面),用let和const声明的变量才有块级作用域。
-
作用域链(Scope Chain):查找变量时从当前作用域层层向外查找的一种规则。
- 当查找变量时,会沿作用域链逐级向外查找,直到找到或到达全局作用域报错(
ReferenceError)。
- 当查找变量时,会沿作用域链逐级向外查找,直到找到或到达全局作用域报错(
10. 闭包
-
闭包:函数内部可以访问外部函数作用域中的变量。
function outer() { let count = 0; // 外部函数作用域的变量 return function inner() { count++; console.log(count); } } const counter = outer(); // outer 执行并返回 inner 函数 counter(); // 输出: 1 counter(); // 输出: 2- 当
outer()执行时,它返回了inner()函数。 counter引用了inner(),即使outer()已经执行完毕,inner()仍然保留着对count的引用 —— 这就是闭包。count并不会因为outer()的结束而被销毁。
- 当
-
用途(优点):
-
数据封装:创建私有变量,避免全局污染。
-
模块化:隐藏内部实现,仅暴露必要接口,实现模块化编程。
-
缓存:保存函数的计算结果,避免重复计算,提升性能。
-
-
缺点:
-
内存泄漏风险:闭包持有外部变量引用,未合理释放可能导致内存无法回收。
-
性能开销:大量闭包或深层嵌套,增加内存占用和执行成本。
-
调试困难:变量作用域复杂,嵌套层级多时不易追踪问题。
-
11. var、 let、const 区别
| 特性 | var | let | const |
|---|---|---|---|
| 作用域 | 函数作用域 | 块级作用域 | 块级作用域 |
| 是否变量提升 | 是,初始化为 undefined | 是,但不初始化 | 是,但不初始化 |
| 是否可重复声明 | 可以 | 不可以 | 不可以 |
| 是否可重新赋值 | 可以 | 可以 | 不可以(对象内部属性可变) |
| 常见问题 | 容易引发作用域污染、变量提升混乱 | 更安全,用于局部变量 | 用于声明常量 |
- 作用域不同
var声明的变量拥有函数作用域,如果在函数外声明,相当于全局变量。let和const是块级作用域(如{}花括号内有效),不会泄露到外部。
function testVar() {
if (true) {
var x = 1;
}
console.log(x); // 输出 1
}
function testLet() {
if (true) {
let y = 2;
}
console.log(y); // 报错:y is not defined
}
- 变量提升不同
var声明的变量会被提升到函数或全局顶部,但值是undefined。let和const也会被提升,但在实际赋值前不可访问(暂时性死区)。
console.log(a); // undefined
var a = 5;
console.log(b); // 报错
let b = 10;
- 重新赋值与重复声明
var可以重复声明和赋值。let可以赋值,但不允许重复声明。const既不能重复声明,也不能重新赋值(如果是对象,可以修改对象内部的属性)。
var c = 1;
var c = 2; // OK
let d = 1;
// let d = 2; // 报错
const e = 1;
// e = 2; // 报错
const obj = { name: "Alice" };
obj.name = "Bob"; // OK,对象内部属性可以改
const 保证的是绑定关系不可变,而不是对象本身的不可变。
12. this 对象是什么?
-
this:函数运行时指向调用它的对象,根据调用方式动态绑定的。 -
常见的 this 指向情况:
-
全局环境下(浏览器): 指向
window。console.log(this === window); // true -
普通函数调用:指向
window(严格模式下是undefined)。function foo() { console.log(this); } foo(); // window 或 undefined(严格模式) -
对象方法调用:如果函数作为对象的方法被调用,指向这个对象本身。
const obj = { name: "Tom", sayName: function() { console.log(this.name); } }; obj.sayName(); // "Tom" -
构造函数调用(new):使用
new调用函数时,指向新创建的对象实例。function Person(name) { this.name = name; } const p = new Person("Alice"); console.log(p.name); // "Alice" -
箭头函数:箭头函数没有自己的
this,它继承自外层作用域的this。const obj = { name: "Tom", sayName: () => { console.log(this.name); } }; obj.sayName(); // undefined (因为 this 指向 window) -
手动绑定 this(call, apply, bind):显式指定
this。function greet() { console.log(this.name); } const user = { name: "Jack" }; greet.call(user); // "Jack" greet.apply(user); // "Jack" const boundGreet = greet.bind(user); boundGreet(); // "Jack"
-
13. new 操作符的工作过程
-
用途: 根据构造函数创建实例。
- 创建新的空对象。
- 绑定原型,新对象的原型指向构造函数的原型对象。
- 绑定 this,构造函数的
this指向新对象。 - 返回对象,如果构造函数返回对象则返回该对象,否则返回新创建的对象。
-
自定义实现
new
function myNew(Constructor, ...args) {
const obj = {}; // 创建空对象
obj._proto_ = Constructor.prototype; // 绑定原型
const result = Constructor.apply(obj, args); // 绑定this
return result && result instanceof Object ? result : obj; // 返回对象
}
function Person(name) {
this.name = name;
// return { age: 20 }; // 显式返回对象,会覆盖 this
}
const p = new Person("Alice");
console.log(p); // {name: "Alice"} // { age: 20 }
console.log(p.name); // "Alice" // undifined
14. 箭头函数
- 语法规则
| 语法形式 | 示例 |
|---|---|
| 无参数 | () => 42 |
| 一个参数(可省略括号) | x => x * x |
| 多个参数 | (a, b) => a + b |
多行函数体(需用 {} 和 return) | (a, b) => { return a + b; } |
| 返回对象(必须加括号) | () => ({ name: "Alice" }) |
-
特点
- 没有原型(
prototype)属性:不能用于构造函数,无法继承。
const normal = function() {}; const arrow = () => {}; console.log(normal.prototype); // {} console.log(arrow.prototype); // undefined- 没有自己的
this:继承外层函数的this,不会创建自己的this。
const obj = { value: 10, getValue: function() { return this.value; } }; console.log(obj.getValue()); // 10 const objArrow = { value: 10, getValue: () => { return this.value; } }; console.log(objArrow.getValue()); // undefined(不是 obj,而是全局或上层作用域)- 不能作为构造函数:不能使用
new调用箭头函数创建实例,否则会报错。
function Person(name) { this.name = name; } const p1 = new Person("Alice"); // 正常 const PersonArrow = (name) => { this.name = name; }; // const p2 = new PersonArrow("Bob"); // 报错- 没有
arguments对象:如果需要参数信息,必须使用剩余参数...args。
function normal() { console.log(arguments); } normal(1, 2); // [1, 2] const arrow = (...args) => { console.log(args); }; arrow(1, 2); // [1, 2] - 没有原型(
15. 深拷贝与浅拷贝
区别概述
| 拷贝类型 | 行为描述 |
|---|---|
| 浅拷贝 | 拷贝数据的第一层属性,属性为引用类型,会复制引用地址,拷贝对象和原对象相互影响。 |
| 深拷贝 | 递归拷贝数据所有层级属性,属性为引用类型,会创建新的内存副本,拷贝对象和原对象互不影响。 |
浅拷贝方法
- 用于对象:
Object.assign({}, obj),{ ...obj }
const obj = { a: 1, b: { c: 2 } };
const shallow = Object.assign({}, obj)
// const shallow = { ...obj };
shallow.a = 0;
shallow.b.c = 99;
console.log(obj); // { a: 1, b: { c: 99 } }
console.log(shallow); // { a: 0, b: { c: 99 } }
- 用于数组:
slice(),concat(),from(),[ ...array ]
const original = [1, [2], 3];
const copy = original.slice();
// const copy = [].concat(original);
// const copy = Array.from(original);
// const copy = [ ...original ];
copy[0] = 100;
copy[1][0] = 200;
console.log(original); // [1, [200], 3]
console.log(copy); // [100, [200], 3]
深拷贝方法
JSON.parse(JSON.stringify(obj))- 不能拷贝
undefined、Symbol、Function等特殊值。 - 循环引用(对象引用自身)会导致
JSON.stringify报错。
- 不能拷贝
const obj = { a: 1, b: { c: 2 } };
const deep = JSON.parse(JSON.stringify(obj));
deep.b.c = 99;
console.log(obj); // { a: 1, b: { c: 2 } }
- 递归拷贝函数
function deepClone(obj, hash = new WeakMap()) {
// 基本类型和函数直接返回
if (obj === null || typeof obj !== "object") return obj;
// 特殊类型处理
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
// 循环引用处理
if (hash.has(obj)) return hash.get(obj);
const clone = new obj.constructor();
hash.set(obj, clone)
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
clone[key] = deepClone(obj[key],hash);
}
}
return clone;
}
// 例子
const a = {
name: 'Alice',
date: new Date(),
reg: /test/i,
meta: new Map([['key', { nested: true }]]),
tags: new Set([1, 2, 3]),
info: { age: 25 }
};
a.self = a; // 循环引用
const b = deepClone(a);
console.log(b);
console.log(b.self === b); // true(循环引用保留)
console.log(b.info !== a.info); // true(深拷贝成功)
console.log(b.meta.get('key') !== a.meta.get('key')); // true(深拷贝成功)
- 使用第三方库(推荐)
lodash的_.cloneDeep()是最稳定可靠的深拷贝方法。
import cloneDeep from 'lodash/cloneDeep';
const newObj = cloneDeep(obj);
16. 判断对象是否为空
判断对象是否为空通常指检查对象是否没有自身的可枚举属性。以下是几种常用方法及其详细说明:
方法一:使用 Object.keys()
原理:将对象的键转为数组,判断数组长度是否为 0。
特点:
- 仅检查对象自身可枚举属性(不包含继承属性)。
- 兼容性好,推荐使用。
function isEmpty(obj) {
// 确保是普通对象(排除数组、null等非对象类型)
return Object.prototype.toString.call(obj) === '[object Object]' &&
Object.keys(obj).length === 0;
}
// 示例
isEmpty({}); // true
isEmpty({ a: 1 }); // false
isEmpty(null); // false(非对象)
isEmpty([]); // false(数组非普通对象)
方法二:使用 for...in 循环
原理:遍历对象属性,发现任何可枚举属性立即返回 false。
特点:
- 会遍历继承的可枚举属性,需配合
hasOwnProperty过滤。 - 性能略低,适用于需要兼容极旧环境。
function isEmpty(obj) {
if (Object.prototype.toString.call(obj) !== '[object Object]') return false;
for (const key in obj) {
if (obj.hasOwnProperty(key)) return false;
}
return true;
}
方法三:使用 JSON.stringify()
原理:将对象转为 JSON 字符串,判断结果是否为 "{}"。
缺点:
- 忽略
undefined、函数、Symbol 等值。 - 无法处理不可枚举属性和循环引用。
- 不推荐用于严格判断。
function isEmpty(obj) {
return JSON.stringify(obj) === '{}';
}
// 问题示例
isEmpty({ key: undefined }); // true(误判)
方法四:使用 Reflect.ownKeys()
原理:获取对象所有自身属性(包括 Symbol 和不可枚举)。
适用场景:需要严格检查是否存在任何自身属性。
function isEmpty(obj) {
return Object.prototype.toString.call(obj) === '[object Object]' &&
Reflect.ownKeys(obj).length === 0;
}
方法对比表
| 方法 | 自身可枚举属性 | 继承属性 | 不可枚举属性 | Symbol 键 | 性能 | 推荐度 |
|---|---|---|---|---|---|---|
Object.keys() | ✅ | ❌ | ❌ | ❌ | 高 | ⭐⭐⭐⭐ |
for...in | ✅ (需过滤) | ✅ | ❌ | ❌ | 中 | ⭐⭐ |
JSON.stringify() | ✅ (部分忽略) | ❌ | ❌ | ❌ | 低 | ⭐ |
Reflect.ownKeys() | ✅ | ❌ | ✅ | ✅ | 高 | ⭐⭐⭐ |
17. for...in 和 for...of 的区别
for...in | for...of | |
|---|---|---|
| 遍历内容 | 遍历对象的可枚举属性名(包括原型链) | 遍历数组或类数组对象的值(如数组元素、字符串字符) |
| 适用对象 | 普通对象、数组(但会遍历索引和自定义属性) | 可迭代对象(数组、字符串、Map、Set等) |
for...in
-
遍历对象的键名
循环遍历对象自身的和继承的可枚举属性(通过enumerable: true定义)。 -
过滤继承属性
通常需配合hasOwnProperty检查属性是否属于对象自身: -
遍历数组时的陷阱
会遍历数组的索引(字符串类型)和手动添加的自定义属性:
for...of
-
遍历可迭代对象的值
直接获取数组元素、字符串字符等值,不会遍历索引或自定义属性: -
仅适用于可迭代对象
可迭代对象必须实现Symbol.iterator方法。普通对象默认不可迭代:
遍历数组
const arr = [10, 20, 30];
arr.customProp = "foo";
// for...in(遍历索引)
for (const index in arr) {
console.log(index); // "0", "1", "2", "customProp"(字符串类型)
console.log(arr[index]); // 10, 20, 30, "foo"
}
// for...of(遍历值)
for (const value of arr) {
console.log(value); // 10, 20, 30
}
遍历字符串
const str = "hello";
// for...in(遍历索引)
for (const index in str) {
console.log(index); // "0", "1", "2", "3", "4"
console.log(str[index]); // "h", "e", "l", "l", "o"
}
// for...of(遍历字符)
for (const char of str) {
console.log(char); // "h", "e", "l", "l", "o"
}
遍历对象(需手动处理)
const obj = { a: 1, b: 2 };
obj.__proto__.c = 3;
// for...in(遍历键)
for (const key in obj) {
console.log(key); // "a", "b", "c"
console.log(obj[key]); // 1, 2, 3
if(obj.hasOwnProperty(key)) {
console.log(key, obj[key]); // "a 1", "b 2"
}
}
// for...of(需先转换为可迭代对象)
for (const value of Object.values(obj)) {
console.log(value); // 1, 2
}
18. Map 和 Set
一、Map(映射)
Map 键值对集合,键和值都支持任何类型的键,保留插入顺序。
核心特性
- 键的多样性:键可以是任意数据类型(对象、函数、原始值等),而
Object的键只能是字符串或 Symbol。 - 顺序性:插入顺序被保留,遍历时按插入顺序返回键值对。
- 高效性:查找、插入、删除操作的时间复杂度接近
O(1)(基于哈希表实现)。 - 可迭代性:直接支持
for...of、forEach等迭代方法。
常用方法
| 方法/属性 | 作用 | 示例 |
|---|---|---|
new Map() | 创建空 Map | const map = new Map(); |
map.set(key, value) | 添加键值对(支持链式调用) | map.set('a', 1).set('b', 2); |
map.get(key) | 获取键对应的值(不存在返回 undefined) | map.get('a'); // 1 |
map.has(key) | 判断是否包含某键 | map.has('a'); // true |
map.delete(key) | 删除指定键的键值对 | map.delete('a'); // true |
map.clear() | 清空所有键值对 | map.clear(); |
map.size | 返回键值对的数量 | map.size; // 2 |
迭代方法
const map = new Map([['a', 1], ['b', 2]]);
// 遍历键值对
for (const [key, value] of map) {
console.log(key, value); // 'a 1', 'b 2'
}
// 遍历键
for (const key of map.keys()) {
console.log(key); // 'a', 'b'
}
// 遍历值
for (const value of map.values()) {
console.log(value); // 1, 2
}
// 使用 forEach
map.forEach((value, key) => {
console.log(key, value);
});
二、 Set(集合)
Set 是一种唯一值的集合,自动去重,保留插入顺序。
核心特性
- 唯一性:集合中的值唯一(基于
===判断,但NaN视为相等)。 - 顺序性:插入顺序被保留。
- 高效性:检查值是否存在的时间复杂度接近
O(1)。 - 可迭代性:支持
for...of、forEach等迭代方法。
常用方法
| 方法/属性 | 作用 | 示例 |
|---|---|---|
new Set() | 创建空 Set | const set = new Set(); |
set.add(value) | 添加值(支持链式调用) | set.add(1).add(2); |
set.has(value) | 判断是否包含某值 | set.has(1); // true |
set.delete(value) | 删除指定值 | set.delete(1); // true |
set.clear() | 清空所有值 | set.clear(); |
set.size | 返回值的数量 | set.size; // 2 |
迭代方法
const set = new Set([1, 2, 3]);
// 遍历值
for (const value of set) {
console.log(value); // 1, 2, 3
}
// 使用 forEach
set.forEach((value) => {
console.log(value);
});
三、Map vs Object、Set vs Array 对比
Map 与 Object 对比
| 特性 | Map | Object |
|---|---|---|
| 键类型 | 任意类型 | 字符串或 Symbol |
| 键顺序 | 保留插入顺序 | ES6 后字符串键按插入顺序 |
| 默认键 | 无 | 从原型链继承(如 toString) |
| 性能 | 频繁增删时更优 | 适合静态键值对 |
| 序列化 | 无法直接 JSON.stringify | 可直接序列化 |
Set 与 Array 对比
| 特性 | Set | Array |
|---|---|---|
| 唯一性 | 自动去重 | 允许重复元素 |
| 查找效率 | O(1) | O(n) |
| 顺序性 | 保留插入顺序 | 保留插入顺序 |
| API 丰富性 | 简单 | 丰富(如 map、filter) |
19. 同源策略和跨域请求实现方法
同源:两个 URL 的 协议(Protocol)、域名(Host)、端口(Port) 完全相同。
https://example.comvshttp://example.com→ 不同源(协议不同)https://example.com:443vshttps://example.com:8080→ 不同源(端口不同)
同源策略:浏览器限制不同源的文档或脚本之间的交互,防止恶意网站窃取数据。
受限制的操作包括:
- DOM 访问:禁止跨域访问
iframe内容(如document.getElementById)。 - 数据存储:限制访问非同源的
Cookie、LocalStorage。 - 网络请求:默认禁止通过
XMLHttpRequest或Fetch发起跨域请求。
跨域请求实现方法
-
CORS(跨域资源共享)(推荐的主流方法)
原理:服务器通过设置 HTTP 响应头,显式允许某些源的请求。
适用场景:现代浏览器的主流跨域解决方案,需服务端配合。 -
JSONP(JSON with Padding)(旧浏览器)
原理:利用<script>标签不受同源策略限制的特性,通过回调函数获取数据。
特点:仅支持 GET 请求,存在安全风险(如 XSS)。 -
代理服务器
原理:在同源服务器上设置代理,前端请求代理,代理转发请求到目标服务器。
适用场景:适用于无法修改目标服务器配置的情况。// 前端请求同源代理 fetch('/proxy?url=https://api.example.com/data') .then(response => response.json()); // 服务端(Node.js 示例) app.get('/proxy', async (req, res) => { const targetUrl = req.query.url; const response = await fetch(targetUrl); res.send(await response.text()); }); -
WebSocket
原理:WebSocket 协议不受同源策略限制,可直接跨域通信。
示例:const socket = new WebSocket('wss://api.example.com/socket'); socket.onmessage = (event) => { console.log('Message:', event.data); }; -
postMessage (窗口间通信)
原理:HTML5 提供的跨文档通信 API,允许不同源窗口间安全传递消息。
示例:// 发送方(例如 iframe 内部) window.parent.postMessage('Hello from iframe!', 'https://parent-site.com'); // 接收方(父页面) window.addEventListener('message', (event) => { if (event.origin === 'https://child-site.com') { console.log('Received:', event.data); } }); -
修改
document.domain:
仅适用于主域相同、子域不同的场景(如a.example.com和b.example.com均设为example.com)。 -
反向代理:通过 Nginx 等工具配置反向代理,将跨域请求转换为同源请求。
20. 哪些操作会导致内存泄漏?
-
未使用
var/let/const声明的变量:会隐式创建全局变量,长时间驻留内存。 -
闭包:不当使用闭包可能导致外部变量无法被回收,造成内存泄漏。
-
循环引用:两个对象相互引用, 垃圾回收无法判断它们是否还在使用,导致无法释放。
-
未清理的事件监听:移除 DOM 元素但未解绑事件,特别是在 IE 中常见。
-
滥用
console.log:开发工具可能会保留对日志中对象的引用,阻止垃圾回收。 -
setTimeout第一个参数为字符串:类似eval的行为,创建作用域链,可能增加内存负担。
21. 什么是事件循环(Event Loop)?
JavaScript 是一门单线程(同一时间只能做一件事)、非阻塞语言,依靠事件循环机制实现异步操作。
- 任务执行模型分为:
-
同步任务:直接进入主线程,按顺序执行。
-
异步任务:如 AJAX 网络请求、setTimeout 定时器等,会被加入任务队列,等待主线程空闲后执行。
- 宏任务与微任务:
类型 示例 执行时机 微任务 Promise.then/.catch/.finally、MutationObserver同步任务执行完成后立即执行 宏任务 setTimeout、setInterval、UI 事件(点击、滚动)微任务完成后执行
-
- 事件循环流程:
- 主线程执行同步任务。
- 遇到异步任务,将其挂起,注册回调。
- 同步任务执行完毕后,从任务队列取出异步任务。
- 异步任务进入主线程执行。
- 重复此流程 —— 这就是事件循环。
22. Promise 详解
Pomise:
- 表示异步任务的状态和结果的对象,解决了传统回调函数嵌套(回调地狱)的问题。
状态:
- Pending(进行中):初始状态,异步操作尚未完成。
- Fulfilled(已成功):异步操作成功完成,返回结果。
- Rejected(已失败):异步操作失败,返回错误原因。
状态一旦改变(从 Pending → Fulfilled 或 Pending → Rejected),便不可再修改。
通过 new Promise() 构造函数创建 Promise 实例,接收一个执行器函数(executor):
const promise = new Promise((resolve, reject) => {
// 异步操作(如网络请求、定时器等)
setTimeout(() => {
const success = true;
if (success) {
resolve("操作成功!"); // 状态变为 Fulfilled
} else {
reject("操作失败!"); // 状态变为 Rejected
}
}, 1000);
});
静态方法:
Promise.all():并行执行,全部成功时返回结果数组,任意失败则立即终止并返回失败。Promise.allSettled():并行执行,全部完成后返回结果数组,包含状态 + 成果值/失败原因。Promise.race():返回第一个完成(成功或失败)的 Promise。Promise.any():返回第一个成功的 Promise,若全部失败,则抛出异常。
特性:
- 支持链式调用(
.then()成功结果 /.catch()失败结果/.finally(无论成功或失败都执行)) - 错误集中处理
- 组合式调用实现复杂逻辑
async/await 简介
async函数返回一个 Promise。await表达式用于等待一个 Promise 的结果。- 用法简洁、可读性强,让异步代码像同步一样书写。
async function fetchData() {
try {
const result = await fetch(url);
const data = await result.json();
console.log(data);
} catch (err) {
console.error(err);
}
}
23. if...else 条件判断使用分析
if...else 缺点
| 问题 | 描述 |
|---|---|
| ❌ 可读性差 | 多层嵌套让代码不清晰,不易阅读 |
| ❌ 不易扩展 | 每次加分支都要改源码,易引发错误 |
| ❌ 不易测试 | 条件复杂时不容易单元测试 |
| ❌ 违反开闭原则 | 逻辑写死在函数里,新增逻辑需“修改”而不是“扩展” |
优化方案
| 场景 | 推荐方案 |
|---|---|
| 少量判断、简单逻辑 | if...else / 三元运算符 |
| 多分支,进行选择操作 | switch |
| 多值映射/多行为调用 | 对象字典映射 |
| 行为需扩展 | 多态 / 策略模式 |
1. 三元运算符(仅适用于简单条件)
const result = age > 18 ? 'Adult' : 'Minor'
2. 使用 switch(适合多分支,进行选择操作)
switch (status) {
case 'success':
handleSuccess()
break
case 'fail':
handleFail()
break
default:
handleDefault()
}
3. 使用对象字典映射表(根据变量选择对应行为/值)
const actions = {
admin: 'Admin Panel',
user: 'User Dashboard',
guest: 'Login Page',
}
const role = 'user'
const result = actions[role] || ''
console.log(result) // User Dashboard
const PaymentStrategy = {
alipay: () => console.log('支付宝支付'),
wechat: () => console.log('微信支付'),
credit: () => console.log('信用卡支付')
}
function pay(type) {
const fn = PaymentStrategy[type]
if (!fn) throw new Error('不支持该支付方式')
fn()
}
pay('wechat')
4. 多态(面向对象,可扩展的行为封装场景)
class Animal {
speak() { console.log('I make a sound') }
}
class Dog extends Animal {
speak() { console.log('Woof') }
}
class Cat extends Animal {
speak() { console.log('Meow') }
}
function makeAnimalSpeak(animal) {
animal.speak()
}
makeAnimalSpeak(new Dog())