基本类型值:简单的数据段 ,五种基本类型(Number Boolean String Null Undefined)的值都是基本类型值,基本类型的值在内存中大小固定,所以保存在栈内存中。
引用类型值:可能由多个值构成的对象。不能操做引用类型的内存空间。保存在堆内存中。前端
咱们能够为引用类型的值添加、修改、删除属性和方法,好比:正则表达式
var cat = new Animal(); cat.name = "cat"; cat.speak = function() { alert(this.name); }; cat.speak(); // cat
然而为基本类型的值添加属性和方法是无效的。算法
var name = "Sue"; name.age = 18; alert(name.age); //undefined
var num1 = 5; var num2 = 5;
num1与num2的内存空间是彻底独立的,对一方的改变不会影响到另外一方。后端
var obj1 = new Object(); var obj2 = obj1; obj2.name = "Sue"; alert(obj1.name); // Sue
当咱们将对象obj1
复制给obj2
时,只是建立了一个指针副本,这个指针副本与obj1
指向同一个保存在堆内存中的对象。所以改变一方,另外一方也会发生相应的改变。浏览器
var num = 2 function add(num1, num2) { return num1 + num2; } add(1, num);
在上述代码中,add(1, num)
传入的参数是实参,而arguments[]
老是获取由实参串起来的参数值,在函数体中的num1
num2
是形参,至关于声明了两个局部变量,指向arguments[0]
arguments[1]
。app
ECMAScript 中全部函数的参数都是按值传递的,把函数外部的值复制给函数内部的参数,就和把值从一个变量复制到另外一个变量同样(不管是基本类型仍是引用类型)。函数
function changeStuff(num, obj1, obj2) { num = num * 10; obj1.item = "changed"; obj2 = {item: "changed"}; } var num = 10; var obj1 = {item: "unchanged"}; var obj2 = {item: "unchanged"}; changeStuff(num, obj1, obj2); console.log(num); // 10 console.log(obj1.item); // changed console.log(obj2.item); // unchanged
以上的例子是怎么说明ECMAScript中函数的参数都是按值传递的呢?
首先基本数据类型,全局变量num
复制自身给参数num
,两者是彻底独立的,改动不会相互影响。
关于对象,咱们彷佛看到了函数内部对obj1
对象属性的改动反应到了函数外部,而obj2
从新赋值为另外一个对象却没有反应到外部,这是为何呢?书中解释得有点简单,笔者找了一下资料,原来传入对象的时候,其实传入的是对象在内存中的地址,当在函数中改变对象的属性时,是在同一个区域进行操做,因此会在函数外反映出来,然而,若是对这个局部变量从新赋值,内存中的地址改变,就不会对函数外的对象产生影响了,这种思想称为 call by sharing。性能
However, since the function has access to the same object as the caller (no copy is made), mutations to those objects, if the objects are mutable, within the function are visible to the caller, which may appear to differ from call by value semantics. Mutations of a mutable object within the function are visible to the caller because the object is not copied or cloned — it is shared. Wikipedia
使用typeof
能够辨认String
Number
Undefined
Boolean
Object
还有函数。测试
typeof("name"); //string typeof(18); //number typeof(undefined); //undefined typeof(null); //object typeof(true); //boolean typeof(new Array()); //object typeof(Array); //function
正则表达式在某些浏览器中typeof
返回结果为object
,某些返回function
。优化
instanceof
能够判断是不是给定类型的实例
var a = new Array; a instanceof Array; //true
使用instanceof
测试基本数据类型时,用于返回false
。
定义了变量或函数有权访问的其余数据。每一个执行环境都有一个与之关联的变量对象(variable object),环境中定义的全部变量和函数都保存在这个对象中。
全局执行环境是最外围的一个执行环境。在Web 浏览器中,全局执行环境被认为是window 对象,所以全部全局变量和函数都是做为window 对象的属性和方法建立的。某个执行环境中的全部代码执行完毕后,该环境被销毁,保存在其中的全部变量和函数定义也随之销毁(全局执行环境直到应用程序退出,例如关闭网页或浏览器时才会被销毁)。
当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行以后,栈将其环境弹出,将控制器返还给以前的执行环境。
当代码在一个环境中执行时,会建立变量对象的一个做用域链,以保证对执行环境有权访问的全部变量和函数的有序访问。做用域链的前端,始终都是当前执行的代码所在环境的变量对象,对于全局执行环境,就是window
对象,对于函数执行环境,就是该函数的活动对象。做用域链的后续,是该函数对象的[[scope]]属性(全局执行环境没有后续)。
在一个函数被定义时,会建立这个函数对象的[[scope]]属性,指向这个函数的外围。
在一个函数被调用时,会建立一个活动对象,首先将该函数的形参和实参(arguments
)添加进该活动对象,而后添加函数体内声明的变量和函数(提早声明,在刚进入该函数执行环境时,值为undefined
),这个活动对象将做为该函数执行环境做用域链的最前端。
关于JS的提早声明机制,咱们举个例子证实一下:
function add (num1){ console.log(num2); var num3 = 4; return num1 + num2; } add(1); //undefined 5
上述代码中,咱们在变量声明前使用它,却没有跑出ReferenceError
,说明函数执行时,一开始,num2
就已经声明了。
内部环境能够经过做用域链访问全部的外部环境,但外部环境不能访问内部环境中的任何变量和函数。这些环境之间的联系是线性、有次序的。每一个环境均可以向上搜索做用域链,以查询变量和函数名;但任何环境都不能经过向下搜索做用域链而进入另外一个执行环境。
咱们举一个例子,顺便理解一下前面的概念:
var num = 10; function add (num1, num2) { function preAdd(num) { var pre = 1; return pre + num; num1 = preAdd(num1); var result = num1 + num2 + num; return result; } add(10, 20);
开始执行add()
时,首先建立一个执行上下文,而后建立一个活动对象,将arguments
num1
num2
pre
preAdd()
result
保存在活动对象中,并将活动对象放在做用域链的前端,执行上下文取得add
保存的[[scope]]
,并将其放入做用域链的后端,而后执行到preAdd
,preAdd
建立一个执行上下文,并压入栈顶,建立一个活动对象,保存arguments
num
pre
,放在做用域链的前端,取得preAdd
的[[scope]]
,放入做用域链的后端。当编译器开始解析pre
时,首先从preAdd
做用域链的前端开始找,找到了马上中止。当编译器开始解析result = num1 + num2 + num
,因为在add
的做用域链前端(局部变量)中没有该变量,所以继续在做用域后端中寻找,并最终在全局变量中找到了num
在块做用域内,将指定变量放在做用域链的前端
建立一个新的变量对象,其中包含的是被抛出的错误对象的声明,将这个对象放在做用域链的最前端,catch
执行结束后,做用域链恢复。
ECMAScript中没有块级做用域,所以块的执行环境与其外部的执行环境相同。
使用var 声明的变量会自动被添加到最接近的环境中。若是初始化变量时没有使用var 声明,该变量会自动被添加到全局环境(严格模式下,这样写会抛错)。
当对一个变量进行读取或修改操做时,咱们首先要搜索到它,搜索的顺序如图:
标识符解析是沿着做用域链一级一级地搜索标识符的过程。搜索过程始终从做用域链的前端开始,而后逐级地向后回溯,直至找到标识符为止(若是找不到标识符,一般会致使错误发生)。
Javascript具备自动垃圾收集机制,周期性地回收那些再也不使用的变量,并释放其占用的内存。
这是Javascript中最经常使用的垃圾收集方式,当变量进入环境时,将其标记为“进入环境”,离开环境时,标记为“离开环境”。理论上,不能够回收标记为“进入环境”的变量。
可使用任何方式来标记变量。好比,能够经过翻转某个特殊的位来记录一个变量什么时候进入环境,或者使用一个“进入环境的”变量列表及一个“离开环境的”变量列表来跟踪哪一个变量发生了变化。说到底,如何标记变量其实并不重要,关键在于采起什么策略。
不太常见,跟踪记录每一个值被引用的次数。
当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是1。若是同一个值又被赋给另外一个变量,则该值的引用次数加1。相反,若是包含对这个值引用的变量又取得了另一个值或当它们的生命期结束的时候,要给它们所指向的对象的引用计数减1。当这个值的引用次数变成0 时,则说明没有办法再访问这个值了,于是就能够将其占用的内存空间回收回来。
var a = new Cat(); // 1 var b = a; // 2 var c = b; // 3 b = new Dog(); // 2 c = new Fox(); // 1 a = new Object(); // 0
这样看起来,引用计数法彷佛没什么问题,然而,当遇到循环引用时,就跪了。。。
var a = new Object(); //a指向的Object的引用次数+1 var b = new Object(); //b指向的Object的引用次数+1 a.another = b; //b指向的Object的引用次数+1 b.another = a; //a指向的Object的引用次数+1
此时,两个对象的引用次数都为2,用于都不会变为0,永远都不会被GC,浪费内存。
因为引用计数存在上述问题,所以早在Navigator 4.0就放弃了这一策略,但循环引用带来的麻烦却依然存在。
IE 中有一部分对象并非原生JavaScript 对象。例如,BOM 和DOM 中的对象就是使用C++以COM(Component Object Model,组件对象模型)对象的形式实现的,COM的垃圾回收策略是引用计数法,所以只要涉及到COM对象,就会存在循环引用的问题,举一个例子:
var element = document.getElementById("some_element"); var myObject = new Object(); myObject.element = element; element.someObject = myObject;
IE9 把BOM 和DOM 对象都转换成了真正的JavaScript 对象。这样,就避免了
两种垃圾收集算法并存致使的问题。
因为系统分配给浏览器的内存比较小(比桌面应用小),而内存限制势必会影响网页性能,所以Javascript中,优化内存占用是一个必要的问题,最佳方式就是只保留必要的数据。局部变量会在离开执行环境后自动解除引用,然后被GC,所以咱们只需在再也不须要某个全局变量时,将其设为null
,来解除它对内存的引用(即解除引用dereferencing),适用于大多数全局变量和全局对象的属性。
针对上一节的例子,咱们可使用一样的方法:
myObject.element = null; element.someObject = null