JS 手写题大汇总(二)--- 实现

72 阅读3分钟

数组转树

function arrayToTree(arr) {
  const map = new Map()
  const idMap = new Map()
  arr.forEach(i => {
    const ids = map.get(i.parentId) || []
    ids.push(i.id)
    map.set(i.parentId, ids)
    idMap.set(i.id, i)
  })
  return loop(map.get(''))
  function loop(ids, parent = []) {
    ids.forEach(id => {
      const cur = idMap.get(id)
      const next = map.get(id)
      const o = { ...cur }
      if (next) {
        o.children = []
        loop(next, o.children)
      }
      parent.push(o)
    })
    return parent
  }
}

// 测试用例
const arr = [
  { id: 2, label: '2222', parentId: 1 },
  { id: 1, label: '1111', parentId: '' },
  { id: 3, label: '3333', parentId: 2 },
  { id: 4, label: '4444', parentId: 2 },
  { id: 5, label: '5555', parentId: 4 },
  { id: 6, label: '6666', parentId: 3 },
  { id: 7, label: '7777', parentId: 6 },
  { id: 8, label: '8888', parentId: '' },
  { id: 9, label: '9999', parentId: 8 },
]

打印结果:[
  {
    "id": 1,
    "label": "1111",
    "parentId": "",
    "children": [
      {
        "id": 2,
        "label": "2222",
        "parentId": 1,
        "children": [
          {
            "id": 3,
            "label": "3333",
            "parentId": 2,
            "children": [
              {
                "id": 6,
                "label": "6666",
                "parentId": 3,
                "children": [
                  {
                    "id": 7,
                    "label": "7777",
                    "parentId": 6
                  }
                ]
              }
            ]
          },
          {
            "id": 4,
            "label": "4444",
            "parentId": 2,
            "children": [
              {
                "id": 5,
                "label": "5555",
                "parentId": 4
              }
            ]
          }
        ]
      }
    ]
  },
  {
    "id": 8,
    "label": "8888",
    "parentId": "",
    "children": [
      {
        "id": 9,
        "label": "9999",
        "parentId": 8
      }
    ]
  }
]

发布-订阅

class dep {
  constructor() {
    this.deps = []
  }
  on(type, fn) {
    if (!this.deps[type]) this.deps[type] = []
    this.deps[type].push(fn)
  }
  notify(type, ...args) {
    if (!this.deps[type]) throw new Error('不存在')
    this.deps[type].forEach(dep => dep.call(this, ...args))
  }
  off(type, fn) {
    const index = this.deps[type].findIndex(dep => dep === fn)
    if (index < 0) throw new Error('不存在')
    this.deps[type].splice(index, 1)
    if (this.deps[type].length === 0) {
      delete this.deps[type]
    }
  }
  once(type, fn) {
    const c = () => {
      fn()
      this.deps.off(type, c)
    }
    this.deps.on(type, c)
  }
}

双向绑定

const obj = {}
const input = document.querySelector('input')
Object.defineProperty(obj, 'value', {
  get() {
    return obj[value]
  },
  set(newValue) {
    if (obj[value] !== newValue) {
      obj[value] = newValue
      input.value = newValue
    }
  }
})

input.addEventListener('keyup', function(e) {
  obj.value = e.target.value
})

分片渲染

let total = 10000
let pageNum = 20
let curTotal = 0
const container = document.querySelector('.container')
loop()
function loop() {
  if (curTotal >= total) return
  let nextTotal = Math.min(curTotal + pageNum, total)
  for (let i = curTotal; i < nextTotal; i++) {
    const node = document.createElement('div')
    node.innerText = curTotal
    container.append(container)
  }
  curTotal = nextTotal
  requestAnimationFrame(loop)
}

LRU

class LRU {
  constructor(capacity) {
    this.map = new Map()
    this.capacity = capacity
  }
    
  get(key) {
    if (this.map.has(key)) {
      const temp = this.map.get(key)
      this.map.delete(key)
      this.map.set(key, temp)
      return temp
    } else return -1
  }
  
  put(key, value) {
    if (this.map.has(key)) {
      this.get(key)
    } else {
      this.map.set(key, value)
      if (this.map.size > capacity) {
        this.map.delete(this.map.keys().next().value)
      }
    }
  }
}

深拷贝

const SetTag = '[object Set]'
const MapTag = '[object Map]'
const ArrayTag = '[object Array]'
const ObjectTag = '[object Object]'

const NumberTag = '[object Number]'
const StringTag = '[object String]'
const BooleanTag = '[object Boolean]'
const DateTag = '[object Date]'
const SymbolTag = '[object Symbol]'
const ErrorTag = '[object Error]'
const RegExpTag = '[object RegExp]'
const deepTag = [SetTag, MapTag, ArrayTag, ObjectTag]

function getType(target) {
  return Object.prototype.toString.call(target)
}

function cloneOtherType(target, type) {
  const Ctor = target.constructor
  switch(type) {
    case NumberTag:
    case StringTag:
    case BooleanTag:
    case DateTag:
    case ErrorTag: return new Ctor(target)
    case SymbolTag: return Object(Symbol(target.description))
    case RegExpTag: return new Ctor(target.source, target.flags)
  }
}

function deepClone(target, map = new Map()) {
  if (typeof target === 'symbol') return Symbol(target.description)
  if (!target || typeof target !== 'object') return target
  
  const type = getType(target)
  if (!deepTag.includes(type)) return cloneOtherType(target, type)
  
  if (map.has(target)) return map.get(target)
  const res = new target.constructor()
  map.set(target, res)
  
  if (type === MapTag) {
    target.forEach((value, key) => res.set(key, deepClone(value)))
    return res
  }
  if (type === SetTag) {
    target.forEach(item => res.add(deepClone(item)))
    return res
  }
  
  Reflect.ownKeys(target).forEach(key => {
    res[key] = deepClone(target[key], map)
  })
  
  return res
}

// 测试用例
const target = {
  field1: 1,
  field2: undefined,
  field3: {
    child: 'child'
  },
  field4: [2, 4, 8],
  empty: null,
  map: new Map().set(1, 'map'),
  set: new Set().add('set'),
  bool: Boolean(true),
  num: new Number(1),
  str: new String(1),
  date: new Date(),
  objSymbol: Object(Symbol('2')),
  [Symbol('key')]: 'symbol',
  reg: /\d+de$/gm,
  error: new Error('error'),
  symbol: Symbol('1'),
}
target.target = target
console.log(deepClone(target))

懒加载

const imgs = document.querySelectorAll('.img')

// 视口高度
const clientHeight = window.innerHeight || document.documentElement.clientHeight || docuement.body.clientHeight
function lazyload() {
  // 滚动高度
  let scrollTop = window.pageYScroll || document.documentElement.scrollTop || document.body.scrollTop
  for (let i = 0; i < imgs.length; i++) {
    // 该图片顶部距离视口最下方的大小
    let y = clientHeight + scrollTop - imgs[i].offsetTop
    if (y > 0 && y < clientHeight + imgs[i].height) {
      // 使用之前定位到的 data 属性来替换 src
      img[i].src = imgs[i].getAttribute('data')
    }
  }
}
window.addEventListener('resize', lazyload)

简单路由跳转

class Router {
  constructor() {
    this.routes = {}
    this.freshRoute = this.freshRoute.bind(this)
    this.currentHash = '/'
    window.addEventListener('hash', this.freshRoute, false)
    window.addEventListener('hashchange', this.freshRoute, false)
  }
  
  freshRoute() {
    this.currentHash = window.location.hash.slice(1) || '/'
    this.routes[this.currentHash]()
  }
  
  storeRoute(path, cb) {
    this.routes[path] = cb || function(){}
  }
}

创建对象

1. new Object()
2. 字面量
3. 工厂模式
function createObj(name) {
  const obj = new Object()
  obj.name = name
  return obj
}

4. 构造函数
function Person(name) {
  this.name = name
}

5. 原型对象
function Person(){}
Person.prototype.name = 'vancats'

6. 混合模式
function Person(name) {
  this.name = name
}
Person.prototype.intr = function(){}

7. 动态混合
function Person(name) {
  this.name = name
  if (!Person.prototype.intr) {
    Person.prototype.intr = function(){}
  }
}

8. class
9. 稳妥构造函数
function Person(name) {
  let person = {}
  person.getName = function () { return name }
  person.setName = function (val) { name = val }
  return person
}

继承

// 父类
function Person(name) {
  this.name = name
}
Person.prototype.intr = function() {}

1. 寄生组合继承
function Student(name, age) {
  Person.call(this, name)
  this.age = age
}
Student.prototype = Object.create(Person.prototype)
Student.prototype.constructor = Student

2. class
class Student extends Person {
  constructor(name) {
    super(name)
  }
}

3. 原型链继承:父类实例变成子类的原型(无法向父类传参)
function Student() { }
Student.prototype = new Person()
Student.prototype.name = 'vancats'

4. 构造函数继承
function Student(name, age) {
  Person.call(this, name)
  this.age = age
}

5. 实例继承
function Student(name, age) {
  let person = new Person(name)
  person.age = age
  return person
}

6. 拷贝继承:无法获取父类不可 for in 的方法
function Student(name, age) {
  let person = new Person(name)
  for (var p in person) {
    Student.prototype[p] = person[p]
  }
  this.age = age
}

7. 组合继承
function Student(name, age) {
  Person.call(this, name)
  this.age = age
}
Student.prototype = new Animal()
Student.prototype.constructor = Student