THIS的指向及改变(apply,call,bind)

492 阅读6分钟

this的指向

4种情况:

  1. 对象内方法中的this;
  2. 普通函数中的this;
  3. 类中的this,分为构造函数内和构造函数外;
  4. 箭头函数中的this;

作为对象的方法被调用

当函数作为对象的方法被调用时,this指向该对象

        let obj = {
            name: "东北F4",
            movie: function () {
                console.log(this.name);  // 东北F4
                console.log(this === obj); // true
            }
        }
        obj.movie();
        console.log(this);  // window

注:对象外部的this指向全局对象window,也属此种情况。

普通函数中的this

当普通函数方法被调用时,this指向全局对象window

            let obj = {
                name: "东北F4",
                movie: function () {
                    console.log(this.name);  // 东北F4
                    console.log(this === obj); // true
                }
            }
            window.name = '赵家班';

            function fun1(){
                console.log(this.name); //赵家班
                console.log(this.obj);  //undefined
            }
            fun1()

注:用let和const定义的全局变量并不属于对象window,只存在与块级作用域中。ES6规定用var和function定义的全局变量仍旧会存在对象window中。

类中的this,分为构造函数内和构造函数外

构造函数中的this

当new运算符调用函数时,总是返回一个对象,this通常也指向这个对象

ES6前

作为构造函数this指向和作为普通函数不同

        var MyClass = function () {
            this.name = '小沈阳';
            console.log(this);
        }
        MyClass(); // window
        var obj = new MyClass(); // obj =  MyClass {name: '小沈阳'}
        console.log(obj.name); //小沈阳

如果显式的返回了一个object对象,那么此次运算结果最终会返回这个对象。

var MyClass = function () {
    this.age = 1;
    return {
        name: '小沈阳'
    }
}
var myClass = new MyClass(); 
console.log('myClass:', myClass); // myClass: {name: '小沈阳'}
console.log('age:', myClass.age); // age: undefined
console.log('name:', myClass.name); // name: 小沈阳

构造函数不显示的返回任何数据,或者返回非对象类型的数据,就不会造成上述问题。

ES6后

构造函数中的this指向new出来的实例

        class Person{
            constructor(){
                 this.name = '东北F4'; 
            }
        }
        var F4 = new Person();
        console.log(F4); // Person {name: '东北F4'}
        console.log(F4.name); //东北F4

构造函数外的this指向调用者

        class Person{
            constructor(){
                 this.name = '东北F4';  
            }
            fun1 = function () {
              console.log("this:",this);
            }
        }
        let F4 = new Person();
        F4.fun1(); // Person {name: '东北F4', fun1: ƒ}

构造函数外的this调用时,向下面的调用会导致this为undefined,原因之后详说。 当定义时的方法为箭头函数时,this的指向不会变,原因在下面。

 let changThis = F4.fun1;
 changThis(); // undefined

箭头函数

箭头函数不会创建自己的this,它只会从自己的作用域链的上一层继承this。

因此,在下面的代码中,传递给getVal函数内的this并不是调用者自身,而是外部的this~

this.val = 2;
var obj = {
    val: 1,
    getVal: () => {
        console.log(this.val);
    }
}
obj.getVal(); // 2

改变this的指向

在JavaScript 函数方法中,call、apply 和 bind 是 Function 对象自带的三个方法,这三个方法的主要作用是绑定且改便this 的指向,以适应需求。

call的使用

主要功能:允许你在一个对象上调用该对象没有定义的方法(另一个指向的对象上的),并且这个方法可以访问该对象中的属性。

基本使用:

      var  group = {
            name: "东北F4",
            fun: function(){
                console.log(this);
            }
      } 
      var newGroup = { name: "江南四大才子" }; 
      var groupShow = function(name, No){ 
          console.log("我们的组合是:", this.name); //"东北F4"
          console.log("我们的团队是:", name); // "赵家班"
          console.log("我们的团队是:", No); // "小沈阳"
        };

      // groupShow为要调用的方法
      // group为this绑定的指向
      // "赵家班" 为向调用方法中穿的参数,传多个参数依次向后加
      groupShow.call(group, "赵家班", "小沈阳")
复制代码

指向:

      var  group = {
            name: "东北F4",
            fun: function(){
                console.log(this);
            }
      } 
      var newGroup = { name: "江南四大才子" }; 
      var groupShow = function(){ };

      group.fun(); //group对象
      group.fun.call(); // window对象
      group.fun.call(newGroup); // newGroup对象
      group.fun.call(groupShow); // groupShow方法
复制代码

注:如果输入基本类型或实例,那么this会指向其创造的对象或方法。null和underfind除外,其指向window。

apply

apply 和 call的基本功能大致相同,使用方法也差不多,唯一的不同是传参。call是多个参数时列表依次传递,apply则以数组的形式传递。

function b(x,y,z){
    console.log(x,y,z);
}

b.apply(null,[1,2,3]); // 1 2 3
复制代码

bind

bind是区别与call和apply的,这不是从功能或者使用上来说的,而是从实现上来说的。

基本使用:传参的方式和call相同

      var  group = {
            name: "东北F4",
            fun: function(){
                console.log(this);
            }
      } 
      var newGroup = { name: "江南四大才子" }; 
      let a = group.fun.bind(newGroup); // 不会执行,只会绑定
      let b = group.fun.call(newGroup);
      let c = group.fun.apply(newGroup) 
      a() // {name: '江南四大才子'}
      b() // TypeError: b is not a function
      c() // TypeError: b is not a function
复制代码

模拟实现bind

Function.prototype.bind = function (obj) {
    var _this = this; // 保存调用bind的函数
    var obj = obj || window; // 确定被指向的this,如果obj为空,执行作用域的this就需要顶上喽
    return function(){
        return _this.apply(obj, arguments); // 修正this的指向
    }
};

var obj = {
    name: "孙悟空",
    getName: function(){
        console.log(this.name)
    }
};

var func = function(){
    console.log(this.name);
}.bind(obj);

func(); // 孙悟空

bind 和 apply、call的区别

bind: 不会立刻执行,this的改变是长久的,属于copy了一个方法,然后把this替换成想要指向的目标。只要用bind绑定了,用apply和call也不会改变this的指向。 当对一个函数调用多次bind的时候,最终起作用的是第一个bind。

apply 和 call: 立刻执行,this的改变不是长久的,只是通过这两个方法调用时this才指向目标,否则this指向不变。

ES5引入 bind 的真正目的是为了弥补 call/apply 的不足,由于 call/apply 会对目标函数自动执行,从而导致它无法在事件绑定函数中使用,因为事件绑定函数不需要我们手动执行,它是在事件被触发时由JS 内部自动执行的。而 bind 在实现改变函数 this 的同时又不会自动执行目标函数,因此可以完美的解决上述问题。

陷阱

this 永远指向最后调用它的那个对象

举个例子,当我们希望自己封装Dom方法,来精简代码时:

 var getDomById = document.getElementById
     getDomById('div1') // Uncaught TypeError: Illegal invocation(非法调用)
复制代码

我们期望的结果是能正常获取到DOM,但是结果却是非法调用。

这是因为当 document.getElementById 赋值给 getDomById时,其中的this已经不是document了,而是全局对象window。这就是this 永远指向最后调用它的那个对象。

注:document是window的子对象

修正:

document.getElementById = (function (func) {
    return function(){
        return func.call(document, ...arguments)
    }
})(document.getElementById) 
// 利用立即执行函数将document保存在作用域中。

复制代码

类:构造函数类的this可能会是underfind

书接上回:changThis()的执行结果为undefined

       class Person{
            constructor(){
                 this.name = '东北F4';  
            }
            fun1 =  function(){
              console.log("this:",this);
            }
        }
        let F4 = new Person();
        let changThis = F4.fun1;
        changThis(); // undefined
复制代码

原因是由陷阱1导致F4.fun1赋值给changThis时this的执行改变成了window。但是根据 JavaScript 的语法规则,所有在类中定义的方法都默认开启局部严格模式。在严格模式下,所有指向 window 对象的 this,都全部变更为 undefined

总结

陷阱1的这个例子是从网上找到的,但我认为再经典不过了。其实我类似这样的陷阱还有很多,如果之后遇到,我也会将其更新到其中。

最后,希望大家学有所成!