重读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;
posted @ 2020-03-28 14:29  浮华而已-  阅读(619)  评论(2编辑  收藏  举报