Javascriptの中のthisの取得値

30520 ワード

もっと読む
thisはjavascriptの中の非常に基礎的な知識点であり、多くの初心者が迷う知識点でもあります.
Ecmascriptでは、以下のように説明されています.
The e e e is a this value assited with everactive execution context.The this value depends on the caler and the type of code being exted and is determined when control enters the ext context.The is is
thisの値はexecutable codeのタイプと直接関連しています.execute codeには3つのGlobal code、Function code、Eval codeがあります.thisの値取りに関するものは前の二つです.
thisのglobal codeの中のは値をとります
この場合問題は比較的簡単で、thisの値はglobalオブジェクト自体です.
this in global code
1
2
3
4
5
6
7
8
9
10
11
12
// explicit property definition of the global object
this.a = 10; // global.a = 10
alert(a); // 10
// implicit property definition of the global object
b = 20;
alert(this.b); // 20
// also implicit via variable declaration
// in global context: this =  global = VO
var c = 30;
alert(this.c); // 30
demoの中のbはvarを通じて声明していません.実はvariableではなく、windowのpropertyです.変数と属性の著しい違いは変数が「DotDelete」を持っていますが、後者はありません.だからよく言います
varを使わずに直接グローバル変数を宣言することができます.
という言い方は間違っていますが、実はウィンドウズに属性を追加しました.
variable and property
1
2
3
4
var a =5;
b = 4;  // equal to windows.b = 4
alert(delete a); // false
alert(delete b); // true
thisのfunction codeでの取得値
function codeにおけるthisの値はglobal codeのように簡単ではない:
  • thisの値は、対応する関数と結合されているものではなく、すなわち関数の作成時に決定されるものではない.
  • thisの値はコンテキストに入ると決定され、実行中には可変されない.ie.変数の割当値のようにthisに値をつけてはいけません.
  • this
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
    var foo = {x: 10};
    var bar = {
      x: 20,
      test: function () {
        alert(this === bar); // true
        alert(this.x); // 20
        this = foo; // error, can't change this value
        alert(this.x); // if there wasn't an error, then would be 10, not 20
      }
    };
    // on entering the context this value is
    // determined as "bar" object;
    bar.test(); // true, 20
    foo.test = bar.test;
    // however here this value will now refer
    // to "foo" – even though we're calling the same function
    foo.test(); // false, 10
    
    どのような要因がthisに影響を与えるのですか?いくつかの文章と本の中には、次のような言い方があります.
    thisの取得値は関数の定義により決定され、関数が大域関数として定義されている場合、対応するthisはglobalである.関数がオブジェクトの方法であれば、thisの値は対応するオブジェクトです.
    このような言い方は間違っています.一例を見てみます.
    Is the definition of function determing this
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    function foo() {
        bar: function() {
            console.log(this);
        }
    }
    foo.bar(); // foo
    var baz = foo.bar;
    console.log(baz === foo.bar); // true
    baz(); // global
    
    bazはfoo.barの値と同じですが、同じ関数を指します.しかし、呼び出しの結果は違っていますので、thisの取得値は関数の定義によって決定されません.
    実際、thisの値は、関数の呼び出しによって決定される、例えば、関数の親コンテキストなどの使用者によって提供される.
    どうしてthisの値は関数の呼び出しによって決まるのですか?
    この質問に答えるには、まずECMAの内部タイプを見てください.Reference type
    Reference typeRefferenceは内部タイプで、Referenceタイプの値を返すことができる関数は何もありません.built-in関数でも、ユーザ定義の関数でもあります.Referenceに関する仕様の説明は以下の通りである.
    The internal Reference type is not a langage data type.It is defined by this specification for expository purposes.However,a value of type Reference is used only as anterinmediate of expressity vation
    The Reference type is used to explane the behaviour of such operators as delete、typeof、and the assignment operators.
    other use of the Reference type is to expline the determination of the this value for a function call.
    A Reference is a reference to a property of an object.A Reference consists of two components,the base object and the property name.
    The follwing abstract operations arused in this specification to access the components of references:
  • GetBase(V).Returns the base object component of the reference V.
  • GetPropertyName(V).Returns the property name component of the reference V.
  • Referenceの構成は、以下のように説明することができる.
    Referenceの構造
    1
    2
    3
    4
    
    var ReferenceType = {
        base: <base object>,
        propertyName: <property name>
    };
    
    仕様によれば、以下の2つの場合のみ、Referenceタイプの値が返される.
  • 識別子解析(dentifers resolution)は、識別子に変数名、関数名、関数パラメータ名、および上記のvar宣言を用いずに直接値を付与する変数を含む.
  • 属性アクセス(Property access)は、[]または.を介して、foo.barのような2つの方法でアクセスされている.
    Reference
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    
    //      
    var foo = 10;
    function bar() {}
    //    Reference    
    var fooReference = {
        base: global,
        propertyName: 'foo'
    };
    var barReference = {
        base: global,
        propertyName: 'bar'
    };
    //     
    foo.bar();
    foo['bar']();
    //    Reference type 
    var fooBarReference = {
        base: foo,
        propertyName: 'bar'
    };
    
    foo['bar']は中間値であり、real valueを取得するにはReference方法が必要である(参照).
    GetValueダミーコード
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
    function GetValue(value) {
        if (Type(value) != Reference) {
            return value;
        }
        var base = GetBase(value);
        if (base === null) {
            throw new ReferenceError;
        }
        return base.[[Get]](GetPropertyName(value));
    }
    
    GetValue関数は、プロトタイプチェーン上の継承属性を考慮しながらオブジェクト属性の値を返します.
    以上、[[Get]]の取得値について:
  • 関数におけるthisの取得値は、使用者によって提供され、関数の現在の呼び出し方式によって決定される
  • である.
  • は、関数呼び出し括弧thisの左に()のタイプの値がある場合、Referenceの値をその値のBase属性の値
  • に設定する.
  • の他のすべての場合(例えば、thisの左の値は()タイプではない)、Referencethisに設定されます.nullは何の意味もないので、それらは陰的にglobal object
  • に変換されます.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    
    // eg1
    function foo() {
        return this;
    }
    foo(); // global
    //    Reference 
    var fooReference = {
        base: global,
        propertyName: 'foo'
    };
    // eg2
    var foo = {
        bar: function () {
            return this;
        }
    };
    foo.bar(); // foo
    //    Reference 
    var fooBarReference = {
        base: foo,
        propertyName: 'bar'
    };
    // eg3
    //          foo.bar
    var test = foo.bar;
    test(); // global
    //      Reference       
    var testReference = {
        base: global,
        propertyName: 'test'
    };
    
    上のデモに
  • eg 1でnullを呼び出した場合、上記の原則に従って、fooは変数であり、識別子解析でfoo() typeの値fooReferenceを返します.thisはfooReferenceオブジェクトのbase属性の値に設定されます.
  • がeg 2でReferenceを呼び出したとき、barはfooの属性の一つとして、属性を読み取ってglobalタイプの値fooBarReferenceを返します.thisはbase属性の値に設定されます.foo.bar()
  • はeg 3でfoo.barをtestに割り当てました.testを識別子として、testを呼び出した時にtestReferenceが発生しましたので、global
  • に戻りました.
    同じ関数を理解して、異なる方法で呼び出します.なぜ戻り値が違いますか?
    二つの問題を見て、実行結果はどうなるかを考えます.
    思考
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    
    //     :
    function foo () {
        console.log(this);
    }
    foo(); // ?
    foo.prototype.constructor(); // ?
    //     :
    function foo() {
        console.log(this.bar);
    }
    var x = {bar: 10};
    var y = {bar: 20};
    x.test = foo;
    y.test = foo;
    x.test(); // ?
    y.test(); // ?
    
    Function call and non-Reference type
    上記のように、関数呼び出しReferenceの左側がReferenceタイプ値ではない場合、thisはnullに設定され、次いで暗黙的にglobalに変換される.
    non-Reference call
    1
    2
    3
    
    (function () {
        alert(this); // null => global
    })();
    
    上記の例ではfooの左側は関数オブジェクトであり、識別子でも属性でもないので、Reference値には戻りません.thisはnullに設定され、次いでglobalに変換されます.
    複雑な点の例をいくつか見てみます.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
    var foo = {
        bar: function () {
            alert(this);
        }
    };
    foo.bar(); // Reference, OK => foo
    (foo.bar)(); // Reference, OK => foo
    (foo.bar = foo.bar)(); // global?
    (false || foo.bar)(); // global?
    (foo.bar, foo.bar)(); // global?