美文网首页
「转」JavaScript call, apply, bind实

「转」JavaScript call, apply, bind实

作者: 音视频直播技术专家 | 来源:发表于2018-12-05 23:24 被阅读25次

前言

本来计划是, 先把深入React技术栈过完,
但是,现在在满足RN app开发情况下,我还是先深入js一个系列。

(一)call源码解析

首先上一个call使用

function add(c, d) {
  return this.a + this.b + c + d;
}

const obj = { a: 1, b: 2 };
console.error(add.call(obj, 3, 4)); // 10

大统上的说法就是,call改变了this的指向。然后,介绍this xxx什么一大堆名词,反正不管你懂不懂,成功绕晕你就已经ok了,但是实际发生的过程,可以看成下面的样子。

const o = {
  a: 1,
  b: 2,
  add: function(c, d) {
    return this.a + this.b + c + d
  }
};

给o对象添加一个add属性,这个时候 this 就指向了 o,
o.add(5,7)得到的结果和add.call(o, 5, 7)相同。
但是给对象o添加了一个额外的add属性,这个属性我们是不需要的,所以可以使用delete删除它。
so, 基本为以下三部。

// 1. 将函数设为对象的属性
o.fn = bar
// 2. 执行该函数
o.fn()
// 3. 删除该函数
delete o.fn

所以我们基于ES3 实现call

Function.prototype.es3Call = function (context) {
  var content = context || window;
  content.fn = this;
  var args = [];
  // arguments是类数组对象,遍历之前需要保存长度,过滤出第一个传参
  for (var i = 1, len = arguments.length ; i < len; i++) {
    // 避免object之类传入
    args.push('arguments[' + i + ']');
  }
  var result = eval('content.fn('+args+')');
  delete content.fn;
  return result;
}
console.error(add.es3Call(obj, 3, 4)); // 10
console.log(add.es3Call({ a: 3, b: 9 }, 3, 4)); // 19
console.log(add.es3Call({ a: 3, b: 9 }, {xx: 1}, 4)); // 12[object Object]4

基于ES6 实现call, 其实差别不大,es6新增… rest,这就可以放弃eval的写法,如下

// ES6 call 实现
Function.prototype.es6Call = function (context) {
  var context = context || window;
  context.fn = this;
  var args = [];
  for (var i = 1, len = arguments.length; i < len; i++) {
    args.push('arguments[' + i + ']');
  }
  const result = context.fn(...args);
  return result;
}

console.error(add.es6Call(obj, 3, 4));
console.log(add.es3Call({ a: 3, b: 9 }, {xx: 1}, 4)); // 12[object Object]4

(二)apply源码解析

apply和call区别在于apply第二个参数是Array,而call是将一个个传入

// 基于es3实现

Function.prototype.es3Apply = function (context, arr) {
  var context = context || window;
  context.fn = this;
  var result;
  if (!arr) {
    result = context.fn();
  } else {
    if (!(arr instanceof Array)) throw new Error('params must be array');
    result = eval('context.fn('+arr+')');
  }
  delete context.fn;
  return result
}

console.log(add.apply(obj, [1, 2])); // 6

// 基于es6实现

Function.prototype.es6Apply = function (context, arr) {
  var context = context || window;
  context.fn = this;
  var result;
  if (!arr) {
    result = context.fn();
  } else {
    if (!(arr instanceof Array)) throw new Error('params must be array');
    result = context.fn(...arr);
  }
  delete context.fn;
  return result
}

console.error(add.es6Apply(obj, [1, 2])); // 6

(三)bind源码解析

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

function foo(c, d) {
  this.b = 100
  console.log(this.a)
  console.log(this.b)
  console.log(c)
  console.log(d)
}
// 我们将foo bind到{a: 1}
var func = foo.bind({a: 1}, '1st'); 
func('2nd'); // 1 100 1st 2nd
// 即使再次call也不能改变this。
func.call({a: 2}, '3rd'); // 1 100 1st 3rd

// 当 bind 返回的函数作为构造函数的时候,
// bind 时指定的 this 值会失效,但传入的参数依然生效。
// 所以使用func为构造函数时,this不会指向{a: 1}对象,this.a的值为undefined。如下

// new func('4th'); //undefined 100 1st 4th

首先使用ES3实现

Function.prototype.es3Bind = function (context) {
  if (typeof this !== "function") throw new TypeError('what is trying to be bound is not callback');
  var self = this;
  var args = Array.prototype.slice.call(arguments, 1);
  const fBound = function () {
    // 获取函数的参数
    var bindArgs = Array.prototype.slice.call(arguments);
    // 返回函数的执行结果
    // 判断函数是作为构造函数还是普通函数
    // 构造函数this instanceof fNOP返回true,将绑定函数的this指向该实例,可以让实例获得来自绑定函数的值。
    // 当作为普通函数时,this 指向 window,此时结果为 false,将绑定函数的 this 指向 context
    return self.apply(this instanceof fNOP ? this: context, args.concat(bindArgs));
  }
  // 创建空函数
  var fNOP = function () {};
  // fNOP函数的prototype为绑定函数的prototype
  fNOP.prototype = this.prototype;
  // 返回函数的prototype等于fNOP函数的实例实现继承
  fBound.prototype = new fNOP();
  // 以上三句相当于Object.create(this.prototype)
  return fBound;
}

var func = foo.es3Bind({a: 1}, '1st');
func('2nd');  // 1 100 1st 2nd
func.call({a: 2}, '3rd'); // 1 100 1st 3rd
new func('4th');  //undefined 100 1st 4th

es6实现

Function.prototype.es6Bind = function(context, ...rest) {
  if (typeof this !== 'function') throw new TypeError('invalid invoked!');
  var self = this;
  return function F(...args) {
    if (this instanceof F) {
      return new self(...rest, ...args)
    }
    return self.apply(context, rest.concat(args))
  }
}

var func = foo.es3Bind({a: 1}, '1st');
func('2nd');  // 1 100 1st 2nd
func.call({a: 2}, '3rd'); // 1 100 1st 3rd
new func('4th');  //undefined 100 1st 4th

原址

相关文章

网友评论

      本文标题:「转」JavaScript call, apply, bind实

      本文链接:https://www.haomeiwen.com/subject/scigcqtx.html