资讯专栏INFORMATION COLUMN

手写call、apply、bind及相关面试题解析

TwIStOy / 1357人阅读

摘要:我是前端我的全名是我是一个前端指向接收多个参数,第一个是返回值返回值是一个函数上下文的,不会立即执行。柯里化相关讲解请移步简版的实现就算完成了欢迎吐槽点赞

它们有什么不同?怎么用?

call 接收多个参数,第一个为函数上下文也就是this,后边参数为函数本身的参数。

        let obj = {
            name: "一个"
        }

        function allName(firstName, lastName) {
            console.log(this)
            console.log(`我的全名是“${firstName}${this.name}${lastName}”`)
        }
        // 很明显此时allName函数是没有name属性的
        allName("我是", "前端") //我的全名是“我是前端”  this指向window
        allName.call(obj, "我是", "前端") //我的全名是“我是一个前端” this指向obj

apply

apply接收两个参数,第一个参数为函数上下文this,第二个参数为函数参数只不过是通过一个数组的形式传入的。

allName.apply(obj, ["我是", "前端"])//我的全名是“我是一个前端” this指向obj
bind

bind 接收多个参数,第一个是bind返回值返回值是一个函数上下文的this,不会立即执行。

        let obj = {
            name: "一个"
        }

        function allName(firstName, lastName, flag) {
            console.log(this)
            console.log(`我的全名是"${firstName}${this.name}${lastName}"我的座右铭是"${flag}"`)
        }
        allName.bind(obj) //不会执行
        let fn = allName.bind(obj)
        fn("我是", "前端", "好好学习天天向上")

        // 也可以这样用,参数可以分开传。bind后的函数参数默认排列在原函数参数后边
        fn = allName.bind(obj, "你是")
        fn("前端", "好好学习天天向上")

接下来搓搓手实现call、apply和bind

实现call
      let Person = {
            name: "Tom",
            say() {
                console.log(this)
                console.log(`我叫${this.name}`)
            }
        }

        // 先看代码执行效果
        Person.say() //我叫Tom 
        Person1 = {
            name: "Tom1"
        }

        // 我们尝试用原生方法call来实现this指向Person1
        Person.say.call(Person1) //我叫Tom1

通过第一次打印执行和第二次打印执行我发现,如果Person1有say方法那么Person1直接执行Person1.say() 结果就是我是Tom1,是的call就是这么实现的。
再看代码

        Function.prototype.MyCall = function(context) {
            //context就是demo中的Person1
            // 必须此时调用MyCall的函数是say方法,那么我们只需要在context上扩展一个say方法指向调用MyCall的say方法这样this
            console.log(this)
            context.say = this //Mycall里边的this就是我们虚拟的say方法
            context.say()
        }
        // 测试
        Person.say.MyCall(Person1)//我叫Tom1


perfect!爆棚的满足感!不过拿脚趾头想想也不会这么简单,继续完善
我们自己找茬
1、call支持多个参数,有可能一个也不没有
2、考虑多参数时要把参数传给扩展方法。
3、给上下文定义的函数要保持唯一不能是say
4、扩展完我们需要吧自定义函数删除
接下来针对找茬问题一一解决

        let Person = {
            name: "Tom",
            say() {
                console.log(this)
                console.log(`我叫${this.name}`)
            }
        }
        Person1 = {
            name: "Tom1"
        }
        //如果没有参数
        Person.say.call()

没有指定this,this指向window

我们也要这样

        Function.prototype.MyCall = function(context) {
            // 如果没有参数我们参考call的处理方式
            context = context || window
                //context就是demo中的Person1
                // 必须此时调用MyCall的函数是say方法,那么我们只需要在context上扩展一个say方法指向调用MyCall的say方法这样this
            context.say = this //Mycall里边的this就是我们虚拟的say方法
            context.say()
        }

        Person.say.MyCall()


没毛病!
继续解决

// 找茬2:我们默认定义context.say = this  fn如果已经被占用 嘎嘎 sb了。 不怕 搞定它

        // say需要是一个唯一值 是不是突然想到es6的新类型 Symbol   fn = Symbol() 不过我们装逼不嫌事大 都说自己实现了

        function mySymbol(obj) {
            // 不要问我为什么这么写,我也不知道就感觉这样nb
            let unique = (Math.random() + new Date().getTime()).toString(32).slice(0, 8)
                // 牛逼也要严谨
            if (obj.hasOwnProperty(unique)) {
                return mySymbol(obj) //递归调用
            } else {
                return unique
            }
        }
//接下来我们一并把多参数和执行完删除自定义方法删除掉一块搞定
        Function.prototype.myCall1 = function(context) {
            // 如果没有传或传的值为空对象 context指向window
            context = context || window
            let fn = mySymbol(context)
            context.fn = this //给context添加一个方法 指向this
            // 处理参数 去除第一个参数this 其它传入fn函数
            let arg = [...arguments].slice(1) //[...xxx]把类数组变成数组,arguments为啥不是数组自行搜索 slice返回一个新数组
            context.fn(...arg) //执行fn
            delete context.fn //删除方法
        }
        
        let Person = {
            name: "Tom",
            say(age) {
                console.log(this)
                console.log(`我叫${this.name}我今年${age}`)
            }
        }

        Person1 = {
            name: "Tom1"
        }

        Person.say.call(Person1,18)//我叫Tom1我今年18

测试结果相当完美!

实现apply

接下来apply就简单多了,只有多参数时第二个参数是数组,就不一步步细说了。

        Function.prototype.myApply = function(context) {
            // 如果没有传或传的值为空对象 context指向window
            if (typeof context === "undefined" || context === null) {
                context = window
            }
            let fn = mySymbol(context)
            context.fn = this //给context添加一个方法 指向this
                // 处理参数 去除第一个参数this 其它传入fn函数
            let arg = [...arguments].slice(1) //[...xxx]把类数组变成数组,arguments为啥不是数组自行搜索 slice返回一个新数组
            context.fn(arg) //执行fn
            delete context.fn //删除方法

        }
实现bind

这个和call、apply区别还是很大的,容我去抽根烟回来收拾它
还是老套路先分析bind都能干些什么,有什么特点

    1、函数调用,改变this
    2、返回一个绑定this的函数
    3、接收多个参数
    4、支持柯里化形式传参 fn(1)(2)
    
       Function.prototype.bind = function(context) {
            //返回一个绑定this的函数,我们需要在此保存this
            let self = this
                // 可以支持柯里化传参,保存参数
            let arg = [...arguments].slice(1)
                // 返回一个函数
            return function() {
                //同样因为支持柯里化形式传参我们需要再次获取存储参数
                let newArg = [...arguments]
                console.log(newArg)
                    // 返回函数绑定this,传入两次保存的参数
                    //考虑返回函数有返回值做了return
                return self.apply(context, arg.concat(newArg))
            }
        }

        // 搞定测试
        let fn = Person.say.bind(Person1)
        fn()
        fn(18)

是的,完美,实现了绑定this,返回函数,不立即执行,可以柯里化形式传参。柯里化相关讲解请移步:https://segmentfault.com/a/11...

简版的实现就算完成了

欢迎吐槽or点赞!

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/101171.html

相关文章

  • 【周刊-1】三年大厂面试官-面试精选答案

    摘要:前言在阿里和腾讯工作了年,当了年的前端面试官,把期间我和我的同事常问的面试题和答案汇总在我的中。项目地址是我是小蝌蚪,腾讯高级前端工程师,跟着我一起每周攻克几个前端技术难点。 前言 在阿里和腾讯工作了6年,当了3年的前端面试官,把期间我和我的同事常问的面试题和答案汇总在我 Github 的 Weekly-FE-Interview 中。希望对大家有所帮助。 如果你在bat面试的时候遇到了...

    Bamboy 评论0 收藏0
  • 【周刊-1】三年大厂面试官-面试精选答案

    摘要:前言在阿里和腾讯工作了年,当了年的前端面试官,把期间我和我的同事常问的面试题和答案汇总在我的中。项目地址是我是小蝌蚪,腾讯高级前端工程师,跟着我一起每周攻克几个前端技术难点。 前言 在阿里和腾讯工作了6年,当了3年的前端面试官,把期间我和我的同事常问的面试题和答案汇总在我 Github 的 Weekly-FE-Interview 中。希望对大家有所帮助。 如果你在bat面试的时候遇到了...

    ThinkSNS 评论0 收藏0
  • 面试里的那些各种手写

    摘要:最近准备初级前端面试,发现有很多手写实现什么的,例如什么手写实现,。后面以这道题为引线面试官可能会追问什么是执行上下文的判断,的区别手写一个函数实现斐波那契数列首先拷一个阮神在他教程里的一个写法。 最近准备初级前端面试,发现有很多手写实现什么的,例如什么手写实现bind,promise。手写ajax,手写一些算法。翻阅了很多书籍和博客。 这里做一个总结改进,算是对我后面大概为期一个月找...

    wh469012917 评论0 收藏0
  • 22道JavaScript高频手写面试

    JavaScript笔试部分 点击关注本公众号获取文档最新更新,并可以领取配套于本指南的 《前端面试手册》 以及最标准的简历模板. 实现防抖函数(debounce) 防抖函数原理:在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。 那么与节流函数的区别直接看这个动画实现即可。 showImg(https://segmentfault.com/img/remote/146000002...

    Alan 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<