面试手写篇

数组去重

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
// indexOf
function unique(arr) {
let res = [];

for (let i = 0; i < arr.length; i++) {
if (res.indexOf(arr[i]) < 0) {
res.push(arrp[i]);
}
}

return res;
}

// includes
function unique(arr) {
let res = [];

for (let item of arr) {
if (!res.includes(item)) {
res.push(item);
}
}

return res;
}

// 利用filter + indexOf
function unique(arr) {
const res = arr.filter(function (item, index, array) {
return array.indexOf(item) === index;
});

return res;
}

// 利用Es6中的Set数据结构(扩展运算符)
const unique = (arr) => [...new Set(arr)];

// Array.form 方法将set结构转换为数组
const unique = (arr) => Array.from(new Set(arr));

数组扁平化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let arr = [1, [2, [3]]];

// let res = arr.flat(Infinity)

// let res = JSON.parse(JSON.stringify(arr).replace(/\[|\]/g,""))

function flatArr(arr) {
let res = arr.reduce((accu, curr) => {
return accu.concat(Array.isArray(curr) ? flatArr(curr) : curr);
}, []);

return res;
}

console.log(flatArr(arr));

浅拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 只考虑对象类型
function shallowCopy(obj) {
if (typeof obj !== 'object') return;
let newObj = obj instanceof Array ? [] : {};

if (obj === null) return obj;
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);

for (let key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = obj[key];
}
}

return newObj;
}

// Object.assign()
// arr.slice(0)
// arr.concat()
// 扩展运算符

深拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 只考虑普通对象属性,不考虑内置对象和函数
function deepCone(obj) {
if (typeof obj !== 'object') return obj;
let newObj = obj instanceof Array ? [] : {};

if (obj === null) return obj;
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);

for (let key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] =
typeof obj[key] === 'object' ? deepClone(obj[key]) : obj[key];
}
}

return newObj;
}

// JSON.parse(JSON.stringify())
// lodash: _.cloneDeep()

实现一个 compose(组合)函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function compose(...fn) {
if(!fn.length) return (v) => v;
if(fn.length === 1) return fn[0];

return fn.reduce((accu, curr) => (...args) => accu(curr(...args)) );
}

例子:
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 res = compose(fn1, fn2, fn3, fn4);
console.log(res(5)); // 5+4+3+2+1=15

函数柯里化

函数柯里化就是让我们传递参数的方式不在局限于一次传完,可以分步,所以柯里化的核心就在于等到接收到的参数等于函数参数时再调用函数把所有参数传递进去

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
function curry(fn, ...args) {
// 需要柯里化的函数fn,也可以支持初始参数的传入

return function () {
//参数缓存在args里面,合并上次参数和本次参数
args = [...args, ...arguments];

// 判断参数个数,不够继续递归
if (args.length < fn.length) {
return curry(fn, ...args);
} else {
//参数足够返回函数执行结果
return fn.apply(null, args);
}
};
}

function bar(a, b, c) {
return a + b + c;
}

const f = curry(bar);

console.log(f(1)(2)(3));
console.log(f(1, 2)(3));
console.log(f(1, 2, 3));

assign

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Object.assign = function (target, ...source) {
if (target === null || target === undefined) {
throw new TypeError('Cannot convert undefined or null to object');
}

let result = Object(target);

source.forEach(function (obj) {
if (obj !== null) {
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
result[key] = obj[key];
}
}
}
});

return result;
};

防抖函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function debounce(func, wait) {
let timer = null;
return function () {
if (timer) {
clearTimeout(timeout);
}

timer = setTimeout(() => {
func.apply(this, arguments);
timer=null;
}, wait);
};
}

// 搜索联想,用户在不断输入时,用防抖来节约请求资源
// window触发resize的时候

节流函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function throttle(func, wait) {
const previous = 0;

return function () {
const now = +new Date();
if (now - previous > wait) {
func.apply(this, arguments);

previous = now;
}
};
}

// 鼠标不断点击触发,可以使其单位时间内只触发一次
// 监听滚动事件
// 防止高频点击提交

如何把字符串中大小写取反

1
2
3
4
5
6
7
let str = 'LiBoShi';

str = str.replace(/[a-zA-Z]/g, (content) => {
return content.toUpperCase() === content
? content.toLowerCase()
: content.toUpperCase();
});

从 S 中查找 T 字符串,找到返回索引值,没有则返回-1,类似 indexOf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 普通方法
function myIndexOf(T) {
let index = -1;
for (let i = 0; i <= this.length - T.length; i++) {
if (this.substr(i, T.length) === T) {
return (index = i);
}
}
return index;
}

String.prototype.myIndexOf = myIndexOf;
// 正则方法
function myIndexOf(T) {
let reg = new RegExp(T);
let res = reg.exec(this);

return res === null ? -1 : res.index;
}

new 操作符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 构造一个全新的对象
// 新对象会被执行[[prototype]]连接
// 新对象绑定到函数调用的this
// 如果函数没有返回其他对象,那么返回对象本身,否则返回其他对象
function newFn(fn, ...args) {
const obj = Object.create(fn.prototype);
const result = fn.apply(obj, args);
return typeof result === 'object' && result !== null ? result : obj;
}

function Person(name) {
this.name = name;
}

const p = newFn(Person, 'Jerome');

console.log('p.name :>> ', p.name); // p.name :>> Jerome

call

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
Function.prototype.myCall = function (context, ...args) {
const ctx = context || window;
const fn = Symbol();

ctx[fn] = this;

const res = ctx[fn](...args);

delete ctx[fn];

return res;
};

// 使用
let obj = {
desc: function (from, to) {
console.log(`${this.name}来自${from}去往${to}`);
},
};

let person = {
name: 'lbs',
};

obj.desc.myCall(person, '北京', '上海');

apply

1
2
3
4
5
6
7
8
9
10
11
Function.prototype.myApply = function (context, args = []) {
const ctx = context || window;
const fn = Symbol();

ctx[fn] = this;
const res = ctx[fn](...args);

delete ctx[fn];

return res;
};

bind

1
2
3
4
5
6
7
8
9
10
11
12
13
Function.prototype.myBind = function (content, ...args) {
const fn = this;

return function newFn(...newFnArgs) {
// 检测 New
// 如果当前函数的this指向的是构造函数中的this 则判定为new 操作
if (this instanceof newFn) {
return new fn(...args, ...newFnArgs);
}

return fn.myApply(context, [...args, ...newFnArgs]);
};
};

instanceof

1
2
3
4
5
6
7
8
9
10
11
// 1. 通过left.__proto__.__proto__这种方式从下往上的获取原型对象
// 2. 通过Object.create(null)的实例是没有原型链
// 3. 有原型链的实例的尽头都是Object

function _instanceof(left, right) {
if (!left.__proto__) return false;
if (right === Object || left.__proto__ === right.prototype) return true;

return _instanceof(left.__proto__, right);
}
_instanceof(/123/, RegExp);

手写 Object.is

1
2
3
4
5
6
7
8
9
function is(x, y) {
if (x === y) {
// x,y都为0,但是1 / +0 = +Infinity,1 / -0 = -Infinity 是不一样的
return x !== 0 || y !== 0 || 1 / x === 1 / y;
} else {
//NaN === NaN = false是不对的,做一个拦截操作
return x !== x && y !== y;
}
}

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
function ajax(method, url, headers, body) {
return new Promise((resolve, reject) => {
let req = null;
try {
req = new XMLHttpRequest();
} catch {
req = new ActiveXObject('Microsoft.XMLHTTP');
}

req.open(method, url);

for (let key in headers) {
req.setRequestHeader(key, headers[key]);
}

req.onreadystatechange(() => {
if (req.readystate === 4) {
if (req.status >= 200 && req.status <= 300) {
resolve(req.responseText);
} else {
reject(req);
}
}
});

req.send(body);
});
}

map

1
2
3
4
5
6
7
8
9
10
11
12
Array.prototype.map = function (fn, toThis) {
const arr = this;
const result = [];
const _this = toThis || Object.create(null);

for (let i = 0; i < arr.length; i++) {
const item = fn.call(_this, arr[i], i, arr);
result.push(item);
}

return result;
};

手写 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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
class Promise1 {
static PENDING = 'pending';
static FULFILLED = 'fulfilled';
static REJECTED = 'rejected';

constructor(executor) {
this.status = Promise.PENDING; //默认状态,进行中
this.value = null; //成功值
this.reason = null; // 失败原因
// 解决异步问题
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];

try {
executor(this.resolve.bind(this), this.reject.bind(this));
} catch (e) {
this.reject(e);
}
}

resolve(value) {
if (this.status === Promise.PENDING) {
this.status = Promise.FULFILLED;
this.value = value;

setTimeout(() => {
this.onFulfilledCallbacks.forEach((cb) => cb(this.value));
});
}
}

reject(reason) {
if (this.status === Promise.PENDING) {
this.status = Promise.REJECTED;
this.reason = reason;

setTimeout(() => {
this.onRejectedCallbacks.forEach((cb) => cb(this.reason));
});
}
}

then(onFulfilled, onRejected) {
if (typeof onFulfilled !== 'function') {
onFulfilled = () => this.value;
}

if (typeof onRejected !== 'function') {
onRejected = () => this.reason;
}

return new Promise1((resolve, reject) => {
if (this.status === Promise.PENDING) {
return new Promise1((resolve, reject) => {
this.onFulfilledCallbacks.push(() => {
this.parse(onFulfilled(this.value), resolve, reject);
});
});

return new Promise1((resolve, reject) => {
this.onRejectedCallbacks.push(() => {
this.parse(onRejected(this.value), resolve, reject);
});
});
}

if (this.status === Promise.FULFILLED) {
setTimeout(() => {
return new Promise1((resolve, reject) => {
this.parse(onFulfilled(this.value), resolve, reject);
});
});
}

if (this.status === Promise.REJECTED) {
setTimeout(() => {
return new Promise1((resolve, reject) => {
this.parse(onRejected(this.reason), resolve, reject);
});
});
}
});
}

parse(result, resolve, reject) {
try {
if (result instanceof Promise1) {
result.then(resolve, reject);
} else {
resolve(result);
}
} catch (error) {
reject(error);
}
}

static resolve(value) {
return new Promise1((resolve, reject) => {
if (value instanceof Promise1) {
value.then(resolve, reject);
} else {
resolve(value);
}
});
}

static reject(reason) {
return new Promise1((resolve, reject) => {
reject(reason);
});
}

static all(promises) {
const result = [];

return new Promise1((resolve, reject) => {
promises.forEach((promise) => {
promise.then(
(value) => {
result.push(value);

if (result.length === promises.length) {
resolve(result);
}
},
(reason) => {
reject(reason);
}
);
});
});
}

static race(promises) {
return new Promise1((resolve, reject) => {
promises.forEach((promise) => {
promise.then(
(value) => {
resolve(value);
},
(reason) => {
reject(reason);
}
);
});
});
}
}

let aaa = new Promise1((resolve, reject) => {
resolve('lbs');
// reject('error')
}).then((value) => {
console.log(value);
return '666';
});

aaa.then(() => {
console.log(123);
});
console.log(aaa);

手写 promise.allSettled

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
function PromiseAllSettled(promiseArr) {
return new Promise((resolve, reject) => {
if (!Array.isArray(promiseArr)) {
reject(new TypeError('arguments must be an array'));
}
const res = [];
let count = 0;
for (let i = 0; i < promiseArr.length; i++) {
Promise.resolve(promiseArr[i])
.then((value) => {
res[i] = {
status: 'fulfilled',
value,
// 出题:输入一个 Promise 实例数组,输出最快、最慢的实例,以及每个实例的响应时长
//加一个执行时间计算
cost: Date.now() - startTime,
};
})
.catch((reason) => {
res[i] = {
status: 'rejected',
reason,
//加一个执行时间计算
cost: Date.now() - startTime,
};
})
.finally(() => {
count++;
if (count == promiseLen) {
resolve(res);
}
});
}
});
}

手写 event bus

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
class EventEmitter {
constructor() {
this.events = {};
this.maxListeners = maxListeners || Infinity;
}

emit(event, ...args) {
const cbs = this.events[event];

if (!cbs) {
console.log('没有这个事件函数');
return this;
}

cbs.forEach((cb) => cb.apply(this, args));

return this;
}

on(event, cb) {
if (!this.events[event]) {
this.events[event] = [];
}

if (
this.maxListeners !== Infinity &&
this.events[event].length >= this.maxListeners
) {
console.log('当事件超过了最大监听数');
return this;
}

this.events[event].push(cb);
return this;
}

once(event, cb) {
const fn = (...args) => {
this.off(event, fn);
cb.apply(this, args);
};

this.on(event, func);
return this;
}

off(event, cb) {
if (!cb) {
this.events[event] = null;
} else {
this.events[event] = this.events[event].filter((item) => item !== cb);
}
return this;
}
}

继承

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
// 构造函数继承
function Person() {
this.name = 'lbs';
}

function Student() {
Person.call(this);
this.age = 18;
}

const s = new Student();

// 原型链继承
function Person() {
this.name = 'lbs';
}

function Student() {
this.age = 18;
}

Student.prototype = new Person();

const s = new Student();
//寄生式组合继承
function Person(obj) {
this.name = obj.name;
}

function Student(obj) {
Person.call(this, obj);
this.age = obj.age;
}

// object.create()
// 方法创建一个新对象,使用现有的对象来提供新创建对象的__proto__
Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;

// 或者:
Student.prototype = Object.create(Person.prototype, {
constructor: {
value: Student,
enumerable: false,
writable: true,
configurable: true,
},
});

const student = new Student({ name: 'lbs', age: 18 });

console.log(student);

createElement 手写

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
const symbolFor = Symbol.for;
const REACT_ELEMENT_TYPE = symbolFor('react.element');

const RESERVED_PROPS = {
key: true,
ref: true,
__self: true,
__source: true,
};

function createElement(type, config, children) {
const props = {};
let key = null;

if (config !== null) {
key = config.key;
}

for (let propName in config) {
if (!RESERVED_PROPS.hasOwnProperty(propName)) {
props[propName] = config[propName];
}
}

const childrenLength = arguments.length - 2;
if (childrenLength === 1) {
props.children = children;
} else if (childrenLength > 1) {
const childArray = Array(childrenLength);

for (let i = 0; i < childrenLength; i++) {
childArray[i] = arguments[i + 2];
}

props.children = childArray;
}

const element = {
$$typeof: REACT_ELEMENT_TYPE,
type,
key,
props,
};
}

编写正则,验证一个 6 ~ 16 位的字符串,必须同时包含大小写字母和数字

1
2
3
4
5
6
7
正向预查 ?= 必须
反向预查 ?!必须不
let reg = /(?!^[a-zA-Z]+$)(?!^[0-9]+$)(?!^[a-z0-9]+$)(?!^[A-Z0-9]+$)^[a-zA-Z0-9]{6,16}$/;

补充:数字、字母、下划线组成字符串,必须有_
let reg = /(?=_)\w/;
let reg = /(?!^[a-zA-Z0-9]+$)^\w{1,10}$/; 限制1-10

获取所有属性为 name,值为 value 的元素集合

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
// 正则\b单词边界
function getElements(property, value) {
let elements = document.getElementsByTagName('*');
let arr = [];

elements = Array.from(elements);
elements.forEach((item) => {
// 当前元素property对应的值
let itemValue = item.getAttribute(propertype);

if (property === 'class') {
const reg = new RegExp(`\b${value}\b`);

if (reg.test(itemValue)) {
arr.push(item);
}
}

if (itemValue === value) {
arr.push(item);
}
});

return arr;
}

英文字母汉字组成的字符串,用正则给英文单词前后加空格

1
2
3
4
5
6
7
8
let str = '中国hello你好';
let reg = /\b[a-z]+\b/gi;

str = str
.replace(reg, (value) => {
return ` ${value} `;
})
.trim(); // 去除首尾空格

js 实现斐波那契数列的几种方法

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
// 1.递归
function fibonacci(n) {
if (n === 1 || n === 2) {
return 1;
}

return fibonacci(n - 1) + fibonacci(n - 2);
}

// 2.尾递归(每次调用都在收集结果,避免了线性递归不收集结果只依次展开消耗内存的坏处)
function fibonacci(n, res1 = 1, res2 = 1) {
if (n <= 2) return res2;

return fibonacci(n - 1, res2, res1 + res2);
}

// 循环
function fibonacci(n) {
let num1 = 1;
let num2 = 2;
let sum = 1;

for (let i = 3; i < n; i++) {
sum = num1 + num2;
num1 = num2;
num2 = sum;
}

return sum;
}

// 数组
function fibonacci(n) {
const arr = [0, 1, 1];
if (n < 0) {
throw new Error('输入的数字不能小于0');
}

if (n >= 3) {
for (let i = 3; i <= n; i++) {
arr[i] = arr[i - 1] + arr[i - 2];
}
}

return arr[n];
}

js 并发调度器

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
//题目:
// 延迟函数
const sleep = (time) => new Promise((resolve) => setTimeout(resolve, time));

// 同时进行的任务最多2个
const scheduler = new Scheduler(2);

// 添加异步任务
// time: 任务执行的时间
// val: 参数
const addTask = (time, val) => {
scheduler.add(() => {
return sleep(time).then(() => console.log(val));
});
};

addTask(1000, '1');
addTask(500, '2');
addTask(300, '3');
addTask(400, '4');

// 答:
class Scheduler {
constructor(max) {
this.max = max;
this.count = 0;
this.queue = [];
}
add(p) {
this.queue.push(p);
this.start();
}
start() {
if (this.count >= this.max || !this.queue.length) return;
this.count++;
this.queue
.shift()()
.finally(() => {
this.count--;
this.start();
});
}
}

并发加载

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
function limitLoad(urls, handler, limit) {
let promises = [];
const limitUrls = urls.slice(0, limit);
const restUrls = urls.slice(limit);
promises = limitUrls.map((url, index) => {
return handler(url).then(() => {
return index;
});
});

let p = Promise.race(promises);

for (let i = 0; i < restUrls.length; i++) {
p = p.then((index) => {
promises[index] = handler(restUrls[i]).then(() => {
return index;
});

return Promise.race(promises);
});
}
}

function loadImg(url) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(url.info + '---OK!!!');
resolve();
}, url.time);
});
}

let urls = [
{ info: 1, time: 2000 },
{ info: 2, time: 1000 },
{ info: 3, time: 3000 },
{ info: 4, time: 4000 },
{ info: 5, time: 5000 },
];

limitLoad(urls, loadImg, 3);

并发控制

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
import axios from 'axios';

const requestQueue = (concurrency = 6) => {
const queue = [];// 请求池
const current = 0;
const dequeue = () => {
while(current < concurrency && queue.length) {
current++;
const curRequest = queue.shift();

curRequest
.then(() =>{})
.catch(() => {})
.finally(() => {
current--;
dequeue();
})
}
}

return (request) => {
queue.push(request);
dequeue();
}
}

const enqueue = requestQueue(3);
for (let i = 0; i < reqs.length; i++) {
enqueue(() => axios.get('/api/test' + i))
}

前端内存处理

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
1. 内存的生命周期
内存分配:声明变量、函数对象的时候,js会自动分配内存
内存使用
内存回收

2. js中的垃圾回收机制
引用计数:缺点循环引用无法清除
标记清除

3. 常见内存泄漏
全局变量(记得手动回收)
未被清除的定时器
闭包
dom的引用

4. 怎么避免内存泄漏
减少不必要的全局变量
使用完数据,及时解除引用

实现sizeOf,传入object,计算其所占字节大小
number: 8 字节
string: 2 字节
boolean; 4 字节

const seen = new WeakSet();

function sizeOfObject(obj) {
if (obj === null) {
return 0;
}

let bytes = 0;
const keys = Object.keys(obj);

for(let i = 0; i < keys.length; i++) {
const key = keys[i];
bytes += calculator(key);

if (typeof obj[key] === 'object' && obj[key] !== null) {
if (seen.has(obj[key])) {
continue;
}

seen.add(obj[key])
}

bytes += calculator(obj[key])
}
}

function calculator(obj) {
const objType = typeof obj;

switch(objType) {
case 'string': {
return obj.length * 2
}
case 'boolean': {
return 4
}
case 'number': {
return 8
}
case 'object': {
if (Array.isArray(obj)) {
return obj.map(calculator).reduce((accu, curr) => {
return accu + curr
}, 0)
} else {
return sizeOfObject(obj)
}
}
default: {
return 0
}
}
}

数据结构就是在计算机中存储和组织数据的方式。

算法(Algorithm)解决问题的逻辑或步骤

封装函数使字符串以驼峰式命名

  • 已知字符串 foo = ‘get-element-by-id’,写一个函数将其转换为驼峰式命名“getElementById”
1
2
3
4
5
6
var foo = 'get-element-by-id';
var arr = foo.split('-');
for (var i = 1; i < arr.length; i++) {
arr[i] = arr[i].charAt(0).toUpperCase() + arr[i].substring(1);
}
console.log(arr.join(''));
1
2
3
4
5
6
7
8
9
10
11
//封装
function toString(foo) {
var arr = foo.split('-');

for(var i = 1; i < arr.length; i++) {
arr[i] = arr[i].charAt(0).toUpperCase() + arr[i].substr(1, arr[i].length - 1)
}

return arr.join('');
}
console.log(toString('get-element-by-id'))
  • 把 the-first-name 变成 theFirstName
1
2
3
4
5
6
var reg = /-(\w)/g
var str = "the-first-name"
console.log(str.replace(reg, function($, $1){
return $1.toUpperCase()
})
)

把 aabb 换成 bbaa

1
2
3
4
5
6
7
8
var reg = /(\w)\1(\w)\2/g;
var str = 'aabb';
// console.log(str.replace(reg,"$2$2$1$1"));//"bbaa"
console.log(
str.replace(reg, function ($, $1, $2) {
return $2 + $2 + $1 + $1;
})
);

正则简单的去重

1
2
3
var str = "aaaabbbbbccccc";
var reg = /(\w)\1*/g;
console.log.replace(reg,"$1"));//abc

定制化输出特定数组

  • 随机生成一个长度为 10 的整数类型的数据
  • 例如 [2, 10, 3, 35, 5, 11, 10, 11, 20]
  • 将其排列成一个新数组,要求新数组形式如下:
  • [[2, 3, 5], [10, 11],[20],[35]]
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
// 1. 获取随机数 0-99
function getRandomNumber(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1) + min);
}
let arr = Array.from({ length: 10 }, () => getRandomNumber(0, 99));
// 2. 去重(没必要)
arr = [...new Set(arr)];
// 3. 排序
arr.sort((a, b) => a - b);
// 4. 存储 0-9 10-19 20-29
const map = {};
arr.forEach((item) => {
const key = Math.floor(item / 10);

if (!map[key]) {
map[key] = [];
}

map[key].push(item);
});
const result = [];
for (const key in map) {
result.push(map[key]);
}

console.log(result);

深度比较 isEqual

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
function isEqual(obj1, obj2) {
//其中一个为值类型或null
if (!isObject(obj1) || !isObject(obj2)) {
return obj1 === obj2;
}

//判断是否两个参数是同一个变量
if (obj1 === obj2) {
return true;
}

//判断keys数是否相等
const obj1Keys = Object.keys(obj1);
const obj2Keys = Object.keys(obj2);
if (obj1Keys.length !== obj2Keys.length) {
return false;
}

//深度比较每一个key
for (let key in obj1) {
if (!isEqual(obj1[key], obj2[key])) {
return false;
}
}

return true;
}

根据数组的 key 去重

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const dedup = (data, getKey = () => {}) => {
const dateMap = data.reduce((pre, cur) => {
const key = getKey(cur);
if (!pre[key]) {
pre[key] = cur;
}
return pre;
}, {});
return Object.values(dateMap);
};

let data = [
{ id: 1, v: 1 },
{ id: 2, v: 2 },
{ id: 1, v: 1 },
];
console.log(dedup(data, (item) => item.id));

react 自定义封装不会反复创建的定时器(setInterval)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { useRef, useState } from 'react';

const useTimer = (step = 1) => {
const timer = useRef(null);
const [num, setNum] = useState(0);

const start = () => {
const timeout = setInterval(() => {
setNum((num) => num + 1);
}, step * 1000);
timer.current = timeout;
};

const clear = () => {
setNum(0);
clearInterval(timer.current);
};

return {
num,
start,
clear,
};
};

修改下面代码,顺序输出 0-99

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
// 要求:
// 1. 只能修改 setTimeout
// 2. 不能修改Math.floor(Math.random() * 1000)
// 3. 不能使用全局变量
function print(n) {
setTimeout(() => {
console.log(n);
}, Math.floor(Math.random() * 1000));
}
for (var i = 0; i < 100; i++) {
print(i);
}

// 答案
// 方法1: 立即执行函数
function print(n) {
setTimeout(
(() => {
console.log(n);
return () => {};
})(),
Math.floor(Math.random() * 1000)
);
}
for (var i = 0; i < 100; i++) {
print(i);
}
// 方法2: setTimeout第三个参数
function print(n) {
setTimeout(
() => {
console.log(n);
},
10,
Math.floor(Math.random() * 1000)
);
}
for (var i = 0; i < 100; i++) {
print(i);
}

for 循环和 splice 的坑

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
// for循环和splice的坑
// 具体描述:在对一个数组执行for循环时,
// 删除数组元素,会存在什么问题
// 方法1: i--
const arr = ['a', 'a', 'a', 'd', 'e', 'f'];
for (let i = 0; i < arr.length; i++) {
if (arr[i] === 'a') {
arr.splice(i, 1);
i--; // 需要处理下 i--
}
}
// 方法2: 倒序
const arr = ['a', 'a', 'a', 'd', 'e', 'f'];
for (let i = arr.length - 1; i >= 0; i--) {
if (arr[i] === 'a') {
arr.splice(i, 1);
}
}
// for...in
const arr = ['a', 'a', 'a', 'd', 'e', 'f'];
for (let index in arr) {
if (arr[index] === 'a') {
arr.splice(index, 1);
index--; // 仍然会有问题
}
}

console.log(arr);

复杂度

  • 程序执行时需要的计算量和内存空间
  • 复杂度是数量级,不是具体的数字
  • 一般是针对一个具体的算法,而非一个完整的系统

将一个数组旋转 K 步

  • 输入一个数组[1, 2, 3, 4, 5, 6, 7]
  • k=3,即旋转 3 步
  • 输出[5, 6, 7, 1, 2, 3, 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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
/**
* @param arr 原数组
* @param k 步数
* 时间复杂度O(n^2),空间复杂度O(1)
*/
function rotate1(arr: number[], k: number): number[] {
const length = arr.length;
if (!k || length === 0) return arr;

const step = Math.abs(k % length);

for (let i = 0; i < step; i++) {
const n = arr.pop();

if (n != null) {
// unshift内置api时间复杂度位O(n),开销比较大
arr.unshift(n);
}
}

return arr;
}

/**
* @param arr 原数组
* @param k 步数
* 时间复杂度O(1),空间复杂度O(n)
*/
function rotate2(arr: number[], k: number): number[] {
const length = arr.length;
if (!k || length === 0) return arr;

const step = Math.abs(k % length);

const part1 = arr.slice(-step);
const part2 = arr.slice(0, length - step);
const part3 = part1.concat(part2);

return arr;
}

/**
* 性能测试
*/
const arr = [];
for (let i = 0; i < 10 * 10000; i++) {
arr.push(i);
}

console.time('rotate1');
rotate1(arr, 9 * 10000);
console.time('rotate1');

console.time('rotate2');
rotate2(arr, 9 * 10000);
console.time('rotate2');

两个栈实现一个队列

  • 请用两个栈实现一个队列
  • API:add delete length
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 Queue {
private stack1: number[] = [];
private stack2: number[] = [];

add(n: number) {
this.stack1.push(n);
}

delete(): number | null {
let res;

while (stack1.length) {
const n = stack1.pop();

if (n != null) {
stack2.push(n);
}
}

res = stack2.pop();

while (stack2.length) {
const n = stack2.pop();

if (n != null) {
stack1.push(n);
}
}

return res || null;
}

get length(): number {
return this.stack1.length;
}
}

定义一个 js 函数,反转单向链表

链表是一种物理结构(非逻辑结构),类似数组
数组需要一段连续的内存空间,而链表是零散的
链表节点的数据结构{ value, next?, prev? }

链表 vs 数组
都是有序结构
链表:查询慢 O(n),新增和删除快 O(1)
数组:查询快 O(1),新增和删除慢 O(n)

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
interface ILinkListNode {
value: number;
next?: ILinkListNode;
}

/**
* 创建链表
* @param arr 数组
*/
function createLinkList(arr: number[]): ILinkListNode {
const length = arr.length;
if (length === 0) throw new Error('arr is empty');

let curNode: ILinkListNode = {
value: arr[length - 1],
};
if (length === 1) return curNode;

for (let i = length - 2; i >= 0; i--) {
curNode = {
value: arr[i],
next: curNode,
};
}

return curNode;
}

const arr = [100, 200, 300];
const list = createLinkList(arr);
console.info('list', list);

/**
* 反转单向链表,返回反转后的head node
* @param listNode 需要操作的链表
*/
function reverseLinkList(listNode: ILinkListNode): ILinkListNode {
let prevNode: ILinkListNode | undefined;
let curNode: ILinkListNode | undefined;
let nextNode: ILinkListNode | undefined = listNode;

while (nextNode) {
// 第一个元素,删除next指针,防止循环引用
if (curNode && !prevNode) {
delete curNode.next;
}

// 反转指针
if (curNode && prevNode) {
curNode.next = prevNode;
}

prevNode = curNode;
curNode = nextNode;
nextNode = nextNode?.next;
}

// 处理链表最后一个元素
curNode!.next = prevNode;

return curNode!;
}

const reverseList = reverseLinkList(list);
console.info('reverseList', reverseList);

栈(封装)

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
function Stack() {
this.items = [];
// 1. 将元素压入栈
Stack.prototype.push = function (element) {
this.items.push(element);
};
// 2.从栈中取出元素
Stack.prototype.pop = function () {
return this.items.pop();
};
// 3.查看一下栈顶元素
Stack.prototype.peek = function () {
return this.items[this.items.length - 1];
};
// 4.判断栈是否为空
Stack.prototype.isEmpty = function () {
return !this.items.length;
};
// 5.获取栈中元素个数
Stack.prototype.size = function () {
return this.item.length;
};
// 6. toString方法
Stack.prototype.toString = function () {
let res = '';
for (let i = 0; i < this.items.length; i++) {
res += `${this.items[i]} `;
}
return res;
};
}

const stack = new Stack();

// 实例:将十进制转换成二进制
function dec2bin(decimalNumber) {
let stack = new Stack();
let binary = '';

while (decimalNumber > 0) {
stack.push(decimalNumber % 2);

decimalNumber = Math.floor(decimalNumber / 2);
}

while (!stack.isEmpty()) {
binary += stack.pop();
}

return binary;
}

深度封装 typeof 判断

function myTypeof(val) {
    var type = typeof(val)
    var res = {
        '[object Object]' : 'object',
        '[object Array]' : 'array',
        '[object Number]' : 'object number',
        '[object String]' : 'object string',
        '[object Boolean]' : 'object boolean'
    }
    if (val === null) {
        return 'null'
    } else if (type == 'object') {
        var str = Object.prototype.toString.call(val)
        return res[str]
    } else {
        return type
    }
}

翻转链表

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
// 递归
var reverseList = function (head) {
if (head === null || head.next === null) return head;

let res = reverseList(head.next);

head.next.next = head;
head.next = null;

return res;
};

// 循环
var reverseList = function (head) {
let pre = null;
let cur = head;

if (cur === null || cur.next === null) {
return cur;
}

while (cur) {
const t = cur.next;
cur.next = pre;
pre = cur;
cur = t;

// cur.next = null
// cur.next.next = cur
// cur = cur.nexxt
}

return pre;
};

打乱数组

1. 常见的sort打乱数组的方法

function shuffle(arr) {
   return arr.sort (function () {
        return Math.random() - 0.5
    })
}
var arr = [1,2,3,4,5,6,7]
shuffle(arr)

更加简洁的ES6写法

function shuffle(arr) {
    return arr.sort(() => Math.random() - 0.5)
}

但是这种写法有问题,并不能真正地随机打乱数组,经过大量的实验发现
每个元素仍然有很大的几率出现在它原来的位置附近。

2.洗牌算法
    从最后一个数据开始往前,每次随机一个位置,将两者的位置进行交换,直到数组交换完毕。


ES6实现:

function shuffle(arr) {
    let i =  arr.length;
    while(i) {
        let j = Math.floor(Math.random() * i--);
        [arr[j], arr[i]] = [arr[i], arr[j]];
    }
    return arr
}

var arr = [1,2,3,4,5,6,7]
shuffle(arr)

用链表实现队列

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
interface ILinkListNode {
value: number;
next: ILinkListNode | null;
}

class Queue {
private head: ILinkListNode | null = null;
private tail: ILinkListNode | null = null;
private len = 0;

// 入队,在tail位置
add(n: number) {
const newNode: ILinkListNode = {
value: n,
next: null,
};

if (this.head === null) {
this.head = newNode;
}

if (this.tail) {
this.tail.next = newNode;
}

this.tail = newNode;
this.len++;
}

// 出队,在head位置
delete(): number | null {
if (this.head === null || this.len <= 0) return null;

const value = this.head.value;

this.head = this.head.next;
this.len--;

return value;
}

get length(): number {
return this.len;
}
}

const queue = new Queue();
queue.add(100);
queue.add(200);
queue.add(300);
console.log('length', queue.length); //3
console.log(queue.delete()); //100
console.log('length2', queue.length); //2

链表和数组,哪个实现队列更快?

  • 空间复杂度都是 O(n)
  • add 时间复杂度:链表 O(1),数组 O(1);
  • delete 时间复杂度:链表 O(1),数组 O(n);

斐波那契数列

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
/**
* 斐波那契数列(递归)
* @param n
* 时间复杂度O(2^n)
*/
function fibonacci(n: number): number {
if (n <= 0) return 0;
if (n === 1) return 1;

return fibonacci(n - 1) + fibonacci(n - 2);
}

/**
* 斐波那契数列(循环)
* @param n
* 时间复杂度O(n)
*/
function fibonacci(n: number): number {
if (n <= 0) return 0;
if (n === 1) return 1;

let n1 = 1; // 记录n-1的结果
let n2 = 0; // 记录n-2的结果
let res = 0;

for (let i = 2; i <= n; i++) {
res = n1 + n2;
n2 = n1;
n1 = res;
}

return res;
}

青蛙跳台阶(动态规划思想解决问题)

  • 一只青蛙,一次可以跳 1 级,也可以跳 2 级
  • 问:青蛙跳到 n 级台阶,总共有多少种方式?
1
// 答案同上一题

将数组中的 0 移动到末尾

  • 如输入[1, 0, 3, 0, 11, 0],输出[1, 3, 11, 0, 0, 0]
  • 只移动 0 ,其他顺序不变
  • 必须在原数组进行操作
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
/**
* (嵌套循环)
* @param arr number arr
* 时间复杂度 O(n^2)
*/
function moveZero1(arr: number[]): void {
const length = arr.length;
if (length === 0) return;

let zeroCount = 0;
for (let i = 0; i < length - zeroCount; i++) {
if (arr[i] === 0) {
arr.push(0);
arr.splice(i, 1); // O(n)
i--;
zeroCount++;
}
}
}

/**
* (双指针)
* @param arr number arr
* 时间复杂度 O(n)
*/
function moveZero2(arr: number[]): void {
const length = arr.length;
if (length === 0) return;

let i;
let j = -1; // 指向第一个 0

for (i = 0; i < length; i++) {
if (arr[i] === 0) {
if (j < 0) {
j = i;
}
}

if (arr[i] !== 0 && j >= 0) {
const n = arr[i];
arr[i] = arr[j];
arr[j] = n;
j++;
}
}
}

const arr = [0, 1, 2, 0, 3, 0, 0, 4];

// moveZero1(arr);
moveZero2(arr);
console.log(arr);

计算字符串中连续最多的字符以及次数

  • 输入’abbbcccccccddeee1234412’
  • 计算得到连续最多的字符是’c’,7 次
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
/**
* @param str
* 时间复杂度: O(n)
*/
interface IRes {
char: string;
length: number;
}
function findContinuousChar1(str: string): IRes {
const res: IRes = {
char: '',
length: 0,
};

const length = str.length;
if (length === 0) return res;

let tempLength = 0; // 临时记录当前连续字符的长度

for (let i = 0; i < length; i++) {
tempLength = 0; // 重置

for (let j = i; j < length; j++) {
if (str[i] === str[j]) {
tempLength++;
}

if (str[i] !== str[j] || j === length - 1) {
if (tempLength > res.length) {
res.char = str[i];
res.length = tempLength;
}

if (i < length - 1) {
i = j - 1; // 跳步
}

break;
}
}
}

return res;
}

/**
* 双指针
* @param str
* 时间复杂度: O(n)
*/
interface IRes {
char: string;
length: number;
}
function findContinuousChar2(str: string): IRes {
const res: IRes = {
char: '',
length: 0,
};

const length = str.length;
if (length === 0) return res;

let tempLength = 0; // 临时记录当前连续字符的长度
let i = 0;
let j = 0;
for (; i < length; i++) {
if (str[i] === str[j]) {
tempLength++;
}
if (str[i] !== str[j] || i === length - 1) {
// 如果不相等或者 i 循环到了末尾
if (tempLength > res.length) {
res.char = str[j];
res.length = tempLength;
}
tempLength = 0;
if (i < length - 1) {
j = i; // 让 j 追上 i
i--;
}
}
}

return res;
}

const str = 'abbbcccccccddeee1234412';
console.log(findContinuousChar1(str));

对称数(回文)

  • 求 1 - 10000 之间的所有对称数(回文)
  • 例如:0, 1, 2, 11, 22, 101, 232,1221
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
function findPalindromeNumbers1(max: number): number[] {
const res: number[] = [];
if (max < 0) return res;

for (let i = 1; i <= max; i++) {
// 转换为字符转 -> 转换为数组 -> 再反转 -> 比较
const s = i.toString();
if (s === s.split('').reverse().join('')) {
res.push(i);
}
}

return res;
}

console.log(findPalindromeNumbers1(200));

function findPalindromeNumbers2(max: number): number[] {
const res: number[] = [];
if (max <= 0) return res;

for (let i = 1; i <= max; i++) {
let n = i;
let rev = 0;
// n: 123
// rev: 321

while (n > 0) {
rev = rev * 10 + (n % 10);
n = Math.floor(n / 10);
}

if (i === rev) res.push(i);
}
}

console.log(findPalindromeNumbers2(200));

最长回文串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function longestPalindrome(s: string): string {
let res = '';
for (let i = 0; i < s.length; i++) {
// 寻找长度为奇数的回文子串(以当前元素向两边扩散)
const s1 = palindrome(s, i, i);
// 寻找长度为偶数的回文子串(以s[i],s[i + 1])向两边扩散
const s2 = palindrome(s, i, i + 1);
res = res.length > s1.length ? res : s1;
res = res.length > s2.length ? res : s2;
}
return res;
}

function palindrome(s, l, r) {
// 左右指针,从s[l]和s[r]向两边扩散,找到最长回文串
while (l >= 0 && r < s.length && s[l] == s[r]) {
l--;
r++;
}
return s.substr(l + 1, r - l - 1);
}

判断字符串是否括号匹配

  • 一个字符串 s 可能包含{}()[]三种括号
  • 判断 s 是否是括号匹配的
  • 如(a{b}c)匹配,而{a(b 或者{a(b}c)就是不匹配的

栈 vs 数组
栈:逻辑结构,理论模型,不管如何实现,不受任何语言的限制。
数组:物理结构,真实的功能实现,受限于编程语言。

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
function isMatch(left: string, right: string): boolean {
if (left === '{' && right === '}') return true;
if (left === '(' && right === ')') return true;
if (left === '[' && right === ']') return true;
return false;
}

function matchBracket(str: string): boolean {
const length = str.length;

if (length === 0) return true;

const stack = [];
const leftSymbols = '{[(';
const rightSymbols = ')]}';

// 时间复杂度O(n),空间复杂度O(n)
for (let i = 0; i < length; i++) {
const s = str[i];

if (leftSymbols.includes(s)) {
// 左括号,压栈
stack.push(s);
} else if (rightSymbols.includes(s)) {
const top = stack[stack.length - 1];

// 判断右括号是否匹配
if (isMatch(top, s)) {
stack.pop();
} else {
return false;
}
}
}

return stack.length === 0;
}

高效的字符串前缀匹配

  • 有一个英文单词库(数组),里面有几十万个英文单词
  • 输入一个字符串,快速判断是不是某一个单词的前缀
  • (说明思路,不用写代码)

思路一:

  1. 遍历单词库数组

  2. indexOf 判断前缀

  3. 实际时间复杂度超过 O(n),因为 indexOf 的计算量

思路二(对象取 key 时间复杂度为 O(1)):

  1. 英文字母一共就 26 个,可以提前把单词库数组拆分为 26 个

  2. 第一层 26 个,第二层、第三层,继续拆分…

  3. 最后把单词库拆分为一颗树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 树结构
const wordsTree = {
a: {
a: {...}
b: {...}
},
b: {
a: {...}
},
c: {
a: {...}
}
...
}

数字千分位格式化

  • 将数字千分位格式化,输出字符串
  • 如输入数字 12050100,输出字符串 12,050,100
  • (注意:逆序判断)
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
/**
* @param n
* 使用数组
*/
function format1(n: number): string {
n = Math.floor(n); // 只考虑整数
const reverseArr = n.toString().split('').reverse();

return reverseArr.reduce((prev, curr, index) => {
if (index % 3 === 0) {
if (prev) {
return curr + ',' + prev;
} else {
return curr;
}
} else {
return curr + prev;
}
}, '');
}
/**
* @param n
* 使用字符串
*/
function format2(n: number): string {
n = Math.floor(n);

let res = '';
const str = n.toString();
const length = str.length;

for (let i = length - 1; i >= 0; i--) {
const j = length - i;

if (j % 3 === 0) {
if (i === 0) {
res = str[i] + res;
} else {
res = ',' + str[i] + res;
}
} else {
res = str[i] + res;
}
}

return res;
}

"1000000000"变成"100.000.000"这种写法,把后面往前面查,三位加个点


var str = '100000000';
var reg = /(?=(\B)(\d{3})+$)/g;
console.log(str.replace(reg, '.')) || //"100.000.000"
string.replace(/\B(?=(\d{3})+(?!\d))/g, '.') || //先行断言?=,后行断言(?!\d)
(25435345.22).toLocaleString('en-US');

切换字母大小写

  • 输入一个字符串,切换其中字母的大小写
  • 如:输入字符串 12aBc34,输出字符串 12AbC34
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
/**
* 正则表达式
* @param s
*/
function switchLetterCase1(s: string): string {
let res = '';
const length = s.length;
if (length === 0) return res;

const reg1 = /[a-z]/;
const reg2 = /[A-Z]/;

for (let i = 0; i < length; i++) {
const c = s[i];

if (reg1.test(c)) {
res += c.toUpperCase();
} else if (reg2.test(c)) {
res += c.toLowerCase();
} else {
res += c;
}
}

return res;
}

/**
* ASCII 编码
* @param s
*/
function switchLetterCase2(s: string): string {
let res = '';
const length = s.length;
if (length === 0) return res;

const reg1 = /[a-z]/;
const reg2 = /[A-Z]/;

for (let i = 0; i < length; i++) {
const c = s[i];
const code = c.charCodeAt(0);

if (code >= 65 && code <= 90) {
res += c.toLowerCase();
} else if (code >= 97 && code <= 122) {
res += c.toUpperCase();
} else {
res += c;
}
}

return res;
}

「扁平数组」转「树形结构」

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function treeing(arr) {
let tree = [];
let map = {};

for (let item of arr) {
const newItem = (map[item.id] = {
...item,
children: [],
});

if (map[item.pid]) {
const parent = map[item.pid];
parent.children.push(newItem);
} else {
tree.push(newItem);
}
}
return tree;
}

「树形结构」转「扁平结构」

1
2
3
4
5
6
7
8
9
10
function flatten(tree, arr = []) {
tree.forEach((item) => {
const { children, ...rest } = item;
arr.push(rest);
if (children.length) {
flatten(children, arr);
}
});
return arr;
}

将树状结构转换为属性平铺的结构

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
// 题目
const entry = {
a: {
b: {
c: {
dd: 'abcdd',
},
},
d: {
ee: 'adee',
},
f: 'af',
},
};

const output = {
'a.b.c.dd': 'abcdd',
'a.d.ee': 'adee',
'a.f': 'af',
};

// 解答
// 方法1: 递归
function flatObj(obj, preKey = '', result = {}) {
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
const newKey = `${preKey}${key}`;

if (typeof obj[key] === 'object') {
flatObj(obj[key], `${newKey}.`, result);
} else {
result[newKey] = obj[key];
}
}
}
return result;
}
// 方法2: while循环-队列
function flatObj2(obj) {
const queue = Object.entries(obj);
const result = {};

while (queue.length) {
const [key, value] = queue.pop();

for (const [k, v] of Object.entries(value)) {
if (typeof v === 'object') {
queue.push([`${key}.${k}`, v]);
} else {
result[`${key}.${k}`] = v;
}
}
}

return result;
}

// 测试用例
flatObj(entry);
flatObj2(entry);

将平铺属性的数据结构转换为树状数据结构

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
// 题目
const entry = {
'a.b.c.dd': 'abcdd',
'a.d.ee': 'adee',
'a.f': 'af',
};

const output = {
a: {
b: {
c: {
dd: 'abcdd',
},
},
d: {
ee: 'adee',
},
f: 'af',
},
};

// 答案
// 方法1: 双重循环
function map(entry) {
const result = {};

for (const key in entry) {
const value = entry[key];
const keyMap = key.split('.');

if (!result[keyMap[0]]) {
result[keyMap[0]] = {};
}

let tmp = result[keyMap[0]];
let length = keyMap.length;

for (let i = 1; i < length; i++) {
if (!tmp[keyMap[i]]) {
if (i === length - 1) {
tmp[keyMap[i]] = value;
} else {
tmp[keyMap[i]] = {};
}
}
tmp = tmp[keyMap[i]];
}
}

return result;
}
// 方法2: 递归
// {"a.b.c.dd": "abcdd"}
// => {"a.b.c": {"dd":"abcdd"}}
// =>...
// => {"a": {"b": {"c": {"dd": "abcdd"}}}}
function map2(entry) {
function getNest(key) {
const lastIndex = key.lastIndexOf('.');
const value = entry[key];

if (lastIndex !== -1) {
delete entry[key];
const preKey = key.substring(0, lastIndex);
const restKey = key.substring(lastIndex + 1);

if (!entry[preKey]) {
entry[preKey] = { [restKey]: value };
} else {
entry[preKey][restKey] = value;
}

if (/./.test(preKey)) {
getNest(preKey);
}
}
}

for (const key in entry) {
getNest(key);
}

return entry;
}

map(entry);
map2(entry);

实现 jsonp,传入 url、callback 和 callbackName 三个参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function jsonp(url, callback, callbackName) {
const script = document.createElement('script');

script.src = `${url}?type=jsonp&callbackName=${callbackName}}`;
script.onload = callback;

document.body.appendChild(script);
window[callbackName] = function (data) {
console.log(data);
document.body.removeChild(script);
};
}

jsonp(
'http://www.xxx.com/xxx',
function (data) {
console.log(data);
},
'fn'
);

实现计时器 timer,仅暴露 start、stop、reset 方法

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
function Timer() {
let second = 0;
let refId = null;
const clear = function () {
if (refId !== null) {
clearInterval(refId);
}
};

const start = function () {
refId = setInterval(() => {
console.log(second);
second += 1;
}, 1000);
};

const stop = function () {
clear();
};

const reset = function () {
second = 0;
clear();
};
return { start, stop, reset };
}

点击页面链接时,验证连接是否在*.taobao.com 下,如果不是弹框提示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 <a class="link" href="http://www.taobao.com">淘宝</a>
<a class="link" href="http://www.jd.com">京东</a>

<script>
const links = document.getElementsByClassName('link')
const listener = function(e) {
const href = e.target.getAttribute('href')
if (href.indexOf('.taobao.com') === -1) {
const result = confirm('确定离开吗')
if (result === false) {
e.preventDefault()
e.stopPropagation()
}
}
}

// 绑定事件
for (let i = 0, len = links.length; i < len; i++) {
link[i].addEventListener('click', listener, false)
}
</script>

实现 destructuringArray 方法

1
2
3
4
5
6
function destructuringArray(arr, str) {
const variables = str.replace(/[\[\]]/g, ''); //正则去除[]

return new Function(`str=${arr};return {${variables}}`)();
}
destructuringArray([1, [2, 4], 3], '[a,[b],c]'); // {a: 1, b: 2, c: 3}

请你设计并实现一个满足 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 {
cache: Map<number, number>;
constructor(public capacity: number) {
this.cache = new Map();
this.capacity = capacity;
}

get(key: number): number {
if (this.cache.has(key)) {
const value = this.cache.get(key);
this.cache.delete(key);
this.cache.set(key, value);
return value;
}
return -1;
}

put(key: number, value: number): void {
if (this.cache[key]) {
this.cache.delete(key);
} else {
if (this.cache.size >= this.capacity) {
this.cache.delete(this.cache.keys().next().value);
}
}
this.cache.set(key, value);
}
}

const lRUCache = new LRUCache(2);
lRUCache.put(1, 1); // 缓存是 {1=1}
lRUCache.put(2, 2); // 缓存是 {1=1, 2=2}
lRUCache.get(1); // 返回 1
lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}
lRUCache.get(2); // 返回 -1 (未找到)
lRUCache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}
lRUCache.get(1); // 返回 -1 (未找到)
lRUCache.get(3); // 返回 3
lRUCache.get(4); // 返回 4

比较版本号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function Compare(verson1, verson2) {
let v1Arr = verson1.split('.');
let v2Arr = verson2.split('.');
let i = 0;
let j = 0;
while (i < v1Arr.length || j < v2Arr.length) {
let str1 = v1Arr[i] || 0;
let str2 = v2Arr[j] || 0;
if (str1 - str2 > 0) {
return 1;
} else if (str1 - str2 < 0) {
return -1;
}
i++;
j++;
}
return 0;
}

// Compare("2.0.1","2") 1

找出字符串中重复次数最多的字符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function findMaxStr(str) {
let res = {};
for (let i = 0; i < str.length; i++) {
if (!res[str.charAt(i)]) {
res[str.charAt(i)] = 1;
} else {
res[str.charAt(i)] = res[str.charAt(i)] + 1; // 如果有,增加一次
}
}
let iMax = 0;
let target = '';
for (let key in res) {
if (res[key] > iMax) {
iMax = res[key]; // iMax要被重写
target = key;
}
}
console.log('res:', res);
console.log('出现次数最多的是:' + target + ', 出现' + iMax + '次');
}
findMaxStr('sabcdEs');

实现 url 的 parse 解析?

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
function parseUrl(url) {
const parser = document.createElement('a');
parser.href = url;
return {
protocol: parser.protocol,
host: parser.host,
hostname: parser.hostname,
port: parser.port,
pathname: parser.pathname,
search: parser.search,
hash: parser.hash,
params: (function(){
let ret = {},
let seg = a.search.replace(/^\?/,'').split('&'),

for (let i = 0;i<seg.length;i++) {
if (!seg[i]) { continue; }
let s = seg[i].split('=');
ret[s[0]] = s[1];
}
return ret;
})(),
};
}

// Example usage:
const url = 'https://www.example.com:8080/path/to/page?query=string#hash';
const parsedUrl = parseUrl(url);
console.log(parsedUrl);

实现对象数组 group

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
function group(arr, fn) {
const result = {};
for (let index = 0; index < arr.length; index++) {
const element = arr[index];
const category = fn(element, index, arr);
if (result[category]) {
result[category].push(element);
} else {
result[category] = [element];
}
}
return result;
}

const orderList = [
{
nickName: 'steven',
productName: '西瓜',
price: 29,
province: 'henan',
},
{
nickName: '对方的',
productName: '杨梅',
price: 22,
province: 'shanxi',
},
];

const result = group(orderList, ({ province }) => province);
console.log(result);

怎么可以使用 for-of 来遍历对象

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
// 手动实现Symbol.iterator迭代器函数
Object.prototype[Symbol.iterator] = function () {
const keys = Object.keys(this);
let index = 0;
return {
next: () => {
const value = this[keys[index]];
const done = index > keys.length - 1;
index++;
return {
value,
done,
};
},
};
};

const obj = {
key: '1',
value: '2',
};

for (const item of obj) {
console.log(item);
}

实现 lodash.get

1
2
3
4
5
6
7
8
9
10
11
12
function get(obj, path) {
let newPath = [];
if (Array.isArray(path)) {
newPath = path;
} else {
newPath = path.replace(/\[/g, '.').replace(/\]/, '').split('.');
}

return newPath.reduce((obj = {}, key) => obj[key], obj);
}
const object = { a: [{ b: { c: 3 } }] };
console.log(get(object, 'a[0].b.c'));

数组随机排序 shuffle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 简单有缺陷
// ECMAScript标准提到对于同一组a、b的值,compareFn(a, b)需要总是返回相同的值,sort采用原地算法
// 且vs引擎中丨阿宇数组部分的sort源码,考虑性能原因,对于短数组(小于等于22)使用插入排序,长数组(大于22)使用快速排序
function shuffle(arr) {
arr.sort(() => Math.random() - 0.5);
}

// es6洗牌算法
function shuffle(arr) {
let i = arr.length;
while (--i) {
let j = Math.floor(Math.random() * i);
[arr[j], arr[i]] = [arr[i], arr[j]];
}
}

二分查找 O(log2n)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// elements有序
function binarySearch(elements, value, _start, _end) {
let end = _end || elements.length - 1;
let start = _start || 0;
let povitIndex = Math.floor((start + end) / 2);

if (elements[povitIndex] === value) {
return povitIndex;
}

if (value < elements[povitIndex]) {
return binarySearch(elements, value, 0, povitIndex - 1);
} else {
return binarySearch(elements, value, povitIndex + 1, end);
}

return false;
}

冒泡排序 O(n^2)

重复地遍历要排序的数组,比较相邻的元素并交换位置,直到整个数组都已经排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function bubbleSort(elements) {
let elementLength = elements.length;

for (let i = 0; i < elementLength - 1; i++) {
for (let j = 0; j < elementLength - i - 1; j++) {
if (elements[j] > elements[j + 1]) {
let temp = elements[j];
elements[j] = elements[j + 1];
elements[j + 1] = temp;
}
}
}

console.log(elements);
}

let elements = [2, 4, 3, 7, 5];

bubbleSort(elements);

// console.log(elements)

快速排序 O(最好 nlogn,最慢 n^2,平均 nlog2n)

基本思想是选择一个基准元素,然后将数组中的元素分为小于基准元素和大于基准元素的两部分,再对这两部分分别进行排序

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
/**
* @param arr
* 时间复杂度:O(nlogn)
*/
function quickSort1(arr: number[]): number[] {
const length = arr.length;
if (length === 0) return arr;

const midIndex = Math.floor(length / 2);
const midValue = arr.splice(midIndex, 1)[0];

const left: number[] = [];
const right: number[] = [];

// 注意: splice会改变原数组,不能直接使用length
for (let i = 0; i < arr.length; i++) {
const n = arr[i];
if (n < midValue) {
left.push(n);
} else {
right.push(n);
}
}

return quickSort1(left).concat(midValue, quickSort1(right));
}
const arr = [2, 7, 5, 2, 3, 1];
console.log(quickSort1(arr));

插入排序(n^2)

将数组分为已排序和未排序两部分,然后将未排序部分的第一个元素插入到已排序部分的正确位置上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function insertSort(arr) {
let handle = [arr[0]];
for (let i = 1; i < arr.length; i++) {
let newItem = arr[i];
for (let j = handle.length - 1; j >= 0; j--) {
if (newItem > handle[j]) {
handle.splice(j + 1, 0, newItem);
break;
}
if (j === 0) {
handle.unshift(newItem);
}
}
}
return handle;
}

let arr = [2, 1, 5, 4, 8, 6];
console.log(insertSort(arr));

选择排序(n^2)

将数组分为已排序和未排序两部分,然后从未排序部分选择最小的元素并放到已排序部分的末尾

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function selectSort(arr) {
let index;
for (let i = 0; i < arr.length - 1; i++) {
index = i;
for (let j = i + 1; j < arr.length; j++) {
if (arr[index] > arr[j]) {
index = j;
}
}
if (index !== i) {
[arr[i], arr[index]] = [arr[index], arr[i]];
}
}
return arr;
}
let arr = [6, 4, 9, 8, 1, 3, 2];
console.log(selectSort(arr));

归并排序(nlogn)

将数组分成两个子数组,分别对这两个子数组进行排序,然后将排序后的子数组合并成一个有序的数组

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 merge(left, right) {
let temp = [];
while (left.length > 0 && right.length > 0) {
if (left[0] < right[0]) {
temp.push(left.shift());
} else {
temp.push(right.shift());
}
}
return temp.concat(left, right);
}

// 分治思想,归并排序
function mergeSort(arr) {
if (arr.length == 1) {
return arr;
} else {
let mid = parseInt(arr.length / 2);
let left = arr.slice(0, mid);
let right = arr.slice(mid);
return merge(mergeSort(left), mergeSort(right));
}
}

合并两个有序数组?

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
function mergeArr(arr1, arr2) {
let mergedArr = [];

let pointer1 = 0;
let pointer2 = 0;

while (pointer1 < arr1.length && pointer2 < arr2.length) {
if (arr1[pointer1] < arr2[pointer2]) {
mergedArr.push(arr1[pointer1]);
pointer1++;
} else {
mergedArr.push(arr2[pointer2]);
pointer2++;
}
}

while (pointer1 < arr1.length) {
mergedArr.push(arr1[pointer1]);
pointer1++;
}

while (pointer2 < arr2.length) {
mergedArr.push(arr2[pointer2]);
pointer2++;
}

return mergedArr;
}

手写useState、useEffect钩子函数?

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
let memoizedState = [];
let corsor = 0;

function useState(initialValue) {
memoizedState[cursor] = memoizedState[cursor] || initialValue;
const currentCursor = cursor;

function setState (newState) {
if (typeof newState === 'function') {
newState = newState(memoizedState[currentCursor]);
}
memoizedState[currentCursor] = newState;
render();
}
return [memoizedState[cursor++], setState];
}


function useEffect (callback, deps) {
const _deps = memoizedState[cursor];
const hasChange = _deps ? !deps.every((dep, i) => dep === _deps[i]) : true;
if (!deps || hasChange) {
callback();
memoizedState[cursor] = deps;
}
cursor++;
}

如何让 var [a, b] = {a: 1, b: 2} 解构赋值成功?

1
2
3
4
5
6
Object.prototype[Symbol.iterator] = function () {
return Object.values(this)[Symbol.iterator]()
}

var [a, b] = {a: 1, b: 2};
console.log(a, b);

如何使用 react hooks 实现 useFetch 请求数据?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import {useState, useEffect} from 'react';
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);

useEffect(() => {
const fetchData = async () => {
try {
const res = await fetch(url);
const data = await res.json();
setData(data);
setLoading(false);
} catch (e) {
setError(e);
setLoading(false);
}
}

fetchData();
},[url])

return [data, loading, error]
}

如何在 React 中使用 Render Prop 组件来请求数据?

组件封装:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import {useState, useEffect} from 'react';

const DataFetcher = ({url, children}) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);

useEffect(() => {
const fetchData = async () => {
try {
const res = await fetch(url);
const data = await res.json();
setData(data);
setLoading(false);
} catch (error) {
setError(error);
setLoading(false);
}
}
fetchData();
}, [url])

return children({data, loading, error})
}

使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import DataFetcher from './DataFetcher';

const MyComponent = () => {
return (
<DataFetcher url="/api/data">
{
({data, loading, error}) => {
if (loading) {
return <div>loading...</div>
}
if (error) {
return <div>error...{error.message}</div>
}
return (
<div>
success-{data}
</div>
)
}
}
</DataFetcher>
)
}

持续更新中

评论