《javascript函数式编程思想》——名称

starrow      2022-06-09     332

关键词:

第1章  名称

一般对函数式编程的介绍都会从一等值和纯函数等概念开始,本书却准备在那之前先花些篇章讨论两个通常未得到足够重视的主题:名称和类型系统。前者包括名称绑定、作用域和闭包等内容,后者包括类型的含义和划分、强类型和弱类型、静态类型和动态类型以及多态性的内容。理解这些概念对编程很有意义,无论使用的是哪种语言,采用的是什么范式。具体到本书的核心,使用JavaScript进行函数式编程,在对以上普适概念理解的基础上,掌握它们在JavaScript中的特定表现和行为,又有格外的重要性。这一方面是因为JavaScript长期以来被认为是一种简单的脚本语言,缺少在通用知识背景下对其特性和行为的分析,以致对其行为的认识往往是零碎的、实用的。另一方面是因为名称和类型系统与JavaScript的函数式编程有着紧密的关联。嵌套函数和闭包是JavaScript的函数式编程离不开的技术,鸭子类型是JavaScript藉以实现函数式编程通常具备的参数多态性特征的机制。这些内容都将在下面两章中得到充分的讨论。

1.1  名称绑定    
1.1.1  常量和变量    
1.2  作用域    
1.2.1  包块作用域与就近声明    
1.2.2  静态作用域和动态作用域    
1.2.3  前向引用和提升    

1.3  闭包

名称绑定和作用域这两个概念看上去有些平凡,远没有闭包(Closure)引起的兴趣和疑问多。没有函数式编程经验的人,在初次接触到JavaScript的闭包概念时,多会觉得这是一个很新奇的东西,一时无法理解它的效果,也体会不了有经验的程序员所说的它带来的好处和运用它的技巧。而实际上如果掌握了名称绑定和作用域,就会发现闭包的出现是水到渠成的。

在程序运行中的某一刻或代码中的某一处,所有当前有效的名称组成的集合被称为此刻或此处的引用环境(Referencing environment)。当不针对某个名称时,我们把代码中引用环境保持不变的区域也称为作用域。这个意义上的作用域与前面讨论的名称的作用域是息息相关的,假如用前者来解释后者,它就是代码中所有作用域相同的名称所在的区域。根据名称作用域的规则,在全局代码中的某一处,引用环境就是全部全局名称组成的集合。在一个全局函数内,引用环境包括所有的局部名称、参数和全局名称。JavaScript的函数与C的一个巨大区别就是前者可以嵌套,也就是说一个函数可以声明在另一个函数内。一个内嵌函数的引用环境包括它自身所有的局部名称和参数、外套函数的局部名称和参数以及所有的全局名称。我们在本书后面会看到,嵌套函数是JavaScript编程中必不可少的写法,许多模式和技巧都是赖之以成立的。

内嵌函数需要能访问外套函数的引用环境,当内嵌函数在它的作用域内被直接调用时,满足这个要求是很平凡的。但是JavaScript中的函数还可以作为参数和返回值,这时从内嵌函数的声明到调用它的代码,引用环境发生了改变,若还要访问原来的引用环境,就必须以某种方式将内嵌函数的引用环境和它捆绑在一起,这个整体就称为函数的闭包。很多有关JavaScript的文章在介绍闭包时,都把它定义为从某个函数返回的函数所记住的上下文信息。一个函数可能成为返回值,确实是建立闭包的有力理由。因为函数的局部名称都存在于调用堆栈中,若没有闭包,外套函数返回内嵌函数后,外套函数的堆栈帧被删除,返回的内嵌函数所能引用的外套函数中的局部名称也将消失。

function createClosure() 
    let i = 1;
    return function () 
        console.log(i);
    


const fn = createClosure();
//若没有闭包,fn将无法引用createClosure的局部变量i。
fn();
//=> 1

但实际上闭包并不是只在函数被返回时才创建的,任何JavaScript的函数都是同它的闭包一同创建的。下面的代码不涉及返回函数,却显示了闭包的效果。  

function f(fn, x) 
    if (x < 1) 
        f(g, 1);
     else 
        fn();
    

    function g() 
        console.log(x);
    


function h() 


//假如没有闭包,此处的结果将会是1。
f(h, 0);
//=> 0

当函数g最终被调用时,参数x的值为1,但是g输出的x为0。这就是因为函数g使用的是它闭包中的x,而它的闭包是在声明函数时创建的,在第一次调用函数f时获得值0。等到f调用自身后再次进入其代码时,g的引用环境已经与声明它时的不同,参数x虽然名称相同,但与g闭包中的x是身份不同的值。

以上行为也可以用另一对概念来解释。上一节指出,在代码中遇到某个名称时,静态作用域使用的是空间上最近的声明,动态作用域使用的是时间上最近的声明。略加推敲会发现,在没有调用函数的情况下,代码的文本顺序和程序的执行顺序是一致的,空间上最近的声明就是时间上最近的声明,两种作用域方式的效果是相同的。假如所有的函数都在调用处声明,或者说在调用前内联化(Inline),也会导致同样的结果。两种作用域差别的关键就在于,函数的引用环境建立的时间,静态作用域是在函数声明时建立的,称为深绑定(Deep binding)【注:有些文献中将深绑定定义为在函数被作为参数传递时绑定引用环境,JavaScript中的函数可以作为返回值被赋值给变量,也可以作为对象的方法被动态调用,所以函数被作为参数传递时才绑定引用环境是不能满足所有情况的需求的。】;动态作用域是在函数执行时建立的,称为浅绑定(Shallow binding)。回看上面的代码,假如JavaScript采用的是浅绑定,函数g使用的就将是它执行时包围它的函数f的参数x,输出的结果将会是1。

在一个函数中,引用环境包括它的局部名称、参数和外套作用域中的名称(可能存在的外套函数的局部名称和参数以及所有的全局名称)。容易看出,闭包只需记住外套作用域的部分,因为函数自身的局部名称在每次运行时都会重新创建。

总而言之,闭包对于静态作用域来说并不是什么新的概念,而是以函数为中心的视角来看待静态作用域,或者说是在函数可以被传递、返回的语言中为了贯彻静态作用域的理念而采取的一种实现层面的技术。如果不关心关于静态作用域如何实现的细节,完全可以忽略闭包的概念。因为仅仅就理念上理解和分析代码中名称的含义而言,掌握静态作用域的理论就足够了。本书后面有用到闭包术语的地方,也只是为了强调它反映出的视角和语境。

1.3.1  包块作用域与闭包

1.4  小结    

更多内容,请参看拙著:

《JavaScript函数式编程思想》(京东)

《JavaScript函数式编程思想》(当当)

《JavaScript函数式编程思想》(亚马逊)

《JavaScript函数式编程思想》(天猫)

《javascript函数式编程思想》

自序伴随着Web技术的普及,JavaScript已成为应用最广泛的编程语言之一。由于其在Web前端编程中的统治地位、语言本身的表现力、灵活性、开源的本质和ECMAScript标准近年来的快速发展,JavaScript向各个领域渗透的势头仍然... 查看详情

《javascript函数式编程思想》——从面向对象到函数式编程

...假如本书的写作时间倒退回十年前,书名可能会变成JavaScript面向对象编程思想。自上世纪90年代兴起的面向对象编程思想随Java的繁荣达于顶点,在JavaScript从一门只被用来编写零星的简单的表单验证代码的玩具语言变成日... 查看详情

《javascript函数式编程思想》

自序伴随着Web技术的普及,JavaScript已成为应用最广泛的编程语言之一。由于其在Web前端编程中的统治地位、语言本身的表现力、灵活性、开源的本质和ECMAScript标准近年来的快速发展,JavaScript向各个领域渗透的势头仍然... 查看详情

《javascript函数式编程思想》——部分应用和复合

第5章 部分应用和复合一等值的函数,是函数式编程的基石。部分应用和复合,则是函数式编程的重要特征。采用命令式编程时,每当我们感觉需要抽象出一个新的功能时,就会定义一个函数。在函数式编程中... 查看详情

《javascript函数式编程思想》——函数是一等值

第4章 函数是一等值在函数式编程的标准或特点中,“函数是一等值”是最基本和重要的,也是最为人所知的,所有介绍函数式编程的书籍和文章都会优先介绍这一点,以至于“一等值”几乎成为函数的专属头衔&... 查看详情

《javascript函数式编程思想》——类型系统

第2章 类型系统为什么在许多编程语言中整数和浮点数是两种类型?结构体、数组、列表、映射……这些类型有什么关系?用户自定义的各种类型与它们又有什么关系?函数也是类型吗?强类型和弱类型意味着什... 查看详情

《javascript函数式编程思想》——递归

第7章 递归王二、张三和赵四一日无聊,决定玩击鼓传花讲冷笑话的游戏。王二和张三围成一圈传花,赵四负责击鼓。张三接连讲了几个诸如小菜、狐狸狡猾的笑话。花停在了王二的手中。王二:这个笑话很短。你要... 查看详情

《javascript函数式编程思想》——副作用和不变性

第6章 副作用和不变性6.1 副作用6.2 纯函数6.2.1 外部变量6.2.2 实现6.2.3 函数内部的副作用6.2.4 闭包6.3 不变性6.3.1 哲学上的不变性与身份6.3.2 简单类型和复合类型6.3.3 值类型和引用类型6.3.4 可变类型和不可变类型6.3.5 可变... 查看详情

玩转javascript面试:何为函数式编程?(代码片段)

函数式编程在JavaScript领域着实已经成为一个热门话题。就在几年前,很多JavaScript程序员甚至都不知道啥是函数式编程,但是就在近三年里我看到过的每一个大型应用的代码库中都包含了函数式编程思想的大规模使用。函数式编... 查看详情

函数式编程思想

对于函数式编程来说,其只关心,定义输入数据和输出数据相关的关系,数学表达式里面其实是在做一种映射(mapping),输入的数据和输出的数据关系是什么样的,是用函数来定义的。http://www.yxtvg.com/toutiao/5413179/20180212a04ro500.ht... 查看详情

函数响应式编程(frp)思想

...让你的代码像数学一样简洁,业务像流水一样清晰流畅。函数响应式编程响应式编程思想为体,函数式编程思想为用。响应式编程例如,在命令式编程环境中,a:=b+c表示将表达式的结果赋给a,而之后改变b或c的值不会影响a。但... 查看详情

javascript进阶笔记

js是一门函数式语言,因为js的强大威力依赖于是否将其作为函数式语言进行使用。在js中,我们通常要大量使用函数式编程风格。函数式编程专注于:少而精、通常无副作用、将函数作为程序代码的基础构件块。在函数式编程中... 查看详情

kotlin函数式编程思想fpinkotlin

Kotlin函数式编程思想:FPinKotlin函数式编程特性闭包和高阶函数函数编程支持函数作为第一类对象,有时称为闭包或者仿函数(functor)对象。实质上,闭包是起函数的作用并可以像对象一样操作的对象。与此类似,FP语言... 查看详情

javascriptes6函数式编程:柯里化偏应用组合管道(代码片段)

上一篇介绍了闭包和高阶函数,这是函数式编程的基础核心。这一篇来看看高阶函数的实战场景。首先强调两点:注意闭包的生成位置,清楚作用域链,知道闭包生成后缓存了哪些变量高阶函数思想:以变量作用域作为根基,以... 查看详情

javascript函数式编程

第1章JavaScript函数式编程简介11.1JavaScript案例11.2开始函数式编程41.2.1为什么函数式编程很重要41.2.2以函数为抽象单元71.2.3封装和隐藏91.2.4以函数为行为单位101.2.5数据抽象141.2.6函数式JavaScript初试171.2.7加速191.3Underscore示例221.4总结2... 查看详情

js函数式编程思想(v客学院知识分享)

...现在已经更新到ES7,不久ES8规范即将面世,为了是JS语法对函数编程更加友好,诸如RxJS(ReactiveX)等函数式框架的不断流行。函数式编程则应该是以函数做为舰载主体,然后对函数进行拆分封装、更加抽象,可扩展性极强。 与传... 查看详情

函数式编程思想:耦合和组合,第2部分

...者可能会对这一方法的缺点及其他的可选做法视而不见,函数式编程使用不同的构建块来实现重用,其基于的是更一般化的概念,比如说列表转换和可移植代码。函数式编程思想的这一部分内容比较了作为重用机制的经由继承的... 查看详情

函数式编程思想:以函数的方式思考,第3部分

...ealFord发布:2011-07-0611:23:24挑错|查看译者版本|收藏本文在函数式编程思想的第一部分和第二部分中,我考察了一些函数式编程的主题,研究了这些主题如何与Java?及其相关语言产生关联。本篇文章继续这一探索过程,给出来自前... 查看详情