一文带你了解js中的this指向

89 阅读5分钟

        在JavaScript中,this指向一直是一个容易搞混的问题,往往在出错的时候还不知道各种原因,今天就来详细研究一下。

1.this

💡什么是this ?

        this是JavaScript的一个关键字,只能在对象内部使用。 在绝大多数情况下,函数的调用方式决定了this的值(即运行时绑定);而在ES6引入了箭头函数后,this的值将保持为闭合词法上下文的值。

2.标准函数中的this绑定

💡下面来介绍在4种this绑定规则,助你在函数调用时判断this指向。

2.1 默认绑定

function f1() {
  var a = 1;
  console.log(this.a);
}

function f2() {
  "use strict"
  console.log(this);
}

var a = 5;
f1();    // 5
f2();    // undefined

        this默认绑定可以理解为函数调用时无需任何调用前缀的情况,即直接使用而不带任何修饰的函数调用,如上述代码中的f1() 与 f2()

        通常情况下,默认绑定在非严格模式下一般是在window上,严格模式是为undefined

2.2 隐性绑定

function add() {
  console.log(this.a + this.b);
}

var obj = {a: 1, 
           b: 2,
           add: add
          };
var obj1 = {o: obj}

obj.add();        // 3
obj1.o.add();     // 3

        如果函数调用时,前面存在调用它的对象,那么this就会隐式绑定到这个对象上。在函数add()执行的时候有了上下文对象,即obj,this就绑定到obj上。同时,当函数调用前存在多个对象,即链性关系时,this指向距离自己最近的对象,如obj1.o.add()

    但如果按照上述所说,隐性绑定中规定上下文对象必须包含我们要调用的函数,可是如此一来扩展和维护性就太差了,为了解决这个问题,我们引入了显性绑定。

2.3 显性绑定

        在讲显性绑定前,我们首先去了解几个函数:call()、apply()和 bind()。它们的作用都是改变函数的this指向。但是,当我们在使用call()等方法改变this指向时,如果指向参数为null或者undefined,那么this将指向全局对象。

let obj1 = { x: 1 };
let obj2 = { x: 2 };
let obj3 = { x: 3 };
var x = 4;

function fn() {
  console.log(this.x)
}

fn.call(obj1);   // 1
fn.apply(obj2);  // 2
fn.bind(obj3)(); // 3
fn();            // 4

fn.call(undefined);    // 4
fn.apply(null);        // 4
fn.bind(undefined)();  // 4

        由上述代码可知,显性绑定中将隐性绑定中的上下文对象的函数去掉了。

番外 --- call、apply与bind有什么区别?

  1. calll、apply 与 bind 都用于this绑定,但 call、apply 函数在改变this指向的同时还会执行函数;而 bind 函数在改变this后返回一个全新的绑定函数。
  2. bind 属于硬绑定,返回的绑定函数的this指向不能再通过 bind、apply 或 call 修改,即this被永久绑定;call 与 apply 只适用于当前调用,一次调用后就结束。
  3. call 和 apply 功能完全相同,但call 从第二个参数后的所有参数都是原函数的参数;而 apply 只接受两个参数,第二个参数必须是数组,该数组包含着原函数的参数列表。

(下一篇会详细讲讲这三个有意思的函数)。

2.4 new绑定

        js中用new修饰的函数就是构造函数,准确点就是**函数的构造应用。**当我们new一个函数时,js会做以下工作:

  1. 创建新对象;
  2. 继承原函数的原型prototype;
  3. 将这个新对象绑定到该函数的this上;
  4. 如果构造器没有手动返回对象,则返回第一步创建的对象。
function foo() {
  this.a = 20;
  console.log(this);
}
foo();                  // window

var obj = new foo();    // foo{a: 20}
console.log(obj.a);     // 20

        通常来说,使用new调用函数后,函数会以自己的名字命名和创建一个新的对象并返回。但当原函数返回一个对象类型,我们将丢失绑this的新对象,原因在于无法返回新对象。

function foo() {
  this.a = 20;
  return {b: 30}
}

var obj = new foo();
console.log(obj.a);     // undefined
console.log(obj)        // {b: 30}

2.5 4种绑定优先级

        由于显示绑定与new绑定不能共存,所以绑定优先级如下:

                显式绑定 > 隐式绑定 > 默认绑定

                new 绑定 > 隐式绑定 > 默认绑定

3.箭头函数中的this

        箭头函数中没有this,箭头函数中的this指向取决于外层作用域的this,外层作用域中的this指向谁,箭头函数中的this便指向谁。一般来说,箭头函数在确定了this指向后无法被修改,即使将this传递给callbind、或者apply来调用箭头函数,它也被忽略。

var foo = (() => {
  console.log(this.a)
})
var a = 10;
var obj1 = { a: 20, foo: foo };
var obj2 = { a: 30 };

foo()               // 10, this指向window
obj1.foo();         // 10
foo.call(obj2);     // 10

        当然了,箭头函数的this与上级作用域的this指向一致,我们可以通过修改外层函数的this指向达到间接修改箭头函数this的目的。

function foo() {
  return () => {
    console.log(this.a);
  }
}
var obj1 = { a: 20 };
var obj2 = { a: 30 };

foo.call(obj1)(); // 20, this指向obj1
foo.call(obj2)(); // 30, this指向obj2

4.全文总结

通过阅读上述文字,对this的5种绑定场景全部介绍完毕。 通过本文,我们可以知道:

  1. 默认绑定在严格模式和非严格模式下的this会有所不同;
  2. 隐性绑定中this指向上下文对象;
  3. 显性绑定中this指向的是callbind、或者apply的第一个参数;
  4. new绑定中this指向的是新创建的对象;
  5. 箭头函数中this指向取决于外层函数的this指向。

谢谢大家的阅读,比心!