Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
yanyue404 opened this issue Sep 26, 2019 · 0 comments
Open

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

yanyue404 opened this issue Sep 26, 2019 · 0 comments

Comments

@yanyue404
Copy link
Owner

yanyue404 commented Sep 26, 2019

目录

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
}

参考链接

@yanyue404 yanyue404 changed the title FE interview written test 前端基本功——笔试 Dec 13, 2019
@yanyue404 yanyue404 changed the title 前端基本功——笔试 前端基本功 —— 笔试 Dec 13, 2019
@yanyue404 yanyue404 changed the title 前端基本功 —— 笔试 前端基本功 —— 笔试篇 Mar 20, 2022
@yanyue404 yanyue404 changed the title 前端基本功 —— 笔试篇 构建前端知识体系 —— 手写代码篇 Dec 8, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant