JavaScript之变量、作用域和内存问题

变量作用域和内存问题


变量

JavaScript中的变量分为:

(1)基本类型值:简单的数据段。如5种基本类型值:Undefined\Null\Number\Boolean\String。基本类型是按值访问的,可以直接操作保存在变量中的实际的值

(2)引用类型值:指可能由多个值构成的对象。引用类型的值是按引用访问的。


变量的复制

基本类型:如果从一个变量向另一个变量复制基本类型的值,会在变量对象上创建一个新值,然后把该值复制到为新变量分配的位置上。即原来的变量值和新的变量值是完全独立的。

引用类型:当从一个变量向另一个变量复制引用类型的值时,会将存储在变量对象中的值复制一份放到为新变量分配的空间中,不同的是,这个值的实际上是一个指针,而这个指针指向存储在堆中的一个对象。复制操作结束后,两个变量实际上将会引用同一个对象


传递参数

所有函数的参数都是按值传递的。


检测类型

typeof操作符:确定一个变量是字符串、数值、布尔值还是undefined。如果变量的值是一个对象或null,则typeof操作符会返回object。typeof操作符用于检测基本数据类型

语法: typeof 变量名

instanceof操作符:一般用于检测引用类型。

语法:变量名  instanceof  引用类型

<!DOCTYPE html>
<html>
    <head>
        <title>typeof和instanceof操作符</title>
    </head>
    <body>
        <script>

            var num = 5;
            var str = "123";
            var boo = true;
            var unde;
            var no = null;
            var obj1 = new Object();

            console.log(typeof num);
            console.log(typeof str);
            console.log(typeof boo);
            console.log(typeof unde);
            console.log(typeof no);
            console.log(typeof obj1);
        

            var arr = [1,2,3,4];
            console.log(arr instanceof Array);
            console.log(obj1 instanceof Object);
        </script>
    </body>
</html>

结果



执行环境及作用域

执行环境(execution context):定义了变量或函数有权访问的其他数据,决定了它们各自的行为

每个执行环境都有一个与之关联的变量对象(variable object),环境中定义的所有变量和函数都保存在这个对象中。

执行环境分为全局执行环境和局部执行环境

全局执行环境是最外围的一个执行环境。web浏览器中,全局执行环境是window对象。

局部执行环境是指函数{}中的执行环境。

某个执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁。全局执行环境直到应用程序退出时才会被销毁。

每个函数都有自己的执行环境,当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境。


作用域

作用域就是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期。

JavaScript中作用域只有两种,一种是全局作用域,另一种是局部作用域(函数作用域)【没有块级作用域】

全局作用域

在代码中任何地方都能访问到的对象拥有全局作用域

(1)最外层函数和在最外层函数外部定义的变量拥有全局作用域

<!DOCTYPE html>
<html>
    <head>
        <title>typeof和instanceof操作符</title>
    </head>
    <body>
        <script>
            var out = "外面的";

            function doThing(){
                var inner = "里面的";

                function innerFun(){
                    console.log(inner);
                }

                innerFun();
            }

            console.log(out);
            // console.log(inner);
            doThing();
            innerFun();
            
        </script>
    </body>
</html>
结果



将21行注释掉



没有块级作用域

在C中,由花括号封闭的代码块都有自己的作用域,但是在JavaScript中,是没有块级作用域的。以for语句为例。

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

alert(i)
对于上面的代码,在C中,执行到最后一句会出错,因为没有定义i这个变量,但是在javascript中,最后一句的结果为10,i变量依然可以存在于循环外部的执行环境中,即在JavaScript中,实没有块级作用域的。


作用域链(scope chain)

作用:保证对执行环境有权访问的所有变量和函数,能够有序访问执行环境。

JavaScript中,函数是对象,函数拥有一个仅由JavaScript引擎可访问的内部属性----[[Scope]],该内部属性包含了创建该函数的作用域中所包含的对象的集合,这个集合被称为函数的作用域链,它决定了函数能够访问哪些数据。

当一个函数创建后,它的作用域链会被创建此函数的作用域中可访问的数据对象填充。

如:

function add(num1,num2)
{
    var sum = num1 + num2;
    return sum;
}
在函数add创建时,它的作用域链中会填入一个全局对象,该全局对象包含了所有全局变量,如下图所示(图中只标列举部分全局变量)



函数的作用域链将会在执行时用到,如:

var total = add(5,10);


执行此函数时,会创建一个称为“执行上下文”(Execution Context)的内部对象,该对象定义了函数执行时的环境。而与此同时,每个执行上下文也有自己的作用域链,用于标识符解析,当运行上下文被创建时,它的作用域链会被初始化为当前运行函数的作用域链。随后,JavaScript引擎会创建一个Active Object(活动对象)【就是add的局部环境】,这个活动对象里包含了函数运行期的所有局部变量、参数以及this等变量。


在函数执行过程中,每遇到一个变量,都会经历一次标识符解析过程以决定从哪里获取和存储数据,该过程从作用域链头部,也就是从活动对象开始搜索,查找同名的标识符(就是变量名),如果找到就使用这个标识符对应的变量,如果没找到继续搜索作用域链中的下一个对象,如果搜索完所有对象都未找到,则认为该标识符未定义。这也就是作用域链如何保证有序地访问执行环境中的变量。

【下面偷个懒,直接截图啦】





延长作用域链

在JavaScript中,有些语句可以在作用域链的前端临时添加一个变量对象,该变量对象会在代码执行后被移除。

当执行流进入下列任何一个语句时,作用域链就会得到加长:

(1)try-catch语句的catch块。创建一个新的变量对象,其中包含的是被抛出的错误对象的声明,将该变量对象添加到作用域链的前端。

(2)with语句。会将指定的对象添加到作用域链的前端。

function initUI(){  
  with (document){ //avoid!  
  var bd = body,  
  links = getElementsByTagName("a"),  
  i= 0,  
  len = links.length;  
  while(i < len){  
  update(links[i++]);  
}  
  getElementById("go-btn").onclick = function(){  
  start();  
};  
  bd.className = "active";  
}  

当执行上面的initUI函数的时候,JavaScript会动态的创建一个with语句对应的作用域放到执行上下文作用域链的最前端,通过下图可以形象的描述上述过程,下图红色标注的区域就显示了with语句产生的作用域。



垃圾收集

JavaScript具有自动垃圾收集机制。执行环境会负责管理代码执行过程中使用的内存。

自动垃圾收集机制原理:找到那些不再继续使用的变量,然后释放其占用的内存。垃圾回收器会按照固定的时间间隔(或代码执行中预定的收集时间),周期性地执行这一操作。

函数中局部变量的生命周期:局部变量只在函数执行的过程中存在。

为了判断变量是否还有存在的必要,为不再有用的变量打上标记,标记策略分为两种:

(1)标记清除(mark-and-sweep)

垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记,然后收集器去掉环境中的变量以及被环境中的变量引用的变量的标记。而在次之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后,垃圾收集器完成内存清除工作,销毁那些带标记的值并回收他们所占用的内存空间。

(2)引用计数(reference counting)

引用计数的含义是跟踪记录每个值被引用的次数。当声明一个变量并将一个引用类型值复制给该变量时,则这个值的引用次数就是1,。如果同一个值又被赋值给另一变量,则该值的引用次数加1.相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数减1.当这个值的引用次数变为0时,则说明没有办法再访问这个值了,因为就可以将其占用的内存空间回收回来。

这种策略存在的问题是:循环引用。


参考资源

JavaScript作用域链解析

JavaScript 开发进阶:理解 JavaScript 作用域和作用域链


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值