# 直接调用函数

function fn() {
    console.log(this)
}
fn() // 全局对象

console.log输出的内容是全局对象,fn()可以理解为window.fn(),在浏览器环境下则是window对象。

# 对象函数调用

谁调用函数,this则指向谁

let obj = {
    a: 111,
    fn: function () {
        console.log(this.a)
    }
}
obj.fn() // 111

let obj2 = {
    a: 222
}
obj2.fn = obj.fn
obj2.fn() // 222

# 构造函数调用

let Student = function () {
    this.name = 'lim'
}
let student = new Student()
console.log(student.name) // lim
student.name = 'zhong'
console.log(student.name) // zhong

之前笔记记载过new到底做了些什么,其实就是3件事:创建一个空对象,将空对象的__proto__属性指向构造函数的prototype,已该空对象为上下文调用构造函数。因此该例子中的student其实就是new过程中的空对象,因此有一个name的属性。

# apply、call、bind

apply、call、bind都可以用来改变一个函数的this指向,区别在于,apply和call的第二个参数形式上有些不一样,apply的第二个参数是个数组,call则是一个参数列表;而bind可以永久绑定一个函数的上下文。多次bind只有第一个生效。

# 箭头函数

箭头函数中的this是固定的

function foo () {
	setTimeout(function () {
		console.log(this.id)
	}, 1000)
}

var id = 12
foo.call({id: 24}) // 12

function foo () {
	setTimeout(() => {
		console.log(this.id)
	}, 1000)
}

var id = 12
foo.call({id: 24}) // 24

上述例子运行在浏览器console中。第一个例子中,setTimeout的回调函数是普通函数,但是console.log出来的结果是12,这是因为发生回调函数的时候,原本的执行上下文已经不存在了。而第二个例子中,回调函数使用了箭头函数,foo是通过对象{id: 24}调用的,因此函数中的this指向该对象,箭头函数的特点在于,它的this是继承自外面环境的,它本身是没有this的。这有什么好处呢?看下面例子:

var handler = {
  id: '123456',

  init: function() {
    document.addEventListener('click',
      event => this.doSomething(event.type), false);
  },

  doSomething: function(type) {
    console.log('Handling ' + type  + ' for ' + this.id);
  }
}

上面例子中,init方法执行一个事件绑定,采用了箭头函数。考虑一下这样一个场景:当我通过handler调用init的时候,init中箭头函数的this就指向handler对象了,如果不采用箭头函数会发生什么呢?那就是当我们点击绑定了click事件的dom元素时会报错,为什么呢?因为这是个回调函数,和上面例子中一样,不使用箭头函数的话,回调函数执行的时候,原来的上下文已经不在了,this指向了window,而window中没有doSomething这个方法,因此会报错。使用箭头函数,this始终指向handler对象,因此当回调函数发生时,是会调用handler中的doSomething的。

箭头函数转成ES5的代码如下:

// ES6
function foo() {
  setTimeout(() => {
    console.log('id:', this.id);
  }, 100);
}

// ES5
function foo() {
  var _this = this;

  setTimeout(function () {
    console.log('id:', _this.id);
  }, 100);
}

从这里看出,箭头函数的this就是定义时的this。正因为this中本身没有this,因此下面代码中只有一个this:

function foo() {
  return () => {
    return () => {
      return () => {
        console.log('id:', this.id);
      };
    };
  };
}

var f = foo.call({id: 1});

箭头函数也有一些不适用的场合:

定义函数的方法,且该方法内部包括this:

const cat = {
  lives: 9,
  jumps: () => {
    this.lives--;
  }
}

当调用cat.jumps()时,this的指向是全局对象,并非cat。

第二个场合是需要动态this的时候,也不应使用箭头函数。

var button = document.getElementById('press');
button.addEventListener('click', () => {
  this.classList.toggle('on');
});

这里的this也是指向全局对象。