
核心原理:JavaScript 是单线程语言,事件循环是处理异步操作的机制,负责协调宏任务与微任务的执行顺序。
1)、宏任务(Macro Task):
包含script(整体代码)、setTimeout、setInterval、setImmediate(Node)、I/O 操作、UI 渲染(浏览器)等。
特点:执行完一个宏任务后,会先清空微任务队列,再执行下一个宏任务。
2)、微任务(Micro Task):
包含Promise.then/catch/finally、async/await(本质是 Promise 的语法糖)、process.nextTick(Node,优先级高于其他微任务)、MutationObserver(浏览器)等。
特点:微任务队列的执行优先级高于宏任务,同一轮事件循环中,所有微任务会在宏任务之间被 “插队” 执行。
那宏任务和微任务在执行过程中遵循怎样的顺序呢?接下来一起看一看。
2、Node 与浏览器的差异:
1)、浏览器:宏任务队列简单,按 “执行一个宏任务→清空微任务” 循环。
2)、Node:宏任务队列分为 6 个阶段(按顺序执行):
timers(处理setTimeout/setInterval)→pending callbacks(处理延迟的 I/O 回调)→idle, prepare(内部阶段)→poll(处理 I/O 回调,可能阻塞)→check(处理setImmediate)→close callbacks(处理关闭回调,如socket.on('close'))。
微任务在每个宏任务阶段结束后执行,且process.nextTick优先级高于其他微任务。
所谓promise,它是用于解决异步编程的回调地狱问题,代表一个异步操作的最终完成(或失败)及结果值。
1)、状态变化:
pending(等待中);fulfilled(成功,调用resolve())或rejected(失败,调用reject());pending→fulfilled或pending→rejected),不可逆。
2)、链式调用:
Promise.then()返回一个新的 Promise(非原 Promise),因此可链式调用。
后一个then的回调参数是前一个then的返回值(若返回非 Promise 值,会被包装为fulfilled状态的 Promise;若返回 Promise,则等待其状态变化)。
3)、异常捕获:catch()捕获链式调用中的任意错误(包括reject和代码报错);then(onFulfilled, onRejected)的第二个参数也可捕获错误,但优先级低于catch(且无法捕获onFulfilled中的错误);unhandledrejection事件。所谓async/await,它其实是Promise 的语法糖,使异步代码写法更接近同步代码,解决回调地狱。
async:修饰的函数返回值必为 Promise(若返回非 Promise 值,会自动包装为fulfilled状态的 Promise);
await:只能在async函数中使用,后跟一个 Promise,会暂停函数执行,等待 Promise 状态变为fulfilled后,继续执行并返回结果;若 Promise 被rejected,则抛出异常。
try/catch捕获await的异常(替代 Promise 的catch):看下面这段代码:
asyncfunctionfetchData(){
try{
const res =awaitfetch('url');// 若fetch失败,直接进入catch
const data =await res.json();
return data;
}catch(err){
console.error('出错了:', err);
}
}
async/await通过线性代码结构替代嵌套,使逻辑更清晰:再看下面这段代码:
// 回调地狱(嵌套)
ajax1(()=>{
ajax2(()=>{
ajax3(()=>{/* ... */});
});
});
// async/await(线性)
asyncfunctionrun(){
awaitajax1();
awaitajax2();
awaitajax3();
}
1)、创建节点:
document.createElement(tagName):创建元素节点(如div);document.createTextNode(text):创建文本节点;document.createDocumentFragment():创建文档片段(批量插入节点时使用,减少重绘)。2)、删除节点:
parentNode.removeChild(childNode):通过父节点删除子节点(返回被删除节点);node.remove():直接删除节点(IE 不兼容)。
3)、修改节点:
node.innerHTML(HTML 内容)、node.textContent(纯文本);node.setAttribute(name, value)、node.removeAttribute(name);node.style.cssProperty(如node.style.color = 'red');parentNode.appendChild(childNode)(若节点已存在,会移动位置)、parentNode.insertBefore(newNode, referenceNode)(插入到参考节点前)。事件流描述事件在 DOM 树中的传播路径,分为三个阶段(W3C 标准):
补充一个问题:还有其他的标准吗?
window向下传播至目标节点的父节点(由外到内)。window(由内到外)。示例:点击div内部的button,事件流为:
window → document → body → div(捕获)→ button(目标)→ div(冒泡)→ body → document → window。
原理:利用事件冒泡机制,将子元素的事件监听委托给父元素(或更上层元素),由父元素统一处理。
li)的点击事件(如动态渲染的列表);td)的交互事件等。看下面这段代码:
const ul =document.querySelector('ul');
ul.addEventListener('click',(e)=>{
if(e.target.tagName==='LI'){// 判断事件源是否为目标子元素
console.log('点击了li:', e.target.textContent);
}
});
在上面的代码中,我们可以看到子元素的点击事件其实是父元素来处理的。这就涉及到了事件委托。想一想,在日常的开发中,还有哪些场景会涉及到事件委托?
event.stopPropagation():
阻止事件在冒泡阶段向上传播(不影响当前节点的其他事件处理函数,也不阻止默认行为)。
注意:stopImmediatePropagation()会同时阻止事件传播和当前节点后续的事件处理函数执行。event.preventDefault():
阻止事件的默认行为(如a标签跳转、表单提交、右键菜单弹出等),但不影响事件冒泡。stopPropagation()是否preventDefault()否是核心作用:实现跨组件(或模块)的通信,解耦事件的发送者和接收者。
1)、实现原理: 基于发布 - 订阅模式(Pub/Sub),维护一个事件注册表(键为事件名,值为回调函数数组),提供三个核心方法:
on(eventName, callback):订阅事件,注册回调;emit(eventName, ...args):发布事件,触发所有订阅该事件的回调(传入参数);off(eventName, callback):取消订阅,移除指定回调(若未传callback,移除该事件所有回调
来看下面这段代码:
class EventBus
{
constructor(){
this.events={};// 事件注册表:{ 'event1': [cb1, cb2], ... }
}
on(event, cb){
if(!this.events[event])this.events[event]=[];
this.events[event].push(cb);
}
emit(event,...args){
if(this.events[event]){
this.events[event].forEach(cb=>cb(...args));
}
}
off(event, cb){
if(!this.events[event])return;
if(cb){
this.events[event]=this.events[event].filter(fn=> fn !== cb);
}else{
this.events[event]=[];// 移除该事件所有回调
}
}
}
// 使用
const bus =newEventBus();
bus.on('msg',(data)=>console.log('收到:', data));
bus.emit('msg','Hello EventBus');// 输出:收到:Hello EventBus
跨域:浏览器的同源策略(协议、域名、端口三者一致为同源)限制不同源的页面之间的资源交互(如 AJAX 请求)。
那我们怎样去解决跨域呢?本文将介绍几种常见的方案:
1)、CORS(跨域资源共享) 原理:服务器通过 HTTP 响应头告知浏览器允许跨域请求。 实现:服务器端设置响应头:
Access-Control-Allow-Origin:https://example.com # 允许的源(*表示允许所有)
Access-Control-Allow-Methods:GET, POST, PUT # 允许的方法
Access-Control-Allow-Headers:Content-Type # 允许的请求头
优势:支持所有 HTTP 方法,安全性高;缺点:需服务器配合。
2)、JSONP
原理:利用script标签不受同源策略限制的特性,通过动态创建script标签加载跨域脚本,脚本执行时调用预设的回调函数传递数据。
实现:
// 客户端
functionhandleData(data){console.log('JSONP数据:', data);}
const script =document.createElement('script');
script.src='https://api.example.com/data?callback=handleData';// 服务器返回:handleData({...})
document.body.appendChild(script);
优势:兼容性好(支持老浏览器);缺点:仅支持 GET 方法,安全性低(可能注入恶意代码)。
3)、使用代理服务器 原理:同源策略仅限制浏览器,服务器之间无跨域限制。通过同源的代理服务器转发请求(浏览器→代理服务器→目标服务器)。 实现(以 webpack-dev-server 为例):
// webpack.config.js
module.exports={
devServer:{
proxy:{
'/api':{// 匹配以/api开头的请求
target:'https://api.example.com',// 目标服务器
changeOrigin:true,// 欺骗目标服务器,使其认为请求来自代理服务器
pathRewrite:{'^/api':''}// 移除/api前缀
}
}
}
};
优势:前端无需修改代码,支持所有 HTTP 方法;缺点:需额外配置代理服务。
好啦,本文为大家详细介绍了JS进阶相关的基础知识,基本是覆盖了开发中大多数的场景,感谢你的阅读,我们下期再见~