1 函数
每个函数都是 Function 类型的实例, 且都与其他引用类型一样具有属性和方法. 由于函数式对象, 因此函数名实际上也是一个指向函数对象的指针, 不会与某个函数绑定.
1.1 函数概述
1.1.1 函数定义
函数通常都是使用函数声明语法定义的, 如下:
使用函数声明语法定义 (必须有函数名):
function sum(num1, num2) {return num1 + num2;}
使用函数表达式定义 (可以没有函数名):
var sum = function(num1, num2) {return num1 + num2;};
使用 Function 构造函数定义:
Function 构造函数可以接收任意数量的参数, 但后一个参数始终都被看成是函数体, 而前面的参数则枚举出了新函数的参数.
var sum = new Function("num1", "num2", "return num1 + num2"); // 不推荐
这种方式可以很直观理解 "函数是对象, 函数名是指针".
从技术上角度讲, 这是一个函数表达式. 但这种语法会导致解析两次代码 (解析常规 ECMAScript 代码, 解析传入构造函数中的字符串), 影响性能. 因此, 不推荐使用这种方法定义. 由于函数名仅仅是指向函数的指针, 因此函数名与包含对象指针的其他变量没有什么不同. 换句话说, 一个函数可能会有多个名字. 使用带圆括号的函数名是调用函数, 使用不带圆括号的函数名是访问函数指针, 而非调用函数.
1.1.2 函数调用 --functionName(arg0, arg1,...,argN);
sayHi("Nicholas", "how are you today?"); 输出结果: 弹出 "Hello Nicholas,how are you today?"
1.1.3 函数返回值
任何函数, 任何时候都可以通过 return 语句后跟要返回的值来是实现返回值. 函数会在执行完 return 语句后停止并立即退出. 因此, return 之后的任何代码都永远不会执行.
- function sum(num1, num2) {
- return num1 + num2;
- }
调用: var result = sum(5, 10); // result 为 15
注: return 可以不带人和返回值. 则函数将返回 undefined 值. 一般用在需要提前停止函数执行, 又不需要返回值的情况下. 推荐: 要么让函数始终都有返回值, 要么就永远都不要有返回值, 否则会给代码调试带来不便. 严格模式下的限制: 发生以下情况, 会导致语法错误, 代码无法执行. 不能把函数 / 参数命名为 eval 或 arguments; 不能出现两个命名参数同名的情况.
1.1.4 理解参数
ECMAScript 函数不介意传递的参数的个数及类型, 即使个数与定义的个数不同, 也不会报错. 因为参数在内部使用一个数组来表示的. 函数体内部可以通过 arguments 对象来访问这个参数数组, 从而获取传递过来的每一个参数.
function sayHi(name, message) { alert("Hello" + name + "," + message);}
可以像下面这样重写
function sayHi() { alert("Hello" + arguments[0] + "," + arguments[1]);}
ECMAScript 函数的重要特点:
命名的参数只提供便利, 但不是必需的;
在调用时, 对应参数名字不一定要一致;
aruments 对象可以与命名参数一起使用;
arguments 的值永远与对应命名参数的值保持同步;
arguments 对象的长度由传入函数的参数决定, 而非定义函数时的命名参数个数;
没有传递值的命名参数自动被赋予 undefined 值, 类似于定义了变量但未初始化.
下面这个函数会在每次被调用时, 输出传入其中的参数个数:
- function howManyArgs() {
- alert(arguments.length);
- }
- howManyArgs("string", 45); //2
- howManyArgs(); //0
- howManyArgs(12); //1
严格模式下: 严格模式对如何使用 arguments 对象做出了一些限制.
首先, 像前面例子中那样的赋值会变得无效. 也就是说, 即使把 arguments[1] 设置为 10,num2 的值仍然还是 undefined. 其次, 重写 arguments 的值会导致语法错误 (代码将不会执行).
1.2 没有重载 (深入理解)
重载函数
重载函数是函数的一种特殊情况, 在同一个作用域中, 如果有多个函数的名字相同, 但形参列表不同 (参数类型不同或参数个数不同), 返回值类型可同也可不同, 我们称之为重载函数.
ECMAScript 函数不能重载
ECMAScript 函数不能像传统意义上那样实现重载.
如果在 ECMAScript 中定义了两个名字相同的函数, 则该名字只属于后定义的函数, 后面的会覆盖前面的. 通过检查传入函数中参数的类型和数量并作出不同的反应, 可以模仿方法的重载.
1.3 函数声明与函数表达式
函数声明: 率先读取函数声明, 并使其在执行任何代码前可用 (可访问); 解析器会在代码开始执行之前, 通过一个名为函数声明提升的过程, 读取并将函数声明添加到执行环境中. 对代码求值时, JavaScript 引擎在第一遍会声明函数并将它们放到源代码树的顶部. 所以, 即使声明函数的代码在调用它的代码后面, JavaScript 引擎也能把函数声明提升到顶部.
使用函数声明语法定义函数 (必须有函数名):
- alert(sum(10,10));
- function sum(num1, num2) {
- return num1 + num2;
- }
函数表达式: 等到解析器执行到它所在的代码行, 才会真正被解释执行. 使用函数表达式定义函数 (可以没有函数名):
- alert(sum(10,10));
- var sum = function(num1, num2) {
- return num1 + num2;
- };
使用函数表达式定义函数, 如上, 若在定义前通过变量访问函数, 会导致报错. 因为在执行到函数所在的语句之前, 变量 sum 中不会保存有对函数的引用; 而且, 第一行代码就会导致 "unexpected identifier"(意外标识符) 错误, 代码并不会执行到下一行.
1.4 作为值的函数
因为 ECMAScript 中的函数名本身就是变量, 所以函数也可以作为值来使用. 即不仅可以像传递参数一样把一个函数传递给另一个函数, 也可以将一个函数作为另一个函数的结果返回. 如下:
- // 接受两个参数, 第一个参数是一个函数, 第二个是要传递给该函数的一个值
- function callSomeFunction(someFunction, someArgument){
- return someFunction(someArgument);
- }
- function add10(num){
- return num + 10;
- }
- var result1 = callSomeFunction(add10, 10);
- alert(result1); //20
- function getGreeting(name){
- return "Hello," + name;
- }
- var result2 = callSomeFunction(getGreeting, "Nicholas");
- alert(result2); //"Hello, Nicholas"
1.5 函数内部属性
函数内部有两个特殊的对象: arguments 和 this.
1.5.1 arguments
arguments 是一个类数组对象, 包含着传入函数中的所有参数. arguments 有一个 callee 的属性, 该属性是一个指针, 指向拥有这个 arguments 对象的函数. 下面是非常经典的阶乘函数:
- function factorial(num){
- if (num <=1) {
- return 1;
- } else {
- return num * factorial(num-1)
- } }
- var trueFactorial = factorial;
- factorial = function(){
- return 0;
- };
- alert(trueFactorial(5)); //0
- alert(factorial(5)); //0
- alert(factorial(5)); //0
如上, 若这个函数的执行与函数名 factorial 紧紧耦合在了一起, 当函数名 factorial 重写后, 会对函数执行有所影响, 为了消除这种紧密耦合的现象, 可以像下面这样使用 arguments.callee.
- function factorial(num){
- if (num <=1) {
- return 1;
- } else {
- return num * arguments.callee(num-1)
- }
- }
- var trueFactorial = factorial;
- factorial = function(){
- return 0;
- };
- alert(trueFactorial(5)); //120
- alert(factorial(5)); //0
- alert(factorial(5)); //0
- 1.5.2 this
this 引用的是函数据以执行的环境对象.
- window.color = "red";
- var o = { color: "blue" };
- function sayColor(){
- alert(this.color);
- }
- sayColor(); //"red" 全局作用域调用, this 引用全局对象 window,this.color => window.color
- o.sayColor = sayColor; o.sayColor(); //"blue" 函数赋给了对象 o,this 引用的是对象 o,this.color => o.color
注: 函数名仅仅是一个包含指针的变量而已. 因此, 即使是在不同环境中执行, 全局的 sayColor() 函数与 o.sayColor() 指向的仍是同一个函数.
1.5.3 caller
caller 属性中保存着调用当前函数的函数的引用.
- function outer(){
- inner();
- }
- function inner(){
- alert(inner.caller);
- }
- outer();
以上代码, 会导致警告框中显示 outer() 函数的源代码. 因为 outer() 调用了 inter(), 所以 inner.caller 就指向 outer(). 如果是在全局作用域中调用当前函数, 它的值为 null. 为了实现更松散的耦合, 也可以通过 arguments.callee.caller 来访问相同的信息.
- function outer(){
- inner();
- }
- function inner(){
- alert(arguments.callee.caller);
- }
- outer();
注: 严格模式下, 访问 arguments.callee 会导致错误, 不能为函数的 caller 属性赋值, 否则也会导致错误.
1.6 函数属性和方法
ECMAScript 中的函数都是对象, 因此函数也有属性和方法. 每个函数都包含两个属性: length 和 prototype.
length: 函数希望接收的命名参数的个数;
prototype: 保存所有实例方法的真正所在.
- 1.6.1 length
- function sayName(name){
- alert(name);
- }
- function sum(num1, num2){
- return num1 + num2;
- }
- function sayHi(){
- alert("hi");
- }
- alert(sayName.length); //1
- alert(sum.length); //2
- alert(sayHi.length); //0
- 1.6.2 prototype
对于 ECMAScript 中的引用类型而言, prototype 是保存它们所有实例方法的真正所在. 每个函数都包含两个非继承而来的方法: apply() 和 call().
这两个方法的用途都是在特定的作用域中调用函数, 实际上相当于设置函数体内 this 对象的值.
1.6.2.1 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
注: 严格模式下, 未指定环境对象而调用函数, 则 this 值不会转型为 window. 除非明确把函数添加到某个对象或者调用 apply() 或 call(), 否则 this 值将是 undefined.
1.6.2.2 call
call() 方法与 apply() 方法作用相同, 区别仅在于接收参数的方式不同. 对于 call() 而言, 第一个参数是 this 值, 剩下的参数都直接传递给函数. 即使用 call() 方法时, 传递给函数的参数必须逐个列举出来. 如下:
- function sum(num1, num2){
- return num1 + num2;
- }
- function callSum(num1, num2){
- return sum.call(this, num1, num2);
- }
- alert(callSum(10,10)); //20
使用 apply() 还是 call() , 完全取决于你才去哪种给函数传递参数的方式最方便. 不传参数, 则哪种方法都无所谓. 事实上, 传递参数并非 apply() 和 call() 真正的用武之地; 它们真正强大的地方是能够扩充函数赖以运行的作用域.
- window.color = "red";
- var o = { color: "blue" };
- function sayColor(){
- alert(this.color);
- }
- sayColor(); //red this=>window
- sayColor.call(this); //red this=>window
- sayColor.call(window); //red this=>window
- sayColor.call(o); //blue this=>o
使用 call() 或 apply() 来扩充作用域的最大好处: 对象不需要与方法有任何耦合关系. 不需要像前面那样把要用的函数放到对象中, 调用.
1.6.2.3 bind
这个方法会创建一个函数的实例, 其 this 值会被绑定到传给 bind() 函数的值. 如:
- window.color = "red";
- var o = { color: "blue" };
- function sayColor(){
- alert(this.color);
- }
- var objectSayColor = sayColor.bind(o);
- objectSayColor(); //blue
这里, sayColor() 调用 bind() 并传入对象 o, 创建了 objectSayColor() 函数.
object- SayColor() 函数的 this 值等于 o, 因此即使是在全局作用域中调用这个函数, 也会看到 "blue".
来源: http://www.jianshu.com/p/0c4602c5a9f4