bind() 和 箭头函数的this

箭头函数的This指向

更新 2017年11月26号


前言

楼主在昨天在看Vue文档的时候,主要到methodscomputed里面不要使用箭头函数,去看了下源码解析,发现里面调用的是通过自定义的bind函数,通过call()来执行函数以及绑定作用域,想巩固一下箭头函数,于是这篇有内涵的blog就上线了。

之前楼主有一篇箭头函数的This, 对于它的理解感觉有偏差,这里全部再重复总结一遍。

看完本篇文章,你可以彻底了解this和bind

涉及知识点

  1. bind函数的深入了解解析
  2. 作用域
  3. thisArg

实现一个bind函数

var fn = function(a,b,c,d) {
  return a+b+c+d ;
}
fn.bind(scope,a,b)(c,d)
// 调用方式  var fn1 = fn.bind(scope,a,b)  fn1(c, d)
// scope是传递进来的this
Function.prototype.bind = function(scope) {
  let newFn = this;
  // 获取通过bind传递的参数
  let args = Array.prototype.slice.call(arguments,1)
  let fbind = function() {
    // 这里执行函数,通过闭包绑定了this===scope
    // 然后通过concat合并2个参数
    return newFn.apply(scope,args.concat(Array.prototype.slice.call(arguments)))
  }
  return fbind
}

调用方式:

  1. 直接调用 (内部this一般是window)
  1. 构造函数的调用,通过bind绑定的this无效 (this是实例对象)

当使用构造函数的时候,我们需要构建原型链,所以需要加工一下。

Function.prototype.bind = function(scope) {
  if( typeof this !== 'function') {
    throw('this is illegal')
  }
  let newFn = this;
  // 获取通过bind传递的参数
  let args = Array.prototype.slice.call(arguments,1)
  let fbind = function() {
    // 里面的this可能是window和构造函数实例
    // 然后通过concat合并2个参数
    return newFn.apply(this instanceof fbind ? this : scope              ,args.concat(Array.prototype.slice.call(arguments)))
  }
  // 维持原型链
  if(this.prototype) {
    fbind.prototype = this.prototype
  }
  return fbind
}
// 
function Fn(a,b) {
  this.a = a
  this.b = b
}

Fn.prototype = function() { console.log(this)}
let hcc = Fn.bind({a:1})
// 直接调用
hcc()  // 返回一个新函数fbind,  fbind里面的this则是window
// 所以 this instanceof fbind 为 false 

// 构造函数的调用
let obj = new hcc() // fbind里面的this则是fbind的实例
// 所以 this instanceof fbind 为 true ,所以改变this没有生效 

解析 : 当我们调用bind()的时候,即执行了var fn1 = fn.bind({name: 1},1,3), 会返回一个新的函数,下面是作用域链解析。

实例 : 当我们调用bind()的时候,即执行了var fn1 = fn.bind({name: 1},1,3), 会返回一个新的函数,下面是作用域链解析。

bind => function(scope) {
  let newFn = fn;
  let args = [1,3]
  let fbind = function() {
    return newFn.apply(scope,[1,3].concat(Array.prototype.slice.call(arguments)))
  }
  return fbind
}

fn1 =>  function() {
    return fn.apply({name: 1},[1,3].concat(Array.prototype.slice.call(arguments)))
}

再调用fn1(3,4),相当于执行函数

 fn.apply({name: 1},[1,3].concat(Array.prototype.slice.call(arguments)))
 // 即相当于这样执行
 fn.apply({name: 1},[1,3,3,4]) =>  11

思考,下面函数执行时多少,this是什么

var fn = function(a,b) {
  console.log(this)
  return a+b ;
}
fn.bind({name:1},1,2).call({name:2},3,4)  

答案 {name:1} 3

//fn.bind({name:1},1,2)  返回xxx
function xxx() {
   return fn.apply({name: 1},[1,2].concat(Array.prototype.slice.call(arguments)))
} 

// xxx.call({name:2},3,4) 调用 xxx(绑定了xxx的this= {name:2})
// xxx里面通过apply调用已经制定了this的fn函数
 fn.apply({name: 1},[1,2,3,4]) // this => {name: 1} a=1, b=2 

所以当我们执行fn.bind({name:1},1,2).call({name:2},3,4) ,本质上call并不能改变bind的返回函数的this,只是改变了内部封装了一个函数(xxx)的this,这也是bind的this参数不能被重写的原因。

总结bind函数到底做了什么

fun.bind(thisArg[, arg1[, arg2[, ...]]])
// 简化版
Function.prototype.bind = function bind(self) {
  return function() { return fn.apply(self) }
}

一个函数(fn)使用函数原型链上面的bind函数的时候,传递this(thisArg)和参数进去,返回的是一个新函数(xxx),新函数内部调用的是通过apply调用原来的函数(fn)并制定原函数(fn)的this。用简单的代码表示就是:

function fn(a,b) {
  return a+b;
}
fn.bind({name:1},1,3)  相当于变成这样=> function xxx() {
  return fn.apply({name: 1},Array.prototype.slice.call(arguments));
}

箭头函数的this(定义时候的this)

一句话总结: 箭头函数的函数体内的this就是定义时候的this,和使用所在的this没有关系。

:在定义箭头函数的时候就已经绑定了this,可以理解为就是在定义的时候,通过bind函数进行强行绑定this。

案例一

var calculate = {
  array: [1, 2, 3],
  sum:() => {
    console.log(this === window) // => true
  }
};
// 当我们在定义sum是一个箭头函数的时候,还没有执行,内部已经绑定了this,而此时的this就是全局的window

//可以转换成
var calculate = {
  array: [1, 2, 3],
  sum:function() {
    console.log(this === window) // => true
  }.bind(this)
};

案例二

var calculate = {
  array: [1, 2, 3],
  sum() => {
    return () => {
      console.log(this === window) 
    }
  }
};
// 此时我们在写calculate.sum的时候,由于还没有执行,所有并不存在里面的箭头函数,当我们执行calculate.sum()才算生成了箭头函数,箭头函数就是在这个时候绑定this的,所有这里就会和怎么调用sum函数有关系了。

案例三

const App = new Vue({
    el: '#app',
    methods: {
        foo: () => {
            console.log(this) // undefined
        }
    }
})
// 如果我们在Vue的实例中的methods使用箭头函数,那么在定义的时候,箭头函数会自动绑定当前作用域的this,并不会是绑定实例中的this
// 初始化的时候,执行的initMethods中绑定了this(vm)
function initMethods (vm: Component) {
  const methods = vm.$options.methods
  if (methods) {
    for (const key in methods) {
      vm[key] = methods[key] == null ? noop : bind(methods[key], vm)
      if (process.env.NODE_ENV !== 'production' && methods[key] == null) {
        warn(
          `method "${key}" has an undefined value in the component definition. ` +
          `Did you reference the function correctly?`,
          vm
        )
      }
    }
  }
}

//bind

function bind (fn, ctx) {
  function boundFn (a) {
    var l = arguments.length;
    return l
      ? l > 1
        ? fn.apply(ctx, arguments)//通过返回函数修饰了事件的回调函数。绑定了事件回调函数的this。并且让参数自定义。更加的灵活
        : fn.call(ctx, a)
      : fn.call(ctx)
  }
  // record original fn length
  boundFn._length = fn.length;
  return boundFn
}

总结:我们知道Vue内部调用methods的时候,通过的call方法来执行methods中的相应的key函数,当我们使用箭头函数的时候,定义的时候就绑定了this,它源码中写的call()并不会被使用,所以必须不能使用箭头函数

Vue文档中methods的使用


参考文章

Vue methods 用箭头函数取不到 this

vue源码解析-事件机制

什么时候“不要”用箭头函数

ES6 箭头函数使用禁忌

推理例子

自己写的推理例子

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