📕
余烬的小册
数据结构与算法GitHub
  • 总述
  • 经验记录
    • 经验总结
      • web component
      • 前端性能优化总结与分析
      • 我的长列表优化方案
      • 双向通讯解决方案
      • 🔧基于istanbul实现代码测试覆盖率工具
      • 表单系统(低代码表单)
      • 跨端小程序
      • 设计一个即时聊天功能
      • 跨页面通讯 3658699fe4cb4d0bbe22b0881390bacd
    • 踩坑记录
      • HTML踩坑记录
      • Flutter踩坑记录
      • CSS踩坑记录
  • 源码解析
    • Vue源码解析
      • Vue2源码解析系列-响应式原理
      • Vue2源码解析系列-模板编译
      • Vue2源码解析系列-渲染系统(待更新)
        • Patch
      • Vue2源码解析系列-调度系统(todo)
      • Vue2组件更新流程(todo)
      • 如何学习Vue源码
      • Vue3源码解析系列-响应系统
      • Vue3源码解析系列-渲染系统
      • Vue3源码解析系列-组件化和渲染优化(todo)
      • Vue router源码解析(todo)
    • React源码解析(todo)
    • 微前端
      • qiankun源码解析(todo)
    • Vite源码解析
      • Vite Client源码
      • Vite Server源码(todo)
  • 前端技术
    • javaScript
      • ES6
        • 变量声明
        • 模块化
        • 箭头函数
        • 你不知道的for...of
        • 新的数据结构Set和Map
        • JavaScript异步编程终极解决方案
        • ES6 Class 3a0c0a225a534984aabe9a943c5df975
      • JavaScript Error
      • JavaScript浅拷贝和深拷贝
      • JavaScript闭包
      • JavaScript最佳实践
      • JavaScript设计模式
      • async函数的polyfill
    • 深入理解JavaScript系列
      • JavaScript中的继承
      • JavaScript原始类型和引用类型
      • JavaScript浅拷贝和深拷贝
      • JavaScript手写系列
      • JavaScript之this
      • 词法环境和环境记录
      • JavaScript内存泄漏
      • 执行上下文
      • 从ECMAScript规范中学习this
    • TypeScript
      • TypeScript基础教程
      • Typescript高级操作
      • TypeScript工具类型
      • Typescript手写实现工具类型
      • Typescript总结(思维导图)
    • 浏览器原理
      • 页面渲染原理
      • 浏览器存储
      • JavaScript事件循环
      • 事件循环
      • 跨域
      • DOM事件流
      • 从输入url到页面渲染
      • 判断节点之间的关系及根据节点关系查找节点
      • history API
    • 跨端技术
      • Flutter
        • Flutter布局组件
    • 前端工程化
      • Babel插件开发指南
      • 循环依赖
      • pm2
    • React
      • React 状态管理
      • React组件通讯
      • Redux入门
      • Flux
      • React Hook(todo)
      • Effect
  • 服务器端
    • 计算机网络
      • 应用层
      • 运输层
      • 物理层
      • 数据链路层
      • HTTP缓存
      • HTTPS
      • 网络层
    • NodeJs
      • Node.js
      • nodejs最佳实践
      • 《深入浅出Nodejs》小结
      • mongoose填充(populate)
      • node事件循环
      • Node子进程
      • nestjs从零开始
      • nodejs流
      • Nodejs调试
      • Koa源码解析
    • 服务器
      • 操作系统
      • Linux
      • nginx常用指令
      • nginx常用配置
    • 数据库
      • Mysql常见语法
      • MongoDB Indexes索引
  • 前端安全与性能优化
    • 前端安全
      • 跨站脚本攻击(XSS)
      • 跨站点请求伪造(CSRF)
      • 点击劫持
      • 中间人攻击
      • 越权攻击与JWT
    • 前端性能优化
      • 前端监控系统
      • 前端性能优化总结与分析 7348bba0918645b1899006dc842a64c1
      • 衡量性能的核心指标 0dc15ef127cf4f4a9f1137c377420292
      • 图片懒加载
  • 杂项
    • 其他
      • Git
      • web component框架
      • 实现滚动框的懒加载
      • Stencil指南
    • CSS
      • 定位和层叠上下文
      • BFC
      • 盒模型
      • css选择器
      • css变量
由 GitBook 提供支持
在本页
  • 干净代码(clean code)
  • includes优化多条件判断
  • replace/match/exec使用分组
  • 使用箭头函数
  • 使用扩展运算符
  • 使用结构赋值
  • 使用函数默认值
  • 使用模板字符串
  • 使用promise和async function处理异步
  • 控制反转
  • 可读性
  • JSdoc
  • Eslint
  • Typescript
  • 稳定性
  • 为展开运算符使用默认值
  • 使用可选链
  • Reduce始终提供初始值
  • Sort排序传递回调
  • Array.prototype.join遇到只有一个元素的数组时
  • 避免使用arguments
  • 禁用arguments.callee
  • 为for…in添加hasOwnProperty
  • 判断构造函数是否被new调用
  • 冻结对象
  • 使用Symbol
  • 属性描述符
  • Array.prototype.fill不要传递引用值为参数
  • Match和MatchAll使用捕获组
  • 技巧
  • 获取随机数
  • 判断类型
在GitHub上编辑
  1. 前端技术
  2. javaScript

JavaScript最佳实践

干净代码(clean code)

includes优化多条件判断

replace/match/exec使用分组

replace传递回调函数以获得更细粒度的操作。

使用箭头函数

使用扩展运算符

使用结构赋值

使用函数默认值

使用模板字符串

使用promise和async function处理异步

控制反转

可读性

JSdoc

Eslint

Typescript

稳定性

为展开运算符使用默认值

使用可选链

Reduce始终提供初始值

如果我们要合并数组内的元素,例如这样的结构:

const arr = [{keywords: [...]}, {keywords: [...]}]

很容易想到这样的代码

arr.reduce((a,b)=>[...a.keywords, ...b.keywords])

但是这隐藏着一个巨大的坑,因为当数组仅有一个元素(无论位置如何)并且没有提供初始值 initialValue,或者有提供 initialValue但是数组为空,那么此唯一值将被返回且 callbackfn不会被执行。

因此,当上面的例子中arr如果只有一个元素时,那么只能得到那个元素本身,而无法得到我们想要的数组。

const arr = [{keywords: [...]}]
arr.reduce((a,b)=>[...a.keywords, ...b.keywords])
// {keywords: [...]}

此外,当数组为空时调用reduce方法,会触发报错。

[].reduce((a,b)=>a>b?a:b)
// Uncaught TypeError: Reduce of empty array with no initial value

要避免出现这两个问题,就需要传递一个初始值,可以使用这个方法来解决上个例子中的问题。

arr.reduce((a, b) => a.concat(b.keywords), [])

提供初始值后,无论是原数组是一个空数组,抑或是数组中只有一个元素,都能得到一个数组结构的返回值,且不会触发异常(原数组是空数组时)。

因此使用reduce时通常传递初始值更安全。

Sort排序传递回调

在JavaScript中,Array.prototype.sort()方法用于对数组元素进行排序,默认行为(即不传递回调函数时)是将元素转换成字符串再进行比较。这可能会出现以下问题:

  1. 排序结果不符合预期:由于默认的排序算法只是将元素转换成字符串后进行比较,并不考虑数字大小或其他特殊情况,因此可能会导致排序结果不符合预期。

例如:

[1, 10, 2].sort()
// [1, 10, 2]

如果指明了 compareFn ,那么数组会按照调用该函数的返回值排序。即 a 和 b 是两个将要被比较的元素:

  • 如果 compareFn(a, b) 大于 0,b 会被排列到 a 之前。

  • 如果 compareFn(a, b) 小于 0,那么 a 会被排列到 b 之前;

  • 如果 compareFn(a, b) 等于 0,a 和 b 的相对位置不变。备注:ECMAScript 标准并不保证这一行为,而且也不是所有浏览器都会遵守(例如 Mozilla 在 2003 年之前的版本);

  • compareFn(a, b) 必须总是对相同的输入返回相同的比较结果,否则排序的结果将是不确定的。

如果要比较数字而非字符串,则可以传递这样的回调函数来实现排序:

// 升序排序
[1,10,2].sort((a,b)=> a-b)
// [1, 2, 10]

// 降序排序
[1,10,2].sort((a,b)=> b-a)
// [10, 2, 1]

因此,如果要比较数字大小或其他特殊情况,则建议传入回调函数来确保排序结果的正确性和稳定性。如果必须使用默认排序算法,请确保测试充分,并在使用时注意兼容性问题。

Array.prototype.join遇到只有一个元素的数组时

当数组只有一个元素时,Array.prototype.join 方法不会对元素做处理,而是直接返回这个元素,这意味着如果想对数组进行某些处理,那么一定要记得考虑只有一个元素的情况。

例如你想实现为数组里每个元素都加上一个标记和换行符的效果时,你可能会这么写:

[1,2,3].join('(标记)\n')
// '1(标记)\n2(标记)\n3' 

但是当数值只有一个元素时,就不会做任何处理:

[1].join('(标记)\n')
// 1

有些时候join函数不对空数组进行处理可能是更合理的,但是有时候我们却不想这样,哪怕只有一个元素,我们也希望能够处理一遍。

解决的方法很简单,可以判断一下数组的长度,然后分别处理不同的情况。但是有一个更便捷的方法:先利用map先处理一遍,然后再利用join进行拼接。这样无论数组的长度如何,都可以正常地进行处理。

arr.map(i=>i+'(标记)\n').join('')
// 当 arr = [1,2,3]
// '1(标记)\n2(标记)\n3(标记)\n'
// 当arr = [1]
// '1(标记)\n'

避免使用arguments

arguments是类数组,拥有length属性且以属性索引以0开始,类数组不拥有数组的任何方法,例如forEach、map等。

另一个问题是arguments在箭头函数中不存在,在箭头函数中访问arguments会返回undefined。

使用剩余参数替代,或者扩展运算符/Array.from将其转换成真正的数组是更好的选择,除非是需要在不支持ES6的浏览器上运行。如果需要统一在函数和箭头函数中都能访问到所有参数,那应该使用剩余参数。

禁用arguments.callee

arguments.callee引用当前执行函数本身,当这个函数是一个匿名函数时就很有用了,可以直接通过arguments.callee 执行自身,以实现递归调用。

(function(arg){
     if(arg === 0) return  // 结束条件
     console.log(arg);
     arguments.callee(arg - 1) // 调用匿名函数自身
})(5)

这是一个非常糟糕的方案,其缺点在于不能实现尾调用、内联,并且每次递归调用会获取到一个不同的 this 值。

(function(arg){
    if(arg === 0) return
    console.log(this);
     arguments.callee(arg - 1)
})(5)
// 每次打印的this都不同

而解决的方式也很简单,为使用函数名称进行调用即可。

(function fn(arg){
    if(arg === 0) return
    console.log(this);
    fn(arg - 1);
})(5)
// window

避免使用arguments.callee 的优势在于:

  • 该函数可以像代码内部的任何其他函数一样被调用

  • 它具有比访问 arguments 对象更好的性能

为for…in添加hasOwnProperty

判断构造函数是否被new调用

JavaScript中的构造函数(或者说类)也可以作为普通函数被调用,虽然可以通过函数首字母是否大写的形式来判断是否是构造函数,但是最好的方法还是应该在代码中硬性规定一定要被new调用,以此避免可能的错误。

function F(a){
	this.a = a
}

// 构造函数可以被直接调用,也可以被new调用
F(1)
new F(1)

检查是否被new调用可以使用new.target。ES6 为 new 命令引入了一个 new.target 属性,该属性一般用在构造函数之中,返回 new 命令作用于的那个构造函数。如果构造函数不是通过 new 命令或 Reflect.construct() 调用的,new.target 会返回 undefined 。

function F(a){
    if(new.target == undefined){
        throw new Error('构造函数F必须要被new调用')
    }
	this.a = a
}
F(1)  
// 报错
new F(1)  
// {a: 1}

每次手动写判断方法太过麻烦,ES6的Class是构造函数的语法糖,Class规定必须要被new调用,否则将会报错。

class A{}
// Uncaught TypeError: Class constructor A cannot be invoked without 'new'

因此可以使用Class语法替代原本的构造函数写法,如果要使用构造函数,那么最好加一层判断,避免被直接调用。

冻结对象

使用Symbol

Symbol最常用于避免对象冲突,另外一点就是常见的遍历方法不会遍历到symbol类型。

属性描述符

Array.prototype.fill不要传递引用值为参数

使用Array.prototype.fill时不要直接将一个引用值作为参数传入,比较常见的例子是快速创建一个嵌套数组:

let result = new Array(10).fill([])

这将导致result中所有的元素都指向同一个数组,当你改变result中某个元素时,除非破坏这种引用关系,否则其他的元素都会一起改变。

let result = new Array(10).fill([])
result
// [[],[],...,[]]
result[0].push(1)
// [[1],[1],...,[1]]

另一种想法是通过map来遍历依次赋值:

let result = new Array(10).map(i=>([]))
result
// []
result.length
// 10

但是map这种ES6遍历方法会跳过数组中所有空的(undefined)元素,因此无法达到我们想要的效果。

有两种解决办法:

let result = Array(5).fill(1).map(() => []);
let result = Array.from({length: 5}, () => []);

第一种先通过fil填充,再调用map依次赋值,第二种方法是通过Array.from来创建。

Match和MatchAll使用捕获组

String.prototype.match在开启全局匹配(g)和不开启全局匹配时,返回的结果是不一样的,不开启全局匹配时,返回一个类数组结构,包含input、groups等字段,而在开启全局匹配时,返回的却是一个包含所有匹配结果的数组结构,此时不包含input、groups等字段。

在需要使用捕获组时,建议在不需要开启全局匹配时使用match,在需要开启全局匹配时使用matchAll。

'1-1-1'.match(/(?<number>\\d)/)

[...'-1-1-1'.matchAll(/(?<number>\\d)/g)]

需要注意的是,matchAll返回的是一个迭代器,并且matchAll必须开启全局匹配模式(g),否则会报错。

技巧

获取随机数

判断类型

上一页JavaScript闭包下一页JavaScript设计模式

最后更新于1年前

排序稳定性不确定:在某些JavaScript引擎中,使用默认排序算法在排序相等元素时可能会导致排序稳定性不确定,即在排序前和排序后两个相等的元素的位置关系可能会发生改变。自 ES10(EcmaScript 2019)起, 要求 Array.prototype.sort为稳定排序,但是ES10(EcmaScript 2019)以前没有要求稳定性。

在下,第 5 版 ECMAScript (ES5) 禁止使用 arguments.callee()。当一个函数必须调用自身的时候,避免使用 arguments.callee(),通过要么给函数表达式一个名字,要么使用一个函数声明。

它不会在外部作用域中创建一个变量 ()

规范
严格模式 (en-US)
除了 IE 8 及以下