资讯专栏INFORMATION COLUMN

如何写一个实用的bind?

zhaofeihao / 298人阅读

摘要:方法创建一个新的函数当被调用时,它的关键字被设置为提供的值。语法简单地看一下这些参数的含义当绑定函数被调用时,该参数会作为原函数运行时的指向当使用操作符调用绑定函数时,该参数无效。结尾文章很简短,知道怎么实现一个原生的就行。

前言

这是underscore.js源码分析的第五篇,如果你对这个系列感兴趣,欢迎点击

underscore-analysis/ watch一下,随时可以看到动态更新。

事情要从js中的this开始说起,你是不是也经常有种无法掌控和知晓它的感觉,对于初学者来说,this简直如同回调地狱般,神乎其神,让人无法捉摸透。但是通过原生js中的bind方法,我们可以显示绑定函数的this作用域,而无需担心运行时是否会改变而不符合自己的预期。当然了下划线中的bind也是模仿它的功能同样可以达到类似的效果。

bind简单回顾

我们从mdn上的介绍来回顾一下bind的使用方法。

bind方法创建一个新的函数, 当被调用时,它的this关键字被设置为提供的值。

语法

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

简单地看一下这些参数的含义

thisArg

当绑定函数被调用时,该参数会作为原函数运行时的this指向,当使用new 操作符调用绑定函数时,该参数无效。

arg1, arg2, ...

当绑定函数被调用时,这些参数将置于实参之前传递给被绑定的方法。

绑定this作用域示例

window.name = "windowName"

let obj = {
  name: "qianlongo",
  showName () {
    console.log(this.name)
  }
}

obj.showName() // qianlongo

let showName = obj.showName
  showName() // windowName

let bindShowName = obj.showName.bind(obj)
  bindShowName() // qianlongo

通过以上简单示例,我们知道了第一个参数的作用就是绑定函数运行时候的this指向

第二个参数开始起使用示例

let sum = (num1, num2) => {
  console.log(num1 + num2)
}

let bindSum = sum.bind(null, 1)
bindSum(2) // 3

bind可以使一个函数拥有预设的初始参数。这些参数(如果有的话)作为bind的第二个参数跟在this(或其他对象)后面,之后它们会被插入到目标函数的参数列表的开始位置,传递给绑定函数的参数会跟在它们的后面。

参数的使用基本上明白了,我们再来看看使用new去调用bind之后的函数是怎么回事。

function Person (name, sex) {
  console.log(this) // Person {}
  this.name = name
  this.sex = sex
}
let obj = {
  age: 100
}
let bindPerson = Person.bind(obj, "qianlongo")

let p = new bindPerson("boy")

console.log(p) // Person {name: "qianlongo", sex: "boy"}

有没有发现bindPerson内部的this不再是bind的第一个参数obj,此时obj已经不再起效了。

实际上bind的使用是有一定限制的,在一些低版本浏览器下不可用,你想不想看看下划线中是如何实现一个兼容性好的bind呢!!!come on

下划线中bind实现

源码

 _.bind = function(func, context) {
  // 如果原生支持bind函数,就用原生的,并将对应的参数传进去
  if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
  // 如果传入的func不是一个函数类型 就抛出异常
  if (!_.isFunction(func)) throw new TypeError("Bind must be called on a function");
  // 把第三个参数以后的值存起来,接下来请看executeBound
  var args = slice.call(arguments, 2);
  var bound = function() {
    return executeBound(func, bound, context, this, args.concat(slice.call(arguments)));
  };
  return bound;
};

executeBound实现

var executeBound = function(sourceFunc, boundFunc, context, callingContext, args) {
  // 如果调用方式不是new func的形式就直接调用sourceFunc,并且给到对应的参数即可
  if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args); 
  // 处理new调用的形式
  var self = baseCreate(sourceFunc.prototype);
  var result = sourceFunc.apply(self, args);
  if (_.isObject(result)) return result;
  return self;
};

上面的源码都做了相应的注释,我们着重来看一下executeBound的实现

先看一下这些参数都代表什么含义

sourceFunc:原函数,待绑定函数

boundFunc: 绑定后函数

context:绑定后函数this指向的上下文

callingContext:绑定后函数的执行上下文,通常就是 this

args:绑定后的函数执行所需参数

ok,我们来看一下第一句

if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args); 

这句话是为了判断绑定后的函数是以new关键字被调用还是普通的函数调用的方式,举个例子

function Person () {
  if (!(this instanceof Person)) {
    return console.log("非new调用方式")
  }

  console.log("new 调用方式")
}

Person() // 非new调用方式
new Person() // new 调用方式

所以如果你希望自己写的构造函数无论是new还是没用new都起效的话可以用下面的代码

function Person (name, sex) {
  if (!(this instanceof Person)) {
    return new Person(name, sex)
  }

  this.name = name
  this.sex = sex
}

new Person("qianlongo", "boy") // Person {name: "qianlongo", sex: "boy"}

Person("qianlongo", "boy") // Person {name: "qianlongo", sex: "boy"}

我们回到executeBound的解析

if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args); 

callingContext是被绑定后的函数的this作用域,boundFunc就是那个被绑定后的函数,那么通过这个if判断,当为非new调用形式的时候,直接利用apply处理即可。

但是如果是用new调用的呢?我们看下面这段代码,别看短短的四行代码里面知识点挺多的呢!

// 这里拿到的是一个空对象,且其继承于原函数的原型链prototype
var self = baseCreate(sourceFunc.prototype);
// 构造函数执行之后的返回值
// 一般情况下是没有返回值的,也就是undefined
// 但是有时候写构造函数的时候会显示地返回一个obj
var result = sourceFunc.apply(self, args);
// 所以去判断结果是不是object,如果是那么返回构造函数返回的object
if (_.isObject(result)) return result;
// 如果没有显示返回object,就返回原函数执行结束后的实例
return self;

好,到这里,我有一个疑问,baseCreate是个什么鬼?

var Ctor = function(){};

var baseCreate = function(prototype) {
  // 如果prototype不是object类型直接返回空对象
  if (!_.isObject(prototype)) return {};
  // 如果原生支持create则用原生的
  if (nativeCreate) return nativeCreate(prototype); 
  // 将prototype赋值为Ctor构造函数的原型
  Ctor.prototype = prototype; 
  // 创建一个Ctor实例对象
  var result = new Ctor; 
  // 为了下一次使用,将原型清空
  Ctor.prototype = null; 
  // 最后将实例返回
  return result; 
};

是不是好简单,就是实现了原生的Object.create用来做一些继承的事情。

结尾

文章很简短,知道怎么实现一个原生的bind就行。如果你对apply、call和this感兴趣,欢迎查看

js中call、apply、bind那些事

[this-想说爱你不容易](https://qianlongo.github.io/2...
)

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

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

相关文章

  • 如何一个实用bind

    摘要:方法创建一个新的函数当被调用时,它的关键字被设置为提供的值。语法简单地看一下这些参数的含义当绑定函数被调用时,该参数会作为原函数运行时的指向当使用操作符调用绑定函数时,该参数无效。结尾文章很简短,知道怎么实现一个原生的就行。 前言 这是underscore.js源码分析的第五篇,如果你对这个系列感兴趣,欢迎点击 underscore-analysis/ watch一下,随时可以看到动态...

    Prasanta 评论0 收藏0
  • 一行代码看懂bind,call

    摘要:代码方法返回一个新的数组对象原数组的浅拷贝,常用于动态解析参数。看过的童鞋,对这行代码不会陌生。所以笔者觉得,从理解角度来看,新创建的函数命名为更便于理解。总结乍一看,函数有点绕,一经推敲还是很好理解的。 代码 var slice = Function.prototype.call.bind(Array.prototype.slice); slice() 方法返回一个新的数组对象(原数...

    Lavender 评论0 收藏0
  • Android开源架构

    摘要:音乐团队分享数据绑定运行机制分析一个项目搞定所有主流架构单元测试一个项目搞定所有主流架构系列的第二个项目。代码开源,展示了的用法,以及如何使用进行测试,还有用框架对的进行单元测试。 Android 常用三方框架的学习 Android 常用三方框架的学习 likfe/eventbus3-intellij-plugin AS 最新可用 eventbus3 插件,欢迎品尝 简单的 MVP 模...

    sutaking 评论0 收藏0
  • 实用模式之中介者模式

    摘要:好,师傅我们要学习帝吧人民,进能打,退能刷淘宝。恩,大致过程就是这样,我们使用中介者模式想一想。首先,数据需要放在中介者模式内,用户的一切操作,都会传递给中介者模式,由他来选择是哪一个部分发生改变。 俗话说,一个模式三个坑。 中介者模式应该算最坑的一个模式,坑不在于他的原理。而在于他的名字和其他模式的使用,真尼玛像。首先,中介者 好像是一切模式里面都有的一个东西,比如,享元模式中-元对...

    AlexTuan 评论0 收藏0

发表评论

0条评论

zhaofeihao

|高级讲师

TA的文章

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