码迷,mamicode.com
首页 > 编程语言 > 详细

JavaScript中的this关键字

时间:2015-08-26 07:08:18      阅读:198      评论:0      收藏:0      [点我收藏+]

标签:javascript

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 implementation.

请看下例:

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!
}

这样与上文就一致了。

版权声明:本文为博主原创文章,未经博主允许不得转载。

JavaScript中的this关键字

标签:javascript

原文地址:http://blog.csdn.net/u010999240/article/details/47956395

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!