APP下载

Javascript全面解析this

消息来源:baojiabao.com 作者: 发布时间:2024-05-17

报价宝综合消息Javascript全面解析this

01.呼叫位置

this的呼叫位置就是函式在程式码中被呼叫的位置(不是宣告的位置)。分析呼叫位置最重要的就是分析呼叫栈。下面一个简单例子,理解呼叫栈和呼叫位置:

function baz() {

// 当前呼叫栈是:baz

// 因此,当前呼叫位置是全域性作用域

console.log( "baz" );

bar(); // }

function bar() {

this全面解析 | 83

// 当前呼叫栈是baz -> bar

// 因此,当前呼叫位置在baz 中

console.log( "bar" );

foo(); // }

function foo() {

// 当前呼叫栈是baz -> bar -> foo

// 因此,当前呼叫位置在bar 中

console.log( "foo" );

}

baz(); // 上例子中分析出了函式的呼叫位置,也就确定this的呼叫位置。

02.系结规则

2.1.预设系结

下面例子中的foo()是直接使用不带任何修饰的函式引用进行呼叫的,因此只能使用预设系结。

function foo() {

console.log( this.a );

}

var a = 2;

foo(); // 2

然鹅,在严格模式(strict mode)下,全域性物件无法使用预设系结,因此下面的例子中,this会系结到undefined。

function foo() {

console.log( this.a );

}

var a = 2;

(function(){

"use strict";

foo(); // 2

})();

2.2.隐式系结

隐式系结会把函式呼叫中的this系结到上下文物件。下面的例子中的foo()的this系结在obj2上,所以输出是42.

function foo() {

console.log( this.a );

}

var obj2 = {

a: 42,

foo: foo

};

var obj1 = {

a: 2,

obj2: obj2

};

obj1.obj2.foo(); // 42

隐式丢失

一个最常见的this系结问题就是被隐式系结的函式会丢失系结物件,也就是说它会应用预设系结,从而把this 系结到全域性物件或者undefined 上。

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 其实引用的是foo

fn(); // }

var obj = {

a: 2,

foo: foo

};

var a = "oops, global"; // a 是全域性物件的属性

doFoo( obj.foo ); // "oops, global"

上例中的引数传递其实就是一种隐式赋值,所以结果跟前面的例子一样。

2.3.显式系结

JavaScript有函式都可以使用call(..)和apply(..) 方法,进行显式系结this。call(..)和apply(..)的区别就在于传入引数形式不同,

function foo() {

console.log( this.a );

}

var obj = {

a:2

};

foo.call( obj ); // 2

2.3.1.硬系结:

硬系结可以用来解决上面说到的隐式丢失问题。下面的例子中,实际上是在每次呼叫bar函式的时候都执行了硬系结,所以可以解决上述的问题。

function foo() {

console.log( this.a );

}

var obj = {

a:2

};

var bar = function() {

foo.call( obj );

};

bar(); // 2

setTimeout( bar, 100 ); // 2

// 硬系结的bar 不可能再修改

bar.call( window ); // 2

由于硬系结是一种很常用的放大,所以在ES5中内建了bind()方法,作为辅助系结方法。

function foo(something) {

console.log( this.a, something );

return this.a + something;

}

var obj = {

a:2

};

var bar = foo.bind( obj ); //this硬系结在obj上

var b = bar( 3 ); // 2 3

console.log( b ); // 5

2.3.2.API呼叫的“上下文”

第三方库的许多函式,以及JavaScript 语言和宿主环境中许多新的内建函式,都提供了一个可选的引数,通常被称为“上下文”(context),其作用和bind(..) 一样,确保你的回拨函式使用指定的this。

function foo(el) {

console.log( el, this.id );

}

var obj = {

id: "awesome"

};

// 呼叫foo(..) 时把this 系结到obj

[1, 2, 3].forEach( foo, obj );

// 1 awesome 2 awesome 3 awesome

这些函式实际上就是通过call(..) 或者apply(..) 实现了显式系结,这样可以少些一些程式码。

2.4.new系结

使用new 来呼叫函式,或者说发生建构函式呼叫时,会自动执行下面的操作。

建立(或者说构造)一个全新的物件。这个新物件会被执行[[ 原型]] 连线。这个新物件会系结到函式呼叫的this。如果函式没有返回其他物件,那么new 表示式中的函式呼叫会自动返回这个新物件。

function foo(a) {

this.a = a;

}

var bar = new foo(2);

console.log( bar.a ); // 2

使用new 来呼叫foo(..) 时,我们会构造一个新物件并把它系结到foo(..) 呼叫中的this上。new 是最后一种可以影响函式呼叫时this 系结行为的方法,我们称之为new 系结

03.系结优先级

下面既代表系结的优先级,又是我们在实际应用中,判断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()

04.系结例外

上面介绍了几种系结方法,但总是有例外,这里就总结一下例外的几种情况。

4.1.被忽略的this

如果你把null 或者undefined 作为this 的系结物件传入call、apply或者bind,这些值在呼叫时会被忽略,实际应用的是预设系结规则。

4.2.简介引用

间接引用最容易发生在赋值的时候:

function foo() {

console.log( this.a );

}

var a = 2;

var o = { a: 3, foo: foo };

var p = { a: 4 };

o.foo(); // 3

(p.foo = o.foo)(); // 2

赋值表示式p.foo = o.foo的返回值是目标函式的引用,因此呼叫位置是foo()而不是p.foo()或者o.foo()。所以这时候会执行预设系结,把this系结在全域性。

05.this词法

我们之前介绍的四条规则已经可以包含所有正常的函式。但是ES6 中介绍了一种无法使用这些规则的特殊函式型别:箭头函式。箭头函式不使用this 的四种标准规则,而是根据外层(函式或者全域性)作用域来决定this。

我们来看看箭头函式的词法作用域:

function foo() {

// 返回一个箭头函式

return (a) => {

//this 继承自foo()

console.log( this.a );

};

}

var obj1 = {

a:2

};

var obj2 = {

a:3

};

var bar = foo.call( obj1 );

bar.call( obj2 ); // 2, 不是3 !

foo() 内部建立的箭头函式会捕获呼叫时foo() 的this。由于foo() 的this 系结到obj1,bar(引用箭头函式)的this 也会系结到obj1,箭头函式的系结无法被修改。(new 也不行!)

2019-10-24 20:57:00

相关文章