`
shonelau
  • 浏览: 17050 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

Javascript 一席谈之一:一种函数,多种定义方式

阅读更多

该系列文章的内容主要来自: 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程序设计课件:函数的定义方式.pptx

    JavaScript是一种广泛应用于网页和网络应用开发的脚本语言,它主要负责处理客户端的交互和动态内容。在JavaScript中,函数是代码复用的核心机制,能够封装特定任务的逻辑,使得代码更加模块化和易于管理。本节将深入...

    javascript_函数大全

    不同于许多传统面向对象的语言,JavaScript通过其独特的函数式特性,提供了一种更加灵活、强大的编程方式。以下是对给定文件中提及的JavaScript函数关键知识点的详尽解析。 #### 如何定义和使用函数 定义函数的...

    javascript 函数教程(由浅入深)

    JavaScript 函数是编程语言的核心部分,它是一种组织代码的方式,使得代码可以被多次重用,降低了程序的复杂性。在JavaScript中,函数是一段可执行的代码块,它能够接收参数,执行特定任务,并可能返回结果。以下是...

    javascript指南和函数式编程

    而《JavaScript函数式.zip》可能是一份关于JavaScript函数式编程的资料集合,函数式编程是一种编程范式,强调使用函数和避免改变状态。其中可能涵盖以下知识点: 1. **纯函数**:理解纯函数的定义,即给定相同的...

    JavaScript定义类或函数的几种方式小结

    JavaScript是一种基于原型的面向对象语言,其类和对象的定义方式与传统面向对象的语言(如Java或C++)有所不同。 JavaScript中定义类或函数,主要有以下几种方式: 1. 工厂方式 工厂方式是JavaScript中创建对象的...

    JavaScript程序设计课件:匿名函数.pptx

    概念:所谓函数表达式指的是将声明的函数赋值给一个变量,通过变量完成函数的调用和参数的传递,它也是JavaScript中另一种实现自定义函数的方式。 5.5 匿名函数 函数表达式 var fn = function sum(num1, num2) { ...

    JavaScript程序设计课件:函数的调用方式.pptx

    JavaScript提供了 4 种函数调用: 一般形式的函数调用 作为对象的方法调用 使用 call 和 apply 动态调用 使用 new 间接调用 5.2.2 函数的调用方式 2、函数的调用方式 一般形式的函数调用,是常见的函数调用方式,...

    JavaScript:异步编程:回调函数与Promise

    Promise是一种处理异步操作的方法,它提供了一种比回调函数更清晰的方式来组织和处理异步代码。Promise代表了一个最终可能会完成或失败的值。 **5.1 Promise的基本概念** - **构造器**:创建一个新的Promise实例。...

    javascript实现根据函数名称字符串动态执行函数的方法示例

    这类技术能够提供一种灵活的方式来执行代码,尤其在进行插件化开发、事件驱动编程或实现钩子函数时非常有用。为了实现这一功能,我们可以通过几种方法来完成函数的动态调用,本篇将重点介绍使用字符串来动态调用函数...

    javascript函数速查

    在JavaScript中,有多种方式来声明函数: 1. `function` 关键字声明:这是最常见的方式,如 `function myFunction() {}`。这种声明方式遵循词法作用域,函数在声明时就已经存在于当前作用域中。 2. 函数表达式:...

    JavaScript:函数与作用域

    函数可以通过两种方式进行定义:函数声明和函数表达式。 - **函数声明**: - **定义**:使用`function`关键字来定义函数。 - **格式**:`function 函数名(参数列表) { 函数体 }` - **示例**: ```javascript ...

    JavaScript实用小函数(一)

    JavaScript是一种广泛应用于网页和网络应用的脚本语言,尤其在前端开发中占据核心地位。这篇博客"JavaScript实用小函数(一)"可能涵盖了JavaScript基础以及一些实用技巧,虽然具体细节未在描述中给出,但我们可以根据...

    JavaScript定义类的几种方式总结

    ES6中引入了class关键字和extends关键字,它们提供了一种更加直观和面向对象的方式来定义和继承类,使得JavaScript的面向对象编程更加接近传统面向对象语言的模式。通过class关键字定义的类,其内部的构造函数方法...

    JavaScript函数式编程.pdf

    由于JavaScript的作用域链,闭包能够访问到函数定义时的外部变量,即使外部函数已经执行结束。闭包通常用于创建私有变量和方法,以及数据封装。 4. 纯函数和副作用 纯函数是指在相同的输入下总是产生相同输出,...

    JavaScript ES6函数式编程入门经典_javascript_

    1. **箭头函数**:箭头函数是ES6引入的一种新的函数定义方式,其语法简洁明了。例如,`const add = (x, y) =&gt; x + y;`,这种写法比传统的`function add(x, y) { return x + y; }`更加紧凑,且箭头函数没有自己的`...

    Javascript的匿名函数

    - **函数式编程**:JavaScript作为一种支持函数式编程的语言,匿名函数是实现这一特性的基石之一。 #### 四、注意事项 - **性能考虑**:虽然`new Function()`构造器提供了很大的灵活性,但它可能会导致性能下降。...

    javascript函数速查手册

    函数有两种声明方式:函数声明和函数表达式。前者具有提升(hoisting)特性,后者则不会。例如: ```javascript // 函数声明 hello(); // 正常执行,无语法错误 function hello() { console.log('Hello!'); } // ...

    JavaScript函数的定义方法及函数的参数和优化.docx

    JavaScript 函数是定义一次但可以调用或执行任意多次的一段 JavaScript 代码。函数可能有参数,即函数被调用时指定了值的局部变量。 JavaScript 函数的定义有两种方法:函数声明式和函数赋值式。 函数声明式...

    javascript强制函数自动执行.pdf

    在JavaScript中,函数是一种非常重要的编程构造,它可以被定义为函数声明或函数表达式。这两种定义方式各有特点,并且在不同的上下文中有着不同的行为。 #### 函数声明(Function Declaration) 函数声明是定义一...

Global site tag (gtag.js) - Google Analytics