js中this关键字测试集锦

 参考:阮一峰《javascript的this用法》及《JS中this关键字详解javascript

this是Javascript语言的一个关键字它表明函数运行时,自动生成的一个内部对象,只能在函数内部使用。定义:this是包含它的函数做为方法被调用时所属的对象。总原则,this指的是,调用函数的那个对象,this永远指向函数运行时所在的对象!而非建立时的。
html

 如下是基于浏览器环境作的测试:java

做为函数调用: 程序员

function $(){
    this.count = 1;
    return this;
}
window.onload = function(){
    console.info($());
}

控制台返回结果以下:编程

一个window对象。segmentfault

对于内部函数,即声明在另一个函数体内的函数,这种绑定到全局对象的方式会产生一个问题,即它会隐式声明全局变量。代码以下:数组

var point = {
    x: 0,
    y: 0,
    moveTo: function(x, y) {
        var fn1 = function(x) {
            this.x = this.x + x;
            return this.x;
        };
        var fn2 = function(y) {
            this.y = this.y + y;
        };
        return fn1();
    }
}
console.log(point.moveTo());

结果是:浏览器

而若将fn1中return的值改成this的话,打印结果:app

一个window全局对象。这属于 JavaScript 的设计缺陷,正确的设计方式是内部函数的 this 应该绑定到其外层函数对应的对象上,这个设计错误错误的后果是方法不能利用内部函数来帮助它工做,由于内部函数的this被绑定了错误的值,因此不能共享该方法对对象的访问权。为了规避这一设计缺陷,聪明的 JavaScript 程序员想出了变量替代的方法,约定俗成,该变量通常被命名为 that。以下:框架

var point = {
    x: 0,
    y: 0,
    moveTo: function(x, y) {
        var that = this;
        var x = x;
        var y = y;
        var fn1 = function(x) {
            that.x = that.x + x;
            return that;
        };
        var fn2 = function(y) {
            that.y = that.y + y;
        };
        return fn1(x);
    }
}
console.log(point.moveTo(1,1));  

返回结果:

函数调用中,this老是指向函数的直接调用者(而非间接调用者)。若是在严格模式下,有可能输出undefined,严格模式下,禁止this关键字指向全局对象。以下:

'use strict';
    console.log(this === window); // true
    var foo = function() {
        console.log(this === window);
        console.log('this:',this);
    };
    foo();
    window.foo();

控制台打印结果是:

 使用that的另外一个例子:

var myObj = {
    value: 0,
    increment: function(inc) {
        this.value += typeof inc === 'number' ? inc: 1;
    }
}
myObj.increment();
document.writeln(myObj.value);
myObj.increment(2);
document.writeln(myObj.value);
function add(num1, num2){
    return num1 + num2;
}
console.log(add(40,27));
myObj.double = function() {
    var that = this;
    var helper = function() {
        that.value = add(that.value, that.value);
    }
    helper();
}
myObj.getValue = function() {
    var that = this;
    return that.value;
}
myObj.double();
document.writeln(myObj.getValue());

 

做为对象方法调用:

若是一个调用表达式包含一个属性存取表达式(即一个.点表达式或者[subscript]下标表达式),那么它被当作一个方法来调用。另外一段代码:

var o = {};
o.fn = $;
function $(){
    console.log(this);
}
window.onload = function(){
    o.fn();
}

控制台返回结果以下:

调用函数的o对象。

在事件中,this指向触发这个事件的对象,特殊的是,IE中的attachEvent中的this老是指向全局对象Window;

另一种嵌套调用:

 

var personA={
    name:"xl",
    showName:function(){
        console.log(this.name);
    }
}
var personB={
    name:"XL",
    sayName:personA.showName
}
personB.sayName();

控制台输出结果:

从这里很明显的看出,在js中this关键字指向是直接调用它的对象,而非间接的。

 

做为函数及对象方法的混合调用:

var myObject={

    foo : "",
    func : function(){

    var self = this;
    console.log("outer func : this.foo = " + this.foo );
    console.log("outer func : self.foo = " + self.foo );

    (function(){
        console.log("inner func : this.foo = " + this.foo );
        console.log("inner func : self.foo = " + self.foo );
    }());
}
}
myObject.func();

输出结果以下:

证实函数内部函数的调用是由全局对象引起的,这在上面有所阐述。

将构造函数做为函数调用:

function Person(name) {
    this.name = name;
} 
var personA = Person("xl");
// console.log(personA.name);
console.log(window.name);
console.log(name);
var personB = new Person("xl");
console.log(personB.name);

控制台打印结果:

注释掉的console由于那么已经成为全局对象的属性,所以打印为未定义变量报错。第二个显式调用,第三个隐式调用。最后一个则是做为构造方法调用。

 

做为构造函数调用:

JavaScript 支持面向对象式编程,与主流的面向对象式编程语言不一样,JavaScript 并无类(class)的概念,而是使用基于原型(prototype)的继承方式。相应的,JavaScript 中的构造函数也很特殊,若是不使用 new 调用,则和普通函数同样。做为又一项约定俗成的准则,构造函数以大写字母开头,提醒调用者使用正确的方式调用。若是调用正确,this 绑定到新建立的对象上。

经过new实例化一个函数:

function $(){
    console.log(this);
}
var fn = new $();

 

控制台输出以下:

this就指这个新对象。

 

使用apply或call调用:

使用apply方法((固然使用Function.call也是能够的)),另外一个方法 call 也具有一样功能,不一样的是最后的参数不是做为一个数组统一传入,而是分开传入的。这两个方法异常强大,他们容许切换函数执行的上下文环境(context),即 this 绑定的对象。不少 JavaScript 中的技巧以及类库都用到了该方法。

function $(){
    console.log(this);
}
var o = {};
o.m = $;
o.m.apply();  

控制台打印结果:

注:Function.apply(obj,args)方法能接收两个参数

obj:这个对象将代替Function类里this对象

args:这个是数组,它将做为参数传给Function(args-->arguments)

apply()的参数为空时,默认调用全局对象。

若是将call的第一个destination的值设为一个对象,以下:

function $(){
    console.log(this);
}
var u = {};
var o = {};
o.m = $;
o.m.apply(u,null);

控制台打印结果以下:

也即this指向了apply绑定的那个对象。

做为构造函数及apply/call的混用:

//下面这段代码模拟了new操做符(实例化对象)的内部过程 function person(name){ var o={}; o.__proto__=Person.prototype; //原型继承 Person.call(o,name); return o; } var personB=person("xl"); console.log(personB.name); // 输出 xl

person里面首先建立一个空对象o,将o的proto指向Person.prototype完成对原型的属性和方法的继承。Person.call(o,name)这里即函数Person做为apply/call调用(具体内容下方),将Person对象里的this改成o,即完成了o.name=name操做。返回对象o。

 

 

做为回调函数的this:

咱们来看看 this 在 JavaScript 中常常被误用的一种状况:回调函数。JavaScript 支持函数式编程,函数属于一级对象,能够做为参数被传递。请看下面的例子 myObject.handler 做为回调函数,会在 onclick 事件被触发时调用,但此时,该函数已经在另一个执行环境(ExecutionContext)中执行了,this 天然也不会绑定到 myObject 对象上。

 button.onclick = obj.handler;

代码以下:

<p id="p">click me</p>
<script type="text/javascript">
var obj = {
    handler: function() {
        console.log(this);
    }
}
var p = document.getElementById("p");
p.onclick = obj.handler;

执行结果以下:

很显然指向是触发click事件的dom元素对象,而非obj对象。这是 JavaScript 新手们常常犯的一个错误,为了不这种错误,许多 JavaScript 框架都提供了手动绑定 this 的方法。好比 Dojo 就提供了 lang.hitch,该方法接受一个对象和函数做为参数,返回一个新函数,执行时 this 绑定到传入的对象上。使用 Dojo,能够将上面的例子改成:button.onclick = lang.hitch(myObject, myObject.handler);在新版的 JavaScript 中,已经提供了内置的 bind 方法供你们使用。

 

eval方法中this的指向:

JavaScript 中的 eval 方法能够将字符串转换为 JavaScript 代码,使用 eval 方法时,this 指向哪里呢?答案很简单,看谁在调用 eval 方法,调用者的执行环境(ExecutionContext)中的 this 就被 eval 方法继承下来了。

var name="XL";
var person={
     name:"xl",
     showName:function(){
     eval("console.log(this.name)");
  }
}

person.showName(); //输出 "xl"

var a=person.showName;

a(); //输出 "XL"

 

第一次的执行环境是person对象,第二个是global全局环境。

 

 

Function.prototype.bind()方法:

1.var name = "XL";

           function Person(name) {
              this.name = name;
               console.log(this);
              this.sayName = function() {
                  console.log(this);
                  setTimeout(function(){
                  console.log(this);
                  console.log("my name is " + this.name);
              },50)
           }
       }
var person = new Person("xl");
person.sayName();


2.var name="XL"; function Person(name){ this.name=name; this.sayName=function(){ setTimeout(function(){ console.log("my name is "+this.name); }.bind(this),50) //注意这个地方使用的bind()方法,绑定setTimeout里面的匿名函数的this一直指向Person对象 } } var person=new Person("xl"); person.sayName(); //输出 “my name is xl”;

这里setTimeout(function(){console.log(this.name)}.bind(this),50);,匿名函数使用bind(this)方法后建立了新的函数,这个新的函数无论在什么地方执行,this都指向的调用它的对象,而非window。而若是不加bind在第一段代码中,window执行环境中建立了一个变量person,被赋值了Person的实例,此时的调用顺序是person调用了sayName这个方法,这个方法被赋值了一个函数(此处有问题,该赋值函数是匿名仍是非匿名?下篇讨论),建立了一个执行环境,此时setTimeOut函数开始执行,建立一个环境。匿名函数及setTimeout/setInterval在非手动改变指向额状况下都在全局做用域当中。

SO,第一段代码的打印结果是:

sf的解释:setTimeout/setInterval/匿名函数执行的时候,this默认指向window对象,除非手动改变this的指向。在《javascript高级程序设计》当中,写到:“超时调用的代码(setTimeout)都是在全局做用域中执行的,所以函数中的this的值,在非严格模式下是指向window对象,在严格模式下是指向undefined”。本文都是在非严格模式下的状况。

第二段代码的打印结果是:

 

这至关于手动将this进行了强制转向。

 另外一种手动转向的方法:

var name="XL";
    function Person(){
        this.name="xl";
        var that=this;
        this.showName=function(){
            console.log(that.name);
        }
        setTimeout(this.showName,50)
    }
    var person=new Person(); //输出 "xl"

借用了上面提到的that保存this指针值进行复用的技巧。

 

匿名函数中的this:

var name="XL";
    var person={
        name:"xl",
        showName:function(){
            console.log(this.name);
        }
        sayName:function(){
            (function(callback){
                callback();
            })(this.showName)
        }
    }
    person.sayName();  //输出 XL
    var name="XL";
    var person={
        name:"xl",
        showName:function(){
            console.log(this.name);
        }
        sayName:function(){
            var that=this;
            (function(callback){
                callback();
            })(that.showName)
        }
    }
    person.sayName() ;  //输出  "xl"

此处采用了that技巧保存this指针。

 

箭头函数(点击这里):

 箭头函数看上去是匿名函数的一种简写,但实际上,箭头函数和匿名函数有个明显的区别:箭头函数内部的this是词法做用域,由上下文肯定。示例代码:

var obj = {
     birth: 1990,
     getAge: function (year) {
     var b = this.birth; // 1990
     var fn = (y) => y - this.birth; // this.birth还是1990
     return fn.call({birth:2000}, year);
}
};
console.log(obj.getAge(2015)); // 25

控制台打印结果:

 

this的四种使用场景

面向对象的语言中,this 关键字的含义是明确且具体的,即指代当前对象。通常在编译期肯定下来,或称为编译期绑定。而在 JavaScript 中,this 是动态绑定,或称为运行期绑定,这就致使 JavaScript 中的 this 关键字有能力具有多重含义,它能够是全局对象、当前对象或者任意对象,这彻底取决于函数的调用方式,带来灵活性也带来困惑。js“超级”迟绑定( very late binding)使得函数能够对this高度复用。经过this可取得它们所属对象的上下文的方法称为公共方法。使用this关键字在面向对象语言中多数状况下是为了不命名冲突。总的来讲,JavaScript 中函数的调用有以上几种方式:做为对象方法调用,做为函数调用,做为构造函数调用,和使用 apply 或 call 调用。JavaScript 中的函数既能够被看成普通函数执行,也能够做为对象的方法执行,这是致使 this 含义如此丰富的主要缘由。

 

函数的执行环境

JavaScript 中的函数既能够被看成普通函数执行,也能够做为对象的方法执行,这是致使 this 含义如此丰富的主要缘由。一个函数被执行时,会建立一个执行环境(ExecutionContext),函数的全部的行为均发生在此执行环境中,构建该执行环境时,JavaScript 首先会建立 arguments变量,其中包含调用函数时传入的参数。接下来建立做用域链。而后初始化变量,首先初始化函数的形参表,值为 arguments变量中对应的值,若是 arguments变量中没有对应值,则该形参初始化为 undefined。若是该函数中含有内部函数,则初始化这些内部函数。若是没有,继续初始化该函数内定义的局部变量,须要注意的是此时这些变量初始化为 undefined,其赋值操做在执行环境(ExecutionContext)建立成功后,函数执行时才会执行,这点对于咱们理解 JavaScript 中的变量做用域很是重要,鉴于篇幅,咱们先不在这里讨论这个话题。最后为 this变量赋值,如前所述,会根据函数调用方式的不一样,赋给 this全局对象,当前对象等。至此函数的执行环境(ExecutionContext)建立成功,函数开始逐行执行,所需变量均从以前构建好的执行环境(ExecutionContext)中读取。

 

本文是综合了互联网多篇文章结论以后亲测的结论,目的也在于细化本身的知识体系,并进而更深入的理解js的内核实现。通过此番梳理,对this的灵活指向已经有了一个比较清晰的认识,不过还需在实际的工程中不断运用以达到彻底掌握。不过通过此一番,不必死记硬背以上多种场景,若需使用的时候,在对印位置打印出来看看不就知道了?

相关文章
相关标签/搜索