手写代码题

2021-05-16  本文已影响0人  翔子丶

深浅拷贝

深拷贝和浅拷贝值针对 Object 和 Array 这样的复杂类型

浅拷贝

只复制对象第一层属性,如果基本类型则“传值”,若为引用类型,则“传址”

实现方法

  1. Array.concat()、Array.slice()、Array.from()等

  2. Object.assign()

    如果对象的属性值为简单类型,就是浅拷贝

  3. es6 扩展运算符

    同 Object.assign()

深拷贝

深拷贝是逐级对目标对象进行复制,会在栈内存重新分配空间存储新对象

实现方法

  1. 通过 JSON 对象实现深拷贝(不考虑循环引用、Function、RegExp 的时候)

    1.无法实现对函数、RegExp 等特殊对象克隆 2.会将对象的 constructor 构造函数指向 Object 3.对象有循环引用时,会报错

  2. Object.assign()

    如果属性值为对象或其它引用类型,就是浅拷贝

  3. lodash cloneDeep()方法

  4. 递归方式实现深拷贝,案例

实现 new 方法

new 的过程中做了什么?
  1. 创建一个空的对象
  2. 给这个新对象构造原型链,链接到构造函数的原型对象上,从而新对象可以访问构造函数中的属性和方法
  3. 执行构造函数中的代码,为新对象添加属性
  4. 如果构造函数有返回值,则返回;否则默认返回新对象
自己写代码实现 new

案例地址

Object.create()
  1. 语法 Object.create(proto, [propertiesObject])
  2. 参数
    • proto:新建对象的原型对象,必须
    • propertiesObject:可选,添加到新建对象的可枚举属性,没指定则为 undefined
    • 返回值:指定原型对象上添加新属性后的对象
  3. Objcet.create() vs new Object()
    • new Object()通过构造函数创建对象,添加属性是在自身实例,Object.create()是继承对象,添加的属性是在原型上


      Object.create创建对象.jpg
    • new Object()与 Object.create(null)区别


      空对象区别.jpg

防抖与节流

防抖 debounce

函数防抖是函数短时间内连续触发时,在规定时间内,函数只会执行一次

节流 throttle

函数节流是短时间内大量触发同一时间,在函数执行一次之后,在指定的时间内不再被执行,直到过了这段时间才重新生效

可视化工具帮助理解

Vue 响应式原理

数据响应式原理
发布订阅者模式

vue 兄弟组件通信

/*
  vue自定义事件
  this.$emit('change')
  this.$on('change', () => {
    console.log('change')
  })
 */

class EventEmitter {
  constructor() {
    this.subs = Object.create(null)
  }
  $on(eventType, handler) {
    this.subs[eventType] = this.subs[eventType] || []
    this.subs[eventType].push(handler)
  }
  $emit(eventType) {
    if (this.subs[eventType]) {
      this.subs[eventType].forEach((handler) => {
        handler()
      })
    }
  }
}

let em = new EventEmitter()
em.$on('change', () => {
  console.log('change event1')
})
em.$on('change', () => {
  console.log('change event2')
})
em.$emit('change')
发布订阅者.jpg
观察者模式

观察者 watcher 和发布者 Dep 组成,事件发生时通过 notify 通知所有观察者

// 观察者模式是由具体目标调度,比如当事件触发,Dep 就会去调用观察者的方法,所以观察者模式的订阅者与发布者之间是存在依赖的。
class Dep {
  constructor() {
    this.subs = []
  }
  addSub(sub) {
    if (sub && sub.update) {
      this.subs.push(sub)
    }
  }
  notify() {
    this.subs.forEach((sub) => {
      sub.update()
    })
  }
}

class Watcher {
  update() {
    console.log('update')
  }
}

let dep = new Dep()
let watcher = new Watcher()
dep.addSub(watcher)
dep.notify()
观察者模式.jpg

call、apply、bind 模拟实现

注意
箭头函数没有自己的 this,通过 call 或 apply 方法调用时,只能传递参数不能绑定 this,第一个参数会被忽略

实现
// 模拟实现call
Function.prototype._call = function (context, ...list) {
  context = context || window
  // 获取调用_call的函数,就是this
  context.fn = this

  let result = context.fn(...list)
  delete context.fn
  return result
}
const value = 'hello'
const foo = {
  value: 'nihao',
}

function bar(...list) {
  console.log(this.value)
  console.log(list)
}

bar._call(foo, 1, 2, 3)
bar._call(null, 4, 5, 6)

// 模拟实现apply
Function.prototype._apply = function (context, arr) {
  context = context || window
  context.fn = this

  let result = context.fn(...arr)
  delete context.fn
  return result
}

bar._apply(foo, [1, 2, 3])
bar._apply(null, [4, 5, 6])

// 模拟实现bind
Function.prototype._bind = function (context, ...arr1) {
  context = context || window
  context.fn = this

  return function (...arr2) {
    return context.fn(...arr1, arr2)
  }
}

bar._bind(foo, 1, 2)(3, 4)
bar._bind(null, 5, 6)(7, 8)
bar._bind(null)(5, 6, 7, 8)
call+apply+bind.jpg

js 继承的多种实现方式和优缺点

寄生式继承.jpg

类型检测

如何判断
  1. typeof

    判断一个值属于哪种基本类型

    typeof null // 'object' 无法判定是否为 null
    typeof undefined // 'undefined'
    
    typeof {} // 'object'
    typeof [] // 'object'
    typeof (() => {}) // 'function'
    
  2. instanceof

    可以对引用类型进行判定,其原理就是测试构造函数的 prototype 是否出现在被检测对象的原型链上

    [] instanceof Array            // true
    {} instanceof Object         // true
    (()=>{}) instanceof Function   // true
    
    let arr = []
    arr instanceof Array    // true
    // nstanceof 仍然无法优雅的判断一个值到底属于数组还是普通对象 Array 属于 Object 子类型
    arr instanceof Object   // true
    
  3. Object.prototype.toString.call()

    该方法本质就是依托 Object.prototype.toString() 方法得到对象内部属性 [[Class]
    传入原始类型却能够判定出结果是因为对值进行了包装
    null 和 undefined 能够输出结果是内部实现有做处理

    Object.prototype.toString.call({}) // '[object Object]'
    Object.prototype.toString.call([]) // '[object Array]'
    Object.prototype.toString.call(() => {}) // '[object Function]'
    Object.prototype.toString.call('seymoe') // '[object String]'
    Object.prototype.toString.call(1) // '[object Number]'
    Object.prototype.toString.call(true) // '[object Boolean]'
    Object.prototype.toString.call(Symbol()) // '[object Symbol]'
    Object.prototype.toString.call(null) // '[object Null]'
    Object.prototype.toString.call(undefined) // '[object Undefined]'
    Object.prototype.toString.call(new Date()) // '[object Date]'
    Object.prototype.toString.call(Math) // '[object Math]'
    Object.prototype.toString.call(new Set()) // '[object Set]'
    Object.prototype.toString.call(new WeakSet()) // '[object WeakSet]'
    Object.prototype.toString.call(new Map()) // '[object Map]'
    Object.prototype.toString.call(new WeakMap()) // '[object WeakMap]'
    
实现一个类型判断函数
  1. 判断 null
  2. 判断基础类型
  3. 使用 Object.prototype.toString.call(target)来判断引用类型

先判断基础类型因为:虽然 Object.prototype.toString.call()能判断出某值为:number/string/boolean,但是其实在包装时是先将他们转为对象后再判断类型的,JS 包装类型和原始类型差别是使用 typepf 的值是 object

function getType(target) {
  // 处理null
  if (target === null) {
    return 'null'
  }
  // 判断不是基础类型
  const typeofTarget = typeof target
  if (typeofTarget !== 'object') {
    return typeofTarget
  }
  // 此时已经是引用类型 使用Object.prototype.toString.call(target)
  const template = {
    '[object Object]': 'object',
    '[object Array]': 'array',
    '[object Function]': 'function',
    // 包装类型
    '[object String]': 'object - string',
    '[object Number]': 'object - number',
    '[object Boolean]': 'object - boolean',
  }

  const typeStr = Object.prototype.toString.call(target)
  return template[typeStr]
}
模拟实现 instanceof

遍历左边变量的原型链,直到找到右边变量的 prototype,如果没有找到,返回 false

function instanceOf(left, right) {
  const proto = left.__proto__
  const prototype = right.prototype
  while (true) {
    if (proto === null) return false
    if (proto === prototype) return true
    proto = proto.__proto__
  }
}
let a = []
console.log(getType(a))
console.log(instanceOf(a, Array))
console.log(instanceOf(a, Object))

let b = {}
console.log(getType(b))
console.log(instanceOf(b, Array))
console.log(instanceOf(b, Object))

let c = () => {}
console.log(getType(c))
console.log(instanceOf(c, Array))
console.log(instanceOf(c, Object))

let d = ''
console.log(getType(d))
console.log(instanceOf(d, String))
console.log(instanceOf(d, Object))
模拟instanceof.jpg

数组处理

数组去重
数组扁平化

reduce + 递归实现

// 数组扁平化
const flatten = (arr, deep = 1) => {
  return arr.reduce((cur, next) => {
    return Array.isArray(next) && deep > 1
      ? [...cur, ...flatten(next, deep - 1)]
      : [...cur, next]
  }, [])
}

const arr = [1, [2], [3, [4]]]
console.log(flatten(arr, 1)) // [1, [2], [3, [4]]]
console.log(flatten(arr, 2)) // [1,2, [3, 4]]
console.log(flatten(arr, 3)) // [1,2, 3, 4]
数组扁平化处理.jpg
上一篇 下一篇

猜你喜欢

热点阅读