【譯】深刻理解JavaScript系列:3. this

this的定義

this是執行環境對象的屬性,指向執行環境運行時所在的對象;
this與可執行代碼的類型直接相關,其值在進入執行環境階段肯定,並且在執行過程當中保持不變;javascript

全局環境中的this

在全局執行環境中,this老是執行全局對象自己;前端

//顯式的定義全局對象的屬性
    this.a=10;
    console.log(a);
    //隱式的定義全局對象的屬性
    b=20;
    console.log(b);
    //經過變量聲明間接的定義全局對象的屬性
    var c=30;
    console.log(this.c);//30

函數執行環境中的this

在函數執行環境中,this值不是靜態綁定到函數的;java

this值是在進入執行環境時肯定的,指向調用函數時的執行環境,對於同一函數經過不一樣方式調用其this指向不一樣的對象;數組

this值肯定以後,在代碼執行階段其值是不變的,this值不是變量因此不能爲其分配新值;app

var foo = { x:10 };
    var bar={
        x:20,
        test: function(){
            console.log( this ); // bar, { x: 20, test: [Function] }
            console.log( this.x ); // 20
        }
    };
    bar.test();
    foo.test=bar.test;
    foo.test(); //this指向foo, { x: 10, test: [Function] }

this是由激活函數執行環境的caller提供, 好比調用函數的父級執行環境,但決定this值的唯一因素是調用表達式的形式,即調用函數的語法形式函數

通常會說this值取決於函數的定義方式,全局環境中的函數this值指向全局對象,做爲對象的方法的函數this指向該對象,這種說法是錯誤的;this

  • 全局函數的this不指向全局對象的情形:
var variable = 22;
    function foo(){
        var variable = 11;
        console.log(this.variable);
    }
    console.log( "here" );
    foo(); //22
    console.log( foo === foo.prototype.constructor );// true
    foo.prototype.constructor(); // undefined
  • 對象方法的this不指向對象的情形:
var foo={
        bar:function(){
            console.log(this===foo);
        }
    };
    foo.bar(); // true
    var test=foo.bar;
    console.log(test === foo.bar); // true
    test(); // false

Reference類型

引用類型的值

引用類型的值能夠表示爲包含兩個屬性的對象:prototype

  • base屬性: 表示擁有屬性的對象;
  • propertyName屬性: 屬性名;
var valueOfReferenceType={
        base:<base object>,
        propertyName: <property name>
    };

引用類型的值只適用於標識符解析和屬性訪問兩種狀況:code

標識符解析對象

標識符解析返回的值是一個引用類型的值;
標識符包括變量名、函數名、函數的形式參數名和全局對象中的unqualified的屬性名;

var foo=10;
    function bar(){};

以上代碼對應的引用類型以下所示:

// 變量 foo
    var fooReference={
        base:global,
        propertyName:"foo"
    }
    // 函數 bar
    var barReference={
        base:global,
        propertyName:"bar"
    }

爲了從引用類型的值獲得對真正的值,在內部使用GetValue()方法,用僞代碼表示以下:

內部方法[[Get]]用於從對象獲取指定屬性,包括從原型鏈繼承而來的屬性;

function GetValue(value){
        //非引用類型的值直接返回
        if( Type(value) != Reference){
            return value;
        }
        //獲取值所屬的對象
        var base=GetBase( value );
        if( base===null ){
            throw new ReferenceError;
        }
        //返回從值所屬的對象獲取的屬性值,經過 [[Get]] 方法
        var propertyName = GetPropertyName( value );
        return base.[[Get]]( propertyName );
    }

獲取fooReferencebarReference的值:

GetValue(fooReference); // 10
    GetValue(barReference); // 函數對象 bar

屬性訪問

屬性訪問分爲點訪問法和方括號訪問法;

點訪問法中的屬性必須是標識符;

foo.bar();
    foo["bar"]();

引用類型的值與函數執行環境中的this值的關係

函數執行環境中的this由激活函數執行環境的caller提供,但決定this值的唯一因素是調用表達式的形式,即調用函數的語法形式;

若是表示函數調用的圓括號的左側是一個引用類型的值,則函數中的this指向該引用類型值的base對象;

標識符:

function foo(){
        return this;
    }
    foo(); // window

    // 標識符foo的引用類型表示
    var fooReference={
        base: global,
        propertyName: "foo"
    }

    foo.prototype.constructor(); // foo.prototype
    // foo.prototype.constructor是屬性
    var fooPrototypeConstructorReference = {
        base:foo.prototype,
        propertyName:"constructor"
    }

屬性訪問:

var foo={
        bar:function(){
            return this;
        }
    };
    foo.bar(); // foo

    //屬性foo.bar的引用類型表示
    var fooBarReference={
        base: foo,
        propertyName: "bar"
    };

    //經過不一樣形式的調用表達式調用同一函數:
    var test = foo.bar;
        test(); // window

    // test是標識符
        var testReference={
            base:global,
            propertyName:"test"
        }

當表示函數調用的圓括號的左側不是引用類型的值時,this老是設置爲null;

由於將this設置爲null值沒有意義,因此隱式的將其轉換爲全局對象;

在ES5的嚴格模式下this值再也不強迫轉換爲全局對象,而是設置爲undefined;

  • 圓括號左側不是標識符或屬性訪問表達式,而是一個函數對象:
(function(){
        console.log(this); // window
    })();
  • 複雜情形:
var foo={
        bar: function(){
            console.log(this);
        }
    };
    foo.bar(); // foo
    
    (foo.bar)(); //foo
    //第一個圓括號是一個組運算符,從引用類型獲取值的GetValue()方法不適用與該運算符(why),其返回值仍然是引用類型
    
    (foo.bar=foo.bar)();
    //賦值運算符, 使用GetValue()方法進行求值,返回值是一個函數對象,因此null
    
    (false || foo.bar)();
    // 返回foo.bar表示的值,函數對象,因此null
    
    (foo.bar,foo.bar)();
    // 返回函數對象,因此null

當引用類型值的base對象是活動對象時,this值將指向null,並轉換爲全局對象;

function foo(){
        function bar(){
            console.log(this); // gloabl
        }
        bar(); //等價於AO.bar(), 但AO做爲base對象返回null
    }

with, catch和遞歸調用

經過with調用函數時,with對象被添加到做用域鏈最前端,屏蔽外層函數的活動對象或者全局對象,函數中的this老是指向with對象;

var x=10;
    with({
        foo:function(){
            console.log(this.x)
        },
        x:20
        }){
        foo(); // 20
    }

foo的引用類型:

var fooReference={
        base:__withObject,
        propertyName:"foo"
    };

ES3中調用catch語句傳入的參數函數時,函數中的this指向catch對象,而不是全局對象或活動對象;

這被認爲是一個bug, 在ES5中獲得修正,this將指向全局對象;

try{
        throw function(){
            console.log(this);
        };
    }catch(e){
        e();
    }
    
    var eReference={
        base:global,
        propertyName:"e"
    };

在遞歸的調用命名函數表達式時,第一次調用中base對象是外層函數的活動對象或者全局對象,在以後的遞歸調用中base對象應該是存儲函數表達式名的特殊對象,但實際上this值老是指向全局對象;

(function foo(bar){
        console.log(this);
        !bar && foo(1);
    })()
    //window window

構造函數中的this

在構造函數內this老是指向新建立的對象;
new操做符調用構造函數的內部方法[[construct]]建立新對象,對象建立以後在構造函數上調用[[call]]方法,this指定爲新建的對象,初始化新對象;

function Foo(){
        console.log(this);
        this.x=10;
    }
    var foo=new Foo();
    console.log(foo.x);

手動設置函數中的this

Function.prototype上定義了apply()call()方法用於手動的指定函數調用中的this;
call()apply()的第一個參數是在函數執行環境中使用的this值, 其餘參數傳入調用的函數;

call接受任意數量的參數,apply數組做爲參數;

var variable = 1;
    function act(arg) {
        console.log(this.variable);
        console.log(arg);
    }
    act(2);
    // 1, 2
    
    act.call({variable : 3}, 4);
    // 3, 4
    
    act.apply({variable : 5}, [6]);
    // 5, 6
相關文章
相關標籤/搜索