# 一.基础
- 函数中设置严格模式,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
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
})();
2
3
4
在上面的热身代码中,boo 函数是一个自执行函数,也就是说当浏览器运行这段代码时,会先自动执行 boo 函数。切记:自执行函数里面的 this 指向 window 全局对象。既然 this 指向了 window,那么 this.bar 即就是 window.bar,所以在该行代码处输出 2。
# 情况二:函数调用模式
foo: function () {
console.log(this.bar) //输出2
},
foo() // 函数调用模式
2
3
4
在上面代码中第 15 行这样的单纯的函数调用时,那么切记:在单纯的函数调用模式中,被调用函数内部的 this 指向 window 全局对象。所以 this.bar 即就是 window.bar,在该行代码处输出 2。
# 情况三:方法调用模式
var obj = {
bar: 1,
foo: function() {
console.log(this.bar); //输出1
},
};
obj.foo(); // 方法调用模式
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");
2
3
4
5
6
# 其他
在函数执行的时候会再函数内部创建两个变量,arguments,this。arguments存储着实参的一个类数组对象;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}
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)
第一个参数都是要把 this 修改指向的对象
当函数需要参数的时候,那么 apply 是用数组进行参数的传递,而 call 是使用单个的参数进行传递
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(); //此时才调用函数
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}
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,结果是 唐三;
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
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对象
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
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
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
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);
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
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的原因;
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
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
2
3
4
5
6
7
8
9