同源策略(Same Origin Policy)
核心知识点
-
定义与限制
同源策略要求两个 URL 的 协议、域名、端口 必须完全一致才允许交互。例如:
https://example.com
和https://api.example.com
不同源(域名不同) 主要限制包括:DOM 跨域访问、AJAX 跨域请求、存储隔离(如 Cookie、LocalStorage) -
跨域解决方案
-
CORS :服务器设置
Access-Control-Allow-Origin
头实现跨域资源共享 -
JSONP :利用
<script>
标签无跨域限制的特性,通过回调函数获取数据 -
代理服务器:前端请求同源代理,由代理转发跨域请求
-
javascript
javascript
// CORS 配置示例(后端)
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
next();
});
LocalStorage、SessionStorage、Cookie 区别
特性 | Cookie | LocalStorage | SessionStorage |
---|---|---|---|
存储大小 | 4KB | 5~10MB | 5~10MB |
生命周期 | 可设置过期时间或会话级关闭 | 永久存储,需手动删除 | 会话结束(标签页关闭) |
自动发送到服务端 | 是(每次请求携带) | 否 | 否 |
作用域 | 同源共享 | 同源共享 | 仅当前标签页 |
应用场景
-
Cookie
:身份验证(如 JWT Token) -
LocalStorage
:持久化用户偏好(如主题设置) -
SessionStorage
:临时表单数据存储
GET vs POST 请求区别
核心区别
-
语义与用途
-
GET:获取资源(幂等,可缓存),如搜索请求
-
POST:提交数据(非幂等,不可缓存),如表单提交
-
-
数据传递与安全性
-
GET :参数通过 URL 明文传输(
?key=value
),长度限制约 2KB,安全性较低 -
POST:数据在请求体中传输,支持二进制数据,安全性较高(配合 HTTPS)
-
-
性能与缓存
- GET 支持浏览器缓存和书签保存,POST 不缓存且可能触发 TCP 两次握手
HTTP1.0和HTTP2.0的区别
HTTP/2是HTTP协议的最新版本,它在性能和功能上都有了显著的改进。
-
性能:
- HTTP/2支持多路复用,可以在一个连接上同时发送多个请求和响应,减少了连接的开销。
- HTTP/1.0和HTTP/1.1在处理多个请求时需要建立多个连接,这会增加延迟。
-
连接方式:
- HTTP/2使用二进制分帧层,可以更高效地管理数据传输。
- HTTP/1.0和HTTP/1.1使用文本协议,相对低效。
-
头部压缩:
- HTTP/2对请求和响应的头部进行了压缩,减少了数据传输量。
- HTTP/1.0和HTTP/1.1没有头部压缩功能。
面试常见问题
-
HTTP/2 的主要性能改进是什么? HTTP/2 的主要性能改进包括多路复用、服务器推送和头部压缩。
-
HTTP/2 是否需要使用 HTTPS? 虽然 HTTP/2 不强制要求使用 HTTPS,但大多数浏览器只支持通过 HTTPS 实现的 HTTP/2。
-
如何在服务器上启用 HTTP/2? 在服务器上启用 HTTP/2 通常需要配置服务器软件(如 Nginx、Apache)并确保使用 HTTPS。
垃圾回收机制详解
垃圾回收机制是浏览器自动管理内存的一种方式,它会定期检查并释放不再使用的内存。在前端开发中,了解垃圾回收机制有助于编写高效、稳定的代码,避免内存泄漏等问题。
常见的垃圾回收算法
标记清除
这是最常用的垃圾回收算法。垃圾回收器会标记所有活动的内存块,然后清除未被标记的块。这种方法可以有效回收不再使用的内存,但可能会导致一定的性能开销。
引用计数
这种算法会跟踪每个内存块的引用次数,当引用次数为零时,就认为该块可以被回收。引用计数的优点是简单高效,但存在循环引用的问题,即两个对象相互引用,导致引用计数始终不为零,无法被回收。
代际收集
将内存分为不同的代,新生代对象存活时间短,老生代对象存活时间长,针对不同代采用不同的回收策略。这种方法可以提高垃圾回收的效率,减少对整个内存的扫描。
内存泄漏的常见场景
未释放的DOM引用
在页面中,如果存在对DOM元素的引用,即使该DOM元素已经被移除,只要引用还存在,垃圾回收器就无法回收其占用的内存。
javascript
let element = document.createElement('div');
document.body.appendChild(element);
// 移除DOM元素,但未释放引用
document.body.removeChild(element);
// element 引用仍然存在,导致内存泄漏
闭包引起的内存泄漏
闭包会捕获外部函数的变量,如果闭包长时间存在,可能导致这些变量无法被垃圾回收。
javascript
function createClosure() {
let largeData = new Array(1000000).fill('large data');
return function() {
console.log(largeData.length);
};
}
let closure = createClosure();
// closure 仍然存在,导致 largeData 无法被回收
定时器引起的内存泄漏
定时器中的函数可能会捕获外部变量,如果定时器长时间运行,可能导致这些变量无法被回收。
javascript
let data = new Array(1000000).fill('large data');
setInterval(function() {
console.log('定时器运行中');
}, 1000);
// 如果不清理定时器,data 可能无法被回收
如何避免内存泄漏
及时释放引用
在不再需要某个对象时,将其引用设为 null
,帮助垃圾回收器回收内存。
ini
let largeObject = new Array(1000000).fill('large data');
// 使用完后释放引用
largeObject = null;
合理使用闭包
避免不必要的闭包,或者在闭包中及时释放对外部变量的引用。
ini
function createClosure() {
let largeData = new Array(1000000).fill('large data');
let closure = function() {
console.log(largeData.length);
};
// 在适当的时候释放引用
closure.cleanup = function() {
largeData = null;
};
return closure;
}
let closure = createClosure();
// 使用完后调用 cleanup 方法
closure.cleanup();
清理定时器
在组件销毁或不再需要定时器时,及时清理定时器。
javascript
let timer = setInterval(function() {
console.log('定时器运行中');
}, 1000);
// 清理定时器
clearInterval(timer);
面试常见问题
- 垃圾回收机制的作用是什么? 垃圾回收机制的主要作用是自动管理内存,释放不再使用的内存,避免内存泄漏,提高程序的性能和稳定性。
- 常见的垃圾回收算法有哪些? 常见的垃圾回收算法包括标记清除、引用计数和代际收集。
- 如何避免内存泄漏? 避免内存泄漏的方法包括及时释放引用、合理使用闭包、清理定时器等。
JavaScript 数据类型及其区别
JavaScript 数据类型分为 基本数据类型 (原始类型)和 引用数据类型(对象类型)。它们的核心区别在于存储方式、可变性及比较逻辑
1. 基本数据类型(7种)
-
类型列表 :
Undefined
、Null
、Boolean
、Number
、String
、Symbol
(ES6)、BigInt
(ES10)。 -
特点:
-
不可变性 :值本身无法被修改(如
let s = 'a'; s = 'b'
实际是创建新值)。 -
栈存储:直接存储在栈内存中,占用空间小且固定
-
按值比较 :
1 === 1
为true
。
-
-
特殊值:
-
NaN
:表示无效数值,typeof NaN
返回"number"
,且NaN !== NaN
-
Symbol
:唯一且不可变,用于解决命名冲突(如对象属性键)
-
2. 引用数据类型
-
类型列表 :
Object
、Array
、Function
、Date
、RegExp
等。 -
特点:
-
可变性 :对象内容可修改(如
arr.push(1)
)。 -
堆存储:值存储在堆中,栈中存储指向堆的指针
-
按引用比较 :
{} === {}
为false
(比较内存地址)。
-
3. 核心区别
特性 | 基本类型 | 引用类型 |
---|---|---|
存储位置 | 栈 | 堆 |
赋值行为 | 复制值 | 复制指针 |
添加属性/方法 | 不支持 | 支持 |
内存管理 | 自动回收 | 需手动或 GC 回收 |
数据类型检测方法
1. typeof
运算符
-
用途 :检测基本类型(
undefined
、boolean
、number
、string
、symbol
、bigint
)。 -
局限性:
-
typeof null
返回"object"
(历史遗留问题) -
无法区分数组与普通对象(均返回
"object"
)。
-
csharp
typeof "hello"; // "string"
typeof null; // "object"(注意误判)
2. instanceof
运算符
- 用途:检测对象是否为某个构造函数的实例。
- 局限性:无法检测基本类型,且跨框架对象可能失效。
javascript
[] instanceof Array; // true
{} instanceof Object; // true
3. Object.prototype.toString.call()
- 最精准方法 :返回
[object Type]
格式字符串,支持所有类型
javascript
Object.prototype.toString.call(null); // "[object Null]"
Object.prototype.toString.call([]); // "[object Array]"
4. 其他方法
Array.isArray()
:专用于检测数组。isNaN()
:检测是否为NaN
(需注意类型转换问题)。
作用域与作用域链
1. 作用域类型
-
全局作用域:在函数或代码块外定义的变量,全局可访问。
-
函数作用域 :
var
声明的变量在函数内有效。 -
块级作用域 :
let/const
声明的变量在代码块({}
)内有效(ES6+)
2. 作用域链
-
定义:当前作用域 → 外层作用域 → ... → 全局作用域的链式结构。
-
查找规则:变量不存在时,沿链向上查找,直至全局作用域(未找到则报错)
-
闭包原理 :内层函数保留对外部作用域的引用(如计数器函数保留
count
变量)。
javascript
function outer() {
const outerVar = "outer";
return function inner() {
console.log(outerVar); // 通过作用域链访问外部变量
};
}
原型与原型链
1. 原型(Prototype)
-
构造函数原型 :每个函数都有
prototype
属性,用于共享方法。 -
对象隐式原型 :每个对象都有
__proto__
,指向其构造函数的prototype
ini
function Person(name) { this.name = name; }
Person.prototype.sayHello = function() { console.log(this.name); };
const john = new Person("John");
john.__proto__ === Person.prototype; // true
2. 原型链
- 继承机制 :对象通过
__proto__
链继承属性和方法,终点为null
。 - 查找规则 :先自身查找 → 沿
__proto__
链向上查找。
ruby
john.hasOwnProperty("name"); // true(来自 Object 原型方法)
JavaScript 中 this
的指向
this
的值由 函数调用方式 决定,而非定义位置
1. 默认绑定
- 全局上下文 :
this
指向window
(浏览器)或global
(Node.js)。 - 严格模式 :
this
为undefined
。
scss
function showThis() { console.log(this); }
showThis(); // 浏览器中输出 window
2. 隐式绑定
- 对象方法 :
this
指向调用该方法的对象。
javascript
const obj = {
value: 42,
logValue() { console.log(this.value); }
};
obj.logValue(); // 42
3. 显式绑定
call
/apply
/bind
:强制指定this
。
javascript
function greet() { console.log(this.name); }
const user = { name: "Alice" };
greet.call(user); // "Alice"
4. 构造函数绑定
new
操作符:this
指向新创建的对象实例。
ini
function Car(brand) { this.brand = brand; }
const myCar = new Car("Tesla");
5. 箭头函数
- 词法绑定 :
this
继承外层作用域的this
,无法通过call
修改。
javascript
const obj = {
value: "Hello",
arrowFunc: () => console.log(this.value) // this 指向全局对象
};
事件循环(Event Loop)
JavaScript 通过 事件循环 处理异步任务,核心流程为:
-
同步代码 :优先执行调用栈中的同步任务(如
console.log
)。 -
微任务队列 :清空所有微任务(如
Promise.then
、MutationObserver
)。 -
渲染更新 :执行
requestAnimationFrame
回调,更新页面渲染。 -
宏任务队列 :执行一个宏任务(如
setTimeout
、DOM 事件
),循环往复= 执行优先级 :同步代码 → 微任务 → 渲染 → 宏任务
示例:
javascript
setTimeout(() => console.log('宏任务'), 0);
Promise.resolve().then(() => console.log('微任务'));
console.log('同步代码');
// 输出顺序:同步代码 → 微任务 → 宏任务
对闭包的理解
闭包是函数和其定义时的词法环境的引用的组合。闭包允许函数访问其定义时的作用域中的变量,即使该函数在其他作用域中被调用。
闭包的优缺点
-
优点:
- 可以访问和修改外部函数的变量,实现数据封装和信息隐藏。
- 实现函数的持久状态,用于事件处理、回调函数等场景。
-
缺点:
- 如果不正确使用,可能导致内存泄漏,因为闭包会捕获外部变量,阻止其被垃圾回收。
闭包的应用场景
- 模块模式:通过闭包实现模块的私有变量和方法。
- 事件处理:在事件回调函数中使用闭包访问外部变量。
- 函数节流和防抖:利用闭包保存上次执行的时间或函数调用的状态。
scss
// 闭包示例
function outer() {
let count = 0;
return function inner() {
count++;
console.log(count);
};
}
const counter = outer();
counter(); // 1
counter(); // 2
counter(); // 3
new
运算符底层原理
1. 四步核心流程
当执行 new Constructor()
时,发生以下操作:
-
创建空对象 :生成一个新对象
obj = {}
。 -
绑定原型链 :设置
obj.__proto__ = Constructor.prototype
。 -
绑定
this
:执行构造函数,this
指向obj
。 -
处理返回值 :若构造函数返回对象,则替代
obj
;否则返回obj
手写模拟 new
:
javascript
javascript
function myNew(Constructor, ...args) {
const obj = Object.create(Constructor.prototype);
const result = Constructor.apply(obj, args);
return result instanceof Object ? result : obj;
}
2. 易错场景与规避
- 忘记
new
:构造函数直接调用导致this
指向全局(严格模式报错):
ini
function Person(name) {
this.name = name;
}
const p = Person('Alice'); // 错误!this 指向 window
- 构造函数返回对象:可能中断原型链:
javascript
function Dog() { return { name: '旺财' }; }
const dog = new Dog();
dog instanceof Dog; // false(原型链断裂)
ES6 新特性详解(ECMAScript 2015)
ES6(ECMAScript 2015)是 JavaScript 语言的一次重大升级,引入了诸多现代化特性,显著提升了开发效率和代码可维护性。以下是其核心新特性及实际应用场景:
一、变量声明:块级作用域与常量
-
**
let
和const
**-
块级作用域 :
let
和const
声明的变量仅在代码块(如{}
)内有效,解决了var
的变量提升问题 -
不可重复声明:同一作用域内禁止重复声明变量,避免命名冲突
-
常量
const
:声明时必须赋值且不可修改(对象属性或数组元素可修改)
ini{ let x = 10; const PI = 3.14; // PI = 3.15; // 报错 }
-
二、函数增强:箭头函数与默认参数
-
箭头函数(Arrow Functions)
-
简洁语法 :省略
function
关键字,单行表达式可隐式返回 -
词法
this
:继承外层作用域的this
,适合回调函数和避免this
绑定问题
javascriptconst add = (a, b) => a + b; setTimeout(() => console.log(this.value), 100); // this 指向外层
-
-
默认参数
函数参数支持默认值,简化条件判断
iniconst greet = (name = "Guest") => `Hello, ${name}!`;
三、字符串处理:模板字符串与扩展方法
-
模板字符串
使用反引号 ````` 和
${}
嵌入变量或表达式,支持多行文本javascriptconst name = "Alice"; console.log(`Hello, ${name}! Today is ${new Date().toDateString()}.`);
-
新增字符串方法
includes()
:检查子字符串是否存在padStart()
/padEnd()
:填充字符串至指定长度replaceAll()
:全局替换(ES12 特性)
四、解构赋值与扩展运算符
-
解构赋值
从数组或对象中提取值并赋值,简化代码
arduino// 数组解构 const [a, b] = [1, 2]; // 对象解构 const { name, age } = { name: "Alice", age: 25 };
-
扩展运算符(
...
)展开数组或对象,用于合并、克隆或函数传参
iniconst mergedArr = [...arr1, ...arr2]; const clonedObj = { ...original };
五、数据结构:Set、Map 与 Symbol
-
Set 和 Map
-
Set:存储唯一值,自动去重
-
Map :键值对集合,支持任意类型键(优于
Object
)
dartconst unique = new Set([1, 2, 2, 3]); // {1, 2, 3} const map = new Map(); map.set("key", "value");
-
-
Symbol
创建唯一标识符,用于避免对象属性名冲突
iniconst sym = Symbol("unique"); const obj = { [sym]: "private" };
六、异步处理:Promise
-
Promise 对象
管理异步操作状态(Pending → Fulfilled/Rejected),支持链式调用
inifetchData() .then(data => process(data)) .catch(error => console.error(error));
-
静态方法
-
Promise.all
:并行执行多个 Promise,全成功时返回结果数组 -
Promise.race
:返回首个完成的 Promise 结果
-
七、面向对象:类与继承
-
类(Class)
提供更清晰的语法定义构造函数和方法
javascriptclass Animal { constructor(name) { this.name = name; } speak() { console.log(`${this.name} makes noise.`); } } class Dog extends Animal { speak() { console.log(`${this.name} barks!`); } }
八、模块化:import
与 export
-
模块化语法
支持将代码拆分到不同文件,通过
import
和export
管理依赖javascript// math.js export const PI = 3.14; // app.js import { PI } from './math.js';