zoukankan      html  css  js  c++  java
  • this指向详解及改变它的指向的方法

    一、this指向详解(彻底理解js中this的指向,不必硬背)

    首先必须说的是,this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this到底指向谁,实际上this的最终指向的是那个调用它的对象非箭头函数这句话有些问题,后面会解释为什么会有问题,虽然网上大部分的文章都是这样说的,虽然在很多情况下那样去理解不会出什么问题,但是实际上那样理解是不准确的,所以在你理解this的时候会有种琢磨不透的感觉,那么接下来我会深入的探讨这个问题。

    为什么要学习this?如果你学过面向对象编程,那你肯定知道干什么用的,如果你没有学过,那么暂时可以不用看这篇文章,当然如果你有兴趣也可以看看,毕竟这是js中必须要掌握的东西。

     

    全局环境下:在全局环境下,this 始终指向全局对象(window), 无论是否严格模式;

    console.log(this.document === document); // true
    
    // 在浏览器中,全局对象为 window 对象:
    console.log(this === window); // true
    
    this.a = 37;
    console.log(window.a); // 37

     

    例子1:普通函数调用,此时 this 指向 window

    function a(){
        var user = "追梦子";
        console.log(this.user); //undefined
        console.log(this); //Window
    }
    a();

    按照我们上面说的this最终指向的是调用它的对象,这里的函数a实际是被Window对象所点出来的,下面的代码就可以证明。

    function a(){
        var user = "追梦子";
        console.log(this.user); //undefined
        console.log(this);  //Window
    }
    window.a();

    和上面代码一样吧,其实alert也是window的一个属性,也是window点出来的。

    例子2:对象方法调用, 此时 this 指向 该方法所属的对象

    var o = {
        user:"追梦子",
        fn:function(){
            console.log(this.user);  //追梦子
        }
    }
    o.fn();

    这里的this指向的是对象o,因为你调用这个fn是通过o.fn()执行的,那自然指向就是对象o,这里再次强调一点,this的指向在函数创建的时候是决定不了的,在调用的时候才能决定,谁调用的就指向谁,一定要搞清楚这个。

    其实例子1和例子2说的并不够准确,下面这个例子就可以推翻上面的理论。

    如果要彻底的搞懂this必须看接下来的几个例子(本文出处:追梦子博客

    例子3:

    var o = {
        user:"追梦子",
        fn:function(){
            console.log(this.user); //追梦子
        }
    }
    window.o.fn();

    这段代码和上面的那段代码几乎是一样的,但是这里的this为什么不是指向window,如果按照上面的理论,最终this指向的是调用它的对象,这里先说个而外话,window是js中的全局对象,我们创建的变量实际上是给window添加属性,所以这里可以用window点o对象。

    这里先不解释为什么上面的那段代码this为什么没有指向window,我们再来看一段代码。

    var o = {
        a:10,
        b:{
            a:12,
            fn:function(){
                console.log(this.a); //12
            }
        }
    }
    o.b.fn();

    这里同样也是对象o点出来的,但是同样this并没有执行它,那你肯定会说我一开始说的那些不就都是错误的吗?其实也不是,只是一开始说的不准确,接下来我将补充一句话,我相信你就可以彻底的理解this的指向的问题。

    情况1:如果一个函数中有this,但是它没有被上一级的对象所调用,那么this指向的就是window,这里需要说明的是在js的严格版中this指向的不是window,但是我们这里不探讨严格版的问题,你想了解可以自行上网查找。

    情况2:如果一个函数中有this,这个函数有被上一级的对象所调用,那么this指向的就是上一级的对象。

    情况3:如果一个函数中有this,这个函数中包含多个对象,尽管这个函数是被最外层的对象所调用,this指向的也只是它上一级的对象例子3可以证明,

    如果不相信,那么接下来我们继续看几个例子。

    var o = {
        a:10,
        b:{
            // a:12,
            fn:function(){
                console.log(this.a); //undefined
            }
        }
    }
    o.b.fn();

    尽管对象b中没有属性a,这个this指向的也是对象b,因为this只会指向它的上一级对象,不管这个对象中有没有this要的东西。

    例子4:还有一种比较特殊的情况

    var o = {
        a:10,
        b:{
            a:12,
            fn:function(){
                console.log(this.a); //undefined
                console.log(this); //window
            }
        }
    }
    var j = o.b.fn;
    j();

    这里this指向的是window,是不是有些蒙了?其实是因为你没有理解一句话,这句话同样至关重要。

    this永远指向的是最后调用它的对象,也就是看它执行的时候是谁调用的(非箭头函数,例子4中虽然函数fn是被对象b所引用,但是在将fn赋值给变量j的时候并没有执行所以最终指向的是window,这和例子3是不一样的,例子3是直接执行了fn。

    this讲来讲去其实就是那么一回事,只不过在不同的情况下指向的会有些不同,上面的总结每个地方都有些小错误,也不能说是错误,而是在不同环境下情况就会有不同,所以我也没有办法一次解释清楚,只能你慢慢地的去体会。

    例5:构造函数调用, 此时 this 指向 实例对象

    function Fn(){
        this.user = "追梦子";
    }
    var a = new Fn();
    console.log(a.user); //追梦子

    这里之所以对象a可以点出函数Fn里面的user是因为new关键字可以改变this的指向,将这个this指向对象a,为什么我说a是对象,因为用了new关键字就是创建一个对象实例,理解这句话可以想想我们的例子3,我们这里用变量a创建了一个Fn的实例(相当于复制了一份Fn到对象a里面),此时仅仅只是创建,并没有执行,而调用这个函数Fn的是对象a,那么this指向的自然是对象a,那么为什么对象a中会有user,因为你已经复制了一份Fn函数到对象a中,用了new关键字就等同于复制了一份。

    除了上面的这些以外,我们还可以自行改变this的指向,关于自行改变this的指向请看JavaScript中call,apply,bind方法的总结这篇文章,详细的说明了我们如何手动更改this的指向。

    更新一个小问题当this碰到return时

    例6:当this遇到return

    function fn()  
    {  
        this.user = '追梦子';  
        return {};  
    }
    var a = new fn;  
    console.log(a.user); //undefined

    再看一个

    function fn()  
    {  
        this.user = '追梦子';  
        return function(){};
    }
    var a = new fn;  
    console.log(a.user); //undefined

    再来一个

    function fn()  
    {  
        this.user = '追梦子';  
        return 1;
    }
    var a = new fn;  
    console.log(a.user); //追梦子

    再来一个

    function fn()  
    {  
        this.user = '追梦子';  
        return undefined;
    }
    var a = new fn;  
    console.log(a.user); //追梦子

    什么意思呢?

    如果返回值是一个对象,那么this指向的就是那个返回的对象,如果返回值不是一个对象那么this还是指向函数的实例。

    function fn()  
    {  
        this.user = '追梦子';  
        return undefined;
    }
    var a = new fn;  
    console.log(a); //fn {user: "追梦子"}

    注意:还有一点就是虽然null也是对象,但是在这里this还是指向那个函数的实例,因为null比较特殊。

    function fn()  
    {  
        this.user = '追梦子';  
        return null;
    }
    var a = new fn;  
    console.log(a.user); //追梦子

    例7:定时器中的this指向window

    var name = 'my name is window';
    var obj = {
        name: 'my name is obj',
        fn: function () {
            var timer = null;
            clearInterval(timer);
            timer = setInterval(function () {
                console.log(this.name);  //my name is window
            }, 1000)
        }
    }

    在这里,从this.name可以看出this的指向是window。

    如果没有特殊指向,setInterval和setTimeout的回调函数中this的指向都是window。这是因为JS的定时器方法是定义在window下的。但是平时很多场景下,都需要修改this的指向。这里总结了几种:

    例8:DOM 事件处理函数中的 this & 内联事件中的 this

    DOM事件处理函数:

    1.当函数被当做监听事件处理函数时, 其 this 指向触发该事件的元素 (针对于addEventListener事件);

    // 被调用时,将关联的元素变成蓝色
        function bluify(e){
          //在控制台打印出所点击元素
          console.log(this);
          //阻止时间冒泡
          e.stopPropagation();
          //阻止元素的默认事件
          e.preventDefault();      
          this.style.backgroundColor = '#A5D9F3';
        }
    
        // 获取文档中的所有元素的列表
        var elements = document.getElementsByTagName('*');
    
        // 将bluify作为元素的点击监听函数,当元素被点击时,就会变成蓝色
        for(var i=0 ; i<elements.length ; i++){
          elements[i].addEventListener('click', bluify, false);
        }

    以上代码建议在网页中执行以下,看下效果。

    内联事件:

    内联事件中的this指向分两种情况:

    1. 当代码被内联处理函数调用时,它的this指向监听器所在的DOM元素
    2. 当代码被包括在函数内部执行时,其this指向等同于 ****函数直接调用****的情况,即在非严格模式指向全局对象window, 在严格模式指向undefined

    页面的代码块

    依次点击上边的三个按钮后在控制台的输出结果,

    建议自己操作一遍以便于更好的理解。

    例9:使用es6的箭头函数:箭头函数的最大作用就是this指向。

    箭头函数没有自己的this,它的this继承自外部函数的作用域。所以,在该例中,定时器回调函数中的this,是继承了fn的this。当然箭头函数也有兼容问题,要是兼容低版本ie,需要使用babel编译,并且引入es5-shim.js才可以。

    由于箭头函数不绑定this, 它会捕获其所在(即定义的位置/定义生效时)上下文的this值, 作为自己的this值,

    1. 所以 call() / apply() / bind() 方法对于箭头函数来说只是传入参数,对它的 this 毫无影响。
    2. 考虑到 this 是词法层面上的,严格模式中与 this 相关的规则都将被忽略。(可以忽略是否在严格模式下的影响)

    因为箭头函数可以捕获其所在上下文的this值 所以

    function Person() {  
        this.age = 0;  
        setInterval(() => {
            // 回调里面的 `this` 变量就指向了期望的那个对象了
            this.age++;
        }, 3000);
    }
    
    var p = new Person();

    以上代码可以得到我们所以希望的值,下图可以看到,在setTimeout中的this指向了构造函数新生成的对象,而普通函数指向了全局window对象

    var adder = {
      base : 1,
        
      add : function(a) {
        var f = v => v + this.base;
        return f(a);
      },
    
      addThruCall: function inFun(a) {
        var f = v => v + this.base;
        var b = {
          base : 2
        };
                
        return f.call(b, a);
      }
    };
    
    console.log(adder.add(1));         // 输出 2
    console.log(adder.addThruCall(1)); // 仍然输出 2(而不是3,其内部的this并没有因为call() 而改变,其this值仍然为函数inFun的this值,指向对象adder

    再看一个例子

    var name = 'my name is window';
    var obj = {
        name: 'my name is obj',
        fn: function () {
            var timer = null;
            clearInterval(timer);
            timer = setInterval(() => {
                console.log(this.name);  //my name is obj
            }, 1000)
        }
    }
    obj.fn();

    对于是否严格模式示例代码(可以复制进控制台进行验证)

    var f = () => {'use strict'; return this};
    var p = () => { return this};
    console.log(1,f() === window);
    console.log(2,f() === p());
    //1 true
    //2 true

    以上的箭头函数都是在方法内部,总之都是以非方法的方式使用,如果将箭头函数当做一个方法使用会怎样呢?

    上例子

    var obj = {
     this:this, i:
    10, b: () => console.log(this.i, this), c: function() { console.log( this.i, this) } } obj.b(); // undefined window{...} obj.c(); // 10 Object {...}

    可以看到,作为方法的箭头函数this指向全局window对象,而普通函数则指向调用它的对象

    二、改变this指向的方法

    第一种: new关键字改变this指向

    //构造函数版this
    function Fn(){
        this.user = "追梦子";
    }
    var a = new Fn();
    console.log(a.user); //追梦子

    用变量a创建了一个Fn的实例(相当于复制了一份Fn到对象a里面),此时仅仅只是创建,并没有执行,而调用这个函数Fn的是对象a,那么this指向的自然是对象a,那么为什么对象a中会有user,因为你已经复制了一份Fn函数到对象a中,用了new关键字就等同于复制了一份

    第二种: call()

    var a = {
        user:"追梦子",
        fn:function(){
            console.log(this.user); //追梦子
        }
    }
    var b = a.fn;
    b.call(a);  //若不用call,则b()执行后this指的是Window对象

    把b添加到第一个参数的环境中,简单来说,this就会指向那个对象。

    call方法除了第一个参数以外还可以添加多个参数,如下:

    var a = {
        user:"追梦子",
        fn:function(e,ee){
            console.log(this.user); //追梦子
            console.log(e+ee); //3
        }
    }
    var b = a.fn;
    b.call(a,1,2);

     

    第二种:apply()

    var a = {
        user:"追梦子",
        fn:function(){
            console.log(this.user); //追梦子
        }
    }
    var b = a.fn;
    b.apply(a);

    apply方法和call方法有些相似,它也可以改变this的指向,也可以有多个参数,但是不同的是,第二个参数必须是一个数组,如下:

    var a = {
        user:"追梦子",
        fn:function(e,ee){
            console.log(this.user); //追梦子
            console.log(e+ee); //11
        }
    }
    var b = a.fn;
    b.apply(a,[10,1]);

    注意如果call和apply的第一个参数写的是null,那么this指向的是window对象

    var a = {
        user:"追梦子",
        fn:function(){
            console.log(this); //Window {external: Object, chrome: Object, document: document, a: Object, speechSynthesis: SpeechSynthesis…}
        }
    }
    var b = a.fn;
    b.apply(null);

    第四种:bind()bind()为ES5的标准,低版本IE下有兼容问题,可以引入es5-shim.js解决;

    bind方法和call、apply方法有些不同,如下:

    var a = {
        user:"追梦子",
        fn:function(){
            console.log(this.user);
        }
    }
    var b = a.fn;
    b.bind(a);  //代码没有被打印

    我们发现代码没有被打印,对,这就是bind和call、apply方法的不同,实际上bind方法返回的是一个修改过后的函数。

    var a = {
        user:"追梦子",
        fn:function(){
            console.log(this.user);
        }
    }
    var b = a.fn;
    var c = b.bind(a);
    console.log(c); //function() { [native code] }

    函数c看看,能不能打印出对象a里面的user

    var a = {
        user:"追梦子",
        fn:function(){
            console.log(this.user); //追梦子
        }
    }
    var b = a.fn;
    var c = b.bind(a);
    c();

    同样bind也可以有多个参数,并且参数可以执行的时候再次添加,但是要注意的是,参数是按照形参的顺序进行的。

    var a = {
        user:"追梦子",
        fn:function(e,d,f){
            console.log(this.user); //追梦子
            console.log(e,d,f); //10 1 2
        }
    }
    var b = a.fn;
    var c = b.bind(a,10);
    c(1,2);

    第五种:在外部函数中将this存为一个变量,回调函数中使用该变量,而不是直接使用this。 (最常见方法)

    var name = 'my name is window';
    var obj = {
        name: 'my name is obj',
        fn: function () {
            var that = this;
            var timer = null;
            clearInterval(timer);
            timer = setInterval(function () {
                console.log(that.name);   //my name is obj
            }, 1000)
        }
    }

    在fn中加了var that = this; 回调函数中使用that代替this即可。这种方法最常见,使用也最广泛。

    总结:bind()的作用类似call和apply,都是修改this指向。, call和apply都是改变上下文中的this并立即执行这个函数,bind方法可以让对应的函数想什么时候调就什么时候调用,并且可以将参数在执行的时候添加,它会创建一个与原来函数主体相同的新函数,新函数中的this指向传入的对象。

    var name = 'my name is window';
    var obj = {
        name: 'my name is obj',
        fn: function () {
            var timer = null;
            clearInterval(timer);
            timer = setInterval(function () {
                console.log(this.name);   //my name is obj
            }.bind(this), 1000)
        }
    }

    在这里为什么不能用call和apply,是因为call和apply不是返回函数,而是立即执行函数,那么,就失去了定时器的作用。

    知识点补充:

    1.在严格版中的默认的this不再是window,而是undefined。

    2.new操作符会改变函数this的指向问题,虽然我们上面讲解过了,但是并没有深入的讨论这个问题,网上也很少说,所以在这里有必要说一下。

    function fn(){
        this.num = 1;
    }
    var a = new fn();
    console.log(a.num); //1

    为什么this会指向a?首先new关键字会创建一个空的对象,然后会自动调用一个函数apply方法,将this指向这个空对象,这样的话函数内部的this就会被这个空的对象替代。

    (注意: 当你new一个空对象的时候,js内部的实现并不一定是用的apply方法来改变this指向的,这里我只是打个比方而已.)

    if (this === 动态的可改变的) return true;

    相关文章推荐:

    1、偶遇this之坑

    2、JS中call、apply、bind使用指南,带部分原理。

    3、那些年我们一起过的JS闭包,作用域,this,让我们一起划上完美的句号。

    4、Nodejs中的this

  • 相关阅读:
    【2021-06-14】何太第一次喝断片
    【2021-06-13】管理的目标也就是把事情做好
    判断字符串的两半是否相等
    生成每种字符都是奇数个的字符串
    叶子相似的树
    面试准备
    北京旅行计划
    M笔试
    连续字符
    字符串中第一个唯一字符
  • 原文地址:https://www.cnblogs.com/web-record/p/10288556.html
Copyright © 2011-2022 走看看