JavaScript 中的 this 是什么

  1. 关于 this 还是得先从执行上下文说起:
  2. 从图中可以看出,this 是和执行上下文绑定的,也就是说每个执行上下文中都有一个 this

this 的设计缺陷

嵌套函数中的 this 不会从外层函数中继承

  1. 结合下面代码分析:bar 函数中的 this 是什么?

    var myObj = {
      name : "张三", 
      showThis: function(){
        console.log(this)
        function bar(){console.log(this)} // 输出什么???
        bar()
      }
    }
    myObj.showThis()
    
  2. 函数 bar 中的 this 指向的是全局 window 对象,而函数 showThis 中的 this 指向的是 myObj 对象;解决这个问题有两种思路:

    1. 第一种是把 this 保存为一个 self 变量,再利用变量的作用域机制传递给嵌套函数;
    2. 第二种是继续使用 this,但是要把嵌套函数改为箭头函数,因为箭头函数没有自己的执行上下文,所以它会继承调用函数中的 this
  3. this 没有作用域的限制,这点和变量不一样,所以嵌套函数不会从调用它的函数中继承 this,这样会造成很多不符合直觉的代码;

普通函数中的 this 默认指向全局对象 window

  1. 在默认情况下调用一个函数,其执行上下文中的 this 是默认指向全局对象 window 的;

  2. 这个设计也是一种缺陷,因为在实际工作中,可能并不希望函数执行上下文中的 this 默认指向全局对象,因为这样会打破数据的边界,造成一些误操作;解决这个问题可以:

    1. 可以通过设置 JavaScript“严格模式” 来解决 (在严格模式下,默认执行一个函数,其函数的执行上下文中的 this 值是 undefined)
    2. 可以通过 call/apply/bind 手动指定 this

this 的五种情况

第一种:事件绑定

  1. 给元素的某个事件行为绑定方法,事件触发,方法执行,此时方法中的 this 一般都是当前元素本身;

  2. DOM0 级事件

    // DOM0
    btn.onclick = function anonymous() {
      console.log(this); // 元素
    };
    
  3. DOM2 级事件

    // => DOM2:不兼容 IE6 7 8
    btn.addEventListener('click', function anonymous() {
      console.log(this);  // 元素
    }, false);
    
    btn.attachEvent('onclick', function anonymous() {
      // IE8 浏览器中的 DOM2 事件绑定
      console.log(this); // window
    });
    

第二种:上下文对象调用

  1. 普通函数执行,它里面的 this 取决于方法执行前面是否有 “点”,有 “点” 前面是谁 this 就是谁,没有则指向 window (严格模式下是 undefinde

  2. 示例代码:

    function fn() {
        console.log(this);
    }
    
    let obj = { name: 'OBJ', fn: fn };
    
    fn(); // window
    obj.fn(); // { name: 'OBJ', fn: [Function: fn] }
    

第三种:构造函数执行

  1. 构造函数执行 new xxx,函数中的 this 是当前类的实例

  2. 示例代码:

    function Fn() {
      console.log(this);
      // this.xxx = xxx 是给当前实例设置私有属性
    }
    let f = new Fn;
    

第四种:箭头函数

  1. 箭头函数中没有自身的 this ,箭头函数的 this 指向始终为外层的作用域;

  2. 箭头函数没有的东西很多:

    1. 没有 prototype (也就是没有构造器),所以不能被 new 执行;
    2. 没有 arguments 实参集合 (可以基于 …args 剩余运算符获取)
  3. 示例代码:

    JavaScript
    JavaScript
    JavaScript
    let obj = {
      name: 'OBJ',
      fn: function () {
        // console.log(this); //=>obj
        let _this = this;
        return function () {
          // console.log(this); //=>window
          _this.name = "旺旺";
        };
      }
    };
    
    let ff = obj.fn();
    ff();
    console.log(obj.name); // 旺旺
    
    let obj = {
      name: 'OBJ',
      fn: function () {
        // console.log(this); //=>obj
        return () => {
          // console.log(this); //=>obj
          this.name = "旺旺";
        };
      }
    };
    
    let ff = obj.fn();
    ff();
    console.log(obj.name); // 旺旺
    
    let obj = {
      name: 'OBJ',
      fn: function () {
        setTimeout(_ => {
          // console.log(this); //=>obj
          this.name = "旺旺";
        }, 10);
      }
    };
    obj.fn();
    console.log(obj.name); // OBJ
    

第五种:API 改变 this 指向

  1. 基于 call/apply/bind 可以改变函数中 this 的指向(强行改变),callapplybindFunction.prototype 上的方法;

  2. func.call(context, 10, 20)context 为改变的 this 指向(非严格模式下,传递 null/undefined 指向的也是 window);

  3. 示例代码:

    JavaScript
    JavaScript
    JavaScript
    // call
    Function.prototype.call = function call(context = window, ...args) {
        context === null ? context = window : null;
    
        //=> 此处 this 为 apply 前的函数,也就是调用 apply 的函数
        //=> 把函数赋值给需要指定 this 的对象 context
        context.$fn = this;
        let result = context.$fn(...args); // 这时函数执行 this 就变成了 context
    
        delete context.$fn;
        return result;
    }
    
    // apply
    Function.prototype.apply = function apply(context = window, args) {
        context === null ? context = window : null;
    
        //=> 此处 this 为 apply 前的函数,也就是调用 apply 的函数
        //=> 把函数赋值给需要指定 this 的对象 context
        context.$fn = this;
        let result = context.$fn(...args); // 这时函数执行 this 就变成了 context
    
        delete context.$fn;
        return result;
    }
    
    // bind
    // es5 版本
    Function.prototype.bind = function bind(context) {
      context = context || window;
      // 获取传递的实参集合
      var args = [].slice.call(arguments, 1);
      var _this = this;
    
      // 需要最终执行的函数
      return function anonymous() {
        // 此函数的参数
        var amArg = [].slice.call(arguments, 0);
        _this.apply(context, args.concat(amArg));
      };
    }
    
    // es6 版本
    Function.prototype.bind = function bind(context = window, ...args) {
      // 经过测试:apply 的性能不如 call(3 个参数以上)
      return (...amArg) => this.call(context, ...args.concat(...amArg));
    }
    

面试题

阿里原题

function fn1() {
  console.log(1);
}
function fn2() {
  console.log(2);
}

// fn2.$fn = fn1;
// fn2.$fn() => fn2.fn1();this 指向 fn2,执行 fn1,输出 1
fn1.call(fn2); // 1

// 把 fn1.call 看成 AF0,AF0.call(fn2)
// fn2.$fn() => fn2.AF0(); fn1.call 和 fn1.call.call 都是同一个 call 函数
// 则 AF0 可以看成 call 函数,即 fn2.call 执行,this 为 window,输出 2
fn1.call.call(fn2); // 2

// 同上
Function.prototype.call.call(fn2); // 2
打赏作者
您的打赏是我前进的动力
微信
支付宝
评论

中午好👏🏻,我是 ✍🏻   疯狂 codding 中...

粽子

这有关于前端开发的技术文档和你分享。

相信你可以在这里找到对你有用的知识和教程。

了解更多

目录

  1. 1. JavaScript 中的 this 是什么
  2. 2. this 的设计缺陷
    1. 2.1. 嵌套函数中的 this 不会从外层函数中继承
    2. 2.2. 普通函数中的 this 默认指向全局对象 window
  3. 3. this 的五种情况
    1. 3.1. 第一种:事件绑定
    2. 3.2. 第二种:上下文对象调用
    3. 3.3. 第三种:构造函数执行
    4. 3.4. 第四种:箭头函数
    5. 3.5. 第五种:API 改变 this 指向
  4. 4. 面试题
    1. 4.1. 阿里原题