1、复制变量值与参数传递
函数都有一个arguments对象,函数的参数就是这个对象的一个元素,该对象类似数组,可以通过数组访问的方式访问其中的元素。而所谓的函数的参数定义,就是将对应位置的arguments起了一个名字,便于在函数中引用,因此:
函数的参数是函数的局部变量。
函数传值的过程,就是将 外部变量的值 复制到 arguments的某个元素 中,该元素是函数的局部变量。
换言之,JS函数都是按值传递,无论是基本类型还是引用类型。虽然引用类型是按值传递给函数内部,但是在函数内部访问对象时依然按引用访问。
举个例子:
object1=object2.将object2复制给object1,此时是将object2的指针创建一个副本,然后将这个副本赋值给object1,此时object1,object2指向同一个对象。
function f (o){
o.name="a";
o=new Object();
o.name="b";
}
var p=new Object();
f(p);
alert(p.name);//"a"
因为对象p将值传给了o,即将对象p指向堆空间的指针创建了一个副本,并赋值给了o,这样在给o的属性赋值时,会按引用找到对象,从而改变了那个对象的属性的值。此时,p的属性name也同样被改变了,因为p本来就跟o对应的同一个对象。
而当o=new Object时,又将新创建的Object的指针创建了一个副本,将这个副本给了o,此时o.name,按引用找到的对象就是new Object新创建的对象了。由于函数的参数是函数的局部变量,函数执行完后,该参数就被清除了。
基本变量赋值时,也是给其创建一个副本,然后进行赋值,两个变量虽然值相同,但是没有什么关系。而传递参数时也是一样,因为函数传参的本质是将 外部的值 复制到 参数变量中。I
2、执行环境和作用域
执行环境定义了变量或函数有权访问的其他数据。每个执行环境都有一个环境变量,称之为变量对象,环境中所定义的变量和函数都保存在其中。有一个最外围的执行环境称为全局环境。其他环境都是在一个函数内的局部环境。在Web浏览器中,最外围的环境是window,每个函数都有一个自己的环境。
【作用域链】当代码在环境中执行时,会创建一个变量的作用域链,当访问一个变量或函数时,会沿着当前执行环境向外查找,直到找到最外层,如果没有找到,则报错。
var 变量=1;
fuction 函数1(){
var 函数1里的变量=变量;
function 函数2(){
var 函数2里的变量=函数1里的变量;
函数1里的变量=变量;
变量=函数2里的变量;
}
函数2();
};
函数1();
最里层的函数2可以访问该函数外部环境中的所有变量。但是外部环境不能访问内部环境的变量。即只能从里往外检索。
【函数赋值】如果用var定义,则将变量存入最近的执行环境的变量对象中,其作为局部变量。没有用var定义的变量,会存入全局执行环境的变量对象中,该变量就是全局变量。
3、没有块级作用域
类似于if(){ }, for(){} 这样的块,其内部定义的变量,比如for(var i =0;i<10;i++)中的i,在for循环之外还是有效的,这点与Java不同。
4、引用类型的值和引用类型
引用类型的值(对象)是引用类型的一个实例。引用类型是一种数据结构,包含数据和功能。引用类型 相当于类,但是不具有其他oo语音的特征,因此被称为 对象定义 更确切,因为它描述的是 一类对象 所具有的方法和属性。
Object类型是JS内置的一种引用类型。
【对象创建】
var p=new Object();//()可省略,但不推荐
p.name="可以";// 给对象增加属性。
如果需要定义大量属性,可以使用对象字面量表示法:
var p={
name:"Nike",
age:"18"
};
var p={};与var p=new Object()等价。但不会调用Object的构造函数。
【访问属性的方法】访问属性除了使用圆点法——object.propertyname外,还可以使用方括号 object[propertyname],使用场景是当propertyname是动态的,比如通过遍历对象属性赋值或取值时。
Array类型
【创建数组】 var a=new Array();
参数可以是n个:
n=1:如果是数字,表示创建数字表示的长度的数组。如果是非数字,表示只包含这个值的单值数组
n>1:表示包含n个值的数组,这n个值就是这n个参数,比如new Array("a","b","c")
n=0:数组是动态可扩展的。
其中new 可以省略,变成 var a=Array;
数组字面量:var a=["a","b",""];
数组的length不是只读的。
【栈方法】 a.push("a","b");在数组末尾追加;a.pop() 返回数组最后一项,该项从数组中被去掉。
【队列方法】a.shift();返回数组第一项,该项从数组中被去掉;a.unshift(“a”,"b")在数组前面添加。
【反转数组】a.reverse();
【数组排序】a.sort默认升序排列。 a.sort(compare) 其中compare可以实现比较方法function compare(value1,value2)
【操作方法】concat slice splice
Function类型
Function类型也是引用类型,那么每个函数都是Function类型的实例。而且有自己的属性和方法(所以函数里也可以定义函数)。由于函数是Function类型的对象,对象可以赋值给不同变量,因此同一个函数可以有不同的名字(赋值给不同变量),这个名字是找到函数去执行的指针而已。
函数声明语法定义 function f(){
}与 函数表达式定义(将函数的指针复制给变量f)
var f=function(){
}相同。区别是:在全局执行环境加载数据时,会首先加载函数声明,因此函数的执行在函数声明前后没有关系。而函数表达式是代码执行到表达式时才会加载,因此函数执行必须在表达式之后。
f传递的是函数的指针。f()表示函数执行。
【this】函数内部有一个特殊对象this,其值与函数执行环境有关。this引用的是执行函数操作的对象。如果在全局环境中执行,那么this就引用的是全局环境中的变量;如果在函数是一个对象的方法,那么this引用的就是对象的属性。
c="a";
function f(){alert(this.c);};
f(); //相当于window对象执行f,因此其中的this引用的是window,this.c="a"
var o={c:"b"};
o.f=f; //将f的指针赋给o的函数f
o.f(); //执行时指向f函数,此时this引用的是o,因此this.c=“b”
由于函数是Function类型的对象,因此无论是在哪个环境下执行的f,都是同一个函数。
5、原型(Prototype)
每个函数都有一个原型属性,该属性是一个对象。用途是包含可以由特定类型的所有实例共享的属性和方法。每个原型属性都会自动获得一个constructor(构造函数)属性,这个属性包含一个指针,该指针指向Prototype属性所在的函数。(这样函数和原型属性双向引用)
当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(内部属性__proto__),指向构造函数的原型属性。(这样实例可以找到构造函数的原型属性,间接与构造函数关联)
函数都是对象。构造函数是对象,构造函数通过new创造出的函数也是对象。
//函数表达式法定义函数,
var f=function(){}; //f是一个对象。等价于 var f=new Function(参数0-n,函数体); 函数是一个对象,函数名是一个指针
f();//执行在window环境下。
function F(){}; //与var F=function(){}等价
F.prototype.name="a";
F.prototype.sayName=function(){alert(this.name)};
F();//F自己本身就是个函数,可以执行,这样执行就是成为window的对象
var o=new F();
o.sayName(); //o是一个对象,o执行F的内部的一个函数sayName
JS原生的Function函数也是个对象,也是通过new Function可以实例化成一个个function函数对象。
6、闭包和私有作用域
闭包就是能够访问外部变量,但是其内部变量对外部不可见。
(function(){
私有作用域
})();
上面这个表示一个匿名函数,后面那对括号表示函数立即执行,和var f=function(){}; f();一样。只是这个函数没有名字,也就没法调用,在函数外加上括号后,变成了表达式,就可以执行了。
var f=(function(){})();同理。
这个用法很普遍,特别对于大型项目,防止过多变量污染全局空间,也可以解决没有块作用域的情况(前面讲的for语句的例子):http://segmentfault.com/q/1010000000135703