Ecma仕様からthisを深く理解する

5172 ワード

thisはオブジェクト向けプログラミングの概念であり、現在のメソッド呼び出しが存在するオブジェクトを指すのが一般的であり、java、c++のような比較的厳しいオブジェクト向けプログラミング言語では非常に明確である.しかしjavascriptではthisの定義は柔軟で、正確に把握していないと混同しやすい.本人も面接の過程で、面接者が非常に全面的に答える理由が少ないことに気づいた.本文はthisの様々な状況をまとめ,Ecma規範の観点からthisの具体的な実現を検討し,thisを理解するのに役立つことを望んでいる.
thisが指す四中状況
Javascriptでは,thisの指向は以下の4つのケースにまとめることができる.この4つの状況をしっかり覚えておけば、ほとんどの場合十分です.
1.グローバルコードまたは通常の関数呼び出しで、thisはグローバルオブジェクトを指し、ブラウザではwindowオブジェクトである.
    console.log(this);//  window
    
    function foo(){
        console.log(this);
    }
    foo();//  window

ブラウザ環境で上記のコードを実行し、両方の出力結果はwindowオブジェクトです.
2.callメソッドまたはapplyメソッドで関数を呼び出し、thisはメソッド呼び出しの最初のパラメータを指します.
    var obj = {name:'test'};
    
    function foo(){
        console.log(this);
    }
    
    foo.call(obj);//  obj
    foo.apply(obj);//  obj

以上のコードをブラウザ環境で実行し,出力結果はいずれもオブジェクトobjである.callとapplyはパラメータ形式が異なる以外は同じで,callはカンマ分割,applyは配列を用いる.ここまで言うと、ついでにちょっとしたテクニックを紹介します.新しい配列を生成せずに2つの配列の接続を実現するにはどうすればいいですか?次のコードを見てください.
    var arr1 = [1, 2 , 3],
        arr2 = [4, 5, 6];
    Array.prototype.push.apply(arr1, arr2);
    
    console.log(arr1);//  [1, 2, 3, 4, 5, 6]

上記のコードを実行すると、出力結果は[1,2,3,4,5,6]となる.これは非常に実用的なテクニックであり、applyの2番目のパラメータは配列形式であるため、pushメソッドを「借りる」ことで、2つの配列の接続を実現することができます.
3.オブジェクトを呼び出す方法.thisはオブジェクトを指します.
    var obj = {name:'test'};
    
    function foo(){
        console.log(this);
    }
    obj.foo = foo;
    
    obj.foo();//  obj

以上のコードを実行すると、コンソールはobjオブジェクトとして出力されます.これが私たちがよく言う「誰が呼び出し、誰を指すか」です.
4.コンストラクションメソッドのthisは、新しいコンストラクションのオブジェクトを指します.
    function C(){
        this.name = 'test';
        this.age = 18;
        console.log(this);
    }
    var c = new C();//   c
    console.log(c);//   c

以上のコードを実行すると、コンソール出力はいずれもcが指すオブジェクトである.newオペレータが関数に使用されると、新しいオブジェクトが作成され、thisで指定されます.
Ecma仕様
Ecma仕様ではthisの実現の詳細を詳しく紹介しており、仕様を読むことで、上記の4つの状況がどのようなものなのかをより正確に理解することができます.関数オブジェクトには[[Call]]という内部メソッドがあり、関数の実行は実は[[Call]]メソッドで実行されます.[[Call]]メソッドは2つのパラメータthisArgとargumentListを受信し,thisArgとthisの指向は直接関係し,argumentListは関数の実パラメータリストである.どうやって来たの?前に議論した4つの状況に対応することができます.
  • 通常メソッドはthisArgをundefinedとして呼び出します.
  • はcallまたはapplyによって呼び出され、thisArgは最初のパラメータである.
  • は、オブジェクトによって呼び出され、thisArgはそのオブジェクトを指す.
  • 構築方法では、thisArgは新規構築の対象となる.

  • this Argとthisはどんな関係ですか?仕様の説明は次のとおりです.
  • If the function code is strict code, set the ThisBinding to thisArg.
  • Else if thisArg is null or undefined, set the ThisBinding to the global object.
  • Else if Type(thisArg) is not Object, set the ThisBinding to ToObject(thisArg).
  • Else set the ThisBinding to thisArg.

  • 厳格モードでは、this Argとthisは1つずつ対応しています.
    function foo(){
        'use strict';
        console.log(this);
    }
    foo();//  undefined
    

    この例の出力結果はundefinedです.
    2つ目は、this Argがnullまたはundefinedの場合、thisはグローバルオブジェクトを指します.
    function foo(){
        console.log(this);
    }
    foo.call(null);//  window
    

    この例の出力結果はwindowです.
    3つ目は、thisArgが非オブジェクトタイプの場合、強制的にオブジェクトタイプに変換されます.
    function foo(){
        console.log(this);
    }
    var aa = 2;
    console.log(aa);//  2
    foo.call(aa);//   Number
    

    ここでの出力結果はそれぞれ2とNumberであり,基本タイプをオブジェクトパッケージタイプに変換した.
    4つ目は、残りの状況を説明します.thisArgとthisは1つの対応関係です.
    規範の中でthisが指している説明はまだはっきりしている.このArgがどのように確定しているのか、this Argとthisの対応関係を明らかにすれば、あなたはすべてのthisの状況を解決することができます.
    thisの指向性を確認
    実際にthisを使用する過程で、最も多くの問題に遭遇したのはコンテキストが失われた問題かもしれません.javascriptの関数はパラメータとして伝達できるため、他のオブジェクトがコールバック関数を実行すると、コールバック関数の元のコンテキストが失われる可能性があります.つまり、thisの指向が変わります.
    var C = function(){
        this.name = 'test';
        this.greet = function(){
            console.log('Hello,I am '+this.name+'!');
        };
    }
    var obj = new C();
    
    obj.greet();//   Hello,I am test!
    
    setTimeout(obj.greet, 1000);//   Hello,I am !

    2番目の出力でthisの値が変わったことがわかりますが、実は私たちはthisがobjを指すことを望んでいます.この問題を解決する方法は2つある.
    1.bindメソッド.bind法は閉パケットによってコンテキストのバインドを巧みに実現し,実際には元の方法を新しい方法に包装した.一般的な実装は次のとおりです.
    Function.prototype.bind = function(){
        var args = arguments,
            thisArg = arguments[0],
            func = this;
        return function(){
            var arg = Array.prototype.slice.call(args, 1);
            Array.prototype.push.apply(args, arguments);
            return func.apply(thisArg, arg);
        }
    }

    前の例のコードはbindを加えるだけで、私たちが望んでいる結果を得ることができます.
    ...
    setTimeout(obj.greet.bind(obj), 1000);//   Hello,I am test!
    ...

    2.es 6矢印関数.Es 6には新しい文法糖,矢印関数が提供されている.矢印関数のthisは、関数定義時のthis値を永遠に指します.
    var C = function(){
        this.name = 'test';
        this.greet = ()=>{
            console.log('Hello,I am '+this.name+'!');
        };
    }
    var obj = new C();
    
    obj.greet();//   Hello,I am test!
    
    setTimeout(obj.greet, 1000);//   Hello,I am test!

    前の例を矢印関数にした後、2つの出力結果は同じになりました.この値はもう変わりません.これは私たちが望んでいることです.
    小結
    これは非常に小さな知識点のように見えますが、実際に掘り起こすには細部がたくさんあります.特に規範の中の定義は、jsプログラマーにとって非常に重要だと思います.私の后ろにもecma规范の中の知识の点を探してみんなと分かち合うつもりで、みんなに役に立つことを望みます.本人の能力が限られているため、間違ったところを理解して指摘してほしい.