面试常见问题之实现bind函数

前言:

原文首发于我的博客,说实话,这半年来在各大社区看别人分享的面试题中 bind 函数已经出现 n 多次了,这次准备详细探究下

首先让我们看看 mdn 对于 bind 函数的描述是什么

语法

fun.bind(thisArg[, arg1[, arg2[, ...]]])

参数

  • thisArg   当绑定函数被调用时,该参数会作为原函数运行时的 this 指向。当使用 new 操作符调用绑定函数时,该参数无效。
  • arg1, arg2, ...   当绑定函数被调用时,这些参数将置于实参之前传递给被绑定的方法。

返回值

  返回由指定的 this 值和初始化参数改造的原函数拷贝


当代码 new Foo(...) 执行时,会发生以下事情: 1、一个继承自 Foo.prototype 的新对象被创建。 2、使用指定的参数调用构造函数 Foo ,并将 this 绑定到新创建的对象。new Foo 等同于 new Foo(),也就是没有指定参数列表,Foo 不带任何参数调用的情况。 3、由构造函数返回的对象就是 new 表达式的结果。如果构造函数没有显式返回一个对象,则使用步骤 1 创建的对象。(一般情况下,构造函数不返回值,但是用户可以选择主动返回对象,来覆盖正常的对象创建步骤)如果你看不懂这段话,没关系,看完下面这段代码你就清楚了

function Foo(){}
下面代码就是执行new Foo()时的简单实现
let obj = {};
obj.__proto__  = Foo.prototype
return Foo.call(obj)

对于new的完整实现可以参考这位大神的博客


实现

乞丐版, 无法预先填入参数,仅实现执行时改变 this 指向

let obj = {ll: 'seve'
};Function.prototype.bind = function(that) {var self = this;return function() {return self.apply(that, arguments);};
};
let func0 = function(a, b, c) {console.log(this.ll);console.log([a, b, c]);
}.bind(obj, 1, 2);func0(3); // seve
// [ 3, undefined, undefined ] 发现1,2并没有填入

乞丐版也太 low 了对吧,所以我们继续完善

es6 进阶版

es6 提供了结构运算符,可以很方便的利用其功能实现 bind

Function.prototype.bind = function(that, ...argv) {if (typeof this !== 'function') {throw new TypeError(`${this} is not callable`);}// 保存原函数let self = this;// 获取bind后函数传入的参数return function(...argu) {return self.apply(that, [...argv, ...argu]);};
};
let func1 = function(a, b, c) {console.log(this.ll);console.log([a, b, c]);
}.bind(obj, 1, 2);func1(3); // seve
// [ 1, 2, 3 ]

es6 版实现很简单对吧,但是面试官说我们的运行环境是 es5,这时你心中窃喜,bable 大法好,但是你可千万不要说有 babel,因为面试官的意图不太可能是问你 es6 如何转换成 es5,而是考察你其他知识点,比如下面的类数组如何转换为真正的数组

es5 进阶版

Function.prototype.bind = function() {if (typeof this !== 'function') {throw new TypeError(`${this} is not callable`);}var self = this;var slice = [].slice;// 模拟es6的解构效果var that = arguments[0];var argv = slice.call(arguments, 1);return function() {// slice.call(arguments, 0)将类数组转换为数组return self.apply(that, argv.concat(slice.call(arguments, 0)));};
};
let func2 = function(a, b, c) {console.log(this.ll);console.log([a, b, c]);
}.bind(obj, 1, 2);func2(3); // seve
// [ 1, 2, 3 ]

当然,写到这里,对于绝大部分面试,这份代码都是一份不错的答案,但是为了给面试官留下更好的印象,我们需要上终极版 实现完整的bind函数,这样还可以跟面试官吹一波

终极版

为了当使用new操作符时,bind后的函数不丢失this。我们需要把bind前的函数的原型挂载到bind后函数的原型上

但是为了修改bind后函数的原型而对bind前的原型不产生影响,都是对象惹的祸。。。直接赋值只是赋值对象在堆中的地址 所以需要把原型继承给bind后的函数,而不是直接赋值,我有在一些地方看到说Object.crate可以实现同样的效果,有兴趣的可以了解一下,但是我自己试了下,发现效果并不好,new 操作时this指向错了(可能是我使用姿势错了)

通过直接赋值的效果

Function.prototype.bind = function(that, ...argv) {if (typeof this !== 'function') {throw new TypeError(`${this} is not callable`);}// 保存原函数let self = this;let func = function() {};// 获取bind后函数传入的参数let bindfunc = function(...arguments) {return self.apply(this instanceof func ? this : that, [...argv, ...arguments]);};// 把this原型上的东西挂载到func原型上面// func.prototype = self.prototype;// 为了避免func影响到this,通过new 操作符进行复制原型上面的东西bindfunc.prototype = self.prototype;return bindfunc;
};function bar() {console.log(this.ll);console.log([...arguments]);
}
let func3 = bar.bind(null);func3.prototype.value = 1;console.log(bar.prototype.value) // 1    可以看到bind后的原型对bind前的原型产生的同样的影响

通过继承赋值的效果

Function.prototype.bind = function(that, ...argv) {if (typeof this !== 'function') {throw new TypeError(`${this} is not callable`);}// 保存原函数let self = this;let func = function() {};// 获取bind后函数传入的参数let bindfunc = function(...argu) {return self.apply(this instanceof func ? this : that, [...argv, ...argu]);};// 把this原型上的东西挂载到func原型上面func.prototype = self.prototype;// 为了避免func影响到this,通过new 操作符进行复制原型上面的东西bindfunc.prototype = new func();return bindfunc;
};function bar() {console.log(this.ll);console.log([...arguments]);
}
let func3 = bar.bind(null);func3.prototype.value = 1;console.log(bar.prototype.value) // undefined   可以看到bind后的原型对bind前的原型不产生影响func3(5);     // seve// [ 5 ]
new func3(5); // undefined// [ 5 ]

以上代码或者表述如有错误或者不严谨的地方,欢迎指出,或者在评论区讨论,觉得我的文章有用的话,可以订阅或者star支持我的博客

下系列文章我打算写关于koa框架的实现,第一篇我会带大家探究Object.create的效果及实现


更多专业前端知识,请上 【猿2048】www.mk2048.com

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/366448.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

WIN10 开启右键 命令提示符

PowerShell强行加入右键菜单也罢了,命令提示符还默认禁用,可谓巨硬又一次智障操作。通过操作注册表可以解除这个封印: 移除 HKEY_CLASSES_ROOT\Directory\Background\shell\cmd 键中的 HideBasedOnVelocityId 然后刷新注册表,可能…

使用JUnit和Repeat注​​释编写有效的负载测试

EasyTest最近推出了一套新的注释,可帮助其用户编写有效的测试用例。 进入EasyTest的两个主要注释是: 重复 持续时间 今天,我们将讨论重复标注。 一种新的方法级别注释 重复已添加到EasyTest框架。 此批注可用于重复相同的测试多次。 在您…

mysql权限表_MySQL 数据库赋予用户权限操作表

MySQL清空数据库的操作:truncate table tablename;MySQL 赋予用户权限命令的简单格式可概括为:grant 权限 on 数据库对象 to 用户一、grant 普通数据用户,查询、插入、更新、删除 数据库中所有表数据的权利。1 grant select on testdb.* to c…

Intellij IDEA 部署Web项目,解决 404 错误

https://blog.csdn.net/eaphyy/article/details/72513914转载于:https://www.cnblogs.com/123hll/p/9329676.html

Redux源码简析

Redux核心概念 单一 store ,只能挺过getState()获取状态,不能直接修改只能通过触发 action 修改状态使用纯函数 reducers 描述action如何改变state 整个redux的实现就是围绕上面的这三点进行实现的,整个源码量不大,理解了核心概…

23种计模式之Python实现(史上最全最通俗易懂)内容整改中

第一篇 Python与设计模式:前言 第二篇(23种设计模式) 创建类设计模式(5种) 单例模式、工厂模式、简单工厂模式、抽象工厂模式、建造者模式、原型模式 结构类设计模式(7种) 代理模式、装饰器模式…

使用Apache Felix文件安装配置OSGi服务

最近有关托管服务的帖子让我想起了我值得一提的Apache Felix File Install中的一项功能。 在与Holger合作进行项目时,我从他那里了解到File Install不能仅用于管理包。 它还监视配置文件,并在托管服务各自的配置更改时更新托管服务。 文件安装还可以配置…

mysql 条件分析_数据分析之mysql

MYSQLselect 列名(全部*)计数函数:AVG(列名)返回某列的平均值COUNT()返回某列的行数(count(*)表示对表中行的数目进行计数,不管对表列中包含的是空值还是非空值。MAX()返回某列的最大值MIN()返回某列的最小值SUM()返回某列值之和distinct去重&#xff0c…

css线性渐变

此方式可以实现背景色由上往下渐变,这里加上-webkit-考虑兼容问题,若要改变渐变方向,改变top即可,如right、left、bottom效果图: 代码如下: background: -webkit-linear-gradient(top,red,black); 不带前缀&#xff0c…

scroll-view组件bindscroll实例应用:自定义滚动条

我们知道scroll-view组件作为滑动控件非常好用,而有时候我们想放置一个跟随滚动位置来跟进的滚动条,但又不想用滚动条api该怎么办呢?(当然是自己写一个呗还能怎么办[自黑冷漠脸]) 嗯,没错。自己写一个就好了…

C# -- HttpWebRequest 和 HttpWebResponse 的使用

C# -- HttpWebRequest 和 HttpWebResponse 的使用 结合使用HttpWebRequest 和 HttpWebResponse,来判断一个网页地址是否可以正常访问。 1.举例 class Program{static void Main(string[] args){string strUrl "https://www.baidu.com";HttpWebRequest wr…

MongoDB:GridFS删除方法删除存储桶中的所有文件

不久前,我们遇到了MongoDB GridFS的奇怪行为,这使我为MongoDB Java驱动程序创建了一个故障 单 。 今天,我在浏览器书签中找到了指向故障单的链接。 该票证目前尚未解决,因此我认为值得一小篇博文,以防其他人遇到此问题…

mysql数据库存储引擎和索引的描述_Mysql InnoDB引擎的索引与存储结构详解

前言在Oracle 和SQL Server等数据库中只有一种存储引擎,所有数据存储管理机制都是一样的。而MySql数据库提供了多种存储引擎。用户可以根据不同的需求为数据表选择不同的存储引擎,用户也可以根据自己的需要编写自己的存储引擎。MySQL主要存储引擎的区别M…

Vue结合HTML5拖放API 实现目录拖拽~

拖放事件 dom被拖拽--->经过一些dom--->到达指定dom 被拖拽的dom:(源对象) dragstart 源对象被拖拽 drag 源对象拖拽过程中 dragend 源对象拖拽结束(drop事件后执行) 拖拽过程中经过的dom:&#xf…

【转】EMC存储移除热备盘Hot spare的方法

转载请在文首保留原文出处:EMC中文支持论坛 https://community.emc.com/docs/DOC-17382 介绍 本文将介绍如何移除Hot spare磁盘的两种方法。 更多信息 方法一: 登录Unisphere导航至Storage -> Disks从列表中找到目标hot spare盘确认Hot spare replaci…

jQuery(一)初识

jQuery 的功能概括1、html 的元素选取2、html的元素操作3、html dom遍历和修改4、js特效和动画效果5、css操作6、html事件操作7、ajax异步请求方式 selector: 操作(DOM)/$(selector).action(): <!DOCTYPE html> <html> <head> <meta charset"utf-8&q…

Java方法中的参数太多,第3部分:构建器模式

在我的前两篇文章中&#xff0c;我研究了如何通过自定义类型和参数对象减少构造函数或方法调用所需的参数数量。 在本文中&#xff0c;我将讨论如何使用构建器模式来减少构造器所需的参数数量&#xff0c;并讨论该模式如何甚至可以帮助采用过多参数的非构造器方法。 在《 有效…

c 连接mysql.mwb_CodeSmith连接mysql提示“找不到请求的 .Net Framework Data Provider”的解决方法...

下载了codesmith 8&#xff0c;连接Mysql却提示“找不到请求的 .Net Framework Data Provider"。&#xff11;&#xff0c;下载MySql.Data.dll&#xff1a;https://dev.mysql.com/downloads/windows/visualstudio/ 下载zip格式的即可&#xff0c;解压后将MySql.Data.dll复…

node那点事(二) -- Writable streams(可写流)、自定义流

可写流&#xff08;Writable Stream&#xff09; 可写流是对数据写入目的地的一种抽象。 可写流的原理其实与可读流类似&#xff0c;当数据过来的时候会写入缓存池&#xff0c;当写入的速度很慢或者写入暂停时候&#xff0c;数据流便会进入到队列池缓存起来&#xff0c;当然即…

第16章-使用Spring MVC创建REST API

1 了解REST 1.1 REST的基础知识 REST与RPC几乎没有任何关系。RPC是面向服务的&#xff0c;并关注于行为和动作&#xff1b;而REST是面向资源的&#xff0c;强调描述应用程序的事物和名词。 为了理解REST是什么&#xff0c;我们将它的首字母缩写拆分为不同的构成部分&#xf…