手动实现一个bind函数!

原文地址:手动实现一个bind函数! - 知乎

1.bind函数用法

bind()方法用于创建一个新的函数,这个新函数接收的第一个参数代表的就是this,利用bind()函数我就就可以任意改变函数内部的this指向了。

官网的解释:

bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

官网解释得也比较通透明了,我们这儿为了让大家更加深刻理解bind的用法,利用代码来演示一下。

示例代码:

<script>
  let obj = {
    name: "小猪课堂",
    age: 20
  }
  // 声明一个函数
  function fn(a, b, c) {
    console.log("函数内部this指向:", this);
    console.log("参数列表:", a, b, c);
  }
  // 使用bind创建一个新函数
  let newFn = fn.bind(obj, 10, 20, 30);
  // 调用新函数
  newFn();
  // 调用旧函数
  fn(10, 20, 30);
</script>

输出结果:

上段代码中我们声明了一个函数fn,并且在函数内部打印了this以及参数,然后我们利用bind()创建了一个新的函数,且第一个参数传入了obj,意味着新函数内部的this指向了obj。分别执行两个函数,两个函数内部的this指向一个指向了全局,一个指向了window。

2.bind函数的特点

如果我们想要手动实现一个bind函数,那么非常有必要了解bind函数的特点,所以知己知彼方能百战不殆。

从上一节中的代码我们大致总结出了bind函数的以下几个特点:

2.1 返回一个新函数

bind函数实际上是对原函数的一个拷贝,原函数认可以按照原逻辑处理。

示例代码:

<script>
  let obj = {
    name: "小猪课堂",
    age: 20
  }
  // 声明一个函数
  function fn(a, b, c) {
    console.log("函数内部this指向:", this);
    console.log("参数列表:", a, b, c);
  }
  // 使用bind创建一个新函数
  let newFn = fn.bind(obj, 10, 20, 30);
  console.log(typeof newFn); // 'function'
</script>

2.2 新函数仍可继续传参

bind函数创建的新函数是可以接收参数的,之前的列子中我们是在创建的时候就将参数传递了进去,实际上可以不必传。

示例代码:

<script>
  let obj = {
    name: "小猪课堂",
    age: 20
  }
  // 声明一个函数
  function fn(a, b, c) {
    console.log("函数内部this指向:", this);
    console.log("参数列表:", a, b, c);
  }
  let newFn = fn.bind(obj, 10);
  newFn(20, 30);
</script>

输出结果:

上面的输出结果和我们直接在创建的时候传递所有参数得出的结果一致,而且上段代码中我们的参数是分开传递的,也就是说使用bind创建新函数后,调用新函数时,函数接收的参数是调用传入的参数+创建时传入的参数。

2.3 新函数作为构造函数

如果我们将使用bind创建的新函数当作构造函数来执行,那么this的指向将和bind创建时绑定的无关,它会指向一个新的引用。

示例代码:

<script>
  let obj = {
    name: "小猪课堂",
    age: 20
  }
  function fn(name) {
    this.name = name;
    console.log("函数内部this指向:", this);
  }
  let newFn = fn.bind(obj);
  let obj2 = new newFn("构造函数");
</script>

输出结果:

上段代码中我们使用bind新创建了一个函数newFn,而且将这个函数的this指向了obj,但是我们后续使用的时候使用了new关键词来创建,这个时候函数内部的this指向不在指向obj了,而是指向了fn。

那么既然this指向了fn,那么我们在fn原型上添加属性或方法后,obj2是能访问到的。

3.实现bind函数

既然我们知道了bind的几个特点,那么我们遵循它的即可特点就可以来实现它了。首先它是返回一个新函数,我们可以先把架子搭起来,代码如下:

Function.prototype.myBind = function () {
  // 返回新函数
  return function () {
    // 代码先省略
  }
}

上段代码只是一个基本的架子,我们在里面填充代码就好了。接下来我们需要将函数的this指向为传进来的第一个参数,并且使用bind创建的新函数可以继续接收参数,代码如下:

<script>
  let obj = {
    name: "小猪课堂",
    age: 20
  }
  // 手写bind函数
  Function.prototype.myBind = function (context) {
    const _this = this; // 当前函数
    let args = Array.from(arguments).slice(1); // 将参数列表转化为数组,出去第一个参数外
    // 返回新函数
    return function () {
      // context 是传进来的this
      _this.apply(context, args.concat(Array.from(arguments))); // 利用apply将this指向context,参数进行拼接
    }
  }
  // 声明一个函数
  function fn(a, b, c) {
    console.log("函数内部this指向:", this);
    console.log("参数列表:", a, b, c);
  }
  let newFn = fn.myBind(obj, 10, 20);
  newFn(30);
</script>

上段代码中需要注意的有两点,第一点是利用apply函数将函数的this指向了传经来的context,第二点是将参数新传进来的参数与args拼接,因为我们调用newFn时,可能传进来新参数,所以需要将新老参数拼接上。

输出结果:

上面的输出结果是和直接使用bind函数输出的结果是一样的。上面的代码还不够完善,如果我们将创建的新函数以构造函数的方式执行的话,this的执行和原生的bind不太一致。

代码如下:

<script>
  let obj = {
    name: "小猪课堂",
    age: 20
  }
  // 手写bind函数
  Function.prototype.myBind = function (context) {
    const _this = this; // 当前函数
    let args = Array.from(arguments).slice(1); // 将参数列表转化为数组,除去第一个参数外
    // 返回新函数
    return function () {
      // context 是传进来的this
      _this.apply(context, args.concat(Array.from(arguments))); // 利用apply将this指向context,参数进行拼接
    }
  }
  // 声明一个函数
  function fn(a, b, c) {
    console.log("函数内部this指向:", this);
    console.log("参数列表:", a, b, c);
  }
  let newFn = fn.myBind(obj, 10, 20); // 调用封装的bind
  let newFn1 = fn.bind(obj, 10, 20); // 调用原生的bind


  new newFn("myBind构造函数");
  new newFn1("bind构造函数");
</script>

输出结果:

上面的输出结果不一致,说明使用原生bind创建的新函数,如果使用构造函数的方式执行,那么函数内部的this执行会作为一个新的引用指向fn。

修改代码如下:

<script>
  let obj = {
    name: "小猪课堂",
    age: 20
  }
  // 手写bind函数
  Function.prototype.myBind = function (context) {
    const _this = this; // 当前函数
    let args = Array.from(arguments).slice(1); // 将参数列表转化为数组,除去第一个参数外
    // 返回新函数
    let fn = function () {
      // 如果被new调用,this应该是fn的实例
      return _this.apply(this instanceof fn ? this : (context || window), args.concat(Array.from(arguments)))
    }
    // 维护fn的原型
    let temp = function () { }
    temp.prototype = _this.prototype;
    fn.prototype = new temp(); // new的过程继承temp原型
    return fn
  };
  // 声明一个函数
  function fn(a, b, c) {
    console.log("函数内部this指向:", this);
    console.log("参数列表:", a, b, c);
  }
  let newFn = fn.myBind(obj, 10, 20);
  let newFn1 = fn.bind(obj, 10, 20)


  new newFn("myBind构造函数");
  new newFn1("bind构造函数");
</script>

输出结果:

上段代码的输出结果是不是就和实际的bind函数输出结果一样了啊!想要理解上段代码,大家有必要去学习以下JS中new一个对象发生了什么,主要是下面几步:

  • 创建一个新对象
  • 将构造函数的this赋值给新对象
  • 执行构造函数代码,给这个新的对象添加属性
  • 返回新的对象

具体的new实现过程还需要大家自己去理解。

总结

想要实现bind函数,就必须要理解其中的原理,无非就是改变this指向的问题。其中唯一的难点就是如何实现构造函数执行的方式,也就是要明白js中new一个对象的时候发生了什么?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用android studio 运行,下面是一个简单的文档,这个代码是一个demo 一、Activity的使用 1、SNActivity 框架最基本的activity,可调用$(SNManager)进行操作activity,具体用法请参考文档或代码 2、SNNavigationSlidingActivity 包含SNActivity的功能,继承于com.jeremyfeinstein.slidingmenu.lib.app.SlidingActivity 支持导航条和左滑视图的Activity 加载导航条: loadNavBar(int height,int background_color_id) loadNavBarResId(int height_id,int background_id) 加载左侧视图: /** * load left view * @param left_id left layout id * @param offset_value offset value * @param shadow_width_value shadow width value * @param shadow_drawable_id shadow drawable style * @param fade fade value */ loadLeft(int left_id, int offset_value, int shadow_width_value, int shadow_drawable_id, float fade) /** * load left view * @param left_id left layout id * @param offset_id offset id * @param shadow_width_id shadow width id * @param shadow_drawable_id shadow drawable id * @param fade fade value */ loadLeftResId(int left_id, int offset_id, int shadow_width_id, int shadow_drawable_id, float fade) 二、SNElement的使用 View的伪装对象,支持所有View的功能,详细功能可参考文档或代码 手动伪装:$.create $.id $.findView 注入伪装:$.setContent(view class or layout id,inject class); 获取原型:elem.toView(); 三、注入 1、视图注入 A、创建注入类,属性名称必须和layout中的id对应,如果不对应请加入标签@SNInjectView class DemoInject{ @SNInjectView(id=R.id.tvTest) public SNElement test; } B、实例化注入对象 DemoInject di=new DemoInject(); C、调用$.inject或者$.setContent注入 $.inject(di); D、注入成功后即可调用对象 String text=di.test.text(); 2、依赖注入 A、需要绑定注入对象,建议写到Application中的onCreate SNBindInjectManager.instance().bind(ITest.class, Test.class); B、与视图注入不同的是属性必须添加标签@SNIOC,注入的对象(Test)必须包含只有一个SNManager参数的构造函数,且必须实现注入者 public class Test implements ITest{ SNManager $; public Test(SNManager _$){ this.$=_$; }; } class DemoInject{ @SNIOC public ITest test; } C、调用$.inject或者$.setContent注入 同视图注入 D、注入成功后即可调用对象 di.test.xxx(); 四、fragment的使用 1、SNFragment 2、SNLazyFragment 五、控件的使用 1、SNFragmentScrollable 2、SNPercentLinearLayout、SNPercentRelativeLayout 3、SNScrollable 4、SNSlipNavigation 5、XList 6、slidingtab
call、apply和bind都是用来改变函数中的this指向的方法。其中,call和apply可以直接调用函数并传递参数,而bind则返回一个新的函数,需要手动调用。 具体实现方案如下: - call的实现: 1. 给想要绑定的对象设置一个属性,并将该属性指向需要调用的函数。 2. 使用该对象调用函数,并传递参数。 3. 结束调用后,删除该属性。 - apply的实现: 1. 给想要绑定的对象设置一个属性,并将该属性指向需要调用的函数。 2. 使用该对象调用函数,并传递参数数组。 3. 结束调用后,删除该属性。 - bind实现: 1. 创建一个新的函数,并将原函数作为其中的属性保存起来。 2. 当新函数被调用时,将之前绑定的对象作为this,并传递参数。 3. 返回新函数供后续调用。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [如何实现call、apply、bind](https://blog.csdn.net/XIAO_A_fighting/article/details/116701887)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [原生JS实现 call apply bind](https://download.csdn.net/download/weixin_38628990/14046564)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值