手写面试题

1.手写call方法

改变this指向

执行函数

1
2
3
4
5
6
7
8
9
10
11
12
// 原型上添加 call 方法
Function.prototype.myCall = function (thisObj, ...args) {
// 传进的对象中添加调用它的函数,这样函数在对象中调用时的 this 指向该对象
const funcKey = Symbol("funcKey");
thisObj[funcKey] = this;
// 执行函数 + 返回结果
const res = thisObj[funcKey](...args);
// 从对象中删除该函数
delete thisObj[funcKey];
return res;
};

2.手写apply方法

和 call 不同之处只有接收的第二个参数是数组

1
2
3
4
5
6
7
8
9
10
11
12
// 原型上添加 apply 方法
Function.prototype.myApply = function (thisObj, args) {
// 传进的对象中添加调用它的函数,这样函数在对象中调用时的 this 指向改对象
const funcKey = Symbol("funcKey");
thisObj[funcKey] = this;
// 执行函数 + 返回结果
const res = thisObj[funcKey](...args);
// 从对象中删除该函数
delete thisObj[funcKey];
return res
}

3.手写bind方法

  • 绑定 this 指向
  • 接收部分参数
  • 返回一个可执行函数,可接受剩余参数
1
2
3
4
5
6
7
8
9
10
11
12
13
Function.prototype.myBind = function (thisObj, ...args) {
return (...reArgs) => {
// 使用箭头函数,this 从外部获取到调用 bind 的函数
let funcKey = Symbol("funcKey");
// 把函数添加到指定对象的属性中,这样调用的时候 this 就会指向该对象
thisObj[funcKey] = this;
// 执行函数 + 返回值
const result = thisObj[funcKey](...args, ...reArgs);
delete thisObj[funcKey];
return result;
};
};

4.实现compose函数

  • 接收多个函数作为参数,返回新函数
  • 新函数从右至左一次指向传入的函数,前一个函数多输出作为下一函数的输入
  • compose(f, g, h) 等价于 (x) => f(g(h(x)))
1
2
3
4
5
6
7
8
9
10
const compose = (...fn) => {
if(fn.length === 0) return (v) => v
// pre 是迭代器,初始值为数组第一个参数,之后值不断更新为回调中的返回值
// reduce 对于只有一个元素的数组,直接返回该元素
const cps = fn.reduce((pre, cur) => {
return (...args) => pre(cur(...args))
})
return cps
}

使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function fn1(x) {
return x + 1;
}
function fn2(x) {
return x + 2;
}
function fn3(x) {
return x + 3;
}
function fn4(x) {
return x + 4;
}
const a = compose(fn1, fn2, fn3, fn4);
console.log(a(1)); // 1+4+3+2+1=11

5.使用setTimeout实现setInterval

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const mySetInterval = (fn, t) => {
let timer = null
// 递归 interval
const interval = () => {
timer = setTimeout(() => {
// 保证 fn 执行完,再安排下一次回调
fn()
interval()
}, t)
}
interval()
// 返回取消方法
return {
cancel: () => { clearTimeout(timer) }
}
}

为什么要用 settimeout 模拟实现 setInterval?setInterval 的缺陷是什么?

  • 可能会导致回调函数堆积,如果回调函数时间过长的化,他不会等你执行完,而是按照固定的时间安排回调

    1
    2
    3
    4
    5
    setInterval(() => {
    console.log("执行回调");
    sleep(2000); // 假设 sleep 是一个耗时 2 秒的操作
    }, 1000);

  • 它在执行过程中遇到回调抛出异常不会停止,而是一直循环安排回调

扩展:使用 setInterval 实现 setTimeout

1
2
3
4
5
6
7
8
const mySetTimeout = (fn, t) => {
const timer = setInterval(() => {
// 不能调换位置,防止有 sleep
clearTimeout(timer)
fn()
}, t)
}

6.实现发布订阅模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class EventEmitter {
// 存储事件和事件回调,键:字符串 值:数组
events = {}
constructor() {
this.events = {}
}
// 订阅事件
on(type, callback) {
if (!this.events[type]) {
this.events[type] = [callback]
} else {
this.events[type].push(callback)
}
}
// 取消订阅
off(type, callback) {
if (!this.events[type]) return
this.events[type] = this.events[type].filter(item => {
item !== callback
})
}
// 一次性订阅
once(type, callback) {
// 把回调封装一下,执行后立刻取消订阅
function fn(...args) {
callback(...args)
this.off(type, fn)
}
this.on(type, fn)
}
// 发布事件
emit(type, ...args) {
if (!this.events[type]) return
this.events[type].forEach(item => { item.apply(this, args) });
}
}

发布订阅模式:

  • 解耦事件的发布者和订阅者,使得发布者和订阅者不需要直接知道对方的存在
  • 而是通过一个中间层(事件中心)进行通信
  • 发布者:负责发布事件
  • 订阅者:订阅事件并在事件发布时执行回调

7.数组去重

1
2
3
4
function uniqueArr(arr) {
return [...new Set(arr)]
}

8.数组扁平化

实现一个方法使多维数组变成一维数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const flatten = (arr) => {
let result = []
for (let i = 0; i < arr.length; i++) {
// 如何还是数组就继续递归
if (Array.isArray(arr[i])) {
result = result.concat(flatten(arr[i]))
} else {
// 不是数组就直接加入结果
result.push(arr[i])
}
}
return result
}

9.实现继承的方法

  1. 原型链继承

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    function Parent(name) {
    this.name = name
    this.say = function () {
    console.log('hello world')
    }
    }
    Parent.prototype.play = function () {
    console.log('你好 世界')
    }
    function Children(name) {
    }
    // 原型链继承
    Children.prototype = new Parent()

    缺点:所有字类实例共享父类的引用属性,修改其中一个,所有实例都会被修改

2.构造函数继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Parent(name) {
this.name = name
this.say = function () {
console.log('hello world')
}
}
Parent.prototype.play = function () {
console.log('你好 世界')
}
function Children(name) {
// 构造函数继承
Parent.call(this, name)
}

缺点:无法继承父类的 prototype 属性上的方法

3.组合继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Parent(name) {
this.name = name
this.say = function () {
console.log('hello world')
}
}
Parent.prototype.play = function () {
console.log('你好 世界')
}
function Children(name) {
// 构造函数继承
Parent.call(this, name)
}
// 原型链继承
Children.prototype = new Parent()

缺点:父类构造函数会执行两次,性能消耗增加

4.寄生虫继承(完美)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Parent(name) {
this.name = name
this.say = function () {
console.log('hello world')
}
}
Parent.prototype.play = function () {
console.log('你好 世界')
}
function Children(name) {
Parent.call(this, name) // 1.构造函数继承
}
// 2.原型继承
Children.prototype = Object.create(Parent.prototype)
// 3.恢复构造函数,因为第2步原型的构造函数会覆盖子类的构造函数
Children.prototype.constructor = Children

10.手写 new 操作符

  • new 创建一个用户定义的对象类型的实例
1
2
3
4
5
6
7
8
9
function myNew(fn, ...args) {
// 创建新对象,新对象的原型指向构造函数的原型
let obj = Object.create(fn.prototype)
// 绑定 fn 的 this,执行构造函数,初始化对象
let result = fn.apply(obj, args)
// 如果构造函数没有显式返回一个对象,则返回新创建的对象
return result instanceof Object ? result : obj
}

11.实现深拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const deepCopy = (obj, hash = new WeakMap()) => {
if (typeof obj !== 'object' || obj === null) {
return obj
}
// 考虑到 Data 版本
if(obj instanceof Date) {
return new Data(obj.getTime())
}
// 考虑到 function 的版本
if(typeof obj === 'function') {
return obj
}
// 防止循环引用
if (hash.has(obj)) {
return hash.get(obj)
}
// 创建新的对象
let target = Array.isArray(obj) ? [] : {}
hash.set(obj, target)
// 递归深拷贝
for (let key in obj) {
// 保证 key 是实例上的,而不是原型上的
if (obj.hasOwnProperty(key)) {
target[key] = deepCopy(obj[key], hash)
}
}
return target
}

12.实现防抖函数

停下来一段时间后才执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const debounce = (fn, delay) => {
let timer = null
let isImmediate = false // 用户记录立即执行过一次
return function (...args) {
if(!isImmediate) {
fn.apply(this, args)
isImmediate = true
}
// 每次先清空定时器
if (timer) {
clearTimeout(timer)
}
// 再新设置定时器
timer = setTimeout(() => {
isImmediate = false
// 确保传参和 this 指向正确
fn.apply(this, args)
}, delay)
}
}

13.实现节流函数

每隔一段时间执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 设置一共标志,标记当前是否有定时器还在执行
const throttle = (fn, delay) => {
let flag = false
return function (...args) {
// 当前计时器还没执行完
if (flag === true) return
// 开始新的计数器
flag = true
setTimeout(() => {
fn.apply(this, args)
flag = false
}, delay)
}
}

14.手写 Promise 基本实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
// 定义Promise的三种状态
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

class MyPromise {
/**
* 构造函数
* @param {Function} executor 执行器函数,接收resolve和reject两个参数
*/
constructor(executor) {
try {
// 执行器立即执行
executor(this.resolve, this.reject);
} catch (error) {
// 执行器执行出错直接触发reject
this.reject(error);
}
}

// Promise状态,初始为pending
status = PENDING;
// 成功结果
value = undefined;
// 失败原因
reason = undefined;
// 成功回调队列(处理异步场景)
onFulfilledCallbacks = [];
// 失败回调队列(处理异步场景)
onRejectedCallbacks = [];

/**
* 成功回调方法(使用箭头函数绑定this)
* @param {any} value 成功的结果
*/
resolve = (value) => {
// 状态只能从pending改变
if (this.status !== PENDING) return;

// 修改状态为成功
this.status = FULFILLED;
// 保存成功结果
this.value = value;

// 执行所有缓存的成功回调(异步场景)
while (this.onFulfilledCallbacks.length) {
this.onFulfilledCallbacks.shift()(this.value);
}
};

/**
* 失败回调方法(使用箭头函数绑定this)
* @param {any} reason 失败的原因
*/
reject = (reason) => {
// 状态只能从pending改变
if (this.status !== PENDING) return;

// 修改状态为失败
this.status = REJECTED;
// 保存失败原因
this.reason = reason;

// 执行所有缓存的失败回调(异步场景)
while (this.onRejectedCallbacks.length) {
this.onRejectedCallbacks.shift()(this.reason);
}
};

/**
* then方法:注册成功/失败回调,支持链式调用
* @param {Function} onFulfilled 成功回调
* @param {Function} onRejected 失败回调
* @returns {MyPromise} 新的Promise实例
*/
then(onFulfilled, onRejected) {
// 处理回调参数默认值(兼容不传回调的情况)
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };

// 返回新的Promise,实现链式调用
const promise2 = new MyPromise((resolve, reject) => {
// 封装状态处理逻辑
const handleCallback = (callback, result) => {
// 使用setTimeout模拟微任务(实际Promise是微任务,这里简化为宏任务)
setTimeout(() => {
try {
const x = callback(result);
// 处理回调返回值(兼容返回Promise的情况)
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
// 回调执行出错直接reject
reject(error);
}
}, 0);
};

// 状态为成功时执行成功回调
if (this.status === FULFILLED) {
handleCallback(onFulfilled, this.value);
}

// 状态为失败时执行失败回调
if (this.status === REJECTED) {
handleCallback(onRejected, this.reason);
}

// 状态为pending时,缓存回调函数(处理异步场景)
if (this.status === PENDING) {
this.onFulfilledCallbacks.push(() => {
handleCallback(onFulfilled, this.value);
});
this.onRejectedCallbacks.push(() => {
handleCallback(onRejected, this.reason);
});
}
});

return promise2;
}
}

15.手写 Promise 实例方法 then

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
// 处理异步任务
const runAsyncTask = (callback) => {
if (typeof queueMicrotask === "function") {
queueMicrotask(callback);
} else if (typeof MutationObserver === "function") {
// 当 Dom 节点修改时,执行异步任务
const divNode = document.createElement("div");
const obs = new MutationObserver(callback);
obs.observe(divNode, { childList: true });
divNode.innerText = "change";
} else {
setTimeout(callback, 0);
}
};

const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

class MyPromise {
state = PENDING;
result = undefined;
// 保存 then 传进来的回调
#handlers = [];
constructor(func) {
// pending -> fulfilled
const resolve = (result) => {
if (this.state === PENDING) {
this.state = FULFILLED;
this.result = result;
// 支持异步,如果有 then,执行多次传进来的回调
this.#handlers.forEach(({ onFulfilled }) => {
onFulfilled(this.result);
});
}
};
// pending -> rejected
const reject = (result) => {
if (this.state === PENDING) {
this.state = REJECTED;
this.result = result;
// 支持异步,如果有 then,执行多次传进来的回调
this.#handlers.forEach(({ onRejected }) => {
onRejected(this.result);
});
}
};
// 立刻调用传进来的回调函数,并传入处理结果回调函数
try {
func(resolve, reject);
} catch (error) {
reject(error);
}
}

// 添加实例方法 then
then(onFulfilled, onRejected) {
// 参数判断:如果函数未定义,会直接继承上一个实例的结果状态
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (x) => x;
// 是拒绝状态然后不传的话,会抛出错误
onRejected = typeof onRejected === "function" ? onRejected : x => { throw x };

const p2 = new MyPromise((resolve, reject) => {
if (this.state === FULFILLED) {
runAsyncTask(() => {
try {
const x = onFulfilled(this.result);
resolvePromise(x, p2, resolve, reject);
} catch (error) {
reject(error);
}
});
} else if (this.state === REJECTED) {
runAsyncTask(() => {
try {
const x = onRejected(this.result);
resolvePromise(x, p2, resolve, reject);
} catch (error) {
reject(error);
}
});
} else if (this.state === PENDING) {
this.#handlers.push({
onFulfilled: () => {
runAsyncTask(() => onFulfilled(this.result));
},
onRejected: () => {
runAsyncTask(() => onRejected(this.result));
},
});
}
});
return p2;
}
// 添加实例方法 catch
catch(onRejected) {
this.then(undefined, onRejected);
}
// 添加实例方法 finally
finally(onFinally) {
this.then(onFinally, onFinally);
}
}
// 抽离函数
const resolvePromise = (x, p2, resolve, reject) => {
// 处理重复调用
if (x === p2) {
throw new TypeError("Chaining cycle detected for MyPromise #<Promise>");
}
// 处理返回 Promise
if (x instanceof MyPromise) {
x.then(res => resolve(res), err => reject(err))
} else {
resolve(x);
}
};

16.手写 Promise.all 方法

传入的所有 Promise 都正常解决就返回一个 Promise,resolve 中的结果是所有包含所有解决promise结果的数组。如果有一个拒绝就立即返回一个拒绝的Promise

[!IMPORTANT]

Promise.resolve() 会返回一个 Promise,如果传入的值是 Promise,就返回这个 Promise

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
const MyPromiseAll = (promiseArr) => {
return new Promise((resolve, reject) => {
// 传入的不是数组
if (!Array.isArray(promiseArr)) {
return reject(new TypeError('Arguments must be a Array'))
}
let count = 0
let result = []
// 数组长度为0
if (promiseArr.length === 0) {
return resolve([])
}
// 遍历数组
for (let i = 0; i < promiseArr.length; i++) {
// 确保数组中每一项都是 Promise 类型
Promise.resolve(promiseArr[i])
.then(res => {
// 确保顺序正确
result[i] = res
count++
if (count === promiseArr.length) {
// 顺利遍历完的话才返回解决结果
resolve(result)
}
})
.catch(err => {
// 遇到第一个拒绝的话直接返回
return reject(err)
})
}
})
}

17.手写 Promise.race 方法

哪个状态先确定,就返回它d

  • 返回一个新的 Promise

  • 第一个完成的是解决,那么返回的 Promise 也会解决

  • 第一个完成的是拒绝,那么返回的 Promise 也会拒绝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const MyPromiseRace = (promiseArr) => {
return new Promise((resolve, reject) => {
// 传入的不是数组
if (!Array.isArray(promiseArr)) {
return reject(new TypeError('Arguments must be a Array'))
}
// 数组长度为0
if (promiseArr.length === 0) {
return resolve([])
}
// 遍历数组
for (let i = 0; i < promiseArr.length; i++) {
// 确保数组中每一项都是 Promise 类型
Promise.resolve(promiseArr[i])
.then(res => {
// 第一个完成的是解决,那么返回的 Promise 也会解决
return resolve(res)
})
.catch(err => {
// 第一个完成的是拒绝,那么返回的 Promise 也会拒绝
return reject(err)
})
}
})
}

18.手写 instanceof

用于检查某个对象是否为构造函数的实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function MyInstanceof(obj, constructor) {
if (typeof obj !== 'object' || typeof obj === 'null' || typeof constructor !== 'function') {
return false
}
let proto = Object.getPrototypeOf(obj)
// 循环遍历原型链,直到原型为空
while (proto !== null) {
if (proto === constructor.prototype) {
return true
}
proto = Object.getPrototypeOf(proto)
}
return false
}

19.函数柯里化

把一个能解收多个参数的函数转化为可以分步接收参数的函数。

fn(a, b, c) = fn(a, b)(c) = fn(a)(b)(c)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function curry(fn) {
return function curried(...args) {
// 如果收集够参数
if (args.length >= fn.length) {
return fn.apply(this, args)
} else {
// 否则继续收集参数,使用箭头函数以捕获外部的 this
return (...args2) => {
// 收集参数后拼接起来,然后递归调用
return curried.apply(this, args.concat(args2))
}
}
}
}

20.将列表转为树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const listToTree = (list) => {
const tree = []; // 存储最终的树形结构
const nodeMap = new Map(); // 用于缓存节点

// 将每个节点存入 Map
list.forEach((item) => {
nodeMap.set(item.id, { ...item, children: [] });
});

// 构建树形结构
list.forEach((item) => {
if (item.parentId === 0) {
// 根节点
tree.push(nodeMap.get(item.id));
} else {
// 子节点
const parent = nodeMap.get(item.parentId);
if (parent) {
parent.children.push(nodeMap.get(item.id));
}
}
});

return tree;
};

21.将树转为列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 深度优先搜索
const treeToList = (tree) => {
let res = []
const dfs = (nodes) => {
nodes.forEach(node => {
// 有子节点:递归
if (node.children && node.children.length > 0) {
dfs(node.children)
// 回溯时,从父中把子删去
delete node.children
}
// 无节点,推入结果(回溯时也推)
res.push(node)
});

}
// 保证传入的是数组
dfs(Array.isArray(tree) ? tree : [tree])
return res
}

22.将虚拟 DOM 转为真实 DOM

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const render = (vNode) => {
// 数字转为字符串
if (typeof vNode === 'number') {
vNode.toString()
}
// 字符串返回文本节点
if (typeof vNode === 'string') {
const dom = document.createTextNode(vNode)
return dom
}
// 创建真实 DOM 节点
const dom = document.createElement(vNode.tag)
// 为 DOM 添加属性
if (vNode.attrs) {
for (let key in vNode.attrs) {
dom.setAttribute(key, vNode.attrs[key])
}
}
// 如果存在子元素,继续递归
if (vNode.children.length > 0) {
vNode.children.forEach(item => {
dom.appendChild(render(item))
})
}
return dom
}

23.实现 LRU 缓存机制

最近最少使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class LRUCache {
constructor(capacity) {
this.capacity = capacity
this.container = new Map()
}
// 取值
get(key) {
if (this.container.has(key)) {
const tempVal = this.container.get(key)
// 先删除,再放进去,保证插入顺序,标记最近已使用
this.container.delete(key)
this.container.set(key, tempVal)
return tempVal
} else {
return -1
}
}
// 存值
put(key, value) {
// 原来存在,更新
if (this.container.has(key)) {
// 先删除再更新,保证插入顺序,表示最近已经使用过
this.container.delete(key)
this.container.set(key, value)
}
// 原来不存在
else {
// 容器未满
if (this.container.size < this.capacity) {
this.container.set(key, value)
} else {
// 容器已满,找到第一个key,删除
this.container.delete(this.container.keys().next().value)
this.container.set(key, value)
}
}
}
}

24.实现有并行限制的 Promise 调度器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
示例
addTask(1000,"1");
addTask(500,"2");
addTask(300,"3");
addTask(400,"4");
的输出顺序是:2 3 1 4

整个的完整执行流程:

一开始12两个任务开始执行
500ms时,2任务执行完毕,输出2,任务3开始执行
800ms时,3任务执行完毕,输出3,任务4开始执行
1000ms时,1任务执行完毕,输出1,此时只剩下4任务在执行
1200ms时,4任务执行完毕,输出4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Scheduler {
constructor(limit) {
this.queue = []
this.maxCount = limit
}
// 新增代办任务
add(time, res) {
const task = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(res)
}, time)
})
}
this.queue.push(task)
}
// 开始调度执行任务队列
start() {
for (let i = 0; i < this.maxCount; i++) {
this.request()
}
}
// 请求任务
request() {
if (this.queue.length <= 0) {
return
}
this.queue.shift()()
.then(res => {
console.log(res)
// 执行完一个才继续调度
this.request()
})
}
}

25.实现模板字符串的解析功能

1
2
3
4
5
6
7
8
const renderString = (template, data) => {
// match 是匹配到的字符串,key 是 (w+) 匹配展位符中的变量
let computed = template.replace(/\{\{(\w+)\}\}/g, (match, key) => {
return data[key]
})
return computed
}

26.冒泡排序

每一轮都往末尾冒出一个相对最大的元素。时间复杂度 O ( n 2 )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const bubbleSort = (arr) => {
const len = arr.length
// 一共遍历 len - 1 轮即可,最后一轮只剩两元素
for (let i = 0; i < len - 1; i++) {
// 每一轮过后末尾都会新增一个排好的元素
for (let j = 0; j < len - i - 1; j++) {
// 前大于后,交换位置
if (arr[j] > arr[j + 1]) {
[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]
}
}
}
return arr
}

27.选择排序

每一次都选择最小的元素往前排 时间复杂度 O ( n 2 )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const selectSort = (arr) => {
let len = arr.length
for (let i = 0; i < len - 1; i++) {
let minIndex = i
// 找到区间的最小值索引
for (let j = minIndex; j < len; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j
}
}
// 若最小值索引不等于区间首位,交换元素位置
if (minIndex !== i) {
[arr[i], arr[minIndex]] = [arr[minIndex], arr[i]]
}
}
return arr
}

28.插入排序

时间复杂度 O ( n 2 )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const insertSort = (arr) => {
let len = arr.length
for (let i = 1; i < len; i++) {
let cur = arr[i]
let j = i
// 往前找到第一个比自己小的元素
while (j > 0 && arr[j - 1] > cur) {
arr[j] = arr[j - 1]
j--
}
arr[j] = cur
}
return arr
}

29.快速排序

时间复杂度 O ( n log ⁡ n )

1
2
3
4
5
6
7
8
9
10
11
const quickSort = (arr) => {
if (arr.length <= 1) return arr
let base = arr[0]
// 以 base 为基,左小,右大
let left = arr.filter((item, index) => item <= base && index !== 0)
let right = arr.filter(item => item > base)

const result = [...quickSort(left), base, ...quickSort(right)]
return result
}

原地操作数组发方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
var sortArray = function(nums) {
const partition = function(arr, left, right) {
const pivotValue = arr[left];
let i = left;
let j = right;
while (i < j) {
// 从右向左找第一个小于基准的元素
while (i < j && arr[j] >= pivotValue) {
j--;
}
// 从左向右找第一个大于基准的元素
while (i < j && arr[i] <= pivotValue) {
i++;
}
// 交换大值和小值
if (i < j) {
[arr[i], arr[j]] = [arr[j], arr[i]];
}
}
// 将基准值放到正确的位置(j的位置)
[arr[left], arr[j]] = [arr[j], arr[left]];
return j; // 返回基准的最终位置
};

const quickSort = function(arr, left = 0, right = arr.length - 1) {
if (left >= right) return arr;
const pivotIndex = partition(arr, left, right);
quickSort(arr, left, pivotIndex - 1);
quickSort(arr, pivotIndex + 1, right);
return arr;
};

return quickSort(nums, 0, nums.length - 1);
};

30.二分归并排序

两部分排好,再依次比较

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 归并
const merge = (left, right) => {
let res = []
let i = 0, j = 0
while (i < left.length && j < right.length) {
if (left[i] < right[j]) {
res.push(left[i])
i++
} else {
res.push(right[j])
j++
}
}
if (i < left.length) res.push(...left.slice(i))
else if (j < right.length) res.push(...right.slice(j))
return res
}
// 二分
const mergeSort = (arr) => {
if (arr.length <= 1) return arr

let mid = Math.floor(arr.length / 2)

let left = mergeSort(arr.slice(0, mid))
let right = mergeSort(arr.slice(mid))

const result = merge(left, right)
return result
}

31.二分查找

确定元素在一个已经排好序的数组中的位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const binarySearch = (arr, target, start, end) => {
// 不可能的情况
if (end < start) return -1
let mid = Math.floor((start + end) / 2)
// 已经找到返回
if (target === arr[mid]) return mid

if (target < arr[mid]) {
// 小了往前找
return binarySearch(arr, target, start, mid - 1)
} else if (target > arr[mid]) {
// 大了往后找
return binarySearch(arr, target, mid + 1, end)
}
}

32.使用 XHR 实现 AJAX

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
const ajax = (options) => {
// 解构配置参数
const { url, method = 'GET', data = null, headers = {}, onSuccess, onError } = options
// 创建 XHR 对象
const xhr = new XMLHttpRequest()
// 配置请求方法和 URL
xhr.open(method, url, true)
// 设置请求头
for (const key in headers) {
xhr.setRequestHeader(key, headers[key])
}
// 监听请求状态变化
xhr.onreadystatechange = () => {
// 请求完成
if (xhr.readyState === 4) {
// 请求成功
if (xhr.status >= 200 && xhr.status < 300) {
onSuccess && onSuccess(xhr.responseText)
} else {
// 请求失败
onError && onError(xhr.status, xhr.responseText)
}
}
}
// 监听网络错误
xhr.onerror = () => {
onError && onError(0, 'Network Error')
}
// 发送请求
xhr.send()
}

33.对象扁平化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
const flattenObject = (obj, parentKey = '', result = {}) => {
// 如果 obj 为空,不会进入循环
for (let key in obj) {
// 构造新前缀
let newKey = parentKey ? `${parentKey}.${key}` : key
// 1. 处理对象类型
if (typeof obj[key] === 'object' && obj[key] !== null) {
// 1.1 数组
if (Array.isArray(obj[key])) {
// 由于数组的特殊 key,先单独处理数组中的基本类型
obj[key].forEach((item, index) => {
let arrayKey = `${newKey}[${index}]`
if (typeof item === 'object' && item !== null) {
// 继续递归
flattenObject(item, arrayKey, result)
} else {
// 基本类型
result[arrayKey] = item
}
})
}
// 1.2 对象
else {
flattenObject(obj[key], newKey, result)
}
}
// 2. 处理基本类型
else {
result[newKey] = obj[key]
}
}
return result
}

使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// ------------------- 测试代码 -------------------
const obj = {
a: {
b: 1,
c: 2,
d: { e: 5 }
},
b: [1, 3, { a: 2, b: 3 }],
c: 3
}

const res = flattenObject(obj)
console.log(res)

// {
// 'a.b': 1,
// 'a.c': 2,
// 'a.d.e': 5,
// 'b[0]': 1,
// 'b[1]': 3,
// 'b[2].a': 2,
// 'b[2].b': 3
// c: 3
// }

34.实现 Object.is

Object.is不会转换被比较的两个值的类型,这点和===更为相似,他们之间也存在一些区别。
1. NaN 和 NaN 在===中是不相等的,而在Object.is中是相等的
2. +0和-0在===中是相等的,而在Object.is中是不相等的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Object.MyIs = (x, y) => {
if (x === y) {
// 判断特殊情况 -0 +0
if (x === 0 || y === 0) {
// 如果 -0 会转为 -infinity
return 1 / x === 1 / y
}
return true
} else {
// 特殊情况 两个都为 NaN
if (x !== x && y !== x) {
return true
}
return false
}
}

35.类数组转为数组的方法

1
2
3
4
5
6
7
8
9
// 方法 1
function arrayLikeToArray(arrayLike) {
return Array.from(arrayLike);
}
// 方法 2
function arrayLikeToArray(arrayLike) {
return [...arrayLike];
}

36.实现大数相加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const addBigNumbers = (num1, num2) => {
const max = Math.max(num1.length, num2.length)
num1 = num1.padStart(max, '0')
num2 = num2.padStart(max, '0')
// 进位
let carry = 0
// 结果
let result = ''
for (let i = max - 1; i >= 0; i--) {
let sum = Number(num1[i]) + Number(num2[i]) + carry
// 更新进位
carry = Math.floor(sum / 10)
// 更新结果
result = (sum % 10) + result
}
// 如果最后还有进位
if (carry !== 0) {
result = carry + result
}
return result
}

37.多行文本溢出省略

1
2
3
4
5
6
7
8
.multiline-ellipsis {
display: -webkit-box; /* 多行布局 */
-webkit-line-clamp: 3; /* 设置显示的行数 */
-webkit-box-orient: vertical; /* 必须设置为 vertical 排列方向为垂直方向 */
overflow: hidden; /* 隐藏超出的内容 */
text-overflow: ellipsis; /* 超出部分用省略号显示 */
}

38.数组乱序

1
2
3
4
const randomArr = (nums) => {
return nums.sort(() => Math.random() - 0.5)
}

39.手写 JSONP

1
2
3
4
5
6
7
8
9
10
11
12
const addScript = (url) => {
const script = document.createElement('script')
script.src = url
script.type = 'text/javascript'
document.body.appendChild(script)
}
addScript("http://xxx.xxx.com/xxx.js?callback=handleRes");
// 设置一个全局回调来接收结果
const handleRes = (data) => {
console.log(data)
}

40.手写事件委托

1
2
3
4
5
6
7
8
9
10
const eventDelegate = (parent, eventType, target, callback) => {
parent.addEventListener(eventType, (event) => {
// 检查事件是否匹配目标选择器
if (event.target.matches(target)) {
// 把事件传给回调
callback(event)
}
})
}

41.手写 sleep 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
const MySleep = (delay) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve()
}, delay)
})
}
const test = async () => {
console.log('开始')
await MySleep(3000)
console.log('结束')
}

42.异步循环打印 1 2 3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const sleepToPrint = (time, val) => {
return new Promise(resolve => {
setTimeout(() => {
resolve(val)
}, time)
})
}

const toPrint_123 = async () => {
for (let i = 1; i <= 3; i++) {
const res = await sleepToPrint(1000, i)
console.log(res)
}
}
toPrint_123()

43.异步循环打印 红 黄 绿

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const asyncColor = (time, color) => {
return new Promise(resolve => {
setTimeout(() => {
console.log(color)
resolve(color)
}, time)
})
}

const startTask = async () => {
await asyncColor(1000, '红')
await asyncColor(2000, '黄')
await asyncColor(3000, '绿')
startTask()
}

startTask()

44.手写数组 filter 方法

1
2
3
4
5
6
7
8
9
10
Array.prototype.MyFilter = function(callback) {
const result = []
for(let i = 0; i < this.length ; i++) {
if(callback(this[i], i , this)) {
result.push(this[i])
}
}
return result
}

45.手写数组 reduce 方法

对数组中的元素执行一个累加器的操作,最后返回累计结果

1
2
3
4
5
6
7
8
9
Array.prototype.MyReduce = function(callback, initialValue) {
let pre = initialValue !== undefined ? initialValue : this[0]
let start = initialValue !== undefined ? 0 : 1 // 从哪个元素开始,如果不提供初始值就从 0 开始

for(let i = start; i < this.length; i++) {
pre = callback(pre, this[i], i, this)
}
return pre
}

46.手写数组 map 方法

对数组中每个元素执行特定操作,返回新的数组

1
2
3
4
5
6
7
Array.prototype.MyMap = function (callback) {
let result = []
for(let i = 0; i < this.length; i++) {
result[i] = callback(this[i], i, this)
}
return result
}

47.手写数组 fill 方法

使用一个值填充数组起始到终止索引的元素

1
2
3
4
5
6
7
Array.prototype.MyFill = function (val, start, end) {
for(let i = start; i < end; i++) {
this[i] = val
}
return this
}

48.图片懒加载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const imgs = document.querySelectorAll('#imgs-need-to-lazy-load img'); // 获取所有需要懒加载的图片
const len = imgs.length;
const viewHeight = window.innerHeight || document.documentElement.clientHeight; // 获取视口高度
let index = 0; // 记录上次加载到的位置

const lazyLoad = () => {
for (let i = index; i < len; i++) {
const top = imgs[i].getBoundingClientRect().top; // 获取图片顶部到视口顶部的距离
// 如果图片进入可视区域
if (top < viewHeight) {
imgs[i].src = imgs[i].getAttribute('data-src'); // 加载图片
index = i + 1; // 更新索引
}
}
};

// 先执行一次
lazyLoad();

// 监听滚动事件
window.addEventListener('scroll', lazyLoad, false);

49.驼峰转下划线

1
2
3
4
const toLine = (str) => {
return str.replace(/([A-Z])/g, (match) => `_${match}`.toLowerCase())
}

50.下划线转驼峰

1
2
3
4
const toHump = (str) => {
return str.replace(/\_(\w)/g, (match, letter) => letter.toUpperCase())
}

51.实现千分位分割

直接调 API

1
2
3
4
number.toLocaleString()
// 1234567
// => 1,234,567

手写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const formatNumber = (num) => {
const nums = num.toString()
let result = ''
let count = 0
for (let i = nums.length - 1; i >= 0; i--) {
count++
result = nums[i] + result
// 间隔三个,并除了第一位
if (count % 3 === 0 && i !== 0) {
result = ',' + result
}
}
return result
}

52.手写 Object.create

  • 功能,创建一个对象,对象的原型指向指定对象
1
2
3
4
5
6
7
8
9
10
11
12
Object.MyCreate = (proto, properties) => {
function F () {} // 空函数
F.prototype = proto
// obj.__proto__ = F.prototyep = proto
const obj = new F()
// 如果有传进来其他属性
if(properties) {
Object.defineProperties(obj, properties)
}
return obj
}

53.手写 Promise.any

  • 接收一个 Promise 数组
  • 返回第一个成功的 Promise
  • 如果都不成功,返回’all promise rejected’
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const MyPromiseAny = (promiseArr) => {
if(!Array.isArray(promiseArr)) {
throw new TypeError('Arguments must be a Array')
}
return new Promise((resolve, reject) => {
let count = 0
for(let i = 0; i < promiseArr.length; i++) {
Promise.resolve(promiseArr[i])
.then(res => {
resolve(res)
})
.catch(() => {
count++ // 失败的次数
if(count === promiseArr.length) {
reject('All promise rejected')
}
})
}
})
}

54.手写 JSONP

1
2
3
4
5
6
7
8
9
10
11
12
// 动态加载 JS 脚本
const addScript = (src) => {
const script = document.createElement('script')
script.src = src
ducument.body.appenChild(script)
}
addScript('http://www.xxx.com?callback=handleRes')
// 设置一个全局函数来接收数据
const handleRes = (data) => {
con
}

55.扁平对象转嵌套对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const transformObj = (obj) => {
let result = {}
for (const key in obj) {
// 确保值是在实例而非原型上
if (obj.hasOwnProperty(key)) {
let cur = result
const parts = key.split('.')
for (let i = 0; i < parts.length; i++) {
// 遍历到最后一个 part
if (i === parts.length - 1) {
cur[parts[i]] = obj[key]
} else {
// 否则 在这一层创建新对象
if (!cur[parts[i]]) cur[parts[i]] = {}
cur = cur[parts[i]]
}
}
}
}
return result
}

1
2
3
4
5
6
7
8
9
10
// ------------------- 测试代码 -------------------
const entry = {
'a.b.c.dd': 'abcdd',
'a.d.xx': 'adxx',
'a.e': 'ae',
};

const output = transformEntry(entry);
console.log(output);

56.解析 URL 参数为对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const paramsToObject = (url) => {
// 获取到查询参数部分
const queryString = url.split('?')[1]
// 获取参数所有参数
const params = queryString.split('&')
let paramObj = {}
for(let i = 1; i < params.length; i++) {
let [key, val] = [params[i].split('=')[0], params[i].split('=')[1]]
// 还原原始字符串
paramObj[decodeURLComponent(key)] = decodeURLComponent(val)
}
return paramObj
}

57.数组乱序(洗牌算法)

1
2
3
4
5
6
7
8
9
10
11
function shuffle(array) {
for (let i = array.length - 1; i > 0; i--) {
// 随机选择一个索引 j(0 <= j <= i)
const j = Math.floor(Math.random() * (i + 1));

// 交换位置 i 和 j 的元素
[array[i], array[j]] = [array[j], array[i]];
}
return array;
}

58.虚拟 DOM 结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const VNode = {
type:'div',
props: {
className : ['header', 'navigation'],
style: {
color: 'red',
}
},
children:[
{
type:'input',
props: {
classNmae:['my-input'],
placeholder:'请输入内容...'
}
},
{....},
'文本节点'
]
}

59.对象比较

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const isEqual = (obj1, obj2) => {
if (obj1 === obj2) return true
// 类型检测,是基本数据类型的话不可能再相等了
if (typeof obj1 !== 'object' || obj1 === null || typeof obj2 !== 'object' || obj2 === null) {
return false
}

const keys1 = Object.keys(obj1)
const keys2 = Object.keys(obj2)
if (keys1.length !== keys2.length) return false

// 递归比较所有属性
let result = true
for (let key of keys1) {
if (keys2.includes(key)) {
if (!isEqual(obj1[key], obj2[key])) {
result = false
}
} else {
result = false
}
}
return result
}

60.取出括号中的字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const getStrFromMeth = (str) => {
let stack = []
let result = []
for (let i = 0; i < str.length; i++) {
if (str[i] === '(') {
stack.push(i)
} else if (str[i] === ')') {
// 栈不为空,取出最近的右括号来匹配
if (stack.length !== 0) {
let start = stack.pop()
result.push(str.slice(start + 1, i))
}
}
}
return result
}
// ----------- 测试 -----------
const str = '(a+b)*(c+a*(d+e))'
console.log(getStrFromMeth(str)) // ['a+b', 'd+e', 'c+a*(d+e)']

61.实现 safeGet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const safeGet = (obj, path) => {
let keys = path.split('.')
let inObj = obj
for (let i = 0; i < keys.length; i++) {
let target = inObj[keys[i]]
if (!target) return undefined
if (i === keys.length - 1) return target
inObj = inObj[keys[i]]
}
}
// ------------- 测试 --------------
const obj = { a: { b: { c: 42 } } };
console.log(safeGet(obj, 'a.b')); // 输出: 42
console.log(safeGet(obj, 'a.b.x')); // 输出: undefined

62.使用 Node.js 写一个接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const express = require('express')
const app = express()
// 中间件:解析请求体
app.use(express.json())
// POST 接口(GET 接口类似)
app.get('/api/data', (req, res) => {
const data = req.body
res.json({message:'data received', data})
})
// 404
app.use((req, res) => {
res.status(404).json({error:'NOT Found'})
})
// 启动服务器
app.listen(3000, ()=> {
console.log('服务器已启动')
})

63.手写 HTML 空白页面

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-sacle=1.0">
<style></style>
</head>
<body>
<h1></h1>
</body>
</html>

64.手写 fetch 请求超时处理

1
2
3
4
5
6
7
8
const fetchTimeout = (url, options, timeout) => {
const timeGuard = new Promise((resolve, reject) => {
setTimeout(reject(new Error('request time out!')), timeout)
})
const fetchPromise = fetch(url, options)
return Promise.race(timeGuard, fetchPromise)
}

65.手写数组 concat 方法

1
2
3
4
Array.prototype.myConcat = function(args) {
return [...this, ...args]
}

66.手写数组 sort 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Array.prototype.sort = function(callback) {
const quickSort = (arr) => {
let mid = arr[0]
let left = []
let right = []
let left = arr.filter((item, index) => {
return index !== 0 && callback(item, mid)) < 0
})
let left = arr.filter((item, index) => {
return index !== 0 && callback(item, mid)) >= 0
})
return [...quickSort(left), mid, ...quickSort(right)]
}
// 排序
const curArr = quickSort(this)
// 修改原数组
for(let i = 0; i < this.length; i++) {
this[i] = curArr[i]
}
}

67.奇数次调用打印1,偶数次调用打印2

1
2
3
4
5
6
7
8
9
10
11
12
function func() {
let count = 1
function() {
if(count % 2 === 1) {
console.log(1)
} else {
console.log(2)
}
count++
}()
}

68.实现 lazyMan

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class lazyMan {
constructor() {
this.queue = []
setTimeout(() => { this.next() }, 0)
}
eat() {
this.queue.push(() => {
console.log('eat')
this.next()
})
return this
}
sleep(time) {
this.queue.push(() => {
setTimeout(() => {
console.log('sleep')
this.next()
}, time)
})
}
work() {
this.queue.push(() => {
console.log('work')
this.next()
})
return this
}
next() {
const task = this.queue.shift()
if(task) {
task()
}
}
}

// 示例
const obj = new lazyMan();
obj.eat().sleep(5000).work();

69.实现字符串的 trim 方法

1
2
3
4
5
6
7
8
9
10
String.prototype.myTrim = function() {
let start = 0
let end = this.length - 1
// 找第一个非空的字符
while(start <= end && this[start] === ' ') start++
// 找最后一个非空字符
while(start <= end && this[end] === ' ') end--
return this.slice(start, end + 1)
}

70.事件缓存器

第一次的时候计算然后缓存结果,第二次的时候直接从缓存中取结果,不用重复结算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function cache(func) {
let map = new Map()
return function(...args) {
let key = args.join('')
if(map.has(key)) {
return map.get(key)
} else {
const result = func.apply(this, args)
map.set(key, result)
return result
}
}
}
// ------------------- 测试代码 -------------------

function add(a, b) {
return a + b;
}
const addCache = cache(add);
console.log(addCache(1, 2)); // 3(计算并缓存)
console.log(addCache(1, 2)); // 3(直接返回缓存)
console.log(addCache(1, 3)); // 4(计算并缓存)
console.log(addCache(1, 3)); // 4(直接返回缓存)

71.链式调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function sum(value) {
// 内部累加值
let total = value;

// 返回一个对象,该对象支持连续的()调用
function f(nextValue) {
total += nextValue;
return f; // 继续返回自身以支持链式调用
}

// 给定一个value方法,最终返回累加值
f.value = function() {
return total;
};

return f;
}

// 使用方法
console.log(sum(1)(2)(3).value()); // 输出 6

72.检查数字中是否有重复的元素

注意数组中的元素大小值为 0 ~(n-1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const isUniqueArr = (nums) => {
for(let i = 0; i < nums.length; i++) {
let cur = Math.abs(nums[i])
if(nums[cur] < 0) {
return false
} else {
// 0 的话特殊处理
if(cur === 0) {
nums[cur] = -n
} else {
nums[cur] = - -nums[cur]
}
}
}
}


手写面试题
http://example.com/2025/12/29/2025手写面试题/
作者
Guo HL
发布于
2025年12月29日
许可协议