(备忘录)手写常用的JavaScript方法

213 阅读9分钟

call和apply和bind

call

Function.prototype.myCall = function (context = window, ...args) {
  if (typeof this !== 'function') {
    throw new TypeError('is not function')
  }
  const fn = Symbol()
  // 保存执行的函数
  context[fn] = this
  // 执行函数,被执行的函数this指向当前内部的context对象
  let res = context[fn](...args)
  delete context[fn]
  return res
}

apply

Function.prototype.myApply = function (context = window, args = []) {
  if (typeof this !== 'function') {
    throw new TypeError('is not function')
  }
  const fn = Symbol()
  context[fn] = this
  let res = context[fn](...args)
  delete context[fn]
  return res
}

bind

Function.prototype.myBind = function (context = window, ...args1) {
  if (typeof this !== 'function') {
    throw new TypeError('is not function')
  }
  const _this = this
  return function F(...args2) {
    if (this instanceof F) {
      return new _this(...args1, ...args2)
    } else {
      // 利用闭包保存context
      return _this.apply(context, [...args1, ...args2])
    }
  }
}

防抖和节流

防抖

function debounce(fn, wait = 50) {
  let timer
  return function (...args) {
    // 利用闭包,如果在wait时间内重新调用,则清除上一个定时器
    if (timer) clearTimeout(timer)
    // 重新开一个定时器,在wait毫秒后执行FN
    timer = setTimeout(() => {
      fn.apply(this, args)
    }, wait)
  }
}

节流

function throttle(fn, wait = 50) {
    let lastTime = 0
    return function (...args) {
        const nowTime = +new Date()
        // 当前时间减去上一次时间大于设置的时间就执行
        if (nowTime - lastTime >= wait) {
            // 重新赋值
            lastTime = nowTime
            fn.apply(this, args)
        }
    }
}

结合版

function fff(fn, wait = 50) {
  let lastTime = 0
  let timer
  return function (...args) {
    if (nowTime - lastTime >= 50) {
      clearTimeout(timer)
      timer = null
      lastTime = nowTime
      fn.apply(this, args)
    } else if (!timer) {
      timer = setTimeout(() => {
        fn.apply(this, args)
      }, wait)
    }
  }
}

深拷贝和浅拷贝

浅拷贝(值拷贝,引用类型会复制地址)

1.循环赋值

Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。Object.create(proto,[propertiesObject])接收两个参数一个是新创建对象的__proto__, 一个属性列表

function clone(obj) {
  let obj2 = Object.create(null)
  for (const key in obj) {
    obj2[key] = obj[key]
  }
  return obj2
}

2.扩展运算符

创建一个新的对象,并将原始对象的属性和值复制到新对象中。如果原始对象中包含了其他对象作为其属性值,那么新对象和原始对象会共享这些属性,它们都指向同一个对象。

let obj1 = { ...obj }

3.assign方法

Object.assign()用于将所有可枚举属性的值从一个或多个源对象分配到目标对象。它将返回目标对象。

let obj2 = Object.assign({},obj1)

深拷贝(重新开辟空间)

1.JSON方法(无法解决互相引用等问题)

function deepClone(obj){
  return JSON.parse(JSON.stringify(obj))
}

2.递归

function isObject(obj) {
  const {
    toString
  } = Object.prototype
  return toString.call(obj) === '[object Object]' || toString.call(obj) === '[object Array]'
}

function deepClone(obj) {
  if (isObject(obj)) {
    let o = Array.isArray(obj) ? [] : {}
    // 循环判断是不是对象,如果是对象就递归进去,普通属性直接赋值
    for (const key in obj) {
      if (isObject(obj[key])) {
        o[key] = deepClone(obj[key])
      } else {
        o[key] = obj[key]
      }
    }
    return o
  } else {
    return obj
  }
}

对象比较

1.JSON.stringify(obj)

// todo: 判断两个对象转后的字符串是否相等
// 缺陷:当两个对比的对象中key的顺序不是完全相同时会比较出错
function shallowEqual(object1, object2) {
  const obj1 = JSON.stringify(object1);
  const obj2 = JSON.stringify(object2);

  if (obj1.length !== obj1.length || obj1 !== obj2) {
    return false;
  }

  return true;
}

2.逐个访问

Object.prototype.deepCompare = function (obj) { 
    const objKeys1 = Object.keys(this)
    const objKeys2 = Object.keys(obj)
    // 两个对象的属性量不同
    if(objKeys1.length !== objKeys2.length){
        return false
    }
	// 遍历objKeys1对象对比每一个属性
    for(let key of objKeys1){
        if(this[key] !== obj[key]){
            return false
        }
    }

    return true
}

let obj = {
    a:1,
    b:2
}

console.log(obj.deepCompare({a:1,b:2})) // true

数组方法

去重

1. 利用 Set

function unique (arr) {
  return Array.from(new Set(arr))
}

2. 利用 indexOf

function unique(arr) {
  let res = [];
  for (let i = 0; i < arr.length; i++) {
    if (res.indexOf(arr[i]) === -1) {
      res.push(arr[i])
    }
  }
  return res
}

3. 利用filter

function unique(arr) {
  return arr.filter((item, index, arr) =>{
    //当前元素,在原始数组中的第一个索引==当前索引值,否则返回当前元素
    return arr.indexOf(item, 0) === index
  })
}

4. 利用reduce

function unique(arr) {
    return arr.reduce((prev, current) => {
        // 前置数组不包括当前元素就添加
        if (!prev.includes(current)) { prev.push(current) }
        return prev
    }, [])
}

乱序

// 1. 利用sort方法
function random(arr) {
  return arr.sort(() => .5 - Math.random())
}

// 2. 手动调换位置
function random(arr) {
  let len = arr.length
  let current = len - 1
  while (current > -1) {
    let randomNum = Math.floor(Math.random() * len);
    [arr[current], arr[randomNum]] = [arr[randomNum], arr[current]]
    current--
  }
  return arr
}

扁平化

1. 利用原生方法

const arr = [1, [2, [3, 4]]];
function flatten(arr) {
  return arr.flat(Infinity);
}
console.log(flatten(arr)); //  [1, 2, 3, 4,5]

2. 只能解决二维数组

function flat(arr) {
  let res = []
  for (const i of arr) {
    if (Array.isArray(i)) {
      res = res.concat(flat(i))
    } else {
      res.push(i)
    }
  }
  return res
}

3. 利用reduce

function flat(arr) {
  return arr.reduce((pre, cur) => {
    return Array.isArray(cur) ? [...pre, ...flat(cur)] : [...pre, cur]
  }, [])
}

function flat(arr) {
  return arr.reduce((pre, cur) => {
    return Array.isArray(cur) ? pre.concat(flat(cur)) : pre.concat(cur)
  }, [])
}

4. (魔法) 去掉双引号

function flat(arr) {
  // 去掉双引号 得到一个"1,2,3,4,5,6"的字符串
  let str = JSON.stringify(arr).replace(/(\[|\])/g, '')
  return str.split(',').map(key => key - 0)
}

5. (魔法) 利用toString

const arr = [1, [2, [3, 4]]];
function flatten(arr) {
  return arr.toString().split(',');
}
console.log(flatten(arr)); //  [1, 2, 3, 4]

模拟map

1. 利用reduce

// map的参数 current当前正在处理的元素,index索引,array,当前数组
// 返回一个新数组
Array.prototype.myMap = function (handler) {
  return this.reduce((target, current, index, array) => {
    target.push(handler.call(this, current, index, array))
    return target
  }, [])
}

2. 遍历引用的数组

Array.prototype.myMap = function (hanler) {
  let res = []
  // this指向调用的数组
  for (let i = 0; i < this.length; i++) {
    res.push(hanler(this[i], i, this))
  }
  return res
}

模拟filter

1. 利用reduce

// map的参数 current当前正在处理的元素,index索引,array,当前数组 
// 返回过滤后的数组
Array.prototype.reduceToFilter = function (handler) {
  return this.reduce((target, current, index, array) => {
    // 如果满足传入的函数
    if (handler.call(this, current, index, array)) {
      target.push(current)
    }
    return target
  }, [])
}

2. 遍历

Array.prototype.myFilter = function (hanler) {
  let res = []
  for (let i = 0; i < this.length; i++) {
    if (hanler(this[i], i, this)) {
      res.push(this[i])
    }
  }
  return res
}

模拟fill

//array.fill(value, start, end)
//value 必需。填充的值。start 可选。开始填充位置。end 可选。停止填充位置 (默认为 array.length)
Array.prototype.myFill = function (target, start = 0, end = arr.length) {
  for (let i = start; i < end; i++) {
    this[i] = target
  }
}

模拟find

// find() 方法返回数组中满足提供的测试函数的第一个元素的值。 否则返回 undefined
Array.prototype.myfind = function (fn, start = 0, end = this.length) {
  for (let i = start; i < end; i++) {
    if (fn.call(this, this[i], i, this)) {
      return this[i]
    }
  }
}

模拟findIndex

// findIndex() 方法返回数组中满足提供的测试函数的第一个元素的索引。 否则返回 -1
Array.prototype.myFindIndex = function (fn, start = 0, end = this.length) {
    for (let i = start; i < end; i++) {
        if (fn.call(this, this[i], i, this)) {
            return i
        }
    }
     return -1
}

实现 promise.all

1. async

// 用async实现promise.All
async function asyncAlls(jobs) {
    try {
        let results = jobs.map(async job => await job)
        let res = []
        for (const result of results) {
            res.push(await result)
        }
        return res
    } catch (error) {
        throw new Error(error)
    }
}

2. for await of

// 一秒后1,2秒后2,3秒后3,并不是并行,需要trycatch捕获错误
const asyncAll = async (promises) => {
  try {
    for await (item of promises) {
      console.log(item);
    }
  } catch (e) {
    console.log(e)
  }
}

const a = function () {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(1)
    }, 1000);
  })
}

const b = function () {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(2)
    }, 2000);
  })
}

const c = function () {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject(333)
    }, 3000);
  })
}

asyncAll([a(), b(), c()])

模拟promise

1. Typescript版

type PromiseState = 'pending' | 'resolved' | 'rejected'

interface PromiseResolve<T> {
  (value: T): void
}
interface PromiseReject<T> {
  (value: T): void
}

interface MyPromiseMethod<T> {
  then: (onFulfilled: any, onRejected: any) => void
  all: (promises: MyPromise<T>) => any
  race: (promises: MyPromise<T>[]) => any
  reject: (reason: any) => any
  resolve: (value: any) => any
  catch: (error: any) => any
  finally: (fn: any) => any
}

class MyPromise<T> implements MyPromiseMethod<T> {
  state: PromiseState
  value: T
  reason: T
  successCb: PromiseResolve<T>[]
  failCb: any[]
  constructor(exec) {
    this.state = 'pending'
    this.value = null
    this.reason = null
    this.successCb = []
    this.failCb = []
    const resolve: PromiseResolve<T> = (value: T) => {
      if (this.state === 'pending') {
        this.value = value
        this.state = 'resolved'
        this.successCb.forEach((fn) => { fn(value) })
      }
    }
    const reject: PromiseReject<T> = (reason: T) => {
      if (this.state === 'pending') {
        this.reason = reason
        this.state = 'rejected'
        this.failCb.forEach((fn) => { fn(reason) })
      }
    }
    try {
      exec(resolve, reject)
    } catch (e) {
      reject(e)
    }
  }
  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
    onRejected = typeof onRejected === 'function' ? onRejected : reason => reason
    if (this.state === 'pending') {
      this.successCb.push(onFulfilled)
      this.failCb.push(onRejected)
    }
    if (this.state === 'resolved') {
      onFulfilled(this.value)
    }
    if (this.state === 'rejected') {
      onFulfilled(this.reason)
    }
  }
  all(promise) {
    let count = 0
    let res = []
    return new MyPromise((resolve, reject) => {
      for (let i = 0; i < promise.length; i++) {
        (promise[i] as MyPromise<T>).then(value => {
          res.push(value)
          count++
          if (count === promise.length) {
            resolve(res)
          }
        }, e => {
          reject(e)
        })
      }
    })
  }
  race(promise) {
    return new MyPromise((resolve, reject) => {
      for (let i = 0; i < promise.length; i++) {
        (promise[i] as MyPromise<T>).then(value => {
          resolve(value)
        }, e => {
          reject(e)
          return
        })
      }
    })
  }
  static reject(reason) {
    return new MyPromise((resolve, reject) => {
      reject(reason)
    })
  }
  static resolve(value) {
    return new MyPromise((resolve, reject) => {
      resolve(value)
    })
  }
  catch(onRejected) {
    return this.then(null, onRejected)
  }
  finally(fn) {
    this.then((value) => {
      fn()
      return value
    }, err => {
      fn()
      return err
    })
  }
}
function testPromise(value) {
  return new MyPromise((resolve, reject) => {
    setTimeout(() => {
      if (value > 5) {
        resolve('大于5')
      } else {
        reject('小于等于5')
      }
    }, 50)
  })
}
function testPromise2(value) {
  return new MyPromise((resolve, reject) => {
    setTimeout(() => {
      if (value > 5) {
        resolve('大于5')
      } else {
        reject('小于等于5')
      }
    }, 50)
  })
}
MyPromise.prototype
.all([testPromise2(11), testPromise(10)])
.then(value => {
   console.log(value)
 }, e => { console.log(e) })

2. JavaScript版

var PromisePolyfill = (function () {
  // 和reject不同的是resolve需要尝试展开thenable对象
  function tryToResolve (value) {
    if (this === value) {
    // 主要是防止下面这种情况
    // let y = new Promise(res => setTimeout(res(y)))
      throw TypeError('Chaining cycle detected for promise!')
    }

    // 根据规范2.32以及2.33 对对象或者函数尝试展开
    // 保证S6之前的 polyfill 也能和ES6的原生promise混用
    if (value !== null &&
      (typeof value === 'object' || typeof value === 'function')) {
      try {
      // 这里记录这次then的值同时要被try包裹
      // 主要原因是 then 可能是一个getter, 也也就是说
      //   1. value.then可能报错
      //   2. value.then可能产生副作用(例如多次执行可能结果不同)
        var then = value.then

        // 另一方面, 由于无法保证 then 确实会像预期的那样只调用一个onFullfilled / onRejected
        // 所以增加了一个flag来防止resolveOrReject被多次调用
        var thenAlreadyCalledOrThrow = false
        if (typeof then === 'function') {
        // 是thenable 那么尝试展开
        // 并且在该thenable状态改变之前this对象的状态不变
          then.bind(value)(
          // onFullfilled
            function (value2) {
              if (thenAlreadyCalledOrThrow) return
              thenAlreadyCalledOrThrow = true
              tryToResolve.bind(this, value2)()
            }.bind(this),

            // onRejected
            function (reason2) {
              if (thenAlreadyCalledOrThrow) return
              thenAlreadyCalledOrThrow = true
              resolveOrReject.bind(this, 'rejected', reason2)()
            }.bind(this)
          )
        } else {
        // 拥有then 但是then不是一个函数 所以也不是thenable
          resolveOrReject.bind(this, 'resolved', value)()
        }
      } catch (e) {
        if (thenAlreadyCalledOrThrow) return
        thenAlreadyCalledOrThrow = true
        resolveOrReject.bind(this, 'rejected', e)()
      }
    } else {
    // 基本类型 直接返回
      resolveOrReject.bind(this, 'resolved', value)()
    }
  }

  function resolveOrReject (status, data) {
    if (this.status !== 'pending') return
    this.status = status
    this.data = data
    if (status === 'resolved') {
      for (var i = 0; i < this.resolveList.length; ++i) {
        this.resolveList[i]()
      }
    } else {
      for (i = 0; i < this.rejectList.length; ++i) {
        this.rejectList[i]()
      }
    }
  }

  function Promise (executor) {
    if (!(this instanceof Promise)) {
      throw Error('Promise can not be called without new !')
    }

    if (typeof executor !== 'function') {
    // 非标准 但与Chrome谷歌保持一致
      throw TypeError('Promise resolver ' + executor + ' is not a function')
    }

    this.status = 'pending'
    this.resolveList = []
    this.rejectList = []

    try {
      executor(tryToResolve.bind(this), resolveOrReject.bind(this, 'rejected'))
    } catch (e) {
      resolveOrReject.bind(this, 'rejected', e)()
    }
  }

  Promise.prototype.then = function (onFullfilled, onRejected) {
  // 返回值穿透以及错误穿透, 注意错误穿透用的是throw而不是return,否则的话
  // 这个then返回的promise状态将变成resolved即接下来的then中的onFullfilled
  // 会被调用, 然而我们想要调用的是onRejected
    if (typeof onFullfilled !== 'function') {
      onFullfilled = function (data) {
        return data
      }
    }
    if (typeof onRejected !== 'function') {
      onRejected = function (reason) {
        throw reason
      }
    }

    var executor = function (resolve, reject) {
      setTimeout(function () {
        try {
        // 拿到对应的handle函数处理this.data
        // 并以此为依据解析这个新的Promise
          var value = this.status === 'resolved'
            ? onFullfilled(this.data)
            : onRejected(this.data)
          resolve(value)
        } catch (e) {
          reject(e)
        }
      }.bind(this))
    }

    // then 接受两个函数返回一个新的Promise
    // then 自身的执行永远异步与onFullfilled/onRejected的执行
    if (this.status !== 'pending') {
      return new Promise(executor.bind(this))
    } else {
    // pending
      return new Promise(function (resolve, reject) {
        this.resolveList.push(executor.bind(this, resolve, reject))
        this.rejectList.push(executor.bind(this, resolve, reject))
      }.bind(this))
    }
  }

  // for prmise A+ test
  Promise.deferred = Promise.defer = function () {
    var dfd = {}
    dfd.promise = new Promise(function (resolve, reject) {
      dfd.resolve = resolve
      dfd.reject = reject
    })
    return dfd
  }

  // for prmise A+ test
  if (typeof module !== 'undefined') {
    module.exports = Promise
  }

  return Promise
})()

PromisePolyfill.all = function (promises) {
  return new Promise((resolve, reject) => {
    const result = []
    let cnt = 0
    for (let i = 0; i < promises.length; ++i) {
      promises[i].then(value => {
        cnt++
        result[i] = value
        if (cnt === promises.length) resolve(result)
      }, reject)
    }
  })
}

PromisePolyfill.race = function (promises) {
  return new Promise((resolve, reject) => {
    for (let i = 0; i < promises.length; ++i) {
      promises[i].then(resolve, reject)
    }
  })
}

模拟AJAX

function ajax(config: AjaxConfig) {
  return new Promise((resolve, reject) => {
    const { data = null, url, method = 'Get' } = config
    const request = new XMLHttpRequest()
    request.open(method, url, true)
    request.onreadystatechange = function () {
      if (request.readyState === 4 && request.status === 200) {
        resolve(request.responseText);
      } else {
        reject(request.statusText)
      }
    }
    request.send(JSON.stringify(data))
  })
}

new 的过程

function myNew(fn, ...args) {
  //创建一个新对象,将新对象的__proto__ 指向构造函数的原型
  let obj = Object.create(fn.prototype)
  let res = fn.apply(obj, args)
  return res instanceof Object ? res : obj
}

Object.create()

// 用于创建一个新对象,被创建的对象继承另一个对象(o)的原型
function create(obj) {
  function F() { }
  F.prototype = obj
  return new F()
}

实现instanceof的功能

function myInstanceof(obj, F) {
  while (obj.__proto__) {
    if (obj.__proto__ === F.prototype) {
      return true
    }
    obj = obj.__proto__
  }
  return false
}

使用setTimeout实现setInterval方法

// 取消定时器
let obj = {
    flag: false
}

function mySetinterval(fn, timer = 4000, ...args) {
    if (obj.flag) { return }
    setTimeout(() => {
        console.log(777, obj.flag)
        fn(...args)
        mySetinterval(fn, timer, ...args)
    }, timer)

    return obj
}

实现JSONP

//https://sp0.baidu.com/su?wd=Java&cb=cb'; 没有判断是否有?
function jsonP(url, data, cb) {
  let script = document.createElement('script')
  let str = url

  if (data || cb) { str += '?' }

  for (let [key, value] of Object.entries(data)) {
    str += `${key}=${value}&`
  }
  if (cb) {
    str += `${cb}=${cb}`
  }

  script.scr = str
  document.body.appendChild(script)
}

promise 实现sleep函数

function sleep(wait = 50){
  return new Promise((resolve,reject)=>{
    setTimeout(()=>{
      resolve()
    }, wait)
  })
}

单例模式

1.

class Person {
    static instance: null | Person
    private name: string
    private age: string
    constructor(name, age) {
        this.name = name
        this.age = age
    }
    static createPerson(name, age) {
        if (!Person.instance) {
            Person.instance = new Person(name, age)
        }
        return Person.instance
    }
}

2.

class Singleton {
  constructor() {}
}

Singleton.getInstance = (function() {
  let instance
  return function() {
    if (!instance) {
      instance = new Singleton()
    }
    return instance
  }
})()

let s1 = Singleton.getInstance()
let s2 = Singleton.getInstance()
console.log(s1 === s2) // true

策略模式

// 1.HTML
<form class="cs-form">
  <label>
    <span>账号:</span>
    <input name="account" type="text" />
  </label>
  <label>
    <span>密码:</span>
    <input name="password" type="password" />
  </label>
  <label>
    <span>手机号:</span>
    <input name="mobile" type="number" />
  </label>
  <button class="submit" type="submit">登录</button>
</form>

// 2. JavaScript
interface Iform {
  [k: string]: any;
  account: HTMLInputElement;
  password: HTMLInputElement;
  mobile: HTMLInputElement;
}

interface Ilist {
  value: string;
  type: name;
  msg: string;
}

interface Iadd {
  type: name;
  msg: string;
}

type name = keyof typeof rule;
// 匹配规则
const rule = {
  require(value: string, msg: string) {
    if (!value) {
      return msg;
    }
  },
  minLength(value: string, msg: string) {
    if (value.length < 6) {
      return msg;
    }
  },
  mobile(value: string, msg: string) {
    if (!/\d{11}/.test(value)) {
      return msg;
    }
  }
};
class Verification {
  private list: Ilist[] = [];
  public message: string;
  // 用函数重载的形式实现
  add<T extends { value: string }>(dom: T, type: name, msg: string): void;
  add<T extends { value: string }>(dom: T, ruleArr: Array<Iadd>): void;
  add<T extends { value: string }>(
    dom: T,
    ruleArr: Array<Iadd> | name,
    msg?: string
  ): void {
    // 如果匹配规则是数组的话,则遍历添加进去
    if (Array.isArray(ruleArr)) {
      this.list.push(
        ...ruleArr.map(f => {
          return {
            value: dom.value,
            type: f.type,
            msg: f.msg
          };
        })
      );
    } else {
      this.list.push({
        value: dom.value,
        type: ruleArr,
        msg: msg || ''
      });
    }
  }
  start(): boolean {
    // 遍历执行
    for (let i = 0; i < this.list.length; i++) {
      const { value, type, msg } = this.list[i];
      const fn = rule[type](value, msg);
      if (fn) {
        this.message = msg;
        return false;
      }
    }
    this.message = "";
    return true;
  }
}

const form = document.querySelector(".cs-form") as HTMLFormElement;

form.addEventListener("submit", function(e) {
  e.preventDefault();
  const { account, password, mobile } = (form as unknown) as Iform;

  const ruleSet = new Verification();
  // 添加规则
  ruleSet.add(account, "require", "请输入账号");
  ruleSet.add(password, [
    {
      type: "require",
      msg: "请输入密码"
    },
    {
      type: "minLength",
      msg: "密码长度不符合要求"
    }
  ]);
  ruleSet.add(mobile, [
    {
      type: "require",
      msg: "请输入手机号"
    },
    {
      type: "mobile",
      msg: "请输入正确的手机号"
    }
  ]);

  if (!ruleSet.start()) {
    // 提示
    alert(ruleSet.message);
    return false;
  }
  return false;
});

发布订阅模式

class Watcher {
    constructor() {
        this.subs = []
    }
    on(eventName, fn) {
        if (this.subs[eventName]) {
            this.subs[eventName].push(fn)
        } else {
            this.subs[eventName] = [fn]
        }
    }
    emit(eventName, ...args) {
        if (Array.isArray(this.subs[eventName])) {
            this.subs[eventName].forEach(fn => {
                fn.apply(this, args)
            })
        }
    }
    unbind(eventName, fn) {
        if (fn) {
            this.subs[eventName] = this.subs[eventName].filter((e) => e !== fn && e.orgin !== fn)
        } else {
            delete this.subs[eventName]
        }
    }
    once(eventName, fn) {
        let only = (...args) => {
            fn.apply(this, args)
            this.unbind(eventName, fn)
        }
        only.orgin = fn
        this.on(eventName, only)
    }
}

函数柯里化

// 手写一个简单的把普通函数变为柯里化函数的方式

// 每次调用的传进来的参数做累计处理
function reduce (...args) {
    return args.reduce((a, b) => a + b);
}
function currying (fn) {
  // 存放每次调用的参数
  let args = [];
  return function temp (...newArgs) {
    if (newArgs.length) {
      // 有参数就合并进去,然后返回自身
      args = [ ...args, ...newArgs ];
      return temp;
    } else {
      // 没有参数了,也就是最后一个了,执行累计结果操作并返回结果
      let val = fn.apply(this, args);
      args = [] //保证再次调用时清空
      return val;
    }
  }
}
let add = currying(reduce);
console.log(add(1)(2, 3, 4)(5)());  //15
console.log(add(1)(2, 3)(4, 5)());  //15

异步循环打印

1.利用let限制作用域

for (let i = 0; i < 10; i++) {
  setTimeout(() => {
    console.log(i)
  }, 0)
}

2. 利用闭包保存变量

(function (j) {
  setTimeout(() => {
    console.log(j)
  }, 0)
})(i)

3. 利用定时器的第三个参数

// 第三个参数会被当成第一个函数的参数
for (var i = 0; i < 10; i++) {
  setTimeout((j) => {
    console.log(j)
  }, 0, i)
}

实现继承

1. class

class Father {
  constructor(name) {
    this.name = name
  }
  sayHi() {
    console.log(`${this.name} Say Hi`)
  }
}

// 会自动继承父类的原型
class Son extends Father {
  constructor(name, age) {
    //使用super调用父类构造函数
    super(name)
    this.age = age
  }
}

const person = new Son('zs', 15)
person.sayHi()

2. 原型链继承

`缺点 1.创建子类实例时,无法向父类构造函数传参 2.共享父类引用类型对象`
function Father() {
  this.arr = [1,2,3,4,5]
}
Father.prototype.sayHi = function () {
  return `说 Hi`
}

function Son(age) {
  this.age = age
}

Son.prototype = new Father()

const son = new Son(15)
const son2 = new Son(15)
son.arr.push(6)
son.arr === son2.arr //true

3. 构造函数继承

`缺点 1.无法继承父类的原型`
function Father(name) {
  this.name = name
}
Father.prototype.sayHi = function () {
  return `说 Hi`
}

function Son(name, age) {
  Father.call(this, name)
  this.age = age
}

const son = new Son('zs',15)

4. 组合继承

`缺点 1.调用两次构造函数,浪费内存`
function Father(name) {
    this.name = name
}

Father.prototype.sayHi = function () {
    return `${this.name} 说 Hi`
}

function Son(name, age) {
    Father.call(this, name)
    this.age = age
}

Son.prototype = new Father()
const son = new Son('zs', 15)

5. 寄生式继承

function Father(name) {
  this.name = name
}
Father.prototype.sayHi = function () {
  return `${this.name} 说 Hi`
}

function Son(name, age) {
  Father.call(this, name)
  this.age = age
}

Son.prototype = Object.create(Father.prototype, {
  constructor: {
    value: Son
  }
})

const son = new Son('zs', 15)

6. 寄生组合式继承

function Father(){ 
    this.a = [1,2] 
}
father.prototype.say = function(){
    console.log('aaa')
}

function Son(){
    Father.call(this)
    this.b = 2
}

Son.prototype = Object.create(Father.prototype)
Son.prototype.constructor = Son

const test = new Son()

插入大量DOM

let container = document.querySelector('.container')

const sum = 1000
const num = 20
const loop = Math.floor(sum / num)
let count = 0

function render() {
    const frag = document.createDocumentFragment()
    for (let i = 0; i < num; i++) {
        const div = document.createElement('div')
        div.innerHTML = `${Math.floor(count*Math.random())}`
        frag.appendChild(div)
    }
    container.appendChild(frag)
    if (++count !== loop) {
        looper()
    }
}
function looper(fn) {
    requestAnimationFrame(fn)
}
looper(render)

参考链接