JavaScript-this指向及绑定规则

276 阅读5分钟

这是我参与新手入门的第1篇文章


this是JavaScript中的关键字,与传统面向对象的语言相比,JavaScript中的this更加灵活,涉及到它出现的位置和代表的含义。在讲述之前,我们先来看一下不使用this的情况。

this的基本使用

  • 现在有一个需求,需要在对象的方法中输出一些信息,如下

    var obj = {
      name: "zhangsan",
      run: function () {
        console.log(obj.name + " run");
      },
      study: function () {
        console.log(obj.name + " study");
      },
    };
    

    上述代码的缺点:如果将obj名称改成info,那么所有方法中的obj.name都需要换成info.name

  • 在实际开发中,我们会通过this关键字进行优化,如下

    var obj = {
      name: "zhangsan",
      run: function () {
        console.log(this.name + " run");
      },
      study: function () {
        console.log(this.name + " study");
      },
    };
    

    经过上述修改后,方法中通过this引用,解耦了对变量名的依赖。修改对象变量名时,无需修改方法中引用的名称。

this的指向

在全局作用域下,this指向什么?

console.log(this); // window

var name = "zhangsan";

console.log(this.name); // zhangsan

console.log(window.name); // zhangsan

console.log(this === window); // true
  • 在浏览器环境中,全局作用域下,this指向window

在实际开发中,很少会直接在全局作用域下使用this,通常都是在函数中使用。函数在被调用时,都会创建一个执行上下文,用于记录

  • 函数的调用栈、调用方式、传入参数等信息
  • this也是其中的一个属性

定义一个函数、通过3种方式进行调用,产生了3种不同结果

// 定义一个函数
function foo() {
  console.log(this);
}

foo(); // window对象

var obj = {
  name: "zhangsan",
  foo: foo,
};

obj.foo(); // {name: "zhangsan", foo: ƒ}

foo.call("lisi"); // String {"lisi"}
  1. 函数在调用时,JavaScript会默认给this绑定一个值
  2. this的绑定和函数定义的位置无关
  3. this的绑定和函数调用方式及调用位置有关
  4. this是在运行时绑定的

那么,this的绑定规则是怎样的呢?

this绑定规则

1、默认绑定

独立的函数调用,可以理解成函数没有被绑定到某个对象上的调用

案例1:普通函数调用
  1. 函数直接调用,没有与任何对象关联

  2. 这种独立的函数调用就会使用默认绑定,通常情况下,函数中的this指向全局对象(window)

    function foo() {
      console.log(this); // window
    }
    
    foo();
    
案例2:函数调用链
  1. 所有函数调用都没有绑定到对象上

    function foo() {
      console.log(this); // window
      bar();
    }
    
    function bar() {
      console.log(this); // window
      baz();
    }
    
    function baz() {
      console.log(this); // window
    }
    
    foo();
    
案例3:将函数作为另一个函数的参数进行调用
  1. 将函数作为参数,传入另一个函数中,在另一函数中直接调用该函数

    function foo(fn) {
      fn();
    }
    
    function bar() {
      console.log(this); // window
    }
    
    foo(bar);
    
案例4:将对象中的一个方法传入另一个函数中进行调用
  1. 将对象中的方法传入到另一个函数中,另一个函数直接调用

    function foo(fn) {
      fn();
    }
    
    var obj = {
      name: "zhangsan",
      bar: function () {
        console.log(this); // window
      },
    };
    
    foo(obj.bar);
    
    • 上例中的this之所以是window,是因为在真正调用函数的位置,并没有与对象进行关联,是一个独立的函数调用

2、隐式绑定

比较常见的调用方式,通过某个对象进行调用。也就是它的调用位置中,是通过某个对象发起的函数调用

案例1:通过对象调用函数
  1. foo的调用位置是obj.foo()方式进行调用的
  2. foo调用时的this就会绑定到obj对象上
function foo() {
  console.log(this); // obj
  console.log(this === obj); // true
}

var obj = {
  name: "zhangsan",
  foo: foo,
};

obj.foo();
案例2:多层对象调用函数
  1. 通过obj2引用了obj1,再通过obj1对象调用foo函数
  2. foo调用时的this还是绑定了obj1对象上
function foo() {
  console.log(this); // obj1
  console.log(this === obj1); // true
}

var obj1 = {
  name: "zhangsan",
  foo: foo,
};

var obj2 = {
  name: "zhangsan",
  obj1: obj1,
};

obj2.obj1.foo();
案例3:隐式丢失
  1. foo被调用的位置是bar,而bar在调用时没有绑定任何对象,是独立函数调用。
function foo() {
  console.log(this); // window
  console.log(this === window); // true
}

var obj = {
  name: "zhangsan",
  foo: foo,
};

var bar = obj.foo;

bar();

隐式绑定的前提条件是

  • 必须在调用的对象内部有一个对函数的引用
  • 如果没有这样的引用,在进行调用时,会报错找不到该函数
  • 正是通过这个引用,间接将this绑定到这个对象上。

如果不希望对象内部包含这个属性,同时又希望在这个对象上强制调用,可以通过call()、apply()进行显式绑定

3、显式绑定

JavaScript所有函数都可以使用call和apply方法

  • 二者的区别在于第2个参数不同。call是参数列表、apply是数组.
  • 二者的第1个参数,是在函数调用时绑定到的this对象
案例1:通过call、apply绑定this
  1. 显式绑定后,this就会明确的指向绑定的对象
function foo() {
  console.log(this);
}

var obj = {
  name: "zhangsan",
};

foo.call(window); // window
foo.call(obj); // obj {name: "zhangsan"}
foo.call("lisi"); // String {"lisi"}
案例2:通过bind函数
  1. 通过bind绑定this后,调用bind方法返回的函数,该函数的this始终是传入bind函数的参数
function foo() {
  console.log(this);
}

var obj = {
  name: "zhangsan",
};

var bar = foo.bind(obj);

bar(); // obj {name: "zhangsan"}
bar(); // obj {name: "zhangsan"}

4、new绑定

JavaScript中的函数,可以通过new关键字当作一个类的构造函数来使用。通过new关键字调用函数时,会执行如下操作:

  1. 创建一个新对象
  2. 这个新对象会执行prototype连接
  3. 新对象会绑定到函数调用的this上
  4. 如果函数没有返回其他对象,则会返回这个新对象
function Person(name) {
  console.log(this); // Person {}
  this.name = name;
}

var p = new Person("zhangsan");

console.log(p); // Person {name: "zhangsan"}