最新公告
  • 欢迎您光临网站无忧模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 努力说清this的指向和怎么改变this的指向

    正文概述 掘金(颜酱)   2021-01-20   427

    JS 中的 this,总是神神叨叨的,不小心就错了。

    希望我自己写完本文之后,以后也按着现在捋顺的逻辑来分析 this。

    TL;DR

    • 箭头函数的this,和它书写的位置密切相关,在书写阶段(即声明位置)就绑定到它父作用域的 this
    • 构造函数的this,会绑定到我们 new 出来的这个对象上
    • 在不使用call/apply/bind改变this指向的时候,普通函数的this在调用的时候,绑定到调用方,和它的位置没有关系
      • 立即执行函数、setTimeout、setInterval 内部是普通函数的时候,因为其调用方是window,所以thiswindow
    • call/apply/bind 均可改变 this 指向,且均可被手写实现

    箭头函数里的this

    箭头函数的this,和它书写的位置密切相关,在书写阶段(即声明位置)就绑定到它父作用域的 this

    因为其由父作用域决定,所以父作用域至关重要。

    • 若箭头函数的父作用域是全局作用域,则 this 始终指向window
    • 若箭头函数的父作用域是函数作用域,则指向函数作用域的this
    var a = 1;
    var obj = {
      a: 2,
      func2: () => {
        // 父作用域是全局,this始终都是window
        console.log(this.a);
      },
      func3: function() {
        // 等同于func3作用域的this
        () => {
          console.log(this.a);
        };
      }
    };
    // func1
    var func1 = () => {
      //  父作用域是全局,this任何时候都是window
      console.log(this.a);
    };
    // func2
    var func2 = obj.func2;
    // func3
    var func3 = obj.func3;
    func1();
    func2();
    func3();
    obj.func2();
    obj.func3();
    

    构造函数里的 this

    构造函数里面的 this 会绑定到我们 new 出来的这个对象上:

    function Person(name) {
      this.name = name;
      console.log(this);
    }
    // this是person
    var person = new Person("yan");
    

    function 定义的普通函数

    function 定义的普通函数,this 的指向是在调用时决定的,而不是在书写时决定的。这点和闭包恰恰相反。

    换言之:不管方法被书写在哪个位置,它的 this 只会跟着它的调用方走, 再大白话点:xx.fn(),点前面是谁,fn 里的 this 就是谁。没有点就是 window。

    必须严格区分 “声明位置” 与 “调用位置”!!!

    上面的秘诀掌握好了,下面的例子就是 easy 了

    // 声明位置
    var me = {
      name: "yan",
      hello: function() {
        console.log(`你好,我是${this.name}`);
      }
    };
    var you = {
      name: "xiaoming",
      hello: function() {
        var targetFunc = me.hello;
        targetFunc();
      }
    };
    var name = "BigBear";
    // 调用位置
    you.hello();
    

    还有 2 种特殊点的情景: 没有调用方,所以 this 始终都是window

    • 立即执行函数(IIFE),(function(){})()
    • setTimeout/setInterval 中传入的普通函数,setTimeout(function(){...},1000)

    !!!注意,必须是 function 写的普通函数,如果箭头函数,仍遵循箭头函数的规则

    先看个立即执行函数的例子,

    var name = "BigBear";
    var obj = {
      name: "yan",
      fn: function() {
        (function() {
          console.log(this.name);
        })();
      }
    };
    obj.fn();
    

    仔细看看就是自执行函数执行,this 肯定是 window,自然就是BigBear

    注意!!!换成箭头函数的话,规则就变了,因为父作用域是 fn,其 this 绑定成了 obj,所以打印的话是yan

    var obj = {
      fn: function() {
        (() => {
          console.log(this);
        })();
      }
    };
    obj.fn();
    

    再看下 setTimeout

    var name = "BigBear";
    var me = {
      name: "yan",
      hello: function() {
        setTimeout(function() {
          console.log(`你好,我是${this.name}`);
        });
      }
    };
    me.hello();
    

    setTimeout 里面的函数,this 指向window,自然值是BigBear。 同理,如果将 setTimeout 里面的函数修改成箭头函数,则打印yan了。

    var name = "BigBear";
    var me = {
      name: "yan",
      hello: function() {
        setTimeout(() => {
          console.log(`你好,我是${this.name}`);
        });
      }
    };
    me.hello();
    

    严格模式下的 this

    笔者不用严格模式,真要用的时候百度下吧。

    改变 this 的指向

    this 的指向,要么被书写位置限制,要么被调用位置限制,很是被动。

    • 对于箭头函数,因为其this只和书写位置有关,所以一般不修改箭头函数的 this 指向。
    • 构建函数,this 就是 new 出来的实例,所以一般不修改构建函数的 this 指向
    • 于是重点!!!对于 function 定义的普通函数,修改 this,必须要显示的调用call/apply/bind

    一般问的修改 this,也是指 function 定义的普通函数。

    call/apply/bind三者用法及区别:

    • call/apply 改变函数的 this,且函数立即执行。但 apply 的参数是数组,call 的参数是非数组
    • bind 改变函数的 this,但函数未执行
    var init = 0;
    
    function add(num1, num2) {
      console.log(this.init + num1 + num2);
    }
    
    // 普通执行,肯定输出3
    add(1, 2);
    
    var obj = { init: 100 };
    
    // 此时因为this变成obj,所以输出103
    add.apply(obj, [1, 2]);
    // 此时因为this变成obj,所以输出103
    add.call(obj, 1, 2);
    // 此时因为this变成obj,但需要调用一次函数才行,bind本身返回的是函数
    add.bind(obj, 1)(2);
    

    手写实现 call/apply/bind

    其实细看下 call,发现 call 有以下特征:

    • call 是函数的方法,可以被函数直接调用
    • call 第一个参数是 this 绑定的对象,后面的参数是函数的参数
    • call 调用之后,函数执行,但 this 绑定到第一个参数上

    推理下 call 怎么实现:

    • 是函数的方法,每个函数都可调用,可写在 Function.prototype 上
    • 参数区别第一个参数,和后面的参数
    • call 内,this 绑定到第一个参数上,函数执行

    其实想想普通函数 this 的指向只和调用方有关
    => 于是 obj.fn()
    => 但是 fn 并不是 obj 的方法
    => 于是给 obj 增加 fn 这个方法不就行了
    => 但是 call 执行完,必须再将 fn 这个方法删掉就好了

    Function.prototype.myCall = function(context, ...args) {
      if (context === null) {
        return fn(...args);
      }
      // 注意 这里的this就是调用call的函数
      const fn = this;
      // 先增加
      context.fn = fn;
      // 执行
      return fn(...args);
      // 在删除
      delete context.fn;
    };
    
    // 测试下,没问题,输出103
    add.myCall(obj, 1, 2);
    

    同理:

    // 注意因为参数args就是数组,所以不要加...
    Function.prototype.myApply = function(context, args) {
      if (context === null) {
        return fn(...args);
      }
      // 注意 这里的this就是调用myApply的函数
      const fn = this;
      context.fn = fn;
      return fn(...args);
      delete context.fn;
    };
    // 同理输出103
    add.myApply(obj, [1, 2]);
    

    Bind 稍微复杂点:

    • bind 是函数的方法,可以被函数直接调用
    • bind 返回一个原函数的拷贝,但 this 被指定
    • bind 也可以传原函数的部分参数

    其实 bind 就是返回一个函数,函数内部执行 call~

    Function.prototype.myBind = function(context, ...frontArgs) {
      const fn = this;
      return function(...behindArgs) {
        return fn.call(context, ...frontArgs, ...behindArgs);
      };
    };
    // this被指定,但是需要调用一次才能执行函数,输出103
    add.myBind(obj, 1)(2);
    

    下载网 » 努力说清this的指向和怎么改变this的指向

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元