您的位置:首页 > Web前端 > JavaScript

JS 中关于this 执行环境的问题_part2

2017-07-29 23:36 381 查看
今天阅读了《你不知道JavaScript(上)》, 对this又有了新的理解.

误解

经常有人会误解this, 两种常见的对this的误解

1. 指向自身

2. 指向函数的作用域

针对第一个误解,来看一个例子

function foo(num) {
console.log( "foo: " + num );
// 记录 foo 被调用的次数
this.count++;
}

foo.count = 0;
var i;
for (i=0; i<10; i++) {
if (i > 5) {
foo( i );
}
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9
// foo 被调用了多少次?
console.log( foo.count ); // 0 -- WTF?


结果是0的原因是,这里函数foo在调用的时候,this指向的是全局对象.全局中并没有声明count变量,在非严格模式下会自动创建全局变量count, 值为undefined, foo函数中的this.count++的操作都是针对undefined,因此全局变量count的值为NaN. this在这里指向全局对象在这本书中的解释是 this进行了默认绑定.

要想希望上面代码中foo函数的this指向其本身, 这里提供两个方法.

1. 使用foo.count++ 代替this.count++ ,函数foo是具名函数,不是匿名函数,可以用foo指向其自身.(arguments.callee已经被弃用?)

2. 强制this指向foo函数对象,调用foo函数的时候使用foo.call(foo,i);

第二个误解是this指向函数的作用域

针对第二个误解,看下面的例子

function foo() {
var a = 2;
this.bar();
}
function bar() {
console.log( this.a );
}
foo(); // ReferenceError: a is not defined


bar()在全局作用域中, 但在foo()中无法用this调用. bar()也无法访问foo()作用域中的的变量a.

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 的调用位置


调用栈是baz -> bar -> foo, 因此foo的调用位置是bar,bar的调用位置在baz, baz的调用位置在全局作用域

绑定规则

函数在不用的调用位置(可能存在不用的调用模式),其this的指向不同.要判断函数在该调用位置的this指向,需要应用下面四条规则中的一条.

默认绑定

this 的默认绑定指向全局对象

看下面的代码

function foo() {
console.log( this.a );
}
var a = 2;
foo(); // 2


调用栈 ->foo

foo()的调用位置在全局作用域,直接使用不带任何修饰的函数引用进行调用的,因此只能使用默认绑定,无法应用其他规则。

如果使用严格模式(strict mode),那么全局对象将无法使用默认绑定,因此 this 会绑定到 undefined

隐式绑定

观察调用位置是否有上下文对象,或者说是否被某个对象拥有或者包含.

看下面的代码

function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
obj.foo(); // 2


调用栈obj.foo->foo

调用位置在obj.foo. 调用位置会使用 obj 上下文来引用函数,因此你可以说函数被调用时 obj 对象“拥有”或者“包含”它.当函数引用有上下文对象时,隐式绑定规则会把函数调用中的 this 绑定到这个上下文对象.因为调用 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


函数调用栈: obj1.obj2.foo -> foo

这里引用链的顶层是obj2,因此this指向obj2对象

隐式丢失

看例子

function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
var bar = obj.foo; // 函数别名!
var a = "oops, global"; // a 是全局对象的属性
bar(); // "oops, global"


调用栈看起来像 obj.foo(bar)->foo,其实调用栈为->foo, 调用位置在全局作用域

虽然 bar 是 obj.foo 的一个引用,但是实际上,它引用的是 foo 函数本身,因此此时的bar() 其实是一个不带任何修饰的函数调用,因此应用了默认绑定.

另一个例子

function foo() {
console.log( this.a );
}
function doFoo(fn) {
// fn 其实引用的是 foo
fn(); // <-- 调用位置!
console.log(a)// "oops, global"
}
var obj = {
a: 2,
foo: foo
};
var a = "oops, global"; // a 是全局对象的属性
doFoo( obj.foo ); // "oops, global"


foo()的调用栈其实是doFoo->foo, fn引用foo函数本身

doFoo()的调用栈->doFoo

doFoo( obj.foo );中的obj.foo是隐式赋值,因此fn()在调用的时候和上一个例子一样,应用了默认绑定. doFoo()中的this同样也是默认绑定.

显式绑定

是指利用apply(),call() 数组的forEach()等方法强制绑定this

new 绑定

是指用new调用函数的时候, 绑定this的情况

优先级

函数是否在 new 中调用(new 绑定)?如果是的话 this 绑定的是新创建的对象。

var bar = new foo()

函数是否通过 call、apply(显式绑定)或者硬绑定调用?如果是的话,this 绑定的是

指定的对象。

var bar = foo.call(obj2)

函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this 绑定的是那个上

下文对象。

var bar = obj1.foo()

如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到 undefined,否则绑定到

全局对象。

var bar = foo()

例外

被忽略的this

function foo() {
console.log( this.a );
}
var a = 2;
foo.call( null ); // 2


如果你把 null 或者 undefined 作为 this 的绑定对象传入 call、apply 或者 bind,这些值在调用时会被忽略,实际应用的是默认绑定规则.

间接引用

function foo() {
console.log( this.a );
}
var a = 2;
var o = { a: 3, foo: foo };
var p = { a: 4 };
o.foo(); // 3 调用栈 o.foo ->foo
(p.foo = o.foo)(); // 2 调用栈 ->foo


赋值表达式 p.foo = o.foo 的返回值是目标函数的引用,因此调用位置是 全局作用域而不是p.foo() 或者 o.foo().(书上说调用位置在foo() 感觉有问题)根据我们之前说过的,这里会应用默认绑定.

小结

如果要判断一个运行中函数的 this 绑定,就需要找到这个函数的直接调用位置。找到之后

就可以顺序应用下面这四条规则来判断 this 的绑定对象。

1. 由 new 调用?绑定到新创建的对象。

2. 由 call 或者 apply(或者 bind)调用?绑定到指定的对象。

3. 由上下文对象调用?绑定到那个上下文对象。

4. 默认:在严格模式下绑定到 undefined,否则绑定到全局对象。

http://www.fangbinwei.cn/articleDetail?id=59ab975ef1906c31d6c6924a
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  javascript