一些非算法前端代码题,面试中要是碰到,没练习过一时半会很可能出不来,出不来就等着凉凉了。
获取详细的数据类型
实现一个 getType 函数,传入一个变量,能准确的获取它的类型。
如 number string function object array map regexp 等。
function getType(x){
const originType = Object.prototype.toString.call(x) // '[obeject String]'
const spaceIndex = originType.indexOf(' ')
const type = originType.slice(spaceIndex + 1,-1)
return type.toLowerCase()
}
手写new
function myNew(constructor,...args){
//创建一个全新的对象,这个新对象会被执行原型连接,及继承 constructor 的原型
const obj = object.create(constructor.protype);
//这个新对象会绑定到函数调用的 `this`,及将 obj 作为 this ,执行 constructor ,传入参数
constructor.apply(obj,args);
//返回
return obj
}
lazyman
class LazyMan{
tasks = []
constructor(name){
this.name = name
setTimeout(() =>{
this.next()
})
}
next() {
const task = this.tasks.shift() // 取出当前 tasks 的第一个任务
if (task) task()
}
eat(food) {
const task = () =>{
console.log(`eat${food}`)
this.next() // 立刻执行下一个任务
}
this.tasks.push(task);
return this // 链式调用
}
sleep(seconds) {
const task = () => {
console.log(`${this.name} 开始睡觉`);
setTimeout(()=>{
console.log(`${this.name}醒来 ${seconds}s,执行下一个任务`)
this.next() // xx 秒之后再执行下一个任务
},seconds*1000)
}
this.tasks.push(task);
return this //链式调用
}
}
const me = new LazyMan('neos')
me.eat('苹果').eat('香蕉').sleep(2).eat('葡萄').eat('西瓜').sleep(2).eat('橘子')
curry 函数
function curry(fn){
const fnArgsLength = fn.length // 传入函数的参数长度
let args = []
return function calc(...newArgs){
args = [
...args,
...newArgs
];
if (args.length < fnArgsLength){
// 参数不够,返回函数
return calc
}else {
return fn.apply(this,args.slice(0,fnArgsLength))
}
}
}
function add(a, b ,c) {
return a + b + c
}
add(10, 20, 30) // 60
const curryAdd = curry(add)
const res = curryAdd(10)(20)(30) // 60
console.info(res)
手写bind
// context 是 bind 传入的 this
// bindArgs 是 bind 传入的各个参数
Function.prototype.myBind = function(context,...bindArgs){
const self = this // 当前的函数本身
return function(...args){
const newArgs = bindArgs.concat(args);
return self.apply(context,newArgs)
}
}
function fn( a, b, c) {
console.info(this, a, b, c)
}
const fn1 = fn.myBind({x: 100}, 10)
fn1(20, 30)
手写call
Function.prototype.myCall = function (context,...args){
if (content == null) context = globalThis;
if(typeof context !== 'object') context = new Object(context);
const fnKey = Symbol;// 不会出现属性名称的覆盖
context[fnKey] = this // this 就是当前的函数
const res = context[fnKey](...args) 绑定了 this
delete context[fnKey] //清理掉 fn ,防止污染
return res
}
手写apply
Function.prototype.myApply = function(context,args){
if(context == null) context = globalThis;
if(typeof context !=='object') context = newObject(context);
const fnKey = Symbol;
const context[fnKey] = this;
const res = context[fnKey](args);
delete context[fnKey]
return res;
}
Debounce
function debounce(func, delay = 200) {
let timeOut = null;
return function (...args) {
clearTimeout(timeOut);
timeOut = setTimeout(() => {
if (typeof func === 'function') {
func(...args);
}
}, delay);
};
}
Throttle
function throttle(fn, delay = 100) {
let timer = 0
return function () {
if (timer) return
timer = setTimeout(() => {
func(...args);
timer = 0
}, delay)
}
}
手写instanceof
function testInstancof(intance,origin){
if(instance == null) return false;
const type = typeof instance;
if(type !=='object' && type !== 'function') return false;
let tempInstance = instance;
While(tempInstance){
if(tempInstance.__proto === origin.prototype)
return true;
tempInstance = tempInstance.__proto__
}
return false
}
数组扁平化,使用 push
function flatten(arr){
const res = [];
arr.forEach(item =>{
if (Array.isArray(item)){
item.forEach(n => res.push(n))
} else {
res.push(item)
}
})
return res
}
reduce结合concat实现数组扁平化
const arr = [
[1,2],
[3,4],
[5,6]
].reduce((arr,cur) =>{
return acc.concat(cur)
},[])
数组深度扁平化,使用 push
function flattenDeep(arr) {
const res = []
arr.forEach(item => {
if (Array.isArray(item)) {
const flatItem = flattenDeep(item) // 递归
flatItem.forEach(n => res.push(n))
} else {
res.push(item)
}
})
return res
}
reduce结合concat数组深度扁平化
const arr3 = [\
[1, 2],\
[3, 4],\
[5, [7, [9, 10], 8], 6],\
];
const flatten = arr => {
arr.reduce(
(pre,cur) => {pre.concat(Array.isArray(cur)?flatten(cur):cur)},[])
}
console.log(flatten(arr3)); // [ 1, 2, 3, 4, 5, 7, 9, 10, 8, 6 ]
es6 flat(),flatMap()
es6.ruanyifeng.com/?search=fal…
[1, 2, [3, 4]].flat()
// [1, 2, 3, 4]
[1, [2, [3]]].flat(Infinity)
// [1, 2, 3]
数组转树(复杂度要考虑)
function convert(arr) {
// 用于 id 和 item 的映射
const idToTreeNode = new Map();
let result = [];
arr.forEach(item => {
idToTreeNode.set(item.id, item)
})
arr.forEach(item => {
const { id, name, parentId } = item
// 找到 parentNode 并加入到它的 children
const parentNode = idToTreeNode.get(parentId);
if (parentNode) {
if (parentNode.children == null) parentNode.children = []
parentNode.children.push(item)
}else{
// 如果他没有父亲就把他加入到我们首次声明的数组里
result.push(item);
}
})
return result;
}
const arr = [
{ id: 3, name: '部门C', parentId: 1 },
{ id: 4, name: '部门D', parentId: 2 },
{ id: 5, name: '部门E', parentId: 2 },
{ id: 6, name: '部门F', parentId: 3 },
{ id: 1, name: '部门A', parentId: 0 }, // 0 代表顶级节点,无父节点
{ id: 2, name: '部门B', parentId: 1 },
]
const tree = convert(arr)
console.info(tree)
经典的算法接上一题算是扩展,降低时间复杂度
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
例1:
输入:nums = [2,7,11,15],
target = 9 输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 返回 [0, 1] 。
例2:
输入: nums = [3,2,4],
target = 6 输出: [1,2]
例3:
输入: nums = [3,3],
target = 6 输出: [0,1]
function twoSum(nums,target){
const map = new Map();
for(let i=0,len = num.length;i<len;i++){
// 一层遍历,用 target 减去每一项,在 map 的 key 中寻找
if(map.has(target - num[i])){
// 存在则返回结果
return [map.get(target - num[i]),i]
}
// 不存在,则设置 map key 和 val
map.set(nums[i],i)
}
}
深度优先遍历(递归和不用递归)
let treeData=[
{
id:1,
children:[{
id:2
},{
id:3,
children:[
{
id:5
}
]
}]
},{
id:4
}
]
**递归**
function depthFirstTraverse(treeData) {
if (treeData.length>0) {
treeData.forEach(item => {
console.log(item.id)
item.children&&depthFirstTraverse(item.children) // 递归
})
}
}
**非递归,用栈**
function depthFirstTraverse(treeData) {
const stack = [];
//第一层压栈
Array.from(treeData).reverse().forEach(
item => stack.push(item)
)
while(stack.length>0){
const node = stack.pop();// 出栈
console.log(node.id);
const children = node.children;
if(children&&children.length>0){
//reverse 反顺序压栈
Array.from(children).reverse().forEach(
item => stack.push(item)
)
}
}
}
广度优先遍历
let treeData=[
{
id:1,
children:[{
id:2
},{
id:3,
children:[
{
id:5
}
]
}]
},{
id:4
}
]
function breadthFirstTraverse(treeData) {
const queue = []
// 第一层入队列
treeData.forEach(item =>queue.unshift(item));
while (queue.length > 0) {
const item = queue.pop()
if (item == null) break
console.log(item.id)
// 子节点入队
const children = item.children
if (children&&children.length) {
children.forEach(item => queue.unshift(item))
}
}
}
深拷贝
function cloneDeep(obj,map = new WeakMap()){
if (typeof obj !== 'object' || obj == null ) return obj;
// 避免循环引用
const objFromMap = map.get(obj);
if (objFromMap) return objFromMap;
let target = {};
map.set(obj,target);
// Map
if(obj instanceof Map){
target = new Map();
obj.forEach((v,k) => {
const v1 = cloneDeep(v,map);
const k1 = cloneDeep(k,map);
target.set(k1,v1)
})
}
// Set
if(obj instanceof Set){
target = new Set();
obj.forEach(v =>{
const v1 = cloneDeep(v,map);
target.add(v1);
})
}
//Array
if (obj instanceof Array) {
target = obj.map(item => cloneDeep(item, map))
}
//Object
for (const key in obj){
const val = obj[key];
const val1 = cloneDeep(val,map);
target[key] = val1;
}
return target
}
// 功能测试
const a = {
set: new Set([10, 20, 30]),
map: new Map([['x', 10], ['y', 20]]),
info: {
city: '北京'
},
fn: () => { console.info(100) }
}
a.self = a
console.log( cloneDeep(a) )
自定义事件
class EventBus {
/**
* {
* 'key1': [
* { fn: fn1, isOnce: false },
* { fn: fn2, isOnce: false },
* { fn: fn3, isOnce: true },
* ]
* 'key2': [] // 有序
* 'key3': []
* }
*/
constructor() {
this.events = {}
}
on(type, fn, isOnce) {
const events = this.events
if (events[type] == null) {
events[type] = [] // 初始化 key 的 fn 数组
}
events[type].push({ fn, isOnce })
}
once(type, fn) {
this.on(type, fn, true)
}
off(type, fn) {
if (!fn) {
// 解绑所有 type 的函数
this.events[type] = []
} else {
// 解绑单个 fn
const fnList = this.events[type]
if (fnList) {
this.events[type] = fnList.filter(item => item.fn !== fn)
}
}
}
emit(type, ...args) {
const fnList = this.events[type]
if (fnList == null) return
// 注意
this.events[type] = fnList.filter(item => {
const { fn, isOnce } = item
fn(...args)
// once 执行一次就要被过滤掉
if (!isOnce) return true
return false
})
}
}
// const e = new EventBus()
// function fn1(a: any, b: any) { console.log('fn1', a, b) }
// function fn2(a: any, b: any) { console.log('fn2', a, b) }
// function fn3(a: any, b: any) { console.log('fn3', a, b) }
// e.on('key1', fn1)
// e.on('key1', fn2)
// e.once('key1', fn3)
// e.on('xxxxxx', fn3)
// e.emit('key1', 10, 20) // 触发 fn1 fn2 fn3
// e.off('key1', fn1)
// e.emit('key1', 100, 200) // 触发 fn2
LRU
class LRUCache {
private length: number
private data: Map<any, any> = new Map()
constructor(length: number) {
if (length < 1) throw new Error('invalid length')
this.length = length
}
set(key: any, value: any) {
const data = this.data
if (data.has(key)) {
data.delete(key)
}
data.set(key, value)
if (data.size > this.length) {
// 如果超出了容量,则删除 Map 最老的元素
const delKey = data.keys().next().value
data.delete(delKey)
}
}
get(key: any): any {
const data = this.data
if (!data.has(key)) return null
const value = data.get(key)
data.delete(key)
data.set(key, value)
return value
}
}
// const lruCache = new LRUCache(2)
// lruCache.set(1, 1) // {1=1}
// lruCache.set(2, 2) // {1=1, 2=2}
// console.info(lruCache.get(1)) // 1 {2=2, 1=1}
// lruCache.set(3, 3) // {1=1, 3=3}
// console.info(lruCache.get(2)) // null
// lruCache.set(4, 4) // {3=3, 4=4}
// console.info(lruCache.get(1)) // null
// console.info(lruCache.get(3)) // 3 {4=4, 3=3}
// console.info(lruCache.get(4)) // 4 {3=3, 4=4}
Proxy用法(代理沙箱)
let defaultValue = {} // 子应用的沙箱容器
export class ProxySandbox{
constructor() {
this.proxy = null;
this.active()
} // 沙箱激活
active() {
// 子应用需要设置属性,
this.proxy = new Proxy(window, {
get(target, key) {
return defaultValue[key] || target[key]
},
set(target, key, value) {
defaultValue[key] = value
return true
}
}
)
}
// 沙箱销毁
inactive () {
defaultValue = {}
}
}