为什么要用this
回答这个问题我们就先看看如果不使用
this
会出现什么问题。试想下面代码如果不使用
this
应该怎么写:
function speak(){
var name = this.name
console.log(`Hello I am ${name}`)
}
var me = {
name: 'a',
speak: speak
}
var you = {
name: 'b',
speak: speak
}
me.speak() //Hello I am a
you.speak() //Hello I am b
this
可以在同一个执行环境中使用不同的上下文对象。它其实提供了一种更加优雅的方式来隐式“传递”一个对象引用,因此可以使API设计的更加简洁且易于复用。
this到底是谁
this
既不是自身也不是当前函数的作用域。我们可以通过代码来测试。
- 判断是不是自身
function fn(){
console.log(this.name)
}
fn.name = 'xxx'
fn() //undefined
- 判断是不是作用域
function foo() {
var a = 2;
this.bar();
}
function bar() {
console.log( this.a );
}
foo(); // ReferenceError: a is not defined
那么
this
到底是谁呢?其实不一定,
this
是运行时绑定的,所以取决于函数的执行上下文。
当一个函数被调用时,会创建一个活动记录(执行上下文)。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息,this也是这里的一个属性。
如何判断this指向
确定
this
指向就是确定函数的执行上下文,也就是“谁调用的它”,有以下几种判断方式:
独立函数调用
function foo(){
console.log(this.a)
}
var a = 2
foo() // 2
这种直接调用的方式
this
指向全局对象,如果是在浏览器就指向
window
对象上下文(隐式绑定)
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
obj.foo(); // 2
foo
虽然被定义在全局作用域,但是调用的时候是通过
obj
上下文引用的,可以理解为在
foo
调用的那一刻它被
obj
对象拥有。所以
this
指向
obj
。
这里有两个问题:
-
链式调用
链式调用的情况下只有最后一层才会影响调用位置,例如:
obj1.obj2.obj3.fn() //这里的fn中的this指向obj3
- 引式丢失
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
var bar = obj.foo; // 函数别名!
var a = "xxxxx"
bar(); // xxxxx
这里的
bar
其实是引用了
obj.foo
的地址,这个地址指向的是一个函数,也就是说
bar
的调用其实符合“独立函数调用”规则。所以它的
this
不是
obj
。
回调函数其实就是隐式丢失
稍微改一下上面的代码:
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
var a = "xxxxx"
setTimeout( obj.foo ,100); // xxxxx
我们看到,回调函数虽然是通过
obj
引用的,但是
this
也不是
obj
了。其实内置的setTimeout()函数实现和下面的伪代码类似:
function setTimeout(fn, delay){
//等待delay毫秒
fn()
}
其实这段代码隐藏这一个操作就是
fn=obj.foo
,这和上面例子中的
bar=obj.foo
异曲同工。
显式绑定
显式绑定的说法是和隐式绑定相对的,指的是通过
call
、
apply
、
bind
显式地更改
this
指向。
这三个方法第一个参数是
this
要指向的对象。
注意,如果你给第一个参数传递一个值(字符串、布尔、数字)类型的话,这个值会被转换成对象形式(调用new String(..)、new Boolean(..)、new Number(..))。
这三个方法中的
bind
方法比较特殊,它可以延迟方法的执行,这可以让我们写出更加灵活的代码。它的原理也很容易模拟:
function foo(something) {
console.log( this.a, something );
return this.a + something;
}
function bind(fn, obj) {
return function() {
return fn.apply( obj, arguments );
};
}
var obj = {
a:2
};
var bar = bind( foo, obj );
var b = bar( 3 ); // 2 3
console.log( b ); // 5
注意:如果第一个参数传入null或者undefined,这个值会被忽略,相当于符合独立函数调用规则