JS基础面试题,持续更新中

137 阅读4分钟
  1. 实现call方法,思路如下:
const obj = {
    name:'zhangsan',
    getName(){
        console.log(this.name)
    }
}
obj.getName() // zhangsan

此时,我们使getName 内部this指向obj,根据这个思路,我们只需要在执行之后delete getName,具体代码如下:

Function.prototype.myCall = function(ctx){
    // 首先判断this,即需要改变this的方法对象是否为function
    if(typeof this !== 'function') throw new Error('Type err')
    
    // 是否传入指定this,没有指定则为window
    ctx = ctx || window
    
    // 获取传入的参数
    const args = [...arguments].slice(1)
    
    // 在ctx上添加fn,即为需要改变this指向的对象方法,避免fn重名,使用symbol类型
    const uniFn = Synbol()
    ctx[uniFn] = this
    
    // 将参数传入,执行fn
    const result = ctx[uniFn](...args)
    
    // 删除临时添加的fn
    delete ctx[uniFn]
    
    // 返回最终结果
    return result
}
  1. 实现apply方法,如上实现了call,基于call和apply的区别,即传入参数的结构不同,apply 传入的是数组,call传入多个参数,所以直接上代码:
Function.prototype.myApply = function(ctx){
    if(typeof this !== 'function') throw new Error('Type err')
    ctx = ctx || window
    const args = arguments[1]
    const uniFn = Synbol()
    ctx[uniFn] = this
    const result = ctx[uniFn](...args)
    delet ctx[uniFn]
    return result
}
  1. 实现bind方法,bind和apply的区别是bind返回一个方法,而不是直接返回执行结果,代码如下:
Function.prototype.myBind = function(ctx){
    if(typeof this !== 'function') throw new Error('Type err')
    ctx = ctx || window
    const args = [...arguments].slice(1)
    const uniFn = Symbol()
    ctx.[uniFn] = this
    const result = ()=> ctx.[uniFn](...args,...arguments)
    return result
}

4.非递归方式实现二叉树先序遍历

function preOrder(head){
    if(head!==null){
        console.log('satrt')
        // 利用栈先进后出的特性
        const stack = [head]
        while(stack.length){
            head = stack.pop()
            console.log(head.value)
            // 由于栈先进后出,所以先处理右节点,后处理左节点
            if(head.right) stack.push(head.right)
            if(head.left) stack.push(head.left)
        }
        console.log('end')
    }
}

5.非递归方式利用双栈实现二叉树后序遍历

function posOrder(head){
    if(head!==null){
        console.log('start')
        const s1 = [head]
        const s2 = []
        while(head){
            const head = s1.pop()
            s2.push(head)
            if(head.left) s1.push(head.left)
            if(head.right) s1.push(head.right)
        }
        while(s2.length){
            console.log(s2.pop().value)
        }
    }
}

6.非递归方式实现二叉树中序遍历

fonction inOrder(head){
    const stack = []
    while(head!==null || stack.length){
        if(head!==null){
            // 找到左节点的尽头
            stack.push(head)
            head = head.left
        }else{
            // 如果左节点为null,则回到head找到右节点,继续查找左节点
            head = stack.pop()
            // 处理节点
            console.log(head.value)
            head = head.right
        }
    }
}

7.非递归方式按层遍历二叉树

fonction layerOrder(head){
    // 利用队列先进先出特性,只要队列不为空,则出列打印,接着将下一个节点入列
    const queue = [head]
    while(queue.length){
        const cur = queue.shift()
        console.log(cur)
        if(cur.left) queue.push(cur.left)
        if(cur.right) queue.push(cur.right)
    }
}

8.实现防抖,支持立即执行

// 防抖的定义是在规定时间内连续做出同一个动作时,只执行最后一次或者执行第一次和最后一次,
// 例如获取验证码按钮一直点击,通常情况只需要最后一次触发生效
function debounce(fn, wait, immediate){
    // 利用闭包维护一个变量timer,debounce再次执行时,timer为上一次缓存的值
    let timer = null
    return function(){
        // 如果timer存在,则清除定时器,中止执行
        if(timer) clearTimeout(timer)
        // 暂存arguments与this
        const args = arguments
        const ctx = this
        if(immediate){
            // 如果需要立即执行
            let callNow = !timer
            // 在定时器里将timer置为null
            timer = setTimeout(()=> timer = null,wait)
            // 第一次触发的时候,timer为null,所以callNow为true,fn可立即执行,
            // 执行之后timer已经赋值定时器,规定时间内定时器内timer没有重新赋值为null,
            // 则不会再次执行fn
            callNow && fn.apply(ctx, args)
        }else{
            // 首次不立即执行
            timer = setTimeout(()=>{
                fn.apply(ctx, args)
            },wait)
        }
    }
}

9.实现节流(时间戳)

// 节流的定义是,在连续做出同一个动作时,在规定时间内只触发一次,例如监听页面的滚动以及
// 支持远程搜索的下拉选择
function throttle(fn, wait){
    // 初次为0,否则第一次不触发
    let preTime = 0 
    return function(){
        // 暂存arguments与this
        const args = arguments
        const ctx = this
        const now = new Date()
        if(now - preTime < wait) return 
        fn.apply(ctx, args)
        preTime = now
    }
}

10.实现节流(定时器)

function(fn, wait){
    let timer = null
    if(timer) cleartTimeout(timer)
    return function(){
        // 暂存arguments与this
        const args = arguments
        const ctx = this
        timer = setTimeout(()=>{
            fn.apply(ctx, args)
            timer = null
        })
    }
}

11.实现instanceof

// 首先得了解原型链的基本知识,instanceof 是判断origin是否在target的原型链上,即有一句话
// 这样描述,实例的__proto__指向构造函数的prototype,如果存在
// target.__proto__===origin.prototype,或者x=target.__proto__,
// x.__proto__===origin.prototype,即说明origin在target的原型链上,也就是
// target instanceof origin === true
// 综上,可以使用while实现
function myInstance(target, origin){
    // 由于js自身存在的一个遗留bug,即 typeof null === 'object',而target类型必须是
    // 引用类型的,所以需要处理
    if(typeof target !=='object'||target === null) return false
    if(typeof origin!=='function') return false
    
    // js 规范不建议直接使用.__proto__,所以用getPrototypeOf(target)代替
    let proto = Object.getPrototypeOf(target)
    // 利用while沿着原型链查找,知道原型链尽头null
    while(proto){
        if(proto === origin.prototype) return true
        // 一直找下去
        proto = Object.getPrototypeOf(proto)
    }
    return false
}