感恩!~~没想到上篇文章能这么受你们的喜欢,激动不已。🤩。可是却也是坐卧不安,这也意味着责任。下篇许多知识点都须要比较深刻的研究和理解,博主也是水平有限,担忧本身没法承担你们的期待。不过终究仍是须要摆正心态,放下情绪,一字一字用心专一,不负本身,也不负社区。与各位小伙伴相互学习,共同成长,以此共勉!最近业务繁忙,精力有限,虽然我尽可能严谨和反复修订,但文章也定有疏漏。上篇文章中,许多小伙伴们指出了很多的问题,为此我也是深表抱歉,我也会虚心接受和纠正错误。也很是感激那么多经过微信或公众号与我探讨的小伙伴,感谢你们的支持和鼓励。javascript
你们知道,React 如今已经在前端开发中占据了主导的地位。优异的性能,强大的生态,让其没法阻挡。博主面的 5 家公司,所有是 React 技术栈。据我所知,大厂也大部分以 React 做为主技术栈。React 也成为了面试中并不可少的一环。css
中篇主要从如下几个方面对 React 展开阐述:html
原本是计划只有上下两篇,但是写着写着越写越多,受限于篇幅,也为了有更好的阅读体验,只好拆分出中篇,但愿各位童鞋别介意。🙃,另外,下篇还有 Hybrid App / Webpack / 性能优化 / Nginx 等方面的知识,敬请期待。建议仍是先从上篇基础开始哈~有个按部就班的过程: 面试上篇。🤑前端
React 也是现现在最流行的前端框架,也是不少大厂面试必备。React 与 Vue 虽有不一样,但一样做为一款 UI 框架,虽然实现可能不同,但在一些理念上仍是有类似的,例如数据驱动、组件化、虚拟 dom 等。这里就主要列举一些 React 中独有的概念。java
<h3 id="1">1. Fiber</h3>react
React 的核心流程能够分为两个部分:git
reconciliation (调度算法,也可称为 render):github
commit:面试
要了解 Fiber,咱们首先来看为何须要它?算法
简述:
核心:
class Fiber { constructor(instance) { this.instance = instance // 指向第一个 child 节点 this.child = child // 指向父节点 this.return = parent // 指向第一个兄弟节点 this.sibling = previous } }
- **链表树遍历算法**: 经过 **节点保存与映射**,便可以随时地进行 中止和重启,这样便能达到实现任务分割的基本前提; - 一、首先经过不断遍历子节点,到树末尾; - 二、开始经过 sibling 遍历兄弟节点; - 三、return 返回父节点,继续执行2; - 四、直到 root 节点后,跳出遍历; - **任务分割**,React 中的渲染更新能够分红两个阶段: - **reconciliation 阶段**: vdom 的数据对比,是个适合拆分的阶段,好比对比一部分树后,先暂停执行个动画调用,待完成后再回来继续比对。 - **Commit 阶段**: 将 change list 更新到 dom 上,不适合拆分,由于使用 vdom 的意义就是为了节省传说中最耗时的 dom 操做,把全部操做一次性更新,若是在这里又拆分,那不是又懵了么。🙃 - **分散执行**: 任务分割后,就能够把小任务单元分散到浏览器的空闲期间去排队执行,而实现的关键是两个新API: `requestIdleCallback` 与 `requestAnimationFrame` - 低优先级的任务交给`requestIdleCallback`处理,这是个浏览器提供的事件循环空闲期的回调函数,须要 pollyfill,并且拥有 deadline 参数,限制执行事件,以继续切分任务; - 高优先级的任务交给`requestAnimationFrame`处理; ```js // 相似于这样的方式 requestIdleCallback((deadline) => { // 当有空闲时间时,咱们执行一个组件渲染; // 把任务塞到一个个碎片时间中去; while ((deadline.timeRemaining() > 0 || deadline.didTimeout) && nextComponent) { nextComponent = performWork(nextComponent); } }); ``` - **优先级策略**: 文本框输入 > 本次调度结束需完成的任务 > 动画过渡 > 交互反馈 > 数据更新 > 不会显示但以防未来会显示的任务
Tips:Fiber 其实能够算是一种编程思想,在其它语言中也有许多应用(Ruby Fiber)。当遇到进程阻塞的问题时,任务分割、异步调用 和 缓存策略 是三个显著的解决思路。
<h3 id="2">2. 生命周期</h3>
在新版本中,React 官方对生命周期有了新的 变更建议:
getDerivedStateFromProps
替换componentWillMount
;getSnapshotBeforeUpdate
替换componentWillUpdate
;componentWillReceiveProps
;其实该变更的缘由,正是因为上述提到的 Fiber。首先,从上面咱们知道 React 能够分红 reconciliation 与 commit 两个阶段,对应的生命周期以下:
reconciliation:
componentWillMount
componentWillReceiveProps
shouldComponentUpdate
componentWillUpdate
commit:
componentDidMount
componentDidUpdate
componentWillUnmount
在 Fiber 中,reconciliation 阶段进行了任务分割,涉及到 暂停 和 重启,所以可能会致使 reconciliation 中的生命周期函数在一次更新渲染循环中被 屡次调用 的状况,产生一些意外错误。
新版的建议生命周期以下:
class Component extends React.Component { // 替换 `componentWillReceiveProps` , // 初始化和 update 时被调用 // 静态函数,没法使用 this static getDerivedStateFromProps(nextProps, prevState) {} // 判断是否须要更新组件 // 能够用于组件性能优化 shouldComponentUpdate(nextProps, nextState) {} // 组件被挂载后触发 componentDidMount() {} // 替换 componentWillUpdate // 能够在更新以前获取最新 dom 数据 getSnapshotBeforeUpdate() {} // 组件更新后调用 componentDidUpdate() {} // 组件即将销毁 componentWillUnmount() {} // 组件已销毁 componentDidUnMount() {} }
使用建议:
constructor
初始化 state;componentDidMount
中进行事件监听,并在componentWillUnmount
中解绑事件;componentDidMount
中进行数据的请求,而不是在componentWillMount
;须要根据 props 更新 state 时,使用getDerivedStateFromProps(nextProps, prevState)
;
public static getDerivedStateFromProps(nextProps, prevState) { // 当新 props 中的 data 发生变化时,同步更新到 state 上 if (nextProps.data !== prevState.data) { return { data: nextProps.data } } else { return null1 } }
- 能够在`componentDidUpdate`监听 props 或者 state 的变化,例如: ```js componentDidUpdate(prevProps) { // 当 id 发生变化时,从新获取数据 if (this.props.id !== prevProps.id) { this.fetchData(this.props.id); } } ``` - 在`componentDidUpdate`使用`setState`时,必须加条件,不然将进入死循环; - `getSnapshotBeforeUpdate(prevProps, prevState)`能够在更新以前获取最新的渲染数据,它的调用是在 render 以后, mounted 以前; - `shouldComponentUpdate`: 默认每次调用`setState`,必定会最终走到 diff 阶段,但能够经过`shouldComponentUpdate`的生命钩子返回`false`来直接阻止后面的逻辑执行,一般是用于作条件渲染,优化渲染的性能。
<h3 id="3">3. setState</h3>
在了解setState
以前,咱们先来简单了解下 React 一个包装结构: Transaction:
事务 (Transaction):
setState
: React 中用于修改状态,更新视图。它具备如下特色:异步与同步: setState
并非单纯的异步或同步,这其实与调用时的环境相关:
在 合成事件 和 生命周期钩子(除 componentDidUpdate) 中,setState
是"异步"的;
缘由: 由于在setState
的实现中,有一个判断: 当更新策略正在事务流的执行中时,该组件更新会被推入dirtyComponents
队列中等待执行;不然,开始执行batchedUpdates
队列更新;
componentDidUpdate
是在更新以后,此时组件已经不在事务流中了,所以则会同步执行;setState
后立刻从this.state
上获取更新后的值。setState
实际上是能够传入第二个参数的。setState(updater, callback)
,在回调中便可获取最新值;在 原生事件 和 setTimeout 中,setState
是同步的,能够立刻获取更新后的值;
setTimeout
是放置于定时器线程中延后执行,此时事务流已结束,所以也是同步;setState
更新队列时,存储的是 合并状态(Object.assign
)。所以前面设置的 key 值会被后面所覆盖,最终只会执行一次更新;函数式: 因为 Fiber 及 合并 的问题,官方推荐能够传入 函数 的形式。setState(fn)
,在fn
中返回新的state
对象便可,例如this.state((state, props) => newState);
setState
的批量更新的逻辑,传入的函数将会被 顺序调用;注意事项:
当组件已被销毁,若是再次调用setState
,React 会报错警告,一般有两种解决办法:
componentWillUnmount
中标记为 true,在setState
前进行判断;<h3 id="4">4. HOC(高阶组件)</h3>
HOC(Higher Order Componennt) 是在 React 机制下社区造成的一种组件模式,在不少第三方开源库中表现强大。
简述:
用法:
属性代理 (Props Proxy): 返回出一个组件,它基于被包裹组件进行 功能加强;
function proxyHoc(Comp) { return class extends React.Component { render() { const newProps = { name: 'tayde', age: 1, } return <Comp {...this.props} {...newProps} /> } } }
- **提取状态**: 能够经过 props 将被包裹组件中的 state 依赖外层,例如用于转换受控组件: ```js function withOnChange(Comp) { return class extends React.Component { constructor(props) { super(props) this.state = { name: '', } } onChangeName = () => { this.setState({ name: 'dongdong', }) } render() { const newProps = { value: this.state.name, onChange: this.onChangeName, } return <Comp {...this.props} {...newProps} /> } } } ``` 使用姿式以下,这样就能很是快速的将一个 `Input` 组件转化成受控组件。 ```js const NameInput = props => (<input name="name" {...props} />) export default withOnChange(NameInput) ``` - **包裹组件**: 能够为被包裹元素进行一层包装, ```js function withMask(Comp) { return class extends React.Component { render() { return ( <div> <Comp {...this.props} /> <div style={{ width: '100%', height: '100%', backgroundColor: 'rgba(0, 0, 0, .6)', }} </div> ) } } } ``` - **反向继承** (Inheritance Inversion): 返回出一个组件,**继承于被包裹组件**,经常使用于如下操做: ```js function IIHoc(Comp) { return class extends Comp { render() { return super.render(); } }; } ``` - **渲染劫持** (Render Highjacking) - **条件渲染**: 根据条件,渲染不一样的组件 ```js function withLoading(Comp) { return class extends Comp { render() { if(this.props.isLoading) { return <Loading /> } else { return super.render() } } }; } ``` - 能够直接修改被包裹组件渲染出的 React 元素树 - **操做状态** (Operate State): 能够直接经过 `this.state` 获取到被包裹组件的状态,并进行操做。但这样的操做容易使 state 变得难以追踪,不易维护,谨慎使用。
应用场景:
function withAdminAuth(WrappedComponent) { return class extends React.Component { constructor(props){ super(props) this.state = { isAdmin: false, } } async componentWillMount() { const currentRole = await getCurrentUserRole(); this.setState({ isAdmin: currentRole === 'Admin', }); } render() { if (this.state.isAdmin) { return <Comp {...this.props} />; } else { return (<div>您没有权限查看该页面,请联系管理员!</div>); } } }; }
- **性能监控**,包裹组件的生命周期,进行统一埋点: ```js function withTiming(Comp) { return class extends Comp { constructor(props) { super(props); this.start = Date.now(); this.end = 0; } componentDidMount() { super.componentDidMount && super.componentDidMount(); this.end = Date.now(); console.log(`${WrappedComponent.name} 组件渲染时间为 ${this.end - this.start} ms`); } render() { return super.render(); } }; } ``` - **代码复用**,能够将重复的逻辑进行抽象。
使用注意:
React.forwardRef
;<h3 id="5">5. Redux</h3>
Redux 是一个 数据管理中心,能够把它理解为一个全局的 data store 实例。它经过必定的使用规则和限制,保证着数据的健壮性、可追溯和可预测性。它与 React 无关,能够独立运行于任何 JavaScript 环境中,从而也为同构应用提供了更好的数据同步通道。
核心理念:
状态只读: 为了保证状态的可控性,最好的方式就是监控状态的变化。那这里就两个必要条件:
理念实现:
Store: 全局 Store 单例, 每一个 Redux 应用下只有一个 store, 它具备如下方法供使用:
getState
: 获取 state;dispatch
: 触发 action, 更新 state;subscribe
: 订阅数据变动,注册监听器;// 建立 const store = createStore(Reducer, initStore)
- **Action**: 它做为一个行为载体,用于映射相应的 Reducer,而且它能够成为数据的载体,将数据从应用传递至 store 中,是 store **惟一的数据源**; ```js // 一个普通的 Action
const action = {
type: 'ADD_LIST', item: 'list-item-1', } // 使用: store.dispatch(action) // 一般为了便于调用,会有一个 Action 建立函数 (action creater) funtion addList(item) { return const action = { type: 'ADD_LIST', item, } } // 调用就会变成: dispatch(addList('list-item-1')) ``` - **Reducer**: 用于描述如何修改数据的纯函数,Action 属于行为名称,而 Reducer 即是修改行为的实质; ```js // 一个常规的 Reducer // @param {state}: 旧数据 // @param {action}: Action 对象 // @returns {any}: 新数据 const initList = [] function ListReducer(state = initList, action) { switch (action.type) { case 'ADD_LIST': return state.concat([action.item]) break defalut: return state } } ``` > **注意**: > > 1. 遵照数据不可变,不要去直接修改 state,而是返回出一个 **新对象**,可使用 `assign / copy / extend / 解构` 等方式建立新对象; > 2. 默认状况下须要 **返回原数据**,避免数据被清空; > 3. 最好设置 **初始值**,便于应用的初始化及数据稳定;
进阶:
React-Redux: 结合 React 使用;
<Provider>
: 将 store 经过 context 传入组件中;connect
: 一个高阶组件,能够方便在 React 组件中使用 Redux;
store
经过mapStateToProps
进行筛选后使用props
注入组件mapDispatchToProps
建立方法,当组件调用时使用dispatch
触发对应的action
Reducer 的拆分与重构:
combineReducers()
进行重构合并;异步 Action: 因为 Reducer 是一个严格的纯函数,所以没法在 Reducer 中进行数据的请求,须要先获取数据,再dispatch(Action)
便可,下面是三种不一样的异步实现:
<h3 id="6">6. React Hooks</h3>
React 中一般使用 类定义 或者 函数定义 建立组件:
在类定义中,咱们可使用到许多 React 特性,例如 state、 各类组件生命周期钩子等,可是在函数定义中,咱们却无能为力,所以 React 16.8 版本推出了一个新功能 (React Hooks),经过它,能够更好的在函数定义组件中使用 React 特性。
好处:
二、类定义更为复杂:
this
的指向问题;注意:
useEffect
中使用useState
,React 会报错提示;重要钩子*:
useState
): 用于定义组件的 State,其到类定义中this.state
的功能;// useState 只接受一个参数: 初始状态 // 返回的是组件名和更改该组件对应的函数 const [flag, setFlag] = useState(true); // 修改状态 setFlag(false) // 上面的代码映射到类定义中: this.state = { flag: true } const flag = this.state.flag const setFlag = (bool) => { this.setState({ flag: bool, }) }
- **生命周期钩子** (`useEffect`): 类定义中有许多生命周期函数,而在 React Hooks 中也提供了一个相应的函数 (`useEffect`),这里能够看作`componentDidMount`、`componentDidUpdate`和`componentWillUnmount`的结合。 - `useEffect(callback, [source])`接受两个参数 - `callback`: 钩子回调函数; - `source`: 设置触发条件,仅当 source 发生改变时才会触发; - `useEffect`钩子在没有传入`[source]`参数时,默认在每次 render 时都会优先调用上次保存的回调中返回的函数,后再从新调用回调; ```js useEffect(() => { // 组件挂载后执行事件绑定 console.log('on') addEventListener() // 组件 update 时会执行事件解绑 return () => { console.log('off') removeEventListener() } }, [source]); // 每次 source 发生改变时,执行结果(以类定义的生命周期,便于你们理解): // --- DidMount --- // 'on' // --- DidUpdate --- // 'off' // 'on' // --- DidUpdate --- // 'off' // 'on' // --- WillUnmount --- // 'off' ``` - 经过第二个参数,咱们即可模拟出几个经常使用的生命周期: - `componentDidMount`: 传入`[]`时,就只会在初始化时调用一次; ```js const useMount = (fn) => useEffect(fn, []) ``` - `componentWillUnmount`: 传入`[]`,回调中的返回的函数也只会被最终执行一次; ```js const useUnmount = (fn) => useEffect(() => fn, []) ``` - `mounted `: 可使用 useState 封装成一个高度可复用的 mounted 状态; ```js const useMounted = () => { const [mounted, setMounted] = useState(false); useEffect(() => { !mounted && setMounted(true); return () => setMounted(false); }, []); return mounted; } ``` - `componentDidUpdate`: `useEffect`每次均会执行,其实就是排除了 DidMount 后便可; ```js const mounted = useMounted() useEffect(() => { mounted && fn() }) ```
其它内置钩子:
useContext
: 获取 context 对象useReducer
: 相似于 Redux 思想的实现,但其并不足以替代 Redux,能够理解成一个组件内部的 redux:
useContext
的全局性,能够完成一个轻量级的 Redux;(easy-peasy)useCallback
: 缓存回调函数,避免传入的回调每次都是新的函数实例而致使依赖组件从新渲染,具备性能优化的效果;useMemo
: 用于缓存传入的 props,避免依赖的组件每次都从新渲染;useRef
: 获取组件的真实节点;useLayoutEffect
:
useEffect
相似,只是区别于执行时间点的不一样。useEffect
属于异步执行,并不会等待 DOM 真正渲染后执行,而useLayoutEffect
则会真正渲染后才触发;useXxxxx
): 基于 Hooks 能够引用其它 Hooks 这个特性,咱们能够编写自定义钩子,如上面的useMounted
。又例如,咱们须要每一个页面自定义标题:function useTitle(title) { useEffect( () => { document.title = title; }); } // 使用: function Home() { const title = '我是首页' useTitle(title) return ( <div>{title}</div> ) }
<h3 id="7">7. SSR</h3>
SSR,俗称 服务端渲染 (Server Side Render),讲人话就是: 直接在服务端层获取数据,渲染出完成的 HTML 文件,直接返回给用户浏览器访问。
痛点:
首屏渲染性能瓶颈:
SEO 问题: 因为页面初始状态为空,所以爬虫没法获取页面中任何有效数据,所以对搜索引擎不友好。
最初的服务端渲染,便没有这些问题。但咱们不能返璞归真,既要保证现有的前端独立的开发模式,又要由服务端渲染,所以咱们使用 React SSR。
原理:
开发流程: (此处以 React + Router + Redux + Koa 为例)
一、在同个项目中,搭建 先后端部分,常规结构:
src
import * as Router from 'koa-router' const router = new Router() // 若是中间也提供 Api 层 router.use('/api/home', async () => { // 返回数据 }) router.get('*', async (ctx) => { // 返回 HTML })
- 三、经过访问 url **匹配** 前端页面路由: ```js // 前端页面路由 import { pages } from '../../client/app' import { matchPath } from 'react-router-dom' // 使用 react-router 库提供的一个匹配方法 const matchPage = matchPath(ctx.req.url, page) ``` - 四、经过页面路由的配置进行 **数据获取**。一般能够在页面路由中增长 SSR 相关的静态配置,用于抽象逻辑,能够保证服务端逻辑的通用性,如: ```js class HomePage extends React.Component{ public static ssrConfig = { cache: true, fetch() { // 请求获取数据 } } } ``` 获取数据一般有两种状况: - 中间层也使用 **http** 获取数据,则此时 fetch 方法可先后端共享; ```js const data = await matchPage.ssrConfig.fetch() ``` - 中间层并不使用 http,是经过一些 **内部调用**,例如 Rpc 或 直接读数据库 等,此时也能够直接由服务端调用对应的方法获取数据。一般,这里须要在 ssrConfig 中配置特异性的信息,用于匹配对应的数据获取方法。 ```js // 页面路由 class HomePage extends React.Component{ public static ssrConfig = { fetch: { url: '/api/home', } } } // 根据规则匹配出对应的数据获取方法 // 这里的规则能够自由,只要能匹配出正确的方法便可 const controller = matchController(ssrConfig.fetch.url) // 获取数据 const data = await controller(ctx) ``` - 五、建立 Redux store,并将数据`dispatch`到里面: ```js import { createStore } from 'redux' // 获取 Clinet层 reducer // 必须复用前端层的逻辑,才能保证一致性; import { reducers } from '../../client/store' // 建立 store const store = createStore(reducers) // 获取配置好的 Action const action = ssrConfig.action // 存储数据 store.dispatch(createAction(action)(data)) ``` - 六、注入 Store, 调用`renderToString`将 React Virtual Dom 渲染成 **字符串**: ```js import * as ReactDOMServer from 'react-dom/server' import { Provider } from 'react-redux' // 获取 Clinet 层根组件 import { App } from '../../client/app' const AppString = ReactDOMServer.renderToString( <Provider store={store}> <StaticRouter location={ctx.req.url} context={{}}> <App /> </StaticRouter> </Provider> ) ``` - 七、将 AppString 包装成完整的 html 文件格式; - 八、此时,已经能生成完整的 HTML 文件。但只是个纯静态的页面,没有样式没有交互。接下来咱们就是要插入 JS 与 CSS。咱们能够经过访问前端打包后生成的`asset-manifest.json`文件来获取相应的文件路径,并一样注入到 Html 中引用。 ```js const html = ` <!DOCTYPE html> <html lang="zh"> <head></head> <link href="${cssPath}" rel="stylesheet" /> <body> <div id="App">${AppString}</div> <script src="${scriptPath}"></script> </body> </html> ` ``` - 九、进行 **数据脱水**: 为了把服务端获取的数据同步到前端。主要是将数据序列化后,插入到 html 中,返回给前端。 ```js import serialize from 'serialize-javascript' // 获取数据 const initState = store.getState() const html = ` <!DOCTYPE html> <html lang="zh"> <head></head> <body> <div id="App"></div> <script type="application/json" id="SSR_HYDRATED_DATA">${serialize(initState)}</script> </body> </html> ` ctx.status = 200 ctx.body = html ``` > **Tips**: > > 这里比较特别的有两点: > > 1. 使用了`serialize-javascript`序列化 store, 替代了`JSON.stringify`,保证数据的安全性,避免代码注入和 XSS 攻击; > > 2. 使用 json 进行传输,能够得到更快的加载速度; - 十、Client 层 **数据吸水**: 初始化 store 时,以脱水后的数据为初始化数据,同步建立 store。 ```js const hydratedEl = document.getElementById('SSR_HYDRATED_DATA') const hydrateData = JSON.parse(hydratedEl.textContent) // 使用初始 state 建立 Redux store const store = createStore(reducer, hydrateData) ```
<h3 id="8">8. 函数式编程</h3>
函数式编程是一种 编程范式,你能够理解为一种软件架构的思惟模式。它有着独立一套理论基础与边界法则,追求的是 更简洁、可预测、高复用、易测试。其实在现有的众多知名库中,都蕴含着丰富的函数式编程思想,如 React / Redux 等。
常见的编程范式:
函数式编程
函数式编程的理念:
纯函数(肯定性函数): 是函数式编程的基础,可使程序变得灵活,高度可拓展,可维护;
优点:
条件:
new Date()
或者Math.randon()
等这种不可控因素;split / join / map
;函数复合: 将多个函数进行组合后调用,能够实现将一个个函数单元进行组合,达成最后的目标;
扁平化嵌套: 首先,咱们必定能想到组合函数最简单的操做就是 包裹,由于在 JS 中,函数也能够当作参数:
f(g(k(x)))
: 嵌套地狱,可读性低,当函数复杂后,容易让人一脸懵逼;xxx(f, g, k)(x)
结果传递: 若是想实现上面的方式,那也就是xxx
函数要实现的即是: 执行结果在各个函数之间的执行传递;
reduce
,它能够按数组的顺序依次执行,传递执行结果;pipe
,用于函数组合:// ...fs: 将函数组合成数组; // Array.prototype.reduce 进行组合; // p: 初始参数; const pipe = (...fs) => p => fs.reduce((v, f) => f(v), p)
- **使用**: 实现一个 驼峰命名 转 中划线命名 的功能: ```js // 'Guo DongDong' --> 'guo-dongdong' // 函数组合式写法 const toLowerCase = str => str.toLowerCase() const join = curry((str, arr) => arr.join(str)) const split = curry((splitOn, str) => str.split(splitOn)); const toSlug = pipe( toLowerCase, split(' '), join('_'), encodeURIComponent, ); console.log(toSlug('Guo DongDong')) ``` - **好处**: - 隐藏中间参数,不须要临时变量,避免了这个环节的出错概率; - 只需关注每一个纯函数单元的稳定,再也不须要关注命名,传递,调用等; - 可复用性强,任何一个函数单元均可被任意复用和组合; - 可拓展性强,成本低,例如如今加个需求,要查看每一个环节的输出: ```js const log = curry((label, x) => { console.log(`${ label }: ${ x }`); return x; }); const toSlug = pipe( toLowerCase, log('toLowerCase output'), split(' '), log('split output'), join('_'), log('join output'), encodeURIComponent, ); ``` > Tips: > > 一些工具纯函数可直接引用`lodash/fp`,例如`curry/map/split`等,并不须要像咱们上面这样本身实现; - **数据不可变性**(immutable): 这是一种数据理念,也是函数式编程中的核心理念之一: - **倡导**: 一个对象再被建立后便不会再被修改。当须要改变值时,是返回一个全新的对象,而不是直接在原对象上修改; - **目的**: 保证数据的稳定性。避免依赖的数据被未知地修改,致使了自身的执行异常,能有效提升可控性与稳定性; - 并不等同于`const`。使用`const`建立一个对象后,它的属性仍然能够被修改; - 更相似于`Object.freeze`: 冻结对象,但`freeze`仍没法保证深层的属性不被串改; - `immutable.js`: js 中的数据不可变库,它保证了数据不可变,在 React 生态中被普遍应用,大大提高了性能与稳定性; - `trie`数据结构: - 一种数据结构,能有效地深度冻结对象,保证其不可变; - **结构共享**: 能够共用不可变对象的内存引用地址,减小内存占用,提升数据操做性能; - 避免不一样函数之间的 **状态共享**,数据的传递使用复制或全新对象,遵照数据不可变原则; - 避免从函数内部 **改变外部状态**,例如改变了全局做用域或父级做用域上的变量值,可能会致使其它单位错误; - 避免在单元函数内部执行一些 **反作用**,应该将这些操做抽离成更独立的工具单元; - 日志输出 - 读写文件 - 网络请求 - 调用外部进程 - 调用有反作用的函数
高阶函数: 是指 以函数为参数,返回一个新的加强函数 的一类函数,它一般用于:
函数式编程的好处:
总结:
Tips:其实咱们很难也不须要在面试过程当中去完美地阐述出整套思想,这里也只是浅尝辄止,一些我的理解而已。博主也是初级小菜鸟,停留在表面而已,只求对你们能有所帮助,轻喷🤣;
我我的以为: 这些编程范式之间,其实并不矛盾,各有各的 优劣势。
理解和学习它们的理念与优点,合理地 设计融合,将优秀的软件编程思想用于提高咱们应用;
全部设计思想,最终的目标必定是使咱们的应用更加 解耦颗粒化、易拓展、易测试、高复用,开发更为高效和安全;
有一些库能让你们很快地接触和运用函数思想:
Underscore.js
/Lodash/fp
/Rxjs
等。
到此,想必你们会发现已经开始深刻一些理论和原理层面了,并不像上篇那么的浅显易懂了。但这也是个必经之路,不可能永远停留在 5分钟掌握的技术 上。再也不停留在语言的表面,而是理解更深刻的原理,模式,架构,因果,你就会忽然发现你成为高级软件工程师了。😁。
但愿各位小伙伴能沉下心来,一些理论、概念虽然枯燥,但反复琢磨后再本身实践尝试下,就能有本身的理解。
当你开始面试高级工程师时,面试官便再也不重点关注你会不会写stopPropagation
或者会不会水平居中了,而是更在意你本身的思考和研究能力了。表现出本身深刻理解研究的成果,定会让面试官另眼相看。
Tips:字节跳动招中高级前端或实习,有兴趣内推的同窗可简历邮件至 guoxiaodong.tayde@bytedance.com (标题: 姓名-岗位-地点) 或关注下面公众号加我微信详聊哈。
博主真的写得很辛苦,再不 star 下,真的要哭了。~ github。🤑