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

JavaScript中的this关键字

2015-08-29 18:30 776 查看

JavaScript中的this关键字

JavaScript函数中的关键字this并不指其本身(Itself),举例说明:

function foo(num) {
console.log( "foo: " + num );

// keep track of how many times `foo` is called
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

// how many times was `foo` called?
console.log( foo.count ); // 0


上例中尽管foo()执行了4次,但是foo.count依然为0,说明this指代的并不是foo函数自身。那么我们执行的count++到底是给什么值在增加呢?实际上我们创建了一个全局变量count,即window下的一个变量,其初始值为NaN,即使其被增加了4次,其值依然为NaN。

若上述示例改为下面就很容易理解:

function foo(num) {
console.log( "foo: " + num );

// keep track of how many times `foo` is called
data.count++;
}

var data = {
count: 0
};

var i;

for (i=0; i<10; i++) {
if (i > 5) {
foo( i );
}
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9

// how many times was `foo` called?
console.log( data.count ); // 4


但是这里,我们并没有用到this关键字,我们只是选择性地回避了this,采用了另一种方式-Lexical scope来完成的。

如果我们要在函数内部引用其自身时,我们只能采用lexical identifier (variable)来指向其自身,如下:

function foo() {
foo.count = 4; // `foo` refers to itself
}


此种方式对于有函数名的函数有效,但是对于匿名函数就无能为力了,比如下面:

setTimeout( function(){
// anonymous function (no name), cannot
// refer to itself
}, 10 );


对此我们无能为力,不过有一个并不建议使用(目前已废弃的方式)的方式来引用函数自身:arguments.callee,实际上我们最好的方式就是在需要引用函数自身时不要使用匿名函数,这样就避免了上述问题的出现。

所以我们最开始时如果我们要引用自身函数时,采用其自身的函数名(identifier )即可:

function foo(num) {
console.log( "foo: " + num );

// keep track of how many times `foo` is called
foo.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

// how many times was `foo` called?
console.log( foo.count ); // 4


上例中我们似乎还是回避了this关键字,只不过上例中我们要引用函数自身,不能使用this关键字。

如果我们一定要使用this关键字来指代函数自身时,我们可以这样做:

function foo(num) {
console.log( "foo: " + num );

// keep track of how many times `foo` is called
// Note: `this` IS actually `foo` now, based on
// how `foo` is called (see below)
this.count++;
}

foo.count = 0;

var i;

for (i=0; i<10; i++) {
if (i > 5) {
// using `call(..)`, we ensure the `this`
// points at the function object (`foo`) itself
foo.call( foo, i );
}
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9

// how many times was `foo` called?
console.log( foo.count ); // 4


另一个对于this的误解是指:this指的是函数的lexical scope,You Dont Know JS 中关于this有这样的一段话:

To be clear, this does not, in any way, refer to a function’s lexical scope. It is true that internally, scope is kind of like an object with properties for each of the available identifiers. But the scope “object” is not accessible to JavaScript code. It’s an inner part of the Engine’s im
4000
plementation.

请看下例:

function foo() {
var a = 2;
this.bar();
}

function bar() {
console.log( this.a );
}

foo(); //undefined


上述代码试图通过this在foo()和bar()的lexical scopes中建立桥梁,所以通过bar()来获取foo()的inner scope的变量a,这样的桥梁实际上并不存在。

那么this机制到底是怎样的呢?其实,this是在运行时绑定的(runtime binding),而不是创建时绑定(author-time binding),即函数在调用时是基于其上下文来绑定的。

You Dont Know JS 中关于this机制有这样的一段话:

‘This’ is not an author-time binding but a runtime binding. It is contextual based on the conditions of the function’s invocation. this binding has nothing to do with where a function is declared, but has instead everything to do with the manner in which the function is called.

When a function is invoked, an activation record, otherwise known as an execution context, is created. This record contains information about where the function was called from (the call-stack), how the function was invoked, what parameters were passed, etc. One of the properties of this record is the this reference which will be used for the duration of that function’s execution.

上述提到,this是在函数调用时基于其Call-site来绑定的,那么什么是Call-site呢

call-site: the location in code where a function is called (not where it’s declared).

那么如何确定函数的call-site呢,

go locate where a function is called from

但是,确定函数的call-site并不容易,因此我们换一种思路,来寻找函数的call-stack:

the stack of functions that have been called to get us to the current moment in execution

下面我们来通过例子来说明call-site和call-stack的关系:

function baz() {
// call-stack is: `baz`
// so, our call-site is in the global scope

console.log( "baz" );
bar(); // <-- call-site for `bar`
}

function bar() {
// call-stack is: `baz` -> `bar`
// so, our call-site is in `baz`

console.log( "bar" );
foo(); // <-- call-site for `foo`
}

function foo() {
// call-stack is: `baz` -> `bar` -> `foo`
// so, our call-site is in `bar`

console.log( "foo" );
}

baz(); // <-- call-site for `baz`


我们可以通过浏览器的dev Tool的debugger来调试查看函数的call-stack。

在我们清楚 call-site之后,我们来看this绑定的四条准则:

1. Default Binding

这条是最常见的绑定规则,也就是说this指向global object,请看例子:

function foo() {
console.log( this.a );
}

var a = 2;

foo(); // 2


如果在strict mode下,this指向undefined:

function foo() {
"use strict";

console.log( this.a );
}

var a = 2;

foo(); // TypeError: `this` is `undefined`


其中,一个细节要注意,如果foo()不是在strict mode下,其this仍指向global object:

function foo() {
console.log( this.a );
}

var a = 2;

(function(){
"use strict";

foo(); // 2
})();


2. Implicit Binding

does the call-site have a context object, also referred to as an owning or containing object

如果call-site有上下文对象,或者说其有包含它的容器对象,那么此时Implicit Binding起作用了,看下例:

function foo() {
console.log( this.a );
}

var obj = {
a: 2,
foo: foo
};

obj.foo(); // 2


上例中foo先被申明,然后被obj引用,实际上,即使在obj中申明函数,结果也一样:

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

obj.foo(); // 2


call-site uses the obj context to reference the function, so you could say that the obj object “owns” or “contains” the function reference at the time the function is called.

obj在foo()函数被调用之前持有foo函数的引用,即obj是foo的上下文对象,此时Implicit Binding生效,this也就指向了该上下文对象。注意下面的一种情况:

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; // function reference/alias!

var a = "oops, global"; // `a` also property on global object

bar(); // "oops, global"


表面上看,bar是通过obj的foo属性来指向foo函数的,实际上,bar是直接指向foo的引用的,因为obj中的foo也是指向foo引用的。此时,因为this就失去绑定,default binding生效。

function foo() {
console.log( this.a );
}

function doFoo(fn) {
// `fn` is just another reference to `foo`

fn(); // <-- call-site!
}

var obj = {
a: 2,
foo: foo
};

var a = "oops, global"; // `a` also property on global object

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


这里,我们通过参数传递将foo函数引用赋值给fn,此时就与obj无关了,是fn指向foo函数引用了,this失去隐式绑定,default binding生效,即使你传入回调函数(即这里的fn)的函数不是自己写的也没有关系:

function foo() {
console.log( this.a );
}

var obj = {
a: 2,
foo: foo
};

var a = "oops, global"; // `a` also property on global object

setTimeout( obj.foo, 100 ); // "oops, global"


这里的setTimeout我们可以这样理解:

function setTimeout(fn,delay) {
// wait (somehow) for `delay` milliseconds
fn(); // <-- call-site!
}


这样与上文就一致了。

3. Explicit Binding

我们可以通过call(..)和apply(..)来显式绑定this:

function foo() {
console.log( this.a );
}

var obj = {
a: 2
};

foo.call( obj ); // 2


倘若我们将原始类型的变量作为this显示绑定时,其原始类型会被自动包装成对应的对象类型(object-form),Explicit Binding绑定就是Hard Binding。

Hard Binding

explicit binding也不能解决上文中的this失去绑定后this的重新绑定,但是我们可以通过下面的小trick来解决this的重新绑定问题:

function foo() {
console.log( this.a );
}

var obj = {
a: 2
};

var bar = function() {
foo.call( obj );
};

bar(); // 2
setTimeout( bar, 100 ); // 2

// `bar` hard binds `foo`'s `this` to `obj`
// so that it cannot be overriden
bar.call( window ); // 2


上文中我们创建函数bar(),在函数内部调用foo.call(obj),将obj

绑定到this上,我们称这种为Hard Binding。

4. new Binding

In JS, constructors are just functions that happen to be called with the new operator in front of them. They are not attached to classes, nor are they instantiating a class. They are not even special types of functions. They’re just regular functions that are, in essence, hijacked by the use of new in their invocation.

js并不像一般的面向对象语言,具有constructor functions,当我们使用new时,调用函数,我们称该动作为constructor call,使用new时发生了以下几件事:

a brand new object is created (aka, constructed) out of thin air

the newly constructed object is [[Prototype]]-linked

the newly constructed object is set as the this binding for that function call

unless the function returns its own alternate object, the new-invoked function call will automatically return the newly constructed object.

举例说明:

function foo(a) {
this.a = a;
}

var bar = new foo( 2 );
console.log( bar.a ); // 2


首先,创建一个对象,其与var a={}创建的对象没有
da97
任何区别,然后将该对象与foo建立联系,将this与该对象绑定,返回该对象。

this绑定优先级

介绍完上述的几种this绑定,首先,efault binding优先级最低,我们放在一边。

implicit binding 和explicit binding那个优先级高呢?看示例:

function foo() {
console.log( this.a );
}

var obj1 = {
a: 2,
foo: foo
};

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

obj1.foo(); // 2
obj2.foo(); // 3

obj1.foo.call( obj2 ); // 3
obj2.foo.call( obj1 ); // 2


说明explicit binding优先级高于implicit binding,那么implicit binding 和new binding呢?

function foo(something) {
this.a = something;
}

var obj1 = {
foo: foo
};

var obj2 = {};

obj1.foo( 2 );
console.log( obj1.a ); // 2

obj1.foo.call( obj2, 3 );
console.log( obj2.a ); // 3

var bar = new obj1.foo( 4 );
console.log( obj1.a ); // 2
console.log( bar.a ); // 4


说明new binding优先级高于implicit binding,那么new binding和explicit binding呢?

是不是可以这样测试呢:

new foo.call(obj1)


注意,这样是不可以的。

Before we explore that in a code listing, think back to how hard binding physically works, which is that Function.prototype.bind(..) creates a new wrapper function that is hard-coded to ignore its own this binding (whatever it may be), and use a manual one we provide.

因此,很显然hard binding(也就是explicit binding),看示例:

function foo(something) {
this.a = something;
}

var obj1 = {};

var bar = foo.bind( obj1 );
bar( 2 );
console.log( obj1.a ); // 2

var baz = new bar( 3 );
console.log( obj1.a ); // 2
console.log( baz.a ); // 3


由于new bar(3)并没有改变obj1.a的值,可见,explicit binding优先级最高。

总结

现在我们总结this的用法:

函数是否使用new关键字调用,如果有,那么this指向该新创建的对象。

var bar = new foo()


调用函数是否使用了call 和apply,如果有,那么this指向该我们绑定的对象上。

var bar = foo.call( obj2 )


函数调用时是否有上下文对象(context ),如果有,那么this指向该上下文对象。

var bar = obj1.foo()


除上述以外情况,如果是strict mode下,那么this指向undefined,否则指向global object。

var bar = foo()


Binding Exceptions

当我们使用call, apply, 或 bind时传递给this对象的值为null 或undefined,这些值会被忽略, default binding生效。

function foo() {
console.log( this.a );
}

var a = 2;

foo.call( null ); // 2


最后,强调一点,关于Object.create、Object.create()和Object.create(null)之类的区别:

var a=Object.create;
var b=Object.create(null);
console.log(typeof a);//function
console.log(typeof b);//object


Object.create只是对Object中create函数的一个引用,Object.create(null)是创建一个不继承任何原型对象的对象,试比较var a=Object.create(null)和var b={}的区别:

var a=Object.create(null);
var b={};
console.log(a.__proto__);//undefined
console.log(b.__proto__);//Object {}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  javascript