likes
comments
collection
share

构建前端知识体系 —— 手写代码篇

作者站长头像
站长
· 阅读数 22

目录

GitHub Pages 在线访问最新:我的前端态度 —— 前端知识体系

JavaScript 必写

  • 数组排序(多种方法)
  • 数组去重(多种方法)
  • 数组扁平化
  • 将数组扁平化并去除其中重复数据,最终得到一个升序且不重复的数组
  • 深拷贝
  • 函数防抖节流
  • 实现函数原型方法 call apply bind
  • 实现数组原型方法 forEach map filter some reduce
  • 实现 Promise
  • 实现 Promise.all
  • 实现 Promise.allSettled
  • 封装事件类 EventEmitter(发布订阅模式)

JavaScript 编码能力

  • 函数柯里化
  • 函数记忆
  • 对象比较
  • 计算字符串中出现最多的字母与出现次数
  • 实现一个 trim 方法
  • js 实现一个函数 获得 url 参数的值
  • 驼峰转下划线:appleOrangePinkBoy => apple_orange_pink_boy
  • 实现一个 get 方法通过.来取对象的值
  • 封装一下 axios 或者手写封装 ajax
  • 实现一个 once 函数,传入函数参数只执行一次
  • 实现一个简单的模板字符串替换
  • 合并对象 merge
  • 求多个数组之间的交集
  • 实现一个版本比较方法 compareVersion

JavaScript 必写

数组排序(多种方法)

// 冒泡排序
// 冒泡排序的原理如下:
// 从第一个元素开始,把当前元素和下一个元素进行比较。如果当前元素大,那么就交换位置,重复操作直到比较到最后一个元素,那么此时(一轮结束后)最后一个元素就是该数组中最大的数
// 下一轮重复以上操作,但是此时最后一个元素已经是最大数了,所以不需要再比较最后一个元素,只需要比较到 length - 1 -i  的位置
function bubble(array) {
  for (let i = 0; i < array.length - 1; i++) {
    console.log('第' + (i + 1) + '轮开始')
    let flag = true
    // 从 0 到 `length - 1` 遍历
    for (let j = 0; j < array.length - 1 - i; j++) {
      if (array[j] > array[j + 1]) {
        flag = false
        ;[array[j], array[j + 1]] = [array[j + 1], array[j]]
        console.log('第' + (j + 1) + '次:' + array.toString(array))
      }
    }
    if (flag) {
      console.log('第' + (i + 1) + '轮后数据结束变化更新')
      break
    }
  }
  return array
}

console.log(bubble([3, 2, 1, 4, 8, 6, 7]))
// 第1轮开始
// 第1次:2,3,1,4,8,6,7
// 第2次:2,1,3,4,8,6,7
// 第5次:2,1,3,4,6,8,7
// 第6次:2,1,3,4,6,7,8
// 第2轮开始
// 第1次:1,2,3,4,6,7,8
// 第3轮开始
// 第3轮后数据结束变化更新

// 时间复杂度是 O(n * n)
// http://www.ruanyifeng.com/blog/2011/04/quicksort_in_javascript.html
// 快排
// 快排的原理如下:
//  第一步,选择中间的元素作为"基准"。(基准值可以任意选择,但是选择中间的值比较容易理解。)
//  第二步,按照顺序,将每个元素与"基准"进行比较,形成两个子集,一个"小于基准",另一个"大于基准"。
//  第三步,对两个子集不断重复第一步和第二步,直到所有子集只剩下一个元素为止。
function quickSort(arr) {
  if (arr.length <= 1) {
    return arr //递归出⼝
  }
  var pivotIndex = Math.floor(arr.length / 2)
  // 取出基准值
  var pivot = arr.splice(pivotIndex, 1)
  var left = []
  var right = []
  for (var i = 0; i < arr.length; i++) {
    if (arr[i] < pivot) {
      left.push(arr[i])
    } else {
      right.push(arr[i])
    }
  }
  return quickSort(left).concat(pivot, quickSort(right))
}
// 该算法的复杂度和归并排序是相同的,但是额外空间复杂度比归并排序少,只需 O(logN),并且相比归并排序来说,所需的常数时间也更少。
console.log(quickSort([3, 2, 1, 4, 8, 6, 7]))
// 原地快排
function quickSort(arr, low = 0, high = arr.length - 1) {
  if (low >= high) return
  let left = low
  let right = high
  let flag = arr[left]
  // 判断左右游标是否重合,如果重合,循环结束
  while (left < right) {
    // 右边大,继续向左比较
    if (flag <= arr[right]) {
      right--
    }
    // 交换下一个可能不合理的位置
    arr[left] = arr[right]
    // 左边大,继续向右比较
    if (flag >= arr[left]) {
      left++
    }
    // 交换下一个
    arr[right] = arr[left]
  }
  //重合之后,交换基准数
  arr[left] = flag
  quickSort(arr, low, left - 1)
  quickSort(arr, left + 1, high)

  return arr
}
console.log(quickSort([4, 3, 8, 1, 9, 6, 2]))

数组去重(多种方法)

//github.com/mqyqingfeng/Blog/issues/27

var array = [1, 2, 1, 1, '1']

;(() => {
  var unique = a => [...new Set(a)]
  console.log(unique(array)) // [1, 2, "1"]
})()
;(() => {
  function unique(array) {
    var res = []
    for (var i = 0, len = array.length; i < len; i++) {
      var current = array[i]
      if (res.indexOf(current) === -1) {
        res.push(current)
      }
    }
    return res
  }

  console.log(unique(array)) // [1, 2, "1"]
})()
;(() => {
  function unique(array) {
    var res = []
    for (var i = 0, len = array.length; i < len; i++) {
      var current = array[i]
      if (res.indexOf(current) === -1) {
        res.push(current)
      }
    }
    return res
  }

  console.log(unique(array)) // [1, 2, "1"]
})()
;(() => {
  function unique(array) {
    var res = array.filter(function(item, index, array) {
      //若当前元素所在的索引位置 === 在原始数组中出现该值的的第一个索引,则 true 返回当前元素
      return array.indexOf(item) === index
    })
    return res
  }

  console.log(unique(array))
})()
;(() => {
  function unique(array) {
    var o = new Map()
    // set 设置后返回新的 Map 对象
    return array.filter(key => !o.has(key) && o.set(key, true))
  }
  console.log(unique(array))
})()

数组扁平化

let entry = [[1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 10]

// 实现 Array.prototype.flat()
// https://github.com/mqyqingfeng/Blog/issues/36

;(() => {
  function flatten(arr) {
    let res = []
    for (let i = 0; i < arr.length; i++) {
      let item = arr[i]
      if (Array.isArray(item)) {
        res = res.concat(flatten(item))
      } else {
        res.concat(item)
      }
    }
    return res
  }
  console.log(flatten(arr)) // [1, 2, 2, 3, 4, 5, 5, 6, 7, 8, 9, 11, 12, 12, 13, 14, 10]
})()
;(() => {
  function flatten(arr) {
    return arr.reduce((prev, curr) => {
      return prev.concat(Array.isArray(curr) ? flatten(curr) : curr)
    }, [])
  }
  console.log(flatten(arr))
})()
;(() => {
  function flatten(arr) {
    while (arr.some(item => Array.isArray(item))) {
      arr = [].concat(...arr)
    }
    return arr
  }
  console.log(flatten(arr))
})()

ES6 增加了扩展运算符,用于取出参数对象的所有可遍历属性,拷贝到当前对象之中:

var arr = [1, [2, [3, 4]]]
console.log([].concat(...arr)) // [1, 2, [3, 4]]

将数组扁平化并去除其中重复数据,最终得到一个升序且不重复的数组

Array.prototype.flat = function() {
  return [].concat(...this.map(item => (Array.isArray(item) ? item.flat() : [item])))
}

Array.prototype.unique = function() {
  return [...new Set(this)]
}

const sort = (a, b) => a - b

console.log(
  arr
    .flat()
    .unique()
    .sort(sort)
) // [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 ]

深拷贝

用 JSON,存在如下缺点:

  • 不支持 Date、正则、undefined、函数等数据
  • 不支持引用(即环状结构)
const deepClone = o => JSON.parse(JSON.stringify(o))

基础版(新增函数函数类型支持),推荐使用 lodash 的深拷贝函数

function deepCopy(target) {
  if (typeof target == 'object') {
    const result = Array.isArray(target) ? [] : {}
    for (const key in target) {
      if (typeof target[key] == 'object') {
        result[key] = deepCopy(target[key])
      } else {
        result[key] = target[key]
      }
    }
    return result
  } else if (typeof target == 'function') {
    return eval('(' + target.toString() + ')')
    // 也可以这样克隆函数
    // return new Function("return " + target.toString())();
  } else {
    return target
  }
}

递归完整版本

要点:

  1. 递归
  2. 判断类型
  3. 不拷贝原型上的属性
  4. 检查环
const deepClone = (o, cache) => {
  if (!cache) {
    cache = new Map()
  }
  if (o instanceof Object) {
    if (cache.get(o)) {
      return cache.get(o)
    }
    let result

    if (o instanceof Function) {
      // 有 prototype 就是普通函数
      if (o.prototype) {
        result = function() {
          return o.apply(this, arguments)
        }
      } else {
        result = (...args) => {
          return o.call(undefined, ...args)
        }
      }
    } else if (o instanceof Array) {
      result = []
    } else if (o instanceof Date) {
      return +new Date(o)
    } else if (o instanceof RegExp) {
      result = new RegExp(o.source, o.flags)
    } else {
      // 最后是普通对象
      result = {}
    }
    // ! 只要拷贝过下次就不要拷贝了
    cache.set(o, result)
    for (const key in o) {
      if (o.hasOwnProperty(key)) {
        result[key] = deepClone(o[key], cache)
      }
    }
    return result
  } else {
    // string、number、boolean、undefined、null、symbol、bigint
    return o
  }
}
const a = {
  number: 1,
  bool: false,
  str: 'hi',
  empty1: undefined,
  empty2: null,
  array: [
    { name: 'frank', age: 18 },
    { name: 'jacky', age: 19 }
  ],
  date: new Date(2000, 0, 1, 20, 30, 0),
  regex: /\.(j|t)sx/i,
  obj: { name: 'frank', age: 18 },
  f1: (a, b) => a + b,
  f2: function(a, b) {
    return a + b
  }
}
a.self = a
var b = deepClone(a)
console.log(b)
console.log(b.self === b)

函数防抖节流

防抖与节流函数是一种最常用的 高频触发优化方式,能对性能有较大的帮助。

  • 节流(throttle): 每隔一段时间后执行一次,也就是降低频率,将高频操作优化成低频操作,通常使用场景: 滚动条事件 或者 resize 事件,通常每隔 100~500 ms 执行一次即可。
function throttle(fn, wait = 500, immediate) {
  let timer = null
  let callNow = immediate
  return function() {
    let context = this,
      args = arguments
    if (callNow) {
      fn.apply(context, args)
      callNow = false
    }
    if (!timer) {
      timer = setTimeout(() => {
        fn.apply(context, args)
        timer = null
      }, wait)
    }
  }
}
  • 防抖 (debounce): 将多次高频操作优化为只在最后一次执行,通常使用的场景是:用户输入,只需再输入完成后做一次输入校验即可。
function debounce(fn, wait = 1500, immediate) {
  let timer = null
  return function() {
    let args = arguments
    let context = this
    if (immediate && !timer) {
      fn.apply(context, args)
    }
    if (timer) clearTimeout(timer)
    timer = setTimeout(() => {
      fn.apply(context, args)
    }, wait)
  }
}

实现函数原型方法 call apply bind

// https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/apply
// https://github.com/mqyqingfeng/Blog/issues/11

Function.prototype.call__fake = function(context) {
  context = context || window
  let fn = this
  let exec = Symbol('fn')
  let args = [].slice.call(arguments, 1)
  // 将函数设为对象上的临时属性,函数内 this 保证正确
  context[exec] = fn
  // 执行函数,存储返回结果
  const result = context[exec](...args)
  // 删除临时变量
  delete context[exec]
  return result
}

var value = 2

var obj = {
  value: 1
}

function bar(name, age) {
  console.log(this.value)
  return {
    value: this.value,
    name: name,
    age: age
  }
}

bar.call__fake(null) // 2

console.log(bar.call__fake(obj, 'kevin', 18))
// 1
// {value: 1, name: 'kevin', age: 18}
// https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/call
// https://github.com/mqyqingfeng/Blog/issues/11
Function.prototype.apply__fake = function(context) {
  context = context || window
  let fn = this
  let exec = Symbol('fn')
  let args = arguments[1] || []
  // 将函数设为对象的属性
  context[exec] = fn
  const result = context[exec](...args)
  delete context[exec]
  return result
}

var value = 2

var obj = {
  value: 1
}

function bar(name, age) {
  console.log(this.value)
  return {
    value: this.value,
    name: name,
    age: age
  }
}

bar.apply__fake(null) // 2

console.log(bar.apply__fake(obj, ['kevin', 18]))
// 1
// {value: 1, name: 'kevin', age: 18}
// https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
Function.prototype.bind__fake = function(context) {
  context = context || window
  let fn = this
  let exec = Symbol('fn')
  let args = [].slice.call(arguments, 1)
  // 将函数设为对象上的临时属性,函数内 this 保证正确
  context[exec] = fn
  return function() {
    // 执行函数,存储返回结果
    const result = context[exec](...args)
    // 删除临时变量
    delete context[exec]
    return result
  }
}

var value = 2

var obj = {
  value: 1
}

function bar(name, age) {
  console.log(this.value)
  return {
    value: this.value,
    name: name,
    age: age
  }
}

bar.bind__fake(null)() // 2

console.log(bar.bind__fake(obj, 'kevin', 18)())
// 1
// {value: 1, name: 'kevin', age: 18}

实现数组原型方法 forEach map filter some reduce

// forEach  https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
;(() => {
  Array.prototype.forEach__fake = function(fn) {
    const array = this
    for (let index = 0; index < array.length; index++) {
      const element = array[index]
      fn.call(null, element, index, array)
    }
  }

  const array1 = ['a', 'b', 'c']

  array1.forEach__fake((element, i) => console.log(i, element))

  // expected output: 0 'a'
  // expected output: 1 'b'
  // expected output: 2 'c'
})()

// map https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/map
;(() => {
  Array.prototype.map__fake = function(fn) {
    let result = []
    const array = this
    for (let index = 0; index < array.length; index++) {
      const element = array[index]
      result[index] = fn.call(null, element, index, array)
    }
    return result
  }

  const array1 = [1, 4, 9, 16]

  // pass a function to map
  const map1 = array1.map__fake(x => x * 2)

  console.log(map1)
  // expected output: Array [2, 8, 18, 32]
})()
// filter https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
;(() => {
  Array.prototype.filter__fake = function(fn) {
    let result = []
    const array = this
    for (let index = 0; index < array.length; index++) {
      const element = array[index]
      fn.call(null, element, index, array) ? result.push(element) : ''
    }
    return result
  }
  const words = ['spray', 'limit', 'elite', 'exuberant', 'destruction', 'present']

  const result = words.filter__fake(word => word.length > 6)

  console.log(result)
  // expected output: Array ["exuberant", "destruction", "present"]
})()
// some  https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/some
;(() => {
  Array.prototype.some__fake = function(fn) {
    let flag = false
    const array = this
    for (let index = 0; index < array.length; index++) {
      const element = array[index]
      if (fn.call(null, element, index, array)) {
        flag = true
        break
      }
    }
    return flag
  }
  const array = [1, 2, 3, 4, 5]

  // checks whether an element is even
  const even = element => element % 2 === 0

  console.log(array.some__fake(even))
  // expected output: true
})()

// reduce https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce
;(() => {
  Array.prototype.reduce__fake = function(fn, initialValue) {
    let result = initialValue || 0
    const array = this
    for (let index = 0; index < array.length; index++) {
      const element = array[index]
      result = fn.call(null, result, element, index, array)
    }
    return result
  }
  const array1 = [1, 2, 3, 4]

  // 0 + 1 + 2 + 3 + 4
  const initialValue = 0
  const sumWithInitial = array1.reduce__fake(
    (previousValue, currentValue) => previousValue + currentValue,
    initialValue
  )

  console.log(sumWithInitial)
  // expected output: 10
})()

实现 Promise

class Promise__fake {
  constructor(executor) {
    this.status = 'pending'
    this.handleFulfilled = [] // 存储成功后的回调
    this.handleRejection = [] // 存储失败后的回调
    // ! resolve 形参的实际参数在这儿
    const resolve = data => {
      // 状态变更只有一次
      if (this.status !== 'pending') {
        return
      }
      this.status = 'fulfilled'
      // ! 等一会,否则 handleFulfilled 为空
      setTimeout(() => {
        this.handleFulfilled.forEach(fn => fn(data))
      }, 0)
    }
    const reject = reason => {
      if (this.status !== 'pending') {
        return
      }
      this.status = 'rejected'
      setTimeout(() => {
        this.handleRejection.forEach(fn => fn(reason))
      }, 0)
    }
    try {
      executor(resolve, reject)
    } catch (e) {
      // 遇到错误时,捕获错误,执行 reject 函数
      reject(e)
    }
  }
  then(fulfilledFn, rejectedFn) {
    this.handleFulfilled.push(fulfilledFn)
    this.handleRejection.push(rejectedFn)
    return this
  }
}

//1. 链式测试
var p1 = new Promise__fake(function(resolve, reject) {
  console.log('init Promise')
  if (Math.random() > 0.5) {
    resolve('大')
  } else {
    reject('小')
  }
})
p1.then(
  data => console.log('success', data),
  reason => console.log('error', reason)
).then(
  () => console.log('success 2'),
  () => console.log('error 2')
)

// 2. 延时测试
var sleep = (time, data) =>
  new Promise__fake(function(resolve, reject) {
    setTimeout(resolve, time, data)
  })
sleep(3000, '时间到!').then(val => {
  console.log(val)
})

// 3. 状态变更后不可变
const p2 = new Promise__fake(function(resolve, reject) {
  resolve('失败了!')
  reject('还会成功吗!')
})
p2.then(
  data => console.log(data),
  reason => console.log(reason)
)

实现 Promise.all

;(() => {
  var promise1 = 41
  var promise2 = 42
  var promise3 = new Promise(function(resolve, reject) {
    setTimeout(resolve, 5000, 'foo')
  })
  var promise4 = new Promise(function(resolve, reject) {
    setTimeout(reject('[err]: 模拟错误'), 300)
  })

  function p1(time) {
    return new Promise(function(resolve, reject) {
      setTimeout(function() {
        resolve(time)
      }, time)
    })
  }

  // Promise 扩展
  Promise.all__fake = promiseAry => {
    return new Promise((resolve, reject) => {
      let resultAry = [],
        index = 0
      for (let i = 0; i < promiseAry.length; i++) {
        Promise.resolve(promiseAry[i])
          .then(result => {
            index++
            resultAry[i] = result
            if (index === promiseAry.length) {
              resolve(resultAry)
            }
          })
          .catch(reason => {
            reject(reason)
          })
      }
    })
  }

  Promise.all__fake([promise1, promise2, promise3]).then(function(values) {
    console.log(values) //  [41, 42, 'foo']
  })
  Promise.all__fake([promise4, promise2, promise3]).then(function(values) {
    console.log(values) // Uncaught (in promise) [err]: 模拟错误
  })
  Promise.all__fake([p1(5000), p1(1000)]).then(function(res) {
    console.log(res) //[5000,1000]
  })
})()

实现 Promise.allSettled

如果任意的 promise reject,则 Promise.all 整个将会 reject。当我们需要 所有 结果都成功时,它对这种“全有或全无”的情况很有用:Promise.allSettled 等待所有的 promise 都被 settle,无论结果如何。结果数组具有:

  • {status:"fulfilled", value:result} 对于成功的响应,
  • {status:"rejected", reason:error} 对于 error。
Promise.allSettled = function(promises) {
  const rejectHandler = reason => ({ status: 'rejected', reason })
  const resolveHandler = value => ({ status: 'fulfilled', value })
  const convertedPromises = promises.map(p => Promise.resolve(p).then(resolveHandler, rejectHandler))
  return Promise.all(convertedPromises)
}

封装事件类 EventEmitter(发布订阅模式)

class EventEmitter {
  constructor() {
    this._cache = {}
  }
  $on(type, callback) {
    if (this._cache[type]) {
      this._cache[type].push(callback)
    } else {
      this._cache[type] = [callback]
    }
    return this
  }
  $emit(type, data) {
    let fns = this._cache[type]
    if (Array.isArray(fns)) {
      fns.forEach(fn => {
        fn(data)
      })
    }
    return this
  }
  $off(type, callback) {
    let fns = this._cache[type]
    if (Array.isArray(fns) && callback) {
      this._cache[type] = fns.filter(event => {
        return event !== callback
      })
    }
    return this
  }
  $once(type, callback) {
    let that = this
    function func() {
      var args = Array.prototype.slice.call(arguments, 0)
      callback.apply(that, args)
      that.$off(type, func)
    }
    this.$on(type, func)
  }
}

var Event = new EventEmitter()
Event.$once('addAddress', function(address) {
  console.log(JSON.stringify(address, null, 2))
})

Event.$emit('addAddress', {
  location: '北京',
  longitude: '116°20′',
  latitude: '39°56′'
})
Event.$once('once', function(res) {
  console.log(JSON.stringify(res, null, 2))
})

Event.$emit('once', {
  content: '我希望只能执行一次'
})
Event.$emit('once', {
  content: '我希望只能执行一次'
})
Event.$emit('once', {
  content: '我希望只能执行一次'
})
function mitt(all) {
  return {
    all: (all = all || new Map()),
    on(eventName, callback) {
      let cbs = all.get(eventName)
      ;(cbs && cbs.push(callback)) || all.set(eventName, [callback])
    },
    emit(eventName, ...args) {
      let cbs = all.get(eventName)
      if (cbs.length === 0) {
        console.error(`no find ${eventName} function.`)
        return
      }
      cbs.forEach(cb => cb(args))
    },
    off(eventName, callback) {
      let cbs = all.get(eventName)
      cbs &&
        all.set(
          eventName,
          cbs.filter(cb => cb !== callback)
        )
    }
  }
}

const eventHub = new mitt()

eventHub.on('click', console.log)

setTimeout(() => {
  eventHub.emit('click', 'yanyue404')
  eventHub.off('click', console.log)
}, 1500)

setTimeout(() => {
  eventHub.emit('click', '1024')
}, 1500)

JavaScript 编码能力

函数柯里化

const add = (x, y, z) => x + y + z
const curry = fn => {
  const fnLength = fn.length
  return function curried(...args) {
    if (args.length === fnLength) {
      return fn.apply(null, args)
    } else {
      return function(...reset) {
        return curried.apply(null, args.concat(reset))
      }
    }
  }
}
const curriedAdd = curry(add)

const result = curriedAdd(1)(2)(3)
const result2 = curriedAdd(1, 2, 3)
const result3 = curriedAdd(1, 2)(3)
console.log('result', result) // result 6
console.log('result2', result2) // result2 6
console.log('result3', result3) // result3 6

函数记忆

// github.com/mqyqingfeng/Blog/issues/46
https: var memoize = function(func, hasher) {
  var memoize = function(key) {
    var cache = memoize.cache
    var address = '' + (hasher ? hasher.apply(this, arguments) : key)
    if (!cache[address]) {
      cache[address] = func.apply(this, arguments)
    }
    return cache[address]
  }
  memoize.cache = {}
  return memoize
}
var add = function(a, b, c) {
  return a + b + c
}
// 自定义存储 key
var memoizedAdd = memoize(add, function() {
  var args = Array.prototype.slice.call(arguments)
  return JSON.stringify(args)
})

console.log(memoizedAdd(1, 2, 3)) // 6
console.log(memoizedAdd(1, 2, 4)) // 7

// 适用场景:需要大量重复的计算,或者大量计算又依赖于之前的结果
;(() => {
  var count = 0
  var fibonacci = function(n) {
    count++
    return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2)
  }
  for (var i = 0; i <= 10; i++) {
    fibonacci(i)
  }

  console.log('优化前:' + count) // 453
})()
;(() => {
  var count = 0
  var fibonacci = function(n) {
    count++
    return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2)
  }

  fibonacci = memoize(fibonacci)

  for (var i = 0; i <= 10; i++) {
    fibonacci(i)
  }

  console.log('优化后:' + count) // 12
})()

函数组合

/**
 * 执行从右到左的功能组合
 */
function compose(...funcs) {
  return function(result) {
    let list = funcs.slice()
    while (list.length > 0) {
      // 从列表中取第一个函数并执行
      result = list.pop()(result)
    }
    return result
  }
}
/**
 * 执行从左到右的功能组合
 */
function pipe(initialValue, ...funcs) {
  return funcs.reduce((preValue, curFunc) => curFunc(preValue), initialValue)
}

对象比较

function isObject(value) {
  var type = typeof value
  return value != null && (type == 'object' || type == 'function')
}
/**
 * form vue
 * Check if two values are loosely equal - that is,
 * if they are plain objects, do they have the same shape?
 */
function looseEqual(a, b) {
  if (a === b) return true
  const isObjectA = isObject(a)
  const isObjectB = isObject(b)
  if (isObjectA && isObjectB) {
    try {
      const isArrayA = Array.isArray(a)
      const isArrayB = Array.isArray(b)
      if (isArrayA && isArrayB) {
        return (
          a.length === b.length &&
          a.every((e, i) => {
            return looseEqual(e, b[i])
          })
        )
      } else if (a instanceof Date && b instanceof Date) {
        return a.getTime() === b.getTime()
      } else if (!isArrayA && !isArrayB) {
        const keysA = Object.keys(a)
        const keysB = Object.keys(b)
        return (
          keysA.length === keysB.length &&
          keysA.every(key => {
            return looseEqual(a[key], b[key])
          })
        )
      } else {
        return false
      }
    } catch (e) {
      return false
    }
  } else if (!isObjectA && !isObjectB) {
    return String(a) === String(b)
  } else {
    return false
  }
}

计算字符串中出现最多的字母与出现次数

let str = 'configureyourdevicetousewhistleasitsHTTPandHTTPSproxyonIP'
let str2 = 'aabbcbc'

// o、e 出现了 5 次

function getMaxString(string) {
  const map = {}
  let max = 1
  let maxKeys = []
  for (let i = 0; i < string.length; i++) {
    let key = string[i]
    map[key] ? map[key]++ : (map[key] = 1)
    if (map[key] > max) {
      max = map[key]
      maxKeys = [key]
    } else if (map[key] === max) {
      maxKeys.push(key)
    }
  }

  console.log('最大值存在多个', maxKeys.join('、') + '出现了 ' + max + '次')
  return [max, maxKeys]
}

getMaxString(str) // 5, ['e','o']
getMaxString(str2) // 3, ['b']

实现一个 trim 方法

// 删除左右的空格
const trim = s => s.replace(/(^\s+)|(\s+$)/g, '')
// 删除所有的空格
const trimAll = s => s.replace(/\s/g, '')
const greeting = '   Hello world!   '

console.log(trim(greeting)) // Hello world!
console.log(trimAll(greeting)) // Helloworld!

js 实现一个函数 获得 url 参数的值

  1. http://www.domain.com/?user=anonymous&id=123&id=456&city=%E5%8C%97%E4%BA%AC&d&enabled解析为如下格式:
{
   user: 'anonymous',
   id: [123, 456], // 重复出现的 key 要组装成数组,能被转成数字的就转成数字类型
   city: '北京', // 中文
   enabled: true, // 未指定值的 key 约定值为 true
}
let url = `http://www.domain.com/?user=anonymous&id=123&id=456&city=%E5%8C%97%E4%BA%AC&d&enabled`

function getQueryJson(url = '') {
  let json = {}
  url = decodeURIComponent(url || location.href)
  if (typeof url !== 'string') return json
  let splits = url.split('?')
  if (splits && splits.length >= 2) {
    let paramsArr = splits[1].split('&')
    if (paramsArr && paramsArr.length > 0) {
      json = paramsArr.reduce((o, item) => {
        const [key, value] = item.split('=')
        if (key && !o[key]) {
          o[key] = value === undefined ? true : value
        } else {
          if (!Array.isArray(o[key])) {
            o[key] = [o[key]].concat(value)
          } else {
            o[key] = o[key].push(value)
          }
        }
        return o
      }, {})
    }
  }

  return json
}
console.log(JSON.stringify(getQueryJson(url), null, 2))
/*  
    {
    user: "anonymous",
    id: ["123", "456"],
    city: "北京",
    d: true,
    enabled: true,
    }; */

驼峰转下划线:appleOrangePinkBoy => apple_orange_pink_boy

;(() => {
  let str = 'appleOrangePinkBoy'
  function underline(str) {
    // \B 非单词边界,左右占位的字符必须是 \w ([0-9a-zA-Z_])
    return str.replace(/\B([A-Z])/g, (m, p1) => `_${p1.toLowerCase()}`)
  }
  console.log(underline(str)) // apple_orange_pink_boy
})()
;(() => {
  let str = 'apple_orange_pink_boy'
  function decamelize(str) {
    return str.replace(/_(\w)/g, (m, p1) => p1.toUpperCase())
  }
  console.log(decamelize(str)) // appleOrangePinkBoy
})()

实现一个 get 方法通过.来取对象的值

const obj = { a: [{ b: { c: 3 } }] }
function get(obj, path, def) {
  // https://jex.im/regulex/#!flags=&re=%5B%5C.%5C%5B%5C%5D%5D%2B
  let chain = Array.isArray(path) ? path : path.split(/[\.\[\]]+/)
  let val = chain.reduce((prev, curr) => {
    if (prev) {
      return (prev = prev[curr])
    } else {
      return prev
    }
  }, obj)
  return val === undefined ? def : val
}
console.log(get(obj, 'a.b', false)) // false
console.log(get(obj, 'pop_act.pic.aaaaaaaaa')) // undefined
console.log(get(obj, 'pop_act.pic.aaaaaaaaa', false)) // false
console.log(get(obj, ['a', 'b', 'c'])) // undefined
console.log(get(obj, ['a', '0', 'b', 'c'])) // 3
console.log(get(obj, 'a[0].b.c')) // 3

封装一下 axios 或者手写封装 ajax

/**
 * 将参数对象转换为a=1&b=2字符串格式
 * @param {object} params 待转换的参数对象
 */
function stringify(params = {}) {
  let str = ''
  for (const key of Object.keys(params)) {
    str += `&${key}=${params[key]}`
  }
  return str.substring(1)
}
/**
 * ajax请求
 * @param {string} 请求链接
 * @param {string} 方法名,post|get
 * @param {object} 请求头
 * @param {params} 参数
 */
function ajax({ url = '', method = 'GET', headers = {}, params = {} } = {}) {
  return new Promise((resolve, reject) => {
    try {
      let xhr = new XMLHttpRequest()
      if (method === 'GET' && JSON.stringify(params) !== '{}') {
        url = url + '?' + stringify(params)
      } else if (method === 'POST') {
        xhr.open('POST', url, true)
        xhr.setRequestHeader('Content-type', 'application/json')
      }
      // true 为一个异步的请求
      xhr.open(method, url, true)
      for (const key of Object.keys(headers)) {
        xhr.setRequestHeader(key, headers[key])
      }
      xhr.send(method == 'POST' ? JSON.stringify(params) : '')
      xhr.onreadystatechange = function() {
        if (xhr.readyState == 4 && xhr.status == 200) {
          let ret = xhr.response
          try {
            ret = JSON.parse(ret)
          } catch (err) {
            console.log(err)
          }
          resolve(ret)
        } else if (xhr.readyState == 4 && xhr.status != 200) {
          reject(xhr.response)
        }
      }
    } catch (err) {
      reject(err)
    }
  })
}

实现一个 once 函数,传入函数参数只执行一次

function once(func) {
  var flag = true
  return function() {
    if (flag == true) {
      func.apply(null, arguments)
      flag = false
    }
    return undefined
  }
}

实现一个简单的模板字符串替换

const render = function(tpl, data) {
  // m 参数为 匹配的子串
  // p1 参数为 (.*?)的匹配结果
  return tpl.replace(/\{\{(.*?)\}\}/g, function(match, p1) {
    return data[p1.trim()]
  })
}

const text = render('我是{{ name}},年龄{{age}},性别{{sex}}', {
  name: 'yanyue404',
  age: 18,
  sex: '男'
})
console.log(text)
const tpl = "Hei, my name is <% name%>, and I'm <%age%> years old."

const render = (tpl, context) =>
  tpl.replace(/<%(.*?)%>/g, function(_, m) {
    return data[m.trim()]
  })

console.log(
  render(tpl, {
    name: 'Rainbow',
    age: '20'
  })
)

合并对象 merge

const merge = (obj, target = {}) => {
  Object.keys(obj).forEach(key => {
    if (isObject(obj[key])) {
      if (isObject(target[key])) {
        // 都是对象
        Object.keys(obj[key]).forEach(prop => {
          target[key][prop] = obj[key][prop]
        })
      } else {
        // target不是对象 直接重写
        if (target[key]) {
          target[key] = {
            [key]: target[key],
            ...obj[key]
          }
        } else {
          target[key] = obj[key]
        }
      }
    } else {
      if (isObject(target[key])) {
        target[key] = {
          ...target[key],
          [key]: obj[key]
        }
      } else {
        target[key] = obj[key]
      }
    }
  })
  return target
}
const obj1 = {
  pid: 1,
  signup: '注册',
  menu: '菜单',
  headers: {
    common: 'common'
  }
}
const obj2 = {
  pid: 2,
  signup: {
    sin: 2
  },
  menu: {
    id: 1
  },
  headers: {
    test: 123
  }
}
const result = merge(obj1, obj2)
// {
//   pid: 2,
//   signup: { signup: '注册', sin: 2 },
//   menu: { menu: '菜单', id: 1 },
//   headers: { common: 'common', test: 123 }
// }
console.log(result)

求多个数组之间的交集

let array1 = [1, 2, 3, 4, 5, 6, 7, 7]
let array2 = [2, 3, 4, 5, 6, 7, 7, 8, 9]
let array3 = [4, 5, 6, 7, 7, 8, 9]
let array4 = []

function intersection(...args) {
  return Array.from(
    new Set(
      args.reduce((prev, curr) => {
        return prev.filter(item => curr.includes(item))
      })
    )
  )
}
console.log(intersection(array1, array3, array3)) // [4,5,6,7]
console.log(intersection(array4)) // []

实现一个版本比较方法 compareVersion

//   compareVersion("1.11.0", "1.9.9"); // 1
function compareVersion(currVersion, comparedVersion) {
  if (currVersion && comparedVersion) {
    let v1 = currVersion.split('.')
    let v2 = comparedVersion.split('.')
    const len = Math.max(v1.length, v2.length)

    while (v1.length < len) {
      v1.push('0')
    }
    while (v2.length < len) {
      v2.push('0')
    }

    for (let i = 0; i < len; i++) {
      const num1 = parseInt(v1[i])
      const num2 = parseInt(v2[i])

      if (num1 > num2) {
        return 1
      } else if (num1 < num2) {
        return -1
      }
    }
    return 0
  }
  return -1
}

参考链接