Skip to content

Commit 9b84ce3

Browse files
authored
feat: this.md added (azl397985856#70)
upload this.md
2 parents 521fe24 + 8f34b69 commit 9b84ce3

File tree

2 files changed

+333
-1
lines changed

2 files changed

+333
-1
lines changed

docs/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ JavaScript 是前端基础中的基础了, 这里的面试题目层出不穷,
154154
- [作用域与闭包](./topics/js/scope&closures.md)
155155
- [引用和操作符优先级](./topics/js/reference&priority.md)
156156
- [原型和继承](./topics/js/prototype.md)
157-
- [this](./topics/js/this.md)(施工中)
157+
- [this](./topics/js/this.md)
158158
- [执行上下文(EC)](./topics/js/EC.md)(施工中)
159159
- [ES6+](es6+.md)(施工中)
160160

docs/topics/js/this.md

Lines changed: 332 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,332 @@
1+
## 一、引子
2+
3+
```js
4+
var name = "Heternally";
5+
var obj = {
6+
name: "zl",
7+
foo: function() {
8+
console.log(this.bar);
9+
}
10+
};
11+
12+
var foo = obj.foo;
13+
14+
obj.foo(); // "zl"
15+
foo(); // "Heternally"
16+
```
17+
可以看到上面代码中,`obj.foo``foo`都指向同一个函数,但是执行结果却不一样;产生这种差异的原因,就在于函数体内部使用了`this`关键字;
18+
19+
相信很多文章都会说,**`this`指向的是函数运行时所在的环境。**
20+
21+
所以上面的问题,对`obj.foo()`来说,`foo`运行在`obj`环境中,所以`this`指向`obj`;对于`foo()`来说,`foo`运行在全局环境下,所以在非严格模式下`this`指向`window`,所以导致了两者运行的结果不同;
22+
23+
看到这有的同学可能就有疑问了,函数的运行环境是如何判定的?为什么`obj.foo()`就是在`obj`环境,为何`var foo = obj.foo;``foo()`就在全局环境执行了;
24+
25+
接下来就跟同学们讲解一下为何`Javascript`这样处理,带你彻底理解`this`
26+
27+
## 二、为何设计`this`关键字
28+
29+
要理解`this`的设计,我们需要先了解`Javascript`中内存的数据结构;
30+
31+
`Javascript`内置七种数据类型,可以分为**基本数据类型****对象数据类型**,在这里我们主要讲解一下**对象数据类型**在内存中的存储方式;
32+
33+
```js
34+
var obj = { name: 'Heternally'};
35+
```
36+
`Javascript`引擎在处理上面代码时,会在**堆内存**中,生成一个对象`{ name: 'Heternally'}`,然后把这个对象在内存中的地址赋值给变量`obj`。所以在读取`obj.name`时,需要先从变量`obj`拿到地址,然后再从对应地址中拿到对象,再返回它的`name`属性。
37+
38+
可能看到这里会有同学要问这跟`this`有啥关系,别急,接下来重点来啦:
39+
40+
对象的属性可能是一个函数,当引擎遇到对象属性是函数的情况,会将函数单独保存在****中,然后再将函数的地址赋值给对象属性;而`Javascript`是允许在函数体内引用当前环境的其他变量,那么问题来了,函数可以在不同的运行环境执行,所以我们就需要一种机制,能够在函数内获得当前运行环境,由此诞生了`this`**它的设计目的就是指向函数运行时所在的环境。**
41+
42+
理解了`this`的设计,剩下的难点就是如何正确的判定它所指向的环境。
43+
44+
## 三、如何正确判定`this`指向
45+
46+
总结了`this`的绑定规则总共是有下面5种:
47+
* 1、默认绑定(严格/非严格模式)
48+
* 2、隐式绑定
49+
* 3、显式绑定
50+
* 4、new绑定
51+
* 5、ES6箭头函数绑定
52+
53+
### 1、默认绑定
54+
55+
#### 1.1 严格模式
56+
57+
在严格模式下,不能将全局对象`window`作为默认绑定,此时`this`会绑定到`undefined`,但是在严格模式下调用函数则不会影响默认绑定。
58+
59+
```js
60+
(() => {
61+
"use strict"
62+
function foo() {
63+
console.log(this.name);
64+
};
65+
var name = "Heternally";
66+
foo();
67+
})();
68+
69+
// Uncaught TypeError: Cannot read property 'name' of undefined at foo
70+
```
71+
72+
```js
73+
var name = 'Heternally';
74+
function foo() {
75+
console.log(this.name);
76+
};
77+
78+
(() => {
79+
"use strict"
80+
foo();
81+
})();
82+
83+
// Heternally
84+
```
85+
86+
#### 1.2 非严格模式
87+
88+
在非严格模式下,此时`this`就指向**全局对象**
89+
90+
```js
91+
var name = 'Heternally';
92+
function foo() {
93+
console.log(this.name);
94+
}
95+
96+
foo(); // Heternally
97+
```
98+
99+
### 2、隐式绑定
100+
101+
当函数作为对象的属性存在,通过**对象属性执行函数**时,此时隐式绑定规则会将`this`绑定到对象上;
102+
103+
```js
104+
var name = 'Heternally';
105+
function foo() {
106+
console.log(this.name);
107+
}
108+
109+
var obj = {
110+
name: 'zl',
111+
foo,
112+
}
113+
114+
obj.foo(); // zl
115+
```
116+
117+
注意上面代码中函数执行方式是通过**对象属性**执行
118+
119+
```js
120+
var name = 'Heternally';
121+
function foo() {
122+
console.log(this.name);
123+
}
124+
125+
var obj = {
126+
name: 'zl',
127+
foo,
128+
}
129+
130+
foo(); // Heternally
131+
132+
var foo1 = obj.foo;
133+
134+
foo1(); // Heternally
135+
136+
obj.foo(); // zl
137+
```
138+
由上面代码可以发现,通过赋值操作后执行函数,会应用默认绑定,此时在非严格模式下`this`会指向全局对象。
139+
140+
同样的,函数传参也是一种隐式赋值,此时在回调函数中会丢失`this`绑定。
141+
142+
```js
143+
function foo() {
144+
console.log(this.name);
145+
}
146+
147+
function Foo(fn) {
148+
fn();
149+
}
150+
151+
var obj = {
152+
name: 'zl',
153+
foo,
154+
}
155+
156+
var name = 'Heternally';
157+
158+
Foo(obj.foo); // Heternally
159+
```
160+
161+
### 3、显式绑定
162+
163+
**通过 `call` `apply` `bind`绑定**
164+
165+
相信同学们都知道这三个方法的作用,这边就主要拿`call`来举例;
166+
167+
> 一句话介绍`call`:使用一个指定的`this`和若干个指定的参数调用某个函数或方法。
168+
169+
在讲解`call`显示绑定之前,我们先想一下`call`做了哪些事儿。
170+
171+
* 将函数设为对象的属性
172+
* 指定函数的this,并进行传参
173+
* 执行&删除函数
174+
* 判定如果没有指定要绑定的this,非严格模式下默认指向全局对象
175+
176+
可以看到值调用`call`方法后,会将`this`绑定到指定对象,所以称为**显示绑定**
177+
178+
```js
179+
function foo() {
180+
console.log(this.name);
181+
}
182+
183+
var obj = {
184+
name: 'Heternally',
185+
}
186+
187+
var obj1 = {
188+
name: 'Heternally1'
189+
}
190+
191+
var name = 'zl';
192+
193+
foo.call(obj); // Heternally 调用call方法后强行将foo函数的this指向来obj对象上
194+
195+
foo.call(obj).call(obj1); // Heternally 多次调用call方法,以第一次为准
196+
197+
foo.call(obj1).call(obj); // Heternally1
198+
199+
foo.call();// zl 没有传入指定对象,所以this默认指向全局对象
200+
```
201+
> 如果call、apple、bind的绑定对象是null或者undefined,那么实际上在调用时这些值都会被忽略,所以使用的是默认绑定规则
202+
203+
204+
### 4、通过new绑定
205+
206+
我们先看看构造函数在使用`new`后,执行了什么操作:
207+
* 它创建(构造)了一个全新的对象
208+
* 它会被执行[[Prototype]](也就是__proto__)链接
209+
* 它使this指向新创建的对象
210+
* 通过new创建的每个对象将最终被[[Prototype]]链接到这个函数的prototype对象上
211+
* 如果函数没有返回对象类型Object(包含Functoin, Array, Date, RegExg, Error),那么new表达式中的函数调用将返回该对象引用
212+
213+
所以在使用`new`调用构造函数后,会构造一个新对象并将函数调用中的`this`绑定到新对象上。
214+
215+
```js
216+
var name = 'zl';
217+
218+
function foo(name) {
219+
this.name = name;
220+
}
221+
222+
var bar = {
223+
name: 'object',
224+
foo1: new foo('Heternally'),
225+
}
226+
227+
console.log(bar.foo1.name); // Heternally
228+
```
229+
230+
构造函数是存在返回值的,可以将函数的返回值分成三种情况:
231+
232+
* 返回一个对象
233+
* 没有返回值,即默认返回`undefined`
234+
* 返回基本数据类型
235+
236+
```js
237+
1、返回一个对象
238+
function Foo(name,age) {
239+
this.name = name;
240+
return {
241+
age
242+
}
243+
}
244+
245+
var bar = new Foo("Heternally","18");
246+
247+
bar.name; // undefined
248+
249+
bar.age; // 18
250+
251+
2、没有返回值
252+
253+
function Foo(name,age) {
254+
this.name = name;
255+
}
256+
257+
var bar = new Foo("Heternally","18");
258+
259+
bar.name; // Heternally
260+
261+
bar.age; // undefined
262+
263+
3、返回基本数据类型
264+
265+
function Foo(name,age) {
266+
this.name = name;
267+
return 123
268+
}
269+
270+
var bar = new Foo("Heternally","18");
271+
272+
bar.name; // Heternally
273+
274+
bar.age; // undefined
275+
```
276+
277+
所以使用`new`绑定时,需要判断函数返回的值是否为一个对象,如果是对象,那么`this`会绑定到返回的对象上。
278+
279+
### 5、ES6箭头函数绑定
280+
281+
ES6新增了一种函数类型:箭头函数,箭头函数调用时无法使用上面四种规则了,它和普通函数最不同的一点就是对于箭头函数的`this`指向,是根据它外层(函数/全局)作用域来决定。
282+
283+
```js
284+
function foo() {
285+
return (name) => {
286+
console.log(this.name);
287+
}
288+
}
289+
290+
var obj = {
291+
name: 'Heternally'
292+
}
293+
294+
var obj1 = {
295+
name: 'text'
296+
}
297+
298+
var name = 'zl';
299+
300+
var foo1 = foo();
301+
foo1(); // zl
302+
303+
var foo2 = foo.call(obj);
304+
foo2(); // Heternally
305+
306+
foo2.call(obj1); // Heternally 可以看到,箭头函数的`this`绑定后无法被修改
307+
308+
```
309+
310+
311+
### 6、规则优先级
312+
313+
```js
314+
1new绑定
315+
var obj = new Foo();
316+
this绑定新的对象上
317+
318+
2、显示绑定
319+
var obj = foo.call(bar);
320+
this绑定到指定对象上,若指定对象为null/undefined或着没传,则使用默认绑定规则
321+
322+
3、隐式绑定
323+
var obj = bar.foo();
324+
this绑定到调用方法的对象上
325+
326+
4、默认绑定
327+
foo();
328+
this在严格模式下绑定到undefined
329+
在非严格模式下绑定到全局对象
330+
```
331+
332+
> [原文链接](https://github.com/HEternally/Blog/blob/master/this%E5%85%A8%E9%9D%A2%E8%A7%A3%E6%9E%90.md)

0 commit comments

Comments
 (0)