命名规则:除非在小于 5 行的函数里,否则不要使用单字命名变量说明:含义不清晰,不能做到「望文生义」BadCodevar l = data.length;GoodCode// 闭包只有一行代码,可以使用单字变量data.map(d => d.length)规则:不要使用名词加数字的命名方法说明:含义不清晰,不能做到「望文生义」BadCodevar obj = {};var obj2 = {…obj, key: 1};GoodCodevar obj = {};var objWithKey = {…obj, key: 1};规则:应该且只有方法和函数以动词开头此处动词没有包含时态变量名应该是名词或者名词短语。例外:回调函数生命周期函数框架级别函数getter/setter说明:函数的命名需要体现内部工作值变量以动词命名容易让人误解为是一个匿名函数BadCode// 以名词开头,看不明白什么有什么功能function option() {}// 时态不对function updatedTime() {}GoodCodefunction selectOption() {}function updateTime() {}规则:避免使用拼音或者缩写命名。例外:专有名词:weixin/POI传统约定:i/j/k 表示循环索引说明:含义不清晰,不能做到「望文生义」BadCodevar uo = function updateOrder(){}var as = [].slice;var ex = Object.extends;var nu = numberGoodCode// weixin/wx 是专有名词var weixinUser = {};var wx = weixin;// POI 是专有名词var poi = {};规则:名称长短应与其作用域大小相对应。例外:专有 API,如 alert说明:在上层作用域下的代码,会被更多函数使用到。其名称应该尽量长或者通用,以保证能够搜索到。BadCode// 在全局变量上定义了一个 item 变量,但是很难从命名上理解其作用是什么。window.item = {}GoodCodewindow.primaryProductItem = {};不要在变量/函数尾部加符号、数字说明:变量中加符号,往往是为了约定其优先级或者作用域。符号应该在变量名前面。BadCodefunction getDot_(){}function privateFn$$ (){}GoodCodefunction _getDot() {}function $$privateFn() {}规则:实例名称要和类名相关说明:类作为实例的所属,其名称表达的含义要一脉相承BadCodeclass Person() {}var dog = new Person(); // dog is a Person ?GoodCodeclass Person() {}var jack = new Person();规则:避免直白的中英文翻译说明:粗暴的翻译,更容易造成误解,还不如写拼音BadCode// 渲染「页面顶部的执行人」// 还是渲染「执行砍头的人」?function renderHeadExecutantPeople(){}GoodCodefunction renderHeader() {}规则:概念的命名要一以贯之说明:避免通一个概念在不同的代码用多种不同的单词描述。BadCode// 远程请求这个概念,先后用了 get/fetch/query// 三个名词去描述function getUserInfo() {}function fetchProductInfo() {}function queryPayment() {}GoodCode// 统一用 get 描述// 阅读代码的人能体会到其中的共性function getUserInfo() {}function getProductInfo() {}function getPayment() {}规则:别用双关语例外:专有词说明:双关语容易引起歧义BadCode// 订单类型,还是排序类型?var orderTypeGoodCodevar sortType规则:命名需要和实现一致说明:命名往往是实现的隐喻,如果存在差异则会让阅读者看不懂代码。BadCode// empty 的命名含义和实现截然相反// (我真的见过这种代码)function getProduct(id) { axios.delete(’/product’, {id}); }GoodCodefunction deleteProduct(id) { axios.delete(’/product’, {id}); }规则:对于布尔值的命名,需要默认其为「真」说明:布尔变量的名称中,如果加上 「not」之类的否定词,则相当于做了一次了逻辑判断。BadCodeconst notEmpty = !!array.length;GoodCodeconst empty = !array.length;函数规则:长度不能超过 20 行说明:代码太长说明做的事情不够专一,同时也会让阅读变得很困难。规则:Don’t repeat yourself说明:同样功能的代码不要重复三次规则:每个函数只做一件事情,并做好这件事说明:代码里的逻辑分支要尽量少,只做一件事情,并且要处理好边界和异常情况。规则:尽量减少函数的参数,包括 opitons、config 等参数说明:函数的输入越多,往往就代表功能约复杂规则:注释出来项目/业务的坑说明:对于比较奇怪的业务逻辑,或者因为系统、接口原因而写的比较奇怪的逻辑。要通过注释标注出来BadCodeframework.doSomeThing();framework.reset(); // 阅读者内心 OS:这里为啥要做一次 reset?framework.continueSomeThing();GoodCodeframework.doSomeThing();// framework 有个 bug,这里必须要做一次 rest 附链接: http://github.com/issuse/***framework.reset(); framework.continueSomeThing();规则:函数要尽量「纯」没有副作用说明:纯函数比较好测试,逻辑也比较清晰,可以放心的引入和删除。BadCodelet status;function method() { if (status) { … } }GoodCodefunction method(status) { if (status) { … } }规则:函数最好不要修改参数内的数据说明:修改参数会导致函数的作用变得不可预测BadCodefunction updateObj(obj, value) { obj.key = value; return obj;}GoodCodefunction updateObj(obj, value) { return {…obj, key: value};}规则:除非是 class 的方法,否则不要访问 this说明:this 的指向经常不固定,会导致代码难以理解。如果调用方不熟悉的话,很容易引起 Bug。BadCodefunction method() { console.log(this.value); }method() // 报错var obj = { method, value: 1}obj.method() // 输出 1GoodCodefunction method(value) { console.log(value); }规则:处理错误说明:错误也是一种逻辑分支,如果不处理的话,代码就不够健壮。前端代码处理错误的方式一般为提示用户有异常发生。如果错误不影响业务流程,则写入日志里并上报。BadCodefunction method(data) { try { return JSON.parse(data) } catch (e) {}}GoodCodefunction method(data) { try { return JSON.parse(data) } catch (e) { alert(‘数据处理失败’) }}数据规则:不要有 Magic Number说明:magic number 是指直接在代码中硬编码的数字,往往具有一些业务含义。这样会导致:数字的意义难以理解数值要改动时,要改很多地方BadCodeif (status === 1) { …} else if (type === 4) { …}GoodCodeenum Status { Closed }enum Type { Array }if (status === Status.Closed) { …} else if (type === Type.Array) { …}规则:不管是 react state 还是 vue data 存放的业务数据都要具备原子性。说明:原子性意味着独立,且不可分割。其它属性都由原子业务属性推导、计算而来,这样能保证状态的一致。BadCode// 当 status 为 open 的时候展示弹窗// 其它状态则隐藏弹窗{ data() { return { showAlert: false, status: ‘closed’, } }, onStatusChange() { if (status === ‘open’) { this.showAlert = true; } else { this.showAlert = false; } }}GoodCode// showAlert 为非原子的状态// 其状态可以由 status 推导而来{ data() { return { status: ‘closed’, } }, computed: { showAlert() { return this.status === ‘open’; } }}规则:对于 react state 和 vue data,应当区分业务状态和 UI 状态说明:状态和 UI 存储在一起,有时候传给后端的数据里会夹杂着没有必要的 UI 状态。业务代码和 UI 代码耦合在一起,业务代码没法复用。BadCode// 在一个列表中,用户可以对数据做多选// 然后删除他们class extends React.Component { async componentDidMount() { const listData = getData(); this.setState({ listData }) } check = (item) => { const listData = this.state.listData.map(i => { if (i === item) { return {…item, checked: true} } return i; }); this.setState({ listData }); } delete() { // 返回给后端的数据结构,会多出一个 checked 字段 deleteItems(this.state.listData.filter(i => i.checked)); } render() { const list = this.state.listData.map(item => { const className = [‘item’]; if (item.checked) className.push(‘active’); return <label className={className} onClick={() => this.check(item)} >{item.naem}</label>; }); return <> {list} <button onClick={this.delete}>delete</button> </> }}GoodCode// 在一个列表中,用户可以对数据做多选// 然后删除他们class extends React.Component { async componentDidMount() { const listData = getData(); // 使用独立的 selected 来保存 UI 状态 this.setState({ listData, selected: [] }) } check = (item) => { let { selected } = this.state; selected = selected.findOrInsert(s => s.id, item); this.setState({ selected }); } delete() { const { selected, listData } = this.state; deleteItems(listData.filter(i => selected.includes(i.id)))); } render() { const { selected, listData } = this.state; const list = listData.map(item => { const className = [‘item’]; if (selected.includes(item.id)) className.push(‘active’); return <label className={className} onClick={() => this.check(item)} >{item.naem}</label>; }); return <> {list} <button onClick={this.delete}>delete</button> </> }}规则:对于 react 应用,避免在 render 的时候修改状态说明:react 的 render 应该是纯函数,在 render 里运行 setState 会导致重复渲染,或者死循环。BadCode// 如果 type 为 http 的话,则自动转换为 httpsclass extends React.Component { render() { const { type } = this.state; if (type === ‘http’) { this.setState({ type: ‘https’}) } return <label>{type}</label>; }}GoodCode// 如果 type 为 http 的话,则自动转换为 httpsclass extends React.Component { get type() { const { type } = this.state; if (type === ‘http’) return ‘https’; return type; } render() { const type = this.type; return <label>{type}</label>; }}规则:对于双向绑定应用,避免数据循环依赖。说明:循环依赖轻则导致页面相应慢,重则导致出现脏数据。避免循环依赖的前提是理清业务逻辑,搞清楚数据之间的依赖关系。循环依赖也是双向绑定技术的诟病之一。BadCode// foo 和 bar 互相依赖,导致了死循环{ data() { return { foo: 1, }; }, computed: { bar() { return this.foo + 1; } }, watch() { bar() { this.foo = this.bar + 1; }, }}规则:访问数据时,需要考虑边界情况和 JS 弱类型的特性。说明:比如用双等号做判断BadCodeconst foo = ‘0’;const bar = 0// 做数据判断时不能用双等号foo == bar // truefoo ? 1 : 2 // 1bar ? 1 : 2 // 2// 仅通过变量有没有 length 来判断是否为数组if(obj.length) { obj.forEach(…) }GoodCodeconst foo = ‘0’;const bar = 0foo === bar // falseif (Array.isArray(obj)) { obj.forEach(…) }规则:不要在遍历数组的同时,改变数组数据说明:这样做会导致数据的异常。如果需要做这种操作,最好使用数组函数,或者操作拷贝数据。BadCodeconst array = [1,2,3,4,5,6,7,8,9,10];// 删除数组中的偶数for (var i = 0; i < array.length; i++) { if (array[i] % 2 == 0) array.splice(i);}// array 变成了 [1]GoodCodeconst array = [1,2,3,4,5,6,7,8,9,10];array.filter(a => !(a % 2))API规则:对 setTimeout 调用时,传递的时间参数必须有意义。说明:大多数场景下,setTimeout 后面传递一个时间是为了先执行后续的 A 代码,再延后执行代码闭包里的 B 代码,如右边示例代码。但如果随着业务迭代,A 被改成异步,或者执行时间很长的话。之前做的延迟执行的防御措施就时效了,也许反而 B 会比 A 先执行。BadCode// 代码的本来意图是让 B 延后执行setTimeout(() => { B();}, 1000);A();// 代码的意图是让 render 在下一帧执行// 但是不同设备,一帧时间是不固定的setTimeout(() => { render()}, 16);GoodCode// A 函数内要想办法使用 Promise 串接起来await A();b();// 使用系统提供的 API 执行动画帧requestAnimationFrame(() => { render(); });规则:不要使用陈旧的 API说明:陈旧的 API 往往有很多问题,比如安全、性能、不易读等。BadCode// 判断是否为数组Object.prototype.toString.call(array) === “[object Array]” // 查找数组里第一个偶数for (var i = 0; i < array.length; i++) { if (array[i] % 2 === 0) return array[i]; }// 遍历对象的 keyfor (var key in obj) { console.log(key); }// 判断字符串/数组是否包含’some text’.indexOf(‘some’) >= 0// 去除首位空格’ some text ‘.replace(/(^\s+|\s+$)/g, ‘’)// 新建对象/数组const array = new Array();const obj = new Object();GoodCodeArray.isArray(array)array.find(a => a % 2 === 0);Object.keys(obj).forEach(console.log)‘some text’.includes(‘some’)’ some text ‘.trim()const array = [];const obj = {};规则:对于 99.9% 的场景,你都不需要使用 React ref说明:React Ref 一般是用来处理和原生 DOM 交互的场景,比如 canvas。大部分对于 React ref 的使用都是错误的,大多都拿来用来控制子元素。这种场景我们更推荐用数据流(redux,mobx)或者用状态提升去做。React 官方有对「状态提升」的描述 https://react.docschina.org/d...BadCodeclass List extends React.Component { async refresh() { this.setState({ items: getItems(), }); } render() { return this.state.items.map(i => <label>{i}</label>); }}class extends React.Component { onRefresh = () => { // 用 ref 去调用子元素的方法 this.list.refresh(); } render() { return <> <List ref={l => this.list = l}></List> <button onClick={this.onRefresh}/> </>; }}GoodCodeclass List extends React.Component { render() { return this.props.items.map(i => <label>{i}</label>); }}class extends React.Component { // 把数据状态提升到父组件做操作 refresh = async () => { this.setState({ items: getItems(), }); } render() { return <> <List items{this.state.items}></List> <button onClick={this.refresh}/> </>; }}规则:不要用字符串拼接 url说明:字符串拼接 url 需要处理 encode 或者 decode 的情况,还有对于 ?和 # 的判断不对的话,很容易造成漏洞或者 Bug。目前浏览器和 Node 都已经提供了标准的 URL 解析方法。https://developer.mozilla.org…BadCode// 这段代码既没有对 key、value 做 encode// 也没有考虑 url 中 # 出现的情况const url = location.href;if (url.indexOf(’?’) >= 0) { return url + key + ‘=’ + value;} else { return url + ‘?’ + key + ‘=’ + value;}GoodCode// 使用标准的 URL 解析,风险会降低很多const url = new URL(urlStr);url.searchParams.set(key, value);return url.toString();逻辑规则:判真不判假说明:我们应该期望 if 条件内是个「真」值,而不是一个「假」值。第二种情况会导致代码不易理解。解决办法参考 布尔逻辑BadCode// if 条件内期望的是一个「假」值if (!(status !== Closed) { … }if (!(status !== Closed || type !== Array)) { …} GoodCodeif (status === Closed) { … }if (status === Closed && type === Array) { … }规则:if 条件中,不易出现超过 3 个逻辑操作符。例外:if 条件里可以被 「且」(&&)逻辑拆分成多个子条件说明:复杂的条件判断会让代码不易理解,逻辑上有漏洞的话容易引起 Bug。解决办法:声明中间变量BadCodeif (srcElem != dropElem && (srcElem.nextSibling || srcElem.nextElementSibling) != dropElem) {…}if (selectedItem || (selectedEmployee && selectedEmployee.empId && selectedEmployee) || employee) { … }GoodCodeconst nextSibling = srcElem.nextSibling || srcElem.nextElementSiblingif (srcElem != dropElem && nextSibling != dropElem ) { …} // 复杂的逻辑判断可以通过 && 做拆分if ( !Array.isArray(cur) && cur != null && typeof src[key] === ‘object’ && typeof cur === ‘object’) { … }规则:不要用嵌套的三元表达式说明:人们阅读嵌套三元表达式时,容易混淆语法的优先级,进而导致理解错代码的含义。对于这种情况,建议改成 if else。如果是在 react render 里,则建议独立成函数。BadCodefunction render(props) { const value = props.value; return <> {value < 10 ? value > 0 ? value : 200 - value : 100 - value} </>;}GoodCodefunction getValue(value) { if (value < 10) { if (value > 0) return value; return 200 - value; } return 100 - value;}function render(props) { const value = props.value; return <> {getValue(value)} </>;}规则:if 条件逻辑嵌套不要超过三层说明:过深的嵌套会导致理解困难。解决办法:合并判断条件,或者独立成函数。BadCodeif (status = Opened) { if (type = ‘array’) { if (code = Success) { doSomething(); } }}GoodCodeif (status = Opened && type = ‘array’ &&code = Success) { doSomething();}