js之不得不说的this

219 阅读4分钟

   this 是 JavaScript 中重要的一环。由于其灵活的指向,如果没有深刻的理解,在实际运用中也是最容易产生bug的环节。同时,如果能熟练使用它,那我们可能会更加自如的应对一些复杂的使用场景,让开发更容易更简洁。

  this到底指向谁,这是我们在使用this时最纠结的问题。如果用一句话来描述,通俗的来讲:
                谁调用它,this就指向谁。
用专业一点的话来说,this与当前的执行上下文息息相关,其指向是由使用时根据执行上下文动态确定的。

总结下来,有下面的一些确定this指向的规则:

  • call / apply / bind
  • new 操作符构造对象
  • 全局对象,非严格模式指向window/global,严格模式指向Undefined
  • ES6,箭头函数
  • 函数执行时有上下文对象,这时函数里面的this指向该上下文对象。

下面详细介绍一下这些规则:

1. 全局状态的this

首先,有如下示例:

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

function g(){
    'use strict'
    console.log(this);
}

f();  //window
g();  //undefined

  上面这种情况就属于函数在浏览器器的全局环境中调用,相当与当前的执行上下文为浏览器的全局环境变量。即非严格模式在Window,严格模式为undefined.

2. 上下文绑定this

当函数调用时有明确执行上下文时,这时候this则指向当前的执行上下文。看如下代码:

var name = "mini";
var girlfriend = {
    name:"angelababy",
    getName:function(){
      console.log(this.name);
    }
}

var yourGirl = girlfriend.getName;
yourGirl(); //mini
girlfriend.getName();  // angelababy

  其中:console.log(yourGirl())由于是在全局环境中执行的,其还是指向全局环境(window),相当于执行window.name = "mini"。而console.log(girlfriend.getName());这个时候this就指向当前调用它的对象,即this指向girlfriend,this.name就等价于girlfriend.name = “angelababy” 。
如果存在嵌套的链式调用呢?

    var girlfriend= {
    name: 'angelababy',
    china: {
   name: '杨颖',
    fn: function() {
   return this.name
    }
    }
}
girlfriend.china.fn()   // 杨颖

在这种嵌套链式调用的关系中,this 指向最后调用它的对象,也就是最靠近它的那个对象。 总的来说:this 指向最后调用它的对象。

3. 显示绑定之 call / apply / bind

  js 提供了三种函数call / apply / bind可以改变当前this的指向。
 call:  第一个参数设置this指向的对象,从第二个参数起都是原函数的参数,立即执行。
 apply: 第一个参数设置this指向的对象,第二个参数必须是数组,这个数组代表原函数的参数列表,立即执行。
 bind: 不会执行相关函数,只是将一个值绑定到函数的this上,并将绑定好的函数返回,我们需要手动调用返回的函数。

从下面的代码来看其区别(它们是等价的):

var girlfriend;
fn.call(girlfriend, 'arg1', 'arg2');
fn.apply(girlfriend, ['arg1', 'arg2']);
fn.bind(girlfriend, 'arg1', 'arg2')();

下面看一下实际的使用:

var girlfriend = {
    name:"angelababy",
    getName:function(){
        console.log(this.name);
    }
}

var chinaName = {
    name: "杨颖"
}

girlfriend.getName.call(chinaName) //杨颖
girlfriend.getName.apply(chinaName)  //杨颖
girlfriend.getName.bind(chinaName)() //杨颖

可以看出this都指向了chinaName。

4. new操作符绑定

  js 中的new操作符调用构造函数,首先我们看一下new操作的执行过程:

  • 创建一个新的对象
  • 将这个新对象绑定到 此函数的this上
  • 为这个对象添加属性、方法等(包括原型链的继承)
  • 返回新对象,如果这个函数没有返回其他对象 操作实例:
  function girlfriend(){
    this.name = "angelababy";
}

girlfriend(); //undefined
var obj = new girlfriend();
obj.name;

   此时,可以看出使用new 调用函数后,会创建一个新对象,且将this指向它,并返回。 另:如果构造函数显示return的情况下, 如果返回一个对象类型,那么 this 就指向这个返回的对象。

function girlfriend(){
    this.name = "angelababy";
    return {name:"杨颖"};
}

var obj = new girlfriend();
obj.name;   //杨颖。

5. 箭头函数

   箭头函数的绑定规则和上诉的都有所不同,它基本上有两种规则: 根据外层(函数或者全局)上下文来决定。 箭头函数的this绑定无法被修改, 代码示例:

var name = "mini";
var girlfriend = {
    name:"angelababy",
    getName:() =>{
        console.log(this.name);
    }
}

var chinaName = {
    name: "杨颖"
}

girlfriend.getName.call(chinaName); // mini
girlfriend.getName(); //mini

上面说了这么多规则,那么他们优先级怎么样呢?

 var name = "mini";
var girlfriend = {
    name:"angelababy",
    getName:function(){
        console.log(this.name);
    }
}

var chinaName = {
    name: "杨颖"
}

girlfriend.getName.call(chinaName); // 杨颖

function girlfriend(name){
    this.name = name;
}

var chinaName = {}
girlfriend.bind(chinaName)("杨颖");
console.log(chinaName.name);  //杨颖
var newGirl = new girlfriend("angelababy");
console.log(newGirl.name);     //angelababy

   注:由于箭头函数的特殊性,此时不做排序参考。
  结论: new > call/apply/bind > 隐式绑定(1,2)

基本上只要充分理解上面这些规则,我们基本都能明白this的指向。