跟我学习javascript的函数和函数表达式

1、函数声明与函数表达式

在ECMAScript中,创建函数的最常用的两个方法是函数表达式和函数声明,两者期间的区别是有点晕,因为ECMA规范只明确了一点:函数声明必须带有标示符(Identifier)(就是大家常说的函数名称),而函数表达式则可以省略这个标示符:

函数声明:function 函数名称 (参数:可选){ 函数体 }

函数表达式:function 函数名称(可选)(参数:可选){ 函数体 }

所以,可以看出,如果不声明函数名称,它肯定是表达式,可如果声明了函数名称的话,如何判断是函数声明还是函数表达式呢?ECMAScript是通过上下文来区分的,如果function foo(){}是作为赋值表达式的一部分的话,那它就是一个函数表达式,如果function foo(){}被包含在一个函数体内,或者位于程序的最顶部的话,那它就是一个函数声明。

function foo(){} // 声明,因为它是程序的一部分
var bar = function foo(){}; // 表达式,因为它是赋值表达式的一部分

new function bar(){}; // 表达式,因为它是new表达式

(function(){
 function bar(){} // 声明,因为它是函数体的一部分
})();

表达式和声明存在着十分微妙的差别,首先,函数声明会在任何表达式被解析和求值之前先被解析和求值,即使你的声明在代码的最后一行,它也会在同作用域内第一个表达式之前被解析/求值,参考如下例子,函数fn是在alert之后声明的,但是在alert执行的时候,fn已经有定义了:

alert(fn());

function fn() {
 return 'Hello world!';
}

另外,还有一点需要提醒一下,函数声明在条件语句内虽然可以用,但是没有被标准化,也就是说不同的环境可能有不同的执行结果,所以这样情况下,最好使用函数表达式: 因为在条件语句中没有块级作用域这个概念

// 千万别这样做!
// 因为有的浏览器会返回first的这个function,而有的浏览器返回的却是第二个

if (true) {
 function foo() {
 return 'first';
 }
}
else {
 function foo() {
 return 'second';
 }
}
foo();

// 相反,这样情况,我们要用函数表达式
var foo;
if (true) {
 foo = function() {
 return 'first';
 };
}
else {
 foo = function() {
 return 'second';
 };
}
foo();

函数声明的实际规则如下:

函数声明只能出现在程序或函数体内。从句法上讲,它们 不能出现在Block(块)({ … })中,例如不能出现在 if、while 或 for 语句中。因为 Block(块) 中只能包含Statement语句, 而不能包含函数声明这样的源元素。另一方面,仔细看一看规则也会发现,唯一可能让表达式出现在Block(块)中情形,就是让它作为表达式语句的一部分。但是,规范明确规定了表达式语句不能以关键字function开头。而这实际上就是说,函数表达式同样也不能出现在Statement语句或Block(块)中(因为Block(块)就是由Statement语句构成的)。

2、命名函数表达式

提到命名函数表达式,理所当然,就是它得有名字,前面的例子var bar = function foo(){};就是一个有效的命名函数表达式,但有一点需要记住:这个名字只在新定义的函数作用域内有效,因为规范规定了标示符不能在外围的作用域内有效:

var f = function foo(){
 return typeof foo; // function --->foo是在内部作用域内有效
};
// foo在外部用于是不可见的
typeof foo; // "undefined"
f(); // "function"

既然,这么要求,那命名函数表达式到底有啥用啊?为啥要取名?

正如我们开头所说:给它一个名字就是可以让调试过程更方便,因为在调试的时候,如果在调用栈中的每个项都有自己的名字来描述,那么调试过程就太爽了,感受不一样嘛。

tips:这里提出一个小问题:在ES3中,命名函数表达式的作用域对象也继承了 Object.prototype 的属性。这意味着仅仅是给函数表达式命名也会将 Object.prototype 中的所有属性引入到作用域中。结果可能会出人意料。

var constructor = function(){return null;}
var f = function f(){
 return construcor();
}
f(); //{in ES3 环境}

该程序看起来会产生 null, 但其实会产生一个新的对象。因为命名函数表达式在其作用域内继承了 Object.prototype.constructor(即 Object 的构造函数)。就像 with 语句一样,这个作用域会因 Object.prototype 的动态改变而受到影响。幸运的是,ES5 修正了这个错误。

这种行为的一个合理的解决办法是创建一个与函数表达式同名的局部变量并赋值为 null。即使在没有错误地提升函数表达式声明的环境中,使用 var 重声明变量能确保仍然会绑定变量 g。设置变量 g 为 null 能确保重复的函数可以被垃圾回收。

var f = function g(){
 return 17;
}
var g =null;

3、调试器(调用栈)中的命名函数表达式

刚才说了,命名函数表达式的真正用处是调试,那到底怎么用呢?如果一个函数有名字,那调试器在调试的时候会将它的名字显示在调用的栈上。有些调试器(Firebug)有时候还会为你们函数取名并显示,让他们和那些应用该函数的便利具有相同的角色,可是通常情况下,这些调试器只安装简单的规则来取名,所以说没有太大价值,我们来看一个例子:不用命名函数表达式

function foo(){
 return bar();
}
function bar(){
 return baz();
}
function baz(){
 debugger;
}
foo();

// 这里我们使用了3个带名字的函数声明
// 所以当调试器走到debugger语句的时候,Firebug的调用栈上看起来非常清晰明了
// 因为很明白地显示了名称
baz
bar
foo
expr_test.html()

通过查看调用栈的信息,我们可以很明了地知道foo调用了bar, bar又调用了baz(而foo本身有在expr_test.html文档的全局作用域内被调用),不过,还有一个比较爽地方,就是刚才说的Firebug为匿名表达式取名的功能:

function foo(){
 return bar();
}
var bar = function(){
 return baz();
}
function baz(){
 debugger;
}
foo();

// Call stack
baz
bar() //看到了么?
foo
expr_test.html()

然后,当函数表达式稍微复杂一些的时候,调试器就不那么聪明了,我们只能在调用栈中看到问号:

function foo(){
 return bar();
}
var bar = (function(){
 if (window.addEventListener) {
 return function(){
  return baz();
 };
 }
 else if (window.attachEvent) {
 return function() {
  return baz();
 };
 }
})();
function baz(){
 debugger;
}
foo();

// Call stack
baz
(?)() // 这里可是问号哦,显示为匿名函数(anonymous function)
foo
expr_test.html()

另外,当把函数赋值给多个变量的时候,也会出现令人郁闷的问题:

function foo(){
 return baz();
}
var bar = function(){
 debugger;
};
var baz = bar;
bar = function() {
 alert('spoofed');
};
foo();

// Call stack:
bar()
foo
expr_test.html()

这时候,调用栈显示的是foo调用了bar,但实际上并非如此,之所以有这种问题,是因为baz和另外一个包含alert(‘spoofed')的函数做了引用交换所导致的。

归根结底,只有给函数表达式取个名字,才是最委托的办法,也就是使用命名函数表达式。我们来使用带名字的表达式来重写上面的例子(注意立即调用的表达式块里返回的2个函数的名字都是bar):

function foo(){
 return bar();
}
var bar = (function(){
 if (window.addEventListener) {
 return function bar(){
  return baz();
 };
 }
 else if (window.attachEvent) {
 return function bar() {
  return baz();
 };
 }
})();
function baz(){
 debugger;
}
foo();

// 又再次看到了清晰的调用栈信息了耶!
baz
bar
foo
expr_test.html()

好的,整个文章结束,大家对javascript的认识又近了一步,希望大家越来越喜欢小编为大家整理的文章,继续关注跟我学习javascript的一系列文章。

(0)

相关推荐

  • JavaScript基础篇(6)之函数表达式闭包

    其实js支持函数闭包的主要原因是因为js需要函数能够保存数据.这里的保存数据是只函数在运行结束以后函数内变量的值也会进行保存.至于为什么js需要在函数内可以保存数据,那就是js是一种函数式语言.在函数内保存数据是函数式语言的一大特征. 回顾前面介绍过的三种定义函数方式 functiosu(numnumreturnunum//函数声明语法定义 vasufunction(numnum)returnunum}//函数表达式定义 vasuneFunction("num""num&qu

  • 浅谈javascript 函数表达式和函数声明的区别

    javascript中声明函数的方法有两种:函数声明式和函数表达式. 区别如下: 1).以函数声明的方法定义的函数,函数名是必须的,而函数表达式的函数名是可选的. 2).以函数声明的方法定义的函数,函数可以在函数声明之前调用,而函数表达式的函数只能在声明之后调用. 3).以函数声明的方法定义的函数并不是真正的声明,它们仅仅可以出现在全局中,或者嵌套在其他的函数中,但是它们不能出现在循环,条件或者try/catch/finally中,而     函数表达式可以在任何地方声明. 下面分别用两种方法定

  • 老生常谈JavaScript 函数表达式

    JavaScript中创建函数主要有两种方法:函数声明和函数表达式.这两种方式都有不同的适用场景.这篇笔记主要关注的是函数表达式的几大特点以及它的使用场景,下面一一描述. 主要特点 •可选的函数名称 函数名称是函数声明的必需组成部分,这个函数名称相当于一个变量,新定义的函数会复制给这个变量,以后函数的调用都需要通过这个变量进行.而对于函数表达式来说,函数的名称是可选的,例如下面的例子: var sub = function(a1,a2){ return a1-a2; } 这个例子中函数表达式没有

  • JavaScript中函数声明与函数表达式的区别详解

    前言 在ECMAScript中,有两个最常用的创建函数对象的方法,即使用函数表达式或者使用函数声明.对此,ECMAScript规范明确了一点,即是,即函数声明 必须始终带有一个标识符(Identifier),也就是我们所说的函数名,而函数表达式则可以省略.下面看看这两者的详细区别介绍. 什么是 Function Declaration(函数声明)? Function Declaration 可以定义命名的函数变量,而无需给变量赋值.Function Declaration 是一种独立的结构,不能

  • 浅析javascript函数表达式

    开始学习javascript函数表达式,仔细阅读下文. 1.一般形式的创建函数,在执行代码之前会先读取函数声明,所以可以把函数声明写在函数调用的下面: sayHi(); function sayHi(){ alert("Hi!"); } 2.使用函数表达式创建函数,调用前必须先赋值: sayHi(); //错误!!函数不存在 var sayHi=function(){ alert("Hi!"); } 3.递归 一般递归 function factorial(num)

  • javascript高级编程之函数表达式 递归和闭包函数

    定义函数表达式有两种方式:函数声明和函数表达式. 函数声明如下: function functionName(arg0,arg1,arg2){ //函数体 } 首先是function关键字,然后是函数的名字. FF,Safrai,Chrome和Opera都给函数定义了一个非标准的name属性,通过这个属性可以访问到函数指定的名字.这个函数的值永远等于跟在function关键字后面的标识符. //只在FF,Safari,Chrome和Opera有效 alert(functionName.name)

  • 全面解析JS字符串和正则表达式中的match、replace、exec等函数

    正则表达式(regular expression)描述了一种字符串匹配的模式,可以用来检查一个串是否含有某种子串.将匹配的子串做替换或者从某个串中取出符合某个条件的子串等. 正则表达式由于不经常使用,所以容易经常忘记,下面小编把常用的函数和功能,简明扼要的罗列在此,以备日后查看: RegExp对象的函数常用的有2个 1.test函数 用法:RegExpObject.test(string) 返回:如果字符串 string 中含有与 RegExpObject 匹配的文本,则返回 true,否则返回

  • 菜鸟学习JavaScript小实验之函数引用

    复制代码 代码如下: function tt() { alert(11); } var b = tt; var b1 = tt(); //执行tt()函数功能 对于函数的引用,可直接将函数名"tt"赋给变量,但不可以是函数名+括号的形式"tt()",因为第二种是直接执行该函数. 调用函数的时候,用变量名+括号的形式,如:b(). 复制代码 代码如下: function tt() { alert(11); } var b = tt; alert(b); functio

  • 跟我学习javascript的var预解析与函数声明提升

    1.var 变量预编译 JavaScript 的语法和 C .Java.C# 类似,统称为 C 类语法.有过 C 或 Java 编程经验的同学应该对"先声明.后使用"的规则很熟悉,如果使用未经声明的变量或函数,在编译阶段就会报错.然而,JavaScript 却能够在变量和函数被声明之前使用它们.下面我们就深入了解一下其中的玄机. 先来看一段代码: (function() { console.log(noSuchVariable);//ReferenceError: noSuchVari

  • 轻松学习Javascript闭包函数

    闭包函数是什么?在开始学习的闭包的时候,大家很能都比较难理解.就从他的官方解释来说,都是比较概念化的. 不过我们也还是从闭包的含义出发. 闭包是指函数有自由独立的变量.换句话说,定义在闭包中的函数可以"记忆"它创建时候的环境. 官方解释说完后,我们先来看一个简单计数的例子. var c = 0; function count(){ c++; } count();// 1 count();// 2 这个例子是利用了全局变量来实现,但是这里有个问题是,c这个变量也容易被其他方式所调用,这时

  • 学习javascript的闭包,原型,和匿名函数之旅

    本文通过示例给大家介绍javascript的闭包,原型,和匿名函数,具体详情请看下文. 一 .>关于闭包 理解闭包 需要的知识 1.变量的作用域 例1: var n =99; //建立函数外的全局变量 function readA(){ alert(n); //读取全局变量 } readA(); //执行此函数 例2: function readB(){ var c = 9; function readC(){ console.log(c); //ok c可见 } return readC; }

  • 跟我学习javascript的函数和函数表达式

    1.函数声明与函数表达式 在ECMAScript中,创建函数的最常用的两个方法是函数表达式和函数声明,两者期间的区别是有点晕,因为ECMA规范只明确了一点:函数声明必须带有标示符(Identifier)(就是大家常说的函数名称),而函数表达式则可以省略这个标示符: 函数声明:function 函数名称 (参数:可选){ 函数体 } 函数表达式:function 函数名称(可选)(参数:可选){ 函数体 } 所以,可以看出,如果不声明函数名称,它肯定是表达式,可如果声明了函数名称的话,如何判断是函

  • 深入学习JavaScript 高阶函数

    高阶函数 高阶函数英文叫 Higher-order function,它的定义很简单,就是至少满足下列一个条件的函数: 接受一个或多个函数作为输入 输出一个函数 也就是说高阶函数是对其他函数进行操作的函数,可以将它们作为参数传递,或者是返回它们. 简单来说,高阶函数是一个接收函数作为参数传递或者将函数作为返回值输出的函数. 函数作为参数传递 JavaScript 语言中内置了一些高阶函数,比如 Array.prototype.map,Array.prototype.filter 和 Array.

  • JavaScript学习笔记之取值函数getter与取值函数setter详解

    目录 取值函数getter和存值函数setter 使用get与set函数有两个好处 取值函数getter和存值函数setter get和set是两个关键字,用于对某个属性设置存值函数和取值函数,拦截该属性的存取行为. 那么,这两个东西要怎么用呢?而且他们和我们的平日里写的业务又是怎么练习起来的呢? 首先,我们先看一段恩简单的代码: var person={ myname:'' } person.myname='lsh' console.log(person.myname); 相信大家一眼就看出来

  • JavaScript中立即执行函数实例详解

    前言 js立即执行函数可以让你的函数在创建后立即执行,js立即执行函数模式是一种语法,可以让你的函数在定义后立即被执行,这种模式本质上就是函数表达式(命名的或者匿名的),在创建后立即执行. ( function(){-} )()和( function (){-} () )是两种javascript立即执行函数的常见写法,最初我以为是一个括号包裹匿名函数,再在后面加个括号调用函数,最后达到函数定义后立即执行的目的,后来发现加括号的原因并非如此. 下面话不多说了,来一起看看详细的介绍吧. 通常我们声

  • JavaScript中的工厂函数(推荐)

    在学习jQuery的时候,我们经常会看到"工厂函数"这个概念,那么究竟什么是"工厂函数"呢?我们来看看概念,"所谓工厂函数,就是指这些内建函数都是类对象,当你调用他们时,实际上是创建了一个类实例".意思就是当我调用这个函数,实际上是先利用类创建了一个对象,然后返回这个对象.由于Javascript本身不是严格的面向对象的语言(不包含类),实际上来说,Javascript并没有严格的"工厂函数",但是在Javascript中,我

  • Javascript日期格式化format函数的使用方法

    前言 Javascript日期格式化在日常开发中还是挺常见的,那么下面就给大家分享Javascript时间格式format函数的两种使用方法示例,一起来看看. 方法一 Date.prototype.pattern=function(fmt) { var o = { "M+" : this.getMonth()+1, //月份 "d+" : this.getDate(), //日 "h+" : this.getHours()%12 == 0 ? 1

随机推荐