学习Javascript之模拟实现bind

前言

本文1703字,阅读大约需要5分钟。

总括: 本文模拟实现了bind方法的更改this,传参和绑定函数作为构造函数调用时this失效的特性。

  • 参考文档:Function.prototype.bind()
  • 公众号:「前端进阶学习」,回复「666」,获取一揽子前端技术书籍

愿每次回忆,对生活都不感到负疚。

正文

bindcallapply的作用类似,都是用来更改函数的this值的,不同的是,callapply会直接把函数执行,但bind会返回一个函数,我们称之为绑定函数:

function foo(b = 0) {
    console.log(this.a + b);
}
var obj1  = {
  a: 1
};
foo.call(obj1, 1); // 2
foo.apply(obj1, [1]); // 2
var bar = foo.bind(obj1, 1);
bar(); // 2

看下bind()函数最重要的两个特性:

  1. 更改this;
  2. 传参;

更改this&传参

更改this我们可以借助之前模拟实现过的call和apply的方式来实现,传参就必要我们借助闭包来实现了,我们看下我们实现的第一版代码

Function.prototype.bind2 = function(context) {
  var _this = this;
  return function() {
        context.func = _this;
    context.func();
    delete context.func;
  }
}

传参需要将外层函数(bind里面的参数)和传到绑定函数中的参数全部拼接到一起,这就需要借助闭包来实现,更改this我们可以直接使用apply来实现,将参数放到一个数组中传到绑定函数中,我们的第二版代码

Function.prototype.bind2 = function(context) {
  // 保存上层函数this值
  var _this = this;
  // 保存上层函数的参数
  var args = [].slice.call(arguments, 1);
  return function() {
    // 将参数拼接
        var _args = args.concat([].slice.call(arguments));
    // 利用apply更改this,并把拼接的参数传到函数中
    _this.apply(context, _args);
  }
}

现在我们再来测试下:

function foo(b = 0) {
    console.log(this.a + b);
}
var obj1  = {
  a: 1
};
// 我们成为绑定函数
var bar1 = foo.bind2(obj1, 1);
bar1(); // 2
var bar2 = foo.bind2(obj1);
bar2(); // 1

两个特性成功实现,完美。 然后重头戏在下面:

this失效

目前更改this和传递参数两个特性已经实现,如果截止到这就结束了,就不会单独为模拟实现bind()写一篇博客了,bind还有一个特性,即当绑定函数作为构造函数使用的时候里面的this就会失效。例子:

function Animal(name) {
  this.name = name;
}
var obj = {
    name: 'test'
};
var cat = new Animal('Tom');
var Animal2 = Animal.bind(obj);
var cat2 = new Animal2('Tom');
console.log(cat); // {name: "Tom"}
console.log(cat2); // {name: "Tom"}
console.log(obj); // {name: "test"}

我们解释下上面的代码,我们首先使用构造函数Animal实例化了一个cat对象,cat对象的内容如上打印,然后我们声明了一个Animal2来保存对象obj的绑定函数Animal.bind(obj)。实例化Animal2后发现cat2内容和cat是一样的,此时我们发现使用bind绑定的this失效了,因为我们传进去obj对象的内容并没有发生改变。我们再来看下我们目前的bind2的表现:

Function.prototype.bind2 = function(context) {
  // 保存上层函数this值
  var _this = this;
  // 保存上层函数的参数
  var args = [].slice.call(arguments, 1);
  return function() {
    // 将参数拼接
        var _args = args.concat([].slice.call(arguments));
    // 利用apply更改this,并把拼接的参数传到函数中
    _this.apply(context, _args);
  }
}

function Animal(name) {
  this.name = name;
}
var obj = {
    name: 'test'
};
var mouse = new Animal('jerry');
var Animal3 = Animal.bind2(obj);
var mouse2 = new Animal3('jerry');
console.log(mouse); // {name: "jerry"}
console.log(mouse2); // {}
console.log(obj); // {name: 'jerry'}

我们先看下这里的Animal3实际的返回函数,它是bind2方法的这一部分:

function() {
    // 将参数拼接
        args.concat([].slice.call(arguments));
    // 利用apply更改this,并把拼接的参数传到函数中
    _this.apply(context, args);
 }

如上,代码中我们new Animal3(‘jerry‘)实际上就是对上面的这个函数的实例化,这就是为什么mouse2是个空对象的原因。然后由于前面bind2绑定的是obj,_this.apply(context, args)这行代码就把obj对象的name属性给更改了,context指向obj,_this指向Animal函数。而我们的目标是希望当绑定函数被当做构造函数使用的时候,context不会指向被传进来的上下文对象(比如这里的obj)而是指向绑定函数的this。我们的问题转移到这上面上了:如何在一个函数中去判断这个函数是被正常调用还是被当做构造函数调用的。答案是通过原型。不熟悉原型的同学可以移步:理解Javascript的原型和原型链。例子:

function Animal() {
  console.log(this.__proto__ === Animal.prototype);
}
new Animal(); // true
Animal(); // false

因此可以把我们可以在我们返回的函数里面进行这样的判断,这是我们第三版代码

Function.prototype.bind2 = function(context) {
  // 保存上层函数this值
  var _this = this;
  // 保存上层函数的参数
  var args = [].slice.call(arguments, 1);
  function Func() {
    // 将参数拼接
        var _args = args.concat([].slice.call(arguments));
    _this.apply(this.__proto__ === Func.prototype ? this : context, _args);
  }
  return Func;
}

// 测试代码
function Animal(name) {
  this.name = name;
}
var obj = {
    name: 'test'
};
var mouse = new Animal('jerry');
var Animal3 = Animal.bind2(obj);
var mouse2 = new Animal3('jerry');
console.log(mouse); // {name: "jerry"}
console.log(mouse2); //{name: "jerry"}
console.log(obj); // {name: 'test'}

如上例子,我们的mouse2和obj都是正常的返回了。但这样的实现有一个问题,就是我们没法拿到Animal的原型,此时mouse2.__proto__ === Func.prototype

因此需要再改写下,当实例对象能够链接到构造函数的原型,第四版代码如下

Function.prototype.bind2 = function(context) {
  // 保存上层函数this值
  var _this = this;
  // 保存上层函数的参数
  var args = [].slice.call(arguments, 1);
  function Func() {
    // 将参数拼接
        var _args = args.concat([].slice.call(arguments));
    _this.apply(this.__proto__ === Func.prototype ? this : context, _args);
  }
  Func.prototype = this.prototype;
  return Func;
}

这个时候我们再去实例化mouse2,就可以做到mouse2.__proto__ === Animal.prototype了。

还有一个问题,因为我们是直接Func.prototype = this.prototype, 所以我们在修改Func.prototype的时候,也会直接修改函数的prototype,我们看下我们的最终代码

Function.prototype.bind2 = function(context) {
  // 保存上层函数this值
  var _this = this;
  // 保存上层函数的参数
  var args = [].slice.call(arguments, 1);
  function Transfer() {}
  function Func() {
    // 将参数拼接
        var _args = args.concat([].slice.call(arguments));
    _this.apply(this.__proto__ === Func.prototype ? this : context, _args);
  }
  Transfer.prototype = this.prototype;
  Func.prototype = new Transfer();
  return Func;
}

以上。



能力有限,水平一般,欢迎勘误,不胜感激。

订阅更多文章可关注公众号「前端进阶学习」,回复「666」,获取一揽子前端技术书籍

原文地址:https://www.cnblogs.com/jztan/p/12420264.html

时间: 2024-11-06 13:47:50

学习Javascript之模拟实现bind的相关文章

学习Javascript之模拟实现new

前言 本文1021字,阅读大约需要5分钟. 总括: 本文对new进行了一个简单介绍,然后使用一个函数模拟实现了new操作符做的事情. 参考文档:new 运算符 公众号:「前端进阶学习」,回复「666」,获取一揽子前端技术书籍 人生是没有毕业的学校. 正文 new是JS中的一个关键字,用来将构造函数实例化的一个运算符.例子: function Animal(name) { this.name = name; } Animal.prototype.sayName = function() { con

好程序员前端学习路线分享模拟JavaScript中面向对象技术

好程序员前端学习路线分享模拟JavaScript中面向对象技术,在C#和Java语言中,面向对象是以类的方式实现的,特别是继承这个特性,类的方式继承表现出了强大的功能,而且也易于学习.JavaScript不是纯的面向对象的语言,而是基于对象的语言,对象的继承是以原型函数的形式继承的,很多初学者刚开始接触的时候不太理解,但是JavaScript这种以原型函数的形式实现面向对象技术,不仅是可行的,而且还为面向对象技术提供了动态继承的功能,本文主要讨论了JavaScript的面向对象技术.?一.原型对

javascript中call,apply,bind的用法对比分析

这篇文章主要给大家对比分析了javascript中call,apply,bind三个函数的用法,非常的详细,这里推荐给小伙伴们. 关于call,apply,bind这三个函数的用法,是学习javascript这门语言无法越过的知识点.下边我就来好好总结一下它们三者各自的用法,及常见的应用场景. 首先看call这个函数,可以理解成"借用“,"请求".想像一下如下的情景:你孤单一人漂泊在外,有急事想往家里打电话,可是很不巧,手机欠费了,或者没电了,或者掉坑里了,总之你的手机就是用

如何理解并学习javascript中的面向对象(OOP) [转]

如果你想让你的javascript代码变得更加优美,性能更加卓越.或者,你想像jQuery的作者一样,写出属于自己优秀的类库(哪怕是基于 jquery的插件).那么,你请务必要学习javascript面向对象,否则你无法更灵活的使用javascript这门语言. 什么事闭包?到底什么是原型?(知道闭包和原型的,就算得上是javascript的高手了.但真正能够理解,并且灵活运用的人并不多)到底该如何学习javascript中的面向对象呢?在javascript这么语言正如日中天,相信不少人正在为

一步步学习javascript基础篇(7):BOM和DOM

一.什么是BOM.什么是DOM BOM即浏览器对象模型,主要用了访问一些和网页无关的浏览器功能.如:window.location.navigator.screen.history等对象. DOM即文档对象模型,针对HTML(或XML)文档的API(应用程序编程接口).描绘的一个层次化的节点树,开发人员可以添加.修改和删除页面的某一部分. 二.细说BOM对象 1.window对象 window对象表示浏览器的一个实例,同时也是ECMAScript 规定的 Global 对象.(Global :所

一步步学习javascript基础篇(3):Object、Function等引用类型

我们在<一步步学习javascript基础篇(1):基本概念>中简单的介绍了五种基本数据类型Undefined.Null.Boolean.Number和String.今天我们主要介绍下复杂数据类型(即引用数据类型) Object类型 我们用的最多的引用类型就属object类型了,一般用来存储和传输数据是再好不过的.然,它的两种创建方式我们是否了解呢? 1.通过构造函数来创建 如: var obj = new Object(); 在js中的引用类型有个非常灵活的用法,可以动态的附加属性和赋值.

通过使用Chrome的开发者工具来学习JavaScript

本文作者是Peter Rybin,Chrome开发者工具团队成员. 本文中,我们将通过使用Chrome的开发者工具,来学习JavaScript中的两个重要概念"闭包"和"内部属性". 闭包 首先要讲的是闭包(closure) – JavaScript中最有名的东西之一.一个闭包就是一个使用了外部变量的函数.查看下面的例子: function A(a, b, c) { var ar = [a, b, c]; return function B(i) { return

如何系统地学习JavaScript

在过去,JavaScript只是被用来做一些简单的网页效果,比如表单验证.浮动广告等,所以那时候JavaScript并没有受到重视.自从AJAX开始流行后,人们发现利用JavaScript可以给用户带来更好的体验,甚至利用这一优点开发了大型网页游戏,于是这门小语言被重视了起来.现在,很多公司会招专门的JavaScript工程师,通常JavaScript是WEB前端开发的必备技能.简单介绍了JavaScriptr的好处,并不代表大家就会去学习甚至把它学好,兴趣是很关键的,我认为兴趣是最好的老师,它

如何正确学习JavaScript

学习时长:6-8周学习前提:中学水平,无需编程经验 更新(2014-1-7) 在Reddit上创建了一个学习小组January 2014, “Learn JavaScript” Study Group on Reddit 目录 不要这样学习JavaScript 本课程资源 1-2周(简介,数据类型,表达式和操作符) 3~4周(对象,数组,函数,DOM,jQuery) JavaScript终极编辑器:WebStorm 第一个项目-动态问答应用 5-6周(正则表达式,Window对象,事件,jQue