跳至主要內容

手写代码

Mr.LRH大约 4 分钟

手写代码

深度比较,模拟 lodash.isEqual

// 判断是否是对象或数组
function isObject(obj) {
  return typeof obj === 'object' && obj !== null
}

function isEqual(obj1, obj2) {
  if (!isObject(obj1) || !isObject(obj2)) {
    // 值类型(注意,参数 equal 的一般不会是函数)
    return obj1 === obj2
  }
  if (obj1 === obj2) {
    return true
  }
  // 两个都是对象或数组,而且不相等
  // 1. 先取出 obj1 和 obj2 的 keys,比较个数
  const obj1Keys = Object.keys(obj1)
  const obj2Keys = Object.keys(obj2)
  if (obj1Keys.length !== obj2Keys.length) {
    return false
  }
  // 2. 以 obj1 为基数,和 obj2 一次递归比较
  for (let key in obj1) {
    // 比较当前 key 的 val —— 递归
    const res = isEqual(obj1[key], obj2[key])
    if (!res) {
      return false
    }
  }
  // 全相等
  return true
}

call、apply 及 bind 函数

  • call(接受的是一个参数列表)

    Function.prototype.myCall = function(context) {
      if (typeof this !== 'function') {
        throw new TypeError('Error);
      }
      context = context || window;
      context.fn = this;
      const args = [...argumets].slice(1);
      const result = context.fn(...args);
      delete context.fn;
      return result;
    }
    
    • context为可选参数,如果不传的话默认上下文为 window
    • context 创建一个 fn 属性,并将值设置为需要调用的函数
    • call 可以传入多个参数作为调用函数的参数,所以需要将参数剥离出来
    • 然后调用函数并将对像上的函数删除
  • apply(接受的是一个包含多个参数的数组)

    Function.prototype.myApply = function (context) {
      if (typeof this !== 'function') {
        throw new TypeError('Error')
      }
      context = context || window
      context.fn = this
      let result
      // 处理参数和 call 有区别
      if (arguments[1]) {
        result = context.fn(...arguments[1])
      } else {
        result = context.fn()
      }
      delete context.fn
      return result
    }
    
  • bind(返回对应函数)

    Function.prototype.myBind = function (context) {
      if (typeof this !== 'function') {
        throw new TypeError('Error')
      }
      const _this = this
      const args = [...arguments].slice(1)
      // 返回一个函数
      return function F() {
        // 因为返回了一个函数,我们可以 new F(),所以需要判断
        if (this instanceof F) {
          return new _this(...args, ...arguments)
        }
        return _this.apply(context, args.concat(...arguments))
      }
    }
    

    bind 返回了一个函数,对于函数来说有两种方式调用:

    • 直接调用

      使用 apply 的方式实现,但对于参数需要注意一下情况:因为 bind 可以实现类似这样的代码 f.bind(obj, 1)(2),所以需要将两边的参数拼接起来,于是就有了这样的实现 args.concat(...arguments)

    • 通过 new 的方式

      对于 new 的情况来说,不会被任何方式改变 this,所以对于这种情况需要忽略传入的 this

字符串 trim 方法,保证浏览器兼容性

String.prototype.trim = function () {
  return this.replace(/^\s+/, '').replace(/\s+$/, '')
}

获取当前页面 url 参数

// 传动方式
function query(name) {
  const search = location.search.substr(1)
  const reg = new RegExp(`(^|&)${name}=([^&]*)(&|$)`, 'i')
  const res = search.match(reg)
  if (res === null) {
    return null
  }
  return res[2]
}

// URLSearchParams
function query(name) {
  const search = location.search
  const p = new URLSearchParams(seach)
  return p.get(name)
}

手写数组 flatern, 考虑多层级

function flat(arr) {
  // 验证arr中,还有没有深层数组 [1,2,[3,4]]
  const isDeep = arr.some(item => item instanceof Array)
  if (!isDeep) {
    return arr
  }
  const res = Array.prototype.concat.apply([], arr)
  return flat(res) // 递归
}

浅拷贝与深拷贝

和原数据是否指向同一对象第一层数据为基本数据类型,改变是否会使原数据改变原数据中包含子对象,改变是否会使原数据改变
赋值
浅拷贝不会
深拷贝不会不会
  • 浅拷贝:只拷贝第一层的原始类型值,和第一层的引用类型地址

    • 对象的 Object.assign:用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。
    • 数组的 Array.prototype.slice()Array.prototype.concat()
    • ES6 扩展运算符 ...
  • 深拷贝:拷贝所有的属性值,以及属性地址指向的值的内存空间

    • JSON.parse(JSON.stringify(object))

      局限性:会忽略 undefined、会忽略 symbol、不能序列化函数(会忽略函数)、不能解决循环引用的对象(会报错)

    • MessageChannel

      MessageChannel创建了一个通信的管道,这个管道有两个端口,每个端口都可以通过 postMessage 发送数据,而一个端口只要绑定了 onmessage 回调方法,就可以接收从另一个端口传过来的数据。

      局限性:不能包含函数,否则会报错

      // 注:该方法是异步,可以处理 undefined 和循环引用对象
      function structuralClone(obj) {
        return new Promise(resolve => {
          const { port1, port2 } = new MessageChannel()
          port2.onmessage = ev => resolve(ev.data)
          port1.postMessage(obj)
        })
      }
      
    • 简易版深拷贝和 lodash 的深拷贝函数

      实现深拷贝需要考虑很多边界情况,比如原型链如何处理、DOM 如何处理等等。可以使用 lodash 的深拷贝函数

      简易版深拷贝

      function deepClone(obj) {
        function isObject(o) {
          return (typeof o === 'object' || typeof o === 'function') && o !== null
        }
        if (!isObject(obj)) {
          throw new Error('非对象')
        }
        let isArray = Array.isArray(obj)
        let newObj = isArray ? [...obj] : { ...obj }
        // Reflect.ownKeys() 返回一个由目标对象自身的属性键组成的数组
        Reflect.ownKeys(newObj).forEach(key => {
          newObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key]
        })
        return newObj
      }
      

    注:深拷贝还需要考虑到 引用丢失递归爆栈 的问题

    • 引用丢失

      var b = {}
      var a = { a1: b, a2: b }
      a.a1 === a.a2 // true
      var c = clone(a) // 深拷贝
      c.a1 === c.a2 // false
      
    • 递归爆栈:当数据的层次很深是就会栈溢出

    参考:深拷贝的终极探索open in new window

上次编辑于:
贡献者: lrh21g