首先介绍一些即将用到的概念:
执行环境:
所有javascript代码都是在一个执行环境中被执行的,当调用一个javascript函数时,就会创建相应的执行环境,如果又调用另外一个函数,则又会创建一个新的执行环境,当调用函数返回后,执行过程会返回原始的执行环境,所以javascript的执行环境明显是按照堆栈的形式存储,即这一系类的执行环境构成了一个执行环境栈。
在一个函数的执行环境中,会创建一个活动“对象”,接下来创建一个类数组的arguments对象,它以整数索引的数组成员一一对应地保存着调用函数时所传递的参数,同时具备length,callee等属性,然后活动对象创建一个名为arguments的属性,该属性引用前面创建的arguments对象。接着为执行环境分配作用域,指定给一个函数调用执行环境的作用域,由该函数的scope属性所引用的对象列表链组成,同时,活动对象被添加到该对象列表的顶部,即链的前端。最后来说this,如果赋的值是一个对象的引用,则this指向该对象,如果赋null,则this指向全局对象。活动对象包含了arguments,this,及其可变对象属性。
作用域链:
当某个函数第一次被调用时,会创建一个执行环境及相应的作用域链,并把作用域链赋值给一个特殊的内部属性Scope,作用域链包括了一系列活动对象,第一位的是当前函数的this,arguments和其他命名参数的值来初始化的对象。然后从内向外的顺序创建活动对象,直到全局作用域对象。
总结一下他们的关系:当函数作为构造函数调用时,比如:
function Person(){} Person.prototype.sayName=function(name){alert(name)} var person1=new Person(); person1.sayName("wenber");
在创建函数Person时:该函数Person就有一个prototype属性,即Person.prototype,它指向Person的原型对象Prototype。(这里有点绕,可以这么理解,Person的一个属性叫做prototype,这个属性又指向一个对象叫做Prototype,与此同时,这个叫做Prototype的对象它也有个属性叫做constructor,它指向了Person本身;
当实例化对象Person,也就是执行了构造函数Person,该实例person1又具有一个叫做_proto_的属性,它指向Person的原型对象Prototype(记住不是Person本身)。
如图:
1,js是顺序执行的,下面这个大家肯定没有异议
test();// 哈哈
function test(){
alert("哈哈");
}
但是在C语言中,这样就不行了,C语言要求函数必须先声明后调用,那是什么原因让js有如此能力先调用而后声明了?ok,这说明js肯定是在调用函数之前就将函数放入内存中了,这种简单的机制就是我们今天要说的---js预解析
2,变量的预解析,看如下代码
alert(a);//undefined var a="test";
和函数声明一样,变量的声明也会在一开始就被放入内存中了,但是并没有赋值,所以在它赋值之前,它的值就是undefined;
3,变量和函数重名,看如下代码
alert(a); //function(){ return "function"}
var a="var";
function a(){ return "function" }
alert(a); // var
这个就说明了,重名时,函数名优先级高于变量名
4,js的预解析是在程序进入一个新的环境时,把该环境里的变量或函数预解析到它们能调用的环境中。即每一次预解析的单位是一个执行环境。代码如下
function a(){ var b="function" } alert(b);//报错 b is not defined