1、防抖和节流函数

防抖的主要思想是,当事件被触发后,延迟一定时间再执行相关操作。如果在这个延迟期内再次触发了同样的事件,就会重新计时。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const debounce = function(fn, delay) {
let timer = null;
return function() {
const that = this;
if(timer){
clearTimeout(timer);
timer = null;
}
timer = setTimeout(function(){
// 防止this为window
fn.apply(that, arguments);
}, delay)
}
}

window.addEventListener('resize', debounce(()=>{
console.log('防抖执行');
}, 300));

节流的主要思想是,在一定时间内只允许函数执行一次,无论事件触发了多少次。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const throttle = function(fn, delay){
let lastTime = 0;
return function() {
const now = Date.now();
if(now - lastTime > delay){
fn.apply(this, arguments);
lastTime = now;
}
}
}

window.addEventListener('scroll', throttle(()=>{
console.log('节流执行');
}, 300));

2、实现apply方法

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
Function.prototype.myApply = function (thisArg, argsArray){
// 确保只能在 function 上使用 myApply。 如果 this 不是一个函数,抛出 TypeError
if (typeof this !== 'function'){
throw new TypeError('myApply muse be called on a function');
}

// 处理 thisArg。 默认浏览器为 window, nodejs 为 global。 否则 thisArg 转换为对象
thisArg = (thisArg == null ? globalThis : Object(thisArg));

// 生成唯一属性值。 临时绑定函数, 后续需要删除。
const fnKey = Symbol('fn');
thisArg[fnKey] = this;

// 执行函数并传参
const result = argsArray ? thisArg[fnKey](...argsArray) : thisArg[fnKey]();

delete thisArg[fnKey];

return result; // 返回结果

}

// 测试
function greet(m1, m2) {
return `${m1}, ${this.name}, ${m2}`;
}

const person = {
name: 'frank'
}

greet.myApply(person, ['hello', 'come on']); // hello, frank, come on

3、实现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
26
27
28

Function.prototype.myCall = function (thisArg, ...arg){
if(typeof this !== 'function'){
throw new TypeError('myCall must be called on a function');
}

thisArg = (thisArg == null ? globalThis : Object(thisArg))

const fnKey = Symbol('fn');
thisArg[fnKey] = this;

const result = thisArg[fnKey](...arg);

delete thisArg[fnKey];

return result;
}

// 测试
function greet(m1, m2) {
return `${m1}, ${this.name}, ${m2}`;
}

const person = {
name: 'frank'
}

greet.myCall(person, 'hello', 'come on'); // hello, frank, come on

4、实现bind方法

bind() 方法会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 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
// 1、原生bind
const obj = {
name: 'zc',
age: 12
}
function say(sex){
console.log(`name: ${this.name}, age: ${this.age}, sex: ${sex}, 参数: ${[...arguments]}`);
}
const mySay = say.bind(obj, 'female');
mySay(111); // name: zc, age: 12, sex: female, 参数: female,111

// 2、实现myBind
Function.prototype.myBind = function(context) {
const that = this;
const args = Array.prototype.slice.call(arguments, 1); // 获取额外参数

return function() {
const newArgs = Array.prototype.slice.call(arguments); // 获取调用时出入的参数
return that.apply(context, [...args, ...newArgs]); // 执行原始函数并传递参数
}
}

const mySay1 = say.myBind(obj, 'female');
mySay1(222); // name: zc, age: 12, sex: female, 参数: female,222

5、统计字符串出现最多的字符

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
let str = 'afjghdfraaaasdenas';

function findMaxDuplicateChar(str) {
if (str.length === 1) return str;

let charObj = {};
for (let i=0; i<str.length; i++) {
if (!charObj[str.charAt(i)]){
charObj[str.charAt(i)] = 1;
} else {
charObj[str.charAt(i)] += 1;
}
}

let maxChar = '', maxValue = 1;
for (let key in charObj) {
if(charObj[key] >= maxChar) {
maxChar = key;
maxValue = charObj[key];
}
}
return {
maxChar,
maxValue
}
}

6、统计字符串中第一个唯一字符

1
2
3
4
5
6
7
8
9
10
11
12
let str = 'leetcode';  // 返回0
// let str = 'loveleetcode'; // 返回2

function findFirstUniqChar(str) {
for (let i=0; i<str.length(); i++) {
let curChar = str[i];
if (str.lastIndexOf(curChar) === str.indexOf(curChar)) {
return i;
}
}
return -1;
}

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
let arr = [1, 2, 1, 1, 4, 5, 6];

function findMostItemByArr(arr) {
let mostItem, indexs = [], obj = {};
for (let i=0; i<arr.length; i++) {
let item = arr[i].toString();
obj[item] ? obj[item].push(i) : obj[item] = [].concat(i);
}

let tempArr = Object.entries(obj);
mostItem = parseInt(tempArr[0][0]);
indexs = tempArr[0][1];

for (const [key, value] of tempArr) {
if (indexs.length < value.length) {
mostItem = parseInt(key);
indexs = value;
}
}

return {
mostItem,
indexs,
}
}

8、数组扁平化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function flatten(arr) {
const result = [];
for (let i = 0; i < arr.length; i++) {
if (Array.isArray(arr[i])) {
result = result.concat(flatten(arr[i]));
} else {
result = result.concat(arr[i]);
}
}
return result;
}

// or

if (!Array.prototype.flat) {
Array.prototype.flat = function() {
return [].concat(...this.map(item => Array.isArray(item) ? item.flat() : [item]));
}
}

9、讲数组扁平化并去除其中重复数据, 最终得到一个升序且不重复的数组

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

// es6 扁平化
Array.prototype.flat = function() {
return [].concat(...this.map(item => Array.isArray(item) ? item.flat() : [item]));
}

// 数组去重
Array.prototype.unique = function() {
return [...new Set(this)];
}

// 综合
arr.flat().unique().sort((a, b) => a -b);

10、旋转数组

给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数。

输入: [1,2,3,4,5,6,7] 和 k = 3 输出: [5,6,7,1,2,3,4]

解释:

向右旋转 1 步: [7,1,2,3,4,5,6]

向右旋转 2 步: [6,7,1,2,3,4,5]

向右旋转 3 步: [5,6,7,1,2,3,4]

1
2
3
4
5
6
7
8
9
let arr = [1, 2, 3, 4, 5, 6, 7];

function rotate(arr, k) {
for (let i=0; i<k; i++) {
let temp = arr.pop(); // 删除尾部
arr.unshift(); // 往头部新增元素
}
return arr;
}

11、版本比较

根据两个版本号,当前版本v1和比较版本v2,判断app是否为旧版本。

示例1:v1=”1.01”, v2=”1.1”, 则为旧版本

示例2:v1=”2.5”,v2=”2.4”, 则为新版本

示例3: v1=”5.4.3.2”, v2=”5.4.3”, 则为新版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* @param {string} version1
* @param {string} version2
* @return {number}
*/

var compareVersion = function(version1, version2) {
if(!version1 || !version2) return 0
const v1 = String(version1).split('.');
const v2 = String(version2).split('.');
const maxLen = Math.max(v1.length, v2.length);

for(let i = 0; i < maxLen; i++){
const num1 = Number(v1[i]) || 0;
const num2 = Number(v2[i]) || 0;
if(num1 > num2) return 1;
if(num1 < num2) return -1;
}

return 0;

};

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
let arr = [
{ id: 1, name: '1', pid: 0, },
{ id: 2, name: '1-1', pid: 1, },
{ id: 3, name: '1-1-1', pid: 2, },
{ id: 4, name: '1-2', pid: 1, }
];

function json2Tree(data, parentId = 0) {
return data
.filter(item => item.pid === parentId)
.map(item => {
const children = json2Tree(data, item.id);
return {
...item,
children: children.length ? children : undefined
};
});
}

console.log(JSON.stringify(json2Tree(arr), null, 2));

// 结果
[
{
"id": 1,
"name": "1",
"pid": 0,
"children": [
{
"id": 2,
"name": "1-1",
"pid": 1,
"children": [
{
"id": 3,
"name": "1-1-1",
"pid": 2
}
]
},
{
"id": 4,
"name": "1-2",
"pid": 1
}
]
}
]

12、找出字符串中所有字母的所有可能的排列,假定字母不重复

例如:”abc” 可能的排列为:”abc”, “acb”, “bac”, “bca”, “cab”, “cba”

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 allPermutationsBacktrack(str){
const res = [];
const chars = str.split('');
const used = new Array(chars.length).fill(false); // [false, false, false]

function backtrack(path){
if(path.length === chars.length){
res.push(path.join(''));
return;
}

for(let i = 0; i < chars.length; i++){
if(used[i]) continue;
used[i] = true;
path.push(chars[i]);

backtrack(path);

path.pop();
used[i] = false;
}
}
backtrack([]);
return res;
}
console.log(allPermutationsBacktrack('abc')); // ["abc", "acb", "bac", "bca", "cab", "cba"]

13、实现字符串模板

1
2
3
4
5
6
7
8
9
10
function tpl(template, data) { }

// 输入
tpl('<div class={{className}}>{{name}}</div>', {
className: 'class1',
name: 'test'
});

// 输出
<div class="class">test</div>
1
2
3
4
5
function tpl(str, data) {
return str.replace(/{{\s*(\w+)\s*}}/g, (match, key) => {
return key in data ? data[key] : '';
});
}

14、实现字符串大数相加

大数相加指的是两个超出编程语言内置数值精度范围的整数相加,通常用字符串(或数组)来存储并模拟计算的过程。

1
2
3
4
Number.MAX_SAFE_INTEGER // 9007199254740991 (2^53 - 1)

console.log(9007199254740991 + 1); // 9007199254740992 ✅
console.log(9007199254740991 + 2); // 9007199254740992 ❌ 精度丢失

实现思路

  • 反转字符串(或从末位开始遍历),方便低位到高位逐位相加。
  • 用 carry 保存进位。
  • 把对应位数字相加,计算新的一位和新的进位。
  • 最后如果 carry > 0,别忘了加到结果最高位。
  • 反转结果字符串得到最终值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function plus(num1, num2) {
let i = num1.length - 1;
let j = num2.length - 1;
let carry = 0;
let res = [];

while (i >= 0 || j >= 0 || carry) {
const n1 = i >= 0 ? parseInt(num1[i], 10) : 0;
const n2 = j >= 0 ? parseInt(num2[j], 10) : 0;

const sum = n1 + n2 + carry;
res.push(sum % 10); // 当前位
carry = Math.floor(sum / 10); // 进位

i--;
j--;
}

return res.reverse().join('');
}

// 测试
console.log(plus("123456789123456789", "987654321987654321")); // 输出: 1111111111111111110

15、实现安全取值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function getValSafe(obj, keys) {
}
const obj = { a: { b: { c: { d: 'safe' } } } }
console.log(get(obj, 'a.b.c.d')); //=> 'safe'

//方法一: ?. 链式写法

//方法二
function getValSafe(obj, keys) {
let result = obj;
keys.split('.').forEach(key => {
if (result && result[key]) {
result = result[key];
} else {
result = undefined;
}
});
return result;
}
// 方法三
function getValSafe(obj, keys) {
return keys.reduce((acc, key) => (acc && acc[key] !== undefined ? acc[key] : undefined), obj);
}