1、实现数组扁平化
写一个 JS 函数,实现数组扁平化,只减少一级嵌套
如输入 [1,[2,[3]],4],输出 [1,2,[3],4]
思路:
定义空数组 arr =[]。遍历当前数组
如果 item 非数组,则累加到 arr
如果 item 是数组,则遍历之后累加到 arr
export function flatten1(arr: any[]): any[] {
let res: any[] = []
arr.forEach(item=>{
if(Array.isArray(item)){
item.forEach(ele=>res.push(ele))
}else{
res.push(item)
}
})
return res
}
export function flatten2(arr: any[]): any[] {
let res: any[] = []
arr.forEach(item=>{
res = res.concat(item)
})
return res
}
2、获取类型
手写一个 getType 函数,传入任意变量,可准确获取类型
如 number string boolean 等值类型
还有 object array map regexp 等引用类型
常见类型判断
- typeof - 只能判断值类型,其他就是function和object
- instanceof- 需要两个参数来判断,而不是获取类型
- Object.prototype.toString.call(x)
export function getType(x:any):string{
const originType = Object.prototype.toString.call(x)//'[Object String]'
const spaceIndex = originType.indexOf(' ')// 5
const type = originType.slice(spaceIndex+1,-1)//'String'
return type.toLowerCase()//string
}
console.log(getType('test'))
3、new一个对象发生了什么?
- 创建一个空对象,继承构造函数的原型
- 执行构造函数(将obj作为this)
- 返回obj
{}和Object.create()有什么区别
4.遍历DOM树
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>requestAnimationFrame</title>
</head>
<body>
<p>template</p>
<div id="box">
<p>hello<b>world</b></p>
<img src="https://www.baidu.com/img/flexible/logo/pc/result.png" alt="">
<!-- 注释 -->
<ul>
<li>a</li>
<li>b</li>
</ul>
</div>
<script>
function visitNode(n){
if(n instanceof Comment){
// 注释
console.info('Comment node---',n.textContent)
}
if(n instanceof Text){
// 文本
var t = n.textContent?.trim()
if(t){
console.info('Text node---',t)
}
}
if(n instanceof HTMLElement){
// element
console.info('Element node---',`<${n.tagName.toLowerCase()}>`)
}
}
// 深度优先遍历
function depthFirstTraverse(root){
visitNode(root)
const childNodes = root.childNodes//.children和.childNodes不一样
if(childNodes.length){
childNodes.forEach(child => {
depthFirstTraverse(child)
})
}
}
// 广度优先遍历 采用队列方式,先进先出,层级遍历
function breadthFristTraverse(root){
const queue = []
// 根节点入队列
queue.unshift(root)
while(queue.length>0){
const curNode = queue.pop()
if(curNode===null) break
visitNode(curNode)
// 子节点入列
const childNodes = curNode.childNodes
if(childNodes.length){
childNodes.forEach(child=>{
queue.unshift(child)
})
}
}
}
const box = document.getElementById('box')
if(box===null) throw new Error('box is null')
breadthFristTraverse(box)
</script>
</body>
</html>
- 深度优先 ,递归 贪心
- 广度优先,使用队列(数组vs链表)
- children 和 childNodes 不同
5.手写LazyMan
class LazyMan{
private name:string
private tasks:Function []=[]//任务列表
constructor(name:string){
this.name = name
setTimeout(()=>{
this.next()
})
}
private next(){
const task = this.tasks.shift()//取出当前tasks的第一个任务
if(task) task()
}
eat(food:string){
const task = ()=>{
console.info(`${this.name} eat${food}`)
// 立刻执行下一个
this.next()
}
this.tasks.push(task)
return this//链式调用
}
sleep(second:number){
const task=()=>{
console.info(`${this.name}开始睡觉`)
setTimeout(()=>{
console.info(`${this.name}已经睡完了${second}s,可以开始执行下一个任务`)
this.next()
},second*1000)
}
this.tasks.push(task)
return this//链式调用
}
}
const me = new LazyMan('曲奇')
me.eat('西瓜').eat('橙子').eat('李子').sleep(2).eat('红烧鸡')
6.柯里化函数
export function curry(fn:Function){
const fnArgsLength = fn.length//传入函数的参数长度
let args:any[] = []
// ts中,独立的函数,需要声明类型
function calc(this:any,...newArgs:any[]){
// 积累参数
args = [
...args,
...newArgs
]
if(args.length<fnArgsLength){
// 参数不够,返回函数
return calc
}else{
// 参数够了,返回执行结果
return fn.apply(this,args.slice(0,fnArgsLength))
}
}
return calc
}
function add(a:number,b:number,c:number):number{
return a+b+c
}
const curryAdd = curry(add)
const res = curryAdd(10)(20)(30)
console.info(res)
7.instanceof 原理是什么?
例如:f instanceof Foo
顺着f._proto_向上查找(原型链)
看能否找到Foo.prototype
/**
* 自定义instanceof
* @param instance instance
* @param origin class or function
*/
export function myInstanceof(instance:any,origin:any):boolean{
if(instance===null) return false //null
const type = typeof instance
if(type!=='object'&&type!=='function'){
// 值类型
return false
}
let tempInstance = instance//防止修改instance
while(tempInstance){
if(tempInstance.__proto__=== origin.prototype){
return true//匹配上了
}
tempInstance = tempInstance.__proto__//顺原型往上找
}
return false
}
// 功能测试
console.info(myInstanceof({},Object))//true
console.info(myInstanceof([],Object))//true
console.info(myInstanceof([],Array))//true
8.手写bind
bind的应用
返回一个新函数,但不执行
绑定 this 和部分参数
如是箭头函数,无法改变 this,只能改变参数
// @ts-ignore
Function.prototype.customBind = function (context:any,...bindArgs:any[]){
// context 是bind传入的this
// bindArgs 是bind传入的各个参数
const self = this//当前函数本身
return function(...args:any[]){
// 拼接参数
const newArgs = bindArgs.concat(args)
return self.apply(context,newArgs)
}
}
// 功能测试
function fn(this:any,a:any,b:any,c:any){
console.log(this,a,b,c)
}
// @ts-ignore
const fn1 = fn.customBind({x:23},123,23,45)
fn1()
如何实现call、apply
分析:如何在函数执行时绑定this
- 如const obj = {x:100,fn(){this.x}}
- 执行obj.fn,此时fn内部的this指向obj
- 借此来实现绑定this
Function.prototype.customCall = function (context: any, ...args: any[]) {
if (context === null) context = globalThis//相当于window
if (typeof context !== 'object') context = new Object(context)//值类型,变对象
const fnKey = Symbol()//不会出现属性名称的污染
context[fnKey] = this//this就是当前的函数
const res = context[fnKey](...args)//绑定了this
delete context[fnKey]//清理掉fnkey,防止污染
return res
}
function fn(this:any,a:any,b:any,c:any){
console.info(this,a,b,c)
}
// @ts-ignore
fn.customCall({s:"城市"},1,3,4,5)
// @ts-ignore
Function.prototype.customApply = function (context: any, args: any[]) {
if (context === null) context = globalThis//相当于window
if (typeof context !== 'object') context = new Object(context)//值类型,变对象
const fnKey = Symbol()//不会出现属性名称的污染
context[fnKey] = this//this就是当前的函数
const res = context[fnKey](...args)//绑定了this
delete context[fnKey]//清理掉fnkey,防止污染
return res
}
9.手写EventBus自定义事件(事件总线)
- on和once注册函数,存储起来
- emit时找到对应函数,执行
- off找到对应函数,从对象中删除
class EventBus{
private events:{
[key:string]:Array<{fn:Function;isOnce:Boolean}>
}
constructor(){
this.events = {}
}
on(type:string,fn:Function,isOnce:Boolean=false){
const events = this.events
if(events[type]===null){
events[type] = []//初始化key的fn数组
}
events[type].push({fn,isOnce:isOnce})
}
once(type:string,fn:Function){
this.on(type,fn,true)
}
off(type:string,fn?:Function){
if(!fn){
// 解绑所有type的函数
this.events[type] = []
}else{
// 解绑单个事件
const fnList = this.events[type]
if(fnList){
this.events[type] = fnList.filter(ele=>ele.fn!==fn)
}
}
}
emit(type:string,...args:any[]){
const fnList = this.events[type]
if(fnList===null) return
// 注意
this.events[type] = fnList.filter(item=>{
const {fn,isOnce} = item
fn(...args)
// once执行一次就要被过滤掉
return !isOnce
})
}
}
10.用js实现LRU缓存
什么是LRU缓存
- LRU–Least Recently Used 最近使用
- 如果内存优先,只缓存最近使用的,删除“沉水”数据
- 核心API两个:get set
用哈希表存储数据,这样get set 才够快O(1)
必须是有序的,常用数据放在前面,“沉水”数据放在后面
哈希+有序,就是Map—其他都不行
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)){//map删除后,重新赋值,会到最新的排位
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
}
}
使用双向链表实现
双向链表:
interface IListNode {
value: any,
key: string//存储key,方便删除(否则删除时,需要遍历对象),
prev?: IListNode,
next?: IListNode
}
export default class LRUCache {
private length: number
private data: { [key: string]: IListNode } = {}
private dataLength: number = 0
private listHead: IListNode | null = null
private listTail: IListNode | null = null
constructor(length: number) {
if (length < 1) throw new Error('invalid length')
this.length = length
}
private moveToTail(curNode: IListNode) {
const tail = this.listTail
if(tail===curNode) return
// ---------- 1、让preNode nextNode断绝与curNode的关系 -----------
const preNode = curNode.prev
const nextNode = curNode.next
if(preNode){
if(nextNode){
preNode.next = nextNode
}else{
delete preNode.next
}
}
if(nextNode){
if(preNode){
nextNode.prev = preNode
}else{
delete nextNode.prev
}
if(this.listHead === curNode)this.listHead = nextNode
}
// ---------- 2、让curNode 断绝与pre next 的关系-----------
delete curNode.prev
delete curNode.next
// ---------- 3、在list末尾重新建立新的关系 -----------
if(tail){
tail.next = curNode
curNode.prev = tail
}
this.listTail = curNode
}
private tryClear(){
while(this.dataLength>this.length){
const head = this.listHead
if(head==null) throw new Error('head is null')
const headNext = head.next
if(headNext==null) throw new Error('headNext is null')
// 1.断绝head和next的关系
delete headNext.prev
delete head.next
// 2.重新赋值listhead
this.listHead = headNext
// 3.清理data
delete this.data[head.key]
// 4.重新计数
this.dataLength--
}
}
get(key: string): any {
const data = this.data
const curNode = data[key]
if (curNode == null) return null
if (this.listTail === curNode) {
// 本身就是在末尾(最新鲜的位置),直接返回value
return curNode.value
}
// 移动到末尾
this.moveToTail(curNode)
return curNode.value
}
set(key: string, value: any) {
const data = this.data
const curNode = data[key]
if (curNode == null) {
// 新增数据
const newNode:IListNode = {
value,key
}
// 移动到末尾
this.moveToTail(newNode)
data[key] = newNode
this.dataLength++
if(this.dataLength===1)this.listHead = newNode
} else {
// 修改现有数据
curNode.value = value
// 移动到末尾
this.moveToTail(curNode)
}
// 尝试清理
this.tryClear()
}
}
var lruCache = new LRUCache(4)
lruCache.set('1',1)
lruCache.set('2',2)
lruCache.set('3',3)
console.info(lruCache.get('2'),lruCache)
11.手写一个深拷贝、考虑Map,set,循环引用
使用JSON.stringify和parse
- 无法转换函数
- 无法转换Map、set
- 无法转换循环引用
使用Object.assign
- 不是深拷贝
普通深拷贝
- 只考虑Object Array
- 无法转换Map Set和循环引用
/**
* 深拷贝
* @param obj obj
* @param map weakmap为了避免循环引用
*/
export function cloneDeep(obj: any, map = new WeakMap()): any {
if (typeof obj !== 'object' || obj == null) return obj
// 避免循环引用
const objFromMap = map.get(obj)
if (objFromMap) return objFromMap
let target: any = {}
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 obj ={
a:function(){
return 'text'
},
map:new Map([['x',12],['y',20]]),
set:new Set([90,80,79]),
x:{w:100}
}
var obj1 = cloneDeep(obj)
obj.x.w = 200
console.info(obj,obj1)