当前位置: 首页 > news >正文

理解 JavaScript Mutation 突变和 PureFunction 纯函数

  • 作者:Chidume Nnamdi
  • 英文原文:blog.bitsrc.io/understandi…

[译] 理解 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://juejin.im/post/5c8bc5cce51d45158f6f90d5

相关文章:

  • table tr,td,div ... focus
  • 数控切割机造船行业应用
  • 小明的调查作业
  • virtualbox 不能为虚拟电脑打开一个新任务/VT-x features locked or unavailable in MSR.
  • Linux Cluster
  • Mac开发环境配置相关:/usr/include缺失了怎么办?
  • [2019.2.28]BZOJ4033 [HAOI2015]树上染色
  • 正确优雅地解决用户退出——JSP及Struts解决方案
  • 亚马逊是如何进行软件开发的
  • zookeeper系列之一—zookeeper入门
  • vuex视频教程
  • 关于 +new Date 的个人见解
  • JOB SERVER 负载均衡
  • 如何保护云中的工作负载
  • mysql触发器的作用及语法
  • 【前端学习】-粗谈选择器
  • 5、React组件事件详解
  • CSS中外联样式表代表的含义
  • java8 Stream Pipelines 浅析
  • javascript数组去重/查找/插入/删除
  • Java-详解HashMap
  • js继承的实现方法
  • maya建模与骨骼动画快速实现人工鱼
  • Mybatis初体验
  • MySQL QA
  • Python学习之路16-使用API
  • Sass 快速入门教程
  • 测试如何在敏捷团队中工作?
  • 高程读书笔记 第六章 面向对象程序设计
  • 关于Android中设置闹钟的相对比较完善的解决方案
  • 关于字符编码你应该知道的事情
  • 开放才能进步!Angular和Wijmo一起走过的日子
  • 类orAPI - 收藏集 - 掘金
  • 聊聊sentinel的DegradeSlot
  • 批量截取pdf文件
  • 什么软件可以提取视频中的音频制作成手机铃声
  • 算法之不定期更新(一)(2018-04-12)
  • 小程序测试方案初探
  • 职业生涯 一个六年开发经验的女程序员的心声。
  • 主流的CSS水平和垂直居中技术大全
  • 湖北分布式智能数据采集方法有哪些?
  • ​ 全球云科技基础设施:亚马逊云科技的海外服务器网络如何演进
  • #QT(一种朴素的计算器实现方法)
  • #Z2294. 打印树的直径
  • #我与Java虚拟机的故事#连载07:我放弃了对JVM的进一步学习
  • $.each()与$(selector).each()
  • ${ }的特别功能
  • (02)vite环境变量配置
  • (33)STM32——485实验笔记
  • (Redis使用系列) Springboot 在redis中使用BloomFilter布隆过滤器机制 六
  • (附源码)springboot金融新闻信息服务系统 毕业设计651450
  • (原创)Stanford Machine Learning (by Andrew NG) --- (week 9) Anomaly DetectionRecommender Systems...
  • (转)eclipse内存溢出设置 -Xms212m -Xmx804m -XX:PermSize=250M -XX:MaxPermSize=356m
  • (自用)learnOpenGL学习总结-高级OpenGL-抗锯齿
  • ****** 二十三 ******、软设笔记【数据库】-数据操作-常用关系操作、关系运算