你该知道的JS知识---this指向

159 阅读7分钟

JS到底指向谁???

JS里的this的指向是很容易让人感到头疼的地方,而且也是面试题里面经常出现的考点。所以今天我们就来彻底搞懂this的指向
首先,要想理解this,我们要记住:
1. this永远指向一个对象
2. this的指向完全取决于函数调用的位置

上面第一点应该是比较容易理解的,不管在什么地方使用this,它一定会指向某个对象。紧接着第二点就解释了this指向哪里。在JS里面,一切皆是对象,运行环境也是对象,所以函数都是在某个对象下运行,而this就是函数运行时所在的对象。由于JS支持运行环境动态切换,也就说,this的指向是动态的,很难事先确定到底指向谁,所以才经常感到困惑。

原理

function fun(){
    console.log(this.s);
}
var obj={
    s:'1',
    f:fn
}
var s='2';
obj.f();  //1
fun();    //2

上述代码里,fun函数被调用了两次,但是得到的结果是完全不一样的。
很多人都会这样解释,obj.f()的调用中,因为运行环境在obj对象内,因此函数中的this指向对象obj
而在全局作用域下调用fun(),函数中的this就会指向全局作用域对象window
但是大部分人不会告诉你,this的指向为什么发生改变,this指向的改变到底是什么时候发生的,搞懂了这些,this的使用才不会出现意外
首先我们都知道,在JS中,数组,函数,对象都是引用类型,在参数传递的时候是引用传递
上面代码中,obj对象有两个属性,但是属性的值类型是不同的,在内存中的表现形式也是不同的

image.png 调用时就变成了

image.png 因为函数在JS中既可以当做值传递和返回,也可当做对象和构造函数,所有函数在运行时需要确定其当前的运行环境,this也随之确定。所以,this会根据运行环境的改变而改变,同时,函数中的this也只能在运行时才能最终确定运行环境。
下面再看例子,加深this对运行环境的动态切换规则

var A={
    name:'张三',
    f:function(){
        console.log('姓名'+this.name);
    }
}
var B={
    name:'李四'
}
B.f=A.f;
B.f() //'姓名:李四'
A.f() //'姓名:张三'

在上面代码中,A.f属性被赋给B.f,也就是A对象的将匿名函数的地址给了B对象
那么在调用的时候,函数根据运行环境的不同,指向对象A和B

image.png

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
以上内容就是this的原理,接下来对this使用最频繁的几种情况做一个总结

事件绑定中的this

事件绑定一共有三种方式:行内绑定,动态绑定,事件监听 行内绑定的两种情况:

<input type="button" value="按钮" onclick="clickFun()">
<script>
    function clickFun() {
       console.log(this) //此时函数运行环境在全局window对象下,因此this指向window
    }
</script>
<input type="button" value="按钮" onclick="this">
<!-- 运行环境在节点对象中,因此this指向本节点对象 -->

行内绑定事件的语法是在html节点内,以节点属性的方式绑定,属性名是事件名称前面加'on',属性的值则是一段可执行的JS代码段,而属性值最常见的就是一个函数调用
当事件触发时,属性值就会作为JS代码被执行,当前运行环境下没有clickFun函数,因此浏览器就需要跳出当前运行环境,在整个环境中寻找一个叫clickFun的函数并执行,所以函数内部的this就指向了全局对象window,如果不是一个函数调用,直接在当前节点对象环境下使用this,那么显然this就会指向当前节点对象。
动态绑定和事件监听

<input type="button" value="按钮" id="btn">
<script>
    var btn = document.getElementById('btn');
    btn.onclick = function(){
        console.log(this);  // this指向本节点对象
    }
</script>

因为动态绑定的事件本身就是为了节点对象的属性(事件名称前面加'on')重新赋值为一个匿名函数,因此函数在执行时就是在节点对象环境下,this自然就指向了笨节点对象。
事件监听中的this指向原理和动态绑定原理基本一致。

构造函数中的this

function foo(){
    this.x='1';
    this.y=function(){};
}
var f=new foo();

image.png 对于接触过JS面向对象编程的来说,上面的代码和图示基本都能看懂,new一个构造函数并执行函数内部代码的过程就是这五个步骤,当JS引擎指向到第3步的时候,会强制的将this指向新创建出来的这个对象。 window定时器中的this

var obj={
    fun:functon(){
        this;
    }
}
setInterval(obj.fun,1000) //this指向window对象
setInterval('obj.fun('`,1000)  //this指向obj对象

setInterval()是window对象下内置的一个方法,接收两个参数,第一个参数运行是一个函数或者是一段可执行的JS代码,第二个参数则是执行前面函数或者代码的时间间隔。
在上面的代码中,setInterval(obj,fn,1000)的第一个参数是obj对象的fn,因为JS中函数可以被当作值来引用传递,实际就是将这个函数的地址当作参数传递给了setInterval方法,换句话说就是setInterval的第一个参数接受了一个函数,那么此时1000ms后,函数的运行就已经是在window对象下了,也就是函数的调用者已经变成了window对象,所以其中的this则指向的全局window对象。
而在setInterval('obj.fn()',1000)中的第一个参数,实际上传递的是一段可执行的JS代码,1000ms后当JS引擎来执行这段代码时,则是通过obj对象找到fun函数并调用执行,那么函数的运行环境依然在对象obj内,所以函数内部的this也就指向了obj对象。

函数对象的call(),apply(),bind()方法

apply
apply接受两个参数,第一个参数是this的指向,第二个参数是函数接受的参数,以数组的形式传入,且当第一个参数为null、undefined的时候,默认指向window(在浏览器中),使用apply方法改变this指向后原函数会立即执行,且此方法只是临时改变thi指向一次。

call
call方法的第一个参数也是this的指向,后面传入的是一个参数列表(注意和apply传参的区别)。当一个参数为null或undefined的时候,表示指向window(在浏览器中),和apply一样,call也只是临时改变一次this指向,并立即执行。

bind
bind方法和call很相似,第一参数也是this的指向,后面传入的也是一个参数列表(但是这个参数列表可以分多次传入,call则必须一次性传入所有参数),但是它改变this指向后不会立即执行,而是返回一个永久改变this指向的函数。

三者区别

  • 三者都可以改变函数的this对象指向。
  • 三者第一个参数都是this要指向的对象,如果如果没有这个参数或参数为undefined或null,则默认指向全局window。
  • 三者都可以传参,但是apply是数组,而call是参数列表,且apply和call是一次性传入参数,而bind可以分为多次传入。
  • bind 是返回绑定this之后的函数,便于稍后调用;apply 、call 则是立即执行 。