理解 JavaScript Mutation 突变和 PureFunction 纯函数

更新日期: 2019-03-16阅读: 3.2k标签: 函数

[译] 理解 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 开发中这几个最常见的术语,并展示了它们的含义以及它们的用途。如果你付诸实践,这将是非常有益的。

链接: https://fly63.com/article/detial/2388

JavaScript 函数式编程

我理解的 JavaScript 函数式编程,都认为属于函数式编程的范畴,只要他们是以函数作为主要载体的。

Js函数式编程,给你的代码增加一点点函数式编程的特性

给你的代码增加一点点函数式编程的特性,最近我对函数式编程非常感兴趣。这个概念让我着迷:应用数学来增强抽象性和强制纯粹性,以避免副作用,并实现代码的良好可复用性。同时,函数式编程非常复杂。

让我们来创建一个JavaScript Wait函数

Async/await以及它底层promises的应用正在猛烈地冲击着JS的世界。在大多数客户端和JS服务端平台的支持下,回调编程已经成为过去的事情。当然,基于回调的编程很丑陋的。

JavaScript函数创建的细节

如果你曾经了解或编写过JavaScript,你可能已经注意到定义函数的方法有两种。即便是对编程语言有更多经验的人也很难理解这些差异。在这篇博客的第一部分,我们将深入探讨函数声明和函数表达式之间的差异。

编写小而美函数的艺术

随着软件应用的复杂度不断上升,为了确保应用稳定且易拓展,代码质量就变的越来越重要。不幸的是,包括我在内的几乎每个开发者在职业生涯中都会面对质量很差的代码。这些代码通常有以下特征:

javascript回调函数的理解和使用方法(callback)

在js开发中,程序代码是从上而下一条线执行的,但有时候我们需要等待一个操作结束后,再进行下一步操作,这个时候就需要用到回调函数。 在js中,函数也是对象,确切地说:函数是用Function()构造函数创建的Function对象。

js调用函数的几种方法_ES5/ES6的函数调用方式

这篇文章主要介绍ES5中函数的4种调用,在ES5中函数内容的this指向和调用方法有关。以及ES6中函数的调用,使用箭头函数,其中箭头函数的this是和定义时有关和调用无关。

JavaScript中函数的三种定义方法

函数的三种定义方法分别是:函数定义语句、函数直接量表达式和Function()构造函数的方法,下面依次介绍这几种方法具体怎么实现,在实际编程中,Function()构造函数很少用到,前两中定义方法使用比较普遍。

js在excel的编写_excel支持使用JavaScript自定义函数编写

微软 称excel就实现面向开发者的功能,也就是说我们不仅可以全新定义的公式,还可以重新定义excel的内置函数,现在Excel自定义函数增加了使用 JavaScript 编写的支持,下面就简单介绍下如何使用js来编写excel自定义函数。

js中的立即执行函数的写法,立即执行函数作用是什么?

这篇文章主要讲解:js立即执行函数是什么?js使用立即执行函数有什么作用呢?js立即执行函数的写法有哪些?

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!