this的作用

function identify() {
  return this.name.toUpperCase()
}

function speak() {
  var greeting = "Hello" + identify.call(this)
  console.log(greeting)
}

var me = {
  name: 'aa'
}
var you  = {
  name: 'bb'
}
identify.call(me) // AA
identify.call(you) // BB

speak.call(me) // HelloAA
speak.call(you) // HelloBB

这段代码可以在不同的上下文对象中重复使用函数identify()和speak()、不用针对每个对象编写不同版本的函数。
如果不使用this,那就需要给identify()和speak()显示传入一个上下文对象

function identify(context) {
  return context.name.toUpperCase()
}

function speak(context) {
  var greeting = "Hello" + identify(context)
  console.log(greeting)
}

identify(you) // BB
speak(me) //HelloAA

this提供了一种更优雅的方式来隐式”传递“一个对象引用,因此可以将API设计得更加简洁并且易于复用,随着使用的模式越来越复杂,显示传递上下文对象会让代码变得越来越混乱,使用this则不会这样

this是什么

人们很容易把this理解成指向函数自身,从英文翻译上理解似乎是对的,下面看个例子:

function foo(num) {
  console.log("foo" + num)
  this.count++
  console.log(this === window) // true
}
foo.count = 0
var i;
for (i = 0; i < 10; i++) {
  if (i > 5) {
    foo(i)
  }
}
console.log(foo.count) // 0

console.log语句产生了4条输出,证明foo(...)确实被调用了4次,但是foo.count任然是0。显然从字面意思来理解this行不通
执行foo.count = 0时的确向函数对象foo添加了一个属性count。但是函数内部代码this.count的this并不是指向那个函数对象,指向的是全局对象window

那么this到底是什么呢?
this是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件。this的绑定和函数声明的位置没有任何关系,只取决于函数调用的方式(调用位置)。
当一个函数被调用时,会创建一个活动记录(执行上下文)。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方式、传入的参数等信息。this就是这个记录的一个属性,会在函数执行的过程中用到

绑定规则

默认绑定

首先介绍的是最常用的函数调用类型:独立函数调用。可以把这条规则看作是无法应用其他规则时的默认规则,举个例子:

function foo() {
  console.log(this.a) //2
}
var a = 2
foo()

当调用foo()时,this.a被解析成了全局变量a,因为此时foo()是直接使用不带任何修饰的函数引用进行调用的,只能用默认绑定,无法应用其他规则
但是当严格模式,不能将全局对象应用于默认绑定,this会绑定到undefined

隐式绑定

另一条需要考虑的规则是调用位置是否有上下文对象。举个例子

function foo() {
  console.log(this.a)
}
var obj = {
  a: 2,
  foo: foo
}
obj.foo() // 2

当foo()被调用时,它的前面加上了对obj的引用,当函数引用有上下文对象是,隐式绑定规则会把函数调用中this绑定到这个上下文对象。
对象属性引用链中只有上一层或者说最后一层在调用位置中起作用。举个例子

function foo() {
  console.log(this.a)
}
var obj2 = {
  a: 42,
  foo: foo
}
var obj1 = {
  a: 2,
  obj2: obj2
}
obj1.obj2.foo() // 42

隐式丢失

隐式绑定最常见的一个问题就是被隐式绑定的函数会丢失绑定对象,也就是会所它会应用默认绑定,从而把this绑定到全局对象或者undefine上,取决于是否严格模式,举个例子:

function foo() {
  console.log(this.a)
}
var obj = {
  a: 2,
  foo: foo
}

var bar = obj.foo;
var a = "global"
bar() //相当于执行 foo() "global"

虽然bar是obj.foo的一个引用,但是实际上,它引用的是foo函数本身,因此此时的bar()其实是一个不带任何修饰的函数调用,因此应用了默认绑定

显示绑定

javascript中提供的显示绑定的方法就是大家都非常熟悉的call(...)、apply(...)和bind(...)方法,下面就简单介绍一下它们的用法:
call的语法为:

fun.call(thisArg[, arg1[, arg2[, ...]]])

apply的语法为:

fun.apply(thisArg, [argsArray])

它们的第一个参数对象是一个对象,是个this准备的,接着在调用函数将其绑定到this。第二个参数 call 方法接受的是若干个参数列表,而apply接收的是一个包含多个参数的数组。

bind()方法创建一个新的函数, 当被调用时,将其this关键字设置为提供的值,在调用新函数时,在任何提供之前提供一个给定的参数序列。因此:bind 是创建一个新的函数,我们必须要手动去调用,举个例子:

var a ={
  fn : function (a,b) {
    console.log( a + b)
  }
}

var b = a.fn;
b.bind(a,1,2)() // 3

new绑定

使用new来调用函数,会自动执行下面的操作

  1. 创建(或者说构造)一个全新的对象
  2. 这个新对象会被执行[[Prototype]]连接
  3. 这个对象会绑定到函数调用的this
  4. 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象

举个例子:

function foo(a) {
  this.a = a
}
var bar = new foo(2)
console.log(bar.a) // 2

使用new来调用foo(...)时,会构造一个新对象并把它绑定到foo(...)调用中的this上

绑定优先级

  1. 函数是否在new中调用(new绑定)?如果是的话this绑定的是新创建的对象
  2. 函数是否通过call、apply、bind(显示绑定)?如果是的话,this绑定是指定的对象
  3. 函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this绑定的是那个上下文对象
  4. 如果都不是,使用默认绑定,如果在严格模式下就绑定到undefined,否则绑定到全局对象

ES6中this指向

ES6中的箭头函数并不会使用上述的绑定规则,而是根据当前的词法作用域来决定this,具体来说,箭头函数会继承外层函数调用的this绑定(无论this绑定到什么)举个例子:

function Timer() {
  this.s1 = 0;
  this.s2 = 0;
  setInterval(() => this.s1++, 1000);
  setInterval(function () {
    this.s2++;
  }, 1000);
}
var timer = new Timer();

setTimeout(() => console.log('s1: ', timer.s1), 1100); // 1
setTimeout(() => console.log('s2: ', timer.s2), 1100); // 0

上例中s1的值是在箭头函数中,会继承Timer函数作用域中的this,而s2在普通函数中,根据之前的绑定绑定规则可以看出此时的this会执行默认的绑定规则绑定到全局对象上


william
2.7k 声望826 粉丝

love and share