# 手写代码(一)

  • 防抖
  • 节流
  • 深拷贝
  • 柯里化
  • 函数组合
  • new
  • instanceof
  • apply,bind,call
  • promise.all
  • promise.race
  • promise
  • promise封装ajax
  • 发布订阅模式
  • Symbol.iterator
  • 数组扁平化带参数
  • 数组去重
  • 并发请求控制
  • 实现 map 和 reduce
  • 实现 'abcd'.f() 返回 'd-c-b-a'
  • Object.create

# 防抖

所谓防抖,就是指触发事件后 n 秒后才执行函数,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。

// 防抖debounce
function debounce(fn, delay) {
  let timer = null
  return function () {
    timer && clearTimeout(timer)
    timer = setTimeout(() => {
      /**
      	fn 中的 this 指向 debounce 中 return 的这个函数中的 this
      	return 回来的这个函数中的 this 也就是指向直接调用 return 函数那个对象
      */
      fn.apply(this, arguments)
    }, delay)
  }
}

function out(){
    console.log("防抖")
}
window.onscroll = debounce(out, 1000)

# 节流

所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数。 节流会稀释函数的执行频率。

// 使用时间戳写法,事件会立即执行,停止触发后没有办法再次执行
function throttle(fn, delay) {
  let lastTime = 0;
  return function() {
    let now = Date.now();
    if (now - lastTime >= delay) {
      fn.apply(this, arguments);
      lastTime = now;
    }
  }
}

// 使用定时器写法,delay 毫秒后第一次执行,第二次事件停止触发后依然会再一次执行
function throttle2(fn, delay) {
  let timer = null;
  return function() {
    if (!timer) {
      timer = setTimeout(() => {
        fn.apply(this, arguments);
        timer = null;
      }, delay);
    }
  };
}

// 使用定时器写法,事件会立即执行
function throttle(fn, delay) {
  let timer = null;
  return function() {
    if (!timer) {
      fn.apply(this, arguments);
      timer = setTimeout(() => {
        timer = null;
      }, delay)
    }
  };
}

# 深拷贝

function deepClone(obj, hash = new WeakMap()) {
    if (obj instanceof Date) return new Date(obj);
    if (obj instanceof Error) return new Error(obj);
    if (obj instanceof RegExp) return new RegExp(obj);
    if (typeof obj !== "object" || obj === null) return obj; // 考虑数组
    if (hash.get(obj)) return hash.get(obj);
    let cloneObj = new Object();
    hash.set(obj, cloneObj);
    for (let key in obj) {
        if (Object.hasOwn(obj, key)) {
            cloneObj[key] = deepClone(obj[key], hash);
        }
    }
    return cloneObj;
}

使用WeakMaphashobj存在的就是弱引用关系,当下一次垃圾回收机制执行时,这块内存就会被释放掉。设想一下,如果我们要拷贝的对象非常庞大时,使用Map会对内存造成非常大的额外消耗,而且我们需要手动清除Map的属性才能释放这块内存,而WeakMap会帮我们巧妙化解这个问题。

# 柯里化

let curry = (fn,...args)=> args.length < fn.length
                ?(...arguments) => curry(fn,...args,...arguments)
                :fn(...args)

# 函数组合

function compose(...args) {
    return function(value) {
        return args.reverse().reduce((acc, cur) => cur(acc), value)
    }
}

# new

  1. 创造一个新的空对象
  2. 新对象的__proto__指向构造函数的原型对象
  3. 构造函数的this指向正在创建的新对象,并执行构造函数的代码,向新对象中添加属性和方法。
  4. 返回新对象地址
function _new(constructor, ...args) {
    let obj = new Object()
    obj.__proto__ = constructor.prototype
    constructor.call(obj, ...args)
    return obj
}

/**
		上面代码可以再优化一下,模拟了 new 就别再用 new 了
		而且没有考虑构造函数存在返回值的情况,如果构造函数返回了对象那么则返回这个对象
*/
function _new(constructor, ...args) {
  const obj = Object.create(fn.prototype); // 原型式继承
  const ret = constructor.call(obj, ...args); // 继承属性
  return typeof ret === "object" ? ret : obj;
}

# instanceof

function _instanceof(left, right) {
 		if (typeof L !== 'object') return false
    let l = left.__proto__;
    let r = right.prototype;
    while (true) {
        if (l === null) {
            return false;
        }
        if (l === r) {
            return true;
        }
        l = l.__proto__;
    }
}

# apply,call,bind

//apply
Function.prototype.myApply = function(context, args) {
    context = context || window;
    context.fn = this;
    const result = context.fn(...args);
    delete context.fn;
    return result;
    
}

let obj = {}
console.log(Math.max.myApply(obj, [1, 2, 3])) //3

//call
Function.prototype.myCall = function(context, ...args) {
    context = context || window;
    context.fn = this;
    const result = context.fn(...args);
    delete context.fn;
    return result;
}

let obj = {}
console.log(Math.max.myCall(obj, 1, 2, 3)) // 3

//bind
Function.prototype.myBind = function(context, ...args) {
    context = context || window;
    context.fn = this;
    return function() {
        const result = context.fn(...args, ...arguments);
        delete context.fn;
        return result;   
    }
}

let obj = {}
console.log(Math.max.myBind(obj, 1, 2, 3)(4)) // 4

# promise.all

Promise.all = function (promises) {
    return new Promise((resolve, reject) => {
        if (!promises || promises.length === 0) {
            resolve([]);
        }
        let count = 0;
        const result = [];
        for (let i = 0; i < promises[i].length; i++) {
            // 考虑到promises[i]可能是普通对象,则统一包装为Promise对象
            Promise.resolve(promises[i]).then(res => {
                result[i] = res;
                if (++count === promises.length) {
                    resolve(res);
                }
            }).catch(err => {
                // 任何一个Promise对象执行失败,则调用reject()方法
                reject(err);
                return;
            });
        }
    });
}

# promise.race

Promise.race = function(promises) {
    return new Promise((resolve, reject) => {
       	 if (!promises || promises.length === 0) {
            return;
         }
         for (const item of promises) {
              Promise.resolve(item).then((res) => {
                  resolve(res);
                  return;
              }).catch((err) => {
                  reject(err);
                  return;
              })
          }
    });
}

# promise.resolve

Promise.resolve = function (value) {
    if (value instanceof promise) return value
    return new Promise(resolve => resolve(value))
}

// 例子
Promise.resolve(100).then(value => console.log(value))

# Promise

  • 三种状态:pending,fulfilled,rejected
  • new Promise 时传进来一个执行器(参数是resolve和reject),是立即执行的
  • resolve函数的作用是改变状态为fulfilled,存储成功的值:value
  • reject函数的作用的改变状态为rejected,存储失败的原因:reason
  • then函数有两个参数,成功回调和失败回调,首先判断状态,fulfilled则执行成功回调,rejected则执行失败回调,pending则把回调存储起来
  • then要实现链式调用,需要返回 promise,then方法原来的操作可以放进新建 promise2 的执行器中立即执行,并且需要获取当前的返回值 x,传递给下一个 then 方法
const PENDING = 'pending' // 等待
const FULFILLED = 'fulfilled' // 成功
const REJECTED = 'rejected' //失败

class MyPromise {
  constructor (executor) {
    executor(this.resolve, this.reject);
  }
  // promise 状态
  status = PENDING;
  // 成功之后的值
  value = undefined;
  // 失败后的原因
  reason = undefined;
  // 成功回调,用数组的原因是可能有多个 promise.then
  successCallback = [];
  // 失败回调
  failCallback = [];
  // 定义成箭头函数的好处是 this 指向当前类
  resolve = value => {
    // 如果状态不是等待 阻止程序向下执行 因为状态一旦确定就不可以再改变
    if (this.status !== PENDING) return;
    // 将状态更改为成功
    this.status = FULFILLED;
    // 保存成功之后的值
    this.value = value;
    // 判断成功回调是否存在 存在则调用
    while (this.successCallback.length) {
     	this.successCallback.shift()(this.value);
    }
  }
  reject = reason => {
    // 如果状态不是等待 阻止程序向下执行
    if (this.status !== PENDING) return;
    // 将状态更改为失败
    this.status = REJECTED;
    // 保存失败后的原因
    this.reason = reason;
    // 判断失败回调是否存在 存在则调用
    while (this.failCallback.length) {
     	this.failCallback.shift()(this.reason); 
    }
  }
 then = (successCallback, failCallback) => {
   successCallback = successCallback ? successCallback : value => value;
   return new MyPromise((resolve, reject) => {
     if (this.status === FULLFILLED) {
       let x = successCallback(this.value);
       x instanceof MyPromise ? x.then(resolve, reject) : resolve(x);
     }
     else if (this.status === REJECTED) {
       failCallback(this.reason);
     }
     else {
       this.successCallback.push(successCallback);
       this.failCallback.push(failCallback);
     }
   });
 }
 catch = (failCallback) => {
   return this.then(undefined, failCallback);
 }
}

// 测试

// 1. 测试基本功能
let p = new MyPromise((resolve, reject) => {
    resolve('success')
    reject('fail')
})

p.then(val => {
  console.log(val)
}, reason => {
  console.log(reason)
})

// 2. 测试异步
let p = new MyPromise((resolve, reject) => {
    setTimeout(() => {
        resolve('success')   
    }, 2000)
})

p.then(val => {
  console.log(val)
}, reason => {
  console.log(reason)
})

// 3. 测试链式调用
let p = new MyPromise((resolve, reject) => {
    resolve('success')
})

p.then(res => {
    console.log(res)
    return 100
}).then(val => {
    console.log(val)
})

// 4. 测试 then 返回也是 promise
let p = new MyPromise((resolve, reject) => {
    resolve('success')
})

p.then(res => {
    console.log(res)
    return new MyPromise((resolve, reject) => {
        resolve('other')
    })
}).then(val => {
    console.log(val)
})

// 5. 测试 then 不传参数
let promise = new MyPromise((resolve, reject) => {
    resolve('成功')
})

p.then().then().then(val => console.log(val))

# promise封装ajax

function ajax (url) {
    return new Promise(function (reslove, reject) {
        var xhr = new XMLHttpRequest()
        xhr.open('GET', url) 
        xhr.reponeseType = 'json'
        xhr.onload = function () {
            if(this.status === 200) {
                reslove(this.response)
            } else {
                reject(new Error(this.statusText))
            }
        }
        xhr.send()
    })
}

ajax('/api/')
	.then(res => console.log(res))
	.catch(err => console.log(err))


//promise.reslove
Promise.resolve('foo')
    .then(function (value) {
 	   		console.log(value)
		})

# 发布订阅模式

    // 类内部存储一个对象,包含事件名和对应的触发函数
    // { click: [fn1, fn2], change: fn }
    class EventEmitter {
      constructor () {
        this.subs = Object.create(null)
      }
      // 注册事件
      $on (eventType, handler) {
        this.subs[eventType] = this.subs[eventType] || []
        this.subs[eventType].push(handler)
      }

      // 触发事件
      $emit (eventType) {
        if (this.subs[eventType]) {
          this.subs[eventType].foreach(handler => {
            handler()
          })
        }
      }
    }

    // 测试
    let em = new EventEmitter() // 信号中心
    em.$on('click', () => {
      console.log('click1')
    })
    em.$on('click', () => {
      console.log('click2')
    })
    em.$emit('click')

# Symbol.iterator

const todos = {
    life: ['吃饭', '睡觉', '打豆豆'],
    learn: ['语文', '数学', '外语'],
    work: ['喝茶'],

    [Symbol.iterator]: function * () {
        const all = [...this.life, ...this.learn, ...this.work]
        for(const item of all) {
            yield item
        }
    }
}

for (let item of todos) {
    console.log(item)
}

# 数组扁平化带参数

function flattern(arr, depth) {
    let res = []
    for(let item of arr){
        if (Array.isArray(item) && depth > 0) {
            res = res.concat(flattern(item, depth - 1))
        } else {
            res.push(item)
        }
    }
    return res
}

// 一次性拍平
function flat(arr) {
  return arr.toString().split(',').map(item => +item)
}

# 数组去重

(1)reduce + includes

function deduplicate(arr) {
    return arr.reduce((acc, cur) => acc.includes(cur) ? acc : acc.concat(cur), []);
}
// concat 不会改变原有数组,返回值为新数组,可拼接单个元素

(2)利用ES6的Set数据结构

set 类似于数组,且成员值不重复都是唯一的,set本身是一个构造函数。

function deduplicate(arr) {
    return [...new Set(arr)];
}

(3)利用sort

function deduplicate(arr) {
    arr = arr.sort();
    const res = [];
    for (let i = 1; i < arr.length; i++) {
        if (arr[i] !== arr[i - 1]) {
            res.push(arr[i]);
        }
    }
    return res;
}

(4)利用includes

function deduplicate(arr) {
    const res = [];
    for (let i = 0; i < arr.length; i++) {
        if (!res.includes(arr[i])) {
            res.push(arr[i]);
        }
    }
    return res;
}

# 并发请求控制

实现一个批量请求函数 multiRequest(urls, maxNum),要求如下: • 要求最大并发数 maxNum • 每当有一个请求返回,就留下一个空位,可以增加新的请求 • 所有请求完成后,结果按照 urls 里面的顺序依次打出

function multiRequest(urls = [], maxNum) {
    const len = urls.length
    const result = new Array(len).fill(false)
    let count = 0
  
    return new Promise((resolve, reject) => {
        while (count < maxNum) {
            next();
        }
        function next() {
            let current = count++;
            if (current >= len) {
                !result.includes(false) && resolve(result);
                return;
            }
            const url = urls[current];
            fetch(url).then((res) => {
                result[current] = res;
                if (current < len) {
                    next();
                }
            }).catch((err) => {
                result[current] = err;
                if (current < len) {
                    next();
                }
            })
        }
    })
}

# 实现一个sleep函数

const sleep = (time) => {
  return new Promise(resolve => setTimeout(resolve, time))
}

sleep(1000).then(() => {
  // 这里写你的操作
})

# 实现 map 和 reduce

Array.prototype.myMap = function(fn) {
    const arr = this;
    const res = [];
    for (let i = 0; i < arr.length; i++) {
        res[i] = fn(arr[i], i, arr)
    }
    return res;
};

Array.prototype.myReduce = function(fn, initValue) {
    const arr = this;
    let val = initValue || arr[0]
    for (let i = initValue ? 0 : 1; i < arr.length; i++) {
        val = fn(val, arr[i], i, arr)
    }
    return val;
};

# 实现 'abcd'.f() 返回 'd-c-b-a'

String.prototype.f = function() {
    return this.split('').reverse().join('-')
}

# Object.create

// Object.create 实现
let o = Object.create(obj) // o.__proto__ === obj
Object.create = function (obj) {
    function F() {}
    F.prototype = obj;
    return new F();
};

// 可以理解为
Object.create = function (obj) {
  return {
    __proto__: obj
  }
}
最后更新时间: 3/6/2023, 7:42:30 PM