前端分享--常用的前端算法【干货】

240 阅读9分钟

1. 找到两个数组的交集

let a = new Set([1,2,3,4,5,6]);

let b = new Set([4,5,6,7,8,9]);

let jiaoji = new Set([...a].filter(x => b.has(x)));

let arr = [...jiaoji]

2.存在父子关系的list 转换成树结构

let arr = [
    {id:1,name:"king1",parentId:null},
    {id:2,name:"king2",parentId:1},
    {id:3,name:"king3",parentId:2},
    {id:4,name:"king4",parentId:2},
    {id:5,name:"king5",parentId:2},
    {id:6,name:"king6",parentId:null},
    {id:7,name:"king7",parentId:null},
    {id:8,name:"king8",parentId:6}
]

使用递归实现

function getTrees(list, parentId = 0) {
    //此循环只进行一次,在第一次进行,目的是对没有父节点的 根节点进行过滤,和对它们进行子节点的填充
    if (!parentId) {
        let parentObj = {};
        //循环当前list 把每条数据跟自己id相关联
        list.forEach(element => {
            parentObj[element.id] = element;
        });
        //返回
        let filterList = list.filter(o => !parentObj[o.parentId]);
        return filterList.map(o => {
            let tree = getTrees(list, o.id);
            return tree == false ? o : (o.children = tree, o)
        });
    } else {
        //返回根节点下面的子节点
        let filterList = list.filter(o => o.parentId == parentId);
        return filterList.map(o => {
            let tree = getTrees(list, o.id);
            return tree == false ? o : (o.children = tree, o)
        });
    }
}

如果想让箭头函数向外返回一个对象字面量,则需要将该字面量包裹在小括号里。就像这样:

let getTempItem = id => ({ id: id, name: 'Temp' }); 
// 实际上相当于: 
let getTempItem = function(id) { 
    return { id: id, name: 'Temp' } 
};

let reflect = value => value; 
// 实际上相当于: 
let reflect = function(value) { 
    return value; 
};

arr.map(o=> o*2)
// 实际上相当于:
arr.map(function(item){
    return item*2
})

//ps:给所有数据项都添加属性的方法
arr.map(o=>(o.children=1,o)) //返回的所有对象都添加了children:1

当箭头函数只有一个参数时,可以直接写参数名,箭头紧随其后,箭头右侧的表达式被求值后便立即返回(没括号包裹时),不需要更多的语法铺垫。

3.1+100 递归

let addFunc = (a=1)=>a>100?0:a+addFunc(++a)
//相当于
let addFunc = (a=1)=>{return a>100?0:a+addFunc(++a)}

4.数组undefined和undefined的不同

JSON.stringify([undefined])   // "[null]"

JSON.stringify(undefined)  //undefined

JSON.stringify([null])   // "[null]"

JSON.stringify(null)    //"null"

JSON.stringify(NaN)    //"null"

5.解构赋值对象和数值

let a = 1,b = 2,head = {next: { next: 1 }};

[a, b] = [b, a];

[head.next, head.next.next] = [head.next.next, head.next];

console.log(a, b, head);

解析: head.next = 1的时候 head.next.next已经不存在了 所以head值为{next: 1}

结果: 2 1 {next: 1}

6.函数参数传递的区别

const value = { number: 10 };

let number = 2;

const multiply = (x = { ...value }, y = number) => {
    y++;
    console.log((x.number *= y));
};

multiply();

multiply();

multiply(value, number);

multiply(value, number);

解析:首先函数的参数都是按值传递
... 扩展运算符在对象只有一层的时候相当于深拷贝 所以结果不受影响
当传递参数并且参数是对象的时候,此时的值传递就是地址的值的传递,就会使x.number变化

结果:30 30 30 90

7.运算符一般是从左往右

console.log(1 < 2 < 3); //true

console.log(3 > 2 > 1); //false

解析:true 和 1进行比较的时候会进行隐式转换

8.运算符优先级

let a = 3;
let num = 1 * (2 + a) && ++a || 5 > 6 && 7 < 8 || !9 ;
console.log(num);

解析:- 优先级从高到低

-   1、() 优先级最高
-   2、一元运算符 ++ -- !
-   3、算数运算符 先* / % 后 + -
-   4、关系运算符 > >= < <=
-   5、相等运算符 == != === !==
-   6、逻辑运算符 先 && 后 ||
-   7、赋值运算符
(2 + a) = 5 
++a = 4 
!9 = false 
1 * 5 = 5 
5 > 6 = false 
7 < 8 = true 
5 && 4 = 4 
false && true = false 
4 || false = 4 
4 || false = 4 

结果: 4

9.说一说为什么[] == ![]

①、根据运算符优先级 ,! 的优先级是大于 == 的,所以先会执行 ![]
!可将变量转换成boolean类型,null、undefined、NaN以及空字符串('')取反都为true,其余都为false。
所以 ! [] 运算后的结果就是 false
也就是 [] == ! [] 相当于 [] == false

②、根据上面提到的规则(如果有一个操作数是布尔值,则在比较相等性之前先将其转换为数值——false转换为0,而true转换为1),则需要把 false 转成 0
也就是 [] == ! [] 相当于 [] == false 相当于 [] == 0

③、根据上面提到的规则(如果一个操作数是对象,另一个操作数不是,则调用对象的valueOf()方法,用得到的基本类型值按照前面的规则进行比较,如果对象没有valueOf()方法,则调用 toString())
而对于空数组,[].toString() -> '' (返回的是空字符串)

也就是 [] == 0 相当于 '' == 0
④、根据上面提到的规则(如果一个操作数是字符串,另一个操作数是数值,在比较相等性之前先将字符串转换为数值)

Number('') -> 返回的是 0

相当于 0 == 0 自然就返回 true了

总结一下:

[] == ! [] -> [] == false -> [] == 0 -> '' == 0 -> 0 == 0 -> true

那么对于 {} == !{} 也是同理的

关键在于 {}.toString() -> NaN(返回的是NaN)

根据上面的规则(如果有一个操作数是NaN,则相等操作符返回 false)

总结一下:

{} == ! {} -> {} == false -> {} == 0 -> NaN == 0 -> false

10.null 和 0 的比较结果

console.log(null == 0);  //false

console.log(null <= 0);  //true

console.log(null < 0);   //false

Number(null) == 0 // true

解析:javascript中 null不等于零,也不是零
null只等于undefuned ,剩下它俩和谁都不等
但是在关系运算符 在设计上会从尝试转为一个number考虑,所以 null < 0 或 null <= 0 时 会触发 Number(null) < 0 和 Number(null) <= 0

11.reduce的使用

[1, 2, 3, 4].reduce((x, y) => console.log(x, y));

解析:reducer 函数接收4个参数:

  1. Accumulator (acc) (累计器)
  2. Current Value (cur) (当前值)
  3. Current Index (idx) (当前索引)
  4. Source Array (src) (源数组) reduce 函数的返回值将会分配给累计器,该返回值在数组的每个迭代中被记住,并最后成为最终的单个结果值。

reduce 函数还有一个可选参数 initialValue, 该参数将作为第一次调用回调函数时的第一个参数的值。如果没有提供 initialValue,则将使用数组中的第一个元素。

在上述例子, reduce方法接收的第一个参数(Accumulator)是 x, 第二个参数(Current Value)是 y

在第一次调用时,累加器 x1,当前值 “y”2,打印出累加器和当前值:12

例子中我们的回调函数没有返回任何值,只是打印累加器的值和当前值。如果函数没有返回值,则默认返回 undefined。在下一次调用时,累加器为 undefined,当前值为“3”, 因此 undefined3被打印出。

在第四次调用时,回调函数依然没有返回值。累加器再次为 undefined ,当前值为“4”。undefined4被打印出。
结果: 1 2
undefined 3
undefined 4

12.三目运算的假象

const value = 'Value is' + !!Number(['0']) ? 'yideng' : 'undefined';

console.log(value);

解析:+优先级大于?

所以 原题等价于 ‘Value is false’?'yideng':'undefined'

13.模块化的问题

//counter.js

let counter = 10;

const add = () => {

console.log(counter);

};

export { counter, add };


//index.js

add();

import { counter, add } from "./counter";

counter += 1;

console.log(counter);

解析:import命令

1:import命令输入的变量都是只读的(除了对象)

//counter.js

let counter = {};

export default counter;

//index.js

import myCounter from './counter.js';

myCounter.hello = "helloCounter";

上面的代码中 myCounter的属性可以成功改写,并且其他模块也可以读到改写后得值。不过这种写法很难查错,建议凡是import的变量都当做完全只读,不要轻易改变它的属性。

2:import命令具有提升效果

3: import是静态执行,所以不能使用表达式和变量

//报错

let module = "my_module"

import {foo} from module;

4: import语句是Singleton模式

import {foo} from 'my_module'

import {bar} from 'my_module'

//等同于

import {foo,bar} from 'my_module'

虽然在两个语句加载,但是它们对应的是同一个my_module
结果:10 报错

14.符号优先级的问题

var a = { k1: 1 };

var b = a;

a.k3 = a = { k2: 2 };

console.log(a); // ?

console.log(b); // ?

//解析

点的优先级大于等号的优先级

对象以指针的形式进行存储,每个新对象都是一份新的存储地址

先执行a.k3 所以 a和b都等于 {k1:1,k3:undefined}

等号是从右向左执行,先执行 a={k2:2},a是一个新对象(b还存的a的旧地址)

a.k3是最开始执行的 {k1:1,k3:undefined}.k3 = { k2: 2 } ,b和旧的a指的是同一个地址

所以 a={k2:2} , b = {k1:1,k3:{ k2: 2 }}

15.判断字符串值的次数

    let str = 'abbbcccaadbbcs';
    [...str].reduce((acc,cur)=>{
        acc[cur] = acc[cur] ? ++acc[cur] : 1
        return acc
    },{})

16.Set对象的去重(对象或数组)

方法一:

    let arr = [[1,2,3],[-1,3,4],[2,4,5],[1,2,3]];
    let strings = arr.map(item=>JSON.stringify(item))
    let uniqueStr = [...new Set(strings)]
    let uniqueArr = uniqueStr.map(item=>JSON.parse(item))
    console.log(uniqueArr)

方法二:

letoldArr = [
    {name:1,age:1},
    {name:2,age:2},
    {name:2,age:2},
    {name:3,age:3},
    {name:3,age:4},
    {name:4,age:5},
    {name:4,age:4},
    {name:4,age:0},
    {name:5,age:6},
    {name:5,age:6},
    {name:1,age:0},
    {name:1,age:0},
    {name:2,age:0},
    {name:3,age:0},
    {name:1,age:0},
];

let json = {};
oldArr.reduce((acc,cur)=>{
    json[cur.name]?'':json[cur.name]=true && acc.push(cur)
    return acc 
},[])

17.滑动窗口问题

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。

// 注意:答案中不可以包含重复的三元组。
// 示例: 输入:nums = [-1,0,1,2,-1,-4]      输出:[[-1,-1,2],[-1,0,1]]
let threeSum = function (nums) {
    const n = nums.length
    if (nums == null || n < 3) return []
    const res = []
    // 先进行排序
    nums.sort((a, b) => {
        return a - b
    })
    // console.log(nums)
    for (let i = 0; i < n - 2; i++) {
        // 跳过重复元素
        if (i > 0 && nums[i] === nums[i - 1]) {
            continue
        }
        // 设置双指针分别为 (i, nums.length),在区间进行查找
        let j = i + 1,
            k = n - 1
        while (j < k) {
            let s = nums[i] + nums[j] + nums[k]
            if (s === 0) {
                res.push([nums[i], nums[j], nums[k]])
                // 去重
                while (j < k && nums[j] === nums[j + 1]) j++
                while (j < k && nums[k] === nums[k - 1]) k--
                j++
                k--
            } else if (s > 0) {
                k--
            } else if (s < 0) {
                j++
            }
        }
    }
    return res
}
// test
console.log(threeSum([-1, 0, 1, 2, -1, -4]))

18.数组最大值的问题

let arr = [1,2,4,5,3] 
let max = Math.max(...arr) // 最大值为5 
let min = Math.min(...arr) // 最小值为1

19.找规律的问题

数组的n-1项进行+1,需要几次可以使数组各项值相等

//n-1项进行+1

// let arrs = [1,2,3]  [2,3,3] [3,4,3] [4,4,4] 
// let arrs = [1,2,5]  [2,3,5] [3,4,5] [4,5,5] [5,6,5] [6,6,6]

解释:找规律

3-1+2-1 = 3

5-1+2-1=5

20.最长不重复子字符串的问题

//动态规划  自下而上
let lengthOfLongestSubstring = function (s) {
    const arr = [...s]
    let res = 1;
    let result = arr.reduce((total, cur, i, arr) => {
        if (i == 0) {
            return cur;
        } else {
            if (!total.includes(cur)) {
                return total + cur
            } else if (res < total.length) {
                res = total.length
                return total.slice(total.indexOf(cur) + 1, total.length) + cur
            } else {
                return total.slice(total.indexOf(cur) + 1, total.length) + cur
            }
        }
    }, "")
    if (res < result.length) {
        res = result.length
    }

    return res
};

console.log(lengthOfLongestSubstring("loddktdji"))
console.log(lengthOfLongestSubstring("dvdf"))
console.log(lengthOfLongestSubstring("adfafwefffdasdcx"))

21.下面代码输出什么?

async function as1() {
    console.log("as1 start");
    await as2();
    console.log("as1 end");
  }
  
  async function as2() {
    console.log("as2");
  }
  
  console.log("script start");
  
  setTimeout(function () {
    console.log("setTimeout");
  }, 0);
  
  as1();
  
  new Promise(function (resolve) {
    console.log("prom1");
    resolve();
  }).then(function () {
    console.log("prom2");
  });
  console.log("script end");
  
  //script start => as1 start => as2 => prom1 => script end
  //=> as1 end => prom2 => setTimeout