一些JS算法

101 阅读6分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第19天,点击查看活动详情

数组排序

参考往期文章排序与搜索算法

数组去重

方法一

ES6扩展运算符和 Set 结构相结合,就可以去除数组的重复成员

扩展运算符( spread )是三个点(...)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。

Set:Es6提供了新的数据结构set。它类似于数组,但是成员的值都是唯一的,没有重复的值。返回值为{}类型。

在 Set 内部,两个 NaN 是相等。 

另外,两个对象总是不相等的。

// 去除数组的重复成员 
[...new Set([1, 2, 2, 3, 4, 5, 5])]; 
// 结果为[1, 2, 3, 4, 5]

方法二

利用Array.from()把set结构转换为数组

Array.from()方法从一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例.

Array.from(new Set([1, 2, 2, 3, 4, 5, 5]));

方法三

forEach() 方法用于调用数组的每个元素,并将元素传递给回调函数。

注意: forEach() 对于空数组是不会执行回调函数的。

:indexOf() 方法对大小写敏感!

注释:如果要检索的字符串值没有出现,则该方法返回 -1。

var arr = [1,2,3,4,5,4,5] 
var temp = []; 
arr.forEach(e => {     
    if (temp.indexOf(e) == -1) {     
        temp.push(e);     
    } 
}); 
console.log(temp)

反转数组

有以下要求: 输入: I am a student(数组) 输出: student a am I

输入是数组, 输出也是数组;

不允许用 split splice reverse

方法一

var str = "I am a student ." 
var newStr = "" 
var newTemp = [] 
var word = "" var 
temp = Array.from(str) 
for (var i = 0; i < temp.length; i++) {     
    word += temp[i]     
    // if(asc.indexOf(temp[i]) === -1) {     
    if(temp[i] === " ") {         
        newTemp.unshift(word)         
        word = ""     
    } 
} 
// 想数组中加入最后一个词,因为最后一个词后面没有空格 newTemp.unshift(word) 
// 词之间用空格分隔开 
newStr = newTemp.join(" ") 
console.log(newStr)

对方法一进行一点改进

function reverseArray(arr) { 
    let str = arr.join(' ') 
    let result = [] let word = '' 
    for (let i = 0; i < str.length; i++) { 
        if (str[i] != ' ') { 
            word += str[i] 
        } else { 
            result.unshift(word) word = '' 
        } 
    } 
    result.unshift(word) 
    return result 
} 
console.log(reverseArray(['I', 'am', 'a', 'student'])) 
// ["student", "a", "am", "I"]

方法二

function reverseArray(arr) { 
    let result = [] let 
    distance = arr.length - 1 
    for (let i = 0; i <= distance; i++) { 
        result[i] = arr[distance - i] 
    } 
    return result 
}

节流

函数节流是减少连续的高频操作函数执行次数 (例如连续调用10次, 可能只执行3-4次)

函数节流是优化高频率执行js代码的一种手段

可以减少高频调用函数的执行次数

作用:减少代码执行次数, 提升网页性能

应用场景:oninput / onmousemove / onscroll / onresize 等事件

封装一个节流函数

function throttle(fn,delay) {             
    let timer = null             
    return function (...args) {                 
        if(timer) return                 
        timer = setTimeout() => {                     
            fn.apply(this,args)                     
            timer = null                 
        },delay)             
    }         
}

防抖

函数防抖是让连续的高频操作时函数只执行一次(例如连续调用10次, 但是只会执行1次)

函数防抖是也优化高频率执行js代码的一种手段

可以让被调用的函数在一次连续的高频操作中只被调用一次

作用:减少代码执行次数, 提升网页性能

应用场景:oninput / onmousemove / onscroll / onresize 等事件

封装一个防抖函数:

注意:方便防抖函数的调用和回调函数fn的传参问题,这里使用闭包来设计防抖函数

function debounce(fn,delay) {     
    let timer = null     
    return function (...args) {         
        timer&&clearTimeout(timer)         
        timer = setTimeout() => {             
            fn.apply(this,args)         
        },delay)     
    } 
}

深拷贝

class Person{ 
    name = "zs"; 
    cat = { 
        age : 3 
    }; 
    score = [1, 3, 5]; 
} 
let p1 = new Person(); 
let p2 = new Object(); 
// 通过自定义函数实现深拷贝 
function deCopy(target, source) { 
    // 1.通过遍历拿到source中所有的属性 
    for (let key in source){ 
        // 2.取出当前遍历到的属性对应的取值 
        let sourceValue = source[key]; 
        // 3.判断当前的取值是否是引用数据类型 
        if (sourceValue instanceof Object){ 
            // 如果是引用数据类型, 那么要新建一个存储空间保存 
            // 4.通过sourceValue.constructor拿到这个对象的构造函数的类型, 然后新建这个对象或数组 
            let subTarget = new sourceValue.constructor; target[key] = subTarget; 
            // 5.再次调用拷贝, 将遍历到的属性的取值拷贝给新建的对象或者数组
            deCopy(subTarget, sourceValue); 
        }else { 
            // 如果不是引用数据类型, 之间将属性拷贝即可 
            target[key] = sourceValue; 
        } 
    } 
} 

可以利用递归将上述深拷贝进行简化:

// 简化版本 
function deCopy(obj){ 
    // 如果不是引用数据类型, 直接将属性拷贝即可 
    if(typeof obj != 'object') return obj 
    // 如果是引用数据类型, 那么要新建一个存储空间保存 
    let newObj = new obj.constructor 
    for(let key in obj){ 
        // 递归调用拷贝, 将遍历到的属性的取值拷贝给新建的对象或者数组 
        newObj[key] = deCopy(obj[key]) 
    } 
    return newObj 
} 
deCopy(p2, p1); 
p2.cat.age = 666; // 修改新变量的值不会影响到原有变量, 这里是深拷贝
console.log(p1.cat.age); 
// 3 console.log(p2.cat.age); // 666

数组扁平化

假如有一个数组 var arr = [1, [2, 3, [4]]] ,把arr变成[1, 2, 3, 4]?即让多维数组降维,转换为只有一层的数组;即为数组扁平化。

方法一

循环数组+递归 

概念:可以理解为将嵌套数组的维数减少。

function flatten(arr){ 
    var result = []; 
    for(var i = 0, len = arr.length; i < len; i++){ 
        if(Array.isArray(arr[i])){ 
            result = result.concat(flatten(arr[i])); 
        }else{ 
            result.push(arr[i]); 
        } 
    } 
    return result; 
} 
flatten(arr) 
// [1,2,3,4]

方法二

使用apply实现数组扁平化

实现思路:利用数组方法some判断当数组中还有数组的话,递归调用flatten扁平函数(利用apply实现扁平), 用concat连接,最终返回arr;

some:对数组中的每个元素都执行一次指定的函数(callback),直到此函数返回 true,如果发现这个元素,some 将返回 true,如果回调函数对每个元素执行后都返回 false ,some 将返回 false。

concat: 如果concat方法的参数是一个元素,该元素会被直接插入到新数组中;如果参数是一个数组,该数组的各个元素将被插入到新数组中。

apply:apply方法会调用一个函数,apply方法的第一个参数会作为被调用函数的this值,apply方法的第二个参数(一个数组,或类数组的对象)会作为被调用对象的arguments值,也就是说该数组的各个元素将会依次成为被调用函数的各个参数;作为apply方法的第二个参数,本身是一个数组,数组中的每一个元素(还是数组,即二维数组的第二维)会被作为参数依次传入到concat中。利用apply方法,我们将单重循环优化为了一行代码

function flatten(arr){
    while(arr.some(item => Array.isArray(item))){ 
        arr = [].concat.apply([],arr); 
    } return arr; 
} 
flatten(arr) 
// [1,2,3,4]

方法三

使用reduce数组方法

实现思路:使用reduce, 当数组中还有数组的话,递归调用flatten扁平函数(利用reduce扁平), 用concat连接,最终返回arr.reduce的返回值;

arr.reduce(callback,[initialValue]),reduce 为数组中的每一个元素依次执行回调函数,不包括数组中被删除或从未被赋值的元素,接受四个参数:初始值(或者上一次回调函数的返回值),当前元素值,当前索引,调用 reduce 的数组。initialValue作为第一次调用callback的第一个参数

function flatten(arr){ 
    return arr.reduce(function(prev, cur){ 
        return prev.concat(Array.isArray(cur) ? flatten(cur) : cur) 
    },[]) 
} 
flatten(arr) 
// [1,2,3,4]

方法四

使用ES6展开运算符

实现思路:利用数组方法some判断当数组中还有数组的话,递归调用flatten扁平函数(利用ES6展开运算符扁平), 用concat连接,最终返回arr;

function flatten(arr){ 
    while(arr.some(item => Array.isArray(item))){ 
        arr = [].concat(...arr); 
    } 
    return arr; 
} 
flatten(arr) 
// [1,2,3,4]

方法五

使用toString方法,仅适用于数组元素为数字的情况。

function flatten(arr){ 
    return arr.toString().split(',').map(function(item){ 
        return parseInt(item); 
    }) 
} 
flatten(arr) 
// [1,2,3,4]

函数柯里化(curry)

柯里化:把接收多个参数的函数变换成接收一个单一参数的函数(单一参数为多个参数中的第一个), 只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。如:

var add = function(x) {   
    return function(y) {     
        return x + y;   
    }; 
}; 
var increment = add(1); 
var addTen = add(10); 
increment(2); 
// 3 

addTen(2); 
// 12 

add(1)(2); 
// 3

函数柯里化思想:一个JS预处理的思想,降低通用性,提高适用性。

最后

本文也是本人的学习笔记,有教程代码,也有本人自己写的代码,可供参考。

黑眼圈.webp