整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

js 常用简写技巧(干货满满)

js 常用简写技巧(干货满满)

享一些自己常用的js简写技巧,长期更新,会着重挑选一些实用的简写技巧,使自己的代码更简洁优雅~

这里只会收集一些大多数人不知道的用法,但是确实能提高自己的编码技巧,像ES6那些基础的简写语法或者是三目运算符代替if else那些我觉得是基础,没必要写在这里浪费精力。

注意本篇内容涉及到的语法从ES6到ES11不等,具体使用需要考虑项目兼容性问题!

另外推荐一个只用一行代码实现一个方法的实用网站1loc.dev,github

If-Else 用 || 或 ?? 运算符进行简化

逻辑或操作符||,这里要注意的是0和''也会认为是false

如果||前面的值是0 '' false null undefined NaN其中的任意一种,则直接返回||后面的值

function(obj){
  var a=obj || {}
}
// 等价于=>>
function(obj){
  var a;
  if(
    obj===0 || 
    obj==="" || 
    obj===false || 
    obj===null || 
    obj===undefined ||
    isNaN(obj)
  ){
     a={}
   } else {
     a=obj;
  }
}


空值合并操作符??如果没有定义左侧返回右侧。如果是,则返回左侧。

这种方法非常实用,有时候仅仅只是想判断一个字段有没有值,而不是把空字符串或者0也当做false处理

function(obj){
  var a=obj ?? {}
}
// 等价于=>>
function(obj){
  var a;
  if(
    obj===null || 
    obj===undefined
  ){
     a={}
   } else {
     a=obj;
  }
}


输入框非空的判断(有时候不想把0当成false可以用此方法。比如分数0也是个值,这种情况就不能认为是false)

if(value !==null && value !==undefined && value !==''){}
// 等价于==>
if((value ?? '') !==''){}


includes的正确使用姿势

在上面逻辑或操作符||代码段里有一个if判断比较长,这时候就可以用includes去简化代码

if(
  obj===0 || 
  obj==="" || 
  obj===false || 
  obj===null || 
  obj===undefined
){
  // ...
}

// 使用includes简化
if([0, '', false, null, undefined].includes(obj)){
  // ...
}


防止崩溃的可选链(?.)

可选链操作符?. 如果访问未定义的属性,则会产生错误。这就是可选链的用武之地。 在未定义属性时使用可选链运算符,undefined将返回而不是错误。这可以防止你的代码崩溃。

const student={
  name: "lwl",
  address: {
    state: "New York"
  },
}

// 一层一层判断
console.log(student && student.address && student.address.ZIPCode) // 输出:undefined
// 使用可选链操作符
console.log(student?.address?.ZIPCode) // 输出:undefined


可选链运算符也可以用于方法调用。如果方法存在,它将被调用,否则将返回 undefined。例如:

const obj={
  foo() {
    console.log('Hello from foo!')
  }
}

obj.foo?.() // 输出:'Hello from foo!'
obj.bar?.() // 输出:undefined,因为 bar 方法不存在


同样,数组也可以使用。例如:

const arr=[1, 2, 3, 4]

console.log(arr[0]) // 输出:1
console.log(arr[4]) // 输出:undefined

// 使用可选链运算符
console.log(arr?.[0]) // 输出:1
console.log(arr?.[4]) // 输出:undefined
console.log(arr?.[0]?.toString()) // 输出:'1'

// 多维数组也可以
const arr2=[[[1]]]
console.log(arr2?.[0]?.[0]?.[0]) // 输出:1
console.log(arr2?.[0]?.[1]?.[0]) // 输出:undefined


逻辑空赋值(??=)

逻辑空赋值??=逻辑空赋值运算符(x ??=y)仅在 x 是 nullish (null 或 undefined) 时对其赋值。

const a={ duration: 50 };

a.duration ??=10;
console.log(a.duration);
// expected output: 50

a.speed ??=25;
console.log(a.speed);
// expected output: 25


快速生成1-10的数组

生成0-9,利用了数组的下标值。推荐使用方法二或方法三,比较灵活,改成(v, k)=> k + 1就是1-10

// 方法一
const arr1=[...new Array(10).keys()]
// 方法二
const arr2=Array.from(Array(10), (v, k)=> k)
// 方法三
const arr3=Array.from({length: 10}, (v, k)=> k)


生成1-10,通过map的特性

const arr1=[...Array(10)].map((v, i)=> i + 1)
// 下面这两个和上面生成0-9的方式一样
const arr2=Array.from(Array(10), (v, k)=> k + 1)
const arr3=Array.from({length: 10}, (v, k)=> k + 1)


快速生成10个0的数组

const arr=new Array(10).fill(0)


快速生成10个[]的数组(二维数组)

注意: 二维数组不能直接写成new Array(10).fill([])(也就是fill方法不能传引用类型的值,[]换成new Array()也不行),因为fill里传入引用类型值会导致每一个数组都指向同一个地址,改变一个数据的时候其他数据也会随之改变,详见mdn官方说明

// 错误写法
const arr=new Array(10).fill([]) // 注意这是错误写法,不要这么写
// 正确写法
const arr=new Array(10).fill().map(()=> new Array())


数组降维

你是否还在用递归给一个多维数组降维?如果是,那你应该知道一下es6的 flat() 方法。

如果不确定需要降维的数组有多深,可以传入最大值作为参数Infinity,默认值深度为1

const arr=[1, [2, [3, 4], 5], 6]
const flatArr=arr.flat(Infinity) // 输出 [1, 2, 3, 4, 5, 6]


你是否在使用map的时候想要对数组降维?大概像这样:

const arr=[1, 2, 3, 4]
const result=arr.map(v=> [v, v * 2]).flat()
console.log(result); // 输出 [1, 2, 2, 4, 3, 6, 4, 8]


其实js也提供了更简便的方法,那就是flatMap(),可以改成这样:

const result=arr.flatMap(v=> [v, v * 2])


Set集合实践:数组去重、交集、并集、差集

js// 1.数组去重 
let arr1=[1, 3, 2, 1, 4, 2, 1] 
let result1=[...new Set(arr1)] 
console.log(result1) // [1, 3, 2, 4] 

// 2.交集 
let arr2=[4, 3, 4, 7] 
let result2=[...new Set(arr1)].filter(v=> new Set(arr2).has(v)) 
console.log(result2) // [3, 4] 

// 3.并集 
let result3=[...new Set([...arr1, ...arr2])] 
console.log(result3) // [1, 3, 2, 4, 7] 

// 4.差集 
let result4=[...new Set(arr1)].filter(v=> !(new Set(arr2).has(v))) 
console.log(result4) // [1, 2]


在没有第三个变量的情况下交换两个变量

在 JavaScript 中,你可以使用解构从数组中拆分值。这可以应用于交换两个变量而无需第三个

比较简单,es6语法

let x=1;
let y=2;

// 交换变量
[x, y]=[y, x];


将对象的值收集到数组中

用于Object.values()将对象的所有值收集到一个新数组中

const info={ name: "Matt", country: "Finland", age: 35 };

// LONGER FORM
let data=[];
for (let key in info) {
  data.push(info[key]);
}

// SHORTHAND
const data=Object.values(info);


指数运算符(用的不多)

你Math.pow()习惯把一个数字提高到一个幂吗?你知道你也可以使用**运算符吗?

虽然可以简写,不过我还是建议写成Math.pow()方法,代码更有语义化。

注意:**运算符要求操作数为数值类型,不过在js里也能正常运行。

Math.pow(2, 3); // 输出: 8 
2 ** 3; // 输出: 8 

Math.pow(4, 0.5); // 输出: 2 
4 ** 0.5; // 输出: 2 

Math.pow(3, -2); // 输出: 0.1111111111111111 
3 ** -2; // 输出: 0.1111111111111111 

Math.pow('2', '3'); // 输出: 8 (参数被自动转换为数字) 
'2' ** '3'; // js中输出: 8,其他语言可能报错


Math.floor() 简写(用的不多)

向下取整Math.floor()并不是什么新鲜事。但是你知道你也可以使用~~运算符吗?

同上虽然可以简写,不过我还是建议写成Math.floor()方法,代码更有语义化。

注意:对于正数而言两者都是直接去掉小数位,但对于负数来说Math.floor()是向下取整,~~依然是只去掉小数位,整数位不变。 请看下面输出结果:

Math.floor(3.14); // 输出: 3 
Math.floor(5.7); // 输出: 5 
Math.floor(-2.5); // 输出: -3 
Math.floor(10); // 输出: 10

~~3.14; // 输出: 3 
~~5.7; // 输出: 5 
~~(-2.5); // 输出: -2 
~~10; // 输出: 10


逗号运算符(,)

逗号, )运算符对它的每个操作数从左到右求值,并返回最后一个操作数的值。这让你可以创建一个复合表达式,其中多个表达式被评估,复合表达式的最终值是其成员表达式中最右边的值。这通常用于为 for 循环提供多个参数。

这里只说一下函数return的时候用逗号运算符简化代码的技巧,其他用法请直接点击查看官方文档。

举一个简单的例子:

// 简化前
const result=arr=> {
  arr.push('a')
  return arr
}
console.log(result([1,2])) // 输出:[1, 2, 'a']


这段代码需要返回修改后的数组,不能直接return arr.push('a'),因为push的返回值是修改后数组的长度,这时候可以用逗号运算符简化成一行代码。

// 简化后
const result=arr=> (arr.push('a'), arr)
console.log(result([1,2])) // 输出:[1, 2, 'a']


Array.map()的简写

比如想要拿到接口返回的特定字段的值,可以用解构赋值和对象的简写方法对map方法简写,详细解释请移步js map方法应用场景 处理对象数组。

比如接口返回数据,此时如果只想要数据里的id和name,就可以用下面的简写方式。

// 接口返回数据
res=[{
  id: 1,
  name: 'zhangsan',
  age: 16,
  gender: 0
}, {
  id: 2,
  name: 'lisi',
  age: 20,
  gender: 1
}]

// 第一种方法:箭头函数、 解构赋值
const data=res.map(({id, name})=> ({id, name}))
// 第二种方法:箭头函数、返回对象(相对更容易理解)
const data=res.map(v=> ({id: v.id, name: v.name}))


文字排序

我们知道数组方法sort()默认是按照UTF-16码元值升序排序

我们可以使用charCodeAt()方法获取UTF-16码元

对于中文或者其他语言按照拼音排序(字典顺序排序)则需要用到localeCompare()方法

['张三', '李四', '赵五', '王二', '陈六'].sort()
// 输出:['张三', '李四', '王二', '赵五', '陈六']
['张三', '李四', '赵五', '王二', '陈六'].sort((a, b)=> a.localeCompare(b, 'zh-Hans-CN'))
// 输出:['陈六', '李四', '王二', '张三', '赵五']

['apple', 'Banana', 'cherry', 'Date', 'apricot', 'Banana'].sort()
// 输出:['Banana', 'Banana', 'Date', 'apple', 'apricot', 'cherry']
['apple', 'Banana', 'cherry', 'Date', 'apricot', 'Banana'].sort((a, b)=> a.localeCompare(b, 'en'))
// 输出:['apple', 'apricot', 'Banana', 'Banana', 'cherry', 'Date']


  • 'zh' 表示主语言标记,代表中文。
  • 'Hans' 是脚本子标记,表示简体汉字。
  • 'CN' 是区域子标记,表示中国。

如果您不提供locales参数(第二个参数),localeCompare方法通常会默认使用浏览器或操作系统的当前语言环境

at()方法获取数组最后一位值

// 获取arr的最后一位值
const arr=[1, 2, 3, 4, 5]
// 一般写法
const last=arr[arr.length - 1]
// 二般写法
const last=arr.slice(-1)[0]
// 终极写法
const last=arr.at(-1)



作者:lwlcode
链接:https://juejin.cn/post/7236664417308524581

知道浏览器和服务端是通过 HTTP 协议进行数据传输的,而 HTTP 协议又是纯文本协议,那么浏览器在得到服务端传输过来的 HTML 字符串,是如何解析成真实的 DOM 元素的呢,也就是我们常说的生成 DOM Tree,最近了解到状态机这样一个概念,于是就萌生一个想法,实现一个 innerHTML 功能的函数,也算是小小的实践一下。

函数原型

我们实现一个如下的函数,参数是 DOM 元素和 HTML 字符串,将 HTML 字符串转换成真实的 DOM 元素且 append 在参数一传入的 DOM 元素中。

function html(element, htmlString) {
 // 1. 词法分析
 // 2. 语法分析
 // 3. 解释执行
}
复制代码

在上面的注释我已经注明,这个步骤我们分成三个部分,分别是词法分析、语法分析和解释执行。

词法分析

词法分析是特别重要且核心的一部分,具体任务就是:把字符流变成 token 流。

词法分析通常有两种方案,一种是状态机,一种是正则表达式,它们是等效的,选择你喜欢的就好。我们这里选择状态机。

首先我们需要确定 token 种类,我们这里不考虑太复杂的情况,因为我们只对原理进行学习,不可能像浏览器那样有强大的容错能力。除了不考虑容错之外,对于自闭合节点、注释、CDATA 节点暂时均不做考虑。

接下来步入主题,假设我们有如下节点信息,我们会分出哪些 token 来呢。

<p class="a" data="js">测试元素</p>
复制代码

对于上述节点信息,我们可以拆分出如下 token

  • 开始标签:<p
  • 属性标签:class="a"
  • 文本节点:测试元素
  • 结束标签:</p>

状态机的原理,将整个 HTML 字符串进行遍历,每次读取一个字符,都要进行一次决策(下一个字符处于哪个状态),而且这个决策是和当前状态有关的,这样一来,读取的过程就会得到一个又一个完整的 token,记录到我们最终需要的 tokens 中。

万事开头难,我们首先要确定起初可能处于哪种状态,也就是确定一个 start 函数,在这之前,对词法分析类进行简单的封装,具体如下

function HTMLLexicalParser(htmlString, tokenHandler) {
 this.token=[];
 this.tokens=[];
 this.htmlString=htmlString
 this.tokenHandler=tokenHandler
}
复制代码

简单解释下上面的每个属性

  • token:token 的每个字符
  • tokens:存储一个个已经得到的 token
  • htmlString:待处理字符串
  • tokenHandler:token 处理函数,我们每得到一个 token 时,就已经可以进行流式解析

我们可以很容易的知道,字符串要么以普通文本开头,要么以<开头,因此 start 代码如下

HTMLLexicalParser.prototype.start=function(c) {
 if(c==='<') {
 this.token.push(c)
 return this.tagState
 } else {
 return this.textState(c)
 }
}
复制代码

start处理的比较简单,如果是<字符,表示开始标签或结束标签,因此我们需要下一个字符信息才能确定到底是哪一类 token,所以返回tagState函数去进行再判断,否则我们就认为是文本节点,返回文本状态函数。

接下来分别展开tagState和textState函数。tagState根据下一个字符,判断进入开始标签状态还是结束标签状态,如果是/表示是结束标签,否则是开始标签,textState用来处理每一个文本节点字符,遇到<表示得到一个完整的文本节点 token,代码如下

HTMLLexicalParser.prototype.tagState=function(c) {
 this.token.push(c)
 if(c==='/') {
 return this.endTagState
 } else {
 return this.startTagState
 }
}
HTMLLexicalParser.prototype.textState=function(c) {
 if(c==='<') {
 this.emitToken('text', this.token.join(''))
 this.token=[]
 return this.start(c)
 } else {
 this.token.push(c)
 return this.textState
 }
}
复制代码

这里初次见面的函数是emitToken、startTagState和endTagState。

emitToken用来将产生的完整 token 存储在 tokens 中,参数是 token 类型和值。

startTagState用来处理开始标签,这里有三种情形

  • 如果接下来的字符是字母,则认定依旧处于开始标签态
  • 遇到空格,则认定开始标签态结束,接下来是处理属性了
  • 遇到>,同样认定为开始标签态结束,但接下来是处理新的节点信息

endTagState用来处理结束标签,结束标签不存在属性,因此只有两种情形

  • 如果接下来的字符是字母,则认定依旧处于结束标签态
  • 遇到>,同样认定为结束标签态结束,但接下来是处理新的节点信息

逻辑上面说的比较清楚了,代码也比较简单,看看就好啦

HTMLLexicalParser.prototype.emitToken=function(type, value) {
 var res={
 type,
 value
 }
 this.tokens.push(res)
 // 流式处理
 this.tokenHandler && this.tokenHandler(res)
}
HTMLLexicalParser.prototype.startTagState=function(c) {
 if(c.match(/[a-zA-Z]/)) {
 this.token.push(c.toLowerCase())
 return this.startTagState
 }
 if(c===' ') {
 this.emitToken('startTag', this.token.join(''))
 this.token=[]
 return this.attrState
 }
 if(c==='>') {
 this.emitToken('startTag', this.token.join(''))
 this.token=[]
 return this.start
 }
}
HTMLLexicalParser.prototype.endTagState=function(c) {
 if(c.match(/[a-zA-Z]/)) {
 this.token.push(c.toLowerCase())
 return this.endTagState
 }
 if(c==='>') {
 this.token.push(c)
 this.emitToken('endTag', this.token.join(''))
 this.token=[]
 return this.start
 }
}
复制代码

最后只有属性标签需要处理了,也就是上面看到的attrState函数,也处理三种情形

  • 如果是字母、单引号、双引号、等号,则认定为依旧处于属性标签态
  • 如果遇到空格,则表示属性标签态结束,接下来进入新的属性标签态
  • 如果遇到>,则认定为属性标签态结束,接下来开始新的节点信息

代码如下

HTMLLexicalParser.prototype.attrState=function(c) {
 if(c.match(/[a-zA-Z'"=]/)) {
 this.token.push(c)
 return this.attrState
 }
 if(c===' ') {
 this.emitToken('attr', this.token.join(''))
 this.token=[]
 return this.attrState
 }
 if(c==='>') {
 this.emitToken('attr', this.token.join(''))
 this.token=[]
 return this.start
 }
}
复制代码

最后我们提供一个parse解析函数,和可能用到的getOutPut函数来获取结果即可,就不啰嗦了,上代码

HTMLLexicalParser.prototype.parse=function() {
 var state=this.start;
 for(var c of this.htmlString.split('')) {
 state=state.bind(this)(c)
 }
}
HTMLLexicalParser.prototype.getOutPut=function() {
 return this.tokens
}
复制代码

接下来简单测试一下,对于<p class="a" data="js">测试并列元素的</p><p class="a" data="js">测试并列元素的</p>HTML 字符串,输出结果为

看上去结果很 nice,接下来进入语法分析步骤

语法分析

首先们需要考虑到的情况有两种,一种是有多个根元素的,一种是只有一个根元素的。

我们的节点有两种类型,文本节点和正常节点,因此声明两个数据结构。

function Element(tagName) {
 this.tagName=tagName
 this.attr={}
 this.childNodes=[]
}
function Text(value) {
 this.value=value || ''
}
复制代码

目标:将元素建立起父子关系,因为真实的 DOM 结构就是父子关系,这里我一开始实践的时候,将 childNodes 属性的处理放在了 startTag token 中,还给 Element 增加了 isEnd 属性,实属愚蠢,不但复杂化了,而且还很难实现。仔细思考 DOM 结构,token 也是有顺序的,合理利用栈数据结构,这个问题就变的简单了,将 childNodes 处理放在 endTag 中处理。具体逻辑如下

  • 如果是 startTag token,直接 push 一个新 element
  • 如果是 endTag token,则表示当前节点处理完成,此时出栈一个节点,同时将该节点归入栈顶元素节点的 childNodes 属性,这里需要做个判断,如果出栈之后栈空了,表示整个节点处理完成,考虑到可能有平行元素,将元素 push 到 stacks。
  • 如果是 attr token,直接写入栈顶元素的 attr 属性
  • 如果是 text token,由于文本节点的特殊性,不存在有子节点、属性等,就认定为处理完成。这里需要做个判断,因为文本节点可能是根级别的,判断是否存在栈顶元素,如果存在直接压入栈顶元素的 childNodes 属性,不存在 push 到 stacks。

代码如下

function HTMLSyntacticalParser() {
 this.stack=[]
 this.stacks=[]
}
HTMLSyntacticalParser.prototype.getOutPut=function() {
 return this.stacks
}
// 一开始搞复杂了,合理利用基本数据结构真是一件很酷炫的事
HTMLSyntacticalParser.prototype.receiveInput=function(token) {
 var stack=this.stack
 if(token.type==='startTag') {
 stack.push(new Element(token.value.substring(1)))
 } else if(token.type==='attr') {
 var t=token.value.split('='), key=t[0], value=t[1].replace(/'|"/g, '')
 stack[stack.length - 1].attr[key]=value
 } else if(token.type==='text') {
 if(stack.length) {
 stack[stack.length - 1].childNodes.push(new Text(token.value))
 } else {
 this.stacks.push(new Text(token.value))
 }
 } else if(token.type==='endTag') {
 var parsedTag=stack.pop()
 if(stack.length) {
 stack[stack.length - 1].childNodes.push(parsedTag)
 } else {
 this.stacks.push(parsedTag)
 }
 }
}
复制代码

简单测试如下:

没啥大问题哈

解释执行

对于上述语法分析的结果,可以理解成 vdom 结构了,接下来就是映射成真实的 DOM,这里其实比较简单,用下递归即可,直接上代码吧

function vdomToDom(array) {
 var res=[]
 for(let item of array) {
 res.push(handleDom(item))
 }
 return res
}
function handleDom(item) {
 if(item instanceof Element) {
 var element=document.createElement(item.tagName)
 for(let key in item.attr) {
 element.setAttribute(key, item.attr[key])
 }
 if(item.childNodes.length) {
 for(let i=0; i < item.childNodes.length; i++) {
 element.appendChild(handleDom(item.childNodes[i]))
 }
 }
 return element
 } else if(item instanceof Text) {
 return document.createTextNode(item.value)
 }
}
复制代码

实现函数

上面三步骤完成后,来到了最后一步,实现最开始提出的函数

function html(element, htmlString) {
 // parseHTML
 var syntacticalParser=new HTMLSyntacticalParser()
 var lexicalParser=new HTMLLexicalParser(htmlString, syntacticalParser.receiveInput.bind(syntacticalParser))
 lexicalParser.parse()
 var dom=vdomToDom(syntacticalParser.getOutPut())
 var fragment=document.createDocumentFragment()
 dom.forEach(item=> {
 fragment.appendChild(item)
 })
 element.appendChild(fragment)
}
复制代码

三个不同情况的测试用例简单测试下

html(document.getElementById('app'), '<p class="a" data="js">测试并列元素的</p><p class="a" data="js">测试并列元素的</p>')
html(document.getElementById('app'), '测试<div>你好呀,我测试一下没有深层元素的</div>')
html(document.getElementById('app'), '<div class="div"><p class="p">测试一下嵌套很深的<span class="span">p的子元素</span></p><span>p同级别</span></div>')
复制代码

声明:简单测试下都没啥问题,本次实践的目的是对 DOM 这一块通过词法分析和语法分析生成 DOM Tree 有一个基本的认识,所以细节问题肯定还是存在很多的。

总结

其实在了解了原理之后,这一块代码写下来,并没有太大的难度,但却让我很兴奋,有两个成果吧

  • 了解并初步实践了一下状态机
  • 数据结构的魅力

这篇文章中,作者将分享 12 个非常有用的 JavaScript 技巧,可以帮助你写出简洁且高性能的代码。

1. 过滤唯一值

ES6 引入了 Set 对象和延展(spread)语法…,我们可以用它们来创建一个只包含唯一值的数组。

const array=[1, 1, 2, 3, 5, 5, 1]
const uniqueArray=[...new Set(array)];
console.log(uniqueArray); // Result: [1, 2, 3, 5]

在 ES6 之前,获得同样的数组需要更多的代码!

这个技巧可以支持包含原始类型的数组:undefined、null、boolean、string 和 number。但如果你的数组包含了对象、函数或其他嵌套数组,就不能使用这种方法了。

2. 在循环中缓存数组长度

在我们学习使用 for 循环时,一般建议使用这种结构:

for (let i=0; i < array.length; i++){
 console.log(i);
}

在使用这种方式时,for 循环的每次迭代都会重复计算数组长度。

有时候这个会很有用,但在大多数情况下,如果能够缓存数组的长度会更好,这样只需要计算一次就够了。我们可以把数组长度复制给一个叫作 length 的变量,例如:

for (let i=0, length=array.length; i < length; i++){
 console.log(i);
}

这段代码和上面的差不多,但从性能方面来看,即使数组变得很大,也不需要花费额外的运行时重复计算 array.length。

3. 短路求值

使用三元运算符可以很快地写出条件语句,例如:

x > 100 ? 'Above 100' : 'Below 100';
x > 100 ? (x > 200 ? 'Above 200' : 'Between 100-200') : 'Below 100';

但有时候三元运算符仍然很复杂,我们可以使用逻辑运算符 && 和||来替代,让代码更简洁一些。这种技巧通常被称为“短路求值”。

假设我们想要返回两个或多个选项中的一个,使用 && 可以返回第一个 false。如果所有操作数的值都是 true,将返回最后一个表达式的值。

let one=1, two=2, three=3;
console.log(one && two && three); // Result: 3
console.log(0 && null); // Result: 0

使用||可以返回第一个 true。如果所有操作数的值都是 false,将返回最后一个表达式的值。

let one=1, two=2, three=3;
console.log(one || two || three); // Result: 1
console.log(0 || null); // Result: null

示例 1

假设我们想要返回一个变量的 length,但又不知道变量的类型。

我们可以使用 if/else 来检查 foo 是否是一个可接受的类型,但这样会让代码变得很长。这个时候可以使用短路求值:

return (foo || []).length;

对于上述两种情况,如果变量 foo 具有 length 属性,这个属性的值将被返回,否则将返回 0。

示例 2

你是否曾经在访问嵌套对象属性时遇到过问题?你可能不知道对象或某个子属性是否存在,所以经常会碰到让你头疼的错误。

假设我们想要访问 this.state 中的一个叫作 data 的属性,但 data 却是 undefined 的。在某些情况下调用 this.state.data 会导致 App 无法运行。为了解决这个问题,我们可以使用条件语句:

if (this.state.data) {
 return this.state.data;
} else {
 return 'Fetching Data';
}

但这样似乎有点啰嗦,而||提供了更简洁的解决方案:

return (this.state.data || 'Fetching Data');

4. 转换成布尔值

除了标准的布尔值 true 和 false,在 JavaScript 中,所有的值要么是“真值”要么是“假值”。

在 JavaScript 中,除了 0、“”、null、undefined、NaN 和 false 是假值之外,其他的都是真值。

我们可以使用! 云算法来切换 true 和 false。

const isTrue=!0;
const isFalse=!1;
const alsoFalse=!!0;
console.log(true); // Result: true
console.log(typeof true); // Result: "boolean"

5. 转换成字符串

要快速将数字转换成字符串,我们可以使用 + 运算符,然后在后面跟上一个空字符串。

const val=1 + "";
console.log(val); // Result: "1"
console.log(typeof val); // Result: "string"

6. 转换成数字

要把字符串转成数字,也可以使用 + 运算符。

let int="15";
int=+int;
console.log(int); // Result: 15
console.log(typeof int); Result: "number"

也可以使用这种方式将布尔值转成数字,例如:

console.log(+true); // Return: 1
console.log(+false); // Return: 0

在某些情况下,+ 运算符会被解析成连接操作,而不是加法操作。对于这种情况,可以使用两个波浪号:~~。

一个波浪号表示按位取反操作,例如,~15 等于 -16。

const int=~~"15"
console.log(int); // Result: 15
console.log(typeof int); Result: "number"

使用两个波浪号可以再次取反,因为 -(-n-1)=n+1-1=n,所以~-16 等于 15。

7. 快速幂运算

从 ES7 开始,可以使用 ** 进行幂运算,比使用 Math.power(2,3) 要快得多。

console.log(2 ** 3); // Result: 8

但要注意不要把这个运算符于 ^ 混淆在一起了,^ 通常用来表示指数运算,但在 JavaScript 中,^ 表示位异或运算。

在 ES7 之前,可以使用位左移运算符 << 来表示以 2 为底的幂运算:

// 以下表达式是等效的:
Math.pow(2, n);
2 << (n - 1);
2**n;

例如,2 << 3=16 等同于 2 ** 4=16。

8. 快速取整

我们可以使用 Math.floor()、Math.ceil() 或 Math.round() 将浮点数转换成整数,但有另一种更快的方式,即使用位或运算符 |。

console.log(23.9 | 0); // Result: 23
console.log(-23.9 | 0); // Result: -23

| 的实际行为取决于操作数是正数还是负数,所以在使用这个运算符时要确保你知道操作数是正是负。

如果 n 是正数,那么 n|0 向下取整,否则就是向上取整。它会移除小数部分,也可以使用~~ 达到同样的效果。

移除整数尾部数字

| 运算符也可以用来移除整数的尾部数字,这样就不需要像下面这样:

let str="1553"; 
Number(str.substring(0, str.length - 1));

相反,我们可以这样:

console.log(1553 / 10 | 0) // Result: 155
console.log(1553 / 100 | 0) // Result: 15
console.log(1553 / 1000 | 0) // Result: 1

9. 自动类绑定

在 ES6 中,我们可以使用箭头进行隐式绑定,这样可以为类的构造器省下一些代码,并跟一些重复出现的表达式说再见,比如 this.myMethod=this.myMethod.bind(this)。

import React, { Component } from React;
export default class App extends Compononent {
 constructor(props) {
 super(props);
 this.state={};
 }
myMethod=()=> {
 // This method is bound implicitly!
 }
render() {
 return (
 <>
 <div>
 {this.myMethod()}
 </div>
 </>
 )
 }
};

10. 截取数组

如果你想从一个数组尾部移除某些元素,可以使用一种比 splice() 更快的方法。

例如,如果你知道初始数组的大小,可以像下面这样重新定义它的 length 属性:

let array=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
array.length=4;
console.log(array); // Result: [0, 1, 2, 3]

这显然是一种更简洁的解决方案。不过,我发现 slice() 的运行速度更快,所以,如果你更看重速度,可以像下面这样:

let array=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
array=array.slice(0, 4);
console.log(array); // Result: [0, 1, 2, 3]

11. 获取数组最后的元素

数组的 slice() 方法可以接受负整数,并从数组的尾部开始获取元素。

let array=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
console.log(array.slice(-1)); // Result: [9]
console.log(array.slice(-2)); // Result: [8, 9]
console.log(array.slice(-3)); // Result: [7, 8, 9]

12. 格式化 JSON

你之前可能使用过 JSON.stringify,但你是否知道它还可以用来给 JSON 添加缩进?

stringify() 方法可以接受两个额外的参数,一个是函数(形参为 replacer),用于过滤要显示的 JSON,另一个是空格个数(形参为 space)。

space 可以是一个整数,表示空格的个数,也可以是一个字符串(比如’\t’表示制表符),这样得到的 JSON 更容易阅读。

console.log(JSON.stringify({ alpha: 'A', beta: 'B' }, null, '\t'));
// Result:
// '{
// "alpha": A,
// "beta": B
// }'

英文原文:https://medium.com/@bretcameron/12-javascript-tricks-you-wont-find-in-most-tutorials-a9c9331f169d