JavaScript变量作用域和内存管理

前言

JS中变量是弱类型,也就是说它的值和类型可以在脚本的生命周期内改变,这时一个强大的特性,但是使用起来也非常容易出现问题。JS中的数据分为基本类型和引用类型,Undefined、Null、Boolean、Number、String这五种就是基本类型,它们都是按值访问的变量,可以操作保存在变量中的实际值;引用类型的值保存在堆内存中的对象里,引用类型在实际操作的是对象引用。

动态属性

对于引用类型的值可以为其添加属性和方法,也可以改变和删除其属性和方法。

var hello = new Object()
hello.id = 10
hello.name = "zhangsan"
hello.print = function() {
     alert(this.id + " " + this.name)
}

hello.print() // 10 zhangsan

delete hello.id
alert(hello.id) // undefined

对于值类型如果也添加属性和方法,这样做并不会出现错误,但是真正操作的时候无法访问到添加的属性和方法。

var hello = "Hello"
hello.id = 10
hello.name = "zhangsan"
hello.print = function() {
     alert(this.id + " " + this.name)
}

alert(hello.print) // undefined
alert(hello.id)  // undefined

typeof操作符能够监测变量的类型,对于任意的Object引用对象都会返回object,JS为了判定引用对象的具体类型使用instanceof操作符,如果用它来测试基本类型直接返回false,对于引用类型会遍历原型链来做识别。

执行上下文和作用域

每个函数都有自己的执行上下文(Execution Context)对象,每个执行上下文对象又包含一个变量对象VO(Variable Object),这个对象专门用来存储执行上下文中的定义的变量、函数和参数,用户编写的代码无法访问这个对象,但解析器在执行过程中会使用它。当执行流执行到一个函数的时候,会创建变量对象的一个作用域链(Scope Chain);如果执行上下文对象是函数,那么函数的活动对象(Active Object)作为变量对象;作用域链下一个变量对象来自包含它的执行上下文变量对象,下一个来自于下一个的执行上下文变量对象,一直延续到最后一个执行上下文的变量对象。

var color = "red"
function print() {
    alert(color)
    var color = "green"
    alert(color)

    num = 100
    alert(num)
}

print() // undefined green 100

alert(num) // 100

使用var声明的变量会自动加入到最接近的环境中去,如果声明时没有var关键字会自动被添加到全局上下文中。var声明变量并且赋值其实是两个步骤,一个是声明另外一个是赋值,声明会被提前到函数开始的地方,赋值操作依然保持在原来的位置,print函数执行的时候它的变量对象VO里包含color的声明,但是还未赋值,这时alert结果就是undefined,之后做了赋值操作结果就是green了,num未使用var声明也就不会加入到print函数的执行环境中而是直接加入到了全局执行环境的变量对象中,后面在全局访问的时候依然能够获取。

从上面的例子可以看出函数的变量对象VO是在函数执行之前初始化的,这也就是为什么在函数中声明的变量和函数会被提前。其实它的声明过程还是分步骤的,主要有三个步骤:声明参数、声明函数、声明变量,其中函数的声明会自动覆盖变量的声明。

function print(num) {
    alert(num) // undefined
    alert(count) // function count() { return 200 }
    var count = 100 // 这个变量声明和函数count声明交换位置结果也不会变化
    function count() {
        return 200;
    }
    alert(count) // 100 
    alert(count()) // 什么都没展示,表明count是一个数值而不是函数

    alert(add) // undefined
    var add = function(x, y) {
         return x + y;
    }
    alert(add) // fuction(x, y) { return x + y; }
}

print()

上面的print函数在执行之前先初始化变量对象VO,VO中声明了参数num,函数count和变量count,但是函数count覆盖了变量count的声明,所以最开始查看count的值是函数声明,接着count=100执行了赋值操作将count从function类型变成了number类型,后面再调用count因为它不是函数无法被调用。特别需要注意的是函数变量和函数声明是不同的,上面的add是一个变量而不是函数声明,最开始的时候它的值就是undefined。

没有块级作用域

前面提到过除了函数有执行上下文,其他的if、switch、for、while语句块都没有执行上下文,这些语句块中声明的变量都会被加入到最近的执行上下变量对象中,这样它们之后的代码块依然能够访问之前的变量,也就说块级作用域并不存在。

 for (var i = 0; i < 10; i++) {

 }
 alert(i) // 10

 if (i == 10) { 
     var hello = "Hello World"
 }

 alert(hello) // Hello World

内存管理

JS中常用的内存垃圾回收主要包括标记清理和引用计数管理,不过引用计数管理会因为循环引用导致对象无法被回收,为了避免这种情况需要在不需要对象的时候将它的引用赋值为空,这样垃圾收集器就能够正确的回收它们了。

猜你喜欢

转载自blog.csdn.net/xingzhong128/article/details/80336997