bind函数

bind 函数挂在 Function 的原型上

Function.prototype.bind

创建的函数都可以直接调用 bind,使用:


function func(){
console.log(this)
}
func.bind(); // 用函数来调用

bind 的作用:

bind() 方法调用后会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为新函数运行时的 this的值,之后的序列参数将会在传递的实参前传入作为新函数的参数。<MDN>

bind 接收的参数

func.bind(thisArg[,arg1,arg2...argN])

  • 第一个参数thisArg,当 func 函数被调用时,该参数会作为 func 函数运行时的 this 指向。当使用 new 操作符调用绑定函数时,该参数无效。
  • [,arg1,arg2...argN] 作为实参传递给 func 函数。

bind 返回值

返回一个新函数

注意:这和函数调用 call/apply 改变this指向有所不同。调用call/apply 会把原函数直接执行了。

举个例子说明:


function func(){
console.log(this)
} // 用call
func.call({a:1}); // func函数被执行了,打印:{a:1} // 用bind
let newFunc = func.bind({}); // 返回新函数 newFunc(); // 只有当返回的新函数执行,func函数才会被执行

从以上得到如下信息:

  1. bind被函数调用
  2. 返回一个新函数
  3. 能改变函数this指向
  4. 可以传入参数

深入bind 使用

以上知道了 bind 函数的作用以及使用方式,接下深入到 bind 函数的使用中,具体介绍三个方面的使用,这也是之后模拟实现 bind 函数的要点。

  1. 改变函数运行时this指向
  2. 传递参数
  3. 返回的新函数被当成构造函数

改变函数运行时this指向

当调用 bind 函数后,bind 函数的第一个参数就是原函数作用域中 this 指向的值。


function func(){
console.log(this);
} let newFunc = func.bind({a:1});
newFunc(); // 打印:{a:1} let newFunc2 = func.bind([1,2,3]);
newFunc2(); // 打印:[1,2,3] let newFunc3 = func.bind(1);
newFunc3(); // 打印:Number:{1} let newFunc4 = func.bind(undefined/null);
newFunc4(); // 打印:window

以上要注意,当传入为 null 或者 undefined 时,在非严格模式下,this 指向为 window

当传入为简单值时,内部会将简单的值包装成对应类型的对象,数字就调用 Number 方法包装;字符串就调用 String 方法包装;true/false 就调用 Boolean 方法包装。要想取到原始值,可以调用 valueOf 方法。


Number(1).valueOf(); // 1
String("hello").valueOf(); // hello
Boolean(true).valueOf(); // true

当多次调用 bind 函数时,以第一次调用 bind 函数的改变 this 指向的值为准。


function func(){
console.log(this);
} let newFunc = func.bind({a:1}).bind(1).bind(['a','b','c']);
newFunc(); // 打印:{a: 1}

传递的参数

bind 的第二个参数开始,是向原函数传递的实参。bind 返回的新函数调用时也可以向原函数传递实参,这里就涉及顺序问题。


function func(a,b,c){
console.log(a,b,c); // 打印传入的实参
} let newFunc = func.bind({},1,2); newFunc(3)

打印结果为1,2,3。
可以看到,在 bind 中传递的参数要先传入到原函数中。

返回的新函数被当成构造函数

调用 bind 函数后返回的新函数,也可以被当做构造函数。通过新函数创建的实例,可以找到原函数的原型上。


// 原函数
function func(name){
console.log(this); // 打印:通过{name:'wy'}
this.name = name;
}
func.prototype.hello = function(){
console.log(this.name)
}
let obj = {a:1}
// 调用bind,返回新函数
let newFunc = func.bind(obj); // 把新函数作为构造函数,创建实例 let o = new newFunc('seven'); console.log(o.hello()); // 打印:'seven'
console.log(obj); // 打印:{a:1}

新函数被当成了构造函数,原函数func 中的 this 不再指向传入给 bind 的第一个参数,而是指向用 new 创建的实例。在通过实例 o 找原型上的方法 hello 时,能够找到原函数 func 原型上的方法。

在模拟实现 bind 特别要注意这一块的实现,这也是面试的重点,会涉及到继承。

bind函数应用场景

以上只是说了 bind 函数时如何使用的,学会了使用,要把它放在业务场景中来解决一些现实问题。

场景一

先来一个布局:


&lt;ul id="list"&gt;
&lt;li&gt;1&lt;/li&gt;
&lt;li&gt;1&lt;/li&gt;
&lt;li&gt;1&lt;/li&gt;
&lt;/ul&gt;

需求:点击每一个 li 元素,延迟1000ms后,改变 li 元素的颜色,


let lis = document.querySelectorAll('#list li');
for(var i = 0; i &lt; lis.length; i++){
lis[i].onclick = function(){
setTimeout(function(){
this.style.color = 'red'
},1000)
}
}

以上代码点击每一个 li,并不会改变颜色,因为定时器回调函数的 this 指向的不是点击的 li,而是window,(当然你也可以使用箭头函数,let之类来解决,这里讨论的主要是用bind来解决)。此时就需要改变回调函数的 this 指向。能改变函数 this 指向的有:call、apply、bind。那么选择哪一个呢?根据场景来定,这里的场景是在1000ms之后才执行回调函数,所以不能选择使用call、apply,因为它们会立即执行函数,所以这个场景应该选择使用 bind解决。


setTimeout(function(){
this.style.color = 'red'
}.bind(this),1000)

场景二

有时会使用面向对象的方式来组织代码,涉及到把事件处理函数拆分在原型上,然后把这些挂在原型上的方法赋值给事件,此时的函数在事件触发时this都指向了元素,进而需要在函数中访问实例上的属性时,便不能找到成。


function Modal(options){
this.options = options;
} Modal.prototype.init = function(){
this.el.onclick = this.clickHandler; // 此方法挂载原型上
}
Modal.prototype.clickHandler = function(){
console.log(this.left); // 此时点击元素执行该函数,this指向元素,不能找到left
} let m = new Modal({
el: document.querySelector('#list'),
left: 300
}) m.init(); // 启动应用

以上代码,在 init 函数中,给元素绑定事件,事件处理函数挂在原型上,使用 this 来访问。当点击元素时,在 clickHandler 函数中需要拿到实例的 left 属性,但此时 clickHandler 函数中的 this 指向的是元素,而不是实例,所以拿不到。要改变 clickHandler 函数 this 的指向,此时就需要用到 bind


Modal.prototype.init = function(){
this.el.onclick = this.clickHandler.bind(this)
}

以上场景只是 bind 使用的冰山一角,它本质要做的事情是改变 this 的指向,达到预期目的。掌握了 bind 的作用以及应用的场景,在脑海中就会树立一个印象:当需要改变this指向,并不立即执行函数时,就能想到 bind

模拟实现

为什么要自己去实现一个bind函数呢?

bind()函数在 ECMA-262 第五版才被加入;它可能无法在所有浏览器上运行(ie8以下)。
面试用,让面试官找不到拒绝你的理由

抓住 bind 使用的几个特征,把这些点一一实现就OK,具体的点:

  1. 被函数调用
  2. 返回新函数
  3. 传递参数
  4. 改变函数运行时this指向
  5. 新函数被当做构造函数时处理

被函数调用,可以直接挂在Function的原型上,为了补缺那些不支持的浏览器,不用再为支持的浏览器添加,可以做如下判断:


if(!Function.prototype.bind) {
Function.prototype.bind = function(){ }
}

这种行为也叫作 polyfill,为不支持的浏览器添加某项功能,以达到抹平浏览器之间的差距。

注意:如果浏览器支持,方便自己测试,可以把 if 条件去掉,或者把 bind 改一个名字。在下文准备改名字为 bind2,方便测试。

调用 bind 后会返回一个新的函数,当新函数被调用,原函数随之也被调用。


Function.prototype.bind2 = function(thisArg,...args){
let funcThis = this; // 函数调用bind,this指向原函数
// 返回新函数
return function (...rest) {
return funcThis.apply(thisArg,[...args,...rest]/*bind2传递的实参优先于新函数的实参*/)
}
} // 测试
function func(a,b,c){
console.log(this)
console.log(a,b,c)
} let newFunc = func.bind2({a:1},1,2); newFunc(3);
// 打印:{a: 1}
// 打印:1 2 3

以上这个函数已经能够改变原函数 this 的指向,并传递正确顺序的参数。接下来就是比较难理解的地方,当新函数被当做构造函数的情况。

需要作出两个地方的改变:

  1. 新返回的函数要继承原函数原型上的属性
  2. 原函数改变this问题。如果用new调用,则原函数this指向应该是新函数中this的值;否则为传递的thisArg的值。

先做继承,让新函数继承原函数的原型,维持原来的原型关系。匿名函数没办法引用,所以给新函数起一个名字。


Function.prototype.bind2 = function(thisArg,...args){
let funcThis = this; // 函数调用bind,this指向原函数 // 要返回的新函数
let fBound = function (...rest) {
return funcThis.apply(thisArg,[...args,...rest]/*bind2传递的实参优先于新函数的实参*/)
} // 不是所有函数都有prototype属性,比如 Function.prototype就没有。
if(funcThis.prototype){
// 使用Object.create,以原函数prototype作为新对象的原型创建对象
fBound.prototype = Object.create(funcThis.prototype);
}
return fBound;
} // 测试
function func(name){
console.log(this); // {a: 1}
this.name = name;
} func.prototype.hello = function(){
console.log(this.name); // undefined
} let newFunc = func.bind2({a:1});
let o = new newFunc('seven') o.hello();
// 打印:{a: 1}
// 打印:undefined

以上代码,新建的实例 o 能够调用到 hello 这个方法,说明继承已经实现,能够访问新函数上原型方法。

接下来是关于 this 指向问题,上面例子中,使用了 new 运算符调用函数,那么原函数中,this 应该指向实例才对。所以需要在改变 this 指向的 apply 那里对是否是使用 new 操作符调用的做判断。

用到的操作符是 instanceof,作用是判断一个函数的原型是否在一个对象的原型链上,是的话返回true,否则返回false。测试如下:


function Person(){}
let p = new Person();
console.log(p instanceof Person); // true
console.log(p instanceof Object); // true
console.log(p instanceof Array); // fasle

也可以用 instanceof 在构造函数中判断是否是通过 new 来调用的。如果是用 new 来调用,说明函数中 this 对象的原型链上存在函数的原型,会返回true。


function Person(){
console.log(this instanceof Person); // true
} new Person();

回到我们的 bind2 函数上,当调用 bind2 后返回了新函数 fBound,当使用 new 调用构造函数时,实际上调用的就是 fBound 这个函数,所以只需要在 fBound 函数中利用 instanceof 来判断是否是用 new 来调用即可。


Function.prototype.bind2 = function(thisArg,...args){
let funcThis = this; // 函数调用bind,this指向原函数 // 要返回的新函数
let fBound = function (...rest) {
// 如果是new调用的,原函数this指向新函数中创建的实例对象
// 不是new调用,依然是调用bind2传递的第一个参数
thisArg = this instanceof fBound ? this : thisArg;
return funcThis.apply(thisArg,[...args,...rest]/*bind2传递的实参优先于新函数的实参*/)
} // 不是所有函数都有prototype属性,比如 Function.prototype就没有。
if(funcThis.prototype){
// 使用Object.create,以原函数prototype作为新对象的原型创建对象
fBound.prototype = Object.create(funcThis.prototype);
}
return fBound;
}
// 测试
function func(name){
console.log(this); // {a: 1}
this.name = name;
} func.prototype.hello = function(){
console.log(this.name); // undefined
} let newFunc = func.bind2({a:1});
let o = new newFunc('seven') o.hello();
// 打印:{name:'seven'}
// 打印:'seven'

bind 函数源码已实现完成,希望对你有帮助。

如有偏差欢迎指正学习,谢谢。

来源:https://segmentfault.com/a/1190000017543088

bind函数作用、应用场景以及模拟实现的更多相关文章

  1. 【进阶3-4期】深度解析bind原理、使用场景及模拟实现(转)

    这是我在公众号(高级前端进阶)看到的文章,现在做笔记  https://github.com/yygmind/blog/issues/23 bind() bind() 方法会创建一个新函数,当这个新函 ...

  2. apply,call,bind函数作用与用法

    作用 可以把方法借给其它对象使用,并且改变this的指向 a.apply(b,[3,2]);//this指向由a变为b, a的方法借给b使用 实例: function add(a,b){       ...

  3. 网络通讯中 bind函数的作用

    面向连接的网络应用程序分为客户端和服务器端.服务器端的执行流程一般为4步,客户端程序相对简单,一般需要两个步骤. 服务器端执行流程4步如下: (1)调用socket函数,建立一个套接字,该套接字用于接 ...

  4. bind函数的作用

    面向连接的网络应用程序分为客户端和服务器端.服务器端的执行流程一般为4步,客户端程序相对简单,一般需要两个步骤. 服务器端执行流程4步如下: (1)调用socket函数,建立一个套接字,该套接字用于接 ...

  5. 优雅手撕bind函数(面试官常问)

    优雅手撕bind函数 前言: 为什么面试官总爱让实现一个bind函数? 他想从bind中知道些什么? 一个小小的bind里面内有玄机? 今天来刨析一下实现一个bind要懂多少相关知识点,也方便我们将零 ...

  6. 从bind函数看js中的柯里化

    以下是百度百科对柯里化函数的解释:柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术.概念太抽象,可能 ...

  7. javascript中bind函数的作用

    javascript的bind的作用 <!DOCTYPE html> <html> <head> <meta charset="utf-8" ...

  8. 模拟实现兼容低版本IE浏览器的原生bind()函数功能

    模拟实现兼容低版本IE浏览器的原生bind()函数功能: 代码如下: if(!Function.prototype.bind){   Function.prototype.bind=function( ...

  9. bind()函数的作用

    bind()函数是Function原型上的一个属性,当某个函数调用此方法时,可以通过向bind()函数传入执行对象和调用bind的函数的参数来改变函数的执行对象 /*问题:改变func执行环境,使之输 ...

随机推荐

  1. jmeter csv Data Set Config 文件中带引号的数据转换问题(自动添加双引号解决办法)

    1.我们从csv中获取数据,在jmeter中使用这些数据,其中csv的数据如图,有的数据包含引号. 2.问题:我们获取的json数据,被自动添加了双引号 3.解决方式: 在CSV Data Set C ...

  2. Redis大 key的发现与删除方法全解析

    个推作为国内第三方推送市场的早期进入者,专注于为开发者提供高效稳定的推送服务,经过9年的积累和发展,服务了包括新浪.滴滴在内的数十万APP.由于我们推送业务对并发量.速度要求很高,为此,我们选择了高性 ...

  3. opencv_将图像上的4个点按逆时针排序

    1:代码如下: #include "stdafx.h" #include "cxcore.h" #include "cvcam.h" #in ...

  4. Spring Data概览

    总结:JpaRepository继承PagingAndSortingRepository,PagingAndSortingRepository继承CrudRespository,CrudResposi ...

  5. java代码如何在没有安装JDK的Windows下运行

    java代码如何在没有安装JDK的Windows下运行? 对于Java桌面应用来说,比较烦琐的就是安装部署问题,如:客户端是否安装有jre.jre版本.jre在哪里下载.如何用jre启动Java应用等 ...

  6. leetcode-easy-others-118 Pascal's Triangle

    mycode   16.47% class Solution(object): def generate(self, numRows): """ :type numRow ...

  7. QBXT七月D1

    今天是lyd神仙讲课的第一天,可以感觉到的是这位神仙有着不同于他人的气质,比如他的表情包(雾) 好了来讲正经的) 今天讲的比较多的是模拟算法和一些比赛中的好习惯 high-level 这个名词的大体意 ...

  8. IDEA全局配置

    进入全局设置界面: 取消每次启动IDEA就默认打开上一次最后关闭的项目 编译器代码字体设置: 控制台字体大小和颜色设置 同一个文件代码里面的各个不同方法之间显示分割线 代码自动提示不区分大小写 格式化 ...

  9. python - lambda 函数使用

    # if we need it only once and it's quite simple def make_incrementor(n): return lambda x: x + n f = ...

  10. Kotlin之定义变量

    java : int n = 30 ; final int m = 30 ; float k = 2.5f; string s = "sss"; short i = 5; bool ...