该系列文章的内容主要来自: Pro JavaScript with Mootools.作者: Mark Joseph Obceca
在讨论js的函数前,一定要明白js里函数实现的两个特点:
1. js的函数是第一类的函数,也就是说: js的函数可以赋值给变量,作为其他函数的参数,可以作为其他函数的返回值。简单的说:js的函数和js里的其他数据一样,都是对象。这一点和java是不同的。和 c的函数指针,python的函数很像。
2. js定义函数时,允许函数的嵌套,也就是说:定义函数时,在函数体内可以定义新的函数。这一点 和C,和java的函数定义不同,但是 和python的函数定义是相同的。
(js的函数实现,很大程度上更类似与python)
因为js的函数的以上两个特点,使js的闭包得以实现。同时,js的函数里变量的作用域规则也变得比较复杂。
现在正式开始讨论js的函数。
一种函数类型,多种定义方式:
javascript 定义里,只有一种函数类型,但是可以通过不同的方法去定义函数的实现,也就是说函数的定义有多种方式。这些函数定义方式中,大多都通过一种叫: 函数字面量
的语法去定义函数。
函数字面量长的是这个样子滴:
function Identifier(FormalParameters,...){
function body
}
因为函数是对象,函数也可以有自己的方法和属性。这里我们先引入两个基本的属性:
name: function identifier的字符串值
length: 函数定义时,形参的个数
函数定义方式1: 函数声明
函数声明是函数定义方式中最简单的一种。如下代码通过函数声明的方式定义了一个新的函数add:
//a function call add
function add(a,b){
return a+b
};
console.log(typeof add); //function
console.log(add.name); //add
console.log(add.length); //2
console.log(add(20,5)); //25
(关于console.log :类似于alert,但是直接在浏览器的js console显示结果,省去了用alert时没完没了的点击"确定"的操作)
函数声明中, function identifier是必需的,用该标识符在函数定义的当前作用域里生成一个变量,该变量的值就是定义的函数。
(关于作用域:可以从js解释器的角度去理解: js解释器解释执行js代码时:会根据代码的执行,进入不同的"作用域",后文会有详细说明) 在我们的例子中add变量是生成在global作用域中,add变量具有name属性,该属性的值就是函数标识符: add。add变量的length属性是2.因为我们定义该函数时,使用了2个形参。
js的作用域是基于文法的作用域,也就是说,标识符的作用域是基于代码文件中定义的位置,而不是执行时的位置。(这点python也一样如此)
举例如下:
//outer function ,global scope
function outer(){
//inner function, local scope
function inner(){
//...
}
}
//check the outer function
console.log(typeof outer); //'function'
//run outer to create the new function
outer();
//check the inner function:
console.log(typeof inner); // 'undefined'
上例中,outer变量是的作用域是global的, 调用outer 时,outer函数生成一个名字叫inner的变量,以函数声明形式定义的inner函数产生的变量的作用域仅在outer函数定义的内部有效.
因为函数声明生成了一个和函数标识符同名的变量,因此会有覆盖当前作用域中同名变量的情况:
举例如下:
//a variable in the surrent scope
var items =1;
// a function declaration with the same name
function items(){
//...
}
console.log(typeof items); // 'function' ,NOT 'number'
函数定义方式2: 函数表达式
这种定义函数的方式利用了:函数可以存储在变量中 这一事实.
以下,我们同样定义一个add函数,但是使用的是函数表达式的方式。
var add = function(a,b){
return a+b
};
console.log(typeof add); // 'function'
console.log(add.name); // ' ' or 'anonymous'
console.log(add,length);//2
console.log(add(20,5)) ; //25
在上面的例子中,我们把函数字面量定义赋值给了变量add. 上例中函数的length属性和函数声明中的是一样的,但是name属性却不同,这是因为,我们在函数字面量定义中,没有定义函数标识符,一些js的解释器会把name定义为' ',有些会定义成'anonymous'.
函数声明的作用域规则和函数表达式的作用域规则有稍稍的不同,因为函数表达式的作用域由存储函数的变量的作用域决定。让我们牢记一点: js中,var 关键字定义了一个当前作用域的局部变量,省略var关键字会生成一个全局的变量。
举例如下:
// outer function, global scope
var outer = function(){
// inner function, local scope
var localInner = function(){
// ...
};
// inner function, global scope
globalInner = function(){
// ...
};
};
// check the outer function
console.log(typeof outer);// 'function'
// run outer to create the new functions
outer();
// check the new functions
console.log(typeof localInner); // 'undefined'
console.log(typeof globalInner); // 'function'
outer变量被var限定,但是outer定义在global作用域下,global作用域的局部变量就是全局变量。至于定义在outer内部的 localInner和globalInner作用域规则如我们刚刚牢记的:
js中,var 关键字定义了一个当前作用域的局部变量,省略var关键字会生成一个全局的变量。
函数定义方式3: 命名的函数表达式
函数表达式经常使用匿名的函数字面量定义,但是也能在函数字面量定义中显式的定义一个函数标识符。这种函数表达式的变体叫做: 命名的函数表达式
。
举例如下:
var add = function add(a, b){
return a + b;
};
console.log(typeof add); // 'function'
console.log(add.name); // 'add'
console.log(add.length); // 2
console.log(add(20, 5)); // 25
这个例子和函数表达式的唯一不同就是我们在定义函数字面量时,给函数定义了一个标识符,意味着add的name属性不再为空,或anonymous。 命名的函数表达式的之所以要有一个名字,就是为了在函数定义内部访问函数自己。为什么我们需要这个功能呢?还是看例子吧:
例子1:
var myFn = function(){
// reference the function
console.log(typeof myFn);
};
myFn(); // 'function'
这个例子很简单,没什么可说的,但是让我们看下一个例子:
例子2:
// global scope
var createFn = function(){
// result function
return function(){
console.log(typeof myFn);
};
};
// different scope
(function(){
// put the result function of `createFn`
// into a local variable
var myFn = createFn();
// check if reference is available
myFn(); // 'undefined'
})();
在global作用域中,我们定义一个createFn的函数,该函数返回一个如同例子1的log功能的函数。然后我们定义了一个单次执行的匿名函数,在该匿名函数的local左右域中定义了一个myFn变量,并把createFn的返回值(一个函数)赋给了myFn。
例子2,和例子1试图实现的功能差不多,只是有两个变化:1,用调用函数的返回一个函数,而不是用函数字面量定义函数。2,变量myFn在另外一个local左右域中。 js的作用域是文法作用域,在createFn中,myFn是不可见的,因此,在单次执行的匿名函数中,myFn的调用结果是:‘undefined’ 而不是’function‘。(关于左右域规则,本系列之二有详述)
通过给我们在createFn中返回的函数定义一个函数标识符,我们即可解决这个问题。
例子3:
// global scope
var createFn = function(){
// result function
return function myFn(){
console.log(typeof myFn);
};
};
// different scope
(function(){
// put the result function of `createFn`
// into a local variable
var myFn = createFn();
// check if reference is available
myFn(); // 'function'
})();
为函数增加一个显式定义的函数标示符,就如同生成一个在该函数内部可用的指向该函数本身的新的变量。命名的函数表达式和函数表达式定义的函数的作用域规则相同:该函数赋予的变量的作用域决定了该函数是local的还是global的。但是命名函数表达式中新加的名字,具有不同的作用域规则:该名字仅在函数定义的内部可见。
如下例:
//a function with different identifier
var myFn = function fnID(){
console.log(typeof fnID);
}
//the variable
console.log(typeof myFn);//'function'
//the identifier
console.log(typeof fnID);//'undefined'
myFn();// 'function'
该例子显示,在global 作用域下,myFn可以用来引用定义的函数,但是fnID不可以。然而在函数内部,是可以通过fnID对函数进行引用。
函数定义方式4: 单次执行函数
我们在生成函数表达式时,接触过匿名函数,匿名函数有更广阔的用途。一种最重要的用途就是用匿名函数定义一个函数,并且马上执行该函数,而不存储对该函数的任何变量引用。 这种定义函数的方式被称作:单次执行函数。
举例如下:
//create a function and invoke it immediately
(function(){
var msg = 'hello world';
console.log(msg);//'hello word'
})();
在上例子中,我们用括号包裹了一个函数定义字面量,然后用函数调用操作符()去立即执行该函数。该函数没有存储在变量中,也没有对该函数生成任何引用。这一个单次运行,既抛型函数:生成该函数,干活,然后消失。
为了理解单次执行函数的工作原理,我们应该记得:function是对象,而对象就是数值。js的数值可以被立即使用而不用存储在变量中,因此我们也可以生成一个匿名函数然后通过函数调用操作符()去立即执行该函数。
另外,我们应该注意到,前例中,我们用了一对括号去包裹了我们定义的函数,而不是这样:
//create a function and invoke it immediately
function(){
var msg = 'hello world';
console.log(msg);//'hello word'
}();
若如此,js解释器会报语法错误。因为当解释器遇到如上的代码行,它会解释把上面的代码解释成一个函数声明。解释器看到一个函数声明,就要去找函数标识符,结果找不到,于是报错。
我们需要给函数定义包裹一个括号,告知js 解释器,这不是一个函数声明,而是我们要生成一个函数,并且立即使用。因为我们定义该函数时没有标示符去引用该函数,因此我们需要用一对包裹的括号来做直接引用,然后直接调用。
注意:函数调用操作符()可以出现在包裹函数的括号的里面,也可以出现在外面。象: (function(){...}())也是可以的。但是把函数调用操作符()放包裹的括号外面更常见一些。
单次执行函数非常有用,其中最重要的用途就是保持变量和标示符处在一个本地的,受保护的作用域中,
如下例:
//top level scope
var a=1;
//localize scope with a single execution function
(function(){
//local scope
var a=2;
})();
console.log(a); //1
我们第一个变量在顶层的作用域中声明,使该变量全局可见。我们在单次执行函数中,有声明了一个本地的变量。把该本地变量的值改变成2,但是顶层变量的值并不受影响。
这种应用很常见,特别是在开发库的时候。因为本地变量处于一个独立的左右域中,这样救避免了标识符的冲突。若你在你的应用中有两个脚本定义了同样的标识符,则两个标识符冲突导致覆盖的几率就很高了,除非两个脚本之一用单次执行函数把左右域本地化。
单次执行函数的另外一个特点是,可以像函数声明那样定义一个函数标识符:
(function myFn(){
console.log(typeof myFn); //'function'
})();
console.log(typeof myFn);//'undefined'
看起来象函数声明,其实是单次执行函数。尽管我们为函数定义了一个标识符,但是却不会象函数声明那样,在当前作用域中产生一个对应的变量。这个标识符仅允许你在函数定义内部引用该函数。
和其他函数一样,单次执行函数也可以有参数,和单次执行函数的标识符结合,就可以生成一个立马可运行的迭代函数:
var number=12;
var numberFactorial = (function factorial(number){
return (number==0)?1:number*factorial(number-1);
})(number);
console.log(numberFactorial);// 479001600
函数定义方式5: Function 对象
最后一种函数定义方式Function对象,和其他的定义方式都不相同,因为这种方式,不使用函数字面量去定义函数。这种定义函数方式的基本语法如下:
// a function object
new Function('FormalArgument1',"FormalArgument2",...,'FunctionBody');
我们用传递字符串做参数给Function构建器,来生成函数。
举例如下:
var add = new Function('a','b','return a+b;');
console.log(typeof add);//'function'
console.log(add.name);//' ' or 'anonymous'
console.log(add.length);//2
console.log(add(20,5));//25
Function 对象定义的函数一个最大的特点: 该函数的定义里,js解释器对函数里出现的变量是从global作用域里去解析的。
举例如下:
//global varibal
var x=1;
//localized scope
(function(){
//local x variable
var x=5;
// a function object
var myFn = new Function('console.log(x);');
myFn(); //1, not 5
})();
以上就是函数定义的五种方式。
当然,函数还有参数,返回值等问题可以讨论,但是其他的很多书已经讨论的很清楚,就不再次多说了。
P.S. 对iteye的编辑器不熟悉,导致前面发的很多代码段前面都会出现:
<span style=
"font-size: small;"
>,后面出现</span>的情况,略去即可
分享到:
相关推荐
JavaScript是一种广泛应用于网页和网络应用开发的脚本语言,它主要负责处理客户端的交互和动态内容。在JavaScript中,函数是代码复用的核心机制,能够封装特定任务的逻辑,使得代码更加模块化和易于管理。本节将深入...
JavaScript 函数是编程语言的核心部分,它是一种组织代码的方式,使得代码可以被多次重用,降低了程序的复杂性。在JavaScript中,函数是一段可执行的代码块,它能够接收参数,执行特定任务,并可能返回结果。以下是...
而《JavaScript函数式.zip》可能是一份关于JavaScript函数式编程的资料集合,函数式编程是一种编程范式,强调使用函数和避免改变状态。其中可能涵盖以下知识点: 1. **纯函数**:理解纯函数的定义,即给定相同的...
JavaScript是一种基于原型的面向对象语言,其类和对象的定义方式与传统面向对象的语言(如Java或C++)有所不同。 JavaScript中定义类或函数,主要有以下几种方式: 1. 工厂方式 工厂方式是JavaScript中创建对象的...
概念:所谓函数表达式指的是将声明的函数赋值给一个变量,通过变量完成函数的调用和参数的传递,它也是JavaScript中另一种实现自定义函数的方式。 5.5 匿名函数 函数表达式 var fn = function sum(num1, num2) { ...
JavaScript提供了 4 种函数调用: 一般形式的函数调用 作为对象的方法调用 使用 call 和 apply 动态调用 使用 new 间接调用 5.2.2 函数的调用方式 2、函数的调用方式 一般形式的函数调用,是常见的函数调用方式,...
这类技术能够提供一种灵活的方式来执行代码,尤其在进行插件化开发、事件驱动编程或实现钩子函数时非常有用。为了实现这一功能,我们可以通过几种方法来完成函数的动态调用,本篇将重点介绍使用字符串来动态调用函数...
在JavaScript中,有多种方式来声明函数: 1. `function` 关键字声明:这是最常见的方式,如 `function myFunction() {}`。这种声明方式遵循词法作用域,函数在声明时就已经存在于当前作用域中。 2. 函数表达式:...
函数可以通过两种方式进行定义:函数声明和函数表达式。 - **函数声明**: - **定义**:使用`function`关键字来定义函数。 - **格式**:`function 函数名(参数列表) { 函数体 }` - **示例**: ```javascript ...
JavaScript是一种广泛应用于网页和网络应用的脚本语言,尤其在前端开发中占据核心地位。这篇博客"JavaScript实用小函数(一)"可能涵盖了JavaScript基础以及一些实用技巧,虽然具体细节未在描述中给出,但我们可以根据...
ES6中引入了class关键字和extends关键字,它们提供了一种更加直观和面向对象的方式来定义和继承类,使得JavaScript的面向对象编程更加接近传统面向对象语言的模式。通过class关键字定义的类,其内部的构造函数方法...
由于JavaScript的作用域链,闭包能够访问到函数定义时的外部变量,即使外部函数已经执行结束。闭包通常用于创建私有变量和方法,以及数据封装。 4. 纯函数和副作用 纯函数是指在相同的输入下总是产生相同输出,...
1. **箭头函数**:箭头函数是ES6引入的一种新的函数定义方式,其语法简洁明了。例如,`const add = (x, y) => x + y;`,这种写法比传统的`function add(x, y) { return x + y; }`更加紧凑,且箭头函数没有自己的`...
- **函数式编程**:JavaScript作为一种支持函数式编程的语言,匿名函数是实现这一特性的基石之一。 #### 四、注意事项 - **性能考虑**:虽然`new Function()`构造器提供了很大的灵活性,但它可能会导致性能下降。...
函数有两种声明方式:函数声明和函数表达式。前者具有提升(hoisting)特性,后者则不会。例如: ```javascript // 函数声明 hello(); // 正常执行,无语法错误 function hello() { console.log('Hello!'); } // ...
JavaScript 函数是定义一次但可以调用或执行任意多次的一段 JavaScript 代码。函数可能有参数,即函数被调用时指定了值的局部变量。 JavaScript 函数的定义有两种方法:函数声明式和函数赋值式。 函数声明式...
在JavaScript中,函数是一种非常重要的编程构造,它可以被定义为函数声明或函数表达式。这两种定义方式各有特点,并且在不同的上下文中有着不同的行为。 #### 函数声明(Function Declaration) 函数声明是定义一...
JavaScript支持两种函数定义方式:函数声明语句和函数定义表达式。 1. **函数声明语句**:在JavaScript中,可以使用`function`关键字来声明一个函数。例如: ```javascript function fact(x) { // 函数体 } ``...