Skip to content

Array.prototype.sort

js
;[3, 1, 4].sort((a, b) => a - b) // 1,3,4
;[3, 1, 4].sort((a, b) => b - a) // 4,3,1

currying 柯里化函数

js
function currying(fn) {
  // 通用的柯里化函数
  var args = []
  return function() {
    if (arguments.length === 0) {
      return fn.apply(this, args)
    } else {
      args.push(arguments)
      return arguments.callee
    }
  }
}

var cost = currying(function() {
  let result = 0
  let len = arguments.length
  while (len--) {
    result += arguments[len][0]
  }
  return len
})

惰性代码

js
function isEnterKey(keyCode) {
  var finalCode
  // 遥控器 确认按键
  if (keyCode === 13 || keyCode === 14) {
    finalCode = keyCode
    isEnterKey = function(keyCode) {
      return finalCode === keyCode
    }
  }
}

addEventListener 兼容优化写法

js
function addEvent(element, eventName, callback, options) {
  if (element.addEventListener) {
    // 重写函数
    addEvent = function(element, eventName, callback, options) {
      return element.addEventListener(eventName, callback, options)
    }
  } else if (element.attachEvent) {
    addEvent = function(element, eventName, callback, options) {
      element.attachEvent("on" + eventName, callback, options)
    }
  } else {
    addEvent = function() {
      element["on" + eventName] = callback
    }
  }
  addEvent(element, eventName, callback, options)
}

同理解绑函数也是同上

js
function _off(el, event, fn) {
  if (el.removeEventListener) {
    el.removeEventListener(event, fn, false)
  } else if (el.detachEvent) {
    el.detachEvent("on" + event, fn.bind(el))
  } else {
    el["on" + event] = null
  }
}

为元素添加 on 和 trgger 方法

js
// on
Element.prototype.on = Element.prototype.addEventListener
NodeList.prototype.on = function(event, fn) {
  Array.prototype.forEach.call(this, el => {
    el.on(event, fn)
  })
  return this
}

// trigger

使用代理的单例模式

js
function Man() {}

function createSingleClass(Fn) {
  var instance
  return function newFn() {
    if (this instanceof newFn) {
      if (!instance) {
        instance = new Fn(...arguments)
      }
      return instance
    }
  }
}

翻转数字

12345 => 54321

js
function reverserNumber(number) {
  var newNumber = 0
  while (number >= 1) {
    var pop = number % 10
    number = Math.floor(number / 10)
    newNumber = newNumber * 10 + pop
  }
  return newNumber
}

找到第一个未重复的字符

js
function findFirstNumber(str) {
  var i = -1,
    s
  while ((s = str[++i])) {
    if (str.lastIndexOf(s) === i) {
      return i
    }
  }
}

限制并发数并尽快的完成任务

需要上传多张,要求尽快的上传完成,但是同时上传的格式为 m

js
function loadImg(url) {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log(`${url} is loaded`)
      resolve(url)
    }, url * 100)
  })
}

// promise 版本
async function limitLoadAwait(urls, handler, limit) {
  let sequence = [].concat(urls)
  let result = []
  let promises = sequence.splice(0, limit).map(async (url, index) => {
    result.push(await handler(url))
    return index
  })
  await sequence.reduce(async (pCollect, url, currentIndex) => {
    await pCollect
    let fastIndex = await Promise.race(promises)

    promises[fastIndex] = handler(url).then(res => {
      result.push(res)
      return fastIndex
    })
    return promises
  }, Promise.resolve())

  await Promise.all(promises)
  return result
}

// async await 版本
async function limitLoadAwait(urls, handler, limit) {
  let sequence = [].concat(urls)
  let result = []
  let promises = sequence.splice(0, limit).map(async (url, index) => {
    result.push(await handler(url))
    return index
  })
  await sequence.reduce(async (pCollect, url, currentIndex) => {
    await pCollect
    let fastIndex = await Promise.race(promises)

    promises[fastIndex] = handler(url).then(res => {
      result.push(res)
      return fastIndex
    })
    return promises
  }, Promise.resolve())

  await Promise.all(promises)
  return result
}

巧妙结合 promise 和 reduce

需要连续调用三个接口, 如果接口返回 true,继续调用下个接口。直到返回 false 或者到末尾

js
function uploadImg(url) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(url)
    }, 1000)
  })
}

function uploadOneByOne(urls, handler) {
  return urls.reduce((p, url) => {
    return p.then(res => {
      if (res) {
        return handler(url)
      } else {
        return Promise.resolve(false)
      }
    })
  }, Promise.resolve(true))
}

uploadOneByOne([true, true, true, false, true], uploadImg).then(console.log)

上述方法缺点是不能提前跳出

循环队列的思考和实现

传统方式实现队列, 借助数组的 pop 和 unshift 方法

js
function Queue() {
  var _data = []
  this.enQueue = function(value) {
    _data.push(value)
  }
  this.deQueue = function() {
    return _data.unshift()
  }
}

这种方法的弊端是每次入队出队都需要挪动整个队列。 考虑引入头部指针和尾指针

js
function Queue() {
  var _data = []
  var head = 0,
    tail = 0
  this.enQueue = function(value) {
    _data[head++] = value
  }
  this.deQueue = function() {
    if (_data.length === 0) {
      return new Error("队列为空")
    } else {
      return _data[tail++]
    }
  }
}

这种策略空间换时间的方案,长时间使用后会溢出。所以有循环队列的方案

js
function Queue(initLength) {
  var _data = new Array(initLength),
    tail = -1,
    head = -1,
    length = 0
  dataLength = initLength
  this.enQueue = function(value) {
    console.log(value, head, tail)
    if (length === dataLength) {
      // 溢出
      this.resetQueue()
    }
    head = (head + 1) % dataLength
    _data[head] = value
    length++
  }
  this.deQueue = function() {
    if (length === 0) {
      throw new Error("empty")
    }
    tail = (tail + 1) % dataLength
    length--
    var result = _data[tail]
    _data[tail] = undefined
    return result
  }
  this.resetQueue = function() {
    // 扩容
    dataLength = dataLength * 2
    _data.length = dataLength
    // 如果
    if (head < tail) {
      var headList = _data.splice(0, head + 1)
      _data.splice(tail, 0, ...headList)
    }
  }
  this.toString = function() {
    console.log(_data)
  }
}

动态的去扩大长度,还有 deQueue 应该动态减少长度

经典算法-洗牌算法

js
function shuffleArray(arr) {
  var len = arr.length
  while (len--) {
    var index = Math.floor(Math.random() * len)
    ;[arr[index], arr[len]] = [arr[len], arr[index]]
  }
  return arr
}

位运算详解

与或非异或

判断是否为奇数 n & 1n | 0 === n n ^ n === 0 n ^ 0 === n~~表示向 0 取整

js
function power(n) {
  return 1 << n
}

n >>> 0 的含义

toNumber(不能转成 number 的变成 1) => 整数(~~n) => toUint32(负数 + 2 的 30 次方)

GC 那些事儿

  • 引用计数法. 对每个开辟的空间进行引用计数, 缺点是回带来内存溢出(循环引用的情况)
  • 标记回收法:从根开始,递归的标记能访问的对象,标记完成后,去虽有未标记的对象进行回收

常见的内存泄露情况及处理方案

  1. 被遗忘的定时器
  2. 意外的全局变量(使用严格模式)
  3. 脱离 dom 的引用
  4. 闭包

脱离的 dom 的引用举例

html
<script>
  let div = document.getElementById("fa")
  document.body.removeChild(div) // dom删除了
  //div=null          //切断div对div的引用
  console.log(div)
</script>

一行代码实现 flat 函数

js
function flat(arr) {
  return [].concat.apply([], arr)
}