dongzhe3917875 查看完整档案

_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 该用户太懒什么也没留下

个人动态

dongzhe3917875 收藏了文章 · 10月16日

附录 A：Transducing

Transducing 是我们这本书要讲到的更为高级的技术。它继承了第 8 章数组操作的许多思想。

Transducing 就是通过减少来转换。

首先，为什么

``````function isLongEnough(str) {
return str.length >= 5;
}

function isShortEnough(str) {
return str.length <= 10;
}``````

``````var words = [ "You", "have", "written", "something", "very", "interesting" ];

words
.filter( isLongEnough )
.filter( isShortEnough );
// ["written","something"]``````

``````zip(
list1.filter( isLongEnough ).filter( isShortEnough ),
list2.filter( isLongEnough ).filter( isShortEnough ),
list3.filter( isLongEnough ).filter( isShortEnough )
)``````

``````function isCorrectLength(str) {
return isLongEnough( str ) && isShortEnough( str );
}``````

``````words
.map(
pipe( removeInvalidChars, upper, elide )
);``````

``````words
.map( strUppercase )
.filter( isLongEnough )
.filter( isShortEnough )
.reduce( strConcat, "" );
// "WRITTENSOMETHING"``````

如何，下一步

把 Map/Filter 表示为 Reduce

``````function strUppercase(str) { return str.toUpperCase(); }
function strConcat(str1,str2) { return str1 + str2; }

function strUppercaseReducer(list,str) {
list.push( strUppercase( str ) );
return list;
}

function isLongEnoughReducer(list,str) {
if (isLongEnough( str )) list.push( str );
return list;
}

function isShortEnoughReducer(list,str) {
if (isShortEnough( str )) list.push( str );
return list;
}

words
.reduce( strUppercaseReducer, [] )
.reduce( isLongEnoughReducer, [] )
.reduce( isShortEnough, [] )
.reduce( strConcat, "" );
// "WRITTENSOMETHING"``````

``````function strUppercaseReducer(list,str) {
return list.concat( [strUppercase( str )] );
}

function isLongEnoughReducer(list,str) {
if (isLongEnough( str )) return list.concat( [str] );
return list;
}

function isShortEnoughReducer(list,str) {
if (isShortEnough( str )) return list.concat( [str] );
return list;
}``````

参数化 Reducers

``````function filterReducer(predicateFn) {
return function reducer(list,val){
if (predicateFn( val )) return list.concat( [val] );
return list;
};
}

var isLongEnoughReducer = filterReducer( isLongEnough );
var isShortEnoughReducer = filterReducer( isShortEnough );``````

``````function mapReducer(mapperFn) {
return function reducer(list,val){
return list.concat( [mapperFn( val )] );
};
}

var strToUppercaseReducer = mapReducer( strUppercase );``````

``````words
.reduce( strUppercaseReducer, [] )
.reduce( isLongEnoughReducer, [] )
.reduce( isShortEnough, [] )
.reduce( strConcat, "" );``````

提取共用组合逻辑

``````return list.concat( .. );

// 或者
return list;``````

``````function WHATSITCALLED(list,val) {
return list.concat( [val] );
}``````

`WHATSITCALLED(..)` 函数做了些什么呢，它接收两个参数（一个数组和另一个值），将值 concat 到数组的末尾返回一个新的数组。所以这个 `WHATSITCALLED(..)` 名字不合适，我们可以叫它 `listCombination(..)`

``````function listCombination(list,val) {
return list.concat( [val] );
}``````

``````function mapReducer(mapperFn) {
return function reducer(list,val){
return listCombination( list, mapperFn( val ) );
};
}

function filterReducer(predicateFn) {
return function reducer(list,val){
if (predicateFn( val )) return listCombination( list, val );
return list;
};
}``````

参数化组合

``````function mapReducer(mapperFn,combinationFn) {
return function reducer(list,val){
return combinationFn( list, mapperFn( val ) );
};
}

function filterReducer(predicateFn,combinationFn) {
return function reducer(list,val){
if (predicateFn( val )) return combinationFn( list, val );
return list;
};
}``````

``````var strToUppercaseReducer = mapReducer( strUppercase, listCombination );
var isLongEnoughReducer = filterReducer( isLongEnough, listCombination );
var isShortEnoughReducer = filterReducer( isShortEnough, listCombination );``````

``````var curriedMapReducer = curry( function mapReducer(mapperFn,combinationFn){
return function reducer(list,val){
return combinationFn( list, mapperFn( val ) );
};
} );

var curriedFilterReducer = curry( function filterReducer(predicateFn,combinationFn){
return function reducer(list,val){
if (predicateFn( val )) return combinationFn( list, val );
return list;
};
} );

var strToUppercaseReducer =
curriedMapReducer( strUppercase )( listCombination );
var isLongEnoughReducer =
curriedFilterReducer( isLongEnough )( listCombination );
var isShortEnoughReducer =
curriedFilterReducer( isShortEnough )( listCombination );``````

附录 A：Transducing（下）－－－－ 四天后更新

** 【上一章】[翻译连载 | 第 11 章：融会贯通 －《JavaScript轻量级函数式编程》 |《你不知道的JS》姊妹篇
](https://juejin.im/post/5a0cf1... **

iKcamp原创新书《移动Web前端高效开发实战》已在亚马逊、京东、当当开售。

iKcamp官网：https://www.ikcamp.com

《iKcamp出品｜全网最新｜微信小程序｜基于最新版1.0开发者工具之初中级培训教程分享》
《iKcamp出品｜基于Koa2搭建Node.js实战项目教程》

dongzhe3917875 收藏了文章 · 10月16日

附录 A：Transducing

Transducing 是我们这本书要讲到的更为高级的技术。它继承了第 8 章数组操作的许多思想。

Transducing 就是通过减少来转换。

首先，为什么

``````function isLongEnough(str) {
return str.length >= 5;
}

function isShortEnough(str) {
return str.length <= 10;
}``````

``````var words = [ "You", "have", "written", "something", "very", "interesting" ];

words
.filter( isLongEnough )
.filter( isShortEnough );
// ["written","something"]``````

``````zip(
list1.filter( isLongEnough ).filter( isShortEnough ),
list2.filter( isLongEnough ).filter( isShortEnough ),
list3.filter( isLongEnough ).filter( isShortEnough )
)``````

``````function isCorrectLength(str) {
return isLongEnough( str ) && isShortEnough( str );
}``````

``````words
.map(
pipe( removeInvalidChars, upper, elide )
);``````

``````words
.map( strUppercase )
.filter( isLongEnough )
.filter( isShortEnough )
.reduce( strConcat, "" );
// "WRITTENSOMETHING"``````

如何，下一步

把 Map/Filter 表示为 Reduce

``````function strUppercase(str) { return str.toUpperCase(); }
function strConcat(str1,str2) { return str1 + str2; }

function strUppercaseReducer(list,str) {
list.push( strUppercase( str ) );
return list;
}

function isLongEnoughReducer(list,str) {
if (isLongEnough( str )) list.push( str );
return list;
}

function isShortEnoughReducer(list,str) {
if (isShortEnough( str )) list.push( str );
return list;
}

words
.reduce( strUppercaseReducer, [] )
.reduce( isLongEnoughReducer, [] )
.reduce( isShortEnough, [] )
.reduce( strConcat, "" );
// "WRITTENSOMETHING"``````

``````function strUppercaseReducer(list,str) {
return list.concat( [strUppercase( str )] );
}

function isLongEnoughReducer(list,str) {
if (isLongEnough( str )) return list.concat( [str] );
return list;
}

function isShortEnoughReducer(list,str) {
if (isShortEnough( str )) return list.concat( [str] );
return list;
}``````

参数化 Reducers

``````function filterReducer(predicateFn) {
return function reducer(list,val){
if (predicateFn( val )) return list.concat( [val] );
return list;
};
}

var isLongEnoughReducer = filterReducer( isLongEnough );
var isShortEnoughReducer = filterReducer( isShortEnough );``````

``````function mapReducer(mapperFn) {
return function reducer(list,val){
return list.concat( [mapperFn( val )] );
};
}

var strToUppercaseReducer = mapReducer( strUppercase );``````

``````words
.reduce( strUppercaseReducer, [] )
.reduce( isLongEnoughReducer, [] )
.reduce( isShortEnough, [] )
.reduce( strConcat, "" );``````

提取共用组合逻辑

``````return list.concat( .. );

// 或者
return list;``````

``````function WHATSITCALLED(list,val) {
return list.concat( [val] );
}``````

`WHATSITCALLED(..)` 函数做了些什么呢，它接收两个参数（一个数组和另一个值），将值 concat 到数组的末尾返回一个新的数组。所以这个 `WHATSITCALLED(..)` 名字不合适，我们可以叫它 `listCombination(..)`

``````function listCombination(list,val) {
return list.concat( [val] );
}``````

``````function mapReducer(mapperFn) {
return function reducer(list,val){
return listCombination( list, mapperFn( val ) );
};
}

function filterReducer(predicateFn) {
return function reducer(list,val){
if (predicateFn( val )) return listCombination( list, val );
return list;
};
}``````

参数化组合

``````function mapReducer(mapperFn,combinationFn) {
return function reducer(list,val){
return combinationFn( list, mapperFn( val ) );
};
}

function filterReducer(predicateFn,combinationFn) {
return function reducer(list,val){
if (predicateFn( val )) return combinationFn( list, val );
return list;
};
}``````

``````var strToUppercaseReducer = mapReducer( strUppercase, listCombination );
var isLongEnoughReducer = filterReducer( isLongEnough, listCombination );
var isShortEnoughReducer = filterReducer( isShortEnough, listCombination );``````

``````var curriedMapReducer = curry( function mapReducer(mapperFn,combinationFn){
return function reducer(list,val){
return combinationFn( list, mapperFn( val ) );
};
} );

var curriedFilterReducer = curry( function filterReducer(predicateFn,combinationFn){
return function reducer(list,val){
if (predicateFn( val )) return combinationFn( list, val );
return list;
};
} );

var strToUppercaseReducer =
curriedMapReducer( strUppercase )( listCombination );
var isLongEnoughReducer =
curriedFilterReducer( isLongEnough )( listCombination );
var isShortEnoughReducer =
curriedFilterReducer( isShortEnough )( listCombination );``````

附录 A：Transducing（下）－－－－ 四天后更新

** 【上一章】[翻译连载 | 第 11 章：融会贯通 －《JavaScript轻量级函数式编程》 |《你不知道的JS》姊妹篇
](https://juejin.im/post/5a0cf1... **

iKcamp原创新书《移动Web前端高效开发实战》已在亚马逊、京东、当当开售。

iKcamp官网：https://www.ikcamp.com

《iKcamp出品｜全网最新｜微信小程序｜基于最新版1.0开发者工具之初中级培训教程分享》
《iKcamp出品｜基于Koa2搭建Node.js实战项目教程》

dongzhe3917875 收藏了文章 · 9月4日

v3-force VS v4-force

d3.layout.force被重命名为d3.forceSimulation。新的力导向仿真使用速度Verlet算法而不是位置Verlet算法，即追踪节点的位置(node.x，node.y)和速度(node.vx,node.vy)而不是之前的位置(node.px，node.py)。

force布局不再依赖拖拽行为，因为你可以直接创建一个可拖动的力导向布局。设置node.fx和node.fy来修正节点的位置。simulation.find方法替代了泰森多边形的SVG叠加，以找到最近节点的引用。

四叉树可以用来做什么

1. 用来在数据库中放置和定位文件（称作记录或键）

2. 2D空间碰撞校验

3. 地理空间划分常用于GIS查询

4. 图像处理

基于四叉树2D空间碰撞校验

d3.v4里的force就是使用到四叉树的碰撞校验。该方法也经常被游戏领域使用到。

存储方法二：

``````// 测试代码二
var data = [[1.1,1.2]];

``````Quadtree.extend() // [[x0,y0],[x1,y1]]四叉树的边界，即矩形的左上顶点的坐标，与右下顶点坐标

``````                                        root
/                  \
第一象限：_root[0]                      第三象限：_root[2]==> [2,6]
/                   \
/                     \``````

1.1

dongzhe3917875 赞了文章 · 6月18日

介绍

``````const props = {
user: {
posts: [
{ title: 'Foo', comments: [ 'Good one!', 'Interesting...' ] },
{ title: 'Bar', comments: [ 'Ok' ] },
{ title: 'Baz', comments: [] },
]
}
}``````
``````// access deeply nested values...
props.user &&
props.user.posts &&
props.user.posts[0] &&

``````// updating the previous example...
props.user &&
props.user.posts &&
props.user.posts[0] &&

JavaScript

``````const get = (p, o) =>
p.reduce((xs, x) => (xs && xs[x]) ? xs[x] : null, o)
// let's pass in our props object...
// [ 'Good one!', 'Interesting...' ]
// null``````

``````const get = (p, o) =>
p.reduce((xs, x) =>
(xs && xs[x]) ? xs[x] : null, o)``````

``````const get = p => o =>
p.reduce((xs, x) =>
(xs && xs[x]) ? xs[x] : null, o)

``````console.log(getUserComments(props))
// [ 'Good one!', 'Interesting...' ]
// null``````

`get`函数实质上就是在减少先前的路径。

``['id'].reduce((xs, x) => (xs && xs[x]) ? xs[x] : null, {id: 10})``

Ramda

`Ramda`提供了一个`path`方法，两个参数输入， `path`以及`object`。让我们用`Ramda`重写这个例子。

``const getUserComments = R.path(['user', 'posts', 0, 'comments'])``

``````getUserComments(props) // [ 'Good one!', 'Interesting...' ]

``````const getUserComments = R.pathOr([], ['user', 'posts', 0, 'comments'])
getUserComments(props) // [ 'Good one!', 'Interesting...' ]

Ramda + Folktale

``````const getPath = R.compose(Maybe.fromNullable, R.path)

``console.log(userComments) // Just([ 'Good one!', 'Interesting...' ])``

``````console.log(userComments.map(x => x.join(',')))
// Just('Good one!,Interesting...')``````

``````const userComments =
getPath(['user', 'posts', 8, 'title'], props)

// Nothing``````

``````// example using composeK to access a deeply nested value.
const getProp = R.curry((name, obj) =>
Maybe.fromNullable(R.prop(name, obj)))
getProp(0),
getProp('posts'),
getProp('user')
)
// Just([ 'Good one!', 'Interesting...' ])
// Nothing``````

``````// using compose and chain
const getProp = R.curry((name, obj) =>
Maybe.fromNullable(R.prop(name, obj)))
R.compose(
R.chain(getProp(0)),
R.chain(getProp('posts')),
getProp('user')
)
// Just([ 'Good one!', 'Interesting...' ])
// Nothing``````

``````// example using pipeK to access a deeply nested value.
const getProp = R.curry((name, obj) =>
Maybe.fromNullable(R.prop(name, obj)))
getProp('user'),
getProp('posts'),
getProp(0),
)
// Just([ 'Good one!', 'Interesting...' ])
// Nothing``````

Lenses

``````// lenses
// [ 'Good one!', 'Interesting...' ]``````

总结

``````// updating the previous example...
props.user &&
props.user.posts &&
props.user.posts[0] &&

dongzhe3917875 收藏了文章 · 5月13日

全面拥抱React-Hooks

一、React-Hooks要解决什么？

• 大型组件很难拆分和重构，也很难测试。
• 业务逻辑分散在组件的各个方法之中，导致重复逻辑或关联逻辑。
• 组件类引入了复杂的编程模式，比如 Render props 和高阶组件

• 加强版函数组件，完全不使用"类"，就能写出一个全功能的组件
• 组件尽量写成纯函数，如果需要外部功能和副作用，就用钩子把外部代码"钩"进来

二、如何用好React-Hooks？

明确几点概念

• 所有的hook，在默认没有依赖项数组每次渲染都会更新
• 每次 Render 时Props、State、事件处理、Effect等hooks都遵循 Capture Value 的特性
• Render时会注册各种变量，函数包括hooks，N次Render就会有N个互相隔离的状态作用域
• 如果你的useEffect依赖数组为[]，那么它初始化一次，且使用的state，props等永远是他初始化时那一次Render保存下来的值
• React 会确保 setState,dispatch,context 函数的标识是稳定的，可以安全地从 hooks 的依赖列表中省略

Function Component中每次Render都会形成一个快照并保留下来，这样就确保了状态可控，hook默认每次都更新，会导致重复请求等一系列问题，如果给[]就会一尘不变，因此用好hooks最重要就是学会控制它的变化

三、一句话概括Hook API

• useState 异步设置更新state
• useEffect 处理副作用（请求，事件监听，操作DOM等）
• useContext 接收一个 context 对象并返回该 context 的当前值
• useReducer 同步处理复杂state，减少了对深层传递回调的依赖
• useCallback 返回一个 memoized 回调函数，避免非必要渲染
• useMemo 返回一个 memoized 值，使得控制具体子节点何时更新变得更容易，减少了对纯组件的需要，可替代shouldComponentUpdate
• useRef 返回一个在组件的整个生命周期内保持不变 ref 对象，其 .current 属性是可变的，可以绕过 Capture Value 特性
• useLayoutEffect 其函数签名与 useEffect 相同，但它会在所有的 DOM 变更之后同步调用 effect
• useImperativeHandle 应当与 forwardRef 一起使用，将 ref 自定义暴露给父组件的实例值
• useDebugValue 可用于在 React 开发者工具中显示自定义 hook 的标签

四、关注异同点

useState 与 this.setState

• 相同点：都是异步的，例如在 onClick 事件中，调用两次 setState，数据只改变一次。
• 不同点：类中的 setState 是合并，而useState中的 setState 是替换。

useState 与 useReducer

• 相同点：都是操作state
• 不同点：使用 useState 获取的 setState 方法更新数据时是异步的；而使用 useReducer 获取的 dispatch 方法更新数据是同步的。
• 推荐：当 state 状态值结构比较复杂时，使用useReducer

useLayoutEffect 与 useEffect

• 相同点：都是在浏览器完成布局与绘制之后执行副作用操作
• 不同点：useEffect 会延迟调用，useLayoutEffect 会同步调用阻塞视觉更新，可以使用它来读取 DOM 布局并同步触发重渲染
• 推荐：一开始先用 useEffect，只有当它出问题的时候再尝试使用 useLayoutEffect

useCallback 与 useMemo

• 相同点：都是返回memoized，useCallback( fn, deps) 相当于 useMemo( ( ) => fn, deps)
• 不同点：useMemo返回缓存的变量，useCallback返回缓存的函数
• 推荐：不要过早的性能优化，搭配食用口味更佳（详见下文性能优化）

五、性能优化

react中性能的优化点在于：

• 1、调用setState，就会触发组件的重新渲染，无论前后的state是否不同
• 2、父组件更新，子组件也会自动的更新

之前的解决方案

• 使用immutable进行比较，在不相等的时候调用setState；
• 在 shouldComponentUpdate 中判断前后的 props和 state，如果没有变化，则返回false来阻止更新。
• 使用 React.PureComponent

使用hooks function之后的解决方案

• 1、使用 React.memo等效于 PureComponent，但它只比较 props，且返回值相反，true才会跳过更新
``````const Button = React.memo((props) => {
// 你的组件
}, fn);// 也可以自定义比较函数``````
• 2、用 useMemo 优化每一个具体的子节点（详见实践3）
• 3、useCallback Hook 允许你在重新渲染之间保持对相同的回调引用以使得 shouldComponentUpdate 继续工作（详见实践3）
• 4、useReducer Hook 减少了对深层传递回调的依赖（详见实践2）

如何惰性创建昂贵的对象？

• 当创建初始 state 很昂贵时，我们可以传一个 函数 给 useState 避免重新创建被忽略的初始 state
``````function Table(props) {
// ⚠️ createRows() 每次渲染都会被调用
const [rows, setRows] = useState(createRows(props.count));
// ...
// ✅ createRows() 只会被调用一次
const [rows, setRows] = useState(() => createRows(props.count));
// ...
}``````
• 避免重新创建 useRef() 的初始值，确保某些命令式的 class 实例只被创建一次：
``````function Image(props) {
// ⚠️ IntersectionObserver 在每次渲染都会被创建
const ref = useRef(new IntersectionObserver(onIntersect));
// ...
}
function Image(props) {
const ref = useRef(null);
// ✅ IntersectionObserver 只会被惰性创建一次
function getObserver() {
if (ref.current === null) {
ref.current = new IntersectionObserver(onIntersect);
}
return ref.current;
}
// 当你需要时，调用 getObserver()
// ...
}``````

六、注意事项

Hook 规则

• 在最顶层使用 Hook
• 只在 React 函数中调用 Hook，不要在普通的 JavaScript 函数中调用
• 将条件判断放置在 hook 内部
• 所有 Hooks 必须使用 use 开头，这是一种约定，便于使用 ESLint 插件 来强制 Hook 规范 以避免 Bug;
``````useEffect(function persistForm() {
if (name !== '') {
localStorage.setItem('formData', name);
}
});``````

告诉 React 用到了哪些外部变量，如何对比依赖

``````useEffect(() => {
document.title = "Hello, " + name;
}, [name]); // 以useEffect为示例，适用于所有hook``````

不要在hook内部set依赖变量，否则你的代码就像旋转的菊花一样停不下来

``````useEffect(() => {
const id = setInterval(() => {
setCount(count + 1);
}, 1000);
return () => clearInterval(id);
}, [count]);// 以useEffect为示例，适用于所有hook``````

不要在useMemo内部执行与渲染无关的操作

• useMemo返回一个 memoized 值,把“创建”函数和依赖项数组作为参数传入 useMemo，它仅会在某个依赖项改变时才重新计算 memoized 值，避免在每次渲染时都进行高开销的计算。
• 传入 useMemo 的函数会在渲染期间执行，请不要在这个函数内部执行与渲染无关的操作。
``const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);``

七、实践场景示例

1、只想执行一次的 Effect 里需要依赖外部变量

【将更新与动作解耦】-【useEffect，useReducer，useState】

• 1-1、使用setState的函数式更新解决依赖一个变量

``````useEffect(() => {
const id = setInterval(() => {
setCount(c => c + 1);
}, 1000);
return () => clearInterval(id);
}, []);``````
• 1-2、使用useReducer解决依赖多个变量
``````import React, { useReducer, useEffect } from "react";

const initialState = {
count: 0,
step: 1,
};

function reducer(state, action) {
const { count, step } = state;
if (action.type === 'tick') {
return { count: count + step, step };
} else if (action.type === 'step') {
return { count, step: action.step };
} else {
throw new Error();
}
}
export default function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
const { count, step } = state;
console.log(count);
useEffect(() => {
const id = setInterval(() => {
dispatch({ type: 'tick' });
}, 1000);
return () => clearInterval(id);
}, [dispatch]);

return (
<>
<h1>{count}</h1>
<input value={step} onChange={e => {
dispatch({
type: 'step',
step: Number(e.target.value)
});
}} />
</>
);
}``````

2、大型的组件树中深层传递回调

【通过 context 往下传一个 dispatch 函数】-【createContext，useReducer，useContext】

``````/**index.js**/
import React, { useReducer } from "react";
import Count from './Count'
export const StoreDispatch = React.createContext(null);
const initialState = {
count: 0,
step: 1,
};

function reducer(state, action) {
const { count, step } = state;
switch (action.type) {
case 'tick':
return { count: count + step, step };
case 'step':
return { count, step: action.step };
default:
throw new Error();
}
}
export default function Counter() {
// 提示：`dispatch` 不会在重新渲染之间变化
const [state, dispatch] = useReducer(reducer, initialState);
return (
<StoreDispatch.Provider value={dispatch}>
<Count state={state} />
</StoreDispatch.Provider>
);
}

/**Count.js**/
import React, { useEffect,useContext }  from 'react';
import {StoreDispatch} from '../index'
import styles from './index.css';

export default function(props) {
const { count, step } = props.state;
const dispatch = useContext(StoreDispatch);
useEffect(() => {
const id = setInterval(() => {
dispatch({ type: 'tick' });
}, 1000);
return () => clearInterval(id);
}, [dispatch]);
return (
<div className={styles.normal}>
<h1>{count}</h1>
<input value={step} onChange={e => {
dispatch({
type: 'step',
step: Number(e.target.value)
});
}} />
</div>
);
}``````

3、代码内聚，更新可控

【层层依赖，各自管理】-【useEffect，useCallback，useContext】

``````function App() {
const [count, setCount] = useState(1);
const countRef = useRef();// 在组件生命周期内保持唯一实例，可穿透闭包传值

useEffect(() => {
countRef.current = count; // 将 count 写入到 ref
});
// 只有countRef变化时，才会重新创建函数
const callback = useCallback(() => {
const currentCount = countRef.current //保持最新的值
console.log(currentCount);
}, [countRef]);
return (
<Parent callback={callback} count={count}/>
)
}
function Parent({ count, callback }) {
// count变化才会重新渲染
const child1 = useMemo(() => <Child1 count={count} />, [count]);
// callback变化才会重新渲染，count变化不会 Rerender
const child2 = useMemo(() => <Child2 callback={callback} />, [callback]);
return (
<>
{child1}
{child2}
</>
)
}``````

八、自定义 HOOK

实现this.setState的callback

``````function useStateCallback(init) {
const [state, setState] = useState(init)
const ref = useRef(init)

const handler = useCallback((value, cb) => {
setState(value)
if(cb) {
ref.current = value
cb(ref.current)
}
}, [setState])

return [state, handler]
}``````

获取上一轮的 props 或 state

``````function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}``````

只在更新时运行 effect

``````function useUpdate(fn) {
const mounting = useRef(true);
useEffect(() => {
if (mounting.current) {
mounting.current = false;
} else {
fn();
}
});
}``````

组件是否销毁

``````function useIsMounted(fn) {
const [isMount, setIsMount] = useState(false);
useEffect(() => {
if (!isMount) {
setIsMount(true);
}
return () => setIsMount(false);
}, []);
return isMount;
}``````

惰性初始化useRef

``````function useInertRef(obj) { // 传入一个实例 new IntersectionObserver(onIntersect)
const ref = useRef(null);
if (ref.current === null) {
// ✅ IntersectionObserver 只会被惰性创建一次
ref.current = obj;
}
return ref.current;
}``````

dongzhe3917875 收藏了文章 · 5月5日

引言

React v16.8 引入了 Hooks，它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。这些功能可以在应用程序中的各个组件之间使用，从而易于共享逻辑。Hook 令人兴奋并迅速被采用，React 团队甚至想象它们最终将替换类组件。

引入 Typescript 后的变化

有状态组件(ClassComponent)

API 对应为:

``````React.Component<P, S>

class MyComponent extends React.Component<Props, State> { ...``````

``````import * as React from "react";

export interface Props {
name: string;
enthusiasmLevel?: number;
}

interface State {
currentEnthusiasm: number;
}

class Hello extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { currentEnthusiasm: props.enthusiasmLevel || 1 };
}

onIncrement = () => this.updateEnthusiasm(this.state.currentEnthusiasm + 1);
onDecrement = () => this.updateEnthusiasm(this.state.currentEnthusiasm - 1);

render() {
const { name } = this.props;

if (this.state.currentEnthusiasm <= 0) {
throw new Error('You could be a little more enthusiastic. :D');
}

return (
<div className="hello">
<div className="greeting">
Hello {name + getExclamationMarks(this.state.currentEnthusiasm)}
</div>
<button onClick={this.onDecrement}>-</button>
<button onClick={this.onIncrement}>+</button>
</div>
);
}

updateEnthusiasm(currentEnthusiasm: number) {
this.setState({ currentEnthusiasm });
}
}

export default Hello;

function getExclamationMarks(numChars: number) {
return Array(numChars + 1).join('!');
}``````

TypeScript 可以对 JSX 进行解析，充分利用其本身的静态检查功能，使用泛型进行 Props、 State 的类型定义。定义后在使用 this.state 和 this.props 时可以在编辑器中获得更好的智能提示，并且会对类型进行检查。

react 规定不能通过 this.props.xxx 和 this.state.xxx 直接进行修改,所以可以通过 readonly 将 State 和 Props 标记为不可变数据：

``````interface Props {
}

interface State {
}

export class Hello extends React.Component<Props, State> {
someMethod() {
this.props.number = 123; // Error: props 是不可变的
this.state.color = 'red'; // Error: 你应该使用 this.setState()
}
}
``````

无状态组件(StatelessComponent)

API 对应为:

``````// SFC: stateless function components
const List: React.SFC<IProps> = props => null
// v16.8起，由于hooks的加入，函数式组件也可以使用state，所以这个命名不准确。新的react声明文件里，也定义了React.FC类型^_^
React.FunctionComponent<P> or React.FC<P>。

const MyComponent: React.FC<Props> = ...
``````

``````import React from 'react'

const Button = ({ onClick: handleClick, children }) => (
<button onClick={handleClick}>{children}</button>
)
``````

``````import React, { MouseEvent, SFC } from 'react';

type Props = { onClick(e: MouseEvent<HTMLElement>): void };

const Button: SFC<Props> = ({ onClick: handleClick, children }) => (
<button onClick={handleClick}>{children}</button>
);
``````

事件处理

``````function handleMouseChange (event: any) {
console.log(event.clientY)
}``````

• 通用的 React Event Handler

API 对应为:

``React.ReactEventHandler<HTMLElement>``

``````const handleChange: React.ReactEventHandler<HTMLInputElement> = (ev) => { ... }

<input onChange={handleChange} ... />``````
• 特殊的 React Event Handler

``````ClipboardEvent<T = Element> 剪贴板事件对象

DragEvent<T = Element> 拖拽事件对象

ChangeEvent<T = Element>  Change 事件对象

KeyboardEvent<T = Element> 键盘事件对象

MouseEvent<T = Element> 鼠标事件对象

TouchEvent<T = Element>  触摸事件对象

WheelEvent<T = Element> 滚轮事件对象

AnimationEvent<T = Element> 动画事件对象

TransitionEvent<T = Element> 过渡事件对象
``````

``````const handleChange = (ev: React.MouseEvent<HTMLDivElement>) => { ... }

<div onMouseMove={handleChange} ... />``````

React 元素

API 对应为:

``React.ReactElement<P> or JSX.Element``

``````// 表示React元素概念的类型: DOM元素组件或用户定义的复合组件
const elementOnly: React.ReactElement = <div /> || <MyComponent />;``````

React Node

API 对应为:

``React.ReactNode``

``const elementOrComponent: React.ReactNode = 'string' || 0 || false || null || undefined || <div /> || <MyComponent />;``

React CSS 属性

API 对应为:

``React.CSSProperties``

``````const styles: React.CSSProperties = { display: 'flex', ...
const element = <div style={styles} ...``````

Hooks 登场

React 一直都提倡使用函数组件，但是有时候需要使用 state 或者其他一些功能时，只能使用类组件，因为函数组件没有实例，没有生命周期函数，只有类组件才有。

Hooks 是 React 16.8 新增的特性，它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

• 基础

• `useState`
• `useEffect`
• `useContext`
• 高级

• `useReducer`
• `useCallback`
• `useMemo`
• `useRef`
• `useImperativeHandle`
• `useLayoutEffect`
• `useDebugValue`

useState with TypeScript

API 对应为:

``````// 传入唯一的参数: initialState，可以是数字，字符串等，也可以是对象或者数组。
// 返回的是包含两个元素的数组：第一个元素，state 变量，setState 修改 state值的方法。
const [state, setState] = useState(initialState);``````

`useState`是一个允许我们替换类组件中的 this.state 的挂钩。我们执行该挂钩，该挂钩返回一个包含当前状态值和一个用于更新状态的函数的数组。状态更新时，它会导致组件的重新 render。下面的代码显示了一个简单的 useState 钩子:

``````import * as React from 'react';

const MyComponent: React.FC = () => {
const [count, setCount] = React.useState(0);
return (
<div onClick={() => setCount(count + 1)}>
{count}
</div>
);
};``````

useEffect with TypeScript

API 对应为:

``````// 两个参数
// 第一个是一个函数，是在第一次渲染(componentDidMount)以及之后更新渲染之后会进行的副作用。这个函数可能会有返回值，倘若有返回值，返回值也必须是一个函数，会在组件被销毁(componentWillUnmount)时执行。
// 第二个参数是可选的，是一个数组，数组中存放的是第一个函数中使用的某些副作用属性。用来优化 useEffect
useEffect(() => { // 需要在componentDidMount执行的内容 return function cleanup() { // 需要在componentWillUnmount执行的内容 } }, [])``````

`useEffect`是用于我们管理副作用（例如 API 调用）并在组件中使用 React 生命周期的。useEffect 将回调函数作为其参数，并且回调函数可以返回一个清除函数(cleanup)。回调将在第一次渲染(componentDidMount) 和组件更新时(componentDidUpate)内执行，清理函数将组件被销毁(componentWillUnmount)内执行。

``````useEffect(() => {
// 给 window 绑定点击事件

return () => {
// 给 window 移除点击事件
}
});``````

``````useEffect(() => {
// 使用浏览器API更新文档标题
document.title = `You clicked \${count} times`;
}, [count]);    // 只有当数组中 count 值发生变化时，才会执行这个useEffect。``````

useContext with TypeScript

`useContext`允许您利用`React context`这样一种管理应用程序状态的全局方法，可以在任何组件内部进行访问而无需将值传递为 props。

useContext 函数接受一个 Context 对象并返回当前上下文值。当提供程序更新时，此挂钩将触发使用最新上下文值的重新渲染。

``````import { createContext, useContext } from 'react';

props ITheme {
backgroundColor: string;
color: string;
}

const ThemeContext = createContext<ITheme>({
backgroundColor: 'black',
color: 'white',
})

const themeContext = useContext<ITheme>(ThemeContext);
``````

useReducer with TypeScript

``const [state，dispatch] =  useReducer（reducer，initialState，init）;``

``````const initialState = 0;
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {number: state.number + 1};
case 'decrement':
return {number: state.number - 1};
default:
throw new Error();
}
}
function init(initialState){
return {number:initialState};
}
function Counter(){
const [state, dispatch] = useReducer(reducer, initialState,init);
return (
<>
Count: {state.number}
<button onClick={() => dispatch({type: 'increment'})}>+</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
</>
)
}``````

useCallback with TypeScript

`useCallback` 钩子返回一个 `memoized` 回调。这个钩子函数有两个参数：第一个参数是一个内联回调函数，第二个参数是一个数组。数组将在回调函数中引用，并按它们在数组中的存在顺序进行访问。

``````const memoizedCallback =  useCallback（（）=> {
doSomething（a，b）;
}，[ a，b ]，）;``````

useCallback 将返回一个记忆化的回调版本，它仅会在某个依赖项改变时才重新计算 memoized 值。当您将回调函数传递给子组件时，将使用此钩子。这将防止不必要的渲染，因为仅在值更改时才执行回调，从而可以优化组件。可以将这个挂钩视为与`shouldComponentUpdate`生命周期方法类似的概念。

useMemo with TypeScript

`useMemo`返回一个 memoized 值。 传递“创建”函数和依赖项数组。useMemo 只会在其中一个依赖项发生更改时重新计算 memoized 值。此优化有助于避免在每个渲染上进行昂贵的计算。

``const memoizedValue =  useMemo（（） =>  computeExpensiveValue（ a， b），[ a， b ]）;``
useMemo 在渲染过程中传递的函数会运行。不要做那些在渲染时通常不会做的事情。例如，副作用属于 useEffect，而不是 useMemo。

• useCallback 和 useMemo 都可缓存函数的引用或值。
• 从更细的使用角度来说 useCallback 缓存函数的引用，useMemo 缓存计算数据的值。

useRef with TypeScript

`useRef`挂钩允许你创建一个 ref 并且允许你访问基础 DOM 节点的属性。当你需要从元素中提取值或获取与 DOM 相关的元素信息（例如其滚动位置）时，可以使用此方法。

``const refContainer  =  useRef（initialValue）;``

useRef 返回一个可变的 ref 对象，其`.current`属性被初始化为传递的参数（initialValue）。返回的对象将存留在整个组件的生命周期中。

``````function TextInputWithFocusButton() {
const inputEl = useRef<HTMLInputElement>(null);
const onButtonClick = () => {
inputEl.current.focus();
};

return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}``````

useImperativeHandle with TypeScript

`useImperativeHandle`可以让你在使用 ref 时，自定义暴露给父组件的实例值。

``useImperativeHandle(ref, createHandle, [inputs])``

useImperativeHandle 钩子函数接受 3 个参数: 一个 React ref、一个 createHandle 函数和一个用于暴露给父组件参数的可选数组。

``````function FancyInput(props, ref) {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} ... />;
}
FancyInput = React.forwardRef(FancyInput);

const fancyInputRef = React.createRef();
<FancyInput ref={fancyInputRef}>Click me!</FancyInput>;``````

useLayoutEffect with TypeScript

``useLayoutEffect(() => { doSomething });``

``````import React, { useRef, useState, useLayoutEffect } from 'react';

export default () => {

const divRef = useRef(null);

const [height, setHeight] = useState(50);

useLayoutEffect(() => {
// DOM 更新完成后打印出 div 的高度
console.log('useLayoutEffect: ', divRef.current.clientHeight);
})

return <>
<div ref={ divRef } style={{ background: 'red', height: height }}>Hello</div>
<button onClick={ () => setHeight(height + 50) }>改变 div 高度</button>
</>

}``````

useDebugValue with TypeScript

`useDebugValue`是用于调试自定义挂钩(自定义挂钩请参考`https://reactjs.org/docs/hooks-custom.html`)的工具。它允许您在 React Dev Tools 中显示自定义钩子函数的标签。

示例

``````- 组件
- 基础表格
- ECharts 图表
- 表单
- 基础表单
- 分步表单
- 编辑器

- 控制台
- 错误页面
- 404``````

最后

dongzhe3917875 收藏了文章 · 4月8日

前端性能优化 24 条建议（2020）

1. 减少 HTTP 请求

• Queueing: 在请求队列中的时间。
• Stalled: 从TCP 连接建立完成，到真正可以传输数据之间的时间差，此时间包括代理协商时间。
• Proxy negotiation: 与代理服务器连接进行协商所花费的时间。
• DNS Lookup: 执行DNS查找所花费的时间，页面上的每个不同的域都需要进行DNS查找。
• Initial Connection / Connecting: 建立连接所花费的时间，包括TCP握手/重试和协商SSL。
• SSL: 完成SSL握手所花费的时间。
• Request sent: 发出网络请求所花费的时间，通常为一毫秒的时间。
• Waiting(TFFB): TFFB 是发出页面请求到接收到应答数据第一个字节的时间总和，它包含了 DNS 解析时间、 TCP 连接时间、发送 HTTP 请求时间和获得响应消息第一个字节的时间。

2. 使用 HTTP2

HTTP2 相比 HTTP1.1 有如下几个优点：

多路复用

HTTP1.1 如果要同时发起多个请求，就得建立多个 TCP 连接，因为一个 TCP 连接同时只能处理一个 HTTP1.1 的请求。

首部压缩

HTTP2 提供了首部压缩功能。

``````:authority: unpkg.zhimg.com
:method: GET
:path: /za-js-sdk@2.16.0/dist/zap.js
:scheme: https
accept: */*
accept-encoding: gzip, deflate, br
accept-language: zh-CN,zh;q=0.9
cache-control: no-cache
pragma: no-cache
referer: https://www.zhihu.com/
sec-fetch-dest: script
sec-fetch-mode: no-cors
sec-fetch-site: cross-site
user-agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36``````
``````:authority: zz.bdstatic.com
:method: GET
:scheme: https
accept: */*
accept-encoding: gzip, deflate, br
accept-language: zh-CN,zh;q=0.9
cache-control: no-cache
pragma: no-cache
referer: https://www.zhihu.com/
sec-fetch-dest: script
sec-fetch-mode: no-cors
sec-fetch-site: cross-site
user-agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36``````

HTTP/2 在客户端和服务器端使用“首部表”来跟踪和存储之前发送的键－值对，对于相同的数据，不再通过每次请求和响应发送。

``````Header1:foo

``62 63 64``

优先级

HTTP2 可以对比较紧急的请求设置一个较高的优先级，服务器在收到这样的请求后，可以优先处理。

服务器推送

HTTP2 新增的一个强大的新功能，就是服务器可以对一个客户端请求发送多个响应。换句话说，除了对最初请求的响应外，服务器还可以额外向客户端推送资源，而无需客户端明确地请求。

3. 使用服务端渲染

• 优点：首屏渲染快，SEO 好。
• 缺点：配置麻烦，增加了服务器的计算压力。

4. 静态资源使用 CDN

CDN 原理

1. 浏览器要将域名解析为 IP 地址，所以需要向本地 DNS 发出请求。
2. 本地 DNS 依次向根服务器、顶级域名服务器、权限服务器发出请求，得到网站服务器的 IP 地址。
3. 本地 DNS 将 IP 地址发回给浏览器，浏览器向网站服务器 IP 地址发出请求并得到资源。

1. 浏览器要将域名解析为 IP 地址，所以需要向本地 DNS 发出请求。
2. 本地 DNS 依次向根服务器、顶级域名服务器、权限服务器发出请求，得到全局负载均衡系统（GSLB）的 IP 地址。
3. 本地 DNS 再向 GSLB 发出请求，GSLB 的主要功能是根据本地 DNS 的 IP 地址判断用户的位置，筛选出距离用户较近的本地负载均衡系统（SLB），并将该 SLB 的 IP 地址作为结果返回给本地 DNS。
4. 本地 DNS 将 SLB 的 IP 地址发回给浏览器，浏览器向 SLB 发出请求。
5. SLB 根据浏览器请求的资源和地址，选出最优的缓存服务器发回给浏览器。
6. 浏览器再根据 SLB 发回的地址重定向到缓存服务器。
7. 如果缓存服务器有浏览器需要的资源，就将资源发回给浏览器。如果没有，就向源服务器请求资源，再发给浏览器并缓存在本地。

8. 压缩文件

• JavaScript：UglifyPlugin
• CSS ：MiniCssExtractPlugin
• HTML：HtmlWebpackPlugin

gzip 是目前最流行和最有效的压缩方法。举个例子，我用 Vue 开发的项目构建后生成的 app.js 文件大小为 1.4MB，使用 gzip 压缩后只有 573KB，体积减少了将近 60%。

``````npm install compression-webpack-plugin --save-dev
npm install compression``````

webpack 配置

``````const CompressionPlugin = require('compression-webpack-plugin');

module.exports = {
plugins: [new CompressionPlugin()],
}``````

node 配置

``````const compression = require('compression')
// 在其他中间件前使用
app.use(compression())``````

9. 图片优化

(1). 图片延迟加载

``<img data-data-original="https://avatars0.githubusercontent.com/u/22117876?s=460&u=7bd8f32788df6988833da6bd155c3cfbebc68006&v=4">``

``````const img = document.querySelector('img')
img.src = img.dataset.src``````

(2). 响应式图片

``````<picture>
<source srcset="banner_w1000.jpg" media="(min-width: 801px)">
<source srcset="banner_w800.jpg" media="(max-width: 800px)">
<img data-original="banner_w800.jpg" alt="">
</picture>``````

``````@media (min-width: 769px) {
.bg {
background-image: url(bg1080.jpg);
}
}
@media (max-width: 768px) {
.bg {
background-image: url(bg768.jpg);
}
}``````

(4). 降低图片质量

``npm i -D image-webpack-loader``

webpack 配置

``````{
test: /\.(png|jpe?g|gif|svg)(\?.*)?\$/,
use:[
{
options: {
limit: 10000, /* 图片大小小于1000字节限制时会自动转成 base64 码引用*/
name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
},
/*对图片进行压缩*/
{
options: {
bypassOnDebug: true,
}
}
]
}``````

10. 通过 webpack 按需加载代码，提取第三库代码，减少 ES6 转为 ES5 的冗余代码

根据文件内容生成文件名，结合 import 动态引入组件实现按需加载

``````output: {
filename: '[name].[contenthash].js',
chunkFilename: '[name].[contenthash].js',
path: path.resolve(__dirname, '../dist'),
},``````

提取第三方库

``````optimization: {
runtimeChunk: {
name: 'manifest' // 将 webpack 的 runtime 代码拆分为一个单独的 chunk。
},
splitChunks: {
cacheGroups: {
vendor: {
name: 'chunk-vendors',
test: /[\\/]node_modules[\\/]/,
priority: -10,
chunks: 'initial'
},
common: {
name: 'chunk-common',
minChunks: 2,
priority: -20,
chunks: 'initial',
reuseExistingChunk: true
}
},
}
},``````
• test: 用于控制哪些模块被这个缓存组匹配到。原封不动传递出去的话，它默认会选择所有的模块。可以传递的值类型：RegExp、String和Function;
• priority：表示抽取权重，数字越大表示优先级越高。因为一个 module 可能会满足多个 cacheGroups 的条件，那么抽取到哪个就由权重最高的说了算；
• reuseExistingChunk：表示是否使用已有的 chunk，如果为 true 则表示如果当前的 chunk 包含的模块已经被抽取出去了，那么将不会重新生成新的。
• minChunks（默认是1）：在分割之前，这个代码块最小应该被引用的次数（译注：保证代码块复用性，默认配置的策略是不需要多次引用也可以被分割）
• chunks (默认是async) ：initial、async和all
• name(打包的chunks的名字)：字符串或者函数(函数可以根据条件自定义名字)

减少 ES6 转为 ES5 的冗余代码

Babel 转化后的代码想要实现和原来代码一样的功能需要借助一些帮助函数，比如：

``class Person {}``

``````"use strict";

function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}

var Person = function Person() {
_classCallCheck(this, Person);
};``````

``````"use strict";

var _classCallCheck2 = require("@babel/runtime/helpers/classCallCheck");

var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);

function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : { default: obj };
}

var Person = function Person() {
(0, _classCallCheck3.default)(this, Person);
};``````

``npm i -D @babel/plugin-transform-runtime @babel/runtime``

`.babelrc` 文件中

``````"plugins": [
"@babel/plugin-transform-runtime"
]``````

11. 减少重绘重排

1. 解析HTML生成DOM树。
2. 解析CSS生成CSSOM规则树。
3. 将DOM树与CSSOM规则树合并在一起生成渲染树。
4. 遍历渲染树开始布局，计算每个节点的位置大小信息。
5. 将渲染树每个节点绘制到屏幕。

• 添加或删除可见的 DOM 元素
• 元素位置改变
• 元素尺寸改变
• 内容改变
• 浏览器窗口尺寸改变

• 用 JavaScript 修改样式时，最好不要直接写样式，而是替换 class 来改变样式。
• 如果要对 DOM 元素执行一系列操作，可以将 DOM 元素脱离文档流，修改完成后，再将它带回文档。推荐使用隐藏元素（display:none）或文档碎片（DocumentFragement），都能很好的实现这个方案。

12. 使用事件委托

``````<ul>
<li>苹果</li>
<li>香蕉</li>
<li>凤梨</li>
</ul>

// good
document.querySelector('ul').onclick = (event) => {
const target = event.target
if (target.nodeName === 'LI') {
console.log(target.innerHTML)
}
}

document.querySelectorAll('li').forEach((e) => {
e.onclick = function() {
console.log(this.innerHTML)
}
}) ``````

13. 注意程序的局部性

• 时间局部性：在一个具有良好时间局部性的程序中，被引用过一次的内存位置很可能在不远的将来被多次引用。
• 空间局部性 ：在一个具有良好空间局部性的程序中，如果一个内存位置被引用了一次，那么程序很可能在不远的将来引用附近的一个内存位置。

``````function sum(arry) {
let i, sum = 0
let len = arry.length

for (i = 0; i < len; i++) {
sum += arry[i]
}

return sum
}``````

``````// 二维数组
function sum1(arry, rows, cols) {
let i, j, sum = 0

for (i = 0; i < rows; i++) {
for (j = 0; j < cols; j++) {
sum += arry[i][j]
}
}
return sum
}``````

``````// 二维数组
function sum2(arry, rows, cols) {
let i, j, sum = 0

for (j = 0; j < cols; j++) {
for (i = 0; i < rows; i++) {
sum += arry[i][j]
}
}
return sum
}``````

性能测试

• cpu: i5-7400
• 浏览器: chrome 70.0.3538.110

1242316

• 重复引用相同变量的程序具有良好的时间局部性
• 对于具有步长为 k 的引用模式的程序，步长越小，空间局部性越好；而在内存中以大步长跳来跳去的程序空间局部性会很差

14. if-else 对比 switch

``````if (color == 'blue') {

} else if (color == 'yellow') {

} else if (color == 'white') {

} else if (color == 'black') {

} else if (color == 'green') {

} else if (color == 'orange') {

} else if (color == 'pink') {

}

switch (color) {
case 'blue':

break
case 'yellow':

break
case 'white':

break
case 'black':

break
case 'green':

break
case 'orange':

break
case 'pink':

break
}``````

15. 查找表

``````switch (index) {
case '0':
return result0
case '1':
return result1
case '2':
return result2
case '3':
return result3
case '4':
return result4
case '5':
return result5
case '6':
return result6
case '7':
return result7
case '8':
return result8
case '9':
return result9
case '10':
return result10
case '11':
return result11
}``````

``````const results = [result0,result1,result2,result3,result4,result5,result6,result7,result8,result9,result10,result11]

return results[index]``````

``````const map = {
red: result0,
green: result1,
}

return map[color]``````

16. 避免页面卡顿

60fps 与设备刷新率

``````for (let i = 0, len = arry.length; i < len; i++) {
process(arry[i])
}``````

``````const todo = arry.concat()
setTimeout(() => {
process(todo.shift())
if (todo.length) {
setTimeout(arguments.callee, 25)
} else {
callback(arry)
}
}, 25)``````

17. 使用 requestAnimationFrame 来实现视觉变化

``````/**
* If run as a requestAnimationFrame callback, this
* will be run at the start of the frame.
*/
}

18. 使用 Web Workers

Web Worker 使用其他工作线程从而独立于主线程之外，它可以执行任务而不干扰用户界面。一个 worker 可以将消息发送到创建它的 JavaScript 代码, 通过将消息发送到该代码指定的事件处理程序（反之亦然）。

Web Worker 适用于那些处理纯数据，或者与浏览器 UI 无关的长时间运行脚本。

``````var myWorker = new Worker('worker.js');
// 你可以通过postMessage() 方法和onmessage事件向worker发送消息。
first.onchange = function() {
myWorker.postMessage([first.value,second.value]);
console.log('Message posted to worker');
}

second.onchange = function() {
myWorker.postMessage([first.value,second.value]);
console.log('Message posted to worker');
}``````

``````onmessage = function(e) {
var workerResult = 'Result: ' + (e.data[0] * e.data[1]);
console.log('Posting message back to main script');
postMessage(workerResult);
}``````

onmessage处理函数在接收到消息后马上执行，代码中消息本身作为事件的data属性进行使用。这里我们简单的对这2个数字作乘法处理并再次使用postMessage()方法，将结果回传给主线程。

``````myWorker.onmessage = function(e) {
result.textContent = e.data;
}``````

19. 使用位操作

JavaScript 中的数字都使用 IEEE-754 标准以 64 位格式存储。但是在位操作中，数字被转换为有符号的 32 位格式。即使需要转换，位操作也比其他数学运算和布尔操作快得多。

取模

``````if (value % 2) {
// 奇数
} else {
// 偶数
}
// 位操作
if (value & 1) {
// 奇数
} else {
// 偶数
}``````
取反
``````~~10.12 // 10
~~10 // 10
~~'1.5' // 1
~~undefined // 0
~~null // 0``````
位掩码
``````const a = 1
const b = 2
const c = 4
const options = a | b | c``````

``````// 选项 b 是否在选项中
if (b & options) {
...
}``````

21. 降低 CSS 选择器的复杂性

(1). 浏览器读取选择器，遵循的原则是从选择器的右边到左边读取。

``````#block .text p {
color: red;
}``````
1. 查找所有 P 元素。
2. 查找结果 1 中的元素是否有类名为 text 的父元素
3. 查找结果 2 中的元素是否有 id 为 block 的父元素

(2). CSS 选择器优先级

``内联 > ID选择器 > 类选择器 > 标签选择器``

1. 选择器越短越好。
2. 尽量使用高优先级的选择器，例如 ID 和类选择器。
3. 避免使用通配符 *。

22. 使用 flexbox 而不是较早的布局模型

• Chrome 29+
• Firefox 28+
• Internet Explorer 11
• Opera 17+
• Safari 6.1+ (prefixed with -webkit-)
• Android 4.4+
• iOS 7.1+ (prefixed with -webkit-)

24. 合理使用规则，避免过度优化

1. 加载时优化
2. 运行时优化

检查加载性能

• 白屏时间：指从输入网址，到页面开始显示内容的时间。
• 首屏时间：指从输入网址，到页面完全渲染的时间。

``````<script>
</script>``````

其他参考资料

更多文章，欢迎关注

dongzhe3917875 关注了标签 · 3月22日

typescript

TypeScript 是微软开发的 JavaScript 的超集，TypeScript兼容JavaScript，可以载入JavaScript代码然后运行。TypeScript与JavaScript相比进步的地方。包括：加入注释，让编译器理解所支持的对象和函数，编译器会移除注释，不会增加开销；增加一个完整的类结构，使之更新是传统的面向对象语言。

dongzhe3917875 收藏了文章 · 2月2日

使用递归遍历并转换树形数据（以 TypeScript 为例）

``````const data = {
name: "A",
nodes: [
{ name: "B", nodes: [{ name: "F" }] },
{ name: "C" },
{
name: "D",
nodes: [
{ name: "G" },
{ name: "H" },
{ name: "I", nodes: [{ name: "J" }, { name: "K" }] }
]
},
{ name: "E" }
]
};``````

``````interface INode {
name: string;
nodes?: INode[];
}

function makeTree(roots: INode[]): JQuery<HTMLElement> {
function makeNode(node: INode): JQuery<HTMLElement> {
const \$div = \$("<div>").text(node.name || "");
const \$li = \$("<li>").append(\$div);
if (node.nodes && node.nodes.length) {
\$li.append(makeNodeList(node.nodes));
}
return \$li;
}

function makeNodeList(nodes: INode[]): JQuery<HTMLElement> {
return nodes
.map(child => makeNode(child))
.reduce((\$ul, \$li) => {
return \$ul.append(\$li);
}, \$("<ul>"));
}

return makeNodeList(roots);
}``````

遍历方法

广度遍历

1. 准备一个空队列；
2. 将根（单根或多根均可）节点放到队列中；
3. 从队列中取出一个节点
4. 处理（比如打印）这个节点
5. 检查节点的子节点，如果有，全部依次添加到队列中
6. 回到第 3 步开始处理，直到队列为空（处理完成）
``````function travelWidely(roots: INode[]) {
const queue: INode[] = [...roots];
while (queue.length) {
const node = queue.shift()!;
// 打印节点名称及其子节点数
console.log(`\${node.name} \${node.nodes && node.nodes.length || ""}`);
if (node.nodes && node.nodes.length) {
queue.push(...node.nodes);
}
}
}

// 开始遍历
travelWidely([data]);``````

`const node = queue.shift()!`，这后面的 `!` 后缀表示声明其结果不为 `undefined``null`。这是一个 TypeScript 语法。由于 `.shift()` 在数组中没有元素时会返回 `undefined`，所以其返回类型被声明为 `INode | undefined`，由于从逻辑可以保证 `.shift()` 一定会返回一个节点对象，所以这里用 `!` 后缀忽略类型中的 `undefined` 部分，使 `node` 的类型被推导为 `INode`

深度遍历

• 递归调用点，递归调用自己（或另一个可能会调用自己的函数）
• 递归结束点，退出当前函数

• 递归调用点：如果该节点有子节点，依次对子节点调用 `printNode(children[i])`
• 递归结束点：处理完所有子节点（子节点数量是有限的，所以一定会结束）

``````function printNode(node: INode) {
// 处理该节点
console.log(node.name);

// 递归调用点：循环对子节点调用 printNode
node.nodes!.forEach(child => printNode(child));

// 递归结束点：循环完成，return
}``````

``````// 注意参数支持传入单根或多根，
// 如果像 travelWidely 那样只支持多根（单根是特例）也是可以的
function travelDeeply(roots: INode | INode[]) {
function printNode(node: INode) {
console.log(`\${node.name} \${node.nodes && node.nodes.length || ""}`);
if (node.nodes && node.nodes.length) {
// 依次对子节点递归调用 printNode
node.nodes.forEach(child => printNode(child));
}
}

// 这里 printNode 和 node => printNode(node) 等价
(Array.isArray(roots) ? roots : [roots]).forEach(printNode);
}

// 开始遍历
travelDeeply(data);``````

遍历还没讲完

深度遍历生成节点

• `makeNode` 处理单个节点，它调用 `makeNodeList` 处理子节点列表
• `makeNodeList` 遍历节点列表，分别对其调用 `makeNode` 来进行处理

`makeNode``makeNodeList` 的相互调用形成了递归，上述两条都是递归调用点，而递归结束点同样也有两条：

• `makeNode` 处理的节点没有子节点时，不会调用 `makeNodeList`
• `makeNodeList` 中的循环结束时，不会再调用 `makeNode`

广度遍历生成节点

``````interface INode {
name: string;
nodes?: INode[];
dom: JQuery;    // 附加生成的 DOM
}``````
``````function makeTreeWidely(roots: INode[]): JQuery {
// 从一组节点生成 <ul>，为每个节点生成并附加 <li>，
// 同时将 <li> 到到 <ul> 中保存结构信息
function makeUl(nodes: INode[]) {
return nodes
.map(node => {
const \$li = \$("<li>")
.append(\$("<div>").text(node.name || ""));
node.dom = \$li;
return \$li;
})
.reduce((\$ul, \$li) => \$ul.append(\$li), \$("<ul>"));
}

const \$rootUl = makeUl(roots);

const queue: INode[] = [...roots];
while (queue.length) {
const node = queue.shift()!;

if (node.nodes && node.nodes.length) {
const \$ul = makeUl(node.nodes);
node.dom.append(\$ul);
queue.push(...node.nodes);
}
}
return \$rootUl;
}``````

没有副作用的广度遍历生成节点

``````// 声明一个新结构，它把 INode 和 DOM 组合在一起。
// 这个结构将代替 INode 作为队列的元素类型
interface IDomNode {
node: INode;
dom: JQuery;
}

function makeTreeWidely(roots: INode[]): JQuery {
// convert 将节点数组转换为 IDomNode 数组，
// 同时还干了原来 makeUl 干的事情，返回一个 \$ul
function convert(nodes: INode[]) {
const domNodes = nodes
.map(node => {
const \$li = \$("<li>")
.append(\$("<div>").text(node.name || ""));
return {
node,
dom: \$li
};
});

const \$ul = domNodes
.reduce((\$ul, dn) => \$ul.append(dn.dom), \$("<ul>"));

// 将两个数组组成一个元组（对象）返回
return {
domNodes,
\$ul
};
}

// 解析元组，声明变量 queue 和 \$rootUl，
// 并分别将 domNodes 和 \$ul 的值赋值给 queue 和 \$rootUl 两个变量
const { domNodes: queue, \$ul: \$rootUl } = convert(roots);

while (queue.length) {
const { node, dom } = queue.shift()!;

if (node.nodes && node.nodes.length) {
const { domNodes, \$ul } = convert(node.nodes);
dom.append(\$ul);
queue.push(...domNodes);
}
}
return \$rootUl;
}``````

dongzhe3917875 赞了文章 · 1月20日

TypeScript 真的值得吗？

TypeScript 克服了一些很难解决的问题，并成为前端编程领域的主流。 TypeScript 在这篇列出了最受欢迎的编程语言的文章中排名第七位。

• 编写良好的单元测试——应在合理范围内涵盖尽可能多的生产代码
• 结对编程——额外的审视可以捕捉到的错误远远超过语法错误
• 良好的同行评审流程——正确的同行评审可以检查出许多机器无法捕获的错误
• 使用 linter，例如 eslint

TypeScript 可以在这些基础之上增加额外的安全性，但我认为这在编程语言需求列表中应该排在后面。

健全性

``````// 'string' 类型不可分配给 'number' 类型
const increment = (i: number): number => { return i + "1"; }

// Argument of type '"98765432"' is not assignable to parameter of type .
// 无法将参数类型 '"98765432"' 分配给参数类型'number'。
const countdown: number = increment("98765432");``````

不健全

100％ 的健全性不是 Typescript 的目标，这是在 non-goals of TypeScript 列表中第 3 条中明确指出的事实：

...适用健全或“证明正确的”类型的系统。相反，要在正确性和生产率之间取得平衡。

``````interface A {
x: number;
}

let a: A = {x: 3}
let b: {x: number | string} = a;
b.x = "unsound";
let x: number = a.x; // 不健全的

a.x.toFixed(0); // 什么鬼?``````

``a.x.toFixed(0);``

TypeScript 挑战了现状，并声称降低使用类型的认知开销比类型健全性更重要。

TypeScript 不保证运行时的类型检查

``````const getFullName = async (): string => {
const person: AxiosResponse = await api();

//response.name.fullName 可能会在运行时返回 undefined
return response.name.fullName
}``````

可怕的 `any` 类型和严格性选项

`any` 类型就是这样，编译器允许任何操作或赋值。

TypeScript 在一些小细节上往往很好用，但是人们倾向于在 `any` 类型上花费很多时间。我最近在一个 Angular 项目中工作，看到很多这样的代码：

``````export class Person {
public _id: any;
public name: any;
public icon: any;``````

TypeScript 让你忘记类型系统。

``("oh my goodness" as any).ToFixed(1); // 还记得我说的健全性吗？``

`strict` 编译器选项启用了以下编译器设置，这些设置会使事情听起来更加合理：

• `--strictNullChecks`
• `--noImplicitAny`
• `--noImplicitThis`
• `--alwaysStrict`

`any` 的泛滥会破坏你类型的健全性。

结论

TypeScript 提供了基本的类型检查，但健全性和运行时类型检查不是它的目标，这使 TypeScript 在美好的世界和我们所处的现状中采取折衷。

TypeScript 的亮点在于有良好的 IDE 支持，例如 vscode，如果我们输入了错误的内容，将会获得很好的视觉反馈。

vscode中的TypeScript错误

TypeScript 启用了良好的类型检查，并且绝对要比没有类型检查或仅使用普通的 eslint 更好，但是我认为它还可以做更多的事情。对于那些想要更多的人来说，还能够提供足够多的编译器选项。

欢迎继续阅读本专栏其它高赞文章：

认证与成就

• 获得 198 次点赞
• 获得 12 枚徽章 获得 1 枚金徽章, 获得 2 枚银徽章, 获得 9 枚铜徽章

开源项目 & 著作

• 奇云

360奇云平台，从零到一的前端开发。