JavaScript 模式》读书笔记(4)— 函数1
从这篇开始,我们会用很长的章节来讨论函数,这个JavaScript中最重要,也是最基本的技能。本章中,我们会区分函数表达式与函数声明,并且还会学习到局部作用域和变量声明提升的工作原理。以及大量对API、代码初始化、程序性能等有帮助的模式。
我们首先,要来回顾一些基础知识以明确一些概念和定义。
一、背景
JavaScript中的函数有两个主要特点显使其显得比较特殊。第一个特点在于函数是第一类对象(first-class object),第二个特点在于它们可以提供作用域。函数就是对象:
- 函数可以在运行时动态创建,还可以在程序执行过程中创建。
- 函数可以分配给变量,可以将它们的引用复制到其他变量,可以被扩展,此外,除少数特殊情况,函数还可以被删除。
- 可以作为参数传递给其他函数,并且还可以由其他函数返回。
- 函数可以由自己的属性和方法。
因此,对于函数A来说,他可能是一个对象,并且具有自己的属性和方法,而且其中的方法之一可能恰好又是另一个函数B。此外,函数B可以接受函数C作为参数,并且在执行时可以返回另外的函数D。
- function A(){};
- A.name = "aName";
- function D(){console.log(1234)};
- function C(){
- return D();
- };
- A.B = function (callback){
- callback()
- };
- A.B(C);
代码看起来就像上面这样。乍看之下,有许多的函数要记录。但是适应各种函数应用后,将开始欣赏函数所提供的能力、灵活性、以及表现力。一般来说,当考虑JavaScript中的函数、对象时,其唯一的特性在于该对象(即函数)是可调用的,这意味着它是可执行的。
事实上,当看到new Function()构造函数运行时,函数就是对象的意义就变得非常明确了:
- // 反模式
- // 仅用于演示目的
- var add = new Function('a','b','return a + b');
- add(1,2); // returns 3
在以上这段代码中,毫无疑问,add()是一个对象,毕竟它是由一个构造函数所创建。然而,使用Function()构造函数并不是一个好主意,如同使用eval()一样不好。这是由于代码是以字符串方式传递并重新计算。同样,这也不便于编写和阅读,这是因为必须使用引号隔开代码,并且如果出于可读性的目的而希望在函数中正确地缩进代码,那么还需要格外注意。
第二个重要的特征在于函数提供了作用域。在JavaScript中并没有块级作用域(当然,let出现之后,已经有了块级作用域,这里我们不讨论)。函数内部以var关键词定义的任何变量都是局部变量,对于函数外部是不可见的。考虑到花括号{}并不提供作用域(这句话是没问题的,哪怕是在现在的ES6出现之后,因为提供作用域的并不是花括号,而是花括号内使用let声明),因此如果在if条件语句或在for以及while循环中,使用var关键词定义一个变量,这并不意味着定义了一个局部变量。它仅对于包装函数来说是局部变量,并且如果没有包装函数,它将成为一个全局变量。
消除术语的歧义
让我嗯话费一点时间讨论用于定义函数的相关代码的术语,因为在谈论到模式时,使用准确、约定的名称与代码是同等重要的。
- // 命名函数表达式
- var add = function add(a,b) {
- return a + b;
- };
上面的代码显示了一个函数,它使用了命名函数表达式(named function expression)。如果跳过函数表达式中的名称(例子中的第二个add),将会得到一个未命名函数表达式,也简称为函数表达式,或者最常见的是将之称为匿名函数。
- // 函数表达式,又名匿名函数,未命名函数表达式
- var add = function(a,b) {
- return a + b;
- };
因此,广义上称为函数表达式,并且命名函数表达式是一个函数表达式的一种特殊情况,通常发生在定义可选的命名时。
当省略了第二个add并且以一个未命名函数表达式作为结束,这并不会影响该函数的定义以及后续的调用。唯一的区别在于该函数对象的name属性将会变成一个空字符串。name属性是JavaScript语言的一个扩展(它并不是ECMA标准的一部分),但是在许多环境中得到了广泛的应用。如果保留了第二个add,那么add.name属性将会包含字符串“add”。当使用调试器时,或者当从自身递归调用同一个函数时,name属性时非常有用的,否则,可以跳过该属性。
最后,获得了函数声明(function declaration)。这些声明看起来与其他语言中所使用的函数极为相似:
- function foo() {
- // 此处为函数主体
- }
就语法而言,命名函数表达式与函数的声明看起来很相似,尤其是如果不将函数表达式的结果分配给变量(后面的回调模式中会看到)的时候。有时候,没有其他方法可以区分出函数声明和命名函数表达式的差异,除非查看函数出现的上、下文预警,正如将在下一节中所看到的。
在尾随的分号中,这两者之间在语法上存在差异。函数声明中并不需要分号结尾,但在函数表达式中需要分号,并且应该总是使用分号,及时编辑其中分号自动插入机制可能帮您完成了这个工作。
函数字面量,这个术语也经常被使用,它可能表示一个函数表达式或命名函数表达式。由于这种模糊性含义,并不推荐使用该术语。
声明vs表达式:名称和变量声明提升
因此,应该使用哪种方法?函数声明还是函数表达式?在不能使用声明的情况下,下面将为您解决这种困境。
- // 这是一个函数表达式
- // 它作为参数传递给函数“callMe”
- callMe(function (){
- // 这里,即该函数是一个匿名函数表达式
- // 也被称为匿名函数
- });
- // 这是一个命名函数表达式
- callMe(function me() {
- // 这里,即me,是命名函数表达式
- // 名称是me
- });
- // 另一个函数表达式
- var myobj = {
- say:function() {
- // 这里是函数表达式
- }
- };
上面的代码,展示了将函数对象作为参数传递,或者在对象字面量中定义方法。
注意了:函数声明只能出现在“程序代码”中,这表示它仅能在其它函数体内部或全局空间中。它们的定义不能分配给变量或者属性,也不能以参数形式出现在函数调用中。
- // 全局作用域
- function foo() {}
- function local() {
- // 局部作用域
- function bar() {}
- return bar;
- }
上面的代码,foo()、bar()、local()都是以函数声明模式进行定义的。
函数的命名属性
当选择函数定义模式的时候,另一个需要考虑的事情是有关制度name属性的可用性。同样,这个属性并不是标准,但在许多环境中都可以使用它。在函数声明和命名函数表达式中,已经定义了name属性。在匿名函数表达式中,他依赖于其实现方式。其name可能是为定义的,也可能是空字符串来定义name属性。
- function foo(){} //声明
- var bar = function (){}; //表达式
- var baz = function baz() {}; //命名表达式
- console.log(foo.name); //输出“foo”
- console.log(bar.name); //输出“bar”
- console.log(baz.name); //输出“baz”
注意,这里的一个区别,就是在现代浏览器中,若把一个匿名函数表达式赋值给一个变量,那么此时,匿名函数表达式的name属性即该变量的名字。因版本迭代原因,这与书中描述有些出入。
name属性在调试bug和递归调用自身时很有用。其他场景可选择使用匿名函数表达式即可。
- var foo = function bar() {};
- console.log(foo.name)
这样做也是可以的,打印出得结果是bar。这在技术上是没问题的,但是会存在一些兼容问题,所以不建议这样使用。
函数的提升
从前面的讨论中,可能会得出函数声明的行为几乎等同于命名函数表达式的行为。然而这并不是完全正确,其区别在于提升(hoisting)行为。
对于所有变量,无论在函数体的何处进行声明,都会在后台被提升到函数顶部。而这对于函数同样适用,其原因在于函数只是分配给变量的对象。“明白”的地方在于当使用函数声明时,函数定义也被提升,而不仅仅是函数声明被提升。
- // 反模式
- // 全局函数
- function foo() {
- console.log("global foo");
- }
- function bar() {
- console.log("global bar");
- }
- function hoistMe() {
- // 在这里是为了判断提升的内容到底是什么,仅仅是变量名?还是连带函数体一起?
- console.log(typeof foo);
- console.log(typeof bar);
- // 执行
- foo();
- bar();
- // 函数声明
- // 变量“foo”以及其实现者被提升
- function foo() {
- console.log('local foo');
- }
- // 函数表达式
- // 仅变量‘bar’被提升
- // 函数实现未被提升
- var bar = function (){
- console.log('local bar');
- };
- }
- hoistMe();
在这个例子中我们可以看到,如同正常的变量一样,仅存在与hoistMe()函数中的foo和bar移动到了顶部,从而覆盖了全局foo和bar函数。两者之间的区别在于局部foo()的定义被提升到顶部且能正常运行,即使在后面才定义它。bar()的定义并没有被提升,仅有他的声明被提升。这就是为什么代码执行到达bar()的定义时,其显示结果是undefined且并没有作为函数来调用(然而,在作用域链中,仍然防止全局bar()被“看到”)。
最后强调一下函数的两个特征:它们都是对象,它们提供局部作用域。
好了,这篇有关函数的基本情况和定义大家都了解了。下一篇我们继续。
JavaScript 模式》读书笔记(4)— 函数1的更多相关文章
- JavaScript模式读书笔记 第4章 函数
2014年11月10日 1.JavaScript函数具有两个特点: 函数是第一类对象 函数能够提供作用域 函数即对象,表现为: -1,函数能够在执行时动态创建,也 ...
- JavaScript模式读书笔记 文章3章 文字和构造
1.对象字面量 -1.Javascript中所创建的自己定义对象在任务时候都是可变的.能够从一个空对象開始,依据须要添加函数.对象字面量模式能够使我们在创建对象的时候向其加入函数. ...
- 《你不知道的javascript》读书笔记2
概述 放假读完了<你不知道的javascript>上篇,学到了很多东西,记录下来,供以后开发时参考,相信对其他人也有用. 这篇笔记是这本书的下半部分,上半部分请见<你不知道的java ...
- 《编写可维护的javascript》读书笔记(中)——编程实践
上篇读书笔记系列之:<编写可维护的javascript>读书笔记(上) 上篇说的是编程风格,记录的都是最重要的点,不讲废话,写的比较简洁,而本篇将加入一些实例,因为那样比较容易说明问题. ...
- C语言深度解剖读书笔记(6.函数的核心)
对于本节的函数内容其实就没什么难点了,但是对于函数这节又涉及到了顺序点的问题,我觉得可以还是忽略吧. 本节知识点: 1.函数中的顺序点:f(k,k++); 这样的问题大多跟编译器有关,不要去刻意追求 ...
- Javascript & JQuery读书笔记
Hi All, 分享一下我学JS & JQuery的读书笔记: JS的3个不足:复杂的文档对象模型(DOM),不一致的浏览器的实现和便捷的开发,调试工具的缺乏. Jquery的选择器 a. 基 ...
- 《你不知道的javascript》读书笔记1
概述 放假读完了<你不知道的javascript>上篇,学到了很多东西,记录下来,供以后开发时参考,相信对其他人也有用. js的工作原理 引擎:从头到尾负责整个js的编译和运行.(很大一部 ...
- JavaScript中的this—你不知道的JavaScript上卷读书笔记(三)
this是什么? this 是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件.this 的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式.当一个函数被调用时,会 ...
- JavaScript词法作用域—你不知道的JavaScript上卷读书笔记(一)
前段时间在每天往返的地铁上抽空将 <你不知道的JavaScript(上卷)>读了一遍,这本书很多部分写的很是精妙,对于接触前端时间不太久的人来说,就好像是叩开了JavaScript的另一扇 ...
- SQL反模式读书笔记思维导图
在写SQL过程以及设计数据表的过程中,我们经常会走一些弯路,会做一些错误的设计.<SQL反模式>这本书针对这些经常容易出错的设计模式进行分析,解释了错误的理由.允许错误的场景,并给出更好的 ...
随机推荐
- Spring 错误 cvc-complex-type.2.4.c: The matching wildcard is strict, but no declaration can be found for element 'context:property-placeholder'.
我来说下这个出错的原因吧 eclise中xsd的验证问题Description Resource Path Location Type cvc-complex-type.2.4.c: The matc ...
- python ftp sftp
ftp 上传下载文件 12345678910111213141516171819202122232425262728293031323334 from ftplib import FTPimport ...
- SpringMVC之转发重定向
package com.tz.controller; import org.springframework.stereotype.Controller; import org.springframew ...
- 二十一世纪计算 | John Hopcroft:AI革命
编者按:信息革命的浪潮浩浩汤汤,越来越多的人将注意力转向人工智能,想探索它对人类生产生活所产生的可能影响.人工智能的下一步发展将主要来自深度学习,在这个领域中,更多令人兴奋的话题在等待我们探讨:神经网 ...
- bp(net core)+easyui+efcore实现仓储管理系统——入库管理之二(三十八)
abp(net core)+easyui+efcore实现仓储管理系统目录 abp(net core)+easyui+efcore实现仓储管理系统——ABP总体介绍(一) abp(net core)+ ...
- Ueditor富文本编辑器--Ctrl V 粘贴后原有图片显示错误
最近负责将公司官网从静态网站改版成动态网站,方便公司推广营销人员修改增加文案,避免官网文案维护过于依赖技术人员.在做后台管理系统时用到了富文本编辑器Ueditor,因为公司有一个阿里云文件资源服务器, ...
- 7-30 jmu-python-凯撒密码加密算法 (10 分)
编写一个凯撒密码加密程序,接收用户输入的文本和密钥k,对明文中的字母a-z和字母A-Z替换为其后第k个字母. 输入格式: 接收两行输入,第一行为待加密的明文,第二行为密钥k. 输出格式: 输出加密后的 ...
- js中的0就是false,非0就是true。
在处理js代码判断真假时经常会这么写. var vale = fun();//从某个地方获取的值. if(!value){ 进入这里表示value为false或不存在 }但fun()可能得到的是数字0 ...
- 【Amaple教程】2. 模块
正如它的名字,模块用于amaplejs单页应用的页面分割,所有的跳转更新和代码编写都是以模块为单位的. 定义一个模块 一个模块由<module>标签对包含,内部分为template模板.J ...
- Java避坑宝典《Java业务开发常见错误100例》上线了
写这个专栏的缘起 之前我写过一篇博客:<朱晔的互联网架构实践心得S2E2:写业务代码最容易掉的10种坑>,引起的关注还是挺多的.后来和极客时间的编辑一拍即合决定以这个为题写一个专栏.其实所 ...