谈到js中的this,首先要声明的是,this是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件。this的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。当一个函数被调用时,会创建一个活动记录(有时候也成为执行上下文)。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。this就是记录的其中的一个属性,会在函数执行的过程中用到。
要理解this的使用方法,最关键的是理解调用位置和绑定规则,下面我将一一介绍:
调用位置
通常来说,寻找调用位置就是寻找“函数被调用的位置”,但是做起来并没有那么简单,因为某些编程模式可能会隐藏真正的调用位置。
最重要的是要分析调用栈(就是 为了到达当前执行位置所调用的所有函数)。我们关心的调用位置就在当前正在执行的函数的前一个调用中。
下面我们来看看到底什么是调用栈和调用位置:
function baz(){
//当前的调用栈是:baz
//因此,当前的调用位置是全局作用域
console.log("baz");
bar();//bar的调用位置
}
function bar(){
//当前的调用栈是:baz→bar
//因此,当前的调用位置是在baz中
console.log("bar");
foo();//foo的调用位置
}
function foo(){
//当前的调用栈是:baz→bar→foo
//因此,当前的调用位置是在bar中
console.log("foo");
}
baz();//baz的调用位置
调用位置决定了this的绑定
绑定规则
我们来看看在函数的执行过程中调用位置如何决定this的绑定对象
默认绑定
function foo(){
console.log(this.a);
}
var a=2;
foo();//控制台输出2
控制台之所有可以输出2,是因为this默认绑定为全局对象,而a为全局属性,这便是默认绑定。当然默认绑定只在非严格模式下才可以执行,至于严格模式的定义,这里不再赘述。
隐式绑定
function foo(){
console.log(this.a);
}
var obj={
a:2,
foo:foo//给obj对象添加foo方法
}
obj.foo();//控制台输出2
首先需要注意的是foo()的声明方式,及其之后是如何被当作引用属性添加到obj中的。但是无论是直接在obj中定义还是先定义再添加为引用属性,这个函数严格来说都不属于obj对象
然而,调用位置会使用obj上下文来引用函数,因此你可以说函数被调用时obj对象“拥有”或者“包含”它。
对象属性引用链中只有最顶层或者说最后一层会影响调用位置。举例来说
function foo(){
console.log(this.a);
}
var obj2={
a:42,
foo:foo
}
var obj1={
a:2,
obj2:obj2
}
obj1.obj2.foo();//42
显示绑定
如果我们 不想在对象内部包含函数引用,而想在某个对象上强制调用函数,那么该怎么做呢?
JavaScript中的几乎所有函数都有一些有用的特性,可以用来解决这个问题。具体来说,可以使用函数的call()和apply()方法。JavaScript提供的绝大多数函数以及你自己创建的所有函数都可以使用call()和apply()方法。
这两个方法是如何工作的呢?它们的第一个参数是一个对象,它们会把这个对象绑定到this,接着在调用函数时指定这个this。因为你可以直接指定this的绑定对象,因此我们称之为显示绑定。
function foo(){
console.log(this.a);
}
var obj={
a:2
};
foo.call(obj);//2
通过foo.call(),我们可以再调用foo时强制把它的this绑定到obj上。
new绑定
在讨论new绑定之前我们首先需要澄清一个非常常见的关于JavaScript中函数和对象的误解。
在传统面向类的语言中,“构造函数”是类中的一些特殊方法,使用new初始化类时会调用类中的构造函数。通常的形式是这样的:
something=new MyClass{……};
JavaScript也有一个new操作符,使用方法看起来也和那些面向类的语言一样,绝大多数开发者都认为JavaScript中new的机制也和那些语言一样。然而,JavaScript中的new的机制实际上和面向类的语言完全不同。
首先我们重新定义一下JavaScript中的“构造函数”。在JavaScript中,构造函数只是一些使用new操作符时被调用的函数。它们并不会属于某个类,也不会实例化一个类。实际上,它们甚至都不能说是一种特殊的函数类型,它们只是被new操作符调用的普通函数而已。
使用new来调用函数,会自动执行下面的操作。
1.创建(或者说构造)一个全新的对象
2.这个对象会被执行[[原型]]连接,有关原型(prototype)的概念可参照原型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上。new是最后一种可以影响函数调用时this绑定行为的方法,我们称之为new绑定。
优先级
显示绑定>隐式绑定>默认绑定
new绑定优先级大于隐式绑定,与显示绑定比较时需要根据具体情况判断,一般二者不会同时出现。