Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Javascript基础知识-变量和作用域 #6

Open
CharlesGC opened this issue Aug 17, 2021 · 0 comments
Open

Javascript基础知识-变量和作用域 #6

CharlesGC opened this issue Aug 17, 2021 · 0 comments
Labels

Comments

@CharlesGC
Copy link
Owner

1、基本类型和引用类型

Undefined 、 Null 、 Boolean 、 Number 和 String 这 5 种基本数据类型是按值访问的,因为可以操作保存在变量中的实际的值。

JavaScript 不允许直接访问内存中的位置,也就是说不能直接操作对象的内存空间。在操作对象时,实际上是在操作对象的引用而不是实际的对象。为此,引用类型的值是按引用访问的。

复制对象后在内存中的示意

这里分两种情况:

  • 当复制一个保存着对象的某个变量时,操作的是对象的引用,
  • 但当为对象添加属性时,操作是实际的对象,也就是在内存中的值。
    let o1 = {}
    let o2 = o1

    o1.a = 'abc'

    console.log(o1) // { a: 'abc' }
    console.log(o2) // { a: 'abc' }

这里的o2实际上创建了一个新指针,它与o1一样,都指向了同一个对象,所以修改了o1后,同样也会在o2上表现出来。

但如果是基本类型的值,复制操作则会直接创建一个副本,意味着两者不会相互影响。

了解了这个知识点就可以知道为什么 JavaScript 会有深拷贝和浅拷贝这 2 个概念了,两者都是作用于引用类型,深拷贝和浅拷贝区别在于我们是想复制一个对象的引用还是想完完整整复制出一个相同的不会互相干扰的值出来。

  • 浅拷贝会创建一个新对象,并且将原对象的根属性和值赋值给新对象,但是对于属性值仍是引用类型的属性则指向的还是同一个对象
  • 深拷贝会通过递归的方式拷贝每一层属性,从而使得拷贝后的对象和原对象不会相互影响

这两个概念我会放到后面专门写一篇文章来讲解,这里不再赘述。

扩展运算符和 JSON.stringify 是比较常见的拷贝函数,前者用于浅拷贝,后者用于深拷贝。另外还有Object.assign函数也可以用作浅拷贝。

let arr = [1,2,3]
let shallowArr = [...arr]
let deepArr = JSON.parse(JSON.stringify(arr))

2、检测类型

typeof 操作符可以很简单的确定变量是否是基本类型,如果变量的值是一个对象或 null ,则 typeof 操作符会像下面例子中所示的那样返回 "object"

let s = "Nicholas";
let b = true;
let i = 22;
let u;
let n = null;
let o = new Object();
console.log(typeof s); //string
console.log(typeof i); //number
console.log(typeof b); //boolean
console.log(typeof u); //undefined
console.log(typeof n); //object
console.log(typeof o); //object

对于进一步知道变量是哪种引用类型,需要使用 instanceof 操作符

let arr = []

console.log(typeof arr) // 'object'
console.log(arr instanceof Array) // true 表示它是一个数组的引用类型

instanceof操作符也有一个弊端,在它检测一个变量时,你必须提前知道它是哪一种Object的子类型,否则的话,也无法判断出变量属于哪一种引用类型。

let arr = []
console.log(arr instanceof Object) // true 

这里返回 true,这样就无法判断 arr 变量是数组类型还是对象类型,导致这样的原因是数组类型是继承自对象类型,所以 instanceof 无法判断变量是由子类实例化还是由父类实例化的,所以又有了第三种解决方案Object.prototype.toString,它可以解决 instanceof 无法判断子类和父类的问题。

let arr = []
console.log(Object.prototype.toString.call(arr)) // "[object Array]"

然而理想总是美好的,现实总是骨感的,虽然 Object.prototype.toString 解决了具体是那种引用类型的问题,但是又引入了另外一个问题

它无法判断是基本类型还是基本包装类型

let str = 'abc'
let objStr = new String("abc")
console.log(Object.prototype.toString.call(str)) // "[object String]"
console.log(Object.prototype.toString.call(objStr)) // "[object String]"

可以看到,基本类型和基本包装类型始终都返回 "[object String]" ,这样仍无法区分具体的类型,但是我们可以将它和 typeof 结合。

const isType = variable => {
    let type = typeof variable
    if(type === 'object' || type === 'function'){
       return Object.prototype.toString.call(variable).match( /\[object (\w+)]/)[1]
    }else{
        return type
    }
}

console.log(isType('123')) // 'string'
console.log(isType(new String('123'))) // 'String'

3、执行环境及作用域

执行环境也叫执行上下文,是当前 JavaScript 代码被解析和执行时所在环境的抽象概念。

每个函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境。当代码在一个环境中执行时,会创建变量对象的一个作用域链(scope chain)。

执行栈示意

标识符解析是沿着作用域链一级一级地搜索标识符的过程。搜索过程始终从作用域链的前端开始,然后逐级地向后回溯,直至找到标识符为止(如果找不到标识符,通常会导致错误发生)。

标识符解析过程

由于在 innerFunc 中并没有变量 a,JS 引擎就会沿着作用域链,找到保存在 func 中的变量 a (如果 func 中仍没有,则会去全局作用域中寻找,再没有则返回 undefined)

目前的 JavaScript 有 3 种环境

  • 全局环境
  • 函数环境
  • eval 环境

有 3 种作用域

  • 全局作用域
  • 函数作用域
  • 块级作用域 (ES6+)

4、块级作用域

JavaScript 没有块级作用域经常会导致理解上的困惑。在其他类 C 的语言中,由花括号封闭的代码块都有自己的作用域(如果用 ECMAScript 的话来讲,就是它们自己的执行环境),因而支持根据条件来定义变量。
比如下面这段代码:

if (true) {
    var color = "blue";
}
console.log(color); //"blue"

使用 var 声明的变量会自动被添加到最接近的环境中。在函数内部,最接近的环境就是函数的局部环境;如果初始化变量时没有使用 var 声明,该变量会自动被添加到全局环境。

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

if(false) {
    let color = "blue"
    var otherColor = "red"
}

console.log('color' in window) // false
console.log('otherColor' in window) // true

参考文章:

  • 《JavaScript 高级程序设计第三版》
@CharlesGC CharlesGC added the JS label Aug 17, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant