# 一.基础

  • 函数中设置严格模式,this 为 undefined。因此此时会正常输出。(Undefined,Null,Boolean,Number,String,Symbol,BigInt)
  • 处于非严格模式下,要绑定的 this 指定为 null 或 undefined 时会自动替换为全局对象,原始值则会被包装
  • ES6 的箭头函数中,this 只指向它的创建者(声明时它所在的作用域);箭头函数没有⾃⼰的 this, 它会捕获其所在(即定义的位置)上下⽂的 this 值, 作为⾃⼰的 this 值

# 二.示例

# 1. 例子 1

var bar = 2;
var obj = {
  bar: 1,
  foo: function() {
    console.log(this.bar);
  },
  boo: (function() {
    console.log(this.bar);
  })(),
};

var foo = obj.foo;

obj.foo();
foo();

// 输出
// 2
// 1
// 2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

通过分析代码,我们可以知道,第一个 2 是在第 8 行代码处输出的,第二个 1 是在第 14 行代码处输出的,第三个 2 是在第 15 行代码处输出的。

这样的结果是由于不同地方的 this 指向不同,导致此 this.bar 非彼 this.bar。关于在 JavaScript 中几种不同情况下的 this 指向问题,可总结为以下 5 中情况:

# 情况一:自执行函数

//自执行函数
boo: (function() {
  console.log(this.bar); //输出2
})();
1
2
3
4

在上面的热身代码中,boo 函数是一个自执行函数,也就是说当浏览器运行这段代码时,会先自动执行 boo 函数。切记:自执行函数里面的 this 指向 window 全局对象。既然 this 指向了 window,那么 this.bar 即就是 window.bar,所以在该行代码处输出 2。

# 情况二:函数调用模式

foo: function () {
  console.log(this.bar)    //输出2
},
foo() // 函数调用模式
1
2
3
4

在上面代码中第 15 行这样的单纯的函数调用时,那么切记:在单纯的函数调用模式中,被调用函数内部的 this 指向 window 全局对象。所以 this.bar 即就是 window.bar,在该行代码处输出 2。

# 情况三:方法调用模式

var obj = {
  bar: 1,
  foo: function() {
    console.log(this.bar); //输出1
  },
};
obj.foo(); // 方法调用模式
1
2
3
4
5
6
7

在形如 obj.foo()这种,对象.方法这种模式我们称为方法调用模式,在这种模式中,this 指向调用这个方法的对象。在热身代码中,由于 foo 函数是被 obj.foo()这种方式调用的,那么 foo 函数内的 this 就指向了 obj,因此 this.bar 即就是 obj.bar,所以在该行代码处输出 1。

Tips:关于情况二和情况三,我们可以简单粗暴的这么记:函数执行的时候,看函数名前面是否有".",有的话"."前面是谁 this 就指向谁,没有的话 this 就指向 window 全局对象

# 情况四:构造函数调用模式

这种情况最容易理解,在使用构造函数实例化对象时,构造函数中的 this 指向实例化出来的新对象。

function Person(name) {
  this.name = name;
  console.log(this); //输出xiaoming
}

xiaoming = new Person("xiaoming");
1
2
3
4
5
6

# 其他

在函数执行的时候会再函数内部创建两个变量,arguments,thisarguments存储着实参的一个类数组对象;this指向函数的执行上下文(谁调用这个函数,this 就指向谁)

function bbb(){
  console.log(this)
}
var objA={
  b:bbb
  c:{
    d:bbb
  }
}
bbb() //指向window
objA.b() //指向objA {b:f,c:{...}}
objA.c.d() //指向objA.c {d:f}

1
2
3
4
5
6
7
8
9
10
11
12
13

# 情况五:apply/call/bind 改变 this 指向

apply 和 call 这两个方法,可以修改函数调用上下文,也就是 this 的指向。call 和 apply 的区别如下:

  • apply:函数.apply(对象, 函数需要参数列表,是一个数组)

  • call:函数.call(对象,函数所需要的参数 1,参数 2,参数 3...参数 n)

    1. 第一个参数都是要把 this 修改指向的对象

    2. 当函数需要参数的时候,那么 apply 是用数组进行参数的传递,而 call 是使用单个的参数进行传递

    3. apply 和 call 方法第一个传入参数是 null 的时候都表示为函数调用模式,也就是将 this 指向 window

对于这种情况,我们只需看函数的第一个参数是谁 this 就指向谁,如果是 null,则指向 window 全局对象。

  • bind:bind()改过 this 后,不执行函数,会返回一个绑定新 this 的函数
//例如:
function f() {
  console.log("看我怎么被调用");
  console.log(this); //指向this
}
var obj = {};
f.call(obj); //直接调用函数
var g = f.bind(obj); //bind()不能调用函数
g(); //此时才调用函数
1
2
3
4
5
6
7
8
9

# 2. 例子 2

// 严格模式
"use strict";
function test() {
  console.log(this);
}
test.call(2); // 2
// 非严格模式
function test() {
  console.log(this);
}
test.call(2);
// Number {2}
1
2
3
4
5
6
7
8
9
10
11
12

# 3. 例子 3

// ES6的箭头函数中,this只指向它的创建者(声明时它所在的作用域);
window.name = "小舞";
let obj = {
  name: "唐三",
  demo: function demo() {
    console.log(this.name); //第一个this
    let demo1 = () => {
      console.log(this.name); //第二个this
    };
    demo1(); //demo1是一个箭头函数,所以此时第二个this指向的也是obj,结果是 唐三;
  },
};
obj.demo(); //demo是一个传统函数,所以第一个this还是指向它的调用者obj,结果是 唐三;
1
2
3
4
5
6
7
8
9
10
11
12
13

# 4. 例子 4

let length = 10;
function fn() {
  console.log(this.length);
}
let obj = {
  length: 5,
  method: function(fn) {
    fn();
    arguments[0]();
  },
};
obj.method(fn, 1);
// 输出 火狐
// 0
// 2
// 输出 谷歌 edge
// 1
// 2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

解析: ① ⾸先,使⽤ let 关键字定义变量, 在规范中要求,不允许重复定义。 在传统的 JS 习惯中,所有 var 直接定义的变量,均为 window 对象的属性。即 window 充当了全局作⽤域对象,那么,如果 window 中已经存在⼀个 length 属性,重复定义 length 应该是不被允许的。由于规范中没有对这种情况做明确的规定。IE 浏览器和⾕歌浏览器测试结果不⼀样。

② 其次,这道题⽬重点考察的依然是 this 指向。 代码开始于 obj.method(fn,1); 然后进⼊到 method ⽅法中 直接调⽤了 fn(); fn 作为 method ⽅法的参数,是⼀个局部变量,这种调⽤⽅式 既不等于 obj.fn() ,也不等于 window.fn()等价于 scope.fn(); 也就是作⽤域对象的调⽤;⼜因为,this 不能打印作⽤域对象,这时只能显⽰ window 那么第⼀次 fn 的执⾏,打印出的 this.length,就是 window.length,结果为 0。

③ 然后,是 arguments 这句代码 arguments 是⼀个数组,⾥⾯存储了所有的参数。在本例中,method ⽅法被调⽤时,传⼊了两个参数,⼀个是 fn,⼀个是数字 1 因此,arguments[0]即代表 fn;⼜因为,数组是特殊的对象,下标即是特殊的属性名,那么 arguments 其实等价于:arguments.0();最终,this.length,就等于 arguments.length。结果为 2。

# 5. 例子 5

函数返回了⼀个函数表达式形式的声明,⽽这个调⽤的调⽤是在全局环境中,所以其内部的 this 就是指向 window 变量对象。


var name = "The Window"var object = {
  name : "My Object",
  getNameFunc : function(){
    return function(){
      return this.name;
      };
  }
};
 alert(object.getNameFunc()());//"The Window"
 // object.getNameFunc()返回了⼀个匿名函数,然后调⽤执⾏这个匿名函数。
//因为这个匿名函数不是作为某个对象的⽅法来调⽤执⾏,所以它的this就是window对象
1
2
3
4
5
6
7
8
9
10
11
12
13

# 6. 例子 6

对象和箭头函数中的 this

var btn = {
  name: "点我",
  click: function() {
    setTimeout(() => {
      console.log(this);
    }, 1000);
  },
  show: () => {
    console.log(this);
  },
  hide: function() {
    return () => {
      console.log(this);
    };
  },
};
btn.show(); //window   由于箭头函数没有this,且外⾯没有函数包着,就是指向默认的window对象
btn.hide()(); //btn   btn.hide()返回的是箭头函数,外⾯有hide⽅法包着箭头函数,箭头函数this指的是调⽤hide⽅法的那个对象。
let temp = btn.hide();
temp(); //btn   同理2
let bb = btn.hide;
bb()(); //window  把hide函数给了全局变量bb
btn.click(); //btn  箭头函数所在⽗级click⽅法的上下⽂是btn,因此this指向btn
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 7. 例子 7

var x = 11;
var obj = {
 x: 22,
 y:function(){
  console.log(this);
 },
 z:()=>{
  console.log(this);
 }
 methods: {
  x: 33,
  say: function() {
   console.log(this.x)
  },
  say2: () => {
   console.log(this.x)
  }
 }
}
obj.methods.say();//methods 33
obj.methods.say2(); //window 11
obj.y();  //  obj
obj.z();  //window
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 8. 例子 8

var age = 99;
function PersonX() {
  this.age = 0;
  setTimeout(() => {
    this.age++;
    console.log(age);
  }, 1000);
}
PersonX()//1  这⾥的this就是window,本是⼀个构造函数,但没有实例化对象;进⼊PersonX内,全局变量内的age被修改为0,然后⼀秒之后+1,定时器内是个箭头函数,因此 this.age 的this和定时器内的this都是指的当前作⽤域对象,因此输出为 1
1
2
3
4
5
6
7
8
9

# 9. 例子 9

var length = 100;
function fee() {
  console.log(this.length);
}
var obj = {
  length: 10,
  getLength(cb) {
    console.log(cb); // 内容为 fee 函数
    console.log(arguments); // 为三个传递进来的实参
    cb(); //100
    arguments[0](); //3
  },
};
obj.getLength(fee, length, obj);
1
2
3
4
5
6
7
8
9
10
11
12
13
14

① 虽然 getLength 函数只声明了一个形参,但是在调用函数的时候依然可以传递多个实参

② 当只有一个形参而传递了多个形参时,调用函数形参获取的内容为第一个实参的内容

③ 传递的实参最后都会被存储到 arguments 伪数组当中,此时通过 arguments 调用函数,其 this 指向为 arguments

④ 因为函数被当作实参传递进来,此时函数中的 this 指向依然为 window,所以即使是在对象 obj 中调用的该函数,其函数体中的 this 依然为 window,所以打印值为 100

# 10. 例子 10

var x = 0;
function test() {
  console.log(this.x);
}
var obj = {};
obj.x = 1;
obj.m = test;
obj.m.apply(); //0,apply()的参数为空时,默认调用全局对象
obj.m.apply(obj); //1
1
2
3
4
5
6
7
8
9

# 11. 例子 11

var name = "the window";

var object = {
  name: "My Object",
  getName: function() {
    return this.name;
  },
};

object.getName(); //"My Object"

object.getName(); //"My Object"

(object.getName = object.getName)(); //"the window",函数赋值会改变内部this的指向,这也是为什么需要在 React 类组件中为事件处理程序绑定this的原因;
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 12. 例子 12

var a = 10;
var obt = {
  a: 20,
  fn: function() {
    var a = 30;
    console.log(this.a);
  },
};
obt.fn(); // 20

obt.fn.call(); // 10

obt.fn(); // 20

(obt.fn, obt.fn)(); // 10

new obt.fn(); // undefined
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 13. 例子 13

function a(xx) {
  this.x = xx;
  return this;
}
var x = a(5);
var y = a(6);

console.log(x.x); // undefined
console.log(y.x); // 6
1
2
3
4
5
6
7
8
9