JavaScript中创建函数主要有两种方法:函数声明和函数表达式。这两种方式都有不同的适用场景。这篇笔记主要关注的是函数表达式的几大特点以及它的使用场景,下面一一描述。
主要特点
•可选的函数名称
函数名称是函数声明的必需组成部分,这个函数名称相当于一个变量,新定义的函数会复制给这个变量,以后函数的调用都需要通过这个变量进行。而对于函数表达式来说,函数的名称是可选的,例如下面的例子:
var sub = function(a1,a2){ return a1-a2; }
这个例子中函数表达式没有名称,属于匿名函数表达式。再看下面的例子:
var sub = function f(a1,a2){ return a1-a2; } console.log(f(5,3)); //错误调用方式 console.log(sub(5,3)); //正确调用方式
在这个例子中,函数表达式的名称为f,这个名称f实际上变成了函数内部的一个局部变量,并且指代函数对象本身,在函数递归的时候有很大用处,后面会详细讲到。
•在执行阶段创建(区别于函数声明)
这个特点是函数表达式明显区别于函数声明的地方。
解释器在解析JavaScript代码时对于这两种方式并不是一视同仁的。解释器会首先读取函数声明,并使其在执行任何代码之前可用;而对于函数表达式,则必须等到解释器执行到它所在的代码行,才会被真正解析执行。例如:
console.log(add(1,2)); //"3" console.log(sub(5,3)); //"unexpected identifier",报错 function add(a1,a2){ return a1+a2; } var sub = function(a1,a2){ return a1-a2; }
第一条语句完全可以正常执行。对代码求值时,JavaScript引擎在第一遍就会声明函数并通过一个名为函数声明提升的过程将它们放到源代码树的顶部。也就是说在执行环境的创建阶段(函数被调用但还没有开始执行)就会对函数声明进行"hosting"操作。所以,即使声明函数的代码在调用它的代码后面,JavaScript引擎也会把函数声明提升到顶部。但是如果把函数声明更改为函数表达式,就会在执行期间报错。原因在于在执行到函数所在的语句之前,变量sub中并不会包含对函数的引用。也就是说在代码执行阶段,变量sub才会被赋值。除了以上不同,在其它方面函数声明和函数表达式的语法是等价的。
•不会影响变量对象
var sub = function f(a1,a2){ console.log(typeof f); //"function" return a1-a2; } console.log(typeof f); //"Uncaught ReferenceError: f is not defined(…)"
通过上面的例子可以看到,函数名称f只能在函数对象内部使用,函数表达式的函数名称并不存在于变量对象中。
使用场景
函数表达式的使用场景很多。下面主要描述的是函数递归以及代码模块化方面的应用。
•函数递归
看下面的例子:
function factorial(num){ if(num <= 1){ return 1; }else{ return num * factorial(num - 1); } }
这是一个经典的阶乘函数,但是这个例子存在的一个问题是函数名称factorial与函数体紧密耦合在一起,执行下面的语句就会报错:
var anotherFactorial = factorial; factorial = null; console.log(anotherFactorial(5)); //"Uncaught TypeError: factorial is not a function"
报错的原因在于在函数体内部会调用factorial函数,而变量factorial对函数的引用已经被解除所以报错。这种情况的解决方法一般可以使用arguments.callee来解决,arguments.callee始终指向当前的函数,例如:
function factorial(num){ if(num <= 1){ return 1; }else{ return num * arguments.callee(num - 1); } }
这样在此执行anotherFactorial函数就可以得到正确结果了。但是在严格模式"strict"下,arguments.callee是不能通过脚本访问的,这是就可以使用函数表达式来解决这个问题了,例如:
var factorial = (function f(num){ if(num <= 1){ return 1; }else{ return num * f(num - 1); } }); console.log(factorial(5)); //"120"
•代码模块化
JavaScript中没有块级作用域,但我们可以使用函数表达式模块化JavaScript代码。模块化代码中可以封装不必让使用者知道的细节,只暴露给使用者相关接口,同时可以避免对全局环境的污染,例如:
var person = (function(){ var _name = ""; return{ getName:function(){ return _name; }, setName:function(newname){ _name = newname; } }; }()); person.setName("John"); person.getName(); //"John"
这个例子中创建了一个匿名函数表达式,这个函数表达式中包含了模块自身的私有变量和函数;这个函数表达式的执行结果返回一个对象,对象中包含了模块暴露给使用者的公共接口。代码模块化的具体形式还有很多,例如在一些常用的JavaScript库中通常都会使用类似下面例子的立即执行函数:
(function(){ var _name = ""; var root = this; var person = { getName: function(){ return _name; }, setName: function(newname){ _name = newname; } }; root.person = person; }.call(this)); person.setName("John"); person.getName(); //"John"
这种方式直接将包含模块公共接口的对象作为全局对象的一个属性,这样在其它地方直接可以使用全局对象的这个属性来使用这个模块了。
以上这篇老生常谈JavaScript 函数表达式就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持小牛知识库。
本文向大家介绍老生常谈JavaScript 正则表达式语法,包括了老生常谈JavaScript 正则表达式语法的使用技巧和注意事项,需要的朋友参考一下 JavaScript定义正则表达式有两种方法。 1.RegExp构造函数 var pattern = new RegExp("[bc]at","i"); 它接收两个参数:一个是要匹配的字符串模式,另一个是可选的标志字符串。 2.字面量 var pa
本文向大家介绍老生常谈JavaScript数组的用法,包括了老生常谈JavaScript数组的用法的使用技巧和注意事项,需要的朋友参考一下 JavaScript数组简介 JavaScript中的数组与其他语言中的数组是不同的,主要体现在: 数组中存储的各项可以是不同类型的数据 数组的大小是动态变化的,当新增项时或移除项时可以动态的改变大小来容纳当前数据项 在JavaScript中创建数组 在Jav
本文向大家介绍老生常谈JavaScript中的this关键字,包括了老生常谈JavaScript中的this关键字的使用技巧和注意事项,需要的朋友参考一下 相对于很多其他的面向对象语言来说,this代表的就是当前对象。例如Java中的this就是编译期间确定的。而在 JavaScript 中,this 是动态绑定,或称为运行期绑定的 Java中的this 在以下代码中。this代表的就是p对象。
本文向大家介绍老生常谈 关于JavaScript的类的继承,包括了老生常谈 关于JavaScript的类的继承的使用技巧和注意事项,需要的朋友参考一下 其实最一开始学JS的时候就看过继承的实现。当时只是去试着理解从书上看来的代码段而已。今天又重新思考了一下,感觉这是一个思维探索演进的结果。 继承,即复用。 如果抛开继承的固有思想,让b复用a的成员,最简单粗暴的做法, b=a; 那么,问题来了: 对
本文向大家介绍老生常谈javascript的面向对象思想,包括了老生常谈javascript的面向对象思想的使用技巧和注意事项,需要的朋友参考一下 面向对象的三大基本特性 封装(把相关的信息(无论数据或方法)存储在对象中的能力) 继承(由另一个类(或多个类)得来类的属性和方法的能力) 多态(一个对象在不同情况下的多种形态) 定义类或对象 第一种:基于Object对象 缺点:不能创建多个对象。 第二
本文向大家介绍老生常谈java中的Future模式,包括了老生常谈java中的Future模式的使用技巧和注意事项,需要的朋友参考一下 jdk1.7.0_79 本文实际上是对上文《简单谈谈ThreadPoolExecutor线程池之submit方法》的一个延续或者一个补充。在上文中提到的submit方法里出现了FutureTask,这不得不停止脚步将方向转向Java的Future模式。 Futur