JavaScript中的this关键字?

264 阅读7分钟

「这是我参与2022首次更文挑战的第4天,活动详情查看:2022首次更文挑战」。

大家好,我是刘十一,今天我们一起看看什么this、如何判断this的指向以及this的实际应用。

一、什么是this

this 执行上下文中的 一个属性,它 指向 最后一次调用 这个方法 的 对象

二、重点结论

1、this的指向,是在 函数 被调用的时候 确定的,也就是 执行上下文被创建时 确定的。

因此,一个函数中的this指向,可以非常灵活。如下面的例子中,同一个函数由于调用方式的不同,this指向了不一样的对象。

var a = 10;
var obj = {
  a: 20
}

function fn() {
  console.log(this.a);
}

fn(); // 10
fn.call(obj); // 20

2、除此之外,在函数执行过程中,this一旦被确定,就不可更改了。

var a = 10;
var obj = {
  a: 20
}

function fn() {
  this = obj; // 这句话试图修改this,运行后会报错
  console.log(this.a);
}

fn();

三、this指向判断

1、函数调用模式

(1)当 函数 不是一个对象的 属性 时,直接(在全局对象中)作为 函数来调用 ,this 指向全局对象。
// 1)通过this绑定到 全局对象
this.a2 = 20;

// 2)通过声明绑定到 变量对象,但在全局环境中,变量对象就是它自身
var a1 = 10;

// 3)仅仅只有赋值操作,标识符会 隐式绑定 到 全局对象
a3 = 30;

// 输出结果会全部符合预期
console.log(a1);
console.log(a2);
console.log(a3);

2、方法调用模式

(1)如果函数 作为一个对象的 方法 调用时,this 指向这个对象。
var a = 20;
var obj = {
  a: 10,
  c: this.a + 20,
  fn: function () {
    return this.a;
  }
}

console.log(obj.c);    // 40
console.log(obj.fn()); // 10
(2)如果函数独立调用,那么该函数内部的this,则指向undefined。但是在非严格模式中,当this指向undefined时,它会被自动指向全局对象。
var a = 20;
var foo = {
  a: 10,
  getA: function () {
    return this.a;
  }
}
console.log(foo.getA()); // 10

var test = foo.getA;
console.log(test());  // 20 独立调用
var a = 20;
function getA() {
  return this.a;
}
var foo = {
  a: 10,
  getA: getA
}
console.log(foo.getA());  // 10
console.log(getA());   // 20  独立调用
var a = 20;
function foo() {
  var a = 1;
  var obj = {
    a: 10,
    c: this.a + 20,
    fn: function () {
      return this.a;
    }
  }
  return obj.c;
}
console.log(foo());  //40 独立使用
console.log(window.foo());  // 40 

3、构造器调用模式

使用 new 来构建函数,会执行如下四步操作:

  • 创建一个空的简单 JavaScript 对象(即 {} );
  • 为步骤1新创建的对象添加属性 proto ,将该属性链接至构造函数的原型对象 ;
  • 将步骤1新创建的对象作为this的上下文;
  • 如果该函数没有返回对象,则返回 this 。
(1)如果一个函数 用 new 调用 时,函数执行前会 新创建一个对象,this 指向这个新创建的对象。
function Person(name, age) {
    // 这里的this指向了谁?
    this.name = name;
    this.age = age;   
}

Person.prototype.getName = function() {
    // 这里的this又指向了谁?
    return this.name;
}

// 上面的2个this,是同一个吗?
var p1 = new Person('Nick', 20);
p1.getName();

解析: 函数没有返回对象,则返回 this,并且新建的对象 p1是this的上下文,即,构造函数的this,指向了新的实例对象 p1。

根据上边对函数模式中this的定义,如果函数 作为一个对象的 方法 调用时,this 指向这个对象,p1.getName()中getName中的this,也是指向了p1。

4、原型链中的调用模式

(1)指向生成的新对象
function Student(name){
    this.name = name;
}
var s1 = new Student('牛小六');
Student.prototype.doSth = function(){
    console.log(this.name);
}
s1.doSth(); // '牛小六'

5、apply、call、bind调用模式

apply、call、bind三者的作用与区别:

  • apply 、 call 、bind 这三个方法都可以 显示的 指定 调用函数的 this 指向;
  • apply 、 call 、bind 三者 第一个参数 都是this 要指向的对象,也就是想指定的上下文;
  • apply 、 call 、bind 三者都可以利用 后续参数 传参;
  • bind 是 返回 对应函数,便于 稍后调用;
  • apply 、call 则是 立即调用 。
(1)apply 方法接收两个参数:一个是 this 绑定的对象,一个是参数数组(或类数组)。

fun.apply(thisArg,[arg1, arg2, ...])

(2)call 方法接收的参数,第一个是 this 绑定的对象,后面的其余参数是传入函数执行的参数。

即,在使用 call() 方法时,传递给函数的参数必须逐个列举出来。 fun.call(thisArg, arg1, arg2, ...)

(3)bind 方法通过传入一个对象,返回一个 this 绑定了传入对象的新函数。

这个函数的 this 指向 除了使用 new 时会被改变,其他情况下都不会改变。 fun.bind(thisArg[, arg1[, arg2[, ...]]])

代码示例

如果我们有一个对象 banana= {color : "yellow"} ,但我们不想对它重新定义 say 方法,那么我们就可以通过 call 或 apply 使用 apple 中的 say 方法。

function fruits() {}
 
fruits.prototype = {
    color: "red",
    say: function() {
        console.log("My color is " + this.color);
    }
}
 
var apple = new fruits;
apple.say();    //My color is red

banana = {
    color: "yellow"
}
apple.say.call(banana);     //My color is yellow
apple.say.apply(banana);    //My color is yellow

bind 最简单的用法是创建一个函数,使这个函数不论怎么调用都有同样的this值。 常见的错误就像下面的例子一样,将方法从对象中拿出来,然后调用,并且希望this指向原来的对象。

this.num = 80; 
var mymodule = {
  num: 100,
  getNum: function() { 
    console.log(this.num);
  }
};

mymodule.getNum(); // 100

var getNum = mymodule.getNum;
getNum(); // 80, 因为在这个例子中,"this"指向全局对象

var boundGetNum = getNum.bind(mymodule);
boundGetNum(); // 100

6、箭头函数调用模式

箭头函数和普通函数的主要区别:

  • 没有自己的 this、super、arguments和new.target绑定。
  • 不能使用new来调用。
  • 没有原型对象。
  • 不可以改变this的绑定。
  • 形参名称不能重复。
  • 没有this绑定,必须通过查找作用域链来决定其值。
(1) 如果箭头函数被非箭头函数包含,则this绑定的是最近一层非箭头函数的this,否则this的值则被设置为全局对象
var name = 'window';
var student = {
    name: '牛小六',
    doSth: function(){
        // var self = this;
        var arrowDoSth = () => {
            // console.log(self.name);
            console.log(this.name);
        }
        arrowDoSth();
    },
    arrowDoSth2: () => {
        console.log(this.name);
    }
}
student.doSth(); // '牛小六'
student.arrowDoSth2(); // 'window'

也就是说无法通过call、apply、bind绑定箭头函数的this(它自身没有this)。

而call、apply、bind可以绑定缓存箭头函数上层的普通函数的this。

var student = {
    name: '牛小六',
    doSth: function(){
        console.log(this.name);
        return () => {
            console.log('arrowFn:', this.name);
        }
    }
}
var person = {
    name: 'person',
}
student.doSth().call(person); // '牛小六'  'arrowFn:' '牛小六'
student.doSth.call(person)(); // 'person' 'arrowFn:' 'person'

四、实际应用

题目1

var x = 0;
var obj = {
    x: 3,
    p: () => {
       console.log(this.x, "b");
    },
};

function Fn() {
  //函数名大写开头,默认为 构造函数
  this.a = 1;
  this.b = {
    x: 1,
    p: function (callback) {
      //callback 默认为 回调函数
      this.c = this.x + 1; //添加了一个c:2
      console.log(this.x, "a");
      obj.p.apply(this);
      callback();
      // window.callback();
    },
  };
}

var fn = new Fn()
fn.b.p(function () {
   console.log(this.x, "c");
 })

//答案1:1 'a'  0 'b'  0 'c'

解析1:

  • 调用 Fn构造函数的 实例对象fn 里的b对象变量 里的 名为 p的函数
  • 此时p函数 的this指向它的直接调用者 fn.b,即 this.x =1 (如果函数作为对象的一个属性被调用时,函数中的this指向该对象)
  • 欲将obj对象下的p函数的this指向改变到 obj.p函数,但obj.p是箭头函数, 箭头函数特点是:箭头函数不绑定this,会捕获其所在的上下文的this值,作为自己的this值。 此处this.x=0
  • callback()回调函数的调用者为window,则this.x=0

题目2

var x = 0;
var obj = {
    x: 3,
    p: () => {
       console.log(this.x, "b");
    },
};

function Fn() {
  //函数名大写开头,默认为构造函数
  this.a = 1;
  this.b = {
    x: 1,
    p: function (callback) {
      //callback 默认为回调函数
      this.c = this.x + 1; //添加了一个c:2
      console.log(this.x, "a");
      obj.p.apply(this);
      callback();
      // window.callback();
    },
  };
}

var fn = new Fn()

function myFunction() {
   fn.b.p(() => {
     console.log(this.x, "d");
   });
 }
 obj.fn = myFunction;
 obj.fn()
 
 //答案2:1 'a'  0 'b'  3 'd'

解析2:

  • obj对象中新增fn:myFunction(){...}元素
  • 此时p函数 的this指向它的直接调用者 fn.b,即 this.x =1
  • 欲将obj对象下的p函数的this指向改变到 obj.p函数,但obj.p是箭头函数,此处this.x=0
  • callback()回调函数的调用者为obj.fn ,则this.x=3

五、知识点总结

1、全局环境中(即在浏览器环境严格模式中(区别于node)): this、setTimeout、立即执行函数 等this默认指向window,但是,在箭头函数中,this的作用域环境在上层作用域内。

2、对象函数中: 如果函数作为对象的一个属性被调用时,函数中的this指向该对象。

3、构造函数中: 构造函数创建的实例的属性指向构造函数的prototype。

4、箭头函数中 箭头函数的this是在定义函数时绑定的,不是在执行过程中绑定的,箭头函数中的this取决于该函数被创建时的作用域环境。

5、apply、call、bind方法: 这三个方法都可以 显示的 指定 调用函数的 this 指向, 第一个参数 都是this 要指向的对象,也就是想指定的上下文。