跳到主要内容 前端面试题 JavaScript 核心知识点详解 | 极客日志
JavaScript Node.js AI 大前端 算法
前端面试题 JavaScript 核心知识点详解 20 道高频前端 JavaScript 面试题,涵盖 Proxy 深度监听、解构赋值、作用域链与闭包、事件冒泡机制、async/await 原理、script 标签位置、Portal 事件传播、Promise 应用、DOM/BOM 区别、网络请求流程、new 操作符、低代码概念、Map/Set 用法、最大子序和算法、HTTPS 握手过程、二叉树实现、防抖节流、类型转换机制、移动端适配及请求取消等核心知识点。每个问题均提供代码示例与原理分析,适合前端开发者复习与面试准备。
PentesterX 发布于 2026/3/28 更新于 2026/4/16 2 浏览
前端面试题详细解答(JavaScript 篇)
1. Proxy 能够监听到对象中的对象的引用吗?
可以监听,但需要递归代理
const obj = {a : 1 , b : {c : 2 }};
const proxy = new Proxy (obj, {
get (target, key ) {
console .log (`get ${key} ` );
return target[key];
},
set (target, key, value ) {
console .log (`set ${key} = ${value} ` );
target[key] = value;
return true ;
}
});
proxy.a = 3 ;
proxy.b .c = 4 ;
实现深度监听
function deepProxy (obj, handler ) {
if (typeof obj !== 'object' || obj === null ) return obj;
for (let key in obj) {
if (typeof obj[key] === 'object' && obj[key] !== null ) {
obj[key] = deepProxy (obj[key], handler);
}
}
return new Proxy (obj, handler);
}
const obj = {a : 1 , b : {c : 2 , d : {e : 3 }}};
const proxy = deepProxy (obj, {
get (target, key ) {
console .log (`get ${key} ` );
return target[key];
},
set (target, key, value ) {
console .log (`set ${key} = ${JSON .stringify(value)} ` );
target[key] = value;
return true ;
}
});
proxy.b .c = 4 ;
proxy.b .d .e = 5 ;
Vue 3 的响应式实现原理
import { reactive } from 'vue' ;
const state = reactive ({
user : {
name : '张三' ,
address : { city : '北京' , detail : '朝阳区' }
}
});
state.user .address .city = '上海' ;
注意事项
性能考虑 :递归代理会带来性能开销
循环引用 :需要处理循环引用的情况
避免重复代理 :同一对象不应被代理多次
2. 如何让 var [a,b]={a:1,b:2} 解构赋值成功?
正确写法
var {a, b} = {a : 1 , b : 2 };
console .log (a);
console .log (b);
var {a : x, b : y} = {a : 1 , b : 2 };
console .log (x);
console .log (y);
var {a = 0 , b = 0 , c = 0 } = {a : 1 , b : 2 };
console .log (a, b, c);
var {a : {x, y}, b} = {a : {x : 1 , y : 2 }, b : 3 };
console .log (x, y, b);
题目中错误的原因
var [a, b] = {a : 1 , b : 2 };
var [a, b] = [1 , 2 ];
实际应用场景
function getUserInfo ({name, age, city = '未知' } ) {
console .log (`${name} , ${age} 岁,来自${city} ` );
}
getUserInfo ({name : '张三' , age : 25 });
function getCoordinates ( ) {
return {x : 100 , y : 200 };
}
var {x, y} = getCoordinates ();
var a = 1 , b = 2 ;
[a, b] = [b, a];
console .log (a, b);
3. 什么是作用域链?
作用域链的概念 作用域链是 JavaScript 查找变量的一套规则,决定了代码区块中变量和其他资源的访问权限。
var globalVar = 'global' ;
function outer ( ) {
var outerVar = 'outer' ;
function inner ( ) {
var innerVar = 'inner' ;
console .log (innerVar);
console .log (outerVar);
console .log (globalVar);
console .log (nonExist);
}
inner ();
}
outer ();
作用域链的创建过程
function foo ( ) {
var a = 1 ;
function bar ( ) {
var b = 2 ;
console .log (a + b);
}
bar ();
}
foo ();
作用域链与闭包
function createCounter ( ) {
let count = 0 ;
return {
increment : function ( ) {
count++;
return count;
},
decrement : function ( ) {
count--;
return count;
},
getCount : function ( ) {
return count;
}
};
}
const counter = createCounter ();
console .log (counter.increment ());
console .log (counter.increment ());
ES6 的作用域变化
{ var a = 1 ; let b = 2 ; const c = 3 ; }
console .log (a);
for (var i = 0 ; i < 3 ; i++) {
setTimeout (() => {
console .log (i);
}, 100 );
}
for (let j = 0 ; j < 3 ; j++) {
setTimeout (() => {
console .log (j);
}, 100 );
}
4. 不会冒泡的事件有哪些?
不会冒泡的事件列表
element.addEventListener ('focus' , (e ) => {
console .log ('focus 不会冒泡' );
}, false );
element.addEventListener ('mouseenter' , (e ) => {
console .log ('mouseenter 不会冒泡' );
});
img.addEventListener ('load' , (e ) => {
console .log ('load 不会冒泡' );
});
window .addEventListener ('resize' , (e ) => {
console .log ('resize 不会冒泡' );
});
element.addEventListener ('scroll' , (e ) => {
console .log ('scroll 不会冒泡' );
});
事件冒泡机制对比 <div id ="parent" >
<button id ="child" > 点击我</button >
</div >
<script >
const parent = document .getElementById ('parent' );
const child = document .getElementById ('child' );
parent.addEventListener ('click' , () => {
console .log ('父元素点击事件 - 会冒泡' );
});
child.addEventListener ('click' , (e ) => {
console .log ('子元素点击事件' );
});
parent.addEventListener ('focus' , () => {
console .log ('父元素 focus 事件 - 不会触发,因为不会冒泡' );
}, true );
child.addEventListener ('focus' , () => {
console .log ('子元素获得焦点' );
});
</script >
实际应用
document .addEventListener ('focus' , (e ) => {
console .log ('捕获阶段监听到 focus:' , e.target );
}, true );
const inputs = document .querySelectorAll ('input' );
inputs.forEach (input => {
input.addEventListener ('focus' , handleFocus);
});
element.addEventListener ('focusin' , (e ) => {
console .log ('focusin 会冒泡' );
});
element.addEventListener ('mouseover' , (e ) => {
console .log ('mouseover 会冒泡' );
});
5. async、await 的实现原理
async/await 的本质 async/await 是 Generator 函数的语法糖,基于 Promise 实现。
async function foo ( ) {
const result = await someAsyncFunction ();
return result;
}
function * fooGenerator ( ) {
const result = yield someAsyncFunction ();
return result;
}
实现原理:Generator + 自动执行器
function * myGenerator ( ) {
const a = yield Promise .resolve (1 );
const b = yield Promise .resolve (2 );
return a + b;
}
const gen = myGenerator ();
gen.next ().value .then (val1 => {
gen.next (val1).value .then (val2 => {
const result = gen.next (val2);
console .log (result.value );
});
});
function asyncToGenerator (generatorFunc ) {
return function (...args ) {
const gen = generatorFunc.apply (this , args);
return new Promise ((resolve, reject ) => {
function step (key, arg ) {
let result;
try {
result = gen[key](arg);
} catch (error) {
return reject (error);
}
const { value, done } = result;
if (done) {
return resolve (value);
} else {
return Promise .resolve (value).then (val => step ('next' , val), err => step ('throw' , err));
}
}
step ('next' );
});
};
}
const myAsyncFunc = asyncToGenerator (myGenerator);
myAsyncFunc ().then (result => console .log (result));
Babel 编译后的 async/await
async function test ( ) {
const a = await 1 ;
const b = await Promise .resolve (2 );
return a + b;
}
function _asyncToGenerator (fn ) {
return function ( ) {
var self = this ,
args = arguments ;
return new Promise (function (resolve, reject ) {
var gen = fn.apply (self, args);
function _next (value ) {
asyncGeneratorStep (gen, resolve, reject, _next, _throw, "next" , value);
}
function _throw (err ) {
asyncGeneratorStep (gen, resolve, reject, _next, _throw, "throw" , err);
}
_next (undefined );
});
};
}
function asyncGeneratorStep (gen, resolve, reject, _next, _throw, key, arg ) {
try {
var info = gen[key](arg);
var value = info.value ;
} catch (error) {
reject (error);
return ;
}
if (info.done ) {
resolve (value);
} else {
Promise .resolve (value).then (_next, _throw);
}
}
var test = _asyncToGenerator (function * () {
const a = yield 1 ;
const b = yield Promise .resolve (2 );
return a + b;
});
错误处理机制
async function fetchData ( ) {
try {
const response = await fetch ('/api/data' );
const data = await response.json ();
return data;
} catch (error) {
console .error ('请求失败:' , error);
throw error;
}
}
function fetchDataPromise ( ) {
return fetch ('/api/data' ).then (response => response.json ()).catch (error => {
console .error ('请求失败:' , error);
throw error;
});
}
async function multiFetch ( ) {
try {
const user = await fetchUser ();
} catch (error) {
console .error ('获取用户失败' );
}
try {
const posts = await fetchPosts ();
} catch (error) {
console .error ('获取帖子失败' );
}
try {
const [user, posts] = await Promise .all ([fetchUser (), fetchPosts ()]);
} catch (error) {
console .error ('某个请求失败' );
}
}
6. script 标签放在 header 里和放在 body 底部里有什么区别?
位置对比分析
<!DOCTYPE html >
<html >
<head >
<script src ="header-script.js" > </script >
</head >
<body >
<div > 页面内容</div >
</body >
</html >
<!DOCTYPE html >
<html >
<head >
</head >
<body >
<div > 页面内容</div >
<script src ="body-script.js" > </script >
</body >
</html >
性能影响对比
console .log ('脚本开始执行' );
const start = performance.now ();
while (performance.now () - start < 2000 ) {
}
console .log ('脚本执行完成' );
现代最佳实践 <!DOCTYPE html >
<html >
<head >
<script src ="analytics.js" async > </script >
<style >
.above-the-fold { styles }
</style >
<link rel ="stylesheet" href ="non-critical.css" media ="print" onload ="this.media='all'" >
<link rel ="preload" href ="critical-script.js" as ="script" >
</head >
<body >
<script src ="non-critical-script.js" > </script >
<script src ="critical-script.js" > </script >
</body >
</html >
script 属性的使用
<script src ="async-script.js" async > </script >
<script src ="defer-script1.js" defer > </script >
<script src ="defer-script2.js" defer > </script >
<script type ="module" src ="module.js" > </script >
<script >
const script = document .createElement ('script' );
script.src = 'dynamic.js' ;
script.async = true ;
document .body .appendChild (script);
</script >
7. 子组件是一个 Portal,发生点击事件能冒泡到父组件吗?
Portal 事件冒泡机制
import React from 'react' ;
import ReactDOM from 'react-dom' ;
function PortalComponent ({ children } ) {
const portalRoot = document .getElementById ('portal-root' );
return ReactDOM .createPortal (children, portalRoot);
}
function ParentComponent ( ) {
const handleClick = (e ) => {
console .log ('父组件收到点击事件' );
console .log ('事件目标:' , e.target );
console .log ('当前目标:' , e.currentTarget );
};
return (
<div onClick ={handleClick} >
<h3 > 父组件区域</h3 >
{/* 普通子组件 */}
<div className ="normal-child" > 普通子组件(点击会冒泡) </div >
{/* Portal 子组件 */}
<PortalComponent >
<div className ="portal-child" > Portal 子组件(点击也会冒泡到 React 树中的父组件) </div >
</PortalComponent >
</div >
);
}
ReactDOM .render (<ParentComponent /> , document .getElementById ('root' ));
事件冒泡的两种情况
class EventTest extends React.Component {
componentDidMount ( ) {
document .getElementById ('portal-root' ).addEventListener ('click' , (e ) => {
console .log ('portal-root 原生事件' );
});
document .querySelector ('.normal-child' ).addEventListener ('click' , (e ) => {
console .log ('普通子组件原生事件' );
});
}
handleReactClick = (e ) => {
console .log ('React 合成事件触发' );
console .log ('e.nativeEvent:' , e.nativeEvent );
}
render ( ) {
return (
<div onClick ={this.handleReactClick} >
<div className ="normal-child" > 普通子组件</div >
{ReactDOM.createPortal(
<div className ="portal-child" > Portal 子组件</div > ,
document.getElementById('portal-root')
)}
</div >
);
}
}
实际应用场景
function Modal ({ isOpen, onClose, children } ) {
if (!isOpen) return null ;
return ReactDOM .createPortal (
<div className ="modal-overlay" onClick ={onClose} >
<div className ="modal-content" onClick ={e => e.stopPropagation()}>
{children}
<button onClick ={onClose} > 关闭</button >
</div >
</div > ,
document .body
);
}
function App ( ) {
const [isModalOpen, setModalOpen] = useState (false );
const handleButtonClick = ( ) => setModalOpen (true );
const handleModalClose = ( ) => setModalOpen (false );
return (
<div >
<button onClick ={handleButtonClick} > 打开 Modal</button >
<Modal isOpen ={isModalOpen} onClose ={handleModalClose} >
<h2 > Modal 标题</h2 >
<p > Modal 内容...</p >
</Modal >
</div >
);
}
function Tooltip ({ children, content } ) {
const [isVisible, setIsVisible] = useState (false );
const tooltipRef = useRef ();
useEffect (() => {
if (isVisible && tooltipRef.current ) {
}
}, [isVisible]);
return (
<>
<span onMouseEnter ={() => setIsVisible(true)} onMouseLeave={() => setIsVisible(false)}>{children}</span >
{isVisible && ReactDOM.createPortal(
<div ref ={tooltipRef} className ="tooltip" > {content}</div > ,
document.body
)}
</>
);
}
8. 使用 Promise 实现红绿灯交替重复亮
function trafficLight ( ) {
function light (color, duration ) {
return new Promise (resolve => {
console .log (`${color} 灯亮` );
setTimeout (() => {
console .log (`${color} 灯灭` );
resolve ();
}, duration * 1000 );
});
}
function runCycle ( ) {
return light ('红' , 3 )
.then (() => light ('黄' , 1 ))
.then (() => light ('绿' , 2 ));
}
function infiniteLoop ( ) {
return runCycle ().then (infiniteLoop);
}
infiniteLoop ().catch (err => {
console .error ('红绿灯出错:' , err);
});
}
async function trafficLightAsync ( ) {
async function light (color, duration ) {
console .log (`${color} 灯亮` );
await new Promise (resolve => setTimeout (resolve, duration * 1000 ));
console .log (`${color} 灯灭` );
}
try {
while (true ) {
await light ('红' , 3 );
await light ('黄' , 1 );
await light ('绿' , 2 );
}
} catch (error) {
console .error ('红绿灯出错:' , error);
}
}
class TrafficLight {
constructor ( ) {
this .isRunning = false ;
this .cycleCount = 0 ;
this .lights = [
{ color : '红' , duration : 3 },
{ color : '黄' , duration : 1 },
{ color : '绿' , duration : 2 }
];
}
async lightOn (color, duration ) {
console .log (`🚦 ${color} 灯亮 - 持续${duration} 秒` );
await new Promise (resolve => setTimeout (resolve, duration * 1000 ));
console .log (`🚦 ${color} 灯灭` );
}
async start ( ) {
if (this .isRunning ) return ;
this .isRunning = true ;
console .log ('红绿灯开始运行' );
try {
while (this .isRunning ) {
for (const light of this .lights ) {
if (!this .isRunning ) break ;
await this .lightOn (light.color , light.duration );
}
this .cycleCount ++;
console .log (`已完成 ${this .cycleCount} 个循环` );
}
} catch (error) {
console .error ('红绿灯异常:' , error);
this .stop ();
}
}
stop ( ) {
this .isRunning = false ;
console .log ('红绿灯停止' );
}
async manualControl (color, duration ) {
this .stop ();
await this .lightOn (color, duration);
this .start ();
}
}
const trafficLight = new TrafficLight ();
trafficLight.start ();
setTimeout (() => {
trafficLight.stop ();
}, 10000 );
function visualTrafficLight ( ) {
const colors = {
red : '#ff0000' ,
yellow : '#ffff00' ,
green : '#00ff00' ,
off : '#333333'
};
const lightElement = document .getElementById ('traffic-light' );
const redLight = lightElement.querySelector ('.red' );
const yellowLight = lightElement.querySelector ('.yellow' );
const greenLight = lightElement.querySelector ('.green' );
function setLight (element, color ) {
element.style .backgroundColor = color;
}
async function changeLight (color, duration ) {
setLight (redLight, colors.off );
setLight (yellowLight, colors.off );
setLight (greenLight, colors.off );
switch (color) {
case '红' : setLight (redLight, colors.red ); break ;
case '黄' : setLight (yellowLight, colors.yellow ); break ;
case '绿' : setLight (greenLight, colors.green ); break ;
}
console .log (`${color} 灯亮` );
await new Promise (resolve => setTimeout (resolve, duration * 1000 ));
console .log (`${color} 灯灭` );
}
async function run ( ) {
while (true ) {
await changeLight ('红' , 3 );
await changeLight ('绿' , 2 );
await changeLight ('黄' , 1 );
}
}
return { run };
}
9. 什么是 DOM 和 BOM?
DOM(文档对象模型)
const element = document .getElementById ('myId' );
element.innerHTML = '新内容' ;
element.style .color = 'red' ;
BOM(浏览器对象模型)
console .log (window .innerWidth );
console .log (window .location );
console .log (window .navigator );
console .log (window .history );
console .log (window .screen );
console .log (window .localStorage );
console .log (location.href );
console .log (location.protocol );
console .log (location.host );
console .log (location.pathname );
console .log (location.search );
history.back ();
history.forward ();
history.go (-2 );
history.pushState ({}, '' , '/new-url' );
console .log (navigator.userAgent );
console .log (navigator.platform );
console .log (navigator.language );
console .log (navigator.onLine );
console .log (screen.width );
console .log (screen.height );
console .log (screen.availWidth );
const result = confirm ('确定要删除吗?' );
const name = prompt ('请输入姓名' , '张三' );
alert ('操作成功!' );
DOM 和 BOM 的区别
function initPage ( ) {
const isMobile = /Mobi|Android/i .test (navigator.userAgent );
if (isMobile) {
document .body .classList .add ('mobile' );
}
window .addEventListener ('resize' , () => {
const container = document .getElementById ('container' );
if (window .innerWidth < 768 ) {
container.classList .add ('collapsed' );
} else {
container.classList .remove ('collapsed' );
}
});
const theme = localStorage .getItem ('theme' ) || 'light' ;
document .body .setAttribute ('data-theme' , theme);
}
10. 简单描述从输入网址到页面显示的过程
完整流程概述 1. 输入 URL 并回车
2. 浏览器解析 URL
3. DNS 解析(域名 -> IP 地址)
4. 建立 TCP 连接(三次握手)
5. 发送 HTTP 请求
6. 服务器处理请求
7. 服务器返回 HTTP 响应
8. 浏览器接收响应
9. 浏览器解析 HTML,构建 DOM 树
10. 解析 CSS,构建 CSSOM 树
11. 合并 DOM 和 CSSOM,构建渲染树
12. 布局(计算元素位置和大小)
13. 绘制(将像素绘制到屏幕)
14. 合成(层合并,GPU 加速)
详细步骤分析
const url = new URL ('https://www.example.com:443/path?query=1#section' );
console .log (url.protocol );
console .log (url.hostname );
console .log (url.port );
console .log (url.pathname );
console .log (url.search );
console .log (url.hash );
const request = `GET /index.html HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0
Accept: text/html
Connection: keep-alive` ;
const response = `HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 1234
Cache-Control: max-age=3600
<!DOCTYPE html>
<html>...</html>` ;
async function browserRender (html, css, js ) {
const parser = new DOMParser ();
const dom = parser.parseFromString (html, 'text/html' );
const stylesheet = new CSSStyleSheet ();
stylesheet.replaceSync (css);
const renderTree = buildRenderTree (dom, stylesheet);
const layout = calculateLayout (renderTree);
const paintLayers = paint (layout);
compositeLayers (paintLayers);
eval (js);
document .addEventListener ('DOMContentLoaded' , () => {
console .log ('DOM 准备就绪' );
});
window .addEventListener ('load' , () => {
console .log ('页面完全加载' );
});
}
性能优化相关
const performanceMetrics = {
dnsLookup : performance.timing .domainLookupEnd - performance.timing .domainLookupStart ,
tcpConnect : performance.timing .connectEnd - performance.timing .connectStart ,
sslConnect : performance.timing .connectEnd - performance.timing .secureConnectionStart ,
requestResponse : performance.timing .responseEnd - performance.timing .requestStart ,
domParse : performance.timing .domInteractive - performance.timing .responseEnd ,
domReady : performance.timing .domContentLoadedEventEnd - performance.timing .navigationStart ,
pageLoad : performance.timing .loadEventEnd - performance.timing .navigationStart
};
const optimizations = {
dns : '使用 DNS 预连接:<link rel="dns-prefetch" href="//cdn.example.com">' ,
tcp : '使用 HTTP/2,支持多路复用' ,
ssl : '使用 TLS 1.3,会话恢复' ,
request : '减少 HTTP 请求,使用资源合并' ,
render : {
css : '将 CSS 放在头部,避免阻塞渲染' ,
js : '将非关键 JS 放在底部或使用 defer/async' ,
image : '使用懒加载和响应式图片'
},
cache : '合理使用缓存策略' ,
cdn : '使用 CDN 加速静态资源'
};
11. 说说 new 操作符具体干了什么?
new 操作符的执行过程
function myNew (constructor, ...args ) {
const obj = Object .create (constructor.prototype );
const result = constructor.apply (obj, args);
return result instanceof Object ? result : obj;
}
function Person (name, age ) {
this .name = name;
this .age = age;
}
const person1 = new Person ('张三' , 25 );
const person2 = myNew (Person , '李四' , 30 );
console .log (person1);
console .log (person2);
详细步骤分析
function step1 (constructor ) {
const obj = {};
Object .setPrototypeOf (obj, constructor.prototype );
return obj;
}
function step2 (constructor, obj, args ) {
const result = constructor.apply (obj, args);
return result;
}
function step3 (result, obj ) {
if (result && (typeof result === 'object' || typeof result === 'function' )) {
return result;
}
return obj;
}
function createInstance (constructor, ...args ) {
const obj = Object .create (constructor.prototype );
const result = constructor.apply (obj, args);
return result instanceof Object ? result : obj;
}
特殊情况处理
function Foo ( ) {
this .name = 'Foo' ;
return 123 ;
}
const foo = new Foo ();
console .log (foo);
function Bar ( ) {
this .name = 'Bar' ;
return { custom : 'object' };
}
const bar = new Bar ();
console .log (bar);
const ArrowFunc = ( ) => {
this .value = 1 ;
};
try {
const instance = new ArrowFunc ();
} catch (error) {
console .error (error.message );
}
class Animal {
constructor (name ) {
this .name = name;
}
speak ( ) {
console .log (`${this .name} makes a sound` );
}
}
const dog = new Animal ('Dog' );
dog.speak ();
实际应用
function Parent (name ) {
this .name = name;
this .colors = ['red' , 'blue' ];
}
Parent .prototype .sayName = function ( ) {
console .log (this .name );
};
function Child (name, age ) {
Parent .call (this , name);
this .age = age;
}
Child .prototype = Object .create (Parent .prototype );
Child .prototype .constructor = Child ;
Child .prototype .sayAge = function ( ) {
console .log (this .age );
};
const child = new Child ('小明' , 10 );
child.sayName ();
child.sayAge ();
function Singleton ( ) {
if (Singleton .instance ) {
return Singleton .instance ;
}
this .createdAt = new Date ();
Singleton .instance = this ;
}
const s1 = new Singleton ();
const s2 = new Singleton ();
console .log (s1 === s2);
function createUser (role ) {
const user = Object .create (userMethods);
if (role === 'admin' ) {
user.permissions = ['read' , 'write' , 'delete' ];
} else {
user.permissions = ['read' ];
}
return user;
}
const userMethods = {
hasPermission (perm ) {
return this .permissions .includes (perm);
}
};
const admin = createUser ('admin' );
console .log (admin.hasPermission ('delete' ));
12. 说说你对低代码的了解
低代码的核心概念
const lowCodePlatform = {
designer : {
dragAndDrop : true ,
wysiwyg : true ,
componentPalette : ['Button' , 'Form' , 'Table' , 'Chart' ]
},
modelDriven : {
dataModel : '可视化定义数据结构' ,
businessLogic : '配置式业务规则' ,
workflow : '可视化流程设计'
},
codeGeneration : {
frontend : 'Vue/React 代码' ,
backend : 'Java/Node.js 代码' ,
database : 'SQL 脚本'
},
integration : {
apiConnector : 'API 连接器' ,
databaseConnector : '数据库连接' ,
thirdParty : '第三方服务集成'
}
};
低代码的优势和劣势 const lowCodeAnalysis = {
advantages : [
{ name : '开发效率' , description : '减少 70%-80% 的编码工作量' , example : '一个 CRUD 页面传统开发需要 1-2 天,低代码可能只需要 30 分钟' },
{ name : '降低门槛' , description : '业务人员也能参与应用开发' , example : '通过拖拽和配置即可完成简单应用' },
{ name : '标准化' , description : '统一的技术栈和开发规范' , example : '生成的代码遵循最佳实践' },
{ name : '维护成本' , description : '可视化修改,无需深入代码' , example : '业务规则变更可直接在界面调整' }
],
disadvantages : [
{ name : '灵活性限制' , description : '复杂定制化需求难以实现' , example : '特殊动画效果或复杂业务逻辑' },
{ name : '性能问题' , description : '生成的代码可能不够优化' , example : '大型数据表性能可能不如手写代码' },
{ name : '厂商锁定' , description : '迁移到其他平台成本高' , example : '特定平台的配置无法直接迁移' },
{ name : '学习曲线' , description : '需要学习平台特定的概念和操作' , example : '平台的工作流引擎、数据模型等' }
]
};
低代码的实际应用
const internalSystems = ['OA 办公自动化' , 'CRM 客户关系管理' , 'ERP 企业资源计划' , 'HRM 人力资源系统' , '项目管理工具' ];
const dataVisualization = {
components : [
{ type : 'Chart' , config : { chartType : 'line|bar|pie' , dataSource : 'API 或数据库' , filters : ['时间范围' , '部门筛选' ] } },
{ type : 'Dashboard' , config : { layout : '可拖拽栅格' , widgets : ['指标卡' , '趋势图' , '排行榜' ] } }
]
};
const mobileAppBuilder = {
features : ['表单采集' , '信息展示' , '流程审批' , '数据同步' , '消息推送' ],
deployment : ['生成 iOS/Android 原生应用' , '生成微信小程序' , '生成 H5 页面' ]
};
主流低代码平台对比 const platforms = {
outSystems : { type : '专业级低代码' , target : '企业级复杂应用' , features : ['全栈开发' , '高性能' , 'AI 辅助' ], pricing : '较高' },
mendix : { type : '模型驱动低代码' , target : '企业数字化' , features : ['微流' , '领域模型' , '协作开发' ], pricing : '企业级' },
powerApps : { type : '微软生态低代码' , target : 'Office 365 用户' , features : ['Excel 集成' , 'Power Automate' , 'Teams 集成' ], pricing : '按用户订阅' },
amis : { type : '前端低代码' , target : '前端开发者' , features : ['JSON 配置' , '开源' , 'Vue/React 支持' ], pricing : '免费开源' },
h5Dooring : { type : 'H5 页面搭建' , target : '营销页面' , features : ['拖拽生成' , '多模板' , '数据统计' ], pricing : '开源版本免费' }
};
13. Map 和 Set 的用法以及区别
Map(键值对集合)
const map = new Map ();
map.set ('name' , '张三' );
map.set ('age' , 25 );
map.set ({id : 1 }, '对象作为键' );
console .log (map.get ('name' ));
console .log (map.has ('age' ));
console .log (map.size );
map.forEach ((value, key ) => {
console .log (`${key} : ${value} ` );
});
for (let [key, value] of map) {
console .log (key, value);
}
console .log ([...map.keys ()]);
console .log ([...map.values ()]);
console .log ([...map.entries ()]);
map.delete ('age' );
map.clear ();
Set(值集合)
const set = new Set ();
set.add (1 );
set.add (2 );
set.add (2 );
set.add ('hello' );
set.add ({name : 'obj' });
console .log (set.has (1 ));
console .log (set.size );
set.forEach (value => {
console .log (value);
});
for (let value of set) {
console .log (value);
}
const set1 = new Set ([1 , 2 , 3 ]);
const set2 = new Set ([2 , 3 , 4 ]);
const union = new Set ([...set1, ...set2]);
console .log ([...union]);
const intersection = new Set ([...set1].filter (x => set2.has (x)));
console .log ([...intersection]);
const difference = new Set ([...set1].filter (x => !set2.has (x)));
console .log ([...difference]);
set.delete (1 );
set.clear ();
Map 和 Set 的区别 const comparison = {
dataStructure : {
Map : '键值对集合,键可以是任意类型' ,
Set : '值集合,值唯一'
},
initialization : {
Map : 'new Map([["key1", "value1"], ["key2", "value2"]])' ,
Set : 'new Set([1, 2, 3, 3]) // 重复值会被去重'
},
methods : {
Map : ['set(key, value)' , 'get(key)' , 'has(key)' , 'delete(key)' , 'clear()' ],
Set : ['add(value)' , 'has(value)' , 'delete(value)' , 'clear()' ]
},
iteration : {
Map : 'map.forEach((value, key) => {})' ,
Set : 'set.forEach(value => {})'
},
performance : {
Map : '键的查找是 O(1),适合做字典、缓存' ,
Set : '值的查找是 O(1),适合去重、集合运算'
},
useCases : {
Map : ['对象映射' , '数据缓存' , '需要键不是字符串的场景' , '需要保持插入顺序的场景' ],
Set : ['数组去重' , '集合运算' , '存储唯一值' , '标签系统' ]
}
};
实际应用示例
class LRUCache {
constructor (capacity ) {
this .capacity = capacity;
this .cache = new Map ();
}
get (key ) {
if (!this .cache .has (key)) return -1 ;
const value = this .cache .get (key);
this .cache .delete (key);
this .cache .set (key, value);
return value;
}
put (key, value ) {
if (this .cache .has (key)) {
this .cache .delete (key);
} else if (this .cache .size >= this .capacity ) {
const firstKey = this .cache .keys ().next ().value ;
this .cache .delete (firstKey);
}
this .cache .set (key, value);
}
}
function arrayOperations ( ) {
const arr = [1 , 2 , 2 , 3 , 4 , 4 , 5 ];
const uniqueArr = [...new Set (arr)];
console .log (uniqueArr);
function intersection (...arrays ) {
if (arrays.length === 0 ) return [];
let result = new Set (arrays[0 ]);
for (let i = 1 ; i < arrays.length ; i++) {
result = new Set ([...result].filter (x => arrays[i].includes (x)));
}
return [...result];
}
console .log (intersection ([1 , 2 , 3 ], [2 , 3 , 4 ], [3 , 4 , 5 ]));
function hasDuplicates (array ) {
return new Set (array).size !== array.length ;
}
console .log (hasDuplicates ([1 , 2 , 3 , 2 ]));
}
function objectMetadata ( ) {
const users = [
{ id : 1 , name : 'Alice' },
{ id : 2 , name : 'Bob' },
{ id : 3 , name : 'Charlie' }
];
const userMap = new Map (users.map (user => [user.id , user]));
console .log (userMap.get (2 ));
userMap.forEach ((user, id ) => {
userMap.set (id, {...user, lastLogin : new Date (), loginCount : 0 });
});
return userMap;
}
14. 最大子序和
问题描述 给定一个整数数组 nums,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
// 示例 输入:[-2,1,-3,4,-1,2,1,-5,4] 输出:6 解释:连续子数组 [4,-1,2,1] 的和最大,为 6
解法 1:动态规划(Kadane 算法) function maxSubArray (nums ) {
if (!nums || nums.length === 0 ) return 0 ;
let maxSum = nums[0 ];
let currentSum = nums[0 ];
for (let i = 1 ; i < nums.length ; i++) {
currentSum = Math .max (nums[i], currentSum + nums[i]);
maxSum = Math .max (maxSum, currentSum);
}
return maxSum;
}
console .log (maxSubArray ([-2 , 1 , -3 , 4 , -1 , 2 , 1 , -5 , 4 ]));
console .log (maxSubArray ([1 ]));
console .log (maxSubArray ([5 , 4 , -1 , 7 , 8 ]));
解法 2:分治法 function maxSubArrayDivide (nums ) {
return divide (nums, 0 , nums.length - 1 );
function divide (nums, left, right ) {
if (left === right) return nums[left];
const mid = Math .floor ((left + right) / 2 );
const leftMax = divide (nums, left, mid);
const rightMax = divide (nums, mid + 1 , right);
const crossMax = maxCrossing (nums, left, mid, right);
return Math .max (leftMax, rightMax, crossMax);
}
function maxCrossing (nums, left, mid, right ) {
let leftSum = -Infinity ;
let sum = 0 ;
for (let i = mid; i >= left; i--) {
sum += nums[i];
leftSum = Math .max (leftSum, sum);
}
let rightSum = -Infinity ;
sum = 0 ;
for (let i = mid + 1 ; i <= right; i++) {
sum += nums[i];
rightSum = Math .max (rightSum, sum);
}
return leftSum + rightSum;
}
}
console .log (maxSubArrayDivide ([-2 , 1 , -3 , 4 , -1 , 2 , 1 , -5 , 4 ]));
解法 3:返回最大子数组本身 function maxSubArrayWithIndices (nums ) {
if (!nums || nums.length === 0 ) return { sum : 0 , subarray : [] };
let maxSum = nums[0 ];
let currentSum = nums[0 ];
let maxStart = 0 , maxEnd = 0 ;
let currentStart = 0 ;
for (let i = 1 ; i < nums.length ; i++) {
if (nums[i] > currentSum + nums[i]) {
currentSum = nums[i];
currentStart = i;
} else {
currentSum += nums[i];
}
if (currentSum > maxSum) {
maxSum = currentSum;
maxStart = currentStart;
maxEnd = i;
}
}
return {
sum : maxSum,
subarray : nums.slice (maxStart, maxEnd + 1 ),
start : maxStart,
end : maxEnd
};
}
const result = maxSubArrayWithIndices ([-2 , 1 , -3 , 4 , -1 , 2 , 1 , -5 , 4 ]);
console .log (result.sum );
console .log (result.subarray );
console .log (result.start );
console .log (result.end );
解法 4:处理特殊情况 function maxSubArrayEnhanced (nums ) {
if (!nums || nums.length === 0 ) return 0 ;
let allNegative = true ;
let maxElement = -Infinity ;
for (let num of nums) {
if (num >= 0 ) allNegative = false ;
if (num > maxElement) maxElement = num;
}
if (allNegative) return maxElement;
let maxSum = nums[0 ];
let currentSum = nums[0 ];
for (let i = 1 ; i < nums.length ; i++) {
currentSum = Math .max (nums[i], currentSum + nums[i]);
maxSum = Math .max (maxSum, currentSum);
}
return maxSum;
}
console .log (maxSubArrayEnhanced ([-1 , -2 , -3 , -4 ]));
console .log (maxSubArrayEnhanced ([-2 , 1 , -3 , 4 , -1 , 2 , 1 , -5 , 4 ]));
性能对比 const testCases = [
{ name : '小数组' , arr : [1 , 2 , 3 , 4 , 5 ] },
{ name : '混合数组' , arr : [-2 , 1 , -3 , 4 , -1 , 2 , 1 , -5 , 4 ] },
{ name : '全负数' , arr : [-5 , -4 , -3 , -2 , -1 ] },
{ name : '大数组' , arr : Array .from ({ length : 10000 }, (_, i ) => Math .floor (Math .random () * 2000 ) - 1000 ) }
];
function benchmark ( ) {
testCases.forEach (({ name, arr } ) => {
console .time (`动态规划-${name} ` );
maxSubArray (arr);
console .timeEnd (`动态规划-${name} ` );
console .time (`分治法-${name} ` );
maxSubArrayDivide (arr);
console .timeEnd (`分治法-${name} ` );
console .log ('---' );
});
}
15. 说说 https 的握手过程
HTTPS 握手完整流程
const httpsHandshake = {
clientHello : {
protocol : 'TLS 1.2 或 TLS 1.3' ,
random : '客户端随机数' ,
cipherSuites : '支持的加密套件列表' ,
compressionMethods : '压缩方法' ,
extensions : ['服务器名称指示(SNI)' , '支持的椭圆曲线' , '签名算法' ]
},
serverHello : {
protocol : '选择的 TLS 版本' ,
random : '服务器随机数' ,
cipherSuite : '选择的加密套件' ,
compressionMethod : '选择的压缩方法' ,
extensions : '服务器扩展'
},
serverCertificate : {
certificate : '服务器证书链' ,
certificateRequest : '可选,要求客户端提供证书'
},
serverKeyExchange : {
},
serverHelloDone : '表示服务器部分完成' ,
clientKeyExchange : {
encryptedPreMasterSecret : '加密的预主密钥'
},
changeCipherSpec : '通知对方开始使用加密通信' ,
finished : {
message : '加密的 Finished 消息,包含所有握手消息的校验和'
}
};
TLS 1.2 握手详细过程
function tls12HandshakeRSA ( ) {
console .log ('=== TLS 1.2 握手过程(RSA)===' );
const clientHello = {
version : 'TLS 1.2' ,
random : 'client_random' ,
cipherSuites : ['TLS_RSA_WITH_AES_128_GCM_SHA256' , 'TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256' ],
extensions : { server_name : 'example.com' }
};
const serverHello = {
version : 'TLS 1.2' ,
random : 'server_random' ,
cipherSuite : 'TLS_RSA_WITH_AES_128_GCM_SHA256'
};
const serverCertificate = 'certificate_chain' ;
console .log ('Server Hello Done' );
const preMasterSecret = 'pre_master_secret' ;
const encryptedPreMasterSecret = encrypt (preMasterSecret, serverPublicKey);
const clientKeyExchange = {
encryptedPreMasterSecret : encryptedPreMasterSecret
};
const masterSecret = 'master_secret' ;
console .log ('Change Cipher Spec' );
const clientFinished = 'encrypted_finished_message' ;
console .log ('Server Change Cipher Spec' );
const serverFinished = 'encrypted_finished_message' ;
console .log ('握手完成,开始加密通信' );
return {
masterSecret,
clientRandom : clientHello.random ,
serverRandom : serverHello.random
};
}
TLS 1.3 握手过程(更安全高效)
function tls13Handshake ( ) {
console .log ('=== TLS 1.3 握手过程 ===' );
const clientHello = {
version : 'TLS 1.3' ,
random : 'client_random' ,
cipherSuites : ['TLS_AES_128_GCM_SHA256' , 'TLS_CHACHA20_POLY1305_SHA256' ],
extensions : {
supported_versions : 'TLS 1.3' ,
key_share : '客户端的密钥交换参数' ,
signature_algorithms : '支持的签名算法'
}
};
const serverHello = {
version : 'TLS 1.3' ,
random : 'server_random' ,
cipherSuite : 'TLS_AES_128_GCM_SHA256' ,
extensions : {
key_share : '服务器的密钥交换参数'
}
};
console .log ('TLS 1.3 握手完成(1 个 RTT)' );
return {
version : 'TLS 1.3' ,
cipher : 'AES-128-GCM' ,
forwardSecrecy : true
};
}
HTTPS 握手中的关键概念 const httpsConcepts = {
certificateVerification : {
purpose : '验证服务器身份' ,
process : ['验证证书链完整性' , '检查证书是否在有效期内' , '检查域名是否匹配' , '检查是否被吊销(CRL/OCSP)' ]
},
keyExchange : {
RSA : {
description : '服务器证书的公钥加密预主密钥' ,
drawback : '不支持前向保密'
},
DiffieHellman : {
description : '双方交换参数生成共享密钥' ,
advantage : '支持前向保密'
},
ECDHE : {
description : '椭圆曲线版本的 DH' ,
advantage : '更安全,性能更好'
}
},
symmetricCiphers : {
AES : {
modes : ['GCM' , 'CBC' , 'CTR' ],
keyLength : [128 , 256 ]
},
ChaCha20 : 'Google 开发的流密码,移动设备性能好'
},
hashAlgorithms : {
SHA256 : '目前最常用' ,
SHA384 : '更高安全性' ,
SHA512 : '最高安全性'
},
forwardSecrecy : {
definition : '即使服务器私钥泄露,过去的通信也无法被解密' ,
implementation : '使用 DHE 或 ECDHE 密钥交换'
}
};
实际抓包分析
const https = require ('https' );
const tls = require ('tls' );
const options = {
key : '服务器私钥' ,
cert : '服务器证书' ,
ciphers : ['ECDHE-RSA-AES128-GCM-SHA256' , 'ECDHE-ECDSA-AES128-GCM-SHA256' ].join (':' ),
honorCipherOrder : true
};
function connectToHttpsServer ( ) {
const req = https.request ({
hostname : 'example.com' ,
port : 443 ,
path : '/' ,
method : 'GET' ,
secureProtocol : 'TLSv1_2_method' ,
ciphers : 'ECDHE-RSA-AES128-GCM-SHA256' ,
rejectUnauthorized : true
}, (res ) => {
console .log ('状态码:' , res.statusCode );
console .log ('TLS 版本:' , res.socket .getProtocol ());
console .log ('加密套件:' , res.socket .getCipher ());
});
req.on ('error' , (err ) => {
console .error ('HTTPS 请求失败:' , err.message );
});
req.end ();
}
function getCertificateInfo (hostname ) {
const socket = tls.connect (443 , hostname, {
servername : hostname
}, () => {
const cert = socket.getPeerCertificate ();
console .log ('颁发者:' , cert.issuer );
console .log ('有效期:' , cert.valid_from , '至' , cert.valid_to );
console .log ('签名算法:' , cert.signatureAlgorithm );
socket.destroy ();
});
}
16. 用 js 实现二叉树的定义和基本操作
二叉树节点定义 class TreeNode {
constructor (value ) {
this .value = value;
this .left = null ;
this .right = null ;
this .parent = null ;
}
}
二叉树类实现 class BinaryTree {
constructor ( ) {
this .root = null ;
this .size = 0 ;
}
insert (value ) {
const newNode = new TreeNode (value);
if (this .root === null ) {
this .root = newNode;
this .size = 1 ;
return newNode;
}
const queue = [this .root ];
while (queue.length > 0 ) {
const currentNode = queue.shift ();
if (currentNode.left === null ) {
currentNode.left = newNode;
newNode.parent = currentNode;
break ;
} else if (currentNode.right === null ) {
currentNode.right = newNode;
newNode.parent = currentNode;
break ;
} else {
queue.push (currentNode.left , currentNode.right );
}
}
this .size ++;
return newNode;
}
insertBST (value ) {
const newNode = new TreeNode (value);
if (this .root === null ) {
this .root = newNode;
this .size = 1 ;
return newNode;
}
let currentNode = this .root ;
let parent = null ;
while (currentNode !== null ) {
parent = currentNode;
if (value < currentNode.value ) {
currentNode = currentNode.left ;
} else if (value > currentNode.value ) {
currentNode = currentNode.right ;
} else {
console .log ('值已存在:' , value);
return currentNode;
}
}
newNode.parent = parent;
if (value < parent.value ) {
parent.left = newNode;
} else {
parent.right = newNode;
}
this .size ++;
return newNode;
}
find (value ) {
return this ._findNode (this .root , value);
}
_findNode (node, value ) {
if (node === null ) return null ;
if (node.value === value) return node;
const leftResult = this ._findNode (node.left , value);
if (leftResult) return leftResult;
return this ._findNode (node.right , value);
}
remove (value ) {
const node = this .find (value);
if (!node) return false ;
if (node.left === null && node.right === null ) {
this ._replaceNode (node, null );
}
else if (node.left === null ) {
this ._replaceNode (node, node.right );
} else if (node.right === null ) {
this ._replaceNode (node, node.left );
}
else {
const successor = this ._findMin (node.right );
node.value = successor.value ;
this ._replaceNode (successor, successor.right );
}
this .size --;
return true ;
}
_replaceNode (oldNode, newNode ) {
if (oldNode.parent === null ) {
this .root = newNode;
} else if (oldNode.parent .left === oldNode) {
oldNode.parent .left = newNode;
} else {
oldNode.parent .right = newNode;
}
if (newNode !== null ) {
newNode.parent = oldNode.parent ;
}
}
_findMin (node ) {
while (node.left !== null ) {
node = node.left ;
}
return node;
}
}
遍历算法实现 class BinaryTreeTraversal extends BinaryTree {
preorder ( ) {
const result = [];
this ._preorderRecursive (this .root , result);
return result;
}
_preorderRecursive (node, result ) {
if (node === null ) return ;
result.push (node.value );
this ._preorderRecursive (node.left , result);
this ._preorderRecursive (node.right , result);
}
preorderIterative ( ) {
if (this .root === null ) return [];
const result = [];
const stack = [this .root ];
while (stack.length > 0 ) {
const node = stack.pop ();
result.push (node.value );
if (node.right !== null ) stack.push (node.right );
if (node.left !== null ) stack.push (node.left );
}
return result;
}
inorder ( ) {
const result = [];
this ._inorderRecursive (this .root , result);
return result;
}
_inorderRecursive (node, result ) {
if (node === null ) return ;
this ._inorderRecursive (node.left , result);
result.push (node.value );
this ._inorderRecursive (node.right , result);
}
inorderIterative ( ) {
const result = [];
const stack = [];
let currentNode = this .root ;
while (currentNode !== null || stack.length > 0 ) {
while (currentNode !== null ) {
stack.push (currentNode);
currentNode = currentNode.left ;
}
currentNode = stack.pop ();
result.push (currentNode.value );
currentNode = currentNode.right ;
}
return result;
}
postorder ( ) {
const result = [];
this ._postorderRecursive (this .root , result);
return result;
}
_postorderRecursive (node, result ) {
if (node === null ) return ;
this ._postorderRecursive (node.left , result);
this ._postorderRecursive (node.right , result);
result.push (node.value );
}
postorderIterative ( ) {
if (this .root === null ) return [];
const result = [];
const stack1 = [this .root ];
const stack2 = [];
while (stack1.length > 0 ) {
const node = stack1.pop ();
stack2.push (node);
if (node.left !== null ) stack1.push (node.left );
if (node.right !== null ) stack1.push (node.right );
}
while (stack2.length > 0 ) {
result.push (stack2.pop ().value );
}
return result;
}
levelOrder ( ) {
if (this .root === null ) return [];
const result = [];
const queue = [this .root ];
while (queue.length > 0 ) {
const levelSize = queue.length ;
const currentLevel = [];
for (let i = 0 ; i < levelSize; i++) {
const node = queue.shift ();
currentLevel.push (node.value );
if (node.left !== null ) queue.push (node.left );
if (node.right !== null ) queue.push (node.right );
}
result.push (currentLevel);
}
return result;
}
}
二叉树工具方法 class BinaryTreeUtils extends BinaryTreeTraversal {
getHeight ( ) {
return this ._calculateHeight (this .root );
}
_calculateHeight (node ) {
if (node === null ) return 0 ;
const leftHeight = this ._calculateHeight (node.left );
const rightHeight = this ._calculateHeight (node.right );
return Math .max (leftHeight, rightHeight) + 1 ;
}
isComplete ( ) {
if (this .root === null ) return true ;
const queue = [this .root ];
let hasNullChild = false ;
while (queue.length > 0 ) {
const node = queue.shift ();
if (node.left !== null ) {
if (hasNullChild) return false ;
queue.push (node.left );
} else {
hasNullChild = true ;
}
if (node.right !== null ) {
if (hasNullChild) return false ;
queue.push (node.right );
} else {
hasNullChild = true ;
}
}
return true ;
}
isBST ( ) {
return this ._isBSTRecursive (this .root , -Infinity , Infinity );
}
_isBSTRecursive (node, min, max ) {
if (node === null ) return true ;
if (node.value <= min || node.value >= max) return false ;
return this ._isBSTRecursive (node.left , min, node.value ) && this ._isBSTRecursive (node.right , node.value , max);
}
findLCA (value1, value2 ) {
const node1 = this .find (value1);
const node2 = this .find (value2);
if (!node1 || !node2) return null ;
return this ._findLCARecursive (this .root , node1, node2);
}
_findLCARecursive (root, p, q ) {
if (root === null || root === p || root === q) return root;
const left = this ._findLCARecursive (root.left , p, q);
const right = this ._findLCARecursive (root.right , p, q);
if (left !== null && right !== null ) return root;
return left !== null ? left : right;
}
serialize ( ) {
return this ._serializeRecursive (this .root );
}
_serializeRecursive (node ) {
if (node === null ) return 'null' ;
const left = this ._serializeRecursive (node.left );
const right = this ._serializeRecursive (node.right );
return `${node.value} ,${left} ,${right} ` ;
}
deserialize (data ) {
const values = data.split (',' );
let index = 0 ;
this .root = this ._deserializeRecursive (values);
return this ;
}
_deserializeRecursive (values ) {
if (index >= values.length || values[index] === 'null' ) {
index++;
return null ;
}
const node = new TreeNode (parseInt (values[index++]));
node.left = this ._deserializeRecursive (values);
node.right = this ._deserializeRecursive (values);
return node;
}
}
使用示例
const tree = new BinaryTreeUtils ();
tree.insertBST (5 );
tree.insertBST (3 );
tree.insertBST (7 );
tree.insertBST (2 );
tree.insertBST (4 );
tree.insertBST (6 );
tree.insertBST (8 );
console .log ('前序遍历:' , tree.preorder ());
console .log ('中序遍历:' , tree.inorder ());
console .log ('后序遍历:' , tree.postorder ());
console .log ('层序遍历:' , tree.levelOrder ());
console .log ('树的高度:' , tree.getHeight ());
console .log ('是否是 BST:' , tree.isBST ());
console .log ('是否完全二叉树:' , tree.isComplete ());
const serialized = tree.serialize ();
console .log ('序列化:' , serialized);
const newTree = new BinaryTreeUtils ();
newTree.deserialize (serialized);
console .log ('反序列化后的中序遍历:' , newTree.inorder ());
17. 怎么预防用户快速连续点击,造成数据多次提交?
防抖(Debounce)
function debounce (func, wait, immediate = false ) {
let timeout = null ;
let result;
return function (...args ) {
const context = this ;
if (timeout) clearTimeout (timeout);
if (immediate) {
const callNow = !timeout;
timeout = setTimeout (() => {
timeout = null ;
}, wait);
if (callNow) result = func.apply (context, args);
} else {
timeout = setTimeout (() => {
func.apply (context, args);
}, wait);
}
return result;
};
}
const handleSubmit = debounce (function (formData ) {
console .log ('提交数据:' , formData);
return fetch ('/api/submit' , {
method : 'POST' ,
body : JSON .stringify (formData)
});
}, 1000 );
document .getElementById ('submitBtn' ).addEventListener ('click' , () => {
const formData = collectFormData ();
handleSubmit (formData);
});
节流(Throttle)
function throttle (func, wait, options = {} ) {
let timeout = null ;
let previous = 0 ;
const { leading = true , trailing = true } = options;
return function (...args ) {
const context = this ;
const now = Date .now ();
if (!previous && !leading) previous = now;
const remaining = wait - (now - previous);
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout (timeout);
timeout = null ;
}
previous = now;
func.apply (context, args);
} else if (!timeout && trailing) {
timeout = setTimeout (() => {
previous = leading ? Date .now () : 0 ;
timeout = null ;
func.apply (context, args);
}, remaining);
}
};
}
const handleScroll = throttle (function ( ) {
if (isNearBottom ()) {
loadMoreData ();
}
}, 200 );
window .addEventListener ('scroll' , handleScroll);
按钮禁用方案
function simpleDisableButton (button, duration = 2000 ) {
button.disabled = true ;
button.textContent = '提交中...' ;
setTimeout (() => {
button.disabled = false ;
button.textContent = '提交' ;
}, duration);
}
async function submitWithDisable (button, submitFunction ) {
const originalText = button.textContent ;
try {
button.disabled = true ;
button.textContent = '提交中...' ;
await submitFunction ();
button.textContent = '提交成功' ;
setTimeout (() => {
button.textContent = originalText;
button.disabled = false ;
}, 1500 );
} catch (error) {
button.textContent = '提交失败,重试' ;
button.disabled = false ;
throw error;
}
}
document .getElementById ('submitBtn' ).addEventListener ('click' , async () => {
await submitWithDisable (this , async () => {
await fetch ('/api/submit' , {
method : 'POST' ,
body : JSON .stringify (formData)
});
});
});
请求标识方案
class RequestManager {
constructor ( ) {
this .pendingRequests = new Map ();
}
async execute (key, requestFunction ) {
if (this .pendingRequests .has (key)) {
console .log ('检测到重复请求,使用缓存结果' );
return this .pendingRequests .get (key);
}
try {
const promise = requestFunction ();
this .pendingRequests .set (key, promise);
const result = await promise;
return result;
} finally {
this .pendingRequests .delete (key);
}
}
cancel (key ) {
if (this .pendingRequests .has (key)) {
this .pendingRequests .delete (key);
}
}
}
const requestManager = new RequestManager ();
async function submitForm (formData ) {
const requestKey = `submit_${JSON .stringify(formData)} ` ;
return requestManager.execute (requestKey, async () => {
const response = await fetch ('/api/submit' , {
method : 'POST' ,
body : JSON .stringify (formData)
});
return response.json ();
});
}
class RequestCanceler {
constructor ( ) {
this .controllers = new Map ();
}
async fetchWithCancel (key, url, options = {} ) {
if (this .controllers .has (key)) {
this .controllers .get (key).abort ();
}
const controller = new AbortController ();
this .controllers .set (key, controller);
try {
const response = await fetch (url, { ...options, signal : controller.signal });
return response;
} finally {
this .controllers .delete (key);
}
}
}
综合解决方案
class AntiResubmit {
constructor (options = {} ) {
this .options = {
debounceTime : 1000 ,
disableTime : 2000 ,
storageKey : 'submit_lock' ,
...options
};
this .submitLock = false ;
}
debounceSubmit (button, submitFunc ) {
let timer = null ;
return async (...args) => {
if (this .submitLock ) {
console .log ('提交锁定中,请稍候' );
return ;
}
if (timer) clearTimeout (timer);
timer = setTimeout (async () => {
try {
this .submitLock = true ;
this .disableButton (button, true );
await submitFunc (...args);
setTimeout (() => {
this .submitLock = false ;
this .disableButton (button, false );
}, this .options .disableTime );
} catch (error) {
this .submitLock = false ;
this .disableButton (button, false );
throw error;
}
}, this .options .debounceTime );
};
}
disableButton (button, disabled ) {
button.disabled = disabled;
button.style .opacity = disabled ? 0.6 : 1 ;
if (disabled) {
const originalText = button.textContent ;
button.textContent = '提交中...' ;
button.setAttribute ('data-original-text' , originalText);
} else {
const originalText = button.getAttribute ('data-original-text' );
if (originalText) {
button.textContent = originalText;
}
}
}
withStorageLock (key, func ) {
return async (...args) => {
const lockKey = `${this .options.storageKey} _${key} ` ;
const lockTime = localStorage .getItem (lockKey);
if (lockTime && Date .now () - parseInt (lockTime) < this .options .disableTime ) {
throw new Error ('操作太频繁,请稍后再试' );
}
try {
localStorage .setItem (lockKey, Date .now ().toString ());
const result = await func (...args);
return result;
} finally {
setTimeout (() => {
localStorage .removeItem (lockKey);
}, this .options .disableTime );
}
};
}
async withRequestToken (submitFunc ) {
const token = this .generateToken ();
this .currentToken = token;
try {
const result = await submitFunc (token);
if (this .currentToken === token) {
return result;
} else {
throw new Error ('请求已过期' );
}
} finally {
this .currentToken = null ;
}
}
generateToken ( ) {
return Date .now ().toString (36 ) + Math .random ().toString (36 ).substr (2 );
}
}
const antiResubmit = new AntiResubmit ({ debounceTime : 1000 , disableTime : 3000 });
const submitButton = document .getElementById ('submitBtn' );
const handleRealSubmit = async (formData ) => {
const response = await fetch ('/api/submit' , {
method : 'POST' ,
body : JSON .stringify (formData)
});
return response.json ();
};
submitButton.addEventListener ('click' , antiResubmit.debounceSubmit (submitButton, handleRealSubmit));
18. 请简述 == 的机制
== 的类型转换规则
具体转换示例
console .log (1 == 1 );
console .log ('a' == 'a' );
console .log (true == true );
console .log (null == null );
console .log (undefined == undefined );
console .log (null == undefined );
console .log (null == null );
console .log (undefined == undefined );
console .log (null == 0 );
console .log (undefined == 0 );
console .log ('123' == 123 );
console .log ('' == 0 );
console .log (' ' == 0 );
console .log ('abc' == 0 );
console .log (true == 1 );
console .log (false == 0 );
console .log (true == '1' );
console .log (false == '' );
console .log (true == 'true' );
console .log ([] == 0 );
console .log ([1 ] == 1 );
console .log ([1 , 2 ] == '1,2' );
console .log ({} == '[object Object]' );
const obj = {
valueOf ( ) { return 42 ; },
toString ( ) { return '99' ; }
};
console .log (obj == 42 );
== 的完整转换流程
function abstractEqualityComparison (x, y ) {
if (typeof x === typeof y) {
if (Number .isNaN (x) && Number .isNaN (y)) return false ;
return x === y;
}
if (x == null && y == null ) return true ;
if ((x === null && y === undefined ) || (x === undefined && y === null )) {
return true ;
}
if (typeof x === 'string' && typeof y === 'number' ) {
return stringToNumber (x) === y;
}
if (typeof x === 'number' && typeof y === 'string' ) {
return x === stringToNumber (y);
}
if (typeof x === 'boolean' ) {
return abstractEqualityComparison (booleanToNumber (x), y);
}
if (typeof y === 'boolean' ) {
return abstractEqualityComparison (x, booleanToNumber (y));
}
if ((typeof x === 'object' || typeof x === 'function' ) && (typeof y === 'string' || typeof y === 'number' || typeof y === 'symbol' )) {
const primitive = toPrimitive (x);
return abstractEqualityComparison (primitive, y);
}
if ((typeof y === 'object' || typeof y === 'function' ) && (typeof x === 'string' || typeof x === 'number' || typeof x === 'symbol' )) {
const primitive = toPrimitive (y);
return abstractEqualityComparison (x, primitive);
}
return false ;
}
function stringToNumber (str ) {
const num = Number (str);
return Number .isNaN (num) ? NaN : num;
}
function booleanToNumber (bool ) {
return bool ? 1 : 0 ;
}
function toPrimitive (obj ) {
if (typeof obj.valueOf === 'function' ) {
const value = obj.valueOf ();
if (value !== obj) return value;
}
if (typeof obj.toString === 'function' ) {
const value = obj.toString ();
if (value !== obj) return value;
}
throw new TypeError ('Cannot convert object to primitive value' );
}
特殊情况和陷阱
console .log ([] == ![]);
console .log ([] == []);
const trickyObj = {
valueOf : () => 1 ,
toString : () => '2'
};
console .log (trickyObj == 1 );
console .log (trickyObj == '2' );
const sym = Symbol ('foo' );
console .log (sym == sym);
console .log (sym == 'foo' );
console .log (Symbol ('foo' ) == Symbol ('foo' ));
console .log (0.1 + 0.2 == 0.3 );
console .log (0.1 + 0.2 );
最佳实践建议
const bestPractices = {
alwaysUseTripleEquals : [
'比较值和类型是否完全相同' ,
'避免隐式类型转换的意外' ,
'代码意图更清晰'
],
exceptions : [
{ case : '判断变量是否为 null 或 undefined' , example : 'if (value == null) { /* value 是 null 或 undefined */ }' },
{ case : '与 0 比较,但允许空字符串和 false' , example : 'if (count == 0) { /* count 可能是 0、false 或空字符串 */ }' }
],
safeComparisons : `
// 安全的方式
if (value === null || value === undefined) { ... }
if (count === 0 || count === false || count === "") { ... }
// 类型明确的比较
const num = Number(input);
if (num === 42) { ... }
// 布尔值明确转换
const bool = Boolean(value);
if (bool === true) { ... }
`
};
19. 怎么做移动端的样式适配?
1. 视口配置(基础) <!DOCTYPE html >
<html >
<head >
<meta name ="viewport" content ="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no" >
<meta name ="format-detection" content ="telephone=no" >
<meta name ="format-detection" content ="email=no" >
<meta name ="apple-mobile-web-app-capable" content ="yes" >
<meta name ="apple-mobile-web-app-status-bar-style" content ="black-translucent" >
</head >
<body >
</body >
</html >
2. 响应式布局方案
.container {
width : 100% ;
padding : 0 15px ;
margin : 0 auto;
}
@media (min-width : 576px ) {
.container {
max-width : 540px ;
}
}
@media (min-width : 768px ) {
.container {
max-width : 720px ;
}
}
@media (min-width : 992px ) {
.container {
max-width : 960px ;
}
}
@media (min-width : 1200px ) {
.container {
max-width : 1140px ;
}
}
.flex-container {
display : flex;
flex-wrap : wrap;
justify-content : space-between;
}
.flex-item {
flex : 0 0 100% ;
margin-bottom : 20px ;
}
@media (min-width : 768px ) {
.flex-item {
flex : 0 0 calc (50% - 10px );
}
}
@media (min-width : 992px ) {
.flex-item {
flex : 0 0 calc (25% - 15px );
}
}
.grid-container {
display : grid;
grid-template-columns : 1 fr;
gap : 20px ;
}
@media (min-width : 768px ) {
.grid-container {
grid-template-columns : repeat (2 , 1 fr);
}
}
@media (min-width : 992px ) {
.grid-container {
grid-template-columns : repeat (4 , 1 fr);
}
}
3. 相对单位的使用
html {
font-size : 16px ;
}
@media (max-width : 375px ) {
html {
font-size : 14px ;
}
}
@media (max-width : 320px ) {
html {
font-size : 12px ;
}
}
.container {
width : 20rem ;
padding : 1rem ;
margin : 0 auto;
}
.box {
width : 50vw ;
height : 50vh ;
font-size : 4vw ;
}
.parent {
width : 100% ;
}
.child {
width : 50% ;
padding : 5% ;
}
.responsive-box {
width : max (200px , min (80% , 400px ));
font-size : clamp (14px , 2vw , 20px );
}
4. 移动端特殊处理
.border-1px {
position : relative;
}
.border-1px ::after {
content : '' ;
position : absolute;
bottom : 0 ;
left : 0 ;
right : 0 ;
height : 1px ;
background : #e0e0e0 ;
transform : scaleY (0.5 );
transform-origin : 0 0 ;
}
.no-tap-highlight {
-webkit-tap-highlight-color : transparent;
}
.scroll-container {
-webkit-overflow -scrolling: touch;
overflow -scrolling: touch;
}
.no-select {
-webkit-user-select : none;
-moz-user-select : none;
-ms-user-select : none;
user-select : none;
}
.touch-area {
padding : 12px ;
min-height : 44px ;
}
.no-drag {
-webkit-user-drag: none;
user-drag: none;
}
5. 动态 REM 方案(JS 辅助)
(function ( ) {
const baseSize = 16 ;
const designWidth = 375 ;
function setRem ( ) {
const scale = document .documentElement .clientWidth / designWidth;
document .documentElement .style .fontSize = baseSize * Math .min (scale, 2 ) + 'px' ;
}
setRem ();
window .addEventListener ('resize' , setRem);
window .addEventListener ('pageshow' , function (e ) {
if (e.persisted ) {
setRem ();
}
});
})();
html {
font-size : calc (100vw / 375 * 16 );
}
6. 工具类和混合宏
@mixin respond-to($breakpoint ) {
@if $breakpoint == 'phone' {
@media (max-width : 599px ) {
@content ;
}
} @else if $breakpoint == 'tablet' {
@media (min-width : 600px ) and (max-width : 899px ) {
@content ;
}
} @else if $breakpoint == 'desktop' {
@media (min-width : 900px ) {
@content ;
}
}
}
.box {
width : 100% ;
@include respond-to('tablet' ) {
width : 50% ;
}
@include respond-to('desktop' ) {
width : 33.33% ;
}
}
.hide-on-mobile {
display : none;
@media (min-width : 768px ) {
display : block;
}
}
.show-on-mobile {
display : block;
@media (min-width : 768px ) {
display : none;
}
}
7. 移动端适配最佳实践
:root {
--space-xs : 4px ;
--space-sm : 8px ;
--space-md : 16px ;
--space-lg : 24px ;
--space-xl : 32px ;
--text-xs : 12px ;
--text-sm : 14px ;
--text-md : 16px ;
--text-lg : 18px ;
--text-xl : 20px ;
--breakpoint-sm : 576px ;
--breakpoint-md : 768px ;
--breakpoint-lg : 992px ;
--breakpoint-xl : 1200px ;
}
.container {
width : 100% ;
padding-left : var (--space-md);
padding-right : var (--space-md);
margin-left : auto;
margin-right : auto;
@media (min-width : 768px ) {
max-width : 720px ;
padding-left : 0 ;
padding-right : 0 ;
}
@media (min-width : 992px ) {
max-width : 960px ;
}
@media (min-width : 1200px ) {
max-width : 1140px ;
}
}
.responsive-image {
max-width : 100% ;
height : auto;
display : block;
}
.touch-button {
padding : var (--space-md) var (--space-lg);
min-height : 44px ;
font-size : var (--text-md);
border-radius : 8px ;
background : #007aff ;
color : white;
border : none;
cursor : pointer;
&:disabled {
opacity : 0.5 ;
cursor : not-allowed;
}
&:active {
transform : scale (0.98 );
}
}
20. JavaScript 中如何取消请求
1. XMLHttpRequest 的取消
function makeXHRRequest ( ) {
const xhr = new XMLHttpRequest ();
xhr.open ('GET' , '/api/data' , true );
xhr.onreadystatechange = function ( ) {
if (xhr.readyState === 4 ) {
if (xhr.status === 200 ) {
console .log ('请求成功:' , xhr.responseText );
} else {
console .log ('请求失败:' , xhr.status );
}
}
};
xhr.send ();
return function cancel ( ) {
if (xhr.readyState !== 4 ) {
xhr.abort ();
console .log ('请求已取消' );
}
};
}
const cancelXHR = makeXHRRequest ();
setTimeout (() => {
cancelXHR ();
}, 5000 );
2. Fetch API + AbortController
async function makeFetchRequest ( ) {
const controller = new AbortController ();
const signal = controller.signal ;
try {
const response = await fetch ('/api/data' , {
method : 'GET' ,
signal : signal
});
if (!response.ok ) {
throw new Error (`HTTP error! status: ${response.status} ` );
}
const data = await response.json ();
console .log ('请求成功:' , data);
return data;
} catch (error) {
if (error.name === 'AbortError' ) {
console .log ('请求被取消' );
} else {
console .error ('请求失败:' , error);
}
throw error;
}
}
function createCancellableFetch ( ) {
const controller = new AbortController ();
const request = fetch ('/api/data' , {
signal : controller.signal
}).then (response => response.json ()).catch (error => {
if (error.name === 'AbortError' ) {
console .log ('请求已取消' );
}
throw error;
});
return {
promise : request,
cancel : () => controller.abort ()
};
}
const { promise, cancel } = createCancellableFetch ();
promise.then (data => {
console .log ('数据:' , data);
}).catch (error => {
if (error.name !== 'AbortError' ) {
console .error ('错误:' , error);
}
});
setTimeout (() => {
cancel ();
}, 3000 );
3. Axios 的取消机制
function makeAxiosRequestWithCancelToken ( ) {
const source = axios.CancelToken .source ();
axios.get ('/api/data' , {
cancelToken : source.token
}).then (response => {
console .log ('请求成功:' , response.data );
}).catch (error => {
if (axios.isCancel (error)) {
console .log ('请求取消:' , error.message );
} else {
console .error ('请求失败:' , error);
}
});
return function cancel (message = '请求取消' ) {
source.cancel (message);
};
}
function makeAxiosRequestWithAbortController ( ) {
const controller = new AbortController ();
axios.get ('/api/data' , {
signal : controller.signal
}).then (response => {
console .log ('请求成功:' , response.data );
}).catch (error => {
if (axios.isCancel (error)) {
console .log ('请求取消' );
} else {
console .error ('请求失败:' , error);
}
});
return function cancel ( ) {
controller.abort ();
};
}
const cancelAxios = makeAxiosRequestWithCancelToken ();
document .getElementById ('cancelBtn' ).addEventListener ('click' , () => {
cancelAxios ('用户手动取消' );
});
4. 请求管理器(统一管理) class RequestManager {
constructor ( ) {
this .pendingRequests = new Map ();
}
addRequest (key, requestFunction, options = {} ) {
if (this .pendingRequests .has (key)) {
this .cancelRequest (key, '取消重复请求' );
}
const controller = new AbortController ();
const request = requestFunction (controller.signal ).then (response => {
this .pendingRequests .delete (key);
return response;
}).catch (error => {
this .pendingRequests .delete (key);
throw error;
});
this .pendingRequests .set (key, {
controller,
request,
timestamp : Date .now ()
});
if (options.timeout ) {
setTimeout (() => {
if (this .pendingRequests .has (key)) {
this .cancelRequest (key, '请求超时' );
}
}, options.timeout );
}
return request;
}
cancelRequest (key, reason = '请求取消' ) {
const item = this .pendingRequests .get (key);
if (item) {
item.controller .abort ();
this .pendingRequests .delete (key);
console .log (`${key} : ${reason} ` );
}
}
cancelAll (reason = '取消所有请求' ) {
for (const [key, item] of this .pendingRequests ) {
item.controller .abort ();
console .log (`${key} : ${reason} ` );
}
this .pendingRequests .clear ();
}
getPendingCount ( ) {
return this .pendingRequests .size ;
}
cleanup (expireTime = 30000 ) {
const now = Date .now ();
for (const [key, item] of this .pendingRequests ) {
if (now - item.timestamp > expireTime) {
this .cancelRequest (key, '请求过期' );
}
}
}
}
const requestManager = new RequestManager ();
function createFetch (signal ) {
return fetch ('/api/data' , { signal }).then (response => response.json ());
}
const request1 = requestManager.addRequest ('user_data' , (signal ) => createFetch (signal), { timeout : 10000 });
request1.then (data => {
console .log ('获取到用户数据:' , data);
}).catch (error => {
if (error.name !== 'AbortError' ) {
console .error ('获取失败:' , error);
}
});
5. React/Vue 中的请求取消
import React , { useEffect, useState } from 'react' ;
import axios from 'axios' ;
function DataComponent ( ) {
const [data, setData] = useState (null );
const [loading, setLoading] = useState (true );
useEffect (() => {
const source = axios.CancelToken .source ();
const fetchData = async ( ) => {
try {
setLoading (true );
const response = await axios.get ('/api/data' , {
cancelToken : source.token
});
setData (response.data );
} catch (error) {
if (!axios.isCancel (error)) {
console .error ('获取数据失败:' , error);
}
} finally {
setLoading (false );
}
};
fetchData ();
return () => {
source.cancel ('组件卸载,取消请求' );
};
}, []);
if (loading) return <div > 加载中...</div > ;
if (!data) return <div > 暂无数据</div > ;
return (<div > {/* 渲染数据 */}</div > );
}
import { ref, onUnmounted } from 'vue' ;
import axios from 'axios' ;
export function useFetchData ( ) {
const data = ref (null );
const loading = ref (true );
const error = ref (null );
const controller = new AbortController ();
const fetchData = async ( ) => {
try {
loading.value = true ;
const response = await axios.get ('/api/data' , {
signal : controller.signal
});
data.value = response.data ;
} catch (err) {
if (err.name !== 'CanceledError' ) {
error.value = err;
}
} finally {
loading.value = false ;
}
};
fetchData ();
onUnmounted (() => {
controller.abort ();
});
return { data, loading, error };
}
6. 高级技巧:竞态处理
async function fetchWithRaceProtection (url, signal ) {
const requestId = Symbol ('request' );
let currentRequest = null ;
return async function makeRequest ( ) {
if (currentRequest) {
currentRequest.controller .abort ();
}
const controller = new AbortController ();
const combinedSignal = (() => {
const signals = [controller.signal ];
if (signal) signals.push (signal);
if (signals.length === 1 ) return signals[0 ];
const abortController = new AbortController ();
for (const s of signals) {
s.addEventListener ('abort' , () => {
abortController.abort ();
});
}
return abortController.signal ;
})();
currentRequest = { controller, requestId };
try {
const response = await fetch (url, { signal : combinedSignal });
if (currentRequest.requestId === requestId) {
return response.json ();
}
throw new DOMException ('请求被新的请求取代' , 'AbortError' );
} catch (error) {
if (currentRequest.requestId === requestId) {
currentRequest = null ;
}
if (error.name === 'AbortError' ) {
console .log ('请求被取消(可能是新的请求启动了)' );
}
throw error;
}
};
}
const protectedFetch = fetchWithRaceProtection ('/api/data' );
const makeRequest = protectedFetch ();
makeRequest ();
setTimeout (makeRequest, 100 );
setTimeout (makeRequest, 200 );
微信扫一扫,关注极客日志 微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
RSA密钥对生成器 生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
Mermaid 预览与可视化编辑 基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
Keycode 信息 查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
Escape 与 Native 编解码 JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
JavaScript / HTML 格式化 使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online