《JavaScript高级程序设计》Chapter4笔记
1. 关于引用类型值的访问
两种数据类型的值:基本类型值和引用类型值。
引用类型的值是保存在内存中的对象,JavaScript不允许直接访问内存中的位置,即不能直接操作对象的内存空间。在操作对象时,实际上是操作对象的引用而非实际的对象。故引用类型的值是按引用访问的。
2. 复制基本类型值和引用类型值的不同机制
-
复制基本类型值:重新生成一个新值,将原值复制到位新变量分配的位置上。两个变量完全独立,新变量只是原值的一个副本,它们参与任何操作互相不影响。
-
复制引用类型值:值的副本实际是一个指针,这个指针指向存储在堆中的一个对象。复制结束后,两个变量将引用同一个对象。改变其中一个变量,就会改变另一个。
var obj1=new Object(); var obj2=obj1; obj1.name="Nicholas"; console.log(obj2.name);//"Nicholas"
3. 函数参数的按值传递
访问变量有按值和按引用两种方式,函数参数只能按值传递。
-
传递基本类型值:被传递的值会复制给一个局部变量;
-
传递引用类型值:该值在内存中的地址(指针)会复制给一个局部变量。难
以下例子说明即使引用类型的值是按值传递给参数的(传递的是地址),该参数也会按引用访问同一个对象。
function setName(obj) { obj.name="Nicholas"; } var person=new Object(); setName(person); console.log(person.name);//"Nicholas"
以下例子说明引用类型的值确实是按值传递的。因为如果person按引用传递,那么其就会自动被修改为指向name属性为“Greg”的新对象。但person.name不变,可见即使在函数内部修改了参数的值,原始的引用扔未改变。
其实,函数内部重写obj时,这个变量引用的就是一个局部变量对象了,它会在函数执行完后自行销毁。
function setName(obj) { obj.name="Nicholas"; obj=new Object(); obj.name="Greg"; } var person=new Object(); setName(person); console.log(person.name);//"Nicholas"
4. typeof和instanceof检测类型比较
-
typeof: 检测变量是哪种基本数据类型
-
instanceof: 检测变量是不是某一种对象。引用类型的值检测Object都是true。基本类型的值检测Object为false,因为基本类型不是对象。
//使用typeof var s="Nicholas"; var b=true; var i=22; var u; var n=null; var o=new Object(); var reg=new RegExp(); console.log(typeof s);//string console.log(typeof b);//boolean console.log(typeof i);//number console.log(typeof u);//undefined console.log(typeof n);//object console.log(typeof o);//object console.log(typeof reg);//object,低版本Chrome和Safari返回function //使用instanceof var o=new Object(); var arr=new Array(); var reg=new RegExp(); var i=22; console.log(o instanceof Object);//true console.log(arr instanceof Object);//true console.log(arr instanceof Array);//true console.log(reg instanceof Object);//true console.log(reg instanceof RegExp);//true console.log(i instanceof Object);//false
5.有关执行环境和作用域的几个概念
(1)变量对象
- 每个执行环境都有一个对应的变量对象,该环境中定义的所有变量和函数都保存在这个对象中。
- 全局执行环境对应window对象,故所有全局变量和函数都是window对象的属性和方法。
- 某执行环境中的所有代码执行完后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁。全局执行环境直到关闭网页或浏览器才会被销毁。
(2)环境栈机制
当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。函数执行完后,栈将其弹出,控制权又交还给之前的执行环境。
(3)作用域链
- 其用途是保证执行环境可以有序访问变量和函数。
- 作用域链的前端是当前执行环境的变量对象。下一个变量对象来自包含环境。再下一个变量对象来自下一个包含环境,一直延续到全局执行环境的变量对象。
- 内部环境可以通过作用域链访问所有的外部环境,外部环境不能访问内部环境的任何变量和函数。
- 函数参数也相对于函数局部变量,访问规则同其他局部变量。
eg1:
var color1="blue";
function changeColor1() {
var color2="red";
function changeColor2() {
var color3=color2;
color2=color1;
color1=color3;
console.log(color1+" "+color2+" "+color3);//red blue red
//这里可访问color1、color2、color3
}
changeColor2();
console.log(color1+" "+color2);//blue red
//这里可访问color2、color3
}
console.log(color1);//blue
//这里只能访问color1
changeColor1();
注意:一般不在函数声明内部再写函数声明。
eg2:
var color="blue";
function getColor() {
var color="red";
return color;
}
console.log(getColor());//red
6. JavaScript没有块级作用域
注意和C++不同。
7. 关于var声明
- var声明的变量会被添加到最接近的环境中
- 没有var则被添加到全局环境成为全局变量,不推荐不带var的声明。
8.自动垃圾收集机制
垃圾收集机制原理:找出不再继续使用的变量,然后释放其所占用的内存。垃圾收集器会按照固定的时间间隔周期性执行这一操作。
局部变量只在函数执行的过程中存在,这个过程中会为局部变量在栈(或堆)内存上分配相应的空间,以便存储它们的值。直至函数执行结束,局部变量就没有存在的必要了可以释放它们的内存。垃圾收集器必须跟踪哪个变量有用哪个变量没用。
方式一:标记清除
- 垃圾收集器在运行的时候给存储在内存中的所有变量都加上标记;
- 然后,去掉环境中的变量及被环境中的变量引用的标记;
- 之后再被加上标记的变量就是准备删除的变量;
- 最后,垃圾收集器完成内存清除工作,销毁那些带标记的值并收回它们所占的内存空间。
方式二:引用计数
引用计数即跟踪每个值被引用的次数。
- 声明了一个变量并将一个引用类型的值赋给该变量时,该值引用次数就是1;
- 若该值又被赋给了另外一个值,则该值引用次数加1,为2;
- 相反,如果包含对这个值引用的变量又取得了另一个值,则这个值引用次数减1;
- 当值的引用次数变为0时,说明没有办法再访问这个值了,故可将其所占的内存空间收回来;
- 当垃圾收集器再次运行时,它就会释放那些引用次数为0的值所占用的内存。
方式二的问题:循环引用
当两个对象互相引用的时候,它们的引用计数永远也不会为0,造成问题。
浏览器采用方式
目前主流浏览器都是标记清除式的垃圾回收策略。只不过时间间隔不同。
IE有一部分对象不是原生JavaScript对象,其BOM和DOM中的对象就是C++编写的COM(组件对象模型),COM的垃圾回收机制是引用计数。故只要在IE中涉及COM对象就存在循环引用问题。 不过,IE9把BOM和DOM都换成了真正的JavaScript对象,故避免了该问题。
9. 管理内存:提升性能的方式之一
现实:分配给Web浏览器的可用内存数量通常比分配给桌面应用程序的少。这是出于安全方面的考虑,方式运行JavaScript网页耗尽全部系统内存而导致系统崩溃。
故确保占用最少的内存可以让页面获得更好的性能。优化内存占用的最佳方式:为执行中的代码只保存必要的数据。
优化方法:
解除引用: 对全局变量在不需要使用它的时候将它手动设为null。
不过,解除引用并不意味着自动回收该值所占的内存。解除引用的作用是让值脱离执行环境,以便垃圾收集器下次运行时将其回收。