刚开 始学习 JS 时,挺不习惯它函数的用法,就比如一个 function 里面会嵌套一个 function,对于函数里创建变量的作用域也感到很迷惑,这个的语法和 JAVA 相差太多,为此,阅读了《JavaScript高级程序设计》这本书里的相关内容,在 Google 查 阅相关资料,并在此做个总结
函数的创建
// 这是最普通的创建方法
function sum(sum1,sum2){
return num1+num2;
}
// 将函数赋值给一个变量,需要这样调用 sum(10,10)
var sum = function(num1,num2){
return num1 + num2;
};
// 利用这种方式创建的函数阅读性差
// 不推荐使用这种方法
var sum = new Function("num1","num2","return num1 + num2");
参数
说到函数,第一个不可避免的话题就是参数了。在 JS 中,函数对于参数的传递方法和 JAVA 相差无几,都是通过复制变量值的方法来进行传递的。变量一般分为基本变量(Boolean、int 等类型)和引用变量(对象)。基本变量的复制是直接将值复制给对应的变量,而引用变量的复制是将变量的地址复制给对应的变量,接下来这两个例子有助于理解。
// 在这个例子中,changeNumber 接收一个 args 变量,这个变量是一个基本类型
// 然后在函数中对 args 进行修改,最后查看这次修改会不会影响外部 num 的值
// 从结果可知,参数对于基本类型的复制仅仅是对它的值进行复制,不会对原来的值进行修改
var num = 9;
function changeNumber(args){
args = 8;
}
changeNumber(num);
alert(num); // 结果为 9
// 在这个例子中,我们传入的参数是一个 Array 对象
// 修改 Array 第一个元素的值,查看是否会影响外部的 Array
// 通过结果我们可以看到,对外部的 Array 会产生影响。
var array = new Array();
array[0] = 9;
function changeArray(arr){
arr[0] = 8;
}
changeArray(array);
alert(array[0]); // 结果为 8
函数是个对象
在 JAVA 中,经常听到是一句话便是:“在 JAVA 的世界里,无处不对象”,在 JS 中,这句话也是成立的。那么函数是否也是一个对象呢?答案是肯定的。作为一个对象,应该具备独立的属性和方法,接下来就介绍下 Function 这个对象的属性与方法。
this 属性
this 是 JavaScript 中的一个关键字。
它代表函数运行时,自动生成的一个内部对象,只能在函数内部使用。由于函数使用的场合不同,this 代表的对象不同,但是有一个总的原则,那就是 this 指的是调用这个函数的那个对象。
借助例子我们可以更好的理解这个概念var name = "window"; function getName(){ var name = "function"; alert(this.name); } getName(); // 结果为 window
arguments 属性
在 JS 的函数中,对于传入参数的个数和类型都是不做限制的,这样显得有点无拘无束了,那么,它是怎么做到这一点的呢? arguments 起了很大的作用。arguments 是一个类似 Array 的对象,它包含了函数中传入的所有参数,我们可以通过 arguments[i] 的方式来访问指定位置的参数。function add(num1,num2){ alert(arguments[0]+arguments[1]); } add(1,2); // 结果为 3
arguments除了这个功能之外,还有一个重要的方法:callee,该属性指向函数对象,我们可以借助一下这个例子感受下 callee 的作用,也可以顺便理解一下这句话:“函数是一个对象,函数名是指针”
// 首先,这是一个递归函数 function factorial(num){ if(num<=1){ return 1; }else{ return num * factorial(num-1); } } // 调用过程把 factorial 置为 null var trueFactorial = factorial(10); factorial = function(){ return 0; } alert(trueFactorial(5)); // 结果为 120 alert(factorial(5)); // 结果为 0 // 从中我们可以看出,factorial 是一个指向函数的指针 // 因此 var trueFactorial = factorial(10)是把函数地址赋予 trueFactorial // 当 factorial 函数改变了内容之后,trueFactorial 指向的地址并不会受到影响 // 这样的写法有可能会使结果出错,而且难免会使函数名和函数产生耦合 // 这时 callee 就派上用场了 function factorial(num){ if(num<=1){ return 1; }else{ return num*arguments.callee(num-1); } }
思考:
通过上面的例子,我们也应该思考下在 JS 中是否有重载这个概念呢?
- prototype 属性
我们创建的每个函数都有一个 prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。如下面的例子:
``` javascript
function Person(){}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
person1.sayName(); // "Nicholas"
var person2 = new Person();
person2.sayName(); // "Nicholas"
alert(person1.sayName == person2.sayName); // true
```
当创建了一个函数之后,就会根据一组特定的规则为该函数创建一个 prototype 属性,这个属性指向原型对象。默认情况下,所有原型对象都会自动会的一个 construct(构造函数)属性,这个属性包含一个指向 prototype 属性所在函数的指针。当创建一个实例之后,这个实例会存在一个指向构造函数的指针,我们称这个指针为[[Prototype]]。我们可以通过下面的关系图来理解这段话:
虽然存在着 [[Prototype]] 这个指针,但是我们并不能访问它,不过我们可通过 isPrototypeOf 来确定对象之间是否存在这种关系。
alert(Person.prototype.isPrototypeOf(person1)); // true
alert(Person.prototype.isPrototypeOf(person2)); // true
在 ECMAScipt 5 增加了一个新方法,叫 Object.getPrototypeOf,这个方法返回[[Prototype]] 的值。例如:
alert(Object.getPrototypeOf(person1) == Person.prototype); // true
alert(Object.getprototypeOf(person1).name); // "Nicholas"
当想要使用 person1 的 name 属性时,搜索过程是这样子的:首先,编译器会问“person1 有 name 属性吗?”没有。那么编译器就会接着问,person1 的原型有 name 属性吗?有,就返回 “Nicholas”。
根据上面搜索的过程,我们就可以理解下面这段代码了:
function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
var person2 = new Person();
person1.name = "Greg";
alert(person1.name); // "Greg"
alert(person2.name); // "Nicholas"
- call 和 apply 方法
这两个函数都是在特定的作用域中调用特定的函数,实际上等于设置函数体内 this 对象的值。 apply
apply 方法接收两个参数,一个是在其中运行函数的作用域,另一个是参数数组。其中参数数组可以是 Array 的实例,也可以是 arguments 对象。function sum(num1,num2){ return num1 + num2; } function callSum1(num1,num2){ return sum.apply(this,arguments); // 传入 arguments 对象 } function callSum2(num1,num2){ return sum.apply(this,[num1,num2]); // 传入数组 } alert(callSum1(10,10)); // 20 alert(callSum2(10,10)); // 20
call
call 方法与 apply 方法的作用域相同,它们的区别仅在于接收参数的方式不同。对于 call 而言,第一个参数 this 值没什么变化,变化的是其余参数都直接传递给函数。function sum(sum1,sum2){ return num1 + num2; } function callSum1(num1,num2){ return sum.call(this,num1,num2); } alert(callSum(10,10)); // 20
作用扩充函数作用域
下面这个例子就是通过 call 方法改变函数的作用域。window.color = "red"; var o = {color:"blue"}; function getColor(){ alert(this.color); } sayColor(); // red; sayColor.call(this); // red sayColor.call(window); // red sayColor(o); // blue
bind 方法
我个人认为 bind 和 call、apply 方法没有什么差别。还是通过看一个例子来了解它的看法。
``` javascript
window.color = "red";
var o = {color:"blue"};
function sayColor(){
alert(this.color);
}
var objectSayColor = sayColor.bind(o);
objectSayColor(); // blue
```