前言
原文地址 或掘金yeyan1996 好记性不如烂笔头。本人为了消化,对原文逐行敲一遍以加深印象。
基本类型和引用类型
JavaScript 不允许直接访问内存中的位置也就是说不能直接操作对象的内存空间
JS 只能通过指针(保存在栈中的变量)去操作堆内存中的引用类型的值即对象,如果给变量赋值另一个保存引用类型的值的变量,实际上只是创建了一个新指针,指向同一个堆内存中的对象

在上一章末尾也提到过,这就会导致通过其中一个变量修改引用类型的值时,会反映到所有指向它的变量


当了解了这个知识点就可以知道为什么 JavaScript 会有深拷贝和浅拷贝这 2 个概念了,两者都是作用与引用类型
- 浅拷贝会创建一个新对象,并且将原对象的根属性和值赋值给新对象,但是对于属性值仍是引用类型的属性则指向的还是同一个对象
- 深拷贝会通过递归的方式拷贝每一层属性,从而使得拷贝后的对象和原对象不会相互影响
扩展运算符和 JSON.stringify 是比较常见的拷贝函数,前者用于浅拷贝,后者用于深拷贝

检测类型
typeof 操作符可以很简单的确定变量是否是基本类型,但是对于引用类型会始终返回 'object' 或 'function'
对于进一步知道变量是哪种引用类型,需要使用 instanceof 操作符

注意这里 String 并不是 string,它是一个字符串的包装类型,同样也是引用类型,由于是基本类型,所以即使是字符串的包装类型,也会返回 false
但是 instanceof 也有缺陷

这里返回 true,这样就无法判断 arr 变量是数组类型还是对象类型,导致这样的原因是数组类型是继承自对象类型,所以 instanceof 无法判断变量是由子类实例化还是由父类实例化的,所以又有了第三种解决方案 Object.prototype.toString,它可以解决 instanceof 无法判断子类和父类的问题
理想总是美好的,现实总是骨感的,虽然 Object.prototype.toString 解决了具体是那种引用类型的问题,但是又引入了另外一个问题


执行环境及作用域
执行环境有时候也被称作上下文,每个函数都有自己的执行环境,执行函数时,会将执行的函数推入全局唯一的环境栈,同时创建变量对象的一个作用域链,作用域是一套规定 JS 引擎如何查找变量的规则,它是一种嵌套的结构,层层递进,形成了链

由于在 innerFunc 中并没有变量 a,JS 引擎就会沿着作用域链,找到保存在 func 中的变量 a (如果 func 中仍没有,则会去全局作用域中寻找,再没有则返回 undefined)
也有一种说法是在创建函数的同时会创建该函数的作用域链,在执行函数时,复制当前函数的作用域链,并将当前函数的活动对象放入作用域链的最前端,我们来看上述代码的打印结果

可以看到在打印 innerFunc 的时候,innerFunc 还没有执行,此时它的内部已经有 2 个作用域形成的作用域链了,而在运行时才将名为 innerFunc 函数的作用域 (local) 放到作用域链的最前端 目前的 JavaScript 有 3 种环境
- 全局环境
- 函数环境
- eval 环境
有 3 种作用域
- 全局作用域
- 函数作用域
- 块级作用域 (ES6+)
块级作用域
块级作用域通俗来说就是花括号中的代码,在 ES6 之前 JavaScript 没有块级作用域,那时只能使用 var 来声明变量,会有变量提升的效果 (将声明变量的行为,提升到当前环境创建时执行)

而 ES6 后,使用 let/const 可以将其包裹的花括号成为块级作用域,并且使用 let/const 声明的变量不会有变量提升
