上篇文章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调用位置
复制代码
其实,函数的调用栈也可以被想象成一个函数调用链~
绑定规则
默认绑定
独立函数调用(无法应用其他规则时,默认规则就是它了!)
看这段代码
function foo() {
console.log(this.a);
}
var a = 1;
foo(); //2
复制代码
首先,我们需要明白一个知识点:声明在全局作用域中的变量就是全局对象的一个同名属性,本质是一个东西,不是复制得到的
当我们调用foo,this.a 被解析成 全局变量a ,调用函数应用了this的默认绑定,this指向全局对象。
不过这里有一点
虽然this的绑定规则完全取决于调用位置,但是只有 foo() 运行在 非严格模式下,默认绑定才会绑定到全局,严格模式下,与foo()的调用位置无关。
隐式绑定
考虑调用位置,是否有上下文对象。
function foo(){
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
}
obj.foo(); //2
复制代码
我们先来看 foo() 的声明方式,无论是直接在obj中定义还是先定义,再添加为引用属性,这个函数严格来说都不属于obj对象。
但是,调用位置会使用obj上下文来引用函数,所以,你可以说,调用obj对象时,拥有或者包含它。调用foo()时,this被绑定到obj。this.a 和 obj.a 是一样的。
对象属性引用链中,只有最后一层影响调用位置
function foo() {
console.log( this.a );
}
var obj2 = {
a: 42,
foo: foo
}
var obj1 = {
a: 2,
obj2: obj2
}
obj1.obj2.foo(); //42
复制代码
隐式丢失
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
var bar = obj.foo; // 函数别名!
var a = "oops, global"; // a 是全局对象的属性 bar(); // "oops, global"
复制代码
虽然,bar是obj.foo的一个引用,实际上,引用的是foo函数本身!所以此时 bar() 是一个不带任何修饰的函数调用。
传入回调函数
function foo(){
console.log( this.a );
}
function doFoo(fn) {
fn(); //调用位置
}
var obj = {
a: 2,
foo: foo
}
var a = "global";
doFoo( obj.foo ); //global
复制代码
参数传递其实就是一种隐式赋值,因此我们传入函数时也会被隐式赋值,所以结果和上一 个例子一样。
下篇文章将介绍:显式绑定和new绑定两种方式。