JavaScript中的this让很多开发者头疼,this关键字是一个非常重要的句点。 可以毫不夸张地说,如果不理解其含义,大多数开发任务都无法完成。
要理解这一点,首先可以记住以下两点:
1:this始终指向一个对象;
2:this的意义完全取决于函数调用的位置;
上面第一点很容易理解。 无论在哪里使用它,它都必须指向一个对象; 重点说明了这个问题,但关键是在JavaScript语言中,一切都是对象,运行环境也是对象,所以函数是在某个对象下运行的,而这就是函数所在的对象(环境)运行。 这本来并不让我们困惑,但是JavaScript支持运行环境的动态切换,也就是说this的指向是动态的,很难预先确定它指向哪个对象。 这是我们最困惑的地方。
先看原理
function fun(){
console.log(this.s);
}
var obj = {
s:'1',
f:fun
}
var s = '2';
obj.f(); //1
fun(); //2
上面的代码中javascript绑定事件绑定,fun函数被调用了两次,很明显两次的结果是不同的;
很多人会这样解释,在obj.f()的调用中,因为运行环境在obj对象中,所以函数中的this指向对象obj;
而当在全局作用域中调用fun()时,函数中的this会指向全局作用域对象window;
但大多数人不会告诉你这个点为什么变化,这个点的变化是什么时候发生的; 了解了这些之后,这个的用处就不会出乎意料了;
首先我们要知道,在JS中,数组、函数、对象都是引用类型,在传递参数时也是按引用传递;
上面的代码中,obj对象有两个属性,但是属性的值类型不同javascript绑定事件绑定,在显存中的表示形式也不同;
调用时看起来像这样:
因为函数在js中可以作为值传递和返回,也可以作为对象和构造函数,所以所有的函数在运行时都需要确定自己当前的运行环境,而这就是诞生的,所以这会根据运行环境而改变并且改变,同时函数中的this只能在运行时最终确定运行环境;
看看下面的代码,你可能会更加了解这个对于运行环境的动态切换规则:
var A = {
name: '张三',
f: function () {
console.log('姓名:' + this.name);
}
};
var B = {
name: '李四'
};
B.f = A.f;
B.f() // 姓名:李四
A.f() // 姓名:张三
上面代码中,将Af属性赋值给Bf,即A对象将匿名函数的地址赋值给B对象;
然后在调用时,函数根据运行环境的不同,分别指向对象A和B。
function foo() {
console.log(this.a);
}
var obj2 = {
a: 2,
fn: foo
};
var obj1 = {
a: 1,
o1: obj2
};
obj1.o1.fn(); // 2
obj1对象的o1属性值为obj2对象的地址,obj2对象的fn属性值为函数foo的地址;
函数foo的调用环境在obj2中,所以this指向对象obj2;
那么,我们来总结一下这个最常用的情况。 最常见的是以下五种:
对象中的方法、事件绑定、构造函数、定时器、函数对象的call()、apply()方法;
上面解释的this的原理是我们在对象方法中使用this来解释的,所以这里不再重复解释,不懂的朋友请回去再看一遍;
事件绑定中的 this
事件绑定有三种形式:内联绑定、动态绑定、事件监听;
内联绑定的两种情况:
function clickFun(){
this // 此函数的运行环境在全局window对象下,因此this指向window;
}
内联绑定storm的句型在html节点中,通过节点属性的方法进行绑定,属性名称为storm名称后的“on”,属性的值为可执行的JS代码段; 而属性值最常见的是函数调用;
当风暴触发时,属性值将作为JS代码执行。 当前运行环境中没有clickFun函数,所以浏览器需要跳出当前运行环境,在整个环境中找到一个名为clickFun的函数并执行这个函数,因此该函数内部的this指向全局对象window ; 如果不是函数调用并且直接在当前节点对象的上下文中使用this,那么即使this也会指向当前节点对象;
动态绑定和风暴攻击:
var btn = document.getElementById('btn');
btn.onclick = function(){
this ; // this指向本节点对象
}
因为动态绑定的storm是将节点对象的属性重新参数化(事件名后加‘on’)为一个匿名函数,所以该函数是在节点对象的环境中执行的,而this自然指向节点对象;
事件监听中这一点的原理与动态绑定基本相同,不再赘述;
构造函数中的 this
function Pro(){
this.x = '1';
this.y = function(){};
}
var p = new Pro();
对于接触过JS面向对象编程的朋友来说,上面的代码和图基本上都能看懂。 创建新的构造函数并执行函数内部代码的过程分为五个步骤。 当 JS 引擎指向第三步时,this 将被强制指向新创建的对象; 基本不用懂,因为这是JS中的语法规则,记住就好;
这在窗口计时器中
var obj = {
fun:function(){
this ;
}
}
setInterval(obj.fun,1000); // this指向window对象
setInterval('obj.fun()',1000); // this指向obj对象
setInterval() 是 window 对象下的外部方法。 它接受两个参数。 第一个参数可以是一个函数或者一段可执行的JS代码,第二个参数是执行下面的函数或者代码的时间。 间隔;
前面的代码中,setInterval(obj.fun,1000)的第一个参数是obj对象的fun,因为JS中的函数可以通过引用作为值传递,实际上这个函数的地址传递为给定了一个参数setInterval方法,也就是说setInterval的第一个参数接受一个函数,那么1000毫秒后,该函数的操作已经在window对象下了,也就是说,该函数的调用者已经成为了window对象,所以里面的this指向全局window对象;
setInterval('obj.fun()',1000)中的第一个参数实际上是传入的一段可执行的JS代码; 当JS引擎在1000毫秒后执行这段代码时,它通过obj对象找到fun函数并调用它执行,那么该函数的运行环境仍然在对象obj中,因此函数内部的this也指向obj目的;
函数对象的call()、apply()方法
函数以对象的形式提供 call() 和 apply() 方法,它们也可用于调用函数。 这两个方法都接受一个对象作为参数,用于指定本次调用时函数中this的方向;
调用()方法
通话模式使用的语法规则
函数名.call(obj,arg1,arg2...argN);
参数说明:
obj:函数中this要指向的对象,
arg1,arg2...argN:参数列表,用冒号分隔
var lisi = {names:'lisi'};
var zs = {names:'zhangsan'};
function f(age){
console.log(this.names);
console.log(age);
}
f(23);//undefined
//将f函数中的this指向固定到对象zs上;
f.call(zs,32);//zhangsan
apply() 方法
函数名.apply(obj,[arg1,arg2...,argN])
参数说明:
obj : this 所指向的对象
[arg1,arg2...argN] :参数列表,要求格式为链表
var lisi = {name:'lisi'};
var zs = {name:'zhangsan'};
function f(age,sex){
console.log(this.name+age+sex);
}
//将f函数中的this指向固定到对象zs上;
f.apply(zs,[23,'nan']);
注意:call和apply的功能是一样的,只是函数参数传递的方式不同;
这两个方法最大的作用基本上就是在指定函数调用时强制指向this;