javascript笔记之this到底指向哪里?

在执行上下文的创建阶段,会分别生成变量对象,建立作用域链,确定this指向。其中变量对象总结过了,今天就是要确定this的指向。

本文最最核心的一句话就是:this是在函数调用的时候确定的,一定要牢牢记住,其次,在函数执行过程中,this一旦被确定,就不可更改了。

1
2
3
4
5
6
7
8
9
10
11
var a = 10;
var obj = {
a: 20
}

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

fn();

this是什么

重点:this是一个对象,是对象和函数之间达成关联关系的纽带,是在函数调用的时候确定

怎么确定this的指向

因为this是在函数调用的时候确定的,所以this的指向是很灵活的,所以很多人对this的指向总是似懂非懂。那么这里可以告诉大家一个套路,也是本文唯一的套路,按照这个套路走,以后this的问题再也难不倒你了。

当你不能确定this指向哪里的时候,尝试把函数的调用转换为 call/apply 的形式,call/apply 的第一个参数就是this的指向!

call/apply手动指定this

call/apply可以自行手动设置this的指向。所有的函数都具有着两个方法,它们除了参数略有不同,其功能完全一样。它们的第一个参数都为this将要指向的对象。call/apply的具体使用这里就不讲了,如果不熟悉call/apply的用法 送你传送门

1
2
3
4
5
6
7
8
function fn(param) {
console.log(this.a,param);
}
var obj = {
a: "我是this的属性a"
}

fn.call(obj,"我是参数param"); // 我是this的属性a 我是参数param
  • call/apply第一个参数如果传入的是简单数据类型,会被自动包装为对象
1
2
3
4
5
6
function f(){
console.log(this)
}
f.call(10); // Number {10}
f.call(true); // Boolean {true}
f.call("str"); // String {"str"}
  • call/apply第一个参数如果没传,或者传入的是null/undefined的时候,this的指向跟是否在严格模式下有关。

    • 非严格模式下,this指向全局对象(浏览器是window)。

      1
      2
      3
      4
      5
      6
      function f(){
      console.log(this)
      }
      f.call(); // Window {postMessage: ƒ,...}
      f.call(null); // Window {postMessage: ƒ,...}
      f.call(undefined); // Window {postMessage: ƒ,...}
    • 严格模式下,this指向null/undefined

      1
      2
      3
      4
      5
      6
      7
      8
      "use strict"

      function f(){
      console.log(this)
      }
      f.call(); // undefined
      f.call(null); // null
      f.call(undefined); // undefined

几个栗子

只要掌握call/apply,结合是否严格模式,你就会发现虚无缥缈的this已经有迹可循了,下面例子中的函数调用代码都可以通过转换的call模式代码直接替换。

  • 纯粹的函数调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 非严格模式
var x = 1;
function test() {
console.log(this.x);
}

// 在心里将test()翻译为call模式,test.call(),非严格模式
// 所以this===window,所以输出window.x
test(); // 1


// 严格模式
"use strict"
var x = 1;
function test() {
console.log(this.x);
}
// 在心里将test()翻译为call模式,test.call(),严格模式
// 所以this===undefined,所以输出window.x
test(); // Uncaught TypeError: Cannot read property 'x' of undefined
  • 作为对象方法的调用
1
2
3
4
5
6
7
8
9
10
function test() {
console.log(this.x);
}

var obj = {
x:3,
m:test
};
// 在心里将test()翻译为call模式,obj.m.call(obj),这时this===obj,所以输出obj.x
obj.m(); // 3
  • 作为构造函数调用, this就指向构造出来的新对象
1
2
3
4
5
6
7
8
function Test() {
 this.x = 3;
}
// 在心里将test()翻译为call模式
// var obj = {}; Test.call(obj);
// 这时this===obj,所以输出obj.x
var obj = new Test();
obj.x; // 3
  • 通过call/apply改变this指向,将类数组对象转换为数组,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function exam(a, b, c, d, e) {

// 先看看函数的自带属性 arguments 什么是样子的
console.log(arguments);

// 使用call/apply将arguments转换为数组, 返回结果为数组,arguments自身不会改变
var arg = [].slice.call(arguments);

console.log(arg);
}

exam(2, 8, 9, 10, 3);
// { '0': 2, '1': 8, '2': 9, '3': 10, '4': 3 }
// [ 2, 8, 9, 10, 3 ]
// 也常常使用该方法将DOM中的nodelist转换为数组
// [].slice.call( document.getElementsByTagName('li') );
  • 根据自己的需要灵活修改this指向
1
2
3
4
5
6
7
8
9
10
var foo = {
name: 'joker',
showName: function() {
console.log(this.name);
}
}
var bar = {
name: 'rose'
}
foo.showName.call(bar);

总结

我们可以将平时通过括号、通过.、通过new去使用函数的方式,看做是call/apply的语法糖,是简写版,想要深入理解函数的调用,就要学好call
关于this的部分已经总结完了,希望这篇文章有助于你准确的理解this关键字,能够真正学到东西。

参考

this 的值到底是什么?一次说清楚
你怎么还没搞懂 this?
JS 的 new 到底是干什么的?
Javascript 的 this 用法
全方位解读this