this
这个函数的特殊对象,在进行函数跨作用域访问属性和方法时,有非常大的作用,但同时它又因为是动态的,容易引起误解。
那我们先来看两个栗子
例一、js高设的P114
window.color = "red";
var o = { color: "blue" };
function sayColor(){
alert(this.color);
}
sayColor(); //red
o.sayColor = sayColor;
o.sayColor(); //blue
这里的sayColor()
在全局调用时,this
就指向了全局的变量对象,因为这时候函数所在的执行环境(或者说是包含环境)是全局环境;
而当调用的时o.sayColor()
时,因为函数(准确讲应该是对象的方法)是o
这个对象包含的执行环境内的方法,反过来说这时候这个函数所在的执行环境是o
这个对象包含的,相当于将这个函数放在o
这个对象里执行的。
例二、js高设的P117,在例一基础上使用call()
方法
sayColor.call(this);//red
sayColor.call(window);//red
sayColor.call(o);//blue
第一第二个语句返回相同结果,是因为在全局环境中的this
属性指向的不是全局某个函数或对象的包含环境,而是指定为全局变量Global或window(浏览器下)。
第三个跟例二中的sayColor()
一个道理,就是将sayColor
的this
属性指向o
对象包含的执行环境,因此这个函数就可以访问非他的作用域链上的变量对象里的方法和属性了。
再来个例子也是P117的
function sum (num1,num2) {
return num1 + num2;
}
function callSum (num1,num2) {
return sum.call(this,num1,num2);
}
callSum(10,10);
这个例子之前我一直没想明白,今天算是搞懂了。我忽略了其中的return
语句的作用。实际上在callSum()
函数内,传入sum.call()
的this
就是指向的callSum()
函数将来在其中执行的的全局环境,而不是callSum()
函数内部的执行环境。但是因为this
是动态的,通过return
返回的sum.call()
结果就直接“暴露”在了全局环境中,所以这时候就相当于是在全局环境中调用sum.call()
。那为何要多此一举地把sum.call()
放在callSum()
函数里呢?——就是为了能够获得callSum()
函数内部的执行环境的访问权限,这中return
方式将在匿名函数和闭包经常用到。
再来个数组迭代方法使用第二参数的例子
var arr = [1,2,3,4,5,6,2];
var obj = {
sum: 0
};
arr.forEach(function(item,index,array) {
this.sum += item;
console.log(index + ">" + this.sum);
},obj);
这里向forEach()
传入的obj
对象就是用来修改回调函数的this
指针的,这使得回调函数拥有了访问obj
对象内部执行环境的权限。
总结:函数的this
属性指向作为这个函数的调用者的执行环境;而全局环境的this
属性直接指向全局环境(的变量对象gloabl或window)。调用函数的call()
、apply()
和bind()
方法只是使得该函数获取了访问某个作用域/执行环境(的变量对象)的属性和方法的权限,并不改变函数原本的作用域链(嗯,最多可能是在作用域链前端再加了个this
[指向的执行环境[的变量对象[的引用]])。也就是说对于函数内的变量引用,浏览器还是能通过标识符解析规则沿函数内执行环境的作用域链逐级搜寻变量,不会被this
的改变而修改。
再思考
放个例子
var color = "red";
function foo1() {
var color = "blue";
function foo2() {
var color = "green";
console.log(this.color);//red
}
foo2();
console.log(this.color);//red
}
foo1();
console.log(this.color);//red
说明默认情况下,this
是指向全局对象的。
继续放例子
var color = 'red';
function Color () {
console.log(color);//undefined
var color = 'blue';
console.log(this);//Color {}
console.log(this.color);//undefined
}
new Color();//Color {}
这里要考虑到函数内变量color
的变量声明提升,也就是在同一个作用域中通过var
声明的变量实际上会预先读取声明的变量,但是赋值/初始化则要执行流到了代码的位置才发生,所以第一个返回的是undefined
;
第二和第三个语句的返回结果,则是这样的原因:使用new
加构造函数创建实例时,构造函数的this
是指向这个实例的,也可以理解为构造函数本身就成为了这个实例的方法,只不过是通过继承来的constructor
属性来调用的,而这里的构造函数内部并没有通过this.a
为实例创建属性a
,所以第二个返回是一个Color类型的空实例,第三个返回的undefined
(因为实例中没有a
嘛,就是未声明)
var color = 'red';
function Color () {
console.log(color);//undefined
var color = 'blue';
this.color = 'green';
console.log(this);//Color {color: "green"}
console.log(this.color);//green
}
new Color();//Color {color: "green"}
加个 this.color = 'green';
语句就可以了。
但是,另外要注意的是,如果构造函数当作普通函数调用,则结果不一样
var color = 'red';
function Color () {
console.log(color);//undefined
var color = 'blue';
this.color = 'green';
console.log(this);//Window {……}
console.log(this.color);//green
}
Color();//undefined
这里的this
值就是默认的指向了window
对象,但是在局部作用域中又定义了一个this.color
把原有的全局color='red'
覆盖了,所以第三个返回是green
。
var color = 'red';
function Color () {
console.log(color);//undefined
var color = 'blue';
console.log(this);//Window {……}
console.log(this.color);//red
}
Color)();//undefined
这里删除this.color = 'green';
语句后就返回第一行声明的var color = 'red';
了。