Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bind() 和 箭头函数的this #37

Open
huangchucai opened this issue Sep 22, 2017 · 0 comments
Open

bind() 和 箭头函数的this #37

huangchucai opened this issue Sep 22, 2017 · 0 comments

Comments

@huangchucai
Copy link
Owner

huangchucai commented Sep 22, 2017

箭头函数的This指向


前言

楼主在昨天在看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)

  2. 构造函数的调用,通过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 => 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 箭头函数使用禁忌

推理例子

自己写的推理例子

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant