JS面试常见手撕 算法

目录

1.浅拷贝和深拷贝的实现原理

2.apply,call,bind的实现原理

3.防抖节流函数实现原理。

4.new关键字的实现原理


1.浅拷贝和深拷贝的实现原理

  • 浅拷贝:简单复制了变量的值(包括基本数据类型的值和引用数据类型的值都是直接复制)
  • 深拷贝:基本数据类型的值直接复制,引用数据类型的值则根据这个引用数据类型对象生成一个新的对象,再将新对象的地址重新赋值。

区别:对于引用数据类型变量的值,浅拷贝直接将地址值复制过来,而深拷贝则是将原对象重新生成一个新对象,再将新对象的地址值复制过来。故浅拷贝的引用数据类型的变量指向的是同一个对象,而深拷贝则是分别指向不同的对象,改变其中一个对象不会影响另外一个对象。

    // 对象数组的深拷贝
    function deepClone(current) {
        if (current === null) {
            throw("请输入非空对象引用")
        }
        if (current instanceof Object) {
            // 判断为对象还是数组
            const target = ((current instanceof Array) ? [] : {})
            // 遍历对象属性或者数组下标
            for (let porperty in current) {
                // 遍历对象自身属性而非原型身上的属性
                if (current.hasOwnProperty(property)) {
                    // 判断是基本数据类型还是引用数据类型还是基本数据类型中的null
                    if (current[property] === null) {
                        target[property] = current[property]
                    } else if (typeof current[property]==="obj") {
                        target[property]=deepClone(current[property])
                    }else{
                        target[property] = current[property]
                    }
                }
            }
            return target
        } else {
            throw("请输入数组或者对象")
        }
    }
    // 对象数组的浅拷贝
    function shallowClone(current) {
        if (current === null) {
            throw("请输入非空对象引用")
        }
        if (current instanceof Object) {
            const target = ((current instanceof Array) ? [] : {})
            for (let porperty in current) {
                if (current.hasOwnProperty(property)) {
                        target[property] = current[property]
                }
            }
            return target
        } else {
            throw("请输入数组或者对象")
        }
    }

2.apply,call,bind的实现原理

注意:都是函数Function的原型上的方法,供函数实例使用。

相同点:三者都可以改变函数中的this指向

区别:call可以接受多个参数,apply只能接受两个参数,且如果有第二个参数则必须是数组形式的,且call和apply的第一个参数都是改变后this的指向对象,后面的参数将作为实参传递给原函数执行。。bind与call和apply有些不一样,bind是返回一个改变了this的函数,而接受参数的方式和call类似,只不过返回的函数我们同样也可以传递多个参数,都作为实参传递给原函数执行。

Function.prototype.call=function(obj,...arg){
    // 如果传入null默认是window对象
    current=current||window
    //将函数保存在穿进来的obj对象身上,谁调用call,函数实例调用call,那么this就是那个函数实例
    obj.p=this
    // 执行函数,这个时候函数的this就是obj了
    let result=obj.p(...arg)
    // 将在obj对象身上的添加的函数属性删除
    delete obj.p
    // 返回函数的执行结果
    return result
}
Function.prototype.apply=function(obj,arg){
    // 如果传入null默认是window对象
    current=current||window
    //将函数保存在穿进来的obj对象身上,谁调用call,函数实例调用call,那么this就是那个函数实例
    obj.p=this
    // 执行函数,这个时候函数的this就是obj了
    let result=obj.p(...arg)
    // 将在obj对象身上的添加的函数属性删除
    delete obj.p
    // 返回函数的执行结果
    return result
}
Function.prototype.bind=function(obj,...arg){
    // 保存this,返回的函数可以new操作,故返回的函数不能new,所以要保存this变量
    let that=this
    return function(...arg1){
        // 闭包obj,arg,that
        that.call(obj,...arg,...arg1)
    }

}

注意:

  • bind返回的函数可以通过new的形式调用,故不能通过箭头函数的形式返回,所以这个时候里面的this就失效了,故在外面要保存this的值。

3.防抖节流函数实现原理。

防抖(debounce):事件触发过于频繁,只要执行最后一次事件的回调函数的操作

节流(throttle):事件触发过于频繁,控制事件的执行次数。

    // 节流函数
    function throttle(fun, delay) {
        // 闭包flag
        // 为什么这里要用到闭包,不然每次执行事件,
        // 都会执行下面的函数,都会出现一个新的falg=true,这样就没有任何意义了
        let falg = true
        return function () {
            if (flag) {
                setTimeout(() => {
                    // 事件回调函数的this是指向触发事件的那个对象的
                    fun.call(this)
                    flag = true
                }, delay)
            }
            flag = false
        }
    }
    // 防抖函数
    function debounce(fun, delay) {
        let timer
        // 为什么这里要用到闭包,不然每次执行事件,都会执行下面的函数,都会出现一个新的timer
        return function () {
            if (timer) {
                clearInterval(timer)
            }
            timer = setTimeout(() => {
                fun.call(this)
            }, delay)
        }
    }

注意:

  • 防抖和节流里面都用到了闭包,一个是定时器的timer,一个是标记变量flag,如果在返回中的变量中定义这两个变量的话,那么每次都会对这两个变量进行初始化赋值,这样我们就不知道上一次操作中flag和timer的具体情况了,但是我们要判断上一次timer和flag的具体状态,然后来进行不同的操作。所以不能写在返回的函数中,应写在外部函数中,然后内部函数可以对其进行更改,下一次函数调用的时候还是保存的更改后的值,这就是闭包的利用。
  • 返回的函数中的fun()执行的时候是window.fun()这样执行的,所以this肯定是window,但是里面的this应该是返回函数中的this,也是触发这个时间的对象,所以这里要用call改变fun函数的this指向。

4.new关键字的实现原理

知道原理之前我们先看一下new关键都实现了什么?

  1. 创建一个空对象
  2. 将空对象的隐式原型指向构造函数的显式原型
  3. 将空对象作为构造函数的上下文(改变this指向)
  4. 对构造函数做有返回值的处理判断
    // 其中fun为构造函数
    function Mynew(fun,...arg) {
        // 创建一个空对象
        let obj = {}
        // 将空对象的隐式原型指向构造函数的显式原型
        Object.setPrototypeOf(obj,fun.prototype)
        // 将空对象作为构造函数的上下文(改变this指向)
        let result=fun.apply(obj,arg)
        // 对构造函数做有返回值的处理判断tance
        return result instanceof Object?result:obj
    }

注意:

  • 在返回对象的时候内部做了一个判断,如果构造函数return的是基本数据类型(包括null),那么对返回的结果没有任何影响,如果返回的是一个对象的话,那么则返回的结果则变成了我们自己返回的这个对象(包括数组,对象,函数)。
  • 此处设置原型用的是Object的静态方法setPrototypeOf(),Object还有一个静态方法getPrototypetyOf()用户获取原型对象。

非常感谢您的阅读,欢迎大家提出您的意见,指出相关错误,谢谢!越努力,越幸运!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值