JavaScript高级程序设计之变量和作用域
上次写了JS的基本概念,这次来讲下JS的变量和作用域。内容主要涵盖基本类型和引用类型的值、执行环境、垃圾收集。
基本类型和引用类型的值
基本概念篇章讲过JS一共有六种数据类型,这六种数据类型都可以用变量存储起来。按照数据类型分的话,可以分为基本类型值和引用类型值。基本类型:简单的数据。引用(所谓引用,就是地址。JS不允许直接操作对象的内存空间,因此变量存储对象时,存储的是对象的内存空间地址,也就是引用)类型:有多个值构成的对象。
动态的属性
用变量存储值得操作,基本类型和引用类型是相同的,不过它们之后所执行的操作,却不相同。引用类型的值,可以添加、删除属性和方法,而基本类型的只能进行改值操作。例let persion = {};persion.name="xiaoqianqian";
复制变量值
把值赋给新的变量,基本类型和引用类型也不相同。基本类型会把值拷贝一份,赋给新变量。两者之间是独立的。这时修改原始变量的值,并不会影响到新变量。引用类型则会把地址拷贝一份,赋给新变量。两者之间并不是独立的,因为存的是一个地址,所以指向的是同一个对象,也就是说,修改原始变量的属性,新变量对应的属性也会发生变化,当然如果给原始变量赋一个新的对象,则原始变量不会发生变化,因为两个变量所存的地址已经不同了。
传递参数
参数的传递,和变量的赋值是一个道理。可以把参数理解为函数的局部变量,传递参数其实就是执行了一次变量与新变量之间的赋值。
检测类型
在基本概念那一篇中介绍过一个检测类型的工具:typeof。对于基本类型来说这是一个很不错的工具,但对于引用类型来说,就不是特别好用了。为此JS提供了instanceof操作符,用来检测引用类型。instanceof的意思是检测某个对象是否是指定构造函数的实例,如果是返回true,如果不是返回false。例:const a = [] ; a instanceof Array 返回值为true。常量a是一个数组,数组的构造函数是Array,因此返回true
执行环境和作用域
执行环境书中将的不是特别好懂,大概意思就是每个函数都有着自己的执行环境,每个执行环境都有着与其关联的变量对象。每当函数执行的时候,执行环境就会被压入环境栈,并创建变量对象的作用域链,执行到某条语句的时候,会通过作用域链,按照自下而上的顺序去查找具体的变量(注:当前执行环境的变量对象始终位于作用域的前端,也就是最下边),一直到找到该变量的定义为止,然后继续执行计算或判断。如果查找不到变量,则抛出异常。执行完毕后,栈会将环境弹出。整个函数的执行到此也就结束了。作用域链还需要注意的是,全局作用域链(浏览器中指window对象)始终在作用域链的最顶层,所以函数内部可以方位全局变量,但全局变量无法访问到函数内部的局部变量。
没有块级作用域
var声明变量的方式是没有块级作用域的,只有const、let声明的变量才支持块级作用域。
垃圾收集
JS有着自己的垃圾回收机制,它会定时回收内存中没有用的变量,所以编程时基本无需担心内存问题。目前已知的共有两个垃圾回收策略:标记清除和引用计数。其中引用计数只在老的ie浏览器还在被使用。
标记清除
标记清除是最常用的垃圾回收方式,基本原理就是当一个变量进入环境后,就会标记为"进入环境",离开环境的时候,就会标记为“离开环境”,然后等待被回收。
引用计数
前边说过引用计数只有在老的ie上,还被使用,因为它的实现策略,存在着一个很大的问题。引用计数的实现原理就是跟踪变量被引用的次数,并记录下来,减少一次引用就减一,增加一次就加一。这样看似并没有什么问题,但当变量存储的是对象的时候,就会出现循环引用(例:对象A指向对象B,对象B同时指向了对象A,两者互相指向),而循环引用会导致变量一直在内存中得不到回收。
管理内存
虽然JS有着自己的垃圾回收机制,但是因为浏览器分配的内存是有限的,而且变量不会被浏览器立即回收,因此还是有必要对那些不使用的变量的引用进行手动释放。可以把变量值设为null,来释放变量的引用。这种释放引用的操作叫做解除引用。