this的指向以及call、apply、bind

本文深入探讨了JavaScript中this关键字的指向规则,包括ES5中this的默认行为、箭头函数中this的特殊绑定,以及如何通过call、apply、bind等方法改变this的指向。通过多个示例代码,详细解释了不同场景下this的具体指向。
摘要由CSDN通过智能技术生成

前言:最近在刷面筋,发现笔者自己对 this的指向的概念有所模糊,因此特地花了些时间并记录如下,如有错误,请各位大牛指正~ (●'◡'●) 话不多说,正式进入我们的正题:

1.this指向问题,在ES5this永远指向最后调用它的那个对象

var name = 'windowName';
function fn() {
   var name = 'private';
   console.log(this.name); // windowName
  console.log('此时的this', this); // 指向全局的Window
}
fn(); // 这里相当于window.fn()

ES5 中 “this 永远指向最后调用它的那个对象”,我们看最后调用 fn 的地方 fn();,前面没有调用的对象那么就是全局对象 window,这就相当于是 window.fn();注意,这里我们没有使用严格模式,如果使用严格模式的话,全局对象就是 undefined,那么就会报错 Uncaught TypeError: Cannot read property 'name' of undefined

接下来,我们来看看几个变形,仔细对比,相信大家就能发现“this 永远指向最后调用它的那个对象”的理解:

// 变形1
var name = 'windowName';
var foo =  {
  name: 'privateName',
  fn: function() {
  console.log(this.name); // privateName
  console.log('此时的this', this); // 指向调用该方法的foo {name: "privateName", fn: ƒ}
  }
}
foo.fn(); // 这里相当于window.foo.fn(), 因为最后调用fn方法的还是foo,因为输出还是 ‘privateName’

// 变形2
var name = 'windowName';
var foo =  {
//   name: 'privateName',
  fn: function() {
     console.log(this.name); // 此时在foo内并没有声明 name 因此输出为 undefined
     console.log('此时的this', this); // 指向调用该方法的foo {fn: ƒ}
  }
}
window.foo.fn(); // undefined


// 变形 3
var name = 'windowName';
var foo = {
  name: null,
  fn() {
  console.log(this.name); // windowName
   console.log('此时的this', this); // 指向Window 
 }
}
var bar =  foo.fn; // 在这里实际上发生了隐式转换,调用bar() 相当于window.bar() 因此this指向的是全局对象window
bar();


// 变形 4
var name = 'windowName';
function fn() {
  var name = 'privateName';
  innerFn();
  function innerFn() {
   console.log('此时的this', this); // 指向window
   console.log(this.name); // windowName
 }
}
fn(); // window.fn()

 

2. 如何改变this指向

  • 使用ES6的箭头函数

var name = 'windowName';
var a = {
  name: 'privateName',
  fun1: function () {
    console.log(this.name);
  },
  fun2: function () {
      setTimeout(function() { // 这里不是箭头函数,调用setTimeout函数的对象是window,此时window下并没有fun1
            this.fun1();
       }, 100)
    }
 }
a.fun2(); // this.fun1() is not a function 在window下找不到fun1()

​
// 变形1 --验证setTimeout中传入的是普通函数时,此时的this指向window
var name = 'windowName';
function fun1 (a, b) {
  // console.log(this.name); // windowName fun1() 在setTimeout中被调用,setTimeout的调用对象指向window
  return a + b;
}
var a = {
  name: 'privateName',
  fun1: function (a, b) {
    // console.log(this.name);
    return a - b;
  },
  fun2: function () {
      setTimeout(function() {
            console.log(this.fun1(4, 2)); // 这里的this.fun1() 实际上是全局的fun1()
       }, 100)
    }
 }
a.fun2(); // 6, 由此可以证实setTimeout中的this指向window

​
// 变形2 -- 借助箭头函数,箭头函数的 this 始终指向函数定义时的 this, 而非执行时
var name = 'windowName';
function fun1 (a, b) {
  return a + b;
}
var a = {
  name: 'privateName',
  fun1: function (a, b) {
    return a - b;
  },
  fun2: function () {
  setTimeout(() => {
     console.log('此时的this', this); // 此时的this指向a {name: "privateName", fun1: ƒ, fun2: ƒ}
     console.log(this.fun1(4, 2));
   }, 100)
 }
}
a.fun2(); // 2
​

注意:使用箭头函数需要记住:“箭头函数中没有 this 绑定,必须通过查找作用域链来决定其值,如果箭头函数被非箭头函数包含,则 this 绑定的是最近一层非箭头函数的 this,否则,this 为 undefined”。

  • 在函数内部使用 _this = thisfun2函数内部调用setTimeout前,将当前的this赋给变量 _this _this.fun1() 调用时的this就能被绑定到当前的对象fn

    var name = 'windowName';
    var fn = {
        name: 'privateName',
        fun1() {
            console.log('此时的this', this);
            console.log(this.name);
        },
        fun2() {
            var _this = this; // 此时的this指向对象fn
            setTimeout(function() {
                _this.fun1();
            }, 100);
        }
    }
    fn.fun2(); // privateName

     

  • 通过 callapplybind (显式绑定)

    mdn上对三者的释义:

    1. call

      • 语法: function.call(this.Arg,arg1, arg2..)

      • 参数: thisArg:可选参数,在 function 函数运行时使用的 this 值。请注意,this可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 nullundefined 时会自动替换为指向全局对象,原始值会被包装; arg1,arg2,...: 指定的参数列表

      • 返回值: 使用调用者提供的 this 值和参数调用该函数的返回值。若该方法没有返回值,则返回 undefined

        var name = 'windowName';
        var fn = {
            name: 'privateName',
            fun1() {
                console.log('此时的this', this); // {name: "privateName", fun1: ƒ, fun2: ƒ}
                console.log(this.name);
            },
            fun2() {
                setTimeout(function() {
                    this.fun1();
                }.call(fn), 100); // function.call(thiArg, arg1, arg2,..) 这里传入的thisArg为fn
            }
        }
        fn.fun2(); // privateName

         

    2. apply

      • 语法:function.apply(thisArg, [argArray])

      • 参数: thisArg:必选参数。在 func 函数运行时使用的 this 值。请注意,this可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 nullundefined 时会自动替换为指向全局对象,原始值会被包装;argArray: 可选参数,数组或类数组对象

      • 返回值: 调用有指定**this**值和参数的函数的结果。

        var name = 'windowName';
        var fn = {
            name: 'privateName',
            fun1() {
                console.log('此时的this', this); // {name: "privateName", fun1: ƒ, fun2: ƒ}
                console.log(this.name)
            },
            fun2() {
                setTimeout(function() {
                    this.fun1();
                }.apply(fn), 100); // function.apply(thisArg, [argArray])  这里传入的thisArg为fn
            }
        }
        fn.fun2(); // privateName

         

    3. callapply的区别: applycall() 非常相似,不同之处在于提供参数的方式。apply 使用参数数组而不是一组参数列表。apply 可以使用数组字面量(array literal),如 fun.apply(this, ['eat', 'bananas']),或数组对象, 如 fun.apply(this, new Array('eat', 'bananas'))

    4. bind

      • 语法: function.bind(thisArg[, arg1[, arg2[, ...]]])

      • 参数: thisArg 调用绑定函数时作为 this 参数传递给目标函数的值。 如果使用new运算符构造绑定函数,则忽略该值。当使用 bindsetTimeout 中创建一个函数(作为回调提供)时,作为 thisArg 传递的任何原始值都将转换为 object。如果 bind 函数的参数列表为空,或者thisArgnullundefined,执行作用域的 this 将被视为新函数的 thisArgarg1, arg2...: 当目标函数被调用时,被预置绑定函数的参数列表

      • 返回值: 返回一个原函数的拷贝,并拥有指定的 this 值和初始参数

      var name = 'windowName';
      var fn = {
          name: 'privateName',
          fun1() {
              console.log('此时的this', this); // {name: "privateName", fun1: ƒ, fun2: ƒ}
              console.log(this.name);
          },
          fun2() {
              setTimeout(function() {
                  this.fun1();
              }.bind(fn)(), 100); // function.bind(thisArg)() 注意,bind返回的是一个函数,调用时机可由开发者决定
          }
      }
      fn.fun2(); // privateName

       

  • new 实例化对象, 在构造函数中,this始终指向实例化对象

    // 构造函数
    function Person(name, age) {
        this.name = name;
        this.age = age;
    }
    let person = new Person('privateName', 20);
    console.log(person.name); // privateName

    由此可延伸出另一个问题new的过程到底发生了些什么??

    // 构造函数
    function Person(name, age) {
        this.name = name;
        this.age = age;
    }
    let person = new Person('privateName', 20);
    ​
    // 伪代码模拟
    new Person({
        var obj = {}; // 创建一空对象
        obj.__proto__ = Person.prototype; // 空对象的隐式原型指向其构造函数的显式原型
        var result = Person.call(obj, 'privateName', 20); // 利用call实现this指向空对象
        return typeof result === 'obj' ? result : obj; // 返回值
    })
    1. 创建一个空对象 obj;

    2. 将新创建的空对象的隐式原型指向其构造函数的显示原型。

    3. 使用 call 改变 this 的指向,这里也即是this始终指向实例化对象的原因

    4. 如果无返回值或者返回一个非对象值,则将 obj 返回作为新对象;如果返回值是一个新对象的话那么直接直接返回该对象

(✿◡‿◡)目前记录就到这里,后期有学到新的再补充,欢迎各路大神指正,有错误还请轻喷(满满的求生欲 ( •̀ ω •́ )y)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值