this,真的折磨了我蛮久,躺在了我的笔记本中也挺久....今天总结下,this的一系列问题吧.
1.this永远指向一个对象;
2.this的指向完全取决于函数调用的位置.
针对上面的两点,我们可以保证到一个,不管在什么地方使用this,它必然会指向某个对象;确定了第一点后,也引出了一个问题,就是this使用的地方到底在哪里,而this就是函数运行时所在的对象.这本来并不会让我们糊涂,但是在Javascript支持运行环境动态切换,也就是说,this的指向是动态的,很难事先会确定到底指向哪个对象,这才是最让我们感到困惑的地方.
先看原理吧:
function fun(){ console.log(this.s); } var obj={ s:'1', f:fun } var s='3'; obj.f();//1 fun();//3
上述代码中,fun函数被调用了两次,但是两次执行的结果是不一样的.大多数的理解的话,是obj.f()的调用中,因为运行环境在obj对象内,因此函数中的this指向对象obj;而在全局作用域下调用fun(),函数中的this就会指向全局作用域对象window;但是,最开始还是并没有了解到this的指向为啥改变,this指向的改变是什么时候发生的.
首先,我们知道在JS中,数组函数和对象都是引用类型,在参数传递时也就是引用传递.比如上述的代码中,obj对象有两个属性,但是属性的值类型是不同的,在内存中的表现也是不同的.
调用的时候,就会变成
因为函数在JavaScript中,既可以当作值传递与返回,也可以当作对象与构造函数,所有函数在运行时需要确定其当前的运行环境,因此就引出了this,this会根据运行环境的改变而改变,同时,函数中的this也只能在运行时才能最终确定运行环境.
接下来再看个例子,有助于理解运行环境的动态切换规则:
var C={ name:"王五", f:function(){ console.log("姓名" + this.name); } var D={ name:"李四"}; D.f = C.f; D.f();//姓名李四 C.f();//姓名王五
上述代码中,C.f中的属性被赋给D.f,也就是C对象将匿名函数的地址赋值给了D对象;在调用时,函数分别根据运行环境的不同,指向不同的对象.
function baz(){ console.log(this.a); } var obj2={ a:2, fn:baz }; var obj1={ a:1, o1:obj2}; obj1.o1.fn(); //2
obj1对象的o1属性值是obj2对象的地址,而obj2对象的fn属性的值是函数baz的地址;函数baz的调用环境始在obj2中的,因此this指向对象obj2.
接下来,再叙述下几种常用的this情况.
首先,我们先看下事件绑定情况下的,事件绑定的话一共有三种方式,分别为行内绑定、动态绑定、事件监听;
行内绑定的两种情况:
<input type='button' value="ljy的按钮" onclick="btnclick()"> <script> function btnclick(){ this//此函数的运行环境为window上,因此this指向window;} </script>
<input type="button" value="ljy的button" onclick="this"> //运行环境在节点对象中,因此this指向本节点对象
当事件触发时,属性值就会作为JS代码被执行,当前运行环境没有btnclick函数,因此浏览器就需要跳出当前运行环境,在整个环境中寻找一个btnclick的函数并执行,所以函数内部的this指向了全局对象window;若不是一个函数调用,直接在当前节点对象环境下使用this,那么this显然指向本节点对象.
动态绑定与事件监听:
<input type="button" value="ljy的按钮" id="btn"> <script> var btn= document.getElementById('btn'); btn.onclick= function(){ this;//this指向本节点对象} </script>
因为动态绑定的事件本就是为节点对象的属性,重新复制一个匿名函数,因此函数在执行时就是在节点对象环境下,this自然就指向了本节点对象.
构造函数中的this:
function Person(){ this.x="11"; this.y = function(){}; } var person=new Person();
new一个构造函数并执行函数内部代码的过程:
1.创建一个空对象;2.将本对象的原型指向Person.prototype(对象继承Pesron.prototype);3.将构造函数的this指向本对象;4.执行构造函数的代码为对象赋值;5.返回本对象地址;
当Javascript引擎指向到第三步的时候,会强制的将this指向新创建出来的这个对象.所以这里的this指向person.
定时器中的this:
var obj={ fun:function(){ this;} } setInterval(obj.fun,2000);//指向window setInterval('obj.fun()',2000);//指向obj
setInterval()/setTimeout()是window对象下内置的一个方法,接受两个参数,第一个参数,可以是一个函数或者一段js代码,第二个参数实质性前面函数或者代码的时间间隔.
在上述代码中,setInterval(obj.fun,2000)的第一个参数是obj对象的fun,因为Javascript中函数可以被当作值来做引用传递,实际就是将这个函数的地址当作参数传递给了setInterval,那么2000毫秒后,函数的运行就已经是再window对象下了,也就是函数的调用者成为了window,因此指向window.那么下面一个,中的第一个参数,实际则是传入了一段JS代码,2000毫秒后,当JS引擎来执行这段代码的时候,则是通过obj对象来找到fun函数并执行调用的,那么函数依旧执行在对象obj内,所以函数内部的this指向了obj对象.
再来一个定时器的例子!
var name='My name is window'; var obj={ name:'I am obj."; fun:function(){ var timer=null; clerInterVal(timer); timer=setInterval(function(){ console.log(this.name);//My name is window. },1000) } }
在这里,从this.name可以看出this的指向是window.如果没有特殊指向,setInterval和setTimeout的回调函数中this的指向是window.那么怎么修改this的指向,这里总结了点:
在外部函数中,将this存为一个变量,回调函数中使用该变量,而不是直接使用this.
var name='My name is window'; var obj={ name:'I am obj."; fun:function(){ var that=this; var timer=null; clerInterVal(timer); timer=setInterval(function(){ console.log(that.name);//I am obj. },1000) } }
还有一个非常妙的用法,箭头函数:
首先,先介绍下箭头函数中的this指向问题,箭头函数本身是没有自己的this的,它的this继承自外部函数的作用域.我们来举两个例子:
const obj={ num:10, hello:function(){ console.log(this);//obj setTimeout(()=>{ console.log(this);//obj }); } } obj.hello()
这个箭头函数的上一级为hello函数,它是属于obj的,因此其this指向obj.
const obj={ radius:10; diameter(){ return this.radius*2; }, pre:()=>2*Math.PI*this.radius } console.log(obj.diameter())//20 console.log(obj.pre())//NaN
diameter的原因就不说了,pre是箭头函数,this应指向上下文函数this的指向,这里上下文没有函数对象,所以就默认为window,window里面没有radius属性,因此返回NaN.
那么,我们通过箭头函数也可以非常ok的修改setTimeInterval中的this指向.
var name='My name is window'; var obj={ name:'I am obj."; fun:function(){ var that=this; var timer=null; clerInterVal(timer); timer=setInterval(()=>{ console.log(this.name);},1000)//I am obj } }