
sunilwang
2022/09/14阅读:23主题:全栈蓝
变量、作用域与This
作者简介
安国徽
:作为一名入职前端一年多的老腊肉,最近在听组内大神伟伟老师的 微前端- 乾坤 源码分享时,深刻的意识到了js的基础是多么的重要,于是我从抽屉中掏出了尘封已久的略带崭新的红宝书,然后就有了我们下面的故事。
其实这次的重点就是对于我来说 “剪不断,理还乱” 的作用域与This了,且听我慢慢道来。
变量类型
原始值与引用值
-
原始值:就是最简单的数据,包含Undefined、Null、Boolean、Number、String、Symbol。我们对其的操作和访问都是对值进行的。
-
引用值:保存在内存中的对象,因为js无法操作内存空间,所以我们对其操作和访问都是针对引用。
主要区分将会体现在动态属性、复制值、传递参数、确定类型
四个方面。
动态属性
动态属性简
单来说就是动态的赋值一个属性,对象可以直接通过obj.
的形式赋值,但是当给一个原始值类型的变量赋值时会发生什么呢。比如:

复制值
这个就比较简单了,原始值赋值过去的值改变后并不会影响旧值。而引用变量由于是多个索引指向同一块内存,改变一个,当其他的访问时也会发生变化。
传递参数
原始值传参与引用传参都是按照值传递。引用传参为什么也叫做按照值传递,就不是很容易理解。 当我们传递参数时,值会被复制给一个局部变量。
-
原始值传参:

对于引用值来说,相当于把这个对象的内存地址赋值给了这个命名参数,而对这个命名参数本身的修改是不会影响原来obj的。
确定类型
我们如何确定一个值是原始值还是引用类型,假如某个值是引用类型我们又该如何确定他是Object还是Function呢。
对于原始值的确认,我们可以用typeof
去判断。但是对于null
,假如我们:

这是因为对于null来说他在内存中的机器码为一堆0,而标志对象类型的机器码为000,所以就会有些冲突。
对于引用值我们可以根据instanceof
判断,instanceof
用于检测构造函数的 prototype
属性是否出现在某个实例对象的原型链上。
当然不想出错的话,这有一个更好的方法:

执行上下文与作用域
变量或函数的上下文决定他们可以访问哪些变量以及他们的行为。每个上下文都有一个关联的变量对象。主要有全局执行上下文,函数执行上下文,eval执行上下文。
全局上下文是最外层的上下文,就是我们常说的window对象。所有var定义的全局变量和函数都会成为window对象的属性和方法(对象环境记录)。let与const则不会(声明性环境记录)。
详见:https://segmentfault.com/a/1190000012162360
变量声明
-
var声明变量:声明时被自动添加到最接近的上下文,如果未经声明就初始化则会被自动添加到全局上下文。存在变量提升。
-
let声明变量:作用域是块级的,变量不能重复声明,也会被提升但是因为暂时性死区,所以在声明之前不能使用。
-
暂时性死区:当程序的控制流程在新的作用域进行实例化时,在此作用域中用let/const声明的变量会先在作用域中被创建出来,但因此时还未进行词法绑定(对声明语句进行求值运算),所以不能被访问(访问就会抛出错误)。所以在这运行流程进入作用域创建变量,到变量开始被访问之间的一段时间,就称之为TDZ(暂时性死区)。
-
const声明变量: 声明时必须被初始化为某个值。用const声明的变量的值不能再改变,引用类型内部键则不受影响。如果不想引用类型被修改可以使用Object.freeze(obj)。
❝
建议声明时使用const声明,这样编译器运行时会将实例替换成实际的值而不需要通过查询表进行变量查找。
❞
作用域链
-
示例代码如下:

❝在每个执行上下文的变量环境中,都包含了一个外部引用,用来指向外部的执行上下文,我们把这个外部引用称为 outer。
❞
-
调用栈如下:

从图中可以看出,bar 函数和 foo 函数的 outer 都是指向全局上下文的,这也就意味着如果在 bar 函数或者 foo 函数中使用了外部变量,那么 JavaScript 引擎会去全局执行上下文中查找。我们把这个查找的链条就称为作用域链
。
块级作用域中的变量查找
-
示例代码如下:

-
调用栈如下:
❝
if块中test查找流程:先在bar()的词法环境中查找,没有则去变量环境查找,如果也没用则去全局的的词法环境查找,找到test,打印1。
❞
大致查找流程如上。在 JavaScript 执行过程中,其作用域链是由词法作用域决定的,词法作用域就是指作用域是由代码中函数声明的位置来决定的,所以词法作用域是静态的作用域,通过它就能够预测代码在执行过程中如何查找标识符。词法作用域是代码编译阶段就决定好的,和函数是怎么调用的没有关系。
This
执行上下文中的this。作用域链和 this 是两套不同的系统,它们之间基本没太多联系。
JavaScript 中的 this 是什么
this 是和执行上下文绑定的,也就是说每个执行上下文中都有一个 this。因为执行上下文主要分为三种——全局执行上下文、函数执行上下文和 eval 执行上下文,所以对应的 this 也只有这三种——全局执行上下文中的 this、函数中的 this 和 eval 中的 this。因为没怎么用过eval,所以着重看一下全局和函数中执行上下文的this。
全局中的this
当我们在控制台直接执行console.log(this),就会发现打印的就是window对象。所以全局执行上下文中的 this 是指向 window 对象的。这也是 this 和作用域链的唯一交点,作用域链的最底端包含了 window 对象,全局执行上下文中的 this 也是指向 window 对象。
函数中的this

我们在控制台执行这段代码,会发现打印出的同样是window对象,默认情况下调用一个函数,其执行上下文中的 this 也是指向 window 对象的。
改变函数中this指向
call、bind、apply方法:
上面我们判断一个变量的类型就用到了call方法。call方法和apply方法唯一的不同就是传参不同,call可以接收任意个参数,每个参数会映射到相应位置的 Function 的参数上。

❝apply的参数必须是数组或者类数组,它们会被转换成类数组,传入 Function 中,并且会被映射到 Function 对应的参数上。
❞

而call/apply和bind之间的区别是:
-
执行:
call/apply 改变了函数的this上下文后马上执行该函数
bind 则是返回改变了上下文后的函数,不执行该函数 -
返回值:
call/apply 返回fun的执行结果
bind返回fun的拷贝,并指定了fun的this指向,保存了fun的参数。
通过对象调用的方法

在全局环境中调用一个函数,函数内部的 this 指向的是全局变量 window。
通过一个对象来调用其内部的一个方法,该方法的执行上下文中的 this 指向对象本身。
总结
以上就是关于变量、作用域、this的基础知识了。
框架固然要用的明白,但是扎实的基础才会赋予你造轮子的能力,红宝书还是需要读的通透的。---- 鲁迅说我没说过
作者介绍
