微信号:FrontDev

介绍:分享 Web 前端相关的技术文章、工具资源、精选课程、热点资讯

函数声明和函数表达式的区别

2016-08-24 21:36 前端大全

(点击上方公众号,可快速关注)


原文:Angus Croll

译文:伯乐在线专栏作者 - 古鲁伊

链接:http://web.jobbole.com/87534/

点击 → 了解如何加入专栏作者


我们从一些小测试开始。以下情况都会弹出什么结果?


题 1:


function foo(){

    function bar() {

        return 3;

    }

    return bar();

    function bar() {

        return 8;

    }

}

alert(foo());


题 2:


function foo(){

    var bar = function() {

        return 3;

    };

    return bar();

    var bar = function() {

        return 8;

    };

}

alert(foo());


题 3:


alert(foo());

function foo(){

    var bar = function() {

        return 3;

    };

    return bar();

    var bar = function() {

        return 8;

    };

}


题 4:


function foo(){

    return bar();

    var bar = function() {

        return 3;

    };

    var bar = function() {

        return 8;

    };

}

alert(foo());


如果你的答案不是8、3、3和 [Type Error: bar is not a function] 的话,就继续往下读吧……(即使答对了也要继续读哦)


什么是 Function Declaration(函数声明)?


Function Declaration 可以定义命名的函数变量,而无需给变量赋值。Function Declaration 是一种独立的结构,不能嵌套在非功能模块中。可以将它类比为 Variable Declaration(变量声明)。就像 Variable Declaration 必须以“var”开头一样,Function Declaration 必须以“function”开头。


e.g.


function bar() {

    return 3;

}


ECMA 5(13.0)定义语法:


function Identifier ( FormalParameterList[opt] ) { FunctionBody }


函数名在自身作用域和父作用域内是可获取的(否则就取不到函数了)。


function bar() {

    return 3;

}

bar() //3

bar  //function


什么是 Function Expression(函数表达式)?


Function Expression 将函数定义为表达式语句(通常是变量赋值)的一部分。通过 Function Expression 定义的函数可以是命名的,也可以是匿名的。Function Expression 不能以“function”开头(下面自调用的例子要用括号将其括起来)。


e.g.


//anonymous function expression

var a = function() {

    return 3;

}

//named function expression

var a = function bar() {

    return 3;

}

//self invoking function expression

(function sayHello() {

    alert("hello!");

})();


ECMA 5(13.0)定义语法:


function Identifieropt ( FormalParameterList[opt] ) { FunctionBody }


(这个定义感觉并不完整,因为它忽略了一个条件:外围语句是表达式,并且不以“function”开头)


函数名(如果有的话)在作用域外是不可获取的(与 Function Declaration 对比)。


那 Function Statement 是什么?


Function Statement 有时是 Function Declaration 的另一种说法。但是 kangax 指出,在 mozilla 中,Function Statement 是 Function Declaration 的一种拓展,使得 Function Declaration 语句可以在任何允许使用 statement(语句)的地方使用。但是 Function Statement 现在还不是标准,所以不建议应用在产品开发中。


现在来解释下前面的测试。


Question 1 用了 function declaration,也就是说它们 get hoisted(被提升)了……


等一下,什么是 Hoisting?


这里引用 Ben Cherry的话:“Function declaration和function variable(函数变量)通常会被 JavaScript 解释器移(‘hoisted’)到当前作用域顶部”。


function declaration 被提升时,整个函数体都会随之提升,所以 Question 1 的代码经过解释器解释后是像这样运行的:


//**Simulated processing sequence for Question 1**

function foo(){

    //define bar once

    function bar() {

        return 3;

    }

    //redefine it

    function bar() {

        return 8;

    }

    //return its invocation

    return bar(); //8

}

alert(foo());


但是,我们经常被告诉说,return 语句后面的代码是运行不到的啊……


执行 JavaScript 过程中,有 Context(ECMA 5 将之分解为 LexicalEnvironment、VariableEnvironment 和 ThisBinding)和 Process(一系列按序调用的语句)两个概念。当程序进入执行域时,Declaration 会造成 VariableEnvironment。它们不同于 Statement(比如 return),也不遵循 Statement 的运行规则。


Function Expression 会被提升吗?


这取决于表达式。比如 Question 2 中的第一个表达式:


var bar = function() {

    return 3;

};


等号左边的(var bar)是 Variable Declaration。Variable Declaration 会被提升,但是 Assignment Expression(赋值表达式)不会。所以当 bar 提升时,解释器会这样初始化:var bar = undefined。而函数定义本身不会被提升。


(ECMA 5 12.2 带有 initialzier(初始化器)的变量是在 VariableStatement 执行时,由 AssignmentExpression 赋值的,而不是在变量被创建时。)


因此 Question 2 的代码会按以下顺序运行:


//**Simulated processing sequence for Question 2**

function foo(){

    //a declaration for each function expression

    var bar = undefined;

    var bar = undefined;

    //first Function Expression is executed

    bar = function() {

        return 3;

    };

    // Function created by first Function Expression is invoked

    return bar();

    // second Function Expression unreachable

}

alert(foo()); //3


你可能会说,这还能解释的通,但是 Question 3 的答案错了,我在 Firebug 运行会报错。


把代码保存在 HTML 文件中,之后在 Firefox 上运行试试。或者在 IE8、Chrome 或 Safari 控制台中运行。显然 Firebug 控制台在“global(全局)”作用域(实际并不是全局的,而是特有的“Firebug”作用域——试着在 Firebug 控制台中运行“this == window”你就知道了)运行代码时,不会将函数提升。


Question 3 和 Question 1 的逻辑相似。这次是 foo 函数被提升了。


Question 4 就很简单了,根本就没有函数提升……


可以这么说,但是如果根本没有提升的话,TypeError 会是“bar not defined”,而不是“bar not a function”。此例中确实没有函数提升,但是有变量提升。因此 bar 在开始就被声明了,但是它的值并没有定义。其它代码都是按顺序执行的。


//**Simulated processing sequence for Question 4**

function foo(){

    //a declaration for each function expression

    var bar = undefined;

    var bar = undefined;

    return bar(); //TypeError: "bar not defined"

    //neither Function Expression is reached

}

alert(foo());


还应该注意什么?


官方是禁止在非功能模块(比如 if)中使用 Function Declaration 的。但是所有浏览器都支持,但是各自的解释方式不同。


例如下面的代码段在 Firefox 3.6 中会抛错,因为它将 Function Declaration 解释成了 Function Statement(见上文),所以 x 没有定义。但是在 IE8、Chrome 5 和 Safari 5 中,会返回函数 x(和标准的 Function Declaration 一样)。


function foo() {

    if(false) {

        function x() {};

    }

    return x;

}

alert(foo());


可以看出使用 Function Declaration 可能会引起混淆,那么它有什么优点吗?


你可能会说 Function Declaration 很宽松啊——如果试图在声明前使用函数,提升确实可以修正顺序,以便函数可以正确调用。但是这种宽松不利于严谨的编码,从长远的角度来看,很有可能会促进而不是阻止意外的发生。毕竟,程序员按特定的顺序排列语句是有原因的。


那么还有其它理由支持 Function Expression 的吗?


你猜呢?


1)Function Declaration 感觉像是要模仿 Java 风格的方法声明,但是 Java 方法和 JavaScript 并不一样。在 JavaScript 中,函数是含值的 living 对象。Java 方法仅是对元数据的存储。下面的两段代码都定义了函数,但是只有 Function Expression 看着像创建了对象。


//Function Declaration

function add(a,b) {return a + b};

//Function Expression

var add = function(a,b) {return a + b};


2)Function Expression 用处更多。Function Declaration 只能作为“statement”孤立存在。它所能做的就是创建一个当前作用域下的对象变量。相比之下,Function Expression(根据定义)是大型结构的一部分。如果想要创建匿名函数、给 prototype(原型)添加函数或是将函数用作其它对象的 property(属性),都可以用 Function Expression。每当用高阶应用,比如 curry 或 compose,创建新的函数时都是在用 Function Expression。Function Expression 和 Functional Programming(函数式编程)分不开。


//Function Expression

var sayHello = alert.curry("hello!");


Function Expression 有缺点吗?


Function Expression 创建的函数大多是匿名的。比如下面的函数是匿名的,today 只是一个匿名函数的引用:


var today = function() {return new Date()}


这会有问题吗?多数情况下不会,但是就像 Nick Fitzgerald 指出的,调试匿名函数会很烦。他建议使用 Named Function Expressions (NFEs)作为工作区:


var today = function today() {return new Date()}


但是如 Asen Bozhilov 所说(和 Kangax 文档)NFEs 在 IE9 以下无法正确执行。


结论


随意放置的 Function Declaration 具有误导性,并且很少有(如果有的话)情况,用 Function Expression 给变量赋值无法替代 Function Declaration。但是如果必须使用 Function Declaration 的话,将其放在所属作用域顶部可以减少混淆。永远不要把 Function Declaration 放在 if 语句中。


说了这么多,可能在你自己的情况下,Function Declaration 还是很有用的。这没什么。死记教条是危险的,并且通常会造成代码拐弯抹角。更重要的是你理解了概念,这样就可以根据自身情况决定用哪种方式创建函数。我希望此文在这方面有帮助。


欢迎讨论。如果你觉得我哪里说的不对或是有所补充,请联系我。


同见 第5版 ECMA-262 的 10.5、12.2、13.0、13.2 部分


专栏作者简介 ( 点击 → 加入专栏作者 


古鲁伊:立志做一名有格调的程序媛

打赏支持作者写出更多好文章,谢谢!




------------- 推荐 -------------


范品社推出的极客T恤,含程序员、电影、美剧和物理题材,面料舒适、100%纯棉,有黑、白、灰、藏青色,单件 ¥59.9、两件减¥12、四件减¥28、六件减¥42,详见网店商品页介绍。



(上面为部分 T 恤款式)


网店地址:https://fanpinshe.taobao.com


淘口令:复制以下红色内容,然后打开手淘即可购买


范品社,使用¥极客T恤¥抢先预览(长按复制整段文案,打开手机淘宝即可进入活动内容)

 
前端大全 更多文章 HTTP 协议入门 关于 bind 你可能需要了解的知识点以及使用场景 Javascript 深拷贝 简单封装分页功能pageView.js 使用纯前端JavaScript 实现Excel IO
猜您喜欢 做中国的Salesforce 云智慧即将晋身“独角兽”俱乐部 【一周一讯】本周安全资讯回顾 深度学习及其在淘宝图像应用探讨 PostgresSQL HA高可用架构实战 Android相机开发那些坑