手写JS

221 阅读3分钟

一、写一个通用的节流函数(冷却)

const throttle = (fn, delay) => {
    let timer = null;
    
    return function () {
        let context = this;
        let args = arguments;
        if(timer) return;
        fn.apply(context, args); // 立刻执行的节流
        timer = setTimeout(()=>{
            fn.apply(context, args); // 延迟执行的节流
            timer = null;
        }, delay);
    }

}

// 进阶:用Date对象来计算时间间隔
const throttle = (fn, delay) => {
    let pre = 0;
    return function () {
        let context = this;
        let args = arguments;
        let now = new Date();
        if(now - pre > delay) {
            fn.apply(context, args); // 立刻执行的节流
            pre = now;
        }
    }

}

const thr = throttle(fn, 300);

应用场景:重复点击(链接、按钮、抢购),监听页面滚动

二、写一个通用的防抖函数(打断)

const debounce = (fn, time) => {
    let timer = null;
    
    return function () {
        if(timer) clearTimeout(timer);
        let context = this;
        let args = arguments;
        
        timer = setTimeout(()=>{
            // 不需this
            fn.apply(undefined, args); // 这个的this最终会变成window
            // 需要this
            fn.apply(context, args);
           
        }, time);
    }

}

const deb = debounce(fn, 3000);

应用场景:页面尺寸变化(拖动窗口,拖动停止之后再实现某个效果) 解决问题:(节流和防抖)减少不必要的计算和响应,提高性能和避免阻塞。

三、写一个发布订阅模式

const eventHub = {
    map: {
        // click: [f1, f2],
    },
    on: (eventName, fn) => {
        eventHub.map[eventName] = eventHub.map[eventName] || [];
        eventHub.map[eventName].push(fn);
    },
    emit: (eventName, args) => {
        const q = eventHub.map[eventName];
        if(!q) return;
        q.map(f => f.call(undefined, args));
    },
    once: (fn, args, _this) => {
        let result, executed;
        return function() {
            if(executed) {
                return result;
            }
            
            if(!_this) _this = {}   
            
            executed = true;
            result = fn.apply(_this, args);
            
            fn = null;
            
            return result;   
        }
    }
    off: (eventName, fn) => {
        const q = eventHub.map[eventName];
        if(!q) return; // 先确定有无这类event
        const index = q.indexOf(fn);
        if(index<0) return; // 再确定这类event有无此函数
        q.splice(index,1); // 然后再取消订阅
    }
}

// 开始订阅
eventHub.on('click', console.log);
eventHub.on('click', console.error);

//发布订阅
setTimeout(()=>{
    eventHub.emit('click', 'Harry');
}, 3000)


class EventHub {
    map = {}
    on(event, fn){
        this.map[event] = this.map[event] || [];
        this.map[event].push(fn);
    }
    emit(event, data){
        const fnList = this.map[event] || [];
        const index = fnList.indexOf(fn);
        if(index < 0) return;
        fnList.splice(index, 1); 
    }
    off(event, fn){
        const fnList = this.map[event] || [];
        const index = fnList.indexOf(fn);
        if(index < 0) return;
        fnList.splice(index, 1);
    }
}

// 使用
const eventHub = new EventHub();
eventHub.on('click', (name)=>{ console.log('hi'+name);});
eventHub.on('click', (name)=>{ console.log('bye'+name);});

setTimeout(()=>{
    eventHub.emit('click', 'Harry');
}, 3000)

四、写一个AJAX

const ajax = (method, url, data, success, fail) => {
    const xhr = new XMLHttpRequest();
    xhr.open(method,url);
    xhr.send(data);
 
    xhr.onreadystatechange = function() {
        // 注:
        /*
            * xhr.readyStatus === 0 尚未调用 open 方法
            * xhr.readyStatus === 1 已调用open但还未发送请求
            * xhr.readyStatus === 2 已发送请求(已调用 send)
            * xhr.readyStatus === 3 已经收到请求返回的数据
            * xhr.readyStatus === 4 请求已完成
        */
        if(xhr.readyStatus === 4){
            if(xhr.status >= 200 && xhr.status < 300 || xhr.status === 304)
                success(xhr.responseText)
            else 
                fail(xhr.responseText)
        }
    }
    
    //超时时间单位为毫秒 
    xhr.timeout = 1000 
    //当请求超时时,会触发ontimeout方法 
    xhr.ontimeout = () => console.log('请求超时')

}

五、写一个Promise

六、写一个Promise.all

1. 基本用法

let promiseList = [Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)];

Promise.all(promiseList).then(result=>{console.log(result)}, reason=>{console.log(reason)}) 
// [1,2,3]

2. 手写实现

Promise.all2 = (promiseList) => {
    return new Promise((resolve, reject)=>{
         // 定义一个数组保存结果
        const result = [];
        const length = promiseList.length;
        // 执行每一个promise并保存结果
        promiseList.forEach( (promise,index) => {
            promise.then(value => {
                result[index] = value;
                // 结果数组的长度等于原本promise数组的长度说明没有rejected
                // 直接返回结果数组
                if(result.length === length){
                    resolve(result)
                }
            }, reason => {
            // 有一个rejected直接reject
                reject(reason);
            })

        })
    })
   
}

// 测试代码
let promiseList = [Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)];

Promise.all(promiseList).then(result=>{console.log(result)}, reason=>{console.error(reason)}) 

七、数组去重

1. 使用Map

var arr = [1, '1', 2, 2, '3', 3, 4, 5, 5, 6];

Array.uniq = (arr) => {
    const map = new Map();
    for (let ele of arr) {
     if(!ele) continue;
        if(map.has(ele)) continue;
        map.set(ele, true);
    }

    return [...map.keys()];
}

const res = Array.uniq(arr);
console.log(res);

2. 使用对象属性不能重复

Array.prototype.distinct = function (){ 
    let arr = this; 
    let obj = {}; 
    let result = []; 
  
    for(let i = 0; i< arr.length; i++){ 
        //如果能查找到,证明数组元素重复了; 
        // 找不到(即undefined),就加进数组内
        if(!obj[arr[i]]){ 
           obj[arr[i]] = true;
           result.push(arr[i]); 
        } 
    } 
    return result; 
}; 

var a = [1,2,3,4,5,6,5,3,2,4,56,4,1,2,1]; 
var b = a.distinct(); 
console.log(b); //[1,2,3,4,5,6,56]

3. ES5的filter

function distinctArray(arr) { 
    let res = arr.filter((val, ind, arr) => { 
        let bool = arr.indexOf(val) === ind; 
        // console.log(bool); 
        return bool; 
     }); 
     return res; 
}

4. ES6的set

function dedup(array){ 
    return Array.from(new Set(array)); 
}  

let arr = [1,2,3,3]; 
let resultarr = [...new Set(arr)]; 

八、写一个深拷贝

使用递归

function deepClone(x,cache){
    // 缓存不能全局创建,最好第一次创建并递归传递
    if(!cache) {
        cache = new Map();
    }
    // 使用map来避免拷贝环形引用(如:x.self = x)的递归栈溢出 
    if(cache.has(x)){
        return cache.get(x);
    }
    // 声明result变量
    let result
    // 先判断x是普通类型还是引用类型
    if(x instanceof Object){
        // 如果是引用类型还要判断是哪一种引用类型,再来初始化result
        if(x instanceof Function){
            // 判断是普通函数还是箭头函数
            if(x.prototype) { // 普通函数才有prototype
                result = function() {return x.apply(this, arguments)};
            } else {
                result = (...args) => {return x.apply(undefined, args)}; // 注:context是undefined
            }
        } else if (Array.isArray(x)){
            result = [];
        } else if (x instanceof Date) {
            result = new Date(x-0); // 使用了时间戳
        } else if (x instanceof RegExp) {
            result = new RegExp(x.source, x.flags);
        } else { // 排除以上情况,就是普通对象
            result = {}
        }
        // 记录被拷贝的数据(x)和拷贝的结果(result)
        cache.set(x, result);
        // 开始递归拷贝
        for(let key in x){
            if(x.hasOwnProperty(key)){
                result[key] = deepClone(x[key], cache);
            }
        }
        
        return result;
    } else {
        // 普通类型直接返回就好
        return x;
    }
}

九、写一个JSON.stringfy()