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反模式>这本书针对这些经常容易出错的设计模式进行分析,解释了错误的理由.允许错误的场景,并给出更好的 ...
随机推荐
- Python 搭建webdriver环境遇到的问题总结
安装过程是参考<selenium2Python自动化测试实战>中Pythonwebdriver环境搭建章节 在安装过程中,遇到了一些问题,总结一下,为日后自己再遇到相同问题做个笔记以便查看 ...
- 吴裕雄--天生自然 python语言数据分析:开普勒系外行星搜索结果分析
import pandas as pd pd.DataFrame({'Yes': [50, 21], 'No': [131, 2]}) pd.DataFrame({'Bob': ['I liked i ...
- spring提供的事务配置--纯注解
spring提供的事务--纯注解 模拟转账业务 ,出错需要事务回滚,没错正常执行 事务和数据库技术都是spring的内置提供的 --------dao包--------------- IAccoun ...
- JDK1.8新特性Lambda表达式
/** * Lambda * @date 2019/8/2 10:03 */ public class Lamda { public static void main(String[] args){ ...
- Louis的「每周语文」
说明:此专栏为Louis收录的经典语录及书影音标记,每周一更新. 成长的本质是变得复杂.当你的主观世界遇到客观世界,之间的那条沟,你掉进去,叫挫折,爬出来,叫成长. -- 语出罗振宇在奇葩说第四季的结 ...
- mysql JOIN查询
查询左表a,并且关联a表在b表中的关联,如果关联不存在也可以查出左表的,注:只查询a的部分列,和b的部分列 SELECT a.id, b.id as my FROM a LEFT JOIN b ON ...
- Channel Estimation for High Speed Wireless Systems using Gaussian Particle Filter and Auxiliary Particle Filter
目录 论文来源 摘要 基本概念 1.时变信道 2.粒子滤波 3.高斯粒子滤波 4.辅助粒子滤波 比较 借鉴之处 论文来源 International Conference on Communicati ...
- ado.net DataSet
一.概念 DataSet是ADO.NET的中心概念.可以把DataSet当成内存中的数据库,DataSet是不依赖于数据库的独立数据集合.所谓独立,就是说,即使断开数据链路,或者关闭数据库,DataS ...
- Python开发(三):字符编码,文件操作,函数
一:三级菜单 If len(choice) == continue # 判断输入的是否为空,为空就跳出这次循环进行下次循环, exit(“bye”) :退出程序显示,bye 二:编码 最早的编码是as ...
- 【WPF学习】第五十三章 动画类型回顾
创建动画面临的第一个挑战是为动画选择正确的属性.期望的结果(例如,在窗口中移动元素)与需要使用的属性(在这种情况下是Canvas.Left和Canvas.Top属性)之间的关系并不总是很直观.下面是一 ...