在面向对象的编程中,最常用到的就是this关键词。
this是在执行函数时绑定的,而不是在声明的时候绑定的,他的上下文取决于函数调用时的各种条件,和声明的位置没有关系,只和调用的位置有关。
this有四种绑定方式:
第一种:默认绑定
var a = 9;
function abc() {
console.log(this.a)
}
abc() // 9
在这段代码中,this.a被解析成了全局的a,等同于window.a。
其实可以这样理解,调用abc()函数的时候,完整的调用方法应该是window.abc(),因为函数abc()是全局的,默认挂载到了window下面,那么根据this指向只取决于函数执行,也就是调用的位置,且此时离abc()函数最近的上下文是window,所以在这里函数运用了this的默认绑定,this也就指向了window。而变量a的作用域是全局的,所以也是挂载到window下的,故this.a = 9。那么打印结果为9也就顺理成章了。
需要指出的是,如果在严格模式下,this是无法指向全局的
var a = 9;
function abc() {
"use strict"
console.log(this) // undefined
console.log(this.a) // Cannot read property 'a' of undefined
}
abc()
因为this是undefined,所以this.a也同样报错
第二种:隐式绑定
这取决于调用位置是否有上下文对象
function abc() {
console.log(this.a)
}
var obj = {
a: 9,
abc: abc
}
obj.abc() // 9
虽然obj能成功调用abc函数,但实际上函数并不属于obj对象,obj拥有的只是函数的引用。但是调用位置会使用obj上下文来引用函数,所以可以这么认为:函数被调用时,obj对象是“拥有”函数引用的。
在函数abc执行的时候,他前面加上了对obj的引用,此时隐式绑定会把调用函数中的this指向这个上下文对象,即obj对象。所以this.a就等同于obj.a
并且,对象属性引用链中,this指向离调用函数最近的上下文对象。
function abc() {
console.log(this.a)
}
var obj1 = {
a: 56,
abc: abc
}
var obj2 = {
a: 33,
obj1 : obj1
}
obj2.obj1.abc() // 56
这里this指向离执行函数最近的obj1,而不是obj2。
隐式丢失
被隐式绑定的this有时候会丢失绑定对象,从而默认绑定到全局对象上去:
var a = 9 // 全局变量
function abc() {
console.log(this.a)
}
var obj = {
a: 99,
abc: abc
}
var baz = obj.abc
baz() // 9
这里虽然baz是obj.abc的一个引用,但因为obj.abc是abc的一个引用,也就是obj.abc实际上是abc()函数,所以baz实际上也就等于abc函数本身,和obj对象没啥关系了,因此,baz()是一个不带任何修饰的函数调用,等同于abc()调用,this也就自然绑定到了全局window上了。
还有一种比较隐蔽的情况会导致this的绑定丢失:
var a = 9 // 全局变量
function abc() {
console.log(this.a)
}
function doAbc(fn) {
fn() // 调用位置,引用的其实是abc()
}
var obj = {
a: 3,
abc: abc
}
doAbc(obj.abc) // 9
和之前的原因一样,传入的其实是abc的引用,最终和对象obj没有一点关系,相当于直接调用abc函数,所以this引用指向全局。
第三种:显式绑定
要达到隐式绑定的效果,必须在一个对象内部包含一个指向函数的属性,通过调用这个属性间接引用函数,从而把this间接绑定到这个对象上。如果我们不想在对象内包含函数引用,而想在某个对象上强制调用函数,达到把this绑定到该对象上,那就要用到显式绑定。
显式绑定依赖于javascript给所有函数提供的两个方法:call()和apply()
第一个参数是一个对象,也就是想要在调用时让this指向的对象
function abc() {
console.log(this.a)
}
var obj = {
a: 5
}
abc.call(obj) // 5
通过.call()方法,在调用函数时,强制把this绑定到obj上。
如果传入的是一个原始值,比如字符串、数字或者布尔类型,那么调用的时候回自动转换成其对应的对象形式:new String()、new Number()、new Boolean()。
为了解决绑定丢失的问题,需要用到硬绑定:
function abc() {
console.log(this.a)
}
var obj = {
a: 5
}
var bar = function() {
abc.call(obj)
}
bar() // 2
setTimeout(bar(), 100) // 2
bar.call(window) // 2
每次调用bar()函数的时候,内部都会显示的将this指向obj,bar.call()是无法修改this指向的,这种显式的强制绑定称为硬绑定.
es5提供了内置方法bind来实现硬绑定:
function foo(num) {
console.log(this.a, num)
return this.a + num
}
var obj = {
a: 4
}
var bar = foo.bind(obj)
var b = bar(3) // 4 3
console.log(b) // 7
.bind()会返回一个硬编码的函数,把你指定的参数设置为this的上下文并调用原始函数。
第三种:new绑定
在定义好函数后,都会new一下,生成一个新的对象,new的过程中会执行下面的操作:
1、创建一个全新的对象
2、给这个对象挂载prototype属性
3、新对象会绑定到函数调用的this(调用这个对象下的函数方法时,this会指向该对象)
4、如果函数没有返回其他对象,那new表达式中的函数调用会返回这个新对象
es6箭头函数
es6中用箭头定义函数=>,不遵循this的四条规则,而是根据外层作用域来决定this:
function foo() {
return (a) => {
console.log(this.a)
}
}
var obj = {
a: 7
}
var obj1 = {
a: 9
}
var bar = foo.call(obj)
bar.call(obj1) // 7
结果是7,而不是9,这是因为foo()内部创建的箭头函数会捕获调用时foo()的this,由于调用时this绑定了obj,bar(引用箭头函数)的this也会绑定到obj,箭头函数的绑定无法被修改。