【译】深刻理解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
相关文章
相关标签/搜索