函数在创建之后,会构建自己的执行环境以及作用域,这是一开始就确定了。但是实际的上下文(context)环境,也可以理解为就是this,它是动态确定的,即在函数运行时才确定this所指向的对象,而非声明时所指向的对象。
对象方法中调用this
如果函数被当中对象的一个方法进行调用,则this值指向该对象。1
2
3
4
5
6
7
8var person = {
name: 'Alice',
sayName: function() {
alert('welcome ' + this.name);
}
}
person.sayName(); // this == person, alert: 'welcome Alice'
在这里,函数的this指向该对象(即 person);但是有一点需要注意,就是当对象的方法被赋予给一个变量时,其则变为了函数触发,此时的this为 window 或者 undefined(严格模式下),如下:
1 | var name = 'Bob'; |
函数内部使用
在函数内部当中使用了 this,即函数被当做方法使用,不同于 1 当中作为对象的方法使用,此时调用,是在全局作用域下进行调用,即在window下进行调用,由定义可以知道,在全局作用域下声明一个函数,其自动加为window的一个属性。this此时名正言顺的会指向window,严格模式下为 undefined1
2
3function sayThis() {
alert(this == window); // true
}
结合第一点,函数作为对象的一个方法使用,这里存在一个小坑,即闭包,啥是闭包,这个在这里就不扯开了,最简单的理解就是 Function that returns function,如果不理解什么是闭包的话,可以去翻翻 《JavaScript 高级程序设计》第七章关于闭包的相关内容。第一点当中存在一个小坑,就是将对象的方法赋予给一个变量的时候,其变为函数触发,此时的 this 实际上是指向 window(非严格模式)。
那么,当函数中返回一个函数,此时在对象当中调用该方法,其就相当于是函数触发,此时的 this,在不做任何上下文绑定的前提之下,其指向 window(非严格模式)。
1 | var name = 'Bob', |
当然,要解决这个问题的方法,很简单,就是给他绑定一个上下文。1
2
3
4
5
6
7
8
9
10
11
12
13
14var name = 'Bob',
person = {
name: 'Alice',
sayName: function() {
console.log(this === person); // true
return function() {
console.log(this === person); // true
console.log(this === window); // false
console.log(this.name); // Alice
}.bind(this);
}
};
person.sayName()();
new 当中进行使用
我们知道在使用 new 方法创建对象的时候,会经过如下这些个过程:
- 创建对象,将 this 值赋予新的对象
- 调用构造函数,为 this 添加属性和方法
- 返回 this 给当前的对象
1 | function Person(name, age) { |
这里要记得使用 new 运算符,否则,其只能算是普通的调用,而不是创建一个新的实例对象。而当做普通函数调用的话,实际上即 第 2 种情况下,对函数普通调用,此时的 this 指向 window
1 | function Person(name, age) { |
这是正常情况下,this 会正确返回并且指向该对象,但是在构造函数当中,如果返回了一个对象,那么 this 会指向返回的那个对象。1
2
3
4
5
6
7
8
9
10
11function Person(name, age) {
this.name = name;
this.age = age;
return {
name: 'Bob'
};
}
var person1 = new Person('Alice');
console.log(person1.name); // Bob
console.log(person1.age); // undefined
题外话,类似的,联想到 var a = new Person(),则 a instanceof Person一定返回 true吗?留给你们想一想咯。
4使用 call、apply 或 bind 改变 this
在引用类型 Function当中,函数存在两个方法属性,call 和 apply,在 ECMAScript5当中,加入了 bind 方法。题外话,他们三者区别,应该都知道了吧,不知道的加紧补习呀。
1 | var name = 'Bob'; |
这里是使用了 call 方法来改变了 this的执行环境,至于使用 apply,效果一样,只是二者差别在于传入参数的不同。1
2func.call(context, arg1, arg2, ...)
func.apply(context, [arg1, arg2, ...])
使用 bind 方法进行上下文的改变,bind 方法与 call 和 apply有着本质的不同,其不同点是,bind()函数返回的是一个新的函数,即方法,而后两者则都是立即执行函数,使用的时候即调用了该函数,返回方法操作的结果。
并且,使用 bind()方法创建的 上下文,其为永久的上下文环境,不可修改,即使是使用 call 或者 apply方法,也无法修改 this 所指向的值。
1 | var name = 'Bob'; |
箭头函数
箭头函数并不创建其自身的上下文,其上下文 this,取决于其在定义时的外部函数。
并且,箭头函数拥有静态的上下文,即一次绑定之后,便不可再修改,即使是用了 第 4 种用途当中的改变上下文的方法,也不为之动容。
1 | var num = [1, 2, 3]; |
由于箭头函数的外部决定上下文以及静态上下文等的特性,不太建议使用箭头函数在全局环境下来定义方法,因为不能通过其他方法改变其上下文。这很蛋疼。
1 | function Period (hours, minutes) { |
此时的 this 实际上是指向了 window,所以 this.hours 和 this.minutes实际上没有声明的,故为 undefined。
在全局环境下,还是选用 函数表达式 来进行函数的定义,可以保证正确的上下文环境1
2
3
4
5
6
7
8
9
10function Period (hours, minutes) {
this.hours = hours;
this.minutes = minutes;
}
Period.prototype.format = function() {
console.log(this === walkPeriod); // => true
return this.hours + ' hours and ' + this.minutes + ' minutes';
};
var walkPeriod = new Period(2, 30);
walkPeriod.format(); // '2 hours and 30 minutes'