(深入JavaScript 三)细说JavaScript中的this

207 阅读4分钟

this的指向

通常在浏览器中,在全局中使用this,该this指向的是window对象,无论是否开启了严格模式。但我们一般使用this,也是在函数中进行使用。所有的函数在被调用的时候,会产生一个函数执行上下文,该上下文中记录这函数的作用域链、AO对象以及当前这个函数的this指向。我们看下面代码:

function foo() {
  console.log(this)
}

foo()//window

var obj = {
  name: 'why',
  foo: foo
}

obj.foo()//obj

foo.apply("abc")//String{'abc'}

上面代码中,我们定义了一个函数foo来输出调用它的时候this的指向,从上往下分别指向的是window、obj、String{'abc'},所以我们可以知道:

  1. 函数在调用时,JavaScript会默认给this绑定一个值;
  2. this的绑定和定义的位置(编写的位置)没有关系;
  3. this的绑定和调用方式以及调用的位置有关系;
  4. this是在运行时被动态绑定的,而不是编译时固定下来的

判断this绑定的4条规则

规则1:默认绑定

独立函数调用的时候,this永远绑定的是window对象,如下示例:

function foo() {
   console.log(this)
}
function foo2() {
   foo()
}
var obj = {
  name: "xiaomin",
  foo: function() {
    console.log(this)
  }
}

foo()//window
foo2()//window
var bar = obj.foo
bar()//window

规则2:隐式绑定

通过某个对象发起调用的函数,哪个对象发起调用的函数,那么该调用函数的this就隐式绑定在了这个对象:

var obj1 = {
  name: "obj1",
  foo: function () {
    console.log(this);
  },
};

var obj2 = {
  name: "obj2",
  bar: obj1.foo,
  obj1: obj1,
};

obj1.foo();//obj1
obj2.obj1.foo();//obj1
obj2.bar();//obj2

规则3:显示绑定

通过函数的方法call、apply、bind将this显示绑定了某个对象上,那么该函数的this就指向到这个对象上:

function foo() {
       console.log(this)
}
var obj = {
   name: "obj"
}
foo.call(obj)
foo()//obj
foo.apply(obj)
foo()//obj
var newfoo = foo.bind(obj)
newfoo()//obj

规则4:new绑定

我们有如下代码:

function Person(name, age) {
  this.name = name
  this.age = age
  console.log(this)//p
}

var p = new Person("kobe",12);

在这里我们有一个构造函数Person,并用它创建了一个对象,这里简单说明一下通过new调用构造函数创建对象时的5个步骤:

  1. 在内存中创建一个空对象
  2. 将该对象的[[prototype]]指向构造函数的prototype属性
  3. 构造函数里的this指向这个空对象
  4. 开始执行构造函数的函数体代码
  5. 如果构造函数未返回内容,将该对象作为返回值返回出去

从上面我们可以看到,在使用new创建对象的时候,在第三个步骤会将构造函数内部的this绑定到创建的这个对象上,也就是上面代码中,Person函数中打印的this就是会是通过new关键字创建的对象p。

this绑定的优先级

当同时出现上列规则的多种规则绑定的时候,这时候this的指向就会根据上面绑定规则的优先级进行绑定:

  1. 默认绑定优先级最低
  2. 显示绑定优先于隐式绑定,如下列代码打印的this就不是obj而是String {'aaa'}
function foo() {
  console.log(this);//String {'aaa'}
}

var obj = {
  name: "obj",
  foo: foo.bind("aaa"),
};

obj.foo();

  1. new绑定优先于隐式绑定,如下列代码打印出来的就不是obj,而是创建出来的对象f
var obj = {
  name: "obj",
  foo: function () {
    this.name = "kkk";
    console.log("this:", this);//foo {name: 'kkk'}
  },
};
var f = new obj.foo();

4. new绑定优先bind绑定(new绑定不能同时与call、apply同时使用),如下代码this打印出来的就是obj的对象而不是String{'aaa'}

function foo() {
  console.log(this);
}

var bar = foo.bind("aaa");

var obj = new bar();
//var obj2 = new foo.apply("123");//这样写会报错

除了四个this绑定规则外的特殊情况

1、在显示绑定中我们绑定了null或者undefined,this会绑定到window上

function foo() {
  console.log(this)
}
foo.call({})//{}
foo.call(null)//window
foo.call(undefined)//window

2、函数的间距引用,在下列代码中,赋值表达式 p.foo = o.foo 的返回值是目标函数的引用,因此调用位置是 foo(),不是o.foo()。所以打印出来的this是window而不是o或者p

function foo() {
  console.log(this);
}
var o = { foo: foo };
var p = {};
o.foo(); // o
(p.foo = o.foo)(); // window

3、箭头函数。箭头函数是不会有自己的this的,在调用箭头函数的时候,在它的函数执行上下文中并不会生成它this的记录,而如果在箭头函数的函数体中使用到了this,那么它将会在它的上层作用域里面去找this,看下面代码

var obj = {
  data: [],
  changeData: function () {
    console.log("普通函数this:", this);
    (() => {
      console.log("箭头函数this:", this);
    })();
    setTimeout(() => {
      this.data = ["1", "2", "3"];
      console.log("this:", this);
    }, 1000);
  },
};

obj.changeData();

打印结果:

image.png

可以看出,上面打印的this其实都是指向的obj。在箭头函数内它自身没有this,那么它就向外层寻找,找到了changeData这个函数,而这时由于隐式绑定规则,changeData由obj调用的,那么changeData的this就指向的obj,所以箭头函数的this指向的就是obj。