coding

76 阅读7分钟
//防抖
function debounce(fn){
    let timeout = null
    return function(){
        clearTimeout(timeout)
        timeout = setTimeout(()=>{
            fn.apply(this,arguments)
        },500)
    }
}

//节流
function throttle(fn){
    let canRun = true
    return function(){
        if(!canRun) return false
        canRun = false
        setTimeout(()=>{
            fn.apply(this,arguments)
            canRun = true
        },500)
    }
}

//函数柯里化
function add(){
    let _args = Array.prototype.slice.call(arguments)
    var _adder = function(){
        _args.push(...arguments)
        return _adder
    }
    _adder.toString = function(){
        return _args.reduce((a,b)=>{
            return a+b
        })
    }
    return _adder
}
console.log(add(1)(2)(3));
console.log(add(1, 2)(3));

//js reduce: https://juejin.cn/post/6935737320335228936
/*1、定义和用法
reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。reduce() 可以作为一个高阶函数,用于函数的 compose。
注意: reduce() 对于空数组是不会执行回调函数的。
2、语法
array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
1、total (必需。初始值, 或者计算结束后的返回值)。
2、currentValue (必需。当前元素)
3、currentIndex (可选。当前元素的索引)
4、arr (可选。当前元素所属的数组对象。)
5、initialValue (可选。传递给函数的初始值)*/
//5、reduce简单用法:作为累加器函数,进行求和、求乘
var  arr = [1, 2, 3, 4];
var sum = arr.reduce((x,y)=>x+y)
var mul = arr.reduce((x,y)=>x*y)
console.log( sum ); //求和,10
console.log( mul ); //求乘积,24

//6、reduce高级用法:
//(1)计算数组中每个元素出现的次数
let  language = ['Chinese', 'English', 'Japanese', 'Chinese', 'English'];
let  languageNum = language.reduce((total,cur)=>{
    if(cur in total){
        total[cur]++
    }else{
        total[cur] = 1
    }
    return total
},{})
console.log(languageNum); //{Chinese: 2, English: 2, Japanese: 1}

//(2)数组去重
let arr2 = [1,2,3,4,4,1]
let newArr = arr2.reduce((total,cur)=>{
    if(!total.includes(cur)){
        return total.concat(cur)
    }else{
        return total
    }
},[])
console.log(newArr);// [1, 2, 3, 4]

//(3)求数组最大值(最小值同理)
let arr3 = [23,123,342,12];
let max = arr3.reduce((total,cur,index,arr) => {
    return total > cur ? total : cur
});
console.log(max) //342

//(4)求最大值
let arr4 = [68,168,666,12];
let max4 = arr4.reduce((total,cur,index,arr) => {
    return total > cur ? total : cur
});
console.log(max4) //666

//(5)将字符串转换为整数
let str = '1688';
let strParseInt = str.split('')                   // 得到 ['1', '6', '8', '8']
    .map(item => {return item.charCodeAt() - 48}) // 得到 [1, 6, 8, 8]
    .reduce((a, b) => {return a * 10 + b})        // 得到 1688
console.log(strParseInt)

//(6)二维数组转化为一维
let arr6= [[0, 1], [2, 3], [4, 5]]
let newArr6 = arr6.reduce((total,cur)=>{
    return total.concat(cur)
},[])
console.log(newArr6); // [0, 1, 2, 3, 4, 5]

//(7)多维数组转化为一维
/*let arr7 = [[0, 1], [2, 3], [4,[5,6,7]]]
const newArr7 = function(arr){
    return arr7.reduce((total,cur)=>total.concat(Array.isArray(cur)?newArr7(cur):cur),[])
}
console.log(newArr7(arr7)); //[0, 1, 2, 3, 4, 5, 6, 7]*/

//(8)对象里的属性求和
var result = [{    subject: 'male',    score: 45}, {    subject: 'female',    score: 50}];

var sum = result.reduce(function(total, cur) {
    return cur.score + total;
}, 0);
console.log(sum) //95

/*
//reduce在海外H5的应用
import resources from '@/utils/locales/resources.js'
import i18n from "i18next";
//获取不确定路径深度的值
const getObj = (obj,path,defaultVal) => {
    let pathCopy = path.split('.')
    return pathCopy.reduce((pre,cur)=>{
        return (pre||{})[cur]
    },obj) || defaultVal
}
let t = (type)=>{
    let obj = resources[i18n.language]['translation']
    return getObj(obj,type,'')
}
export default t*/

//实现原生reduce函数
Array.prototype.reduceFunc = function(fn,initValue){
    let acc = initValue || this[0]
    const startIndex = initValue?0:1
    for(let i=startIndex;i<this.length;i++){
        acc = fn(acc,this[i],i,this)
    }
    return acc
}
var arr = [1,2,3,4]
var result=arr.reduceFunc((a,b)=>{
    return a+b
},0)
console.log(result)
//实现bind函数
Function.prototype.bind = function(fn){
    const _args = [].slice.call(arguments,1)
    const context = this
    return function(){
        const args = _args.concat([].slice.call(arguments,1))
        return context.apply(fn,_args)
    }
}

//冒泡排序 https://segmentfault.com/a/1190000014175918
/*
数组中有 n 个数,比较每相邻两个数,如果前者大于后者,就把两个数交换位置;
这样一来,第一轮就可以选出一个最大的数放在最后面;
那么经过 n-1(数组的 length - 1) 轮,就完成了所有数的排序。
*/
/*
性能
时间复杂度: 平均时间复杂度O(n*n) 、最好情况O(n)、最差情况O(n*n)
空间复杂度: O(1)
稳定性:稳定

时间复杂度指的是一个算法执行所耗费的时间
空间复杂度指运行完一个程序所需内存的大小
稳定指,如果a=b,a在b的前面,排序后a仍然在b的前面
不稳定指,如果a=b,a在b的前面,排序后可能会交换位置*/
/*冒泡 升序*/
var arr = [3,4,1,2];
function bubbleSort(arr){
    var len = arr.length-1
    var flag = true
    for(var i = 0;i<len;i++){
        for(var j=0;j<len-i;j++){
            if(arr[j]>arr[j+1]){
                var temp = arr[j]
                arr[j]=arr[j+1]
                arr[j+1]=temp
                flag=false
            }
        }
        if(flag){
            break
        }
    }
    return arr

}
console.log(bubbleSort(arr))

//快速排序  https://segmentfault.com/a/1190000017814119
/*
快速排序的3个基本步骤:
从数组中选择一个元素作为基准点
排序数组,所有比基准值小的元素摆放在左边,而大于基准值的摆放在右边。每次分割结束以后基准值会插入到中间去。
最后利用递归,将摆放在左边的数组和右边的数组在进行一次上述的1和2操作。*/
/*在平均状况下,排序n个项目要O(nLogn)次比较。在最坏状况下则需要O(n^2)次比较,但这种状况并不常见*/

var arrQuick = [15,13,3,2,7]
function quickSort(arrQuick){
    if(arrQuick.length<=1){
        return arrQuick
    }
    var pivotIndex = Math.floor(arrQuick.length/2)
    var pivot = arrQuick.splice(pivotIndex,1)[0]
    var left = []
    var right = []
    for(var i=0;i<arrQuick.length;i++){
        if(arrQuick[i]<pivot){
            left.push(arrQuick[i])
        }else{
            right.push(arrQuick[i])
        }
    }
    return quickSort(left).concat([pivot],quickSort(right))

}
console.log(quickSort(arrQuick))

//promise实现控制并发
//使用递归控制并发
function request(urls,max,callback){
    let urlsCopy = [...urls] //浅拷贝复制参数,防止修改外部变量
    let index = 0 //当前并发数
    function req(){
        index++
        console.log('当前并发数-----',index)
        fetch(urlsCopy.shift()).then(res=>{ //把数组的第一个元素从其中删除,并返回第一个元素的值,且改变原数组
            index--
            if(urlsCopy.length>0){ //如果传入的urlsCopy还有长度,则递归
                req()
            }else if(index===0){ //如果传入的urlsCopy长度为0,且当前并发数也为0,说明所有任务执行完毕,则调用callback
                callback && callback()
            }
        })
        if(index<max){ //这里如果当前并发数小于设置的最大并发数max,则递归
            req()
        }
    }
    req()
}
 function fetch(url){
    let time = Math.random()*1000 +1000 //模拟异步任务耗时,1000ms~2000ms随机
     console.log("time-------",time)
     return new Promise((resolve,reject)=>{ //模拟网络请求
         try{
             setTimeout(()=>{resolve()},time)
         }catch(e){
             reject(e)
         }
     })
 }
let urlArray=[1,2,3,4,5,6,7,8,9] //模拟请求的url数组
function cb(){ //任务都执行完成后的回调
    console.log('finally')
    console.timeEnd()
    //console.log(11,urlArray)
}
console.time()//打印任务开始时间
request(urlArray,8,cb)


//JavaScript 发布-订阅模式:https://segmentfault.com/a/1190000019260857
/*
1. 定义
发布-订阅模式其实是一种对象间一对多的依赖关系,当一个对象的状态发送改变时,所有依赖于它的对象都将得到状态改变的通知。
订阅者(Subscriber)把自己想订阅的事件注册(Subscribe)到调度中心(Event Channel),
当发布者(Publisher)发布该事件(Publish Event)到调度中心,也就是该事件触发时,
由调度中心统一调度(Fire Event)订阅者注册到调度中心的处理代码。*/
/*实现思路:
1、创建一个对象
2、在该对象上创建一个缓存列表(调度中心)
3、on方法用来把函数fn都加到缓存列表中(订阅者注册事件到调度中心)
4、emit方法取到arguments里第一个当作event,根据event值去执行对应缓存列表中的函数(发布者发布事件到调度中心,调度中心处理代码)
5、off方法可以根据event值取消订阅(取消订阅)
6、once方法只监听一次,调用完毕后删除缓存函数(订阅一次)*/
/*1. 优点
对象之间解耦
异步编程中,可以更松耦合的代码编写
2. 缺点
创建订阅者本身要消耗一定的时间和内存
虽然可以弱化对象之间的联系,多个发布者和订阅者嵌套一起的时候,程序难以跟踪维护*/
let eventEmitter = {
    // 缓存列表
    list: {},
    // 订阅
    on (event, fn) {
        let _this = this;
        // 如果对象中没有对应的 event 值,也就是说明没有订阅过,就给 event 创建个缓存列表
        // 如有对象中有相应的 event 值,把 fn 添加到对应 event 的缓存列表里
        (_this.list[event] || (_this.list[event] = [])).push(fn);
        return _this;
    },
    // 监听一次
    once (event, fn) {
        // 先绑定,调用后删除
        let _this = this;
        function on () {
            _this.off(event, on);
            fn.apply(_this, arguments);
        }
        on.fn = fn;
        _this.on(event, on);
        return _this;
    },
    // 取消订阅
    off (event, fn) {
        let _this = this;
        let fns = _this.list[event];
        // 如果缓存列表中没有相应的 fn,返回false
        if (!fns) return false;
        if (!fn) {
            // 如果没有传 fn 的话,就会将 event 值对应缓存列表中的 fn 都清空
            fns && (fns.length = 0);
        } else {
            // 若有 fn,遍历缓存列表,看看传入的 fn 与哪个函数相同,如果相同就直接从缓存列表中删掉即可
            let cb;
            for (let i = 0, cbLen = fns.length; i < cbLen; i++) {
                cb = fns[i];
                if (cb === fn || cb.fn === fn) {
                    fns.splice(i, 1);
                    break
                }
            }
        }
        return _this;
    },
    // 发布
    emit () {
        let _this = this;
        // 第一个参数是对应的 event 值,直接用数组的 shift 方法取出
        let event = [].shift.call(arguments),
            fns = [..._this.list[event]];
        // 如果缓存列表里没有 fn 就返回 false
        if (!fns || fns.length === 0) {
            return false;
        }
        // 遍历 event 值对应的缓存列表,依次执行 fn
        fns.forEach(fn => {
            fn.apply(_this, arguments);
        });
        return _this;
    }
};

function user1 (content) {
    console.log('用户1订阅了:', content);
}

function user2 (content) {
    console.log('用户2订阅了:', content);
}

function user3 (content) {
    console.log('用户3订阅了:', content);
}

function user4 (content) {
    console.log('用户4订阅了:', content);
}

// 订阅
eventEmitter.on('article1', user1);
eventEmitter.on('article1', user2);
eventEmitter.on('article1', user3);

// 取消user2方法的订阅
eventEmitter.off('article1', user2);

eventEmitter.once('article2', user4)

// 发布
eventEmitter.emit('article1', 'Javascript 发布-订阅模式');
eventEmitter.emit('article1', 'Javascript 发布-订阅模式');
eventEmitter.emit('article2', 'Javascript 观察者模式');
eventEmitter.emit('article2', 'Javascript 观察者模式');

// eventEmitter.on('article1', user3).emit('article1', 'test111');
/*
    用户1订阅了: Javascript 发布-订阅模式
    用户3订阅了: Javascript 发布-订阅模式
    用户1订阅了: Javascript 发布-订阅模式
    用户3订阅了: Javascript 发布-订阅模式
    用户4订阅了: Javascript 观察者模式
*/
var source = [{    id: 1,    pid: 0,    name: 'body'}, {    id: 2,    pid: 1,    name: 'title'}, {    id: 3,    pid: 1,    name: 'div'}, {    id: 4,    pid: 3,    name: 'span'}, {    id: 5,    pid: 3,    name: 'icon'}, {    id: 6,    pid: 4,    name: 'subspan'}]

function toTree(data) {
    let result = []
    if(!Array.isArray(data)) {
        return result
    }
    data.forEach(item => {
        delete item.children;
    });
    let map = {};
    data.forEach(item => {
        map[item.id] = item;
    });
    data.forEach(item => {
        let parent = map[item.pid];
        if(parent) {
            (parent.children || (parent.children = [])).push(item);
        } else {
            result.push(item);
        }
    });
    return result;
}
console.log(toTree(source))


/*Promise规定:
1、promise存在三个状态(state)pending、fulfilled、rejected
2、pending(等待态)为初始态,并可以转化为fulfilled(成功态)和rejected(失败态)
3、成功时,不可转为其他状态,且必须有一个不可改变的值(value)
4、失败时,不可转为其他状态,且必须有一个不可改变的原因(reason)
5、new Promise((resolve,reject)=>{resolve(value)}) resolve为成功,接收参数value,状态改变为fulfilled,不可再次改变
6、new Promise((resolve,reject)=>{reject(reason)}) reject为失败,接收参数为reason,状态改为rejected,不可再次改变
7、若是executor函数报错直接执行reject()
*/