JavaScript中的纯函数是什么?

本文探讨了纯函数的概念,强调了其在函数式编程中的核心地位。纯函数具备两大特性:相同输入始终产生相同输出,以及执行过程中无副作用。文章通过对比纯函数与非纯函数的示例,阐述了纯函数的一致性和可预测性优势。

Pure functions are the atomic building blocks in functional programming. They are adored for their simplicity and testability.

纯函数是函数编程中的基本组成部分。 他们以其简单性和可测试性而受到赞誉。

This post covers a quick checklist to tell if a function’s pure or not.

这篇文章涵盖了一个快速核对清单,以告诉您函数是否是纯函数。

清单 (The Checklist)

A function must pass two tests to be considered “pure”:

一个函数必须通过两个测试才能被视为“纯”:

  1. Same inputs always return same outputs

    相同的输入总是返回相同的输出

  2. No side-effects

    没有副作用

Let’s zoom in on each one.

让我们放大每个。

1.相同输入=>相同输出 (1. Same Input => Same Output)

Compare this:

比较一下:

const add = (x, y) => x + y;

add(2, 4); // 6

To this:

对此:

let x = 2;

const add = (y) => {
  x += y;
};

add(4); // x === 6 (the first time)
纯函数=一致的结果 (Pure Functions = Consistent Results)

The first example returns a value based on the given parameters, regardless of where/when you call it.

第一个示例根据给定的参数返回值,无论您在何处/何时调用它。

If you pass 2 and 4, you’ll always get 6.

如果您通过24 ,则总会得到6

Nothing else affects the output.

没有其他影响输出。

函数不纯=结果不一致 (Impure Functions = Inconsistent Results)

The second example returns nothing. It relies on shared state to do its job by incrementing a variable outside of its own scope.

第二个示例不返回任何内容。 它依靠共享状态通过在自身作用域之外增加变量来完成其工作。

This pattern is a developer’s nightmare fuel.

这种模式是开发商梦s以求的动力。

Shared state introduces a time dependency. You get different results depending on when you called the function. The first time results in 6, next time is 10 and so on.

共享状态引入了时间依赖性。 根据调用函数的时间,您将获得不同的结果。 第一次是6 ,下次是10 ,依此类推。

哪个版本更容易推理? (Which Version’s Easier to Reason About?)

Which one’s less likely to breed bugs that happen only under certain conditions?

哪一个人不太可能繁殖仅在某些条件下才会发生的错误?

Which one’s more likely to succeed in a multi-threaded environment where time dependencies can break the system?

在时间依赖性可能会破坏系统的多线程环境中,哪一个更有可能成功?

Definitely the first one.

绝对是第一个。

2.没有副作用 (2. No Side-Effects)

This test itself is a checklist. A few examples of side-effects are

该测试本身是一个清单。 副作用的一些例子是

  1. Mutating your input

    改变您的输入
  2. console.log

    console.log

  3. HTTP calls (AJAX/fetch)

    HTTP调用(AJAX /提取)
  4. Changing the filesystem (fs)

    更改文件系统(fs)
  5. Querying the DOM

    查询DOM

Basically any work a function performs that isn’t related to calculating the final output.

基本上,函数执行的任何与计算最终输出无关的工作。

Here’s an impure function with a side-effect.

这是一个带有副作用的不纯函数。

还不错 (Not So Bad)
const impureDouble = (x) => {
  console.log('doubling', x);

  return x * 2;
};

const result = impureDouble(4);
console.log({ result });

console.log is the side-effect here but, in all practicality, it won’t harm us. We’ll still get the same outputs, given the same inputs.

console.log是这里的副作用,但实际上,它不会损害我们。 给定相同的输入,我们仍将获得相同的输出。

This, however, may cause a problem.

但是, 可能会引起问题。

“不正确地”更改对象 (“Impurely” Changing an Object)
const impureAssoc = (key, value, object) => {
  object[key] = value;
};

const person = {
  name: 'Bobo'
};

const result = impureAssoc('shoeSize', 400, person);

console.log({
  person,
  result
});

The variable, person, has been forever changed because our function introduced an assignment statement.

因为我们的函数引入了赋值语句,所以变量person一直被更改。

Shared state means impureAssoc's impact isn’t fully obvious anymore. Understanding its effect on a system now involves tracking down every variable it’s ever touched and knowing their histories.

共享状态意味着impureAssoc的影响不再完全明显。 了解其对系统的影响现在涉及到跟踪它曾经接触过的每个变量并了解其历史。

Shared state = timing dependencies.

共享状态=时序依赖性。

We can purify impureAssoc by simply returning a new object with our desired properties.

我们可以通过简单地返回具有所需属性的新对象来净化impureAssoc

净化它 (Purifying It Up)
const pureAssoc = (key, value, object) => ({
  ...object,
  [key]: value
});

const person = {
  name: 'Bobo'
};

const result = pureAssoc('shoeSize', 400, person);

console.log({
  person,
  result
});

Now pureAssoc returns a testable result and we’ll never worry if it quietly mutated something elsewhere.

现在pureAssoc返回一个可测试的结果,我们永远pureAssoc担心它是否会在其他地方悄悄地进行突变。

You could even do the following and remain pure:

您甚至可以执行以下操作并保持纯净:

另一种纯净的方式 (Another Pure Way)
const pureAssoc = (key, value, object) => {
  const newObject = { ...object };

  newObject[key] = value;

  return newObject;
};

const person = {
  name: 'Bobo'
};

const result = pureAssoc('shoeSize', 400, person);

console.log({
  person,
  result
});

Mutating your input can be dangerous, but mutating a copy of it is no problem. Our end result is still a testable, predictable function that works no matter where/when you call it.

更改您的输入可能很危险,但是更改其副本则没有问题。 我们的最终结果仍然是一个可测试的,可预测的函数,无论您在何处/何时调用它,它都可以工作。

The mutation’s limited to that small scope and you’re still returning a value.

变异仅限于较小的范围,并且您仍在返回值。

深克隆对象 (Deep-Cloning Objects)

Heads up! Using the spread operator ... creates a shallow copy of an object. Shallow copies aren’t safe from nested mutations.

小心! 使用散布运算符...创建对象的浅表副本。 浅拷贝对于嵌套突变并不安全。

Thank you Rodrigo Fernández Díaz for bringing this to my attention!

感谢RodrigoFernándezDíaz引起我的注意!

不安全的嵌套变异 (Unsafe Nested Mutation)
const person = {
  name: 'Bobo',
  address: { street: 'Main Street', number: 123 }
};

const shallowPersonClone = { ...person };
shallowPersonClone.address.number = 456;

console.log({ person, shallowPersonClone });

Both person and shallowPersonClone were mutated because their children share the same reference!

personshallowPersonClone都被突变了,因为他们的孩子共享相同的引用!

安全巢式变异 (Safe Nested Mutation)

To safely mutate nested properties, we need a deep clone.

为了安全地更改嵌套属性,我们需要一个深层克隆。

const person = {
  name: 'Bobo',
  address: { street: 'Main Street', number: 123 }
};

const deepPersonClone = JSON.parse(JSON.stringify(person));
deepPersonClone.address.number = 456;

console.log({ person, deepPersonClone });

Now you’re guaranteed safety because they’re truly two separate entities!

现在,您将得到安全保证,因为它们实际上是两个单独的实体!

摘要 (Summary)

  • A function’s pure if it’s free from side-effects and returns the same output, given the same input.

    如果函数没有副作用,并且在给定相同输入的情况下返回相同的输出,则该函数是纯函数。
  • Side-effects include: mutating input, HTTP calls, writing to disk, printing to the screen.

    副作用包括:更改输入,HTTP调用,写入磁盘,在屏幕上打印。
  • You can safely clone, then mutate, your input. Just leave the original one untouched.

    您可以安全地克隆您的输入, 然后对其进行 变异 。 只需保留原始的一个。

  • Spread syntax ( syntax) is the easiest way to shallowly clone objects.

    传播语法( 语法)是克隆对象的最简单方法。

  • JSON.parse(JSON.stringify(object)) is the easiest way to deeply clone objects. Thanks again Rodrigo Fernández Díaz!

    JSON.parse(JSON.stringify(object))深度克隆对象的最简单方法。 再次感谢RodrigoFernándezDíaz

我的免费课程 (My Free Course)

This tutorial was from my completely free course on Educative.io, Functional Programming Patterns With RamdaJS!

本教程来自我完全免费的课程 Educative.io, RamdaJS函数编程模式

Please consider taking/sharing it if you enjoyed this content.

如果您喜欢此内容,请考虑将其共享。

It’s full of lessons, graphics, exercises, and runnable code samples to teach you a basic functional programming style using RamdaJS.

它包含所有课程,图形,练习和可运行的代码示例,以教您使用RamdaJS的基本功能编程样式。

Thanks for reading! Until next time.

谢谢阅读! 直到下一次。

翻译自: https://www.freecodecamp.org/news/what-is-a-pure-function-in-javascript-acb887375dfe/

<think> 我们正在讨论的是JavaScript中的render函数,特别是在前端框架(如Vue.js)上下文中的含义。根据引用[1]和引用[2],render函数是用于创建虚拟DOM(Virtual DOM)的一种方式。在Vue.js中,我们通常使用模板(template)来构建视图,但在某些复杂场景下(如动态生成锚点标题或可排序表格),使用render函数可以更灵活地控制DOM的生成。 ### 1. render函数的基本概念 在JavaScript框架中,**render函数**是一个用于描述视图结构的函数,它返回一个**虚拟节点(VNode)**,这个节点代表了真实DOM的一部分。虚拟DOM是一个轻量级的JavaScript对象,它是对真实DOM的抽象。当应用状态变化时,render函数会生成新的虚拟DOM树,然后通过比较新旧虚拟DOM树(diff算法)来最小化地更新真实DOM。 ### 2. render函数的作用 - **动态生成内容**:当需要根据复杂逻辑动态生成DOM结构时,render函数比模板更灵活。 - **优化性能**:在需要手动优化渲染性能时,使用render函数可以更精细地控制渲染过程。 - **跨平台渲染**:因为虚拟DOM是平台无关的,所以render函数可以用于非浏览器环境(如服务器端渲染、原生应用)。 ### 3. Vue.js中的render函数详解 在Vue.js中,我们使用`createElement`(通常简写为`h`)作为render函数的参数来创建虚拟节点。`createElement`有三个参数: ```javascript createElement( // 第一个参数:标签名、组件或异步组件(必选) 'div', // 第二个参数:数据对象(可选),包括属性、样式、事件监听器等 { attrs: { id: 'myId' }, class: { active: true }, on: { click: handleClick } }, // 第三个参数:子节点(可选),可以是字符串或由createElement创建的节点数组 [ createElement('h1', '标题'), createElement(MyComponent, { props: { msg: 'Hello' } }) ] ) ``` #### 示例:使用render函数创建简单元素 ```javascript // 使用render函数代替模板:<div id="app"><span>Hello</span></div> new Vue({ el: '#app', render(createElement) { return createElement('div', { attrs: { id: 'app' } }, [ createElement('span', 'Hello') ]); } }); ``` ### 4. 与模板(template)的对比 | **特性** | **模板(template)** | **render函数** | |----------------|------------------------------------------|-----------------------------------------| | **可读性** | 高(类似HTML,结构清晰) | 较低(需要理解JavaScript函数) | | **灵活性** | 一般(受限于模板语法) | 高(可使用JavaScript全部功能) | | **适用场景** | 常规视图结构 | 复杂逻辑(如动态生成表格、条件分支多) | | **性能优化** | 由Vue编译器优化 | 手动优化(可控制渲染细节) | | **学习曲线** | 低(易上手) | 较高(需掌握虚拟DOM和createElement) | > ⚠️ 注意:Vue模板在编译阶段会被转换为render函数,因此两者在本质上是等价的。但直接使用render函数可以跳过编译步骤。 ### 5. 实际应用场景 #### 场景1:动态生成带锚点的标题(引用[1]示例) ```javascript Vue.component('anchored-heading', { render(createElement) { // 根据级别生成h1~h6 return createElement( 'h' + this.level, [ createElement('a', { attrs: { name: this.id, href: '#' + this.id } }, this.$slots.default) ] ); }, props: { level: { type: Number, required: true }, id: String } }); ``` #### 场景2:可排序表格组件(引用[1]中的9.7实战) ```javascript export default { render(createElement) { // 根据数据动态生成表头 const headers = this.columns.map(col => createElement('th', { on: { click: () => this.sortBy(col.key) } }, col.title) ); // 动态生成表格行 const rows = this.sortedData.map(row => createElement('tr', this.columns.map(col => createElement('td', row[col.key]) )) ); return createElement('table', [ createElement('thead', [createElement('tr', headers)]), createElement('tbody', rows) ]); }, data() { return { sortKey: '', sortOrder: 'asc' }; }, computed: { sortedData() { /* 排序逻辑 */ } } }; ``` ### 6. React中的render函数 在React中,render函数是组件必需的方法(类组件中)或函数组件本身: ```jsx // 类组件 class Welcome extends React.Component { render() { return <h1>Hello, {this.props.name}</h1>; } } // 函数组件(本身就是render函数) const Welcome = (props) => <h1>Hello, {props.name}</h1>; ``` > React使用JSX语法,它会被Babel编译为`React.createElement()`调用。 ### 7. 核心原理:虚拟DOM(Virtual DOM) 虚拟DOM是一个轻量级的JavaScript对象树,它描述真实DOM的结构。当状态变化时: 1. 创建新的虚拟DOM树 2. 对比新旧树(diff算法) 3. 计算最小变更集 4. 更新真实DOM ### 8. 性能注意事项 - **避免频繁重渲染**:使用`shouldComponentUpdate`(React)或计算属性(Vue)优化 - **减少嵌套层级**:扁平化的虚拟DOM结构可以提高diff效率 - **使用key属性**:在列表渲染时提供稳定的key以复用元素 --- ### 相关问题 1. 虚拟DOM和真实DOM的区别是什么? 2. 在Vue中如何用JSX代替createElement? 3. React和Vue在render函数实现上有何异同? 4. 如何避免不必要的render调用? </think> ### JavaScript中`render`函数的含义详解 `render`函数是JavaScript框架(如Vue、React)中的**核心渲染机制**,它负责将组件状态转换为浏览器可渲染的DOM结构。以下是详细解析: #### 1. 核心作用 - **虚拟DOM创建**:生成轻量级的JavaScript对象(虚拟DOM),描述真实DOM结构 - **动态渲染**:根据数据变化自动更新视图(响应式) - **性能优化**:通过虚拟DOM差异比较(diff算法)最小化DOM操作 - **逻辑控制**:支持JS全功能编程(条件/循环等),比模板更灵活 #### 2. 工作原理(以Vue为例) ```javascript render(createElement) { return createElement( // 创建虚拟节点 'div', // 标签名 { class: 'container' }, // 属性对象 [ // 子节点数组 createElement('h1', '标题'), this.showContent ? createElement('p', '内容') : null ] ) } ``` ▸ **createElement参数**(引用[2]): 1. **元素类型**:HTML标签/组件/函数 2. **数据对象**:属性/样式/事件等(可选) 3. **子节点**:文本或嵌套`createElement`(可选) #### 3. 与模板(template)的对比 | 特性 | 模板(template) | Render函数 | |------|----------------|------------| | **可读性** | ⭐⭐⭐⭐ (类HTML) | ⭐⭐ (纯JS) | | **灵活性** | ⭐⭐ (语法受限) | ⭐⭐⭐⭐ (全功能JS) | | **适用场景** | 常规布局 | 动态组件(如可排序表格[^1]) | | **性能** | 编译时优化 | 运行时控制优化 | | **学习曲线** | 简单 | 较陡峭 | #### 4. 典型使用场景 1. **动态生成内容**(锚点标题等特殊结构[^1]) ```javascript // 动态生成标题锚点 render(h) { return h(`h${this.level}`, [ h('a', { attrs: { name: this.id } }, this.$slots.default) ]) } ``` 2. **高阶组件**(组件封装) ```javascript // 包装组件添加额外功能 export default { render(h) { const slots = Object.keys(this.$slots).reduce((arr, key) => { return arr.concat(this.$slots[key]) }, []) return h(ChildComponent, { on: { ...this.$listeners }, props: this.$props, scopedSlots: this.$scopedSlots }, slots) } } ``` 3. **性能敏感操作**(避免不必要的重渲染) #### 5. React中的实现差异 React组件直接通过render返回JSX: ```jsx // React组件示例 class MyComponent extends React.Component { render() { return ( <div className="container"> {this.props.showTitle && <h1>{this.props.title}</h1>} </div> ) } } ``` ▸ 本质:JSX会被编译为`React.createElement()`调用 #### 6. 最佳实践建议 1. **优先使用模板**:常规场景保持代码可读性 2. **Render函数适用**: - 需要动态生成组件结构时 - 需要JavaScript全功能控制渲染时 - 性能优化关键路径 3. **配合JSX**:在复杂render函数中使用JSX提升可维护性(需配置编译环境) > 💡 技术本质:所有模板最终都会被编译为render函数(Vue的模板编译器将`<template>`转换为render函数) --- ### 相关问题 1. 虚拟DOM和真实DOM的性能差异具体体现在哪些场景? 2. 如何在Vue的render函数中实现条件渲染和循环? 3. React的JSX和Vue的render函数有何本质区别? 4. 哪些场景下必须使用render函数而不能用模板? 5. 如何优化render函数的执行性能? [^1]: 引用自《Render函数。JSX仍然是 JavaScript而不是 DOM...》 [^2]: 引用自《render函数的理解和简单使用
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值