《高级程序设计》 4 变量、做用域和内存问题

一、基本类型值和引用类型值javascript

javascript变量包含两种不一样数据类型的值:基本类型值和引用类型值。基本类型值指的是简单的数据字段,而引用类型值指那些可能由多个值构成的对象。html

5种基本数据类型:(Undefined,Null,Boolean,Number,String)是按值访问的,由于能够操做保存在变量中的实际的值。前端

引用类型的值是保存在内存中的对象。javascript不能直接访问内存中的位置,也就是不能直接操做对象的内存空间。在操做对象时,其实是在操做对象的引用而不是实际的对象。为此,引用类型的值是按引用访问的。java

注:在不少语言中,字符串以对象的形式来表示,所以被认为是引用类型的。javascript放弃了这一传统。web

1)动态的属性正则表达式

咱们能够为引用类型添加属性,并访问这个新属性,可是不能给基本类型的值添加属性。算法

var person=new Object();
person.name="Nicholas";
alert(person.name);  //"Nicholas"
//not
var name="Nicholas";
name.age=27;
alert(name.age); //undefined
//    只能给引用类型值动态地添加属性,以便未来使用

2)复制变量值编程

除了保存方式不一样,在从一个变量向另外一个变量复制基本类型值和引用类型值时,也存在不一样。浏览器

var num1 = 5;
var num2 = num1;
alert(num2);  //5
//    若是从一个变量向另外一个变量复制基本类型的值,会在变量对象上建立一个新值,而后把该值复制到为新变量分配的位置上。
//    num2中的5与num1中的5是彻底独立的。这两个变量能够参与任何操做而不会相互影响。
var obj1=new Object();
var obj2=obj1;
obj1.name="Nicholas";
alert(obj2.name);  //"Nicholas"
//    当一个变量向另外一个变量复制引用类型的值时,一样也会将存储在变量对象中的值复制一份放到为新的变量分配的空间中。不一样的是,这个值的副本
//    其实是一个指针,而这个指针指向存储在堆中的一个对象。两个变量实际将引用同一个对象。所以,改变其中一个变量,就会影响另外一个变量。

3)传递参数编程语言

javascript中全部函数的参数都是按值传递的。基本类型值的传递如同基本类型变量的复制同样,引用类型值的传递如同引用类型变量的复制同样。

在向参数传递基本类型的值时,被传递的值会被复制给一个局部变量(即命名参数,或者用javascript的概念来讲,就是arguments对象中的一个元素)。

在向参数传递引用类型的值时,会把这个值在内存中的地址复制给一个局部变量,所以这个局部变量的变化会反映在函数的外部。

    //    基本类型
    function addTen(num) {
        num += 10;
        return num;
    }
    var count = 20;
    var result = addTen(count);
    alert(count); //20
    alert(result); //30
    //    引用类型
    function setName(obj) {
        obj.name = "Nicholas";
    }
    var person = new Object();
    person.name = "Greg";
    alert(person.name); //"Greg"
    setName(person);
    alert(person.name); //"Nicholas"
//    在函数内部,obj和person引用的是同一个对象。
//    不少开发人员错误的认为:在局部做用域中修改的对象会在全局做用域中反映出来,就说明参数是按引用传递的,
// 为了证实对象是按值传递的,对比:
    function setName1(obj){
        obj.name="Nicholas";
        obj=new Object();
        obj.name="Greg";
    }
    var person1=new Object();
    setName1(person1);
    alert(person1.name); //Nicholas
//    当在函数内部重写obj时,这个变量引用的就是一个局部对象了。而这个局部对象会在函数执行完毕后当即被销毁。

注意:能够把javascript函数的参数想象成局部变量。

4)检测类型

当要检测一个变量是否是基本数据类型时,使用typeof操做符是最佳的工具。

    //    typeof操做符
//    var s = "Nicholas";
//    var b = true;
//    var i = 22;
//    var u;
//    var n = null;
//    var o = new Object();
//    alert(typeof s); //string
//    alert(typeof b); //boolean
//    alert(typeof i); //number
//    alert(typeof u); //undefined
//    alert(typeof n); //object
//    alert(typeof o); //object
    //    当咱们并非想知道某个值是对象,而是想知道它是什么类型的对象。使用:
    //    instanceof 操做符
    var person = new Object();
    var array = new Array();
    var pattern = new RegExp();
    alert(person instanceof Object); //true
    alert(array instanceof Array); //true
    alert(array instanceof Object); //true .全部引用类型的值都是Object的实例,所以在检测一个引用类型值和Object构造函数时,
//    instanceof操做符始终会返还true
    alert(pattern instanceof RegExp); //true

使用typeof操做符检测函数时,该操做符会返回“function”。

在ie和firefox中,对正则表达式应用typeof会返回“object“

二、执行环境及做用域

执行环境定义了变量或函数有权访问的其余数据,决定了了它们各自的行为。

全局执行环境是最外围的一个执行环境。在web浏览器中,全局执行环境被认为是window对象,所以全部全局变量和函数都是做为window对象的属性和方法建立的。某个执行环境中的全部代码执行完毕后,该环境被销毁,保持在其中的全部变量和函数定义也随之销毁(全局执行环境直到应用程序退出——例如关闭网页或浏览器时才会被销毁)。

当代码在一个环境中执行时,会建立变量对象的一个做用域链。做用域链的用途,是保证对执行环境有权访问的全部变量和函数的有序访问。做用域链的前端,始终都是当前执行的代码所在环境的变量对象。若是这个环境是函数,则将其活动对象做为变量的对象。活动对象在最开始只包含一个变量,即arguments对象(这个对象在全局环境中是不存在的)。做用域链中的下一个变量对象来自包含(外部)环境,而再下一个变量对象则来自下一个包含环境。这样,一直延续到全局执行环境;全局执行环境的变量对象始终都是做用域链中的最后一个对象。

标识符的解析是沿着做用域链一级一级地搜索标识符的过程。搜索过程始终从做用域链的前端开始,而后逐级地向后回溯,直到找到标识符为止(若是找不到标识符,一般会致使错误发生),示例:

//    var color = "blue";
//    function changeColor() {
//        if (color == "blue") {
//            color = "red";
//        } else {
//            color = "blue";
//        }
//    }
//    changeColor();
//    alert("Color is now " + color);  //"Color is now red"
//    函数changeColor()的做用域链包含链各个对象:它本身的变量对象(其中定义着arguments对象)和全局环境的变量对象。
// 能够在函数内部访问变量color,就是由于能够在这个做用域链中找到它。
//    此外,在局部做用域中定义的变量能够在局部环境中与全局变量互换使用,以下:
    var color="blue";
    function changeColor(){
        var anotherColor="red";
        function swapColors(){
            var tempColor=anotherColor;
            anotherColor=color;
            color=tempColor;
//            这里能够访问color,anotherColor,tempColor
        }
//        这里能够访问color和anotherColor,但不能访问tempColor
        swapColors();
        alert(anotherColor); //"blue"
    }
//    这里只能访问color
    changeColor();
    alert("Color is now "+color); //"Color is now red"

注意:函数参数也被看成变量来对待,所以其访问规则与其执行环境中的其它变量相同

1)延长做用域

虽然执行环境的类型总共只有两种——全局和局部(函数),但仍是有其它办法来延长做用域链。这么说是由于有些语句能够在做用域链的前端临时增长一个变量对象,该变量对象会在代码执行后被移除。在两种状况下会发生这种现象,具体来讲,就是当执行流进入下列任何一个语句时,做用域链就会获得加长:

  • try-catch语句的catch块;
  • with语句。
    function buildUrl() {
        var qs = "?debug=true";
        with (location) {
            var url = href + qs;
        }
        return url;
    }
    alert(buildUrl()); //"http://localhost:63342/MYIDEA/index.html?debug=true"

2)没有块级做用域

在其余类C的语言中,有花括号封闭的代码块都有本身的做用域(若是用javascript的话来说,就是它们本身的执行环境)。可是,javascript没有块级做用域。

    if(true){
        var color="blue";
    }
    alert(color); //"blue"
//    if语句中的变量声明会将变量添加到当前执行环境(在这里是全局环境)中。
    for(var i=0;i<10;i++){
//        doSomething();
    }
    alert(i);  //10
//    for语句建立的变量i即便在for循环执行结束后,也依旧会存在于循环外部的执行环境中。

声明变量

注意:在函数内部,若是初始化变量没有使用var声明,该变量会自动被添加到全局环境中。建议在初始化变量以前,必定要先声明。

查询标识符

当在某个环境中为了读取或写入而引用一个标识符时,必须经过搜索来肯定该标识符实际表明什么。搜索过程从做用域链的前端开始,向上逐级查询与给定名字匹配的标识符。若是在局部环境中找到该标识符,搜索过程中止,变量就绪。若是在局部环境中没有找到该变量名,则继续沿做用域链向上搜索。搜索过程将一直追溯到全局环境的变量对象。若是在全局环境中也没有找到这个标识符,则意味着该变量还没有声明。

若是局部环境中存在着同名标识符,就不会使用位于父环境中的标识符。

//    例1
//    var color = "blue";
//    function getColor() {
//        return color;
//    }
//    alert(getColor()); //"blue"
//    例2
    var color="blue";
    function getColor(){
        var color="red";
        return color;
    }
    alert(getColor()); //"red"
//    任何位于局部变量color的声明以后的代码,若是不使用window.color都没法访问全局变量color。

变量查询也不是没有代价的。很明显,访问局部变量要比访问全局变量更快,由于不用向上搜索做用域链。javascript引擎在优化标识符查询方面作得不错,所以这个差异在未来恐怕就能够忽略不计了。

三、垃圾收集

 javascript具备自动垃圾收集机制,也就是说,执行环境会负责管理代码执行过程当中使用的内存。所需内存的分配以及无用内存的回收彻底实现了自动管理。

原理:找出那些再也不继续使用的变量,而后释放其占用的内存。为此,垃圾收集器会按照固定的时间间隔(或代码执行中预约的收集时间)周期性地执行这一操做。

确保占用最少的内存可让页面得到更好的性能。而优化内存占用的最佳方式,就是为执行中的代码只保存必要的数据。

 一旦数据再也不有用,最好经过将其值设置为null来释放其引用——这个作法叫作解除引用。这一作法适用于大多数全局变量和全局对象的属性。局部变量会在它们离开执行环境时自动被解除引用。

不过,解除一个值的引用并不意味着自动回收该值所占用的内存。解除引用的真正做用是让值脱离执行环境,以便垃圾收集器下次运行时将其回收。

小结:

javascript变量能够用来保存两种类型的值:基本类型值和引用类型值。基本类型的值源自如下5中基本数据类型:Undefined,Null,Boolean,Number和String。基本类型值和引用类型值具备如下特色:

  • 基本类型值在内存中占据固定大小的空间,所以被保存在栈内存中;
  • 从一个变量向另外一个变量复制基本类型的值,会建立这个值的一个副本;
  • 引用类型的值是对象,保存在堆内存中;
  • 包含引用类型值的变量实际上包含的并非对象自己,而是一个指向该对象的指针;
  • 从一个变量向另外一个变量复制引用类型的值,复制的实际上是指针,所以两个变量最终都指向同一个对象。
  • 肯定一个值是哪一种基本类型可使用typeof操做符,而肯定一个值是哪一种引用类型可使用instanceof操做符。

全部变量(包括基本类型和引用类型)都存在于一个执行环境中(也称为做用域)当中,这个执行环境决定了变量的生命周期,以及哪一部分代码能够访问其中的变量。如下是关于执行环境的几点总结:

  • 执行环境有全局执行环境(也称为全局环境)和函数执行环境之分;
  • 每次进入一个新执行环境,都会建立一个用于搜索变量和函数的做用域链;
  • 函数的局部环境不只有权访问函数做用域中的变量,并且有权访问其包含(父)环境,乃至全局环境;
  • 全局环境只能访问在全局环境中定义的变量和函数,而不能直接访问局部环境中的任何数据;
  • 变量的执行环境有助于肯定应该什么时候释放内存。

javascript是一门具备自动垃圾收集机制的编程语言,开发人员没必要关心内存分配和回收问题。以对javascript的垃圾收集例程做以下总结:

  • 离开做用域的值将被自动标记为能够回收,所以将在垃圾收集期间被删除;
  • ”标记清除“是目前主流的垃圾收集算法,这种算法的思想是给当前不适用的值加上标记,而后再回收其内存;
  • 另外一种垃圾收集算法是”引用计数“,这种算法的思想是跟踪记录全部值被引用的次数。javascript引擎目前都再也不使用这种算法;但在ie中访问非原生javascript对象(如DOM元素)时,这种算法仍然可能会致使问题。
  • 当代码中存在循环引用现象时,”引用计数“算法就会致使问题。
  • 解除变量的引用不只有助于消除循环引用现象,并且对垃圾收集也有好处。为了确保有效地回收内存,应该及时解除再也不使用的全局对象、全局对象属性以及循环引用变量的引用。
相关文章
相关标签/搜索