JS 变量松散类型的本质,决定了变量只是在特定时间保存特定值的一个名字而已,而不存在定义某个变量的数据类型的规则,因此变量的值及其数据类型可以在脚本的生命周期内发生改变。
基本类型的值是指简单的数据段,引用类型的值指由可能有多个值构成的对象。
基本数据类型有5种:string, number,boolean,null,undefined. 它们按值访问,因此可以操作保存在变量中实际的值。
在赋值给变量时,解析器必须确定这个值是基本类型的值还是引用类型的值。
引用类型的值是保存在内存中的对象。js 不允许直接访问内存中的位置,也就是不能直接操作对象的内存空间。因此,在操作对象时,是在操作对象的引用,而不是实际的对象。
动态属性
对基本类型和引用类型的变量值进行操作的方法不同。
对于引用类型的值,可以为其添加、删除、更改属性和方法:
var person = new Object();
person.name = "Nicholas";
alert(person.name); //"Nicholas"
alert ()是一个函数。通过函数访问属性
如果对象不被销毁或者这个属性不被删除,则这个属性将一直存在。
基本类型的值却不能添加属性。
复制变量值
从一个变量向另一个变量复制基本类型值和引用类型值时,也存在不同。
复制基本类型的值时,会在变量对象上创建一个新值,然后把该值复制到为新变量分配的位置上。
var num1 =5;
var num2 = num1;
复制引用类型的值时,也会将存储在变量对象中的值复制一份放到为新变量分配的空间中。不同在于,这个值的副本是一个指针,这个指针指向存储在堆中的一个对象。复制操作结束后,两个变量引用同一个对象。改变其中一个变量,会影响另一个。
传递参数
ECMAScript 中所有函数的参数都是按值传递的。
在向参数传递基本类型的值时,被传递的值会被复制给一个局部变量(即命名参数,arguments对象中的一个元素)。
在向参数传递引用类型的值时,会把值在内存中的地址复制给一个局部变量,因此这个局部变量的变化会反映在函数的外部。
https://www.zhihu.com/question/27114726
检测类型
检测基本数据类型用 typeof s
如果变量的值是一个对象或null,则会返回object
检测引用类型的值,则用instanceof constructor
示例: alert(person instanceof Object/Array/RegExp) //true or false
所有引用类型的值都是Obeject的实例。因此在检测一个引用类型的值和Object构造函数时,instanceof 操作符始终返回true。如果使用instanceof 操作符检测基本类型的值,则返回false,因为基本类型不是对象。
执行环境及作用域
执行环境,execution context,简称环境,定义了变量或函数有权访问的其他数据,决定了各自的行为。每个执行环境都有一个与之关联的变量对象(variable object),环境中定义的所有变量和函数都保存在这个对象中。编写代码无法访问这个对象,但解析器在处理数据时会在后台使用它。
全局执行环境是最外围的一个执行环境。在web浏览器中,全局执行环境是Window对象,所有全局变量和函数都是作为 Window的属性和方法创建的。某个执行环境中所有代码执行完毕后,该执行环境被销毁,保存在其中的所有变量和函数定义也随之销毁。
每个函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入到一个环境栈中,函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境。ECMAScript程序中的执行流正是由这个方便的机制控制着。
当代码在一个环境中执行时,会创建变量对象的一个作用域链(scope chain)。用途是保证对执行环境有权访问的所有变量和函数的有序访问。作用域链前端,是当前执行代码所在环境的变量对象。如果这个环境是函数,则将其活动对象(activation object)作为变量对象。活动对象最开始只包含一个变量,即arguments对象。作用域链中下一个变量对象来自包含(外部)环境,再下一个变量对象在来自下一个包含环境,最后是全局执行环境。
标识符解析是沿着作用域链一级一级地搜索标识符的过程。
延长作用域链
可以在作用域链的前端临时增加一个变量对象,该变量对象会在代码执行后被移除。
function buildUrl(){
var qs = "?debug=true";
with (location){
var url=href +qs;
}
return url;
}
with 语句接收的是location对象,因此其变量对象中就包含了 location对象的所有属性和方法,
href实际上是location.href
没有块级作用域
在其中c语言中,由花括号封闭的代码都有自己的作用域,因而支持根据条件来定义变量。比如以下例子中,color会在if语句执行完毕后被销毁
if(true) {
var color = "blue";
}
alert(color); //"blue"
但在js中,if语句中的变量声明会将变量添加到当前的执行环境,这里是全局环境。
for (var i=0;i<10;i++){
doSomething(i);
}
alert(i);//10
对于块级作用域的语言来说,for语句初始化变量的表达式所定义的变量,只会存在于循环的局部环境。而对于JS来说,变量i即使在for循环结束后,也依旧会存在于循环外部的执行环境中。
- 变量声明
使用var声明的变量会自动被添加到被接近的环境中,在函数内部,最接近的环境就是函数的局部环境。下面例子中,sum被var声明,添加到add()函数的局部环境中,因此在全局环境中访问时,是导致错误。这里如果省略var,则sum会被添加到全局环境中。
function add(num1,num2) {
var sum =num1 + num2 ;
return sum;
}
var result = add(10,20);//30
alert(sum); //导致错误
访问局部变量要比访问全局变量更快,因为不用向上搜索作用域链。
垃圾收集
js具有自动垃圾收集机制,不用手动跟踪内存的使用情况。
函数中的局部变量只在函数执行的过程中存在,因此在函数执行完毕后,对其打上标记,将来回收其内存。
标记清除
mark-and-sweep
当变量进入环境时(例如在函数中声明一个变量),将这个变量标记为进入环境,而当变量离开环境时,则将其标记为离开环境。
垃圾收集器在允许的时候回给存储在内存中的所有变量都加上标记,然后去掉环境中的变量以及被环境中变量引用的变量的标记,在此之后,再被加上标记的变量将被视为准备删除的变量。最后,垃圾收集器完成内存清楚工作,销毁那些带标记的值回收其占用的内存空间。
另一种是引用计数
性能问题
当环境中存在256个变量,4096个对象,64K的字符串,任意一种都会触发垃圾回收机制,但如果环境中一直都有这么多变量存在,那么垃圾回收器会一直工作。
因此,如果垃圾回收器回收的内存分配量低于程序占用内存的15%,说明大部分内存不可被回收,那么把临界条件翻倍,如果回收的内存高于85%,说明大部分内存该清理了,把临界值重置为默认值。
管理内存
JS中,分配给web浏览器的可用内存数量通常比分配给桌面应用程序的少,处于安全考虑。内存限制问题不仅会影响给变量分配内存问题,还会影响调用栈以及在一个线程中能够同时执行的语句数量。
因此,为了让页面获得更好的性能,我们需要占用最少的内存,优化内存占用,只为执行中的代码保存必要的数据。一旦数据不再有用,可将其值设置为null释放其引用,这个做法叫做解除引用(dereferencing)。一般用于释放全局变量的内存,因为局部变量在离开执行环境时自动被解决引用。