Skip to content
On this page

JS 常考题 ​

实现 Render 函数(JSON2DOM) ​

js
const domObj = {
  tag: 'DIV',
  attrs:{
  id:'app'
  },
  children: [
    {
      tag: 'SPAN',
      children: [
        { tag: 'A', children: [] }
      ]
    },
    {
      tag: 'SPAN',
      children: [
        { tag: 'A', children: [] },
        { tag: 'A', children: [] }
      ]
    }
  ]
}
// 把上诉虚拟Dom转化成下方真实Dom
<div id="app">
  <span>
    <a></a>
  </span>
  <span>
    <a></a>
    <a></a>
  </span>
</div>

Render 函数:

js
const _render = (vnode) => {
  // 数字转为文本类型
  if (typeof vnode === 'number') {
    vnode = String(vnode)
  }
  // 文本节点直接创建
  if (typeof vnode === 'string') {
    vnode = document.createTextNode(vnode)
  }
  // 创建 Dom 节点
  const dom = document.createElement(vnode)
  // 为 Dom 节点添加属性
  if (vnode.attrs) {
    Object.keys(vnode.attrs).forEach((key) => {
      const val = vnode.attrs[key]
      dom.setAttribute(key, val)
    })
  }
  // 递归遍历子节点
  vnode.children.forEach((child) => {
    const childDom = _render(child)
    dom.appendChild(childDom)
  })
  return dom
}

树形结构转列表 ​

js
const data = [
  {
    id: 1,
    text: '节点1',
    parentId: 0,
    children: [
      {
        id: 2,
        text: '节点1_1',
        parentId: 1
      }
    ]
  }
]

将上面的属性结构转化成列表结构

js
const treeToList = (tree) => {
  const res = []
  // 递归遍历所有子节点
  const dfs = (tree) => {
    tree.forEach((item) => {
      res.push(item)
      if (item.children) {
        dfs(item.children)
        delete item.children
      }
    })
  }
  dfs(tree)
  return res
}

列表转树形结构 ​

将下面的列表转化为树形结构:

js
let arr = [
    { id: 1, name: '部门1', pid: 0 },
    { id: 2, name: '部门2', pid: 1 },
    { id: 3, name: '部门3', pid: 1 },
    { id: 4, name: '部门4', pid: 3 },
    { id: 5, name: '部门5', pid: 4 },
    { id: 6, name: '部门6', pid: 0 },
]

解法一(时间复杂度:O(n2)):

双重 for 循环

js
function get_tree_On2(arr) {
  const list = []
  // 遍历所有节点
  arr.forEach((element) => {
    // 再次遍历所有节点,找到该节点的所有子节点
    const chiildren_arr = arr.filter((ele) => {
      return element.id === ele.pid
    })

    if (chiildren_arr.length > 0) {
      element.chiildren = chiildren_arr
    }

    if (element.pid === 0) {
      list.push(element)
    }
  })

  return list
}

解法二(时间复杂度:O(n)):

使用 hash 优化

js
const get_tree_On = (arr) => {
  const list = []
  const hashmap = {}

  for (let i = 0; i < arr.length; i++) {
    // 存储每个 id 下的子元素
    const { pid } = arr[i]
    const { id } = arr[i]

    // 用 hash 表为列表中的每一个节点做记录
    if (!hashmap[id]) {
      hashmap[id] = { children: [] }
    }

    // 存储每个 id 下的子元素,因为该节点可能已经被它的子节点建立了 hash 记录
    hashmap[id] = { ...arr[i], children: hashmap[id].children }

    if (pid === 0) {
      // pid 等于 0,说明没有父节点,直接 push
      list.push(hashmap[id])
    } else {
      if (!hashmap[pid]) {
        // hash 中没有父节点,则加入父节点的记录
        hashmap[pid] = {
          children: []
        }
      }
      // 将该节点 push 到父节点的 children 中
      hashmap[pid].children.push(hashmap[id])
    }
  }
  return list
}

实现 Array.flat() ​

js
Array.prototype._flat = function (deep = 1) {
  let res = []
  deep--
  for (const i of this) {
    if (Array.isArray(i) && deep > 0) {
      res = res.concat(i.flat(deep))
    } else {
      res = res.concat(i) // res.push(i)
    }
  }
  return res
}

手写 instanceOf ​

js
function myInstanceOf(left, right) {
  // 获取对象的原型
  let proto = Object.getPrototypeOf(left)
  // 获取构造函数的 prototype
  const { prototype } = right
  // 判断构造函数的 prototype 是否在对象的原型链上
  while (true) {
    // proto 为 null,说明未找到,直接返回 false
    if (!proto) {
      return false
    }
    if (proto === prototype) {
      return true
    }
    proto = Object.getPrototypeOf(proto)
  }
}

手写 arr.map() ​

arr.map() 方法返回一个新数组,其结果是该数组中的每个元素执行一个提供的函数后的返回值。

js
Array.prototype._map = function (fn) {
  const res = []
  for (let i = 0; i < this.length; i++) {
    res.push(fn(this[i], i, this))
  }
  return res
}

实现 lodash.get() ​

要求:

js
const obj = {
    a: {
        b: 123
    },
    arr: [
        {
            demo: 'demo'
        }
    ]
}
function getKey(obj, str) {
   
}
console.log(getKey(obj, 'a.b'));
console.log(getKey(obj, 'arr[0].demo'));

实现:

思路:传来的 key 可能有多种格式,包括 []、.等,首先要进行一个格式转换。

js
function _get(obj, path, defaultValue = undefined) {
  // 首先进行格式的统一
  let newPath = []
  // 判断 path 是否为数组
  if (Array.isArray(path)) {
    newPath = path
  } else {
    newPath = path.replace(/\[/g, '.').replace(/\]/g, '').split('.')
  }
  // 获取路径后使用 [] 的格式逐个调用,这里可以使用 reduce
  return (
    newPath.reduce((pre, cur) => {
      return (pre || {})[cur] // 这里要用一个空对象,防止 pre 为 undefined 的情况报错
    }, obj) || defaultValue
  )
}

const object = { 'a': [{ 'b': { 'c': 3 } }] }
 
console.log(_get(object, 'a[0].b.c'))

深拷贝的实现 ​

js
// 方法一
function _deepCopy_1(obj) {
  return JSON.parse(JSON.stringify(obj))
}
// 方法二
function _deepCopy_2(obj) {
  // 当传入的参数不为 Object 类型时,直接返回
  if (obj === null || typeof obj !== 'object') {
    return obj
  }
  let newObj
  // 判断 obj 的类型,是数组还是对象
  if (Array.isArray(obj)) {
    newObj = []
  } else {
    newObj = {}
  }
  for (const key in obj) {
    newObj[key] = _deepCopy_2(obj[key])
  }
  return newObj
}

正则表达式模板字符串 ​

js
String.prototype.render = function (data) {
  return this.replace(/{{[.\s\S]*?}}/g, match => {
    // 匹配到的占位符
    if ((match = match.substring(2, match.length - 2).trim()) == "") {
      // 如果占位符为空,则返回空字符串
      return "";
    } else if (match.startsWith("#")) {
      // 如果占位符以"#"开头,则执行占位符内的表达式,并返回结果
      return eval(match.substr(1, match.length - 1));
    } else {
      // 否则,从提供的数据对象中获取占位符对应的值,并返回
      return data[match] ? data[match] : "";
    }
  });
};

实现发布订阅模式 ​

js
class EventEmitter {
  constructor() {
    this.events = {}
  }

  on(name, callback) {
    if (!this.events[name]) {
      this.events[name] = []
    }
    this.events[name].push(callback)
  }

  off(name, callback) {
    this.events[name].filter((element) => {
      return element !== callback
    })
  }

  once(name, callback) {
    const fn = (...args) => {
      callback(...args)
      this.off(name, fn)
    }
    this.on(name, fn)
  }
}

实现寄生组合式继承 ​

寄生组合式继承的具体实现步骤如下:

  1. 定义父类的构造函数
  2. 定义子类的构造函数
  3. 在子类的构造函数中调用父类的构造函数,以继承父类的实例属性;
  4. 使用 Object.create() 方法创建一个以父类原型对象为原型的新对象,并将其赋值给子类的原型对象
  5. 在子类的原型对象上添加子类特有的方法。
js
// 父类构造函数
function Parent(name, age) {
  this.name = name;
  this.age = age;
}
// 子类构造函数
function Son(name, age, skills) {
  // 子类构造函数中调用父类构造函数,以继承父类的实例属性
  Parent.call(this, name, age);
  this.skills = skills;
}
// 使用 Object.create() 方法创建一个以父类原型对象为原型的新对象,并将其赋值给子类的原型对象
Son.prototype = Object.create(Parent.prototype);
// 将子类原型对象的 constructor 属性指向子类构造函数 Son,以确保原型链上的构造函数引用正确。
Son.prototype.constructor = Son;