百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

前端必学——函数式编程(四)

toyiye 2024-06-23 18:54 10 浏览 0 评论

本篇会将谈谈函数编程中一个很重要的细节 —— “副作用”

  • 点赞富三代评论美一生

维基上关于副作用的解释:

函数内部有隐式(Implicit)的数据流,这种情况叫做副作用(Side Effect)。

咱们前文也提到过:开发人员喜欢显式输入输出而不是隐式输入输出。

所以我们将细致的看看副作用中【隐式】和【显式】的区别!

何为副作用?

先来个小例子作开胃菜:

// 片段 1
function foo(x) {
    return x * 2;
}

var y = foo( 3 );

// 片段 2
function foo(x) {
    y = x * 2;
}

var y;

foo( 3 );

片段 1 和片段 2 实现的最终效果是一致的,即 y = 3 * 2 ,但是片段 1 是显示的,片段 2 是隐式的。

原因是:片段 2 在函数内引用了外部变量 y。

片段 2 ,当我们调用 foo( 3 ) 时,并不知道其内部是否会修改外部变量 y。它的修改是隐式的,即产生了副作用!

有副作用的函数可读性更低,我们需要更多的阅读来理解程序。

再举一例:

var x = 1;

foo();

console.log( x );

bar();

console.log( x );

baz();

console.log( x );

如果每个函数内都引用了 x ,有可能对其赋值修改,那么我们很难知道每一步 x 的值是怎样的,要每一步去追踪!

选择在一个或多个函数调用中编写带有(潜在)副作用的代码,那么这意味着你代码的读者必须将你的程序完整地执行到某一行,逐步理解。

如果 foo()bar()、和 baz() 这三个函数没有(潜在)副作用,x 的值一眼可见!

一定是修改外部变量才是产生副作用了吗?

function foo(x) {
    return x + y;
}

var y = 3;

foo( 1 ); 

这段代码中,我们没有修改外部变量 y ,但是引用了它,也是会产生副作用的。

y = 5;

// ..

foo( 1 );   

两次 foo( 1 ) 的结果却不一样,又增大了阅读的负担。相信我,这是个最简单抽象的例子,实际的影响将远大于此。

避免副作用?

  1. const

以上面的例子来说:这样写,foo( 1 ) 的结果当然是确定的,因为用到了 const 来固定外部变量。

const y = 5;

// ..

foo( 1 );
  1. I/O

一个没有 I/O 的程序是完全没有意义的,因为它的工作不能以任何方式被观察到。一个有用的程序必须最少有一个输出,并且也需要输入。输入会产生输出。

还记得 foo(..) 函数片段 2 吗?没有输出 return,这是不太可取的。

// 片段 2
function foo(x) {
    y = x * 2;
}

var y;

foo( 3 );
  1. 明确依赖

我们经常会由于函数的异步问题导致数据出错;一个函数引用了另外一个函数的回调结果,当我们作这种引用时要特别注意。

var users = {};
var userOrders = {};

function fetchUserData(userId) {
    ajax( "http://some.api/user/" + userId, function onUserData(userData){
        users[userId] = userData;
    } );
}

function fetchOrders(userId) {
    ajax( "http://some.api/orders/" + userId, function onOrders(orders){
        for (let i = 0; i < orders.length; i++) {
                // 对每个用户的最新订单保持引用
            users[userId].latestOrder = orders[i];
            userOrders[orders[i].orderId] = orders[i];
        }
    } );
}

fetchUserData(..) 应该在 fetchOrders(..) 之前执行,因为后者设置 latestOrder 需要前者的回调;

写出有副作用/效果的代码是很正常的, 但我们需要谨慎和刻意地避免产生有副作用的代码。

  1. 运用幂等

这是一个很新但重要的概念!

从数学的角度来看,幂等指的是在第一次调用后,如果你将该输出一次又一次地输入到操作中,其输出永远不会改变的操作。

一个典型的数学例子是 Math.abs(..)(取绝对值)。Math.abs(-2) 的结果是 2,和 Math.abs(Math.abs(Math.abs(Math.abs(-2)))) 的结果相同。

幂等在 js 中的表现:

// 例 1
var x = 42, y = "hello";

String( x ) === String( String( x ) );                // true

Boolean( y ) === Boolean( Boolean( y ) );            // true

// 例 2
function upper(x) {
    return x.toUpperCase();
}

function lower(x) {
    return x.toLowerCase();
}

var str = "Hello World";

upper( str ) == upper( upper( str ) );                // true

lower( str ) == lower( lower( str ) );                // true

// 例 3
function currency(val) {
    var num = parseFloat(
        String( val ).replace( /[^\d.-]+/g, "" )
    );
    var sign = (num < 0) ? "-" : "";
    return `${sign}${Math.abs( num ).toFixed( 2 )}`;
}

currency( -3.1 );                                    // "-$3.10"

currency( -3.1 ) == currency( currency( -3.1 ) );    // true

实际上,我们在 js 函数式编程中幂等有更加宽泛的概念,即只用要求:f(x) === f(f(x))

// 幂等的:
obj.count = 2; // 这里的幂等性的概念是每一个幂等运算(比如 obj.count = 2)可以重复多次
person.name = upper( person.name );

// 非幂等的:
obj.count++;
person.lastUpdated = Date.now();

// 幂等的:
var hist = document.getElementById( "orderHistory" );
hist.innerHTML = order.historyText;

// 非幂等的:
var update = document.createTextNode( order.latestUpdate );
hist.appendChild( update );

我们不会一直用幂等的方式去定义数据,但如果能做到,这肯定会减少意外情况下产生的副作用。这需要时间去体会,我们就先记住它。

纯函数

你应该听说过纯函数的大名,我们把没有副作用的函数称为纯函数。

例 1:

function add(x,y) {
    return x + y;
}

输入(x 和 y)和输出(return ..)都是直接的,没有引用自由变量。调用 add(3,4) 多次和调用一次是没有区别的。add(..) 是纯粹的编程风格的幂等。

例 2:

const PI = 3.141592;

function circleArea(radius) {
    return PI * radius * radius;
}

circleArea 也是纯函数。虽然它调用了外部变量 PI ,但是 PI 是 const 定义的常量,引用常量不会产生副作用;

例 3:

function unary(fn) {
    return function onlyOneArg(arg){
        return fn( arg );
    };
}

unary 也是纯函数。

表达一个函数的纯度的另一种常用方法是:给定相同的输入(一个或多个),它总是产生相同的输出。

不纯的函数是不受欢迎的!因为我们需要更多的精力去判断它的输出结果!

写纯函数需要更多耐心,比如我们操作数组的 push(..) 方法,或 reverse(..) 方法等,看起来安全,但实际上会修改数组本身。我们需要复制一个变量来解耦(深拷贝)。

函数的纯度是和自信是有关的。函数越纯洁越好。制作纯函数时越努力,当您阅读使用它的代码时,你的自信就会越高,这将使代码更加可读。

其实,关于函数纯度还有更多有意思的点:

思考一个问题,如果我们把函数和外部变量再封装为一个函数,外界无法直接访问其内部,这样,内部的函数算不算是一个纯函数?

假如一棵树在森林里倒下而没有人在附近听见,它有没有发出声音?

阶段小结

  1. 我们反复强调:开发人员喜欢显式输入输出而不是隐式输入输出。
  2. 如果有隐式的输入输出,那么就有可能产生副作用。
  3. 有副作用的代码让我们的代码理解起来更加费劲!
  4. 解决副作用的方法有:定义常量、明确 I/O、明确依赖、运用幂等......
  5. 在 js 运用幂等是一个新事物,我们需要逐渐熟悉它。
  6. 没有副作用的函数就是纯函数,纯函数是我们追求编写的!
  7. 将一个不纯的函数重构为纯函数是首选。但是,如果无法重构,尝试封装副作用。(假如一棵树在森林里倒下而没有人在附近听见,它有没有发出声音?—— 有没有其实已经不重要了,反正听不到)

以上,便是本次关于 JS 函数式编程 副作用 这个细节的讲解。

这个细节,真的很重要!

相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码