Javascript学习笔记-变量、作用域和内存问题
1、基本类型和引用类型的值
ECMAScript 变量可能包含两种不同数据类型的值:基本类型值和引用类型值。
基本类型值指的是简单的数据段,而引用类型值指那些可能由多个值构成的对象。
基本数据类型:Undefined、Null、Boolean、Number 和String。这5 种基本数据类型是按值访问的。
引用类型的值是保存在内存中的对象。与其他语言不同,JavaScript 不允许直接访问内存中的位置,也就是说不能直接操作对象的内存空间。
(1)动态的属性
var person = new Object(); person.name = "Nicholas"; alert(person.name); //"Nicholas"
以上代码创建了一个对象并将其保存在了变量person 中。然后,为该对象添加了一个名为name 的属性,并将字符串值"Nicholas"赋给了这个属性。
然后通过alert()函数访问了这个新属性。如果对象不被销毁或者这个属性不被删除,则这个属性将一直存在。
只能给引用类型值动态地添加属性,比如一下例子:
var name = "Nicholas"; name.age = 27; alert(name.age); //undefined
为字符串name 定义了一个名为age 的属性,并为该属性赋值27。但在下一行访问这个属性时,发现该属性不见了。
(2)复制变量值
如果从一个变量向另一个变量复制基本类型的值,会在变量对象上创建一个新值,然后把该值复制到为新变量分配的位置上。
如果从一个变量向另一个变量复制引用类型的值时,同样也会将存储在变量对象中的值复制一份放到为新变量分配的空间中。不同的是,这个值的副本实际上是一个指针,而这个指针指向存储在堆中的一个对象。复制操作结束后,两个变量实际上将引用同一个对象。因此,改变其中一个变量,就会影响另一个变量。
var obj1 = new Object(); var obj2 = obj1; obj1.name = "Nicholas"; alert(obj2.name); //"Nicholas"
(3)参数传递
ECMAScript 中所有函数的参数都是按值传递的。
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(); setName(person); alert(person.name); //"Nicholas"
function setName(obj) { obj.name = "Nicholas"; obj = new Object(); obj.name = "Greg"; } var person = new Object(); setName(person); alert(person.name); //"Nicholas"
(4)检测类型
typeof 操作符是确定一个变量是字符串、数值、布尔值,还是undefined 的最佳工具。
var s = "Nicholas"; var b = true; var i = 22; var u; var n = null; var o = new Object(); alert(typeof s); //string alert(typeof i); //number alert(typeof b); //boolean alert(typeof u); //undefined alert(typeof n); //object alert(typeof o); //object
在检测引用类型的值时,这个操作符的用处不大。
想知道它是什么类型的对象,使用instanceof 操作符。
result = variable instanceof constructor
alert(person instanceof Object); // 变量person 是Object 吗? alert(colors instanceof Array); // 变量colors 是Array 吗? alert(pattern instanceof RegExp); // 变量pattern 是RegExp 吗?
所有引用类型的值都是Object 的实例。因此,在检测一个引用类型值和Object 构造函数时,instanceof 操作符始终会返回true。
如果使用instanceof 操作符检测基本类型的值,则该操作符始终会返回false,因为基本类型不是对象。
2、执行环境及作用域
执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。每个执行环境都有一个与之关联的变量对象(variable object),环境中定义的所有变量和函数都保存在这个对象中。
全局执行环境是最外围的一个执行环境。
全局执行环境直到应用程序退出——例如关闭网页或浏览器——时才会被销毁。
当代码在一个环境中执行时,会创建变量对象的一个作用域链(scope chain)。
作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问。
var color = "blue"; function changeColor(){ var anotherColor = "red"; function swapColors(){ var tempColor = anotherColor; anotherColor = color; color = tempColor; // 这里可以访问color、anotherColor 和tempColor } // 这里可以访问color 和anotherColor,但不能访问tempColor swapColors(); } // 这里只能访问color changeColor();
(1)延长作用域链
执行环境的类型总共只有两种——全局和局部(函数),但还是有其他办法来延长作用域链。
就是当执行流进入下列任何一个语句时,作用域链就会得到加长:
- try-catch 语句的catch 块;
- with 语句。
(2)没有块级作用域
if (true) { var color = "blue"; } alert(color); //"blue"
如果是在C、C++或Java 中,color 会在if 语句执行完毕后被销毁。但在JavaScript 中,if 语句中的变量声明会将变量添加到当前的执行环境(在这里是全局环境)中。
for (var i=0; i < 10; i++){ doSomething(i); } alert(i); //10
1)声明变量
使用var 声明的变量会自动被添加到最接近的环境中。
2)查询标识符
当在某个环境中为了读取或写入而引用一个标识符时,必须通过搜索来确定该标识符实际代表什么。搜索过程从作用域链的前端开始,向上逐级查询与给定名字匹配的标识符。
3、垃圾收集
JavaScript 具有自动垃圾收集机制,执行环境会负责管理代码执行过程中使用的内存。
(1)标记清除
JavaScript 中最常用的垃圾收集方式是标记清除(mark-and-sweep)。
(2)引用计数
引用计数的含义是跟踪记录每个值被引用的次数。
(3)性能问题
垃圾收集器是周期性运行的,而且如果为变量分配的内存数量很可观,那么回收工作量也是相当大的。
(4)管理内存
确保占用最少的内存可以让页面获得更好的性能。而优化内存占用的最佳方式,就是为执行中的代码只保存必要的数据。一旦数据不再有用,最好通过将其值设置为null 来释放其引用——这个做法叫做解除引用(dereferencing)。