资讯专栏INFORMATION COLUMN

[译] 理解 JavaScript Mutation 突变和 PureFunction 纯函数

Enlightenment / 3048人阅读

摘要:突变引起状态的改变。纯函数和副作用纯函数是接受输入并返回值而不修改其范围之外的任何数据的函数副作用。如果它们不同,则调用函数,以更新新状态。

作者:Chidume Nnamdi

英文原文:https://blog.bitsrc.io/unders...

[译] 理解 JavaScript Mutation 突变和 PureFunction 纯函数

不可变性、纯函数、副作用,状态可变这些单词我们几乎每天都会见到,但我们几乎不知道他们是如何工作的,以及他们是什么,他们为软件开发带来了什么好处。

在这篇文章中,我们将深入研究所有这些,以便真正了解它们是什么以及如何利用它们来提高我们的Web应用程序的性能。

Javascript:原始数据类型和引用数据类型

我们将首先了解JS如何维护以及访问到我们的数据类型。

在JS中,有原始数据类型和引用数据类型。原始数据类型由值引用,而非原始/引用数据类型指向内存地址。

原始数据类型是:

Boolean

Number

String

Null

Undefined

Symbol

引用数据类型:

Object

Arrays

当我们写原始数据类型时是这个样子:

let one = 1

在调用堆栈中,one 变量直接指向值 1:

Call Stack
#000    one -> | 1 |
#001           |   |
#002           |   |
#003           |   |

如果我们改变这个值:

let one = 1
one = 3

变量 one 的内存地址 #000 原本存储 1 这个值,会直接变成 3

但是,如果我们像这样写一个引用数据类型:

let arr = {
    one: 1
}

或:

let arr = new Object()
arr.one = 1

JS将在内存的堆中创建对象,并将对象的内存地址存储在堆上:

Call Stack       Heap
#000    arr -> | #101 |   #101 | one: 1 |
#001           |      |   #102 |        |
#002           |      |   #103 |        |
#003           |      |   #104 |        |

看到 arr 不直接存储对象,而是指向对象的内存位置(#101)。与直接保存其值的原始数据类型不同。

let arr = { one: 1 }
// arr holds the memory location of the object {one: 1}
// `arr` == #101
let one = 1;
// `one` a primitive data type holds the value `1`
// one == 1

如果我们改变 arr 中的属性,如下所示:

arr.one = 2

那么基本上我们就是在告诉程序更改 arr 对对象属性值的指向。如果你对 C/C++ 等语言的指针和引用比较熟悉,那么这些你都会很容易理解。

传递引用数据类型时,你只是在传递其内存位置的递值,而不是实际的值。

function chg(arg) {
    //arg points to the memory address of { one: 1 }
    arg.one = 99
    // This modification will affect { one: 1 } because arg points to its memory address, 101
}
let arr = { one: 1 } 
// address of `arr` is `#000`
// `arr` contains `#101`, adrress of object, `{one: 1}` in Heap
log(arr); // { one: 1 }
chg(arr /* #101 */)
// #101 is passed in
log(arr) // { one: 99 }
// The change affected `arr`
译者注:arr 本身的内存地址是 #000;arr 其中保存了一个地址 #101;这个地址指向对象 {one:1};在调用 chg 函数的时候,那么修改 arg 属性 one 就会修改 arr 对应的 #101 地址指向的对象 {one:1}

因为引用数据类型保存的是内存地址,所以对他的任何修改都会影响到他指向的内存。

如果我们传入一个原始数据类型:

function chg(arg) {
    arg++
}
let one = 1; // primitive data types holds the actual value of the variable.
log(one) // 1
chg(one /* 1 */)
// the value of `one` is passed in.
log(one) // one is still `1`. No change because primitives only hold the value
译者注:不像原始数据类型,他的值是多少就是多少如果修改了这个值,那么直接修改所在内存对应的这个值
状态突变和不可变性

在生物学领域,我们知道 DNA 以及 DNA 突变。DNA 有四个基本元素,分别是 ATGC。这些生成了编码信息,在人体内产生一种蛋白质。

ATATGCATGCGATA
||||||||||||||   
TACGAGCTAGGCTA
|
|
v
AProteinase
Information to produce a protein (eg, insulin etc)

上述DNA链编码信息以产生可用于骨结构比对的AP蛋白酶蛋白。

如果我们改变DNA链配对,即使是一对:

ATATGCATGCGATA
||||||||||||||   
TACGAGCTAGGCTA
|
v 
GTATGCATGCGATA
||||||||||||||   
TACGAGCTAGGCTA

DNA将产生不同的蛋白质,因为产生蛋白质AP蛋白酶的信息已经被篡改。因此产生了另一种蛋白质,其可能是良性的或在某些情况下是有毒的。

GTATGCATGCGATA
||||||||||||||   
TACGAGCTAGGCTA
|
|
V
Now produces _AProtienase

我们称这种变化突变DNA突变

突变引起DNA状态的改变。

而对于 JS 来说,引用数据类型(数组,对象)都被称为数据结构。这些数据结构保存信息,以操纵我们的应用程序。

let state = {
    wardens: 900,
    animals: 800
}

上面名为 state 的对象保存了 Zoo 应用程序的信息。如果我们改变了 animals 属性的值:

let state = {
    wardens: 900,
    animals: 800
 }
state.animals = 90

我们的 state 对象会保存或编码一个新的信息:

state = {
    wardens: 900,
    animals: 90    
}

这就叫突变 mutation

我们的 state 从:

state = {
    wardens: 900,
    animals: 800    
}

变为:

state = {
    wardens: 900,
    animals: 90    
}

当我们想要保护我们的 state 时候,这就需要用到不可变性了 immutability。为了防止我们的 state 对象发生变化,我们必须创建一个 state 对象的新实例。

function bad(state) {
    state.prp = "yes"
    return state
}
function good(state) {
    let newState = { ...state }
    newState.prp = "yes"
    return newState
}

不可变性使我们的应用程序状态可预测,提高我们的应用程序的性能速率,并轻松跟踪状态的变化。

纯函数和副作用

纯函数是接受输入并返回值而不修改其范围之外的任何数据的函数(副作用)。它的输出或返回值必须取决于输入/参数,纯函数必须返回一个值。

译者注:纯函数必须要满足的条件:不产生副作用、返回值只取决于传入的参数,纯函数必须返回一个值
function impure(arg) {
    finalR.s = 90
    return arg * finalR.s
}

上面的函数不是纯函数,因为它修改了其范围之外的状态 finalR.s

function impure(arg) {
    let f = finalR.s * arg
}

上面的函数也不是纯函数,因为虽然它没有修改任何外部状态,但它没有返回值。

function impure(arg) {
    return finalR.s * 3
}

上面的函数是不纯的,虽然它不影响任何外部状态,但它的输出返回 finalR.s * 3 不依赖于输入 arg。纯函数不仅必须返回一个值,还必须依赖于输入。

function pure(arg) {
    return arg * 4
}

上面的函数才是纯函数。它不会对任何外部状态产生副作用,它会根据输入返回输出。

能够带来的好处

就个人而言,我发现的唯一能够让人理解的好处是 mutation tracking 变异追踪。

知道何时渲染你的状态是非常重要的事情。很多 JS 框架设计了不错的方法来检测何时去渲染其状态。但是最重要的是,要知道在首次渲染完毕后,何时触发再渲染 re-render。这就被称为变异追踪了。这需要知道什么时候状态被改变了或者说变异了。以便去触发再渲染 re-render

于我们已经实现了不变性,我们确信我们的应用程序状态不会在应用程序中的任何位置发生变异,况且纯函数完全准寻其处理逻辑和原则(译者注:不会产生副作用)。这就很容易看出来到底是哪里出现变化了(译者注:反正不是纯函数也不是 immutable 变量)。

let state = {
    add: 0,
}
funtion render() {
    //...
}
function effects(state,action) {
    if(action == "addTen") {
        return {...state, add: state.add + 10}
    }
    return state;
}
function shouldUpdate(s) {
    if(s === state){
        return false
    }
    return true
}
state = effects(state, "addTen")
if(shouldUpdate(state)) {
    render();
}

这里有个小程序。这里有个 state 对象,对象只有一个属性 add。render 函数正常渲染程序的属性。他并不会在程序的任何改变时每次都触发渲染 state 对象,而是先检查 state 对象是否改变。

就像这样,我们有一个 effects 函数和一个纯函数,这两个函数都用来去修改我们的 state 对象。你会看到它返回了一个新的 state 对象,当要更改状态时返回新状态,并在不需要修改时返回相同的状态。

因此,我们有一个shouldUpdate函数,它使用===运算符检查旧状态和新状态是否相同。如果它们不同,则调用render函数,以更新新状态。

结论

我们研究了 Web 开发中这几个最常见的术语,并展示了它们的含义以及它们的用途。如果你付诸实践,这将是非常有益的。

如果有任何对于这篇文章的问题,如我应该增加、修改或删除,请随时评论、发送电子邮件或直接 DM 我。干杯

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/102576.html

相关文章

  • 即学即用系列一:函数

    摘要:于是就有了即学即用这个系列的文章。系列第一篇,就从纯函数开始,由于我是前端方向,所以就从语言中的纯函数说起。并行代码纯函数是健壮的,改变执行次序不会对系统造成影响,因此纯函数的操作可以并行执行。 最近一直在思考如何通过文章或者培训快速提升团队的编码能力,总结下来其实技术的学习分为两类:一种是系统性的学习,比如学习一门语言,学习一个开发框架,这更需要自己从入门到进阶再到实践一步步系统性的...

    Hanks10100 评论0 收藏0
  • JavaScript工作原理(九):使用MutationObserver跟踪DOM的改变

    摘要:概观是现代浏览器提供的,用于检测中的变化。您可能正在使用所见即所得的编辑器,试图实现撤销重做功能。函数的第一个参数是在一个批次中发生的所有改变的集合。虽然有用,但中的每一次更改都会触发突变事件,这又会导致性能问题。 showImg(https://segmentfault.com/img/bV9Z7q?w=1016&h=252);Web应用程序在客户端越来越重要,原因很多,比如需要更丰...

    1fe1se 评论0 收藏0
  • vuex浅入浅出

    摘要:来自不同视图的行为需要变更同一状态。图解后端的行为,响应在上的用户输入导致的状态变化。中的非常类似于事件每个都有一个字符串的事件类型和一个回调函数。 什么是Vuex? Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。 Vuex采用和Redux类似的单向数据流的方式来管理数据。用户...

    琛h。 评论0 收藏0
  • Tasks, microtasks, queues and schedules(

    摘要:事件循环持续运行,直到清空列队的任务。在执行期间,浏览器可能更新渲染。线索可能会发生多次。由于冒泡,函数再一次执行。这意味着队列不会在事件回调之间处理,而是在它们之后处理。当触发成功事件时,相关的对象在事件之后转为非激活状态第四步。 一 前言 一直想对异步处理做一个研究,在查阅资料时发现了这篇文章,非常深入的解释了事件循环中重的任务队列。原文中有代码执行工具,强烈建议自己执行一下查看结...

    tianyu 评论0 收藏0
  • [] Elixir、Phoenix、Absinthe、GraphQL、React Apollo

    摘要:对于每个案例,我们插入所需要的测试数据,调用需要测试的函数并对结果作出断言。我们将这个套接字和用户返回以供我们其他的测试使用。 原文地址:Elixir, Phoenix, Absinthe, GraphQL, React, and Apollo: an absurdly deep dive - Part 2 原文作者:Zach Schneider 译文出自:掘金翻译计划 本文永久链接:gi...

    Cympros 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<