js 中 this,apply,call,bind 详解

JavaScript 中关于 this、apply、call、bind 的介绍文章已经有不少了,看的越多,越会一头雾水。下面从常见的应用场景来讲明上述这些概念可能会容易理解一些。面试

this

this 表明函数运行时的环境。注意是函数,是函数,还有就是运行时!数组

function hello(){
    console.log(this) // window
}
hello()
复制代码

当 hello 函数执行时,在函数内部有个变量 this,表明这个函数运行环境,上述函数在全局环境下且在浏览器环境下运行,那么这个 this 就指向 window,这个比较好理解。可是许多如今许多 js 框架的设计都是使用构造函数方式设计的,例以下面构造函数:浏览器

function Dog(name,color){
    this.name = name;
    this.color = color;
    console.log(this)
}
var a = new Dog("阿黄","黄色")
复制代码

构造函数首先函数名称首字母都会大写,其次都会使用一个 new 来构造一个实例,new 的时候 构造函数就会运行一次,这个时候构造函数里的this就指向这个实例对象bash

上述构造函数只是实现了一些属性,其实更多的时候构造函数内部应该根据传入的值来实现一些方法,从而体现良好的封装性。例如使得上述狗类增长一个方法:闭包

function Dog(name,color){
  this.color = color;
  this.name = name;
  this.say = function(){
    console.log(`I am a ${this.color} dog ,my name is ${this.name}`)
  }
}

let ahuang = new Dog("阿黄","黄色")
ahuang.say() // I am a 黄色 dog ,my name is 阿黄

复制代码

首先先实例出来一个对象 ahuang ,再调用 ahuang 的 say 方法,say 方法内部能够获取到 color 和 name.app

如今稍做改动:框架

function Dog(name,color){
  this.color = color;
  this.name = name;
  this.say = function(){
    console.log(`I am a ${this.color} dog ,my name is ${this.name}`)
  }
}

let ahuang = new Dog("阿黄","黄色")
let a = ahuang.say
a() // I am a undefined dog ,my name is 

复制代码

a 变量只是获取了ahuang的 say 方法定义,尚未执行,此时的 a 定于在全局,若是直接这样执行,那么say方法的this天然也就指向了全局的 window 了,因为全局上没有color和name,因此就成 undefined了。函数

其实个人理解是:函数一个工具和机器,只负责执行,能够理解为函数独立于对象,犹如一个榨汁机,若是放入橙子,运行时天然就会榨出橙汁,若是放入苹果,天然就会榨出苹果汁。这里苹果和橙子就是运行环境。工具

对于给定什么环境运行函数,就可能产生不一样结果,这样虽然比较自由,但过度自由也会致使一些问题产生,例如上述 Dog 的 say 方法:ui

this.say = function(){
    console.log(`I am a ${this.color} dog ,my name is ${this.name}`)
 }
复制代码

显然这个方法只有运行于狗这个对象才有意义,放在全局虽然也能运行,但却失去了这个方法的意义。 因此须要一种机制可以始终使得这个方法可以在狗对象中运行。

方案一:采用箭头函数来定义
箭头函数绑定了运行环境,即定义在哪一个对象内,则这个箭头函数内部 this 始终为这个对象。

function Dog(name,color){
  this.color = color;
  this.name = name;
  this.say = ()=> {
    console.log(`I am a ${this.color} dog ,my name is ${this.name}`)
  }
}

let ahuang = new Dog("阿黄","黄色")
let a = ahuang.say
a() // I am a 黄色 dog ,my name is 阿黄
复制代码

上述函数,即便a在全局定义,可是 say 是箭头函数定义的,里面 this 依然指向 ahuang.

方案二 使用 call 或者 apply 来实现。

call 和 apply

call和 apply 方法功能是同样的,只是传入的参数形式不同,做用都是绑定(劫持)一个特定的执行环境:

func.call(this, arg1, arg2,...);
func.apply(this, [arg1, arg2])
复制代码

call 第一个参数为执行环境,其他参数为 func 的参数,能够有无数个参数,而apply只有两个参数,第一个为执行环境,第二个为其他数组,经过一个数组来传递。

例如上述狗的构造函数也可使用call来实现箭头函数效果:

function Dog(name,color){
  this.color = color;
  this.name = name;
  this.say = function(){
    console.log(`I am a ${this.color} dog ,my name is ${this.name}`)
  }
}
let ahuang = new Dog("阿黄","黄色")
let b = ahuang.say
b.call(ah
复制代码

使用call 和 apply 还有一些经常使用的应用。

1.求数组最大值

let arr = [1,2,3]
// 方法一:
Math.max(...arr)
// 方法二
Math.max.apply(Math,arr) // 巧妙地利用了第二个参数为数组特征
复制代码

2.判断数组

function isArray(obj){ 
    return Object.prototype.toString.call(obj) === '[object Array]' ;
}
复制代码

bind

bind 和 call,apply 也有类似之处,bind()方法会建立一个新函数,称为绑定函数,当调用这个绑定函数时,绑定函数会以建立它时传入 bind()方法的第一个参数做为 this,传入 bind() 方法的第二个以及之后的参数加上绑定函数运行时自己的参数按照顺序做为原函数的参数来调用原函数。

function Dog(name,color){
  this.color = color;
  this.name = name;
  this.say = function(){
    console.log(`I am a ${this.color} dog ,my name is ${this.name}`)
  }
}
let ahuang = new Dog("阿黄","黄色")
let b = ahuang.say.bind(ahuang) // 使用bind后b返回的是一个函数,这个函数内的this永远是ahuang了
b() // I am a 黄色 dog ,my name is 阿黄

复制代码

可见 bind 绑定执行环境是静态的,关键是在定义时就能够绑定,而不是像 call 那样须要调用时去绑定.有点相似于箭头函数。

bind 通常有几个经典的应用场景:

[1,2,3].forEach(function(){
    console.log(this) // window
})
复制代码

匿名的回调函数里面 this 通常为指向 window 假如咱们要在这个函数内用到一些其余对象的值,则能够经过bind来改变回调函数的值,以Vue框架为例:

let vm = new Vue({
  data(){
    return {
      height:768
    }
  },
  mounted(){
    window.addEventListener("resize",(function(){
      this.height = document.body.clientHeight
    }).bind(this))
  }
})
复制代码

若是不绑定this,则回调函数内this为 window ,显然读取不到this.height,固然还可使用箭头函数或者声明一个中间变量来解决:

mounted(){
    window.addEventListener("resize",()=>{
      this.height = document.body.clientHeight
    })
    // 或者
    let That =this
     window.addEventListener("resize", function(){
      That.height = document.body.clientHeight
    })
  }
复制代码

因此在 Vue 框架内建议多用箭头函数来定义,forEach,map等方法也是如此!

实现原理

不少面试时候可能会问到 call 和 bind 实现原理,并手写一个。其实这并不难

call

以上述榨汁机的解释,例若有个榨汁机如今在榨橙汁,如今咱们想让榨汁机在苹果的环境中运行。

榨汁机.call("苹果")
复制代码

调用 call 时会传入苹果对象,咱们须要在苹果对象上增长榨汁机的方法,再执行一次,执行完毕后再把原属于榨汁机的方法给删掉便可!虽然有点牵强,但实际就是这么干的。

Function.prototype.myCall = function(){
  let args = [...arguments]
  let ctx = args.length>0 ? args.shift() : window
  let s = Symbol() // 生成一个惟一值
  // 在被劫持者对象属性中加入这个属性
  ctx[s] = this
  let result = ctx[s](...args)
  delete ctx[s]
  return result
}
复制代码

bind

bind 返回的是一个函数,或者说闭包

Function.prototype.myBind = function(){
  let args = [...arguments]
  let ctx = args.length>0 ? args.shift() : window
  let s = Symbol() // 生成一个惟一值
  // 在被劫持者对象属性中加入这个属性
  ctx[s] = this
  return function(){
    let res = ctx[s](...args) // 定义了在特定的上下文运行的结果,当执行时就能获得这个特定上下文的结果。
    delete ctx[s]
    return res
  }
}
复制代码

完!

相关文章
相关标签/搜索