bind、apply、call的区别与手写实现

首先介绍一下bind、call、apply的作用,然后再介绍区别 | 手写实现往下翻

作用

bind:改变函数运行时this指向,第二个参数为参数列表,返回函数
call:改变函数运行时this指向,第二个参数为参数列表,函数执行
apply:改变函数运行时this指向,第二个参数为数组,函数执行

区别

apply、call异同
同:改变函数运行时this指向,函数执行
异:apply第二个参数为数组;call第二个参数为参数列表
bind与call异同
同:改变函数运行时this指向,第二个参数为参数列表
异:bind返回函数(未执行);call执行函数

手写实现

如何用Javascript实现call?

首先摆出成品代码

//ES6写法
Function.prototype.myCall = function (context) {
  var context = context || window;
  context.fn = this;
  var args = [...arguments].slice(1);
  var result = context.fn(...args);
  delete context.fn;
  return result;
}
//ES5写法
Function.prototype. myCall = 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 + ']');
  }
  var result = eval('context.fn(' + args + ')');
  delete context.fn
  return result;
}

接下来,按步骤实现
call()函数的作用:1.改变this指向2.第二个参数为参数列表

第一步,实现改变this指向

思路:把函数挂在对象上,然后执行函数,不就实现了改变this指向,最后再把对象上的函数删除。
具体实现如下:

var foo = {
    value: 1
};
function bar() {
    console.log(this.value);
}
//添加到对象上
foo.bar = function(){
  console.log(this.value)
}
//执行
foo.bar();//1
//从对象上删除
 delete foo.bar;

那么第一版本的myCall则如下

// 第一版
Function.prototype.myCall = function(context) {
  // 首先要获取调用call的函数,用this可以获取
  context.fn = this;
  context.fn();
  delete context.fn;
}

// 测试一下
var foo = {
  value: 1
};

function bar() {
  console.log(this.value);
}

bar.myCall(foo); // 1

控制台打印1,第一步成功.
但仍然存在一个小问题,如下:

this 参数可以传 null,当为 null 的时候,视为指向 window。则最终代码如下:

// 第一版
Function.prototype.myCall = function(context) {
  var context = context || window;
   // 给 context 添加一个属性
  context.fn = this;
  context.fn();
// 删除 fn
  delete context.fn;
}

// 测试一下
var foo = {
  value: 1
};

function bar() {
  console.log(this.value);
}

bar.call2(foo); // 1
第二步,传递参数

插入一个知识点arguments:函数执行时的参数组成的类数组。

思路:我们截取arguments从下标1到最后一个,转化为数组,再转为列表即可(借用ES6扩展运算符,可以很简单的实现)

Function.prototype.myCall = function (context) {
  var context = context || window;
  // 给 context 添加一个属性
  context.fn = this
  // 将 context 后面的参数取出来
  var args = [...arguments].slice(1)
  var result = context.fn(...args)
  // 删除 fn
  delete context.fn
  return result
}

这样call函数就实现了。

如何用Javascript实现apply?

由于apply和call只有参数格式的区别,所以实现类似,代码如下

Function.prototype.myApply = function (context) {
  var context = context || window
  context.fn = this

  var result
  // 需要判断是否存储第二个参数
  // 如果存在,就将第二个参数展开
  if (arguments[1]) {
    result = context.fn(...arguments[1])
  } else {
    result = context.fn()
  }

  delete context.fn
  return result
}

如何用Javascript实现bind?

先贴上最终版本,在按步骤实现


Function.prototype.myBind= function (context) {
 
    if (typeof this !== "function") {
      throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
    }
 
    var fn = this;
    var args = Array.prototype.slice.call(arguments, 1);
 
    var fNOP = function () {};
 
    var fBound = function () {
        var bindArgs = Array.prototype.slice.call(arguments);
        fn.apply(this instanceof fNOP ? new fn(...arguments) : context, args.concat(bindArgs));
    }
 
    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();
    return fBound;
第1步,返回函数

思路:关于返回函数的指定this,我们可以使用call模拟实现。
代码如下

//返回函数
Function.prototype.myBind = function (context) {
    var fn = this;
    return function () {
        fn.apply(context);
    }
第2步,传递参数
// 传递参数
Function.prototype.myBind = function (context) {
 
  var fn = this;
  // 获取myBind函数从第二个参数到最后一个参数
  var args = Array.prototype.slice.call(arguments, 1);

  return function () {
      // 这个时候的arguments是指bind返回的函数传入的参数
      var bindArgs = Array.prototype.slice.call(arguments);
      fn.apply(context, args.concat(bindArgs));
  }
}

通过调用bind返回的函数,也能使用New创建对象,但是此时构造函数中的this指向实例并不指向bind的this,参数仍然生效。

看个例子


var value = 2;
var foo = {
    value: 1
};
 
function bar(name, age) {
    this.habit = 'sleep';
    console.log(this.value);
    console.log(name);
    console.log(age);
}
 
bar.prototype.friend = '王五';
 
var bindFoo = bar.bind(foo, '张三');
 
var obj = new bindFoo('18');
// undefined
//张三
// 18
console.log(obj.habit);//sleep
console.log(obj.friend);// '王五'

分析:虽然foo对象和全局都定义了value,但是返回了undefind,this没有指向foo,而且this指向了实例obj。
思路:判断一下构造函数是否执行,可以通过instanceof判断。

//构造函数
//构造函数
Function.prototype.myBind = function (context) {
  var fn = this;
  var args = Array.prototype.slice.call(arguments, 1);
  var fBound = function () {
      var bindArgs = Array.prototype.slice.call(arguments);
      // 当作为构造函数时,this 指向实例,此时结果为 true,将绑定函数的 this 指向该实例,可以让实例获得来自绑定函数的值
      // 以上面的是 demo 为例,如果改成 `this instanceof fBound ? null : context`,实例只是一个空对象,将 null 改成 实例 ,实例会具有 habit 属性
      // 当作为普通函数时,this 指向 window,此时结果为 false,将绑定函数的 this 指向 context
      fn.apply(this instanceof fBound ? new fn(...arguments) : context, args.concat(bindArgs));
  }
  // 修改返回函数的 prototype 为绑定函数的 prototype,实例就可以继承绑定函数的原型中的值
  fBound.prototype = this.prototype;
  return fBound;
}

分析:我们直接将 fBound.prototype = this.prototype,直接修改 fBound.prototype 的时候,也会直接修改绑定函数的 prototype.可以用一个函数实例转化一下。


// 第四版
Function.prototype.myBind = function (context) {
   var fn = this;
   var args = Array.prototype.slice.call(arguments, 1);

   var fNOP = function () {};

   var fBound = function () {
       var bindArgs = Array.prototype.slice.call(arguments);
       fn.apply(this instanceof fNOP ? new fn(...arguments) : context, args.concat(bindArgs));
   }

   fNOP.prototype = this.prototype;
   fBound.prototype = new fNOP();
   return fBound;

至此,成功实现。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容