作用域、执行上下文和this
# 前言
作用域
和this
一直都是js里令人头疼的问题,个人总结了一下自己对作用域
以及this指向
的一些理解。
# 作用域
# 什么是作用域?
作用域本身指的就是某个
变量
所能被访问
的区域,也就是你在某处定义了一个变量,那么有这么一个区域内,是可以访问这个变量
的。出了这个区域。你就无法访问
他了。对于这种现象,就可以理解为作用域
而作用域从概念上来区分有两种,一是词法作用域,二是动态作用域。
# 词法作用域和动态作用域
词法作用域
指的是变量在何处定义,他的作用域
就在哪里。而动态作用域
则是变量在何处被调用
,则他的作用域就在何处。在js
中采用的就是词法作用域
# 全局作用域,函数作用域,块级作用域
上面介绍了两种概念上的作用域,接下来介绍一下js中的三种作用域。
- 全局作用域
全局作用域
,顾名思义,就是定义在全局的变量。在代码的任何地方
都可以访问全局作用域内的变量。至于是为什么,这里留个疑问。
- 块级作用域
js本身是没有
块级作用域
的,也就是我们定义在{}
里的变量都会被提升到全局
。而在es6引入了let
关键字后,就有了块级作用域
下面看一段代码示例
if(true){
var a = 1
}
console.log(a)// 1
if(true){
let a = 1
}
console.log(a) //a is not defined
2
3
4
5
6
7
8
9
在没有let
之前,用var声明的变量
会被提升
到全局,所以我们在花括号外面也能访问到a。而在使用了let
之后,就会形成块级作用域
,也就是变量只能再花括号内访问了,所以第二段代码会报错,提示a为定义
- 函数作用域
最有意思的东西来了,
函数作用域
,他是一个能牵扯出很多东西的作用域。许许多多复杂的概念都是在他里头出现的,比如闭包
,作用域链
,执行上下文
等等。 那么他到底是个什么东西呢,其实可以把他理解成函数体
内的区域,和块级作用域相似
。都是花括号包裹的这个区域,不过在函数作用域内会发生许许多多奇怪的事情 例如,在函数内访问别的变量,就会牵扯出闭包,作用域链的概念,再比如在函数体内能访问哪些变量。这就又涉及到了函数的执行上下文的概念。下面就让我来一一解答一下
# 作用域链,执行上下文
- 执行上下文
先从
执行上下文
开始讲起,执行上下文又分为全局执行上下文
和函数执行上下文
。可以把他理解成一个对象。这个对象上包含了当前可访问的活动变量
,this
等一些列东西。 js自身维护了一个执行上下文栈
,没当一个函数被调用
时,就会创建
一个函数的执行上下文
,将他压入
栈内。这个执行上下文
包含了他父级上下文的变量
。栈的最底部就是全局执行上下文
。 当我们在一个函数体内访问一个变量
时,首先js会在当前
函数执行上下文内查找是否有这个变量,没有的话就会去父级执行上下文
内查找,一直到全局执行上下文
为止。这也就是上文中为什么任何地方都可以访问到全局变量
了。因为全局变量就被放在全局执行上下文里头
- 作用域链
根据执行上下文中变量查找的概念,是不是感觉很像原型链的查找,没错这种查找就可以称为是
作用域链
。
# this
this到底是是个什么东西?
在js中this一般分为三种
- 在构造函数中,this指向当前
被创建的对象
- 在函数调用中,this指向
调用函数的那个对象
- 在call,bind,apply中,this指向
传入的那个对象
- 在箭头函数里,指向
父级执行上下文
里的this
在不同的情况下,this会有不同的含义,下面通过几段代码来更深层次的理解一下
// 在函数中
let obj = {
hi:'嗨喽',
sayHi:function (){
console.log(this.hi)
}
}
obj.sayHi() // 嗨喽
// 在构造函数中
let obj2 = new function (){
this.name = 111
console.log(this) //{name:111}
}
// 在箭头函数中,因为父级是全局执行上下文,里面并没有hi这个变量,所以输出undefined
let obj = {
hi:'嗨喽',
sayHi:()=>{
console.log(this.hi) //undefined
}
}
// 在call,bind,apply中,this会变成传入的那个对象
let o = {hi:'我是o的hi属性'}
obj.sayHi.call(o) //'我是o的hi属性'
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25