JavaScript 手写实现合集
1. 手写防抖函数
原理:事件被触发 n 秒后再执行回调,n 秒内又被触发则重新计时。
整理 30 个高频 JavaScript 手写实现,涵盖函数防抖节流、深拷贝、原型链操作、Promise 规范、设计模式及数组处理等核心知识点。通过解析原理与代码示例,帮助开发者深入理解语言机制,适用于面试准备与日常开发优化。涉及 AJAX、JSONP、URL 解析及图片懒加载等实际应用场景。

原理:事件被触发 n 秒后再执行回调,n 秒内又被触发则重新计时。
// 函数防抖的实现
function debounce(fn, wait) {
let timer = null;
return function () {
let context = this, args = arguments;
if (timer) {
clearTimeout(timer);
timer = null;
}
timer = setTimeout(() => {
fn.apply(context, args);
}, wait);
};
}
适用场景:
原理:规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。
// 函数节流的实现
function throttle(fn, delay) {
let curTime = Date.now();
return function () {
let context = this, args = arguments, nowTime = Date.now();
if (nowTime - curTime >= delay) {
curTime = Date.now();
return fn.apply(context, args);
}
};
}
// 函数节流的实现
const throttle = (fn, delay = 500) => {
let flag = true;
return (...args) => {
if (!flag) return;
flag = false;
setTimeout(() => {
fn.apply(this, args);
flag = true;
}, delay);
};
};
适用场景:
// 使用 JSON 处理方法
const newObj = JSON.parse(JSON.stringify(obj));
局限性:
// 递归实现深拷贝
function deepCopy(obj, dep = 0) {
if (typeof obj !== "object" || dep < 1) {
return typeof obj === "object" ? Object.assign({}, obj) : obj;
}
let clone = Array.isArray(obj) ? [] : {};
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
if (obj[key] && typeof obj[key] === "object") {
clone[key] = deepCopy(obj[key], dep - 1);
} else {
clone[key] = obj[key];
}
}
}
return clone;
}
这个实现简单易懂,面试中能写出这个就足够了。
function create(obj) {
function F() {}
F.prototype = obj;
return new F();
}
面试知识点:
Object.create(null)可以创建一个不含原型链的纯净 Object 对象。
instanceof 运算符用于判断构造函数的 prototype 属性是否出现在对象的原型链中的任何位置。
实现思路:
// instanceof 的实现
function myInstanceof(left, right) {
// 获取对象原型
let __proto__ = Object.getPrototypeOf(left),
prototype = right.prototype;
// 获取构造函数的 prototype
while (__proto__) {
if (__proto__ === prototype) {
return true;
}
__proto__ = Object.getPrototypeOf(__proto__);
}
return false;
}
在调用 new 的过程中会发生四件事情(面试常问):
function objectFactory(constructor, ...rest) {
let newObject = null,
result = null;
// 判断参数是否是一个函数
if (typeof constructor !== "function") {
console.error("type error");
return;
}
// 新建一个空对象,对象的原型为构造函数的 prototype 对象
newObject = Object.create(constructor.prototype);
// 将 this 指向新建对象,并执行函数
result = constructor.apply(newObject, ...rest);
// 判断返回对象
let flag = result && (typeof result === "object" || typeof result === "function");
// 判断返回结果
return flag ? result : newObject;
}
实现步骤:
// call 函数实现
Function.prototype.myCall = function (context) {
// 判断调用对象
if (typeof this !== "function") {
console.error("type error");
}
// 获取参数
let args = [...arguments].slice(1),
result = null;
// 判断 context 是否传入,如果未传入则设置为 window
context = context || window;
// 将调用函数设为对象的方法
context.fn = this;
// 调用函数
result = context.fn(...args);
// 将属性删除
delete context.fn;
return result;
};
实现步骤:
// apply 函数实现
Function.prototype.myApply = function (context) {
// 判断调用对象是否为函数
if (typeof this !== "function") {
throw new TypeError("Error");
}
let result = null;
// 判断 context 是否存在,如果未传入则为 window
context = context || window;
// 将函数设为对象的方法
context.fn = this;
// 调用方法
if (arguments[1]) {
result = context.fn(...arguments[1]);
} else {
result = context.fn();
}
// 将属性删除
delete context.fn;
return result;
};
实现步骤:
// bind 函数实现
Function.prototype.myBind = function (context) {
// 判断调用对象是否为函数
if (typeof this !== "function") {
throw new TypeError("Error");
}
// 获取参数
var args = [...arguments].slice(1),
fn = this;
return function Fn() {
// 根据调用方式,传入不同绑定值
return fn.apply(this instanceof Fn ? this : context, args.concat(...arguments));
};
};
柯里化就是把接受「多个参数」的函数变换成接受一个「单一参数」的函数,并且返回接受「余下参数」返回结果的一种应用。
实现思路:
function curry(fn, args) {
// 获取函数需要的参数长度
let length = fn.length;
args = args || [];
return function () {
let subArgs = args.slice(0);
// 拼接得到现有的所有参数
for (let i = 0; i < arguments.length; i++) {
subArgs.push(arguments[i]);
}
// 判断参数的长度是否已经满足函数所需参数的长度
if (subArgs.length >= length) {
// 如果满足,执行函数
return fn.apply(this, subArgs);
} else {
// 如果不满足,递归返回柯里化的函数,等待参数的传入
return curry.call(this, fn, subArgs);
}
};
}
function currying(exeFunc) {
let args = [];
let currFunc = function (...rest) {
args.push(...rest);
if (rest.length <= 0) {
return exeFunc(args);
} else {
return currFunc;
}
};
return currFunc;
}
function curry(fn, ...args) {
return fn.length <= args.length ? fn(...args) : curry.bind(null, fn, ...args);
}
AJAX 是 Asynchronous JavaScript and XML 的缩写,指的是通过 JavaScript 的异步通信,从服务器获取 XML 文档从中提取数据,再更新当前网页的对应部分,而不用刷新整个网页。
创建 AJAX 请求的步骤:
const SERVER_URL = "/server";
let xhr = new XMLHttpRequest();
// 创建 Http 请求
xhr.open("GET", SERVER_URL, true);
// 设置状态监听函数
xhr.onreadystatechange = function () {
if (this.readyState !== 4) return;
// 当请求成功时
if (this.status === 200) {
handle(this.response);
} else {
console.error(this.statusText);
}
};
// 设置请求失败时的监听函数
xhr.onerror = function () {
console.error(this.statusText);
};
// 设置请求头信息
xhr.responseType = "json";
xhr.setRequestHeader("Accept", "application/json");
// 发送 Http 请求
xhr.send(null);
Promise 是 ES6 新增的一个对象,手写 Promise 能帮助更好地理解其原理。
var PromisePolyfill = (function () {
// 和 reject 不同的是 resolve 需要尝试展开 thenable 对象
function tryToResolve(value) {
if (this === value) {
// 主要是防止下面这种情况
// let y = new Promise(res => setTimeout(res(y)))
throw TypeError("Chaining cycle detected for promise!");
}
// 根据规范 2.32 以及 2.33 对对象或者函数尝试展开
// 保证 S6 之前的 polyfill 也能和 ES6 的原生 promise 混用
if (value !== null && (typeof value === "object" || typeof value === "function")) {
try {
// 这里记录这次 then 的值同时要被 try 包裹
// 主要原因是 then 可能是一个 getter, 也也就是说
// 1. value.then 可能报错
// 2. value.then 可能产生副作用 (例如多次执行可能结果不同)
var then = value.then;
// 另一方面,由于无法保证 then 确实会像预期的那样只调用一个 onFullfilled / onRejected
// 所以增加了一个 flag 来防止 resolveOrReject 被多次调用
var thenAlreadyCalledOrThrow = false;
if (typeof then === "function") {
// 是 thenable 那么尝试展开
// 并且在该 thenable 状态改变之前 this 对象的状态不变
then.bind(value)(
// onFullfilled
function (value2) {
if (thenAlreadyCalledOrThrow) return;
thenAlreadyCalledOrThrow = true;
tryToResolve.bind(this, value2)();
}.bind(this),
// onRejected
function (reason2) {
if (thenAlreadyCalledOrThrow) return;
thenAlreadyCalledOrThrow = true;
resolveOrReject.bind(this, "rejected", reason2)();
}.bind(this)
);
} else {
// 拥有 then 但是 then 不是一个函数 所以也不是 thenable
resolveOrReject.bind(this, "resolved", value)();
}
} catch (e) {
if (thenAlreadyCalledOrThrow) return;
thenAlreadyCalledOrThrow = true;
resolveOrReject.bind(this, "rejected", e)();
}
} else {
// 基本类型 直接返回
resolveOrReject.bind(this, "resolved", value)();
}
}
function resolveOrReject(status, data) {
if (this.status !== "pending") return;
this.status = status;
this.data = data;
if (status === "resolved") {
for (var i = 0; i < this.resolveList.length; ++i) {
this.resolveList[i]();
}
} else {
for (i = 0; i < this.rejectList.length; ++i) {
this.rejectList[i]();
}
}
}
function Promise(executor) {
if (!(this instanceof Promise)) {
throw Error("Promise can not be called without new !");
}
if (typeof executor !== "function") {
// 非标准 但与 Chrome 谷歌保持一致
throw TypeError("Promise resolver " + executor + " is not a function");
}
this.status = "pending";
this.resolveList = [];
this.rejectList = [];
try {
executor(tryToResolve.bind(this), resolveOrReject.bind(this, "rejected"));
} catch (e) {
resolveOrReject.bind(this, "rejected", e)();
}
}
Promise.prototype.then = function (onFullfilled, onRejected) {
// 返回值穿透以及错误穿透,注意错误穿透用的是 throw 而不是 return,否则的话
// 这个 then 返回的 promise 状态将变成 resolved 即接下来的 then 中的 onFullfilled
// 会被调用,然而我们想要调用的是 onRejected
if (typeof onFullfilled !== "function") {
onFullfilled = function (data) {
return data;
};
}
if (typeof onRejected !== "function") {
onRejected = function (reason) {
throw reason;
};
}
var executor = function (resolve, reject) {
setTimeout(function () {
try {
// 拿到对应的 handle 函数处理 this.data
// 并以此为依据解析这个新的 Promise
var value =
this.status === "resolved"
? onFullfilled(this.data)
: onRejected(this.data);
resolve(value);
} catch (e) {
reject(e);
}
}.bind(this));
};
// then 接受两个函数返回一个新的 Promise
// then 自身的执行永远异步与 onFullfilled/onRejected 的执行
if (this.status !== "pending") {
return new Promise(executor.bind(this));
} else {
// pending
return new Promise(function (resolve, reject) {
this.resolveList.push(executor.bind(this, resolve, reject));
this.rejectList.push(executor.bind(this, resolve, reject));
}.bind(this));
}
};
// for prmise A+ test
Promise.deferred = Promise.defer = function () {
var dfd = {};
dfd.promise = new Promise(function (resolve, reject) {
dfd.resolve = resolve;
dfd.reject = reject;
});
return dfd;
};
// for prmise A+ test
if (typeof module !== "undefined") {
module.exports = Promise;
}
return Promise;
})();
PromisePolyfill.all = function (promises) {
return new Promise((resolve, reject) => {
const result = [];
let cnt = 0;
for (let i = 0; i < promises.length; ++i) {
promises[i].then(
(value) => {
cnt++;
result[i] = value;
if (cnt === promises.length) resolve(result);
},
reject
);
}
});
};
PromisePolyfill.race = function (promises) {
return new Promise((resolve, reject) => {
for (let i = 0; i < promises.length; ++i) {
promises[i].then(resolve, reject);
}
});
};
千位分隔符是处理数字格式化常用的方式。
function parseToMoney(num) {
num = parseFloat(num.toFixed(3));
let [integer, decimal] = String.prototype.split.call(num, ".");
integer = integer.replace(/\d(?=(\d{3})+$)/g, "$&,");
return integer + "." + (decimal ? decimal : "");
}
当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知依赖它的对象。Vue 双向绑定就是使用了观察者模式。
class Subject {
constructor() {
this.ObserverList = [];
}
add(observer) {
this.ObserverList.push(observer);
}
remove(observer) {
this.ObserverList = this.ObserverList.filter((item) => item !== observer);
}
notify(...arg) {
this.ObserverList.forEach((cb) => cb.update(...arg));
}
}
class Observer {
constructor(name) {
this.name = name;
}
update() {
console.log(this.name);
}
}
发布/订阅模式:基于一个主题/事件通道,希望接收通知的对象(称为 subscriber)通过自定义事件订阅主题,被激活事件的对象(称为 publisher)通过发布主题事件的方式被通知。Vue2 中的全局事件总线就是使用这种设计模式。
/**
* 发布/订阅模式组件
*/
// 定义发布/订阅类
class Pubsub {
constructor() {
this.topics = {};
this.subUid = -1;
}
// 发布事件
publish(topic, args) {
if (!this.topics[topic]) return false;
let subscribers = this.topics[topic];
let len = subscribers ? subscribers.length : 0;
while (len--) {
subscribers[len].func(topic, args);
}
return this;
}
// 订阅事件
subscribe(topic, func) {
if (!this.topics[topic]) this.topics[topic] = [];
let token = (++this.subUid).toString();
this.topics[topic].push({
token: token,
func: func,
});
return token;
}
// 取消订阅
unsubscribe(token) {
for (let m in this.topics) {
if (this.topics[m]) {
for (let i = 0; i < this.topics[m].length; i++) {
if (this.topics[m][i].token == token) {
this.topics[m].splice(i, 1);
return token;
}
}
}
}
return this;
}
}
setInterval 的作用是每隔一段指定时间执行一个函数,但是这个执行不是真的到了时间立即执行,它真正的作用是每隔一段时间将事件加入事件队列中去。只有当执行栈为空的时候,才能去从事件队列中取出事件执行。
针对 setInterval 的这个缺点,我们可以使用 setTimeout 递归调用来模拟 setInterval,这样我们就确保了只有一个事件结束了,我们才会触发下一个定时器事件。
function mySetInterval(fn, timeout) {
setTimeout(() => {
fn();
mySetInterval(fn, timeout);
}, timeout);
}
转换前:
source = [
{ id: 1, pid: 0, name: "body" },
{ id: 2, pid: 1, name: "title" },
{ id: 3, pid: 2, name: "div" },
];
转换为:
tree = [
{
id: 1,
pid: 0,
name: "body",
children: [
{
id: 2,
pid: 1,
name: "title",
children: [
{
id: 3,
pid: 2,
name: "div",
},
],
},
],
},
];
实现方法:
function jsonToTree(data) {
// 初始化结果数组,并判断输入数据的格式
let result = [];
if (!Array.isArray(data)) {
return result;
}
// 使用 map,将当前对象的 id 与当前对象对应存储起来
let map = {};
data.forEach((item) => {
map[item.id] = item;
});
data.forEach((item) => {
let parent = map[item.pid];
if (parent) {
(parent.children || (parent.children = [])).push(item);
} else {
result.push(item);
}
});
return result;
}
function timeout(delay) {
return new Promise((resolve) => {
setTimeout(resolve, delay);
});
}
function sleep(time, fn) {
let curDate = Date.now();
while (Date.now() - curDate < time); // eslint-disable-line no-empty
fn();
}
数组扁平化就是将 [1, [2, [3]] 这种多层的数组拍平成一层 [1, 2, 3]。
// 数组扁平化示例
let arr = [
[22, [1, 2, [5, 8], [9, 2]], [2, 7, 1, [9, 0, [1, 6]]]],
[2],
];
function flatten(arr, dep) {
return dep > 1
? arr.reduce((pre, cur) => pre.concat(Array.isArray(cur) ? flatten(cur, dep - 1) : cur), [])
: Array.prototype.slice.call(arr);
}
function flatten2(arr, dep = 0) {
if (dep < 1) {
return arr.slice();
}
while (dep && arr.some((item) => Array.isArray(item))) {
arr = [].concat(...arr);
dep--;
}
return arr;
}
function flatten3(arr, dep = 0) {
if (dep < 1) {
return Array.prototype.slice.call(arr);
}
let result = [];
arr.forEach((element) =>
Array.isArray(element) ? result.push.apply(result, flatten3(element, dep - 1)) : result.push(element)
);
return result;
}
function flatten4(arr) {
return arr.toString().split(",").map((item) => +item);
}
function flatten5(arr, dep = 0) {
let stack = [...arr];
let result = [];
while (stack.length) {
let data = stack.pop();
if (Array.isArray(data)) {
stack.push(...data);
} else {
result.push(data);
}
}
return result.reverse();
}
Object.assign 用于浅拷贝对象属性。
Object.myAssign = function (target, ...source) {
if (target == null) {
throw new TypeError("Cannot convert undefined or null to object");
}
let ret = Object(target);
source.forEach(function (obj) {
if (obj != null) {
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
ret[key] = obj[key];
}
}
}
});
return ret;
};
var 作用域问题会导致一直输出最后一个数字。
for (var i = 0; i < 5; i++) {
(function (i) {
setTimeout(function () {
console.log(i);
}, i * 1000);
})(i);
}
for (let i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i);
}, i * 1000);
}
let url = "http://www.domain.com/?user=anonymous&id=123&id=456&city=%E5%8C%97%E4%BA%AC&enabled";
parseParam(url);
/* 结果
{
user: 'anonymous',
id: [ 123, 456 ], // 重复出现的 key 要组装成数组,能被转成数字的就转成数字类型
city: '北京', // 中文需解码
enabled: true // 未指定值得 key 约定为 true
}
*/
function parseParam(url) {
const paramsStr = /.+\?(.+)$/.exec(url)[1]; // 将 ? 后面的字符串取出来
const paramsArr = paramsStr.split("&"); // 将字符串以 & 分割后存到数组中
let paramsObj = {}; // 将 params 存到对象中
paramsArr.forEach((param) => {
if (/=/.test(param)) {
// 处理有 value 的参数
let [key, val] = param.split("=");
val = decodeURIComponent(val); // 解码
val = /^\d+$/.test(val) ? parseFloat(val) : val; // 判断是否转为数字
if (paramsObj.hasOwnProperty(key)) {
// 如果对象有 key,则添加一个值
paramsObj[key] = [].concat(paramsObj[key], val);
} else {
// 如果对象没有这个 key,创建 key 并设置值
paramsObj[key] = val;
}
} else {
// 处理没有 value 的参数
paramsObj[param] = true;
}
});
return paramsObj;
}
特性说明:
function jsonStringify(obj) {
let type = typeof obj;
if (type !== "object") {
if (/string|undefined|function/.test(type)) {
obj = '"' + obj + '"';
}
return String(obj);
} else {
let json = [];
let arr = Array.isArray(obj);
for (let k in obj) {
let v = obj[k];
let type = typeof v;
if (/string|undefined|function/.test(type)) {
v = '"' + v + '"';
} else if (type === "object") {
v = jsonStringify(v);
}
json.push((arr ? "" : '"' + k + '":') + String(v));
}
return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}");
}
}
let json = '{"name":"cxk", "age":25}';
let obj = eval("(" + json + ")");
使用
eval实现简单但很容易被 XSS 攻击。
继承有很多方式,这里只归纳最优的两种:寄生组合式和 ES6 的 class 继承。
function inheritPrototype(subType, superType) {
// 创建对象,创建父类原型的一个副本
let prototype = Object.create(superType.prototype);
// constructor 属性为子类构造函数
prototype.constructor = subType;
// 指定对象,将新创建的对象赋值给子类的原型
subType.prototype = prototype;
}
class Rectangle {
// constructor
constructor(height, width) {
this.height = height;
this.width = width;
}
// Getter
get area() {
return this.calcArea();
}
// Method
calcArea() {
return this.height * this.width;
}
}
const rectangle = new Rectangle(40, 20);
console.log(rectangle.area); // 输出 800
// 继承
class Square extends Rectangle {
constructor(len) {
// 子类没有 this,必须先调用 super
super(len, len);
// 如果子类中存在构造函数,则需要在使用"this"之前首先调用 super()
this.name = "SquareIng";
}
get area() {
return this.height * this.width;
}
}
const square = new Square(20);
console.log(square.area); // 输出 400
extends 继承的核心代码如下,其实和寄生组合式继承方式一样:
function _inherits(subType, superType) {
// 创建对象,创建父类原型的一个副本
// 增强对象,弥补因重写原型而失去的默认的 constructor 属性
// 指定对象,将新创建的对象赋值给子类的原型
subType.prototype = Object.create(superType && superType.prototype, {
constructor: {
value: subType,
enumerable: false,
writable: true,
configurable: true,
},
});
if (superType) {
Object.setPrototypeOf
? Object.setPrototypeOf(subType, superType)
: (subType.__proto__ = superType);
}
}
循环引用对象本来没有什么问题,但是序列化的时候就会发生问题,比如调用 JSON.stringify() 对该类对象进行序列化,就会报错:Converting circular structure to JSON。
const isCycleObject = (obj, parent) => {
const parentArr = parent || [obj];
for (let i in obj) {
if (typeof obj[i] === "object") {
let flag = false;
parentArr.forEach((pObj) => {
if (pObj === obj[i]) {
flag = true;
}
});
if (flag) return true;
flag = isCycleObject(obj[i], [...parentArr, obj[i]]);
if (flag) return true;
}
}
return false;
};
数组去重也是常用的处理数组面试题。
function unique(array) {
// res 用来存储结果
var res = [];
for (var i = 0, arrayLen = array.length; i < arrayLen; i++) {
for (var j = 0, resLen = res.length; j < resLen; j++) {
if (array[i] === res[j]) {
break;
}
}
// 如果 array[i] 是唯一的,那么执行完循环,j 等于 resLen
if (j === resLen) {
res.push(array[i]);
}
}
return res;
}
function unique(arr) {
return [...new Set(arr)];
}
function unique_(arr) {
return arr.filter((item, index) => arr.indexOf(item) === index);
}
function unique__(arr) {
return arr.reduce((pre, cur) => (pre.includes(cur) ? pre : pre.concat(cur)), []);
}
function unique___(arr) {
let mp = new Map();
arr.forEach((element) => {
if (!mp.has(element)) {
mp.set(element, 1);
}
});
return [...mp.keys()];
}
function unique(array) {
var res = [];
var sortedArray = array.slice().sort();
var seen;
for (var i = 0, len = sortedArray.length; i < len; i++) {
// 如果是第一个元素或者相邻的元素不相同
if (!i || seen !== sortedArray[i]) {
res.push(sortedArray[i]);
}
seen = sortedArray[i];
}
return res;
}
图片懒加载就是鼠标滑动到哪里,图片加载到哪里。总的来说,一般页面打开,会同时加载页面所有的图片,如果页面的图片请求太多会造成很卡很慢的现象,为了避免这一现象,利用懒加载图片的方法,提高性能。
let imgList = [...document.querySelectorAll("img")];
let length = imgList.length;
const imgLazyLoad = (function () {
let count = 0;
return function () {
let deleteIndexList = [];
imgList.forEach((img, index) => {
let rect = img.getBoundingClientRect();
if (rect.top < window.innerHeight) {
img.src = img.dataset.src;
deleteIndexList.push(index);
count++;
if (count === length) {
document.removeEventListener("scroll", imgLazyLoad);
}
}
});
imgList = imgList.filter((img, index) => !deleteIndexList.includes(index));
};
})();
// 加上防抖处理
document.addEventListener("scroll", debounce(imgLazyLoad, 200));
打乱数组在开发场景中也是很常用的功能。
function shuffle(arr) {
for (let i = 0; i < arr.length; i++) {
let randomIndex = i + Math.floor(Math.random() * (arr.length - i));
[arr[i], arr[randomIndex]] = [arr[randomIndex], arr[i]];
}
return arr;
}
JSONP 的特殊之处在于前端会传递一个 callback 参数给后端,后端返回数据时会将这个 callback 参数的值作为函数名来包裹住 JSON 数据,最终返给前端的就是一段 JS 代码,这样就巧妙地解决了跨域问题。优点是兼容性好,但只能用于 GET 请求,而且需要服务端支持。
const jsonp = ({ url, params, callbackName }) => {
const generateUrl = () => {
let dataSrc = "";
for (let key in params) {
if (params.hasOwnProperty(key)) {
dataSrc += `${key}=${params[key]}&`;
}
}
dataSrc += `callback=${callbackName}`;
return `${url}?${dataSrc}`;
};
return new Promise((resolve, reject) => {
const scriptEle = document.createElement("script");
scriptEle.src = generateUrl();
document.body.appendChild(scriptEle);
window[callbackName] = (data) => {
resolve(data);
document.removeChild(scriptEle);
};
});
};

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online