js的作用域是以function保持的,需要使用var关键字进行声明;若赋值的时候没有使用关键字var,那么在浏览器的环境下,这个变量就会是window对象的属性,node环境下是global对象。javascript的作用域是词法作用域(lexical scope or static scope),也就是说作用域链在定义的时候就确定了。使用eval, with会扰乱作用域链(dynamic scope)。
js可以识别两种对象,即“Native Object”和“Host Object”,“Native Object”属于js语言本身,而“Host Object”属于浏览器环境,比如BOM对象和DOM元素。所有的js对象都有prototype,同时prototype也是对象,它们也有prototype,就这样形成了原型链,当原型链中的一个对象有一个null prototype时,原型链结束,Object 对象默认就是拥有null prototype。当从查找对象的属性的时候,会先从对象本身开始查找,然后顺着原型链查找,将返回第一个匹配的属性,然后不继续查找,若没有匹配的属性,那么返回undefined。js对象中有一个内置的[[prototype]]私有属性,可以通过外部的Object.prototype来调用。
js函数代码代码运行在执行上下文中,每一个函数的引用都有相关的执行上下文。所谓执行上下文,指JavaScript中的函数运行在它们被定义的作用域里,而不是它们被执行的作用域里。使用eval来执行js代码,也会有自己的执行上下文。不同浏览器对eval的作用域解释不同,在w3c标准的浏览器中,eval是全局的作用域;在IE中,eval会受当前的作用域的限制,execScript()则视为window.eval()。
当一个函数被定义的时候,会创建一个执行上下文,每次函数被调用的时候会进入这个执行上下文,当函数退出的时候,会回到之前的执行上下文中。当执行上下文被创建的时候,会有产生一系列的事情,首先会创建一个Activation Object,Activation Object拥有一些被可以引用的属性,但是Activation Object不能直接的引用,也没有一个形式上的定义的prototype属性。
接下来会创建Activation Object的arguemnts属性对象,对应arguemnts[n]对应第(n+1)个实际参数,arguemnts属性拥有callee(函数对象本身)和length属性。
然后Activation Object会创建一个arguments属性,然后指向arguemnts对象。接下来会执行上下文会分配一个包含一系列对象的scope,每一个函数都有一个内置的[[scope]]属性,这个属性同样包含一系列的对象。函数的执行上下文的scope chain是这样构成的,将执行上下文的Activation Object添加到scope chain的顶端,scope chain保留在[[scope]]属性中。js中函数都是对象,它们在参数实例化(预编译)的时候,在使用function关键字的方式声明,或是调用Function constructor的时候。使用Function constructor的函数,[[scope]]属性指向的scope chain通常只包含全局对象。scope chain定义了函数的[[scope]]属性,[[scope]]属性定义了函数的scope。函数中,标识的解析和对象中的属性的解析类似,函数是顺着scope chain去查找相应的标识;而对象是顺着prototype chain去查找相应的属性。
接下来就是参数实例化(预编译),使用一个Variable Object,这个对象就是Activation Object。对函数的每一个形式参数,都会在Variable Object中创建一个属性,如果形式参数对应对实际参数有值,那么就会把这个值分配给对应的属性,如果没有相应的实际参数,那么这个属性就是undefined。然后就是内部的使用function关键字定义的函数块,js会预先加载真个函数体,并把这个函数分配给Variable Object对象的相应的属性。最后就是函数内部使用var关键字声明的变量,此时不会把相应的值加载到内存中,只是Variable Object拥有这个属性,它们的值是undefined,直到解释阶段,真正执行到该赋值的语句的时候,才会给该属性分配相应的值。下面是一个例子,调用函数a的时候,会在下列地方输出以下值(将使用function关键字和var关键字声明变量a的语句调换顺序,输出不变):
var x = {"a" : 1}; var a = function(x){ console.log(x===arguments[0]); //true console.log(typeof(a);); //'function' function a(){ console.log('function'); }; var a = 1; console.log(typeof(a);); //'number' x = 1; console.log(x===arguments[0]); //true }; a();
但是如果变量没有使用关键字声明的变量,它的作用域是全局的,并且不会被预编译。全局执行上下文没有arguemnts对象,但是也有scope和scope chain(scope chain中只包含全局对象);同样也会根据<script>标签进行预编译。
js中有自动的垃圾回收的机制,思想是如果一个对象(所有属性)不能被正在执行的代码引用,那么这个这个对象将在某一时间被销毁,它消耗的资源将被释放,通常发生在执行上下文退出的时候。
闭包是一种当父类对象结束以后,仍旧可以引用子对象的一种方法。js解释器在遇到函数定义的时候,会自动把函数和他可能使用的变量一起保存起来,也就是构建一个闭包,这些变量将不会被内存回收器所回收,只有当内部的函数不可能被调用以后(例如被删除了,或者没有了指针),才会销毁这个闭包,而没有任何一个闭包引用的变量才会被下一次内存回收启动时所回收。正因为如此,闭包可以保留一些动态的变量,闭包只会记录内部变量最后的值:
下面是一个例子:
function associateObjWithEvent(obj, methodName){ return (function(e){ e = e||window.event; /* The event handler calls a method of the object - obj - with the name held in the string - methodName - passing the now normalised event object and a reference to the element to which the event handler has been assigned using the - this - (which works because the inner function is executed as a method of that element because it has been assigned as an event handler):- */ return obj[methodName](e, this); }); }; function DhtmlObject(elementId){ var el = document.getElementById(elementId); if(el){ el.onclick = associateObjWithEvent(this, "doOnClick"); el.onmouseover = associateObjWithEvent(this, "doMouseOver"); el.onmouseout = associateObjWithEvent(this, "doMouseOut"); } } DhtmlObject.prototype.doOnClick = function(event, element){ alert(element.id); } DhtmlObject.prototype.doMouseOver = function(event, element){ alert('mouseover'); } DhtmlObject.prototype.doMouseOut = function(event, element){ alert('mounsenout'); } var main = new DhtmlObject('main');
refer to http://jibbering.com/faq/notes/closures/