学习Javascript之模拟实现bind

前言

本文1703字,阅读大约须要5分钟。前端

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

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

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

正文

bindcallapply的做用相似,都是用来更改函数的this值的,不一样的是,callapply会直接把函数执行,但bind会返回一个函数,咱们称之为绑定函数:app

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);
  }
}
复制代码

如今咱们再来测试下:ui

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失效spa

目前更改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」,获取一揽子前端技术书籍

前端进阶学习
相关文章
相关标签/搜索