# 1. this 的指向
- 以函数的形式调用时,this 永远都是 window
- 以方法的形式调用时,this 就是调用方法的对象
- 以构造函数的形式调用时,this 就是新创建的对象
- 使用 call 和 apply 调用时,this 就是指定的那个对象
在全局作用域中 this 代表 window
this
就是一个对象,this
是在函数被调用时发生的绑定,它指向什么完全取决于函数在哪里被调用。
1、以函数的形式调用,this 永远都是 window
var name = "windowsName";
function a() {
var name = "Cherry";
console.log(this.name); // windowsName
console.log("inner:" + this); // inner: Window
}
a();
console.log("outer:" + this); // outer: Window
2
3
4
5
6
7
8
9
10
调用 a
的地方 a()
,前面没有调用的对象那么就是全局对象 window
,这就相当于是 window.a()
;
注意,这里我们没有使用严格模式,如果使用严格模式的话,全局对象就是 undefined
,那么就会报错 Uncaught TypeError: Cannot read property 'name' of undefined
。
2、以方法的形式调用时,this 就是调用方法的对象
例 1:
var name = "windowsName";
var a = {
name: "Cherry",
fn: function() {
console.log(this.name); // Cherry
},
};
a.fn();
2
3
4
5
6
7
8
这里定义一个对象 a
,对象 a
有一个属性name
和一个方法fn
。
然后对象 a
通过 .
方法调用了其中的 fn 方法。
例 2:
var name = "windowsName";
var a = {
name: "Cherry",
fn: function() {
console.log(this.name); // Cherry
},
};
window.a.fn();
2
3
4
5
6
7
8
最后调用 fn()的仍然是对象 a。
例 3:
var name = "windowsName";
var a = {
fn: function() {
console.log(this.name); // undefined
},
};
window.a.fn();
2
3
4
5
6
7
输出 undefined
:调用 fn 的是 a 对象,也就是说 fn 的内部的 this 是对象 a,而对象 a 中没有 name 这个属性,也不会继续向上一个对象寻找 this.name
,而是直接输出 undefined
。
例 4:
var name = "windowsName";
var a = {
fn: function() {
console.log(this.name); // windowsName
},
};
var f = a.fn;
f();
2
3
4
5
6
7
8
为什么是 windowsName
,这是因为虽然将 a 对象的fn
方法赋值给变量 f
了,但是没有调用,this 指向最后调用它的那个对象,由于一开始f
并没有调用,所以 fn()
最后仍然是被 window 调用的。所以 this 指向的也就是 window。
例 5:
var name = "windowsName";
function fn() {
var name = "Cherry";
innerFunction();
function innerFunction() {
console.log(this.name); // windowsName
}
}
fn();
2
3
4
5
6
7
8
9
10
这里的innerFunction()
的调用:是一个函数调用(它就是作为一个函数调用的,没有挂载在任何对象上,所以对于没有挂载在任何对象上的函数,在非严格模式下 this 就是指向 window 的)
对于回调函数中的this
对象。
- setTimeout函数
//普通`setTimeout`函数: 100ms后执行时,this指向window对象
function foo() {
setTimeout(function() {
console.log(this);
console.log("id: ",this.id);
}, 100);
}
var id=21;
foo(); //this指向window对象, 21
foo.call({id:42}); //this指向window对象,21
-------------------------------------------------------------
//箭头setTimeout函数:
//箭头函数:this是在定义时生效的。this总是指向函数定义生效时所在的对象。
function foo() {
setTimeout(() =>{
console.log(this);
console.log("id: ",this.id);
}, 100);
}
var id=21;
foo(); //this指向window id=21
foo.call({id:42}); //this指向{id:42}对象 id=42
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- 事件处理函数
//普通处理函数
var handler = {
id: "123456",
init: function () {
document.addEventListener(
"click",
function (e) {
this.doSomething(e.type); //this指向window对象。所以会报错:
// Uncaught TypeError: this.doSomething is not a function at HTMLDocument.<anonymous>
},
false
);
},
doSomething: function (type) {
console.log("handler" + type + "for" + this.id);
},
};
//
handler.init();
-------------------------------------------------------------------------------
// 箭头函数:
var handler = {
id: "123456",
init: function () {
document.addEventListener(
"click",
(e) => {
this.doSomething(e.type); //this指向handler
//console.log(this); // {id: '123456', init: ƒ, doSomething: ƒ}
},
false
);
},
doSomething: function (type) {
console.log("handler " + type + " for " + this.id);// handler click for 123456
},
};
handler.init();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
总结:
对于回调函数中的this
对象。对于普通函数,this
指向调用时所在的对象(即window
对象)。对于箭头函数,this
指向定义生效时所在的对象。
3、以构造函数的形式调用时,this 就是新创建的对象
// 构造函数:
function myFunction(arg1, arg2) {
this.firstName = arg1;
this.lastName = arg2;
}
// This creates a new object
var a = new myFunction("Li", "Cherry");
a.lastName; // 返回 "Cherry"
2
3
4
5
6
7
8
9
new 的过程:
var a = new myFunction("Li","Cherry");
new myFunction{
var obj = {};
obj.__proto__ = myFunction.prototype;
var result = myFunction.call(obj,"Li","Cherry");
return typeof result === 'obj'? result : obj;
}
2
3
4
5
6
7
8
- 创建一个空对象 obj;
- 将新创建的空对象的隐式原型指向其构造函数的显示原型。
- 使用 call 改变 this 的指向
- 如果无返回值或者返回一个非对象值,则将 obj 返回作为新对象;
- 如果返回值是一个新对象的话那么直接直接返回该对象。
所以我们可以看到,在 new 的过程中,我们是使用 call 改变了 this 的指向。
4、使用 call 和 apply 调用时,this 就是指定的那个对象
# 2. 改变 this 的指向
var name = "windowsName";
var a = {
name: "Cherry",
func1: function() {
console.log(this.name);
},
func2: function() {
setTimeout(function() {
this.func1();
}, 100);
},
};
a.func2(); // this.func1 is not a function
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
在不使用箭头函数的情况下,是会报错的,因为最后调用 setTimeout
的对象是 window
,但是在 window
中并没有 func1 函数
。
所以需要改变 this 指向,改变 this 的指向总结有以下几种方法:
- 使用 ES6 的箭头函数
- 在函数内部使用
_this = this
- 使用
apply
、call
、bind
- new 实例化一个对象
# 2.1 箭头函数
箭头函数中没有this
绑定,必须通过查找作用域链来决定其值,如果箭头函数被非箭头函数包含,则 this 绑定的是最近一层非箭头函数的 this,否则,this
为undefined
。
var name = "windowsName";
var a = {
name: "Cherry",
func1: function() {
console.log(this.name);
},
func2: function() {
setTimeout(() => {
this.func1();
}, 100);
},
};
a.func2(); // Cherry
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 2.2 _this = this
在函数内部使用 _this = this
如果不使用 ES6,那么这种方式应该是最简单的不会出错的方式了,我们是先将调用这个函数的对象保存在变量 _this
中,然后在函数中都使用这个 _this
,这样 _this
就不会改变了。
var name = "windowsName";
var a = {
name: "Cherry",
func1: function() {
console.log(this.name);
},
func2: function() {
var _this = this;
setTimeout(function() {
_this.func1();
}, 100);
},
};
a.func2(); // Cherry
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
这个例子中,在 func2 中,首先设置 var _this = this;
,这里的 this
是调用 func2
的对象 a,为了防止在 func2
中的setTimeout
被window
调用而导致的在 setTimeout
中的this
为window
。我们将 this(指向变量 a)
赋值给一个变量 _this
,这样,在 func2
中我们使用 _this
就是指向对象a
了。
# 2.3 使用 apply、call、bind
使用 apply、call、bind 函数也是可以改变 this 的指向的。
- apply 方法接收两个参数:一个是 this 绑定的对象,一个是参数数组。
- call 方法接收两个参数:第一个是 this 绑定的对象,后面的其余参数是传入函数执行的参数。也就是说,在使用 call() 方法时,传递给函数的参数必须逐个列举出来。
- bind 方法通过传入一个对象,返回一个 this 绑定了传入对象的新函数。这个函数的 this 指向除了使用 new 时会被改变,其他情况下都不会改变。
apply
var a = {
name: "Cherry",
func1: function() {
console.log(this.name);
},
func2: function() {
setTimeout(
function() {
this.func1();
}.apply(a),
100
);
},
};
a.func2(); // Cherry
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
使用 call
var a = {
name: "Cherry",
func1: function() {
console.log(this.name);
},
func2: function() {
setTimeout(
function() {
this.func1();
}.call(a),
100
);
},
};
a.func2(); // Cherry
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
使用 bind
var a = {
name: "Cherry",
func1: function() {
console.log(this.name);
},
func2: function() {
setTimeout(
function() {
this.func1();
}.bind(a)(),
100
);
},
};
a.func2(); // Cherry
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 3. appll call bind 的区别
apply
和call
基本类似,他们的区别只是传入的参数不同。
apply
语法:
fun.apply(thisArg, [argsArray])
call
的语法:
fun.call(thisArg[, arg1[, arg2[, ...]]])
所以 apply
和call
的区别是call
方法接受的是若干个参数列表,而 apply
接收的是一个包含多个参数的数组。
var a ={
name : "Cherry",
fn : function (a,b) {
console.log( a + b)
}
}
var b = a.fn;
b.apply(a,[1,2]) // 3
var a ={
name : "Cherry",
fn : function (a,b) {
console.log( a + b)
}
}
var b = a.fn;
b.call(a,1,2) // 3
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
bind:
MDN 上的文档说明:
bind()
方法创建一个新的函数, 当被调用时,将其this
关键字设置为提供的值,在调用新函数时,在任何提供之前提供一个给定的参数序列。
var a ={
name : "Cherry",
fn : function (a,b) {
console.log( a + b)
}
}
var b = a.fn;
b.bind(a,1,2)
2
3
4
5
6
7
8
9
所以我们可以看出,bind
是创建一个新的函数,我们必须要手动去调用:
var a ={
name : "Cherry",
fn : function (a,b) {
console.log( a + b)
}
}
var b = a.fn;
b.bind(a,1,2)() // 3
2
3
4
5
6
7
8
9
# 4. 题目
下面代码的输出是什么?
const shape = {
radius: 10,
diameter() {
return this.radius * 2;
},
perimeter: () => 2 * Math.PI * this.radius
};
shape.diameter();
shape.perimeter();
2
3
4
5
6
7
8
9
10
- A:
20
and62.83185307179586
- B:
20
andNaN
- C:
20
and63
- D:
NaN
and63
答案
答案: B
请注意,diameter
是普通函数,而perimeter
是箭头函数。
对于箭头函数,this
关键字指向是它所在上下文(定义时的位置)的环境,与普通函数不同! 这意味着当我们调用perimeter
时,它不是指向shape
对象,而是指其定义时的环境(window)。没有值radius
属性,返回undefined
。