zoukankan      html  css  js  c++  java
  • 重读JS(四)数据类型、作用域和内存问题

    本章内容

    • 理解基本类型和引用类型的值

    • 理解执行环境

    • 理解垃圾收集

    JavaScript的变量与其他语言的变量有很大区别。JavaScript变量松散类型的本质,决定了它只是特定时间用于保存特定值的一个名字而已。由于不存在定义某个变量必须要保存何种数据类型值的规则,变量的值及其数据类型可以在脚本的声明周期内改变。尽管从某种角度看,这可能是一个既有趣又强大,同时又容易出问题的特性,但JavaScript变量实际的复杂程度还远不如此。

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

    基本类型值指的是简单的数据段,而引用类型值值那些可能由多个值构成的对象。

    5种基本数据类型:undefinednullbooleannumberstring

    引用数据类型:Object(ArrayDateRegExpFunction)

    补充:
    1.为了和下一章的基本包装类型区分(new Blooean()、new Number()、new String()),这里的基本数据类型一律小写,看到很多地方大家都大写,这个问题困惑我许久,直到看到基本包装类型才恍然大悟。
    2.Object是一个基础类型,其他所有类型都从Object继承了基本的行为。Array,Date,RegExp,Function会在下一章内容详细介绍。

    区别

    基本数据类型值在内存中占据固定大小的空间,因此保存在栈内存中,且不能为其添加属性值。

    引用数据类型保存在堆内存中,然后在栈内存中保存了一个对堆内存中实际对象的引用,即数据在堆内存中的地址。

    JS对引用数据类型的操作都是操作对象的引用而不是实际的对象,如果obj1拷贝了obj2,那么这两个引用数据类型就指向了同一个堆内存对象,具体操作是obj1将栈内存的引用地址复制了一份给obj2,因而它们共同指向了一个堆内存对象;

    为什么基本数据类型保存在栈中,而引用数据类型保存在堆中?1)堆比栈大,栈比堆速度快;2)基本数据类型比较稳定,而且相对来说占用的内存小;3)引用数据类型大小是动态的,而且是无限的,引用值的大小会改变,不能把它放在栈中,否则会降低变量查找的速度,因此放在变量栈空间的值是该对象存储在堆中的地址,地址的大小是固定的,所以把它存储在栈中对变量性能无任何负面影响;4)堆内存是无序存储,可以根据引用直接获取;

    按引用访问:js不允许直接访问保存在堆内存中的对象,所以在访问一个对象时,首先得到的是这个对象在堆内存中的地址,然后再按照这个地址去获得这个对象中的值;

    传递参数

    ECMAScript中所有函数的参数都是按值来传递的,

    对于基本数据类型值,只是把变量里的值传递给参数,之后参数和这个变量互不影响,相当于复制。

    对于引用值,对象变量里面的值是这个对象在堆内存中的内存地址,因此它传递的值也就是这个内存地址,这也就是为什么函数内部对这个参数的修改会体现在外部的原因,因为它们都指向同一个对象;

    function setName(obj){
        obj.name = "Nicholas"
    }
    
    var person = new Object();
    setName(person);
    alert(person.name);  //"Nicholas"
    
    function setName(obj) {
        obj.name = "Nicholas";
        var newobj = new Object();    
        obj = newobj;   //指针指向改变了
        obj.name = "Adagio";
    }
    
    var person = new Object();
    setName(person);
    alert(person.name); //"Nicholas"
    

    检测类型

    typeof

    基本数据类型使用typeof可以返回其基本数据类型,但是null类型会返回object,因为null值表示一个空对象指针;

    instanceof

    虽然在检测基本数据类型时typeof是非常得力的助手,但在检测引用类型的值时,用处不大。通常我们更想知道的是这个对象是什么类型的。

    根据规定,所有引用类型的值都是Object的实例。因此,在检测一个引用类型值和Object构造函数时,instanceof操作符始终会返回true。

    alert(person instanceof Object);
    alert(colors instanceof Array);
    alert(pattern instanceof RefExp);
    alert(Array instanceof Object);  //true
    

    使用typeof操作符检测函数时,返回'function'。在Safari5及之前版本和Chrome7及之前版本中使用typeof检测正则表达式时,也返回'function'。ECMA-262规定任何在内部实现[[Call]]方法的对象都应该应用typeof操作符返回'function'。上述浏览器中的正则使用了。在IE和Firefox中正则表达式使用typeof返回'object'

    二、执行环境及作用域

    执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。每个执行环境都有一个与之关联的变量对象(variable object),环境中定义的所有变量和函数都保存在这个对象中。虽然我们编写的代码无法访问这个对象,但解析器在处理时会在后台使用它。

    全局和局部执行环境

    在Web浏览器中,是window对象,因此所有全局变量和函数都是作为window对象的属性和方法创建的。某个执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁。(全局执行环境直到应用程序退出-例如关闭网页或浏览器时被销毁)。

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

    作用域链

    当代码在一个环境中执行时,会创建变量对象的一个作用域链(scope chain),保证对执行环境有权访问的所有变量和函数的有序访问。

    作用域链的前端,始终都是当前执行的代码所在环境的变量对象。如果这个环境是函数,则将其活动对象作为变量对象。活动对象在最开始时质保函一个变量,即arguments对象(这个对象在全局环境中是不存在的)。作用域链的下一个变量对象来自包含(外部)环境,而在下一个对象则来自下一个包含环境,这样,一直延续到全局执行环境(作用域链中的最后一个对象)标识符解析是沿着作用域链一级一级地搜索标识符的过程,过程始终从作用域链的前端开始,然后逐级地向后回溯,之道找到标识符为止。例:

    var color = "blue";
    
    function changeColor(){
        var anotherColor = "red";
    
        function swapColors(){
            var tempColor = anotherColor;
            anotherColor = tempColor;
            //这里可以访问tempColor、anotherColor和tempColor
        }
    
        // 这里可以访问color和anotherColor,但不能访问tempColor
        swapColors();
    }
    
    // 这里只能访问color
    changeColor();
    

    作用域链(swapColors()->changeColor()->window)

    延长作用域链

    try-catch语句的catch块

    with语句

    这两个语句都会在作用域链的前端添加一个变量对象。对于with来说,会将指定的对象添加到作用域链中。对于catch语句来说,会创建一个新的变量对象,其中包含的是被刨除的错误对象的声明。例子:

    function buildUrl(){
        var qs = "?debug=true";
        with(location){
            var url = href + qs;
        }
        return url;
    }
    

    在此,with语句接受的是loaction对象,因此其变量对象中就包含了location对象的所有属性和方法,而这个变量对象就被添加到了作用域链的前端。当在with语句中引用变量href时(实际引用的是location.href),可以在当前执行环境的变量对象中找到。

    至于with语句内部定义的url变量,是函数执行环境的一部分,自然可以作为函数的值被返回。

    ⭐没有块级作用域

    在其他类C的语言中,由花括号封闭的代码块都有自己的作用域,但JavaScript不一定。

    if语句中的变量声明会将变量添加到当前的执行环境。

    for语句创建的变量i即使在for循环执行结束后,也依旧会存在于循环外部的执行环境中。

    1.声明变量

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

    2.查询标识符

    访问局部变量(color)和访问全局变量(window.color)更快

    三、圾收集

    JavaScript具有自动垃圾收集机制,执行环境会负责管理代码执行过程中使用的内存。所需内存的分配和无用内存的回收实现了自动管理。

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

    垃圾收集策略

    • 标记清除

    给当前不使用的值加上标记,然后再回收其内存。

    • 引用计数(不再用)

    跟踪记录所有值被引用的次熟。

    性能问题

    随着IE7的发布,其JavaScript引擎的垃圾收集例程改变工作方式:触发垃圾收集的变量分配、字面量和(或)数组元素的临界值被动态调整为动态修正。如果垃圾收集例程回收的内存分配量低于15%,则临界值加倍。如果回收了85%的内存分配量,则将临界值重置回默认值。

    在有的浏览器中可以触发垃圾收集过程,比如IE:window.CollectGarbage()方法,Opera:window.opera.collect()。但不建议这样做

    管理内存

    虽然存在垃圾收集机制,但系统分配给Web浏览器的可用内存数量通常比桌面应用程序少,目的是防止运行JavaScript的网页耗尽全部系统内存而导致系统奔溃。因此,确保占用最少的内存可以让页面获得更好的性能。最佳方式就是为执行中的代码保存必要的数据,一旦不再用,最好通过将其值设为null来释放内存(解除引用)。

    这一做法适用于大多数全局变量和全局对象的属性,局部变量会在它们离开执行环境时自动解除引用。

    解除一个值的引用作用是让值脱离执行环境,以便垃圾收集器下次运行时将其回收。

    var globalPerson = creatPerson("Nicholas")
    
    // ....
    // 手动解除globalPerson的引用
    globalPerson = null;
    
  • 相关阅读:
    asp 向另一个页面传递数组
    TSQL Program Rule and Tips 规则与优化
    虚函数 纯虚函数 抽象类
    static (c#)
    简单游标
    抽象方法 抽象类 (abstract)
    清理电脑
    泛型学习
    继承(对象生命周期) + 覆盖[new](索引函数) + 重载[virtual/override]
    #干货向#jQuery性能优化指南
  • 原文地址:https://www.cnblogs.com/L-xmin/p/12586544.html
Copyright © 2011-2022 走看看