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)
}
}实现寄生组合式继承
寄生组合式继承的具体实现步骤如下:
- 定义父类的构造函数
- 定义子类的构造函数
- 在子类的构造函数中调用父类的构造函数,以继承父类的实例属性;
- 使用 Object.create() 方法创建一个以父类原型对象为原型的新对象,并将其赋值给子类的原型对象
- 在子类的原型对象上添加子类特有的方法。
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;
Blog of Desain