Zerlinda's Blog

深入理解JavaScript之this指向

this永远指向函数运行时所在的对象,而不是函数被创建时所在的对象。
● 1.如果是call,apply,with,指定的this是谁,就是谁。
● 2.普通的函数调用,函数被谁调用,this就是谁。

JS中, this的值到底是什么? 

首先要了解,如果在任何函数定义之外声明了一个变量,则该变量为全局变量,且该变量是定义在window上的,该变量的值在整个持续范围内都可以访问和修改。

如果在函数定义内声明了一个变量,则该变量为局部变量。每次执行该函数时都会创建和破坏该变量;且它不能被该函数外的任何事物访问。局部变量一定要以var申明,否则是全局变量。

1. 函数调用模式

当一个函数被当作一个普通函数来调用, 此时的this指向全局对象(window)。下面是一个典型的例子,可以帮助你更好的理解函数内的this指向:

var x = 1;    //== var window.x = 1
function test() {
    this.x = 2;
    this.getX = function() {
	return this.x;
    }
}
test();
console.log(test.x);       //undified
console.log(test.getX());   //报错,test并不存在这样一个方法

可以看出来test并没有属性x以及方法getX()。这说明在test内部的this并不指向test。 继续做修改:

var x = 1;
function test() {
    test.x = 2;
    test.getX = function() {
	return test.x;
    }
}
test();
console.log(test.x);   //2
console.log(test.getX());   //2

以上代码说明了什么呢?test内部是可以定义方法和变量的。而我们通过前一种方法获取不到的原因就是因为this的指向并不是函数本身而是window。

最后一步,我们再具体输出一下this试试:

var x = 1;      
function test() {
    console.log(this);    //window
    this.x = 2;
    this.getX = function() {
        console.log(this);   //window
	return this.x;
    }
}
test();
console.log(window.x);     //2
console.log(window.getX());   //2

通过上面的例子我们明显可以看到,函数内部的this指向全局window,而通过this,在函数内部定义的方法,其this亦是指向全局window。

个人感觉以上代码值得好好研究一下。

2. 构造函数调用模式

在使用new操作符来调用一个构造函数的时候, 此时的this指向该构造器函数的实例对象。同样是以上的函数,我们将它当作构造函数来调用从而创建该函数的实例化对象:

var x = 1;      
function test() {
    this.x = 2;
    this.getX = function() {
        console.log(this);   //test{....} 注意不带括号,对象
	return this.x;
    }
    console.log(this);    //test{...}
}
var Obj = new test();
console.log(Obj.x);     //2
console.log(Obj.getX());   //2

不言而喻,通过测试,我们可以看出,通过new操作调用构造函数,其this指向其实例化对象。

其实说到这里,有些人可能会有些迷糊,到底构造函数跟普通函数有什么区别呢?

还是代码最具说服力:

var x = 1;      
function test() {
    this.x = 2;
    this.getX = function() {
	return this.x;
    }
    console.log(this);   
}
var f = test,
    ff = test(),      //执行console.log(this); ->window
    fff = new test();   //执行console.log(this); ->test{….}
console.log("------------");
console.log(f);      //test(){……..}注意有(),方法
console.log(ff);     //undified(因为test()作为函数没有return返回值)
console.log(fff);     //test{….}

具体其中的实现细节及原理就不细说了。感兴趣的自己研究一下,相信收获一定会很多。

3. 方法调用模式

当一个函数被保存为对象的一个属性时, 我们称它为一个方法, 当一个方法被调用时, this指向该对象, 如:

var obj = {
    x: 3,
    getX: function() {
	return this.x;
    }
}
console.log(obj.getx());

这种写法使用对象字面量定义对象,不需要定义构造函数,缺点是每创建一个新的对象都需要写出完整的定义语句,不便于创建大量相同类型的对象,不利于使用继承等高级特性,在这里也不赘述了。

4. apply/call调用模式

call 和 apply 都是为了改变某个函数运行时的上下文而存在的,换句话说,就是为了改变函数体内部 this 的指向。因为 JavaScript 的函数存在「定义时上下文」和「运行时上下文」以及「上下文是可以改变的」这样的概念。

二者的作用完全一样,只是接受参数的方式不太一样。如果没有提供 Obj 参数,那么window对象被用作 thisObj。

Call:

语法:call([Obj[,arg1[, arg2[,   [,.argN]]]]])

Apply:

语法:apply([Obj[,arguments]])

它们的不同之处:

apply:最多只能有两个参数——新的this对象和数组 args。args数组用于向函数传递参数。如果 args 不是一个有效的数组或者不是 arguments 对象,那么将导致一个 TypeError。

call:通过call方法向函数传递参数时传递的是每一个参数的具体值,因此参数可以是多个,如果没有提供 Obj 参数,那么window对象被用作 thisObj。

var obj1 = {
    xx: 0,
    getX: function() {
	console.log(this.xx);
    }
},
obj2 = {
    xx: 12
}
obj1.getX.call(obj2);   //输出12

总结:apply和call方法可以让我们设定调用者中的this指向谁。当明确了参数的具体数量,用 call;而不确定的时候,用 apply,然后把参数 push 进数组传递进去。当参数数量不确定时,函数内部也可以通过 arguments 这个数组来便利所有的参数。 

*(补充)箭头函数:箭头函数的this总是指向函数所在的对象,类似于构造函数和方法调用的this。

var id = 33;
function foo(){
  setTimeout(function(){
    console.log("id: ", this.id);
  }, 100);
}   
foo.call({id: 12});     // id:33
function foo(){
  setTimeout(()=>{
    console.log("id: ", this.id);
  }, 100);
}
foo.call({id: 12});     // id:12

发表评论

电子邮件地址不会被公开。 必填项已用*标注