本文主要记录了关于前端面试常考的手写代码题,常看常复习。
JS 基础篇
深入了解 JS 这门语言的运行逻辑与机制,尝试实现其内部的方法。
ES5 实现继承
使用 ES5 的语法实现继承,有几个注意点:
- 使用
Object.create方法,构造以父类的prototype为原型的新对象,防止对子类原型对象的修改影响到父类的原型对象。
- 注意静态方法的继承,使用
Object.setPrototypeOf将父类设置为子类的原型。
function Person(name, age) {
console.log("I'm a person!");
function Man(name, age, address) {
Person.call(this, name, age);
console.log("I'm a man!");
Object.setPrototypeOf(Man, Person);
Man.prototype = Object.create(Person.prototype);
Man.prototype.constructor = Man;
const person = new Person("person", 10);
const man = new Man("man", 11, "Tokyo");
验证的例子:
console.log("I'm a person!");
class Man extends Person {
constructor(name, age, address) {
console.log("I'm a man!");
const person = new Person("person", 10);
const man = new Man("man", 11, "Tokyo");
console.log(person, man);
console.log(Object.getPrototypeOf(Man) === Person);
实现 new 函数
首先对问题进行分析,分析new关键字到底干了什么。
- 创建一个空对象将该对象的原型设置为函数的
prototype。
- 执行函数,
this指向指向该对象。
- 若函数返回结果不为空且为对象,则返回该对象,否则返回先前生成的对象。
* @param {Function} constructor
* @param {any[]} args - argument passed to the constructor
* `myNew(constructor, ...args)` should return the same as `new constructor(...args)`
const myNew = (func, ...params) => {
const newObj = Object.create(func.prototype);
const result = func.apply(newObj, params);
return typeof result === "object" && result !== null ? result : newObj;
手写 instanceof
沿着原型链循环查找,即寻找当前对象实例的__proto__的__proto__,是否等于构造函数的prototype。
const myInstanceOf = (obj, target) => {
if (typeof obj !== "object" || obj === null) {
const proto = target.prototype;
let curProto = Object.getPrototypeOf(obj);
if (curProto === proto) {
curProto = Object.getPrototypeOf(curProto);
手写数据类型判断函数
Object.prototype.toString调用与正则,需要注意的是call方法的调用与match方法的返回结果。
const detectType = (data) =>
Object.prototype.toString
.match(/^\[object (\w+)\]$/)[1]
手写 promisify 函数
-
注意promisify需要返回一个函数,这个函数返回一个promise对象。
-
注意 Node.js 中常规的回调函数形式,因此在改变this执行的时候使用call方法代码更加简洁。
fs.readFile("1.txt", "utf8", (err, data) => {});
-
err的情况就先reject,也是利用了回调函数的特性。
* @param {(...args) => void} func
* @returns {(...args) => Promise<any>}
const promisify = (func) => {
return function (...params) {
return new Promise((resolve, reject) => {
func.call(this, ...params, (err, data) => {
手写 async 方法
async与await的语法其实是generator函数与promise结合的语法糖,通过promise实现自动执行最后实现协程的效果。
首先查看 generator 函数的基本使用
function* helloWorldGenerator() {
const hw = helloWorldGenerator();
// { value: 'hello', done: false }
// { value: 'world', done: false }
// { value: 'ending', done: true }
// { value: undefined, done: true }
function* dataConsumer() {
console.log(`1. ${yield}`);
console.log(`2. ${yield}`);
const genObj = dataConsumer();
console.log(genObj.next());
console.log(genObj.next("a"));
console.log(genObj.next("b"));
// { value: undefined, done: false }
// { value: undefined, done: false }
// { value: 'result', done: true }
const asyncFunc = () => {
const spawn = (genF) => {
return new Promise((resolve, reject) => {
const step = (nextF) => {
return resolve(next.value);
Promise.resolve(next.value).then(
(v) => step(() => gen.next(v)),
(r) => step(() => gen.throw(r))
step(() => gen.next(undefined));
手写 EventEmitter 函数
- 注意
emit回调函数的this指向以及参数的问题。
- 注意
once函数可以将函数包装成另一个执行函数,在执行完这个函数后就移除掉。
addListener(type, callback) {
if (!this.callbacks[type]) {
this.callbacks[type] = [];
this.callbacks[type].push(callback);
prependListener(type, callback) {
if (!this.callbacks[type]) {
this.callbacks[type] = [];
this.callbacks[type].unshift(callback);
this.addListener(type, callback);
removeListener(type, callback) {
if (!this.callbacks[type]) {
console.warn("没有订阅该事件!");
const index = this.callbacks[type].indexOf(callback);
this.callbacks[type].splice(index, 1);
this.removeListener(type, callback);
if (!this.callbacks[type]) {
console.warn("没有订阅该事件!");
this.callbacks[type].forEach((callback) => callback.apply(this, args));
function wrapper(...params) {
callback.apply(this, params);
this.removeListener(type, wrapper);
this.addListener(type, wrapper);
const ee = new EventEmitter();
ee.once("wakeUp", (name) => {
console.log(`${name} 1`);
console.log(`${name} 2`);
console.log(`${name} 3`);
const meetingFn = (name) => {
console.log(`${name} 4`);
ee.on("work", meetingFn);
ee.on("work", (name) => {
console.log(`${name} 5`);
ee.emit("wakeUp", "xx"); // 第二次没有触发
ee.off("work", meetingFn); // 移除事件
ee.emit("work", "xx"); // 再次工作
手写 call,apply,bind
注意自己实现需要实现利用对象的方法隐式绑定做到的,注意bind关键字作为new方法调用的时候对this指向做出特殊的。
super关键字可以作为构造函数在构造函数里面调用super关键字还做作为对象调用父对象上的方法或者静态方法,但在普通方法里面只能调用普通方法而不能调用静态方法,但在子类的静态方法中则可以都可以调用。
Function.prototype.myCall = function (obj, ...params) {
const key = Symbol("key");
const result = obj[key](...params);
Function.prototype.myApply = function (obj, params) {
const key = Symbol("key");
const result = obj[key](...params);
Function.prototype.myBind = function (obj, ...params) {
function bound(...args) {
const context = Boolean(new.target) ? this : obj;
return func.myApply(context, [...params, ...args]);
bound.prototype = Object.create(func.prototype);
bound.prototype.constructor = bound;
function say(arg1, arg2) {
console.log(this.age, arg1, arg2);
let bindSay = say.myBind(person, "我叫", "nova");
手写 Object.create
利用new关键字,实例的__proto__属性会指向构造函数的prototype。
- 在
new初始化一个对象的规范:如果构造函数的prototype不是一个对象,则将新创建的对象的__proto__设置为标准的内置的对象的原型对象,即Object.prototype。
- 在使用
Object.create方法时,强制把这个对象的__proto__设置为该参数。目前该方法是将对象原型设置为空的唯一手段。
Class类的prototype属性默认不可写。
console.log(new Foo().toString); //outputs function toString() { [native code] } (or whatever)
Foo.prototype = Object.create(null);
console.log(new Foo().toString); //output undefined
const myObjectCreate = (proto) => {
if (typeof proto !== "object" || proto === null) {
throw new TypeError("Argument must be an object!");
手写数组 push
这种方法有点取巧,很多特性都是 JS 数组特有的行为,比如数组长度会随之数组的最大索引变化,没有值的数据会自动填充为空。
* Adds one or more elements to the end of an array and returns the new length of the array.
* @param {Array} arr - The target array to which elements will be added.
* @param {...*} params - The elements to add to the array.
* @returns {number} - The new length of the array after adding the elements.
const push = (arr, ...params) => {
for (let i = 0; i < params.length; i++) {
arr[arr.length] = params[i];
const b = push(a, 1, 2, 3);
手写字符串 repeat
实现字符串的repeat方法,注意递归的核心为保证字符串不变
const repeat = (str, n) => {
return new Array(n + 1).join(str);
const repeat0 = (str, n) => {
return n > 1 ? str.concat(repeat0(str, n - 1)) : str;
const a = repeat("sasda", 3);
手写 Object.assign
注意对参数为剩余参数,同时Object强制转换对象会返回对象本身。
const objectAssign = (target, ...sources) => {
"TypeError: Cannot convert undefined or null to object"
const ret = Object(target);
sources.forEach((source) => {
Reflect.ownKeys(source).forEach((key) => {
if (Reflect.getOwnPropertyDescriptor(source, key).enumerable) {
if (ret[key] !== source[key]) {
const c = objectAssign(a, b);
实现数组的 flat 方法
核心思路为递归,使用数组的reduce方法能减少不少代码量。
* @param { number } depth
const flat = (arr, depth = 1) => {
const step = (array, count) => {
if (!Array.isArray(array) || count < 0) {
for (const item of array) {
* @param { number } depth
const flat = (arr, depth = 1) =>
pre.concat(Array.isArray(cur) ? flat(cur, depth - 1) : [cur]),
深入 JS 的使用
柯里化
注意外层函数需要命名以便在递归的时候引用,内层函数则是内部也应该返回函数的相关结果。
* @param { (...args: any[]) => any } fn
* @returns { (...args: any[]) => any }
function curryInner(...params) {
if (params.length < fn.length)
return function (...args) {
return curryInner(...params, ...args);
return fn.apply(this, params);
* @param { (...args: any[]) => any } fn
* @returns { (...args: any[]) => any }
function curryInner(...params) {
if (params.length < fn.length || params.includes(curry.placeholder)) {
return function (...args) {
params = params.map((item) => {
if (item === curry.placeholder) {
return curryInner(...params, ...args);
return fn.apply(this, params);
curry.placeholder = Symbol();
深拷贝
采用ES6之后的方法进行,原型链与属性修饰符
Descriptors来全给你拷了。
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj)
采用for循环实现深拷贝
const cloneDeep = (data) => {
const map = new WeakMap();
if (typeof obj !== "object" || obj === null) return obj;
if (map.has(obj)) return map.get(obj);
const newObj = new obj.constructor();
Reflect.ownKeys(obj).forEach((key) => {
newObj[key] = clone(obj[key]);
防抖与节流
防抖的复杂实现注意isInvoked的位置放在了返回函数的里面,这样可以保证初始执行的这一次不会触发trailing这一次的执行。有定时器就清除,无论之前有没有定时器都需要继续设置setTimeout。
* @param {(...args: any[]) => any} func
* @returns {(...args: any[]) => any}
const debounce = (func, wait) => {
return function (...params) {
timer = setTimeout(() => {
func.apply(this, params);
* @param {(...args: any[]) => any} func
* @param {boolean} option.leading
* @param {boolean} option.trailing
* @returns {(...args: any[]) => any}
const debounce = (func, wait, option = { leading: false, trailing: true }) => {
return function (...params) {
} else if (option.leading) {
func.apply(this, params);
timer = setTimeout(() => {
if (option.trailing && !isInvoked) {
func.apply(this, params);
节流的复杂实现在于实现trailing的功能,这边涉及到递归函数的提取。注意之前有定时器的话就只保存参数什么都不做,没定时器才要设置setTimeout。
* @param {(...args:any[]) => any} func
* @returns {(...args:any[]) => any}
const throttle = (func, wait) => {
func.apply(this, lastArgs);
timer = setTimeout(run, wait);
return function (...params) {
func.apply(this, params);
timer = setTimeout(run, wait);
* @param {(...args: any[]) => any} func
* @param {boolean} option.leading
* @param {boolean} option.trailing
* @returns {(...args: any[]) => any}
const throttle = (func, wait, option = { leading: true, trailing: true }) => {
if (option.trailing && lastArgs) {
func.apply(this, lastArgs);
timer = setTimeout(run, wait);
return function (...params) {
func.apply(this, params);
timer = setTimeout(run, wait);
并发控制
并发池
const asyncPool = async (poolLimit, array, iteratorFn) => {
for (const item of array) {
const p = Promise.resolve(iteratorFn(item));
if (poolLimit < array.length) {
const e = p.finally(() => executing.splice(executing.indexOf(e), 1));
if (executing.length >= poolLimit) {
await Promise.race(executing);
asyncPool(2, [1000, 5000, 3000, 2000], timeout);
递归版本,也叫能够实现并发控制的Promise.all版本。注意while循环的巧妙使用,先将能填进去的数组先填进去。为了严格与Promise.all方法保持一致,这里做到了索引与数组直接相同。同时还需要注意递归调用的重要性。同时还有万能并发池的版本,有点缺陷的地方在于数组splice方法的时间复杂度部分。
* @param {Array<() => Promise<any>>} funcs
const throttlePromises = (funcs, max = Infinity) => {
return new Promise((resolve, reject) => {
const length = funcs.length;
const res = new Array(length);
while (funcs.length && running < max) {
const func = funcs.shift();
* @param {Array<() => Promise<any>>} funcs
const throttlePromises = async (funcs, max = Infinity) => {
for (const func of funcs) {
const p = Promise.resolve(func());
if (funcs.length > max) {
const e = p.finally(() => executing.splice(executing.indexOf(e), 1));
if (executing.length >= max) {
await Promise.race(executing);
调度器版本
难绷,曾经最熟悉的一题,居然没做出来。🤡 卡点主要在于add方法需要返回一个promise对象,这个promise对象的状态应该由task函数的执行结果来决定,但根据并发控制的需求,不能直接执行task函数,这样违背了并发控制的需求,需要将这个函数再包装一下,包装成另一个函数即() => task().then(resolve, reject),推入任务队列,其他即为递归的调度执行。注意由于任务是一个个加进来的所以使用的是if判断条件,和上面的还是有一些不一样。
return new Promise((resolve, reject) => {
this.queue.push(() => task().then(resolve, reject));
if (this.queue.length && this.running < this.size) {
const task = this.queue.shift();
const timeout = (time) =>
new Promise((resolve) => {
setTimeout(resolve, time);
const timeout0 = (time) =>
new Promise((resolve, reject) => {
setTimeout(reject, time);
const scheduler = new Scheduler();
const addTask = (time, order) => {
scheduler.add(() => timeout(time)).then(() => console.log(order));
const addTask0 = (time, order) => {
scheduler.add(() => timeout0(time)).then(() => console.log(order));
lodash.get
基本思路为解析路径至数组然后使用数组的reduce方法进行递归获取对象
* @param {string | string[]} path
* @param {any} [defaultValue]
const get = (source, path, defaultValue = undefined) => {
if (!Array.isArray(path)) {
path = path.replace(/\[(\w+)\]/g, ".$1").split(".");
const res = path.reduce((pre, cur) => pre[cur], source);
return res ?? defaultValue;
lodash.set
这个比较复杂一些,需要注意的细节有点多。
* @param {string | string[]} path
const set = (obj, path, value) => {
if (!Array.isArray(path)) {
path = path.replace(/\[(\w+)\]/g, ".$1").split(".");
const isNumber = (str) => str === String(Number(str));
path.reduce((pre, cur, index) => {
if (index === path.length - 1) {
if (isNumber(path[index + 1])) {
千分位格式化
两种方式,循环与正则,推荐正则,注意正先行断言-存在使用的一些注意事项。
const addComma = (num) => {
const [p, q] = String(num).split(".");
for (let i = p.length - 1; i >= 0; i--) {
if (++count % 3 === 0 && regex.test(p[i - 1])) {
const addComma = (num) => {
let [p, q] = String(num).split(".");
p = p.replace(/(\d)(?=(\d{3})+$)/g, "$1,");
console.log(addComma(-10000000000.102));
LazyMan
很经典的手写题,值得注意的地方有:
- 维护一个
tasks队列存储所有任务。
- 使用
setTimeout函数将任务推入宏任务队列,并且使用async和await语法配合for循环控制任务的执行顺序。
- 返回一个对象,对象身上有相关的执行方法,每个执行方法都返回对象本身,实现链式调用,注意相关方法不能使用箭头函数,这样
this指向会出现问题,不会指向返回的对象。
// sleep: (time: number) => Laziness
// sleepFirst: (time: number) => Laziness
// eat: (food: string) => Laziness
* @param {(log: string) => void} logFn
const LazyMan = (name, logFn) => {
const tasks = [() => logFn(`Hi, I'm ${name}.`)];
const eat = (food) => logFn(`Eat ${food}.`);
new Promise((resolve) => setTimeout(() => resolve(), 1000 * time)).then(
() => logFn(`Wake up after ${time} second${time > 1 ? "s" : ""}.`)
for (const func of tasks) {
tasks.push(() => eat(food));
tasks.push(() => sleep(time));
tasks.unshift(() => sleep(time));
const HardMan = (name) => {
const tasks = [() => console.log(`I am ${name}`)];
new Promise((resolve) => setTimeout(resolve, i * 1000)).then(() =>
console.log(`Start learning after ${i} seconds`)
const learn = (subject) => console.log(`Learning ${subject}`);
for (const task of tasks) {
tasks.push(() => rest(i));
tasks.unshift(() => rest(i));
tasks.push(() => learn(subject));
// HardMan("jack").rest(10).learn("computer");
// Start learning after 10 seconds
// HardMan("jack").restFirst(5).learn("chinese");
// Start learning after 5 seconds
数组转树
JSON文件转化为树结构的问题,关键在于利用对象的引用和map结构来进行操作。
面试的时候这么简单的题居然没做出来,顶级 🤡,DFS入脑了看啥都想DFS,其实只要对每个对象建立一个Map,然后遍历对象进行赋值就好了,主要还是利用了对象本身的唯一性。
{ id: 1, name: "部门1", pid: 0 },
{ id: 2, name: "部门2", pid: 1 },
{ id: 3, name: "部门3", pid: 1 },
{ id: 4, name: "部门4", pid: 3 },
{ id: 5, name: "部门5", pid: 4 },
const convertToTree = (arr) => {
for (const item of arr) {
for (const item of arr) {
const parent = map.get(item.pid);
parent.children.push(item);
console.dir(convertToTree(arr), { depth: null });
// { id: 2, name: '部门2', pid: 1 },
// children: [ { id: 5, name: '部门5', pid: 4 } ]
斐波那契数列的两种实现方式
尾调用优化解决问题。
const fib = (n, a = 0, b = 1) => {
return fib(n - 1, b, a + b);
return fib(n - 1) + fib(n - 2);
业务场景题
-
利用迭代器来使async与await来阻塞当前函数的执行线程。
new Promise((resolve) => {
setTimeout(resolve, delay);
console.log("Stop for 1s!");
console.log("Stop for 2s!");
console.log("Stop for 3s!");
-
使用promise封装一个ajax请求
<button>发送ajax请求</button>
let btn = document.querySelector("button");
btn.onclick = function () {
const p = new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
"https://www.yiketianqi.com/free/day?appid=82294778&appsecret=4PKVFula&unescape=1"
//7.利用onreadystatechange事件
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
resolve(xhr.responseText);
console.log(JSON.parse(value));
-
实现日期格式化函数
const dateFormat = (dateString, format) => {
const date = new Date(dateString);
const day = date.getDay();
const month = date.getMonth() + 1;
const year = date.getFullYear();
format = format.replace(/yyyy/, year);
format = format.replace(/MM/, month);
format = format.replace(/dd/, day);
dateFormat("2020-12-01", "yyyy/MM/dd"); // 2020/12/01
dateFormat("2020-04-01", "yyyy/MM/dd"); // 2020/04/01
dateFormat("2020-04-01", "yyyy年MM月dd日"); // 2020年04月01日
-
交换a, b的值不能用临时变量
-
注意ajax一定需要send方法
const p = new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.responseType = "json";
xhr.setRequestHeader("Accept", "application/json");
xhr.onreadystatechange = () => {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
return resolve(xhr.responseText);
-
数组的乱序输出
const a = [1, 2, 3, 4, 5];
const randomOutput = (arr) => {
for (let i = 0; i < arr.length; i++) {
const index = Math.floor(Math.random() * (arr.length - i)) + i;
[arr[index], arr[i]] = [arr[i], arr[index]];
const randomOutput0 = (arr) => {
const index = Math.floor(Math.random() * length--);
[arr[index], arr[length]] = [arr[length], arr[index]];
console.log(arr[length]);
-
实现数组的斜向打印
function printMatrix(arr) {
for (let i = 0; i < n; i++) {
for (let j = 0, k = i; k >= 0 && j < m; j++, k--) {
for (let i = 1; i < m; i++) {
for (let j = n - 1, k = i; j > 0 && k < m; k++, j--) {
-
电话号码的打印:
const changeNum = (str) => {
return str.replace(/(\d{3})(\d{4})(\d{4})/, "$1****$2");
const changeNum0 = (str) => {
const arr = Array.from(str);
arr.splice(3, 4, "****");
const changeNum1 = (str) => {
return str.replace(str.slice(3, 7), "****");
console.log(changeNum1("15727709770"));
-
循环打印方案
const task = (light, delay, callback) => {
} else if (light === "yellow") {
} else if (light === "green") {
task("green", 1000, () =>
task("red", 1000, () => task("yellow", 1000, () => step()))
const task = (delay, light) => {
return new Promise((resolve, reject) => {
} else if (light === "yellow") {
} else if (light === "green") {
const p = new Promise((resolve, reject) => {
resolve(task(1000, "red"));
p.then(() => task(1000, "green"))
.then(() => task(1000, "yellow"))
const taskMaker = () => {
const stop = () => (run = false);
const execute = async () => {
await task(1000, "yellow");
await task(1000, "green");
return { stop, execute };
const { stop, execute } = taskMaker();
-
丢手帕问题
const Josephu = (num, count) => {
for (let i = 0; i < num; i++) {
for (let i = 0; i < circle.length; i++) {
if (out >= circle.length - 1) break;
if (i === circle.length - 1) i = -1;
for (let i = 0; i < circle.length; i++) {
if (circle[i]) return circle[i];
function childNum(num, count) {
for (let i = 0; i < num; i++) {
let exitCount = 0; // 离开人数
let curIndex = 0; // 当前下标
while (exitCount < num - 1) {
if (allplayer[curIndex] !== 0) counter++;
for (i = 0; i < num; i++) {
if (allplayer[i] !== 0) {
console.log(Josephu(39, 6));
console.log(childNum(39, 6));
-
查找文章中出现频率最高的单词
const findMostWord = (article) => {
console.error("Argument cannot be undefined or null!");
const str = article.trim().toLowerCase();
const words = str.match(/[a-z]+/g);
words.forEach((item) => {
const count = str.match(new RegExp(`\\b${item}\\b`, "g")).length;
return maxStr + ":" + maxCount;
const findMostWord0 = (article) => {
article = article.trim().toLowerCase();
let wordList = article.match(/[a-z]+/g),
article = " " + wordList.join(" ") + " ";
wordList.forEach(function (item) {
if (visited.indexOf(item) < 0) {
let word = new RegExp(" " + item + " ", "g"),
num = article.match(word).length;
return maxWord + " " + maxNum;
console.log(findMostWord0("a a a a a a bbb bbb bbb b b b b b b b b b b b"));
-
setTimeout模仿setInterval
const mySetInterval = (fn, wait) => {
const timer = { flag: true };
const timer = mySetInterval(() => console.log(10), 1000);
setTimeout(() => (timer.flag = false), 5000);
-
判断对象中是否存在循环引用
const isCircle = (target) => {
if (typeof obj !== "object" || obj === null) return false;
if (set.has(obj)) return true;
for (const key of Reflect.ownKeys(obj)) {
const result = step(obj[key]);
console.log(isCircle(a.b));
-
手写一个undefinedToNull函数
const undefinedToNull = (arg) => {
if (arg === undefined) return null;
if (typeof arg !== "object" || arg === null) return arg;
arg[key] = undefinedToNull(arg[key]);
-
判断字符串的有效数字
const validateNumberString = (str) => {
return str !== "" && !isNaN(str);
const validateNumberString = (str) => {
return /^[+-]?(\d+(\.\d*)?|\d*\.\d+)(e[+-]?\d+)?$/i.test(str);
-
实现一个累加器
function sumInner(number) {
return sum(count + number);
sumInner.valueOf = () => count;
-
counter function自执行包裹
-
counter对象,简单的数据代理
* @returns { {count: number}}
const createCounter = () => {
return Object.defineProperty({}, "count", {
console.log("it cannot be altered");
-
失败后自动发起请求,超时后停止。
* @param {() => Promise<any>} fetcher
* @param {number} maximumRetryCount
function fetchWithAutoRetry(fetcher, maximumRetryCount) {
return new Promise((resolve, reject) => {
fetcher().then(resolve, (r) => {
if (count > maximumRetryCount) return reject(r);
-
封装一个fetch请求
const res = await fetch(url);
const data = await res.json();
const res = await fetch(url, {
"Content-type": "application/json",
body: JSON.stringify(data),
const result = await res.json();
const res = await fetch(url, {
"Content-type": "application/json",
body: JSON.stringify(data),
const result = await res.json();
async delete(url, data) {
const res = await fetch(url, {
"Content-type": "application/json",
body: JSON.stringify(data),
const result = await res.json();