当前位置: 首页 > news >正文

记一次面试题——call、apply、bind模拟实现的更好方式

写在前面

看有人分享的某公司面试题,用js闭包实现callapplybind

这是我没有考虑过的面试题,很好,它成功引起了我的兴趣。

很多人表示不理解,为什么面试题总出这些看上去毫无作用的实现。

面试题的意义

上次我写了一个promise模拟实现应该注意的点,也有人吐槽重复造轮子意义何在。

其实这就是理念的问题了,在一家重视基础的公司里,轮子不是会用就行,甚至你可以不需要会轮子,但是理念以及基础一定要过关,不然你写出来的隐患代码往往会给公司带来未知的风险。

(想起了上月某男装店网页上的bug,应该被不少人薅了羊毛 ?。)

分享的兄弟写出了自己的答案,解法中用了evalarguments,忘记return结果等等一些严重的问题。说实话,他给的答案我只能给5分(10分制),甚至让我看着有点难受。

醒醒吧,兄弟们,都9012年了,我们是否可以用js的新特性实现呢?

实现思考

方法容错:

一个合格的程序员,在写方法的时候第一点应该想到的是做好容错——当callapplybind传入第一个参数不是一个引用类型的时候,你应该要知道它的表现。

剩下的applybind测试结果一样。

所以第一步:

    Function.prototype.call = function(context) {
        context = context === undefined || context === null ? window : Object(context)
    }
    
    Function.prototype.apply = function(context) {
        context = context === undefined || context === null ? window : Object(context)
    }
    
    Function.prototype.bind = function(context) {
        context = context === undefined || context === null ? window : Object(context)
    }
复制代码

获取参数

接下来是获取参数,call多个参数用逗号分隔,apply参数作为数组传入,bind是逗号分隔,那么取未知个参数,你一定想到了arguments.

但是相比arguments获取参数,我们是否应该想想,在新特性中能够怎么做?

没错,就是rest参数

    Function.prototype.call = function(context, ...args) {
        context = context === undefined || context === null ? window : Object(context)
    }
    
    Function.prototype.apply = function(context, args) {
        context = context === undefined || context === null ? window : Object(context)
    }
    
    Function.prototype.bind = function(context, ...bindArgs) {
        context = context === undefined || context === null ? window : Object(context)
    }
复制代码

修改this指向

终于可以进入主题了,无论我们用callapply还是bind,唯一的目的就是修改函数内部的this指向。

仔细想想,在callapply方法中我们拿到的this应该就是原函数,所以把this赋值给context然后调用就改变了原函数的this指向。

当然不要直接简单的使用对象赋值,为了外部取不到这个属性,这里使用Symbol是合理的。

    Function.prototype.call = function(context, ...args) {
        context = context === undefined || context === null ? window : Object(context)
        const fn = Symbol('fn')
        context[fn] = this
        context[fn](...args)
    }
    
    Function.prototype.apply = function(context, args) {
        context = context === undefined || context === null ? window : Object(context)
          const fn = Symbol('fn')
        context[fn] = this
        context[fn](...args)
    }
    
    Function.prototype.bind = function(context, ...bindArgs) {
        context = context === undefined || context === null ? window : Object(context)
        const fn = Symbol('fn')
        context[fn] = this
        return function(...args) {
            context[fn](...bindArgs, ...args)
        }
    }

复制代码

最后一步

很明显,这里还有个问题就是修改了原对象上下文context以及函数没有返回值,所以我们应该return 结果以及调用完毕后删除多余的原函数引用,除了bind

bind作为一个特例,它的方法会返回一个新函数,如果这时候把原函数放到context上,我们不能删除它的原函数引用context._$fn,否则将在调用的时候报错。

所幸的是我们已经在上文中实现了callapply函数,在这里用上相得益彰,也表现出了你的封装思想。

那么修改一下bind方法(注意bind传入的参数在新函数传入的参数之前):

    Function.prototype.call = function(context, ...args) {
        context = context === undefined || context === null ? window : Object(context)
        const fn = Symbol('fn')
        context[fn] = this
        const result = context[fn](...args)
        delete context[fn]
        return result
    }
    
    Function.prototype.apply = function(context, args) {
        context = context === undefined || context === null ? window : Object(context)
        const fn = Symbol('fn')
        context[fn] = this
        const result = context[fn](...args)
        delete context[fn]
        return result
    }
    
    Function.prototype.bind = function(context, ...bindArgs) {
        context = context === undefined || context === null ? window : Object(context)
        return (...args) => this.apply(context, [...bindArgs, ...args])
    }
复制代码

完整的实现

    Function.prototype.call = function(context, ...args) {
        context = context === undefined || context === null ? window : Object(context)
        const fn = Symbol('fn')
        context[fn] = this
        const result = context[fn](...args)
        delete context[fn]
        return result
    }
    
    Function.prototype.apply = function(context, args) {
        context = context === undefined || context === null ? window : Object(context)
        const fn = Symbol('fn')
        context[fn] = this
        const result = context[fn](...args)
        delete context[fn]
        return result
    }
    
    Function.prototype.bind = function(context, ...bindArgs) {
        context = context === undefined || context === null ? window : Object(context)
        return (...args) => this.apply(context, [...bindArgs, ...args])
    }
复制代码

写在最后

面试造轮子? 不,这不叫造轮子,一个重复的轮子公司是否有需要你来写的必要? 而且放着社区这么多优秀作品不用,难道相信我们临时写的轮子么,公司也怕翻车。

包括我这里写的方法肯定有所遗漏(希望大家及时指正),这里真的只是为了考查你的js基础与编程思想。

所以大部分公司的面试题自有道理,希望大家少点吐槽,多学习下基础吧。(无力吐槽脸.jpg)

相关文章:

  • 逻辑运算符
  • 古郡敦煌迎新年初雪 雪漠风光引游人
  • 台湾大学生在威海研习中华文化 感叹收获太多“惊喜”
  • 如何使用 Druid 和 Kafka 构造 Kappa 架构完成流量分析
  • 利用位运算实现加减乘除
  • IT应该自动化的7件事
  • 陕西彬州一男子持刀杀害两名女性 警方发布协查通告
  • 圆方圆:python的错误处理——try语句
  • 洛谷 P1824 【进击的奶牛】
  • 自学自用 = B站(操作系统_清华大学(向勇、陈渝))1
  • Docker下部署自己的LNMP工作环境
  • Docker-01-使用镜像
  • C# 分割字符串
  • LDM和STM指令
  • java B2B2C Springcloud电子商城系统-搭建一个简单的Eureka程序
  • [译] 理解数组在 PHP 内部的实现(给PHP开发者的PHP源码-第四部分)
  • __proto__ 和 prototype的关系
  • 【跃迁之路】【585天】程序员高效学习方法论探索系列(实验阶段342-2018.09.13)...
  • 345-反转字符串中的元音字母
  • C语言笔记(第一章:C语言编程)
  • es6--symbol
  • HTTP请求重发
  • Linux gpio口使用方法
  • linux学习笔记
  • Netty 框架总结「ChannelHandler 及 EventLoop」
  • PermissionScope Swift4 兼容问题
  • React+TypeScript入门
  • ubuntu 下nginx安装 并支持https协议
  • vue2.0开发聊天程序(四) 完整体验一次Vue开发(下)
  • 动态魔术使用DBMS_SQL
  • 解决iview多表头动态更改列元素发生的错误
  • 使用Envoy 作Sidecar Proxy的微服务模式-4.Prometheus的指标收集
  • mysql 慢查询分析工具:pt-query-digest 在mac 上的安装使用 ...
  • 关于Kubernetes Dashboard漏洞CVE-2018-18264的修复公告
  • ​Spring Boot 分片上传文件
  • ###51单片机学习(1)-----单片机烧录软件的使用,以及如何建立一个工程项目
  • #14vue3生成表单并跳转到外部地址的方式
  • #162 (Div. 2)
  • #Spring-boot高级
  • #我与Java虚拟机的故事#连载09:面试大厂逃不过的JVM
  • (2)Java 简介
  • (3)Dubbo启动时qos-server can not bind localhost22222错误解决
  • (附源码)python房屋租赁管理系统 毕业设计 745613
  • (七)Java对象在Hibernate持久化层的状态
  • (四)c52学习之旅-流水LED灯
  • (算法)Game
  • (学习日记)2024.03.12:UCOSIII第十四节:时基列表
  • (原创)boost.property_tree解析xml的帮助类以及中文解析问题的解决
  • (转)创业家杂志:UCWEB天使第一步
  • (转)利用PHP的debug_backtrace函数,实现PHP文件权限管理、动态加载 【反射】...
  • .bat批处理(三):变量声明、设置、拼接、截取
  • .NET(C#、VB)APP开发——Smobiler平台控件介绍:Bluetooth组件
  • .Net环境下的缓存技术介绍
  • :not(:first-child)和:not(:last-child)的用法
  • @Builder用法