AwesomeDevin / blog

Welcome to Devin's blog,I'm trying to be a fullstack developer and sticking with it !!!

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

【Javascript】探究bind()的作用及实现原理

AwesomeDevin opened this issue · comments

bind()的作用

在js中,我们通常使用bind()来修改this指向

bind()方法创建一个新的函数,在bind()被调用时,这个新函数的this被bind的第一个参数指定,其余的参数将作为新函数的参数供调用时使用。
我们来看1个demo

var module = {
  x: 42,
  getX: function() {
    return this.x;
  }
}

module.getX.prototype.sayHi = function(name){
  return 'I am ' + name
}

var unboundGetX = module.getX
console.log(module.getX(),new module.getX().sayHi('module.getX'))   // 42 "I am module.getX"   
console.log(unboundGetX(),new unboundGetX().sayHi('unboundGetX'))    // undefined "I am unboundGetX"

unboundGetX()的this指向window,所以输出this.x为undefined,现在,我们使用bind来修改unboundGetX()的this指向

var boundGetx = module.getX.bind(module)  //this指向变量module
console.log(boundGetx(),new boundGetx().sayHi('boundGetx'))   //42 "I am boundGetx"

可以看到bind()返回的函数,其this指向已经指向了module,this.x为=42

不了解this作用域的同学可以先看一下这篇文章【Javascript】深入理解this作用域问题并探究new/let/var/const对this作用域的影响

bind()的实现原理

现在,我们来尝试实现一个newbind(),使得boundGetx的this指向变量module

var boundGetx = module.getX.newbind(module)   

newbind()实现

Function.prototype.newbind = function(){
  const oThis = Array.prototype.shift.call(arguments)  // 获取参数module
  const params = Array.prototype.slice.call(arguments)    
  oThis.fn = this   //函数function
  const res = function(){
    return oThis.fn.apply(
      oThis,params.concat(Array.prototype.slice.call(arguments))
    )   //修改函数function的this指向并执行

    // return oThis.fn(...params.concat(Array.prototype.slice.call(arguments)))   //一样

  }
  if(this.prototype)
  {
     const fn = function(){}
     fn.prototype = this.prototype  //维护原型关系
     res.prototype === new fn()
  }
  return res
}

此时newbind已经返回了1个新的函数

验证newbind正确性

var unboundGetX = module.getX
var boundGetx = module.getX.newbind(module)   

console.log(module.getX(),new module.getX().sayHi('module.getX'))   // 42 "I am module.getX"   
console.log(unboundGetX(),new unboundGetX().sayHi('unboundGetX'))    // undefined "I am unboundGetX"
console.log(boundGetx(),new boundGetx().sayHi('boundGetx'))   //42 "I am boundGetx"

可以发现unboundGetX()仍然输出undefined,但是boundGetx()其this已经指向了module,this.x为42

注意事项

  • res.prototype = this.prototype 存在问题,由于对象属于引用类型,这样的写法会导致module.getX.prototype会随着boundGetx.prototype改变,我们尝试修改一下boundGetx.prototype
boundGetx.prototype.sayHi = function(name){
  return 'changed,I am ' + name
}

console.log(new boundGetx().sayHi('boundGetx'))  // changed,I am boundGetx
console.log(new module.getX().sayHi('module.getX')) //changed,I am module.getX

可以发现boundGetx()module.getX()sayHi()都被改变了,我们需要将代码改为

    res.prototype = new this()  //维护原型关系

    // or
    // const fn = function(){}   
    // fn.prototype = this.prototype
    // res.prototype = new fn()

bind与call,apply的差别

bind返回的是1个函数,call,apply返回值为undefined

Function.prototype.call = function(obj,...args){
	obj.fn = this
	obj.fn(...args)
	delete obj.fn
}

完整代码

codesandbox 运行代码

Function.prototype.newbind = function(){
  const oThis = Array.prototype.shift.call(arguments)  // 第1个参数
  const params = Array.prototype.slice.call(arguments)
  oThis.fn = this   //函数function
  const res = function(){
    return oThis.fn.apply(
      oThis,params.concat(Array.prototype.slice.call(arguments))
    )   //修改函数function的this指向并执行
  }
  if(this.prototype)
  {
    res.prototype = new this()  //维护原型关系
  }
  return res
}


var module = {
  x: 42,
  getX: function(val) {
    return val ? val : this.x 
  }
}
module.getX.prototype.sayHi = function(name){
  return 'I am ' + name
}

var unboundGetX = module.getX;
//使用bind修改unboundGetX的this指向
var boundGetx = module.getX.newbind(module)

console.log(module.getX(),new module.getX().sayHi('module.getX'))     //this指向变量module
console.log(unboundGetX(),new unboundGetX().sayHi('unboundGetX'))     //this指向window,所以为undefined
console.log(boundGetx(),new boundGetx().sayHi('boundGetx'))   //this指向变量modul
console.error('---------sayHi was changed------')
boundGetx.prototype.sayHi = function(name){
  return 'changed,I am ' + name
}
console.log(new boundGetx().sayHi('boundGetx'))
console.log(new module.getX().sayHi('module.getX'))