但事实是,这些令人讨厌的事实是,这些紧凑的设计通常非常有用。同时,它们非常简单。这意味着对使用它们的代码感兴趣的任何人都可以掌握它们并理解这些代码。
在本文中,我将介绍一些在JavaScript和TypeScript中可能会发现的非常有用(有时是神秘的)紧凑结构。研究它们之后,您可以自己使用它们,或者至少您可以了解使用它们的那些程序员的代码。
1.运算符
对检查值的运营商
null
和undefined
(nullish合并运算)看起来像两个问号(??
)。很难相信具有这种名称的它是最受欢迎的运算符。真正?
此运算符的含义是,如果左一个运算符的值等于
null
或,则它返回右运算数的值undefined
。它的名称并没有很清楚地反映出这一点,但是,哦,那是-即。使用方法如下:
function myFn(variable1, variable2) {
let var2 = variable2 ?? "default value"
return variable1 + var2
}
myFn("this has ", "no default value") // "this has no default value"
myFn("this has no ") // "this has no default value"
myFn("this has no ", 0) // "this has no 0"
这里涉及的机制与用于组织操作员工作的机制非常相似
||
。如果表达式的左侧等于null
或undefined
,则将返回表达式的右侧。否则,将返回左侧。结果,该运算符??
非常适合在任何可以分配给变量的情况下使用,但是如果该变量null
或属于该变量,则需要采取一些措施undefined
。
2.运算符?? =
仅当变量具有值
null
或undefined
(逻辑无效的赋值运算符)看起来像两个问号后跟一个等号(??=
)时,才用于为该变量赋值。将其视为上述运算符的扩展??
。
让我们看一下之前使用重写的代码段
??=
。
function myFn(variable1, variable2) {
variable2 ??= "default value"
return variable1 + variable2
}
myFn("this has ", "no default value") // "this has no default value"
myFn("this has no ") // "this has no default value"
myFn("this has no ", 0) // "this has no 0"
操作员
??=
允许您检查功能参数的值variable2
。如果等于null
或undefined
,它将向其写入一个新值。否则,参数值将不会更改。
请记住,对于
??=
不熟悉该设计的人来说,该设计似乎难以理解。因此,如果您正在使用它,则可能需要在代码的适当位置添加简短的注释和说明。
3. TypeScript构造函数的缩写声明
此功能特定于TypeScript。因此,如果您是JavaScript纯洁的拥护者,那么您将丢失很多东西。 (当然,这只是在开玩笑,但这实际上不适用于常规JS)。
如您所知,在声明一个类时,它们通常会使用访问修饰符列出其所有属性,然后在类构造函数中为这些属性分配值。但是在构造函数非常简单的情况下,传递给构造函数的参数值只是简单地写入属性中,可以使用比通常构造更紧凑的构造。
看起来是这样的:
// ...
class Person {
private first_name: string;
private last_name: string;
private age: number;
private is_married: boolean;
constructor(fname:string, lname:string, age:number, married:boolean) {
this.first_name = fname;
this.last_name = lname;
this.age = age;
this.is_married = married;
}
}
// , ...
class Person {
constructor( private first_name: string,
private last_name: string,
private age: number,
private is_married: boolean){}
}
创建构造函数时使用上述方法绝对有助于节省时间。尤其是涉及具有许多属性的类时。
这里的主要目的是不要忘记
{}
在构造函数的描述之后立即添加,因为这是函数主体的表示。编译器遇到这样的描述后,它将了解所有内容并自行完成其余工作。实际上,我们谈论的是TS代码的第一和第二片段最终都将转换为相同的JavaScript代码这一事实。
4.三元运算符
三元运算符是一种易于阅读的构造。通常使用此运算符代替简短的指令
if…else
,因为它可以让您摆脱多余的字符并将多行构造转换为单行构造。
// if…else
let isEven = ""
if(variable % 2 == 0) {
isEven = "yes"
} else {
isEven = "no"
}
//
let isEven = (variable % 2 == 0) ? "yes" : "no"
在三元运算符的结构中,第一个是逻辑表达式,第二个是类似命令(
return
如果逻辑表达式为true则返回值),第三个是类似命令(return
如果逻辑表达式为false则返回值)。尽管三元运算符最好用在值赋值的右侧(如示例中所示),但也可以自主使用,作为何时调用函数或使用哪个参数调用相同函数的调用函数的机制。相同的功能由逻辑表达式的值确定。看起来是这样的:
let variable = true;
(variable) ? console.log("It's TRUE") : console.log("It's FALSE")
请注意,该语句的结构看起来与前面的示例相同。使用三元运算符的缺点是,如果将来需要扩展它的一部分(要么是指逻辑表达式的真值的那一部分,要么是指其假值的那一部分),这意味着您需要转到常规指令
if…else
。
5.使用运算符||的较短计算周期
在JavaScript中(以及在TypeScript中),逻辑OR运算符(
||
)实现了简化的计算模型。也就是说,它返回评估为的第一个表达式true
,而不检查其余的表达式。
这意味着,如果存在以下语句
if
,其中表达式expression1
包含false值(可简化为false
)和expression2
-true(可简化为true
),则仅expression1
和将被计算expression2
。表达式espression3
,expression4
将不被评估。
if( expression1 || expression2 || expression3 || expression4)
我们可以利用语句外的机会
if
,在此为变量赋值。如果某些值(例如,由函数参数表示)变为假(例如,equal undefined
),则这尤其允许将默认值写入变量:
function myFn(variable1, variable2) {
let var2 = variable2 || "default value"
return variable1 + var2
}
myFn("this has ", " no default value") // "this has no default value"
myFn("this has no ") // "this has no default value"
本示例演示如何使用运算符
||
将函数的第二个参数的值或默认值写入变量。但是,如果仔细查看此示例,您会发现其中有一个小问题。事实是,如果或中有variable2
一个值0
或一个空字符串,则将var2
写入默认值,因为和0
和空字符串都被转换为false
。
因此,如果您不需要用默认值替换所有错误值,则可以求助于一个鲜为人知的运算符
??
。
6.双按位运算符〜
JavaScript开发人员通常并不特别热衷于使用按位运算符。如今,谁在乎数字的二进制表示形式?但是事实是,由于这些运算符在位级别工作,因此它们执行相应动作的速度比某些方法快得多。
如果我们谈论按位运算符NOT(
~
),则它需要一个数字,将其转换为32位整数(舍弃“多余”位),然后将该数字的位取反。这导致将值x
转换为value -(x+1)
。我们为什么对这种数字转换感兴趣?而且,如果您两次使用它,将给我们带来与方法调用相同的结果Math.floor
。
let x = 3.8
let y = ~x // x -(3 + 1), ,
let z = ~y // y ( -4) -(-4 + 1) - 3
// :
let flooredX = ~~x //
请注意
~
示例最后一行上的两个图标。可能看起来很奇怪,但是如果您必须将许多浮点数转换为整数,则此技术可能非常有用。
7.为对象属性分配值
ES6标准的功能简化了创建对象的过程,尤其是简化了为其属性分配值的过程。如果基于与这些属性具有相同名称的变量分配属性值,则无需重复这些名称。以前,这是必要的。
这是用TypeScript编写的示例。
let name:string = "Fernando";
let age:number = 36;
let id:number = 1;
type User = {
name: string,
age: number,
id: number
}
//
let myUser: User = {
name: name,
age: age,
id: id
}
//
let myNewUser: User = {
name,
age,
id
}
如您所见,为对象属性分配值的新方法使您可以编写更紧凑和简单的代码。而且,此类代码的可读性不比根据旧规则编写的代码难读(关于使用本文中介绍的其他紧凑结构编写的代码无法说清楚)。
8.从箭头函数隐式返回值
您知道单行箭头函数返回在其单行上执行的计算结果吗?
使用此机制可以消除不必要的表达式
return
。此技术常用于传递给数组方法(例如filter
或)的箭头函数中map
。这是一个TypeScript示例:
let myArr:number[] = [1,2,3,4,5,6,7,8,9,10]
// :
let oddNumbers:number[] = myArr.filter( (n:number) => {
return n % 2 == 0
})
let double:number[] = myArr.map( (n:number) => {
return n * 2;
})
// :
let oddNumbers2:number[] = myArr.filter( (n:number) => n % 2 == 0 )
let double2:number[] = myArr.map( (n:number) => n * 2 )
应用此技术不一定意味着使代码更复杂。这样的构造是清理代码并消除不必要的空格和多余行的好方法。当然,这种方法的缺点是,如果需要扩展此类短函数的主体,则您将不得不再次使用花括号。
这里唯一需要考虑的特性是,此处考虑的短箭头函数的唯一一行中所包含的内容必须是一个表达式(也就是说,它必须产生一些可以从该函数返回的结果)。否则,这样的设计将不起作用。例如,上面的单行函数不能这样写:
const m = _ => if(2) console.log("true") else console.log("false")
在下一节中,我们将继续讨论单行箭头函数,但是现在我们将讨论没有花括号就无法创建的函数。
9.函数参数,可以具有默认值
ES6引入了指定指定默认功能参数值的功能。以前,JavaScript没有这种功能。因此,在需要为参数分配相似值的情况下,有必要诉诸于简化操作员计算模型之类的方法
||
。
但是现在可以很简单地解决相同的问题:
// 2
// ,
function myFunc(a, b, c = 2, d = "") {
// ...
}
简单的机制,对不对?但是,实际上,所有事情都比乍看起来更有趣。关键是默认值可以是任何值-包括函数调用。如果在调用函数时未将相应的参数传递给该函数,则将调用该函数。这样可以轻松实现所需的功能参数模式:
const mandatory = _ => {
throw new Error("This parameter is mandatory, don't ignore it!")
}
function myFunc(a, b, c = 2, d = mandatory()) {
// ...
}
// !
myFunc(1,2,3,4)
//
myFunc(1,2,3)
实际上,这里是单行箭头功能,创建时不能没有花括号。关键是该函数
mandatory
使用一条指令throw
。注意-“说明”,而不是“表达式”。但是,我想,这不是为功能配备所需参数的功能的最高价。
10.使用!!将任何值转换为布尔类型!
该机制的工作原理与上述构造相同
~~
。也就是说,我们正在谈论一个事实,要将任何值转换为逻辑类型,可以使用两个逻辑运算符NOT(!!
):
!!23 // TRUE
!!"" // FALSE
!!0 // FALSE
!!{} // TRUE
一个运算符
!
已经解决了大多数此类问题,即它将值转换为布尔类型,然后返回相反的值。然后第二个运算符!
获取发生的情况,然后返回相反的结果。结果,我们将原始值转换为布尔类型。
这种简短的构造在各种情况下都非常有用。首先,当您需要确保为变量分配了真实的布尔值时(例如,如果我们正在谈论type的TypeScript变量
boolean
)。第二,当你需要执行严格的比较(使用===
与东西)true
或者false
。
11.解构和传播语法
可以讨论和讨论本节标题中提到的机制。关键是,如果使用正确,它们会导致非常有趣的结果。但是在这里我不会太深入。我将告诉您如何使用它们简化某些问题的解决方案。
objects解构对象
您是否曾经面临过将对象属性的多个值写入普通变量的任务?这个任务很常见。例如,当您需要使用这些值(例如通过修改它们)并且同时不影响原始对象中存储的内容时。
使用对象分解可以使您使用最少的代码来解决类似的问题:
const myObj = {
name: "Fernando",
age: 37,
country: "Spain"
}
// :
const name = myObj.name;
const age = myObj.age;
const country = myObj.country;
//
const {name, age, country} = myObj;
使用TypeScript的任何人都已在说明中看到此语法
import
。它允许您导入单个库方法,而不会因为许多不必要的功能而污染项目名称空间:
import { get } from 'lodash'
例如,此指令
lodash
仅允许您从库中导入方法get
。同时,该库的其他方法不属于项目的名称空间。并且其中有很多。
syntax扩展语法并基于现有的语法创建新的对象和数组
使用spread(
…
)语法简化了基于现有数组和对象创建新数组和对象的任务。现在,只需编写一行代码即可解决此任务,而无需诉诸任何特殊方法。这是一个例子:
const arr1 = [1,2,3,4]
const arr2 = [5,6,7]
const finalArr = [...arr1, ...arr2] // [1,2,3,4,5,6,7]
const partialObj1 = {
name: "fernando"
}
const partialObj2 = {
age:37
}
const fullObj = { ...partialObj1, ...partialObj2 } // {name: "fernando", age: 37}
请注意,使用这种方法组合对象将使用相同的名称覆盖其属性。这不适用于数组。特别是,如果要合并的数组具有相同的值,则它们都将最终出现在结果数组中。如果您需要消除重复,则可以使用数据结构
Set
。
▍结合解构和传播语法
解构可以与传播语法结合使用。这样可以达到有趣的效果。例如,删除数组的第一个元素,其余部分保持不变(如在常见示例中,列表的第一个和最后一个元素一样,可以在Python和其他语言中找到其实现)。而且,例如,您甚至可以从对象中提取某些属性,而其余部分保持不变。让我们考虑一个例子:
const myList = [1,2,3,4,5,6,7]
const myObj = {
name: "Fernando",
age: 37,
country: "Spain",
gender: "M"
}
const [head, ...tail] = myList
const {name, age, ...others} = myObj
console.log(head) //1
console.log(tail) //[2,3,4,5,6,7]
console.log(name) //Fernando
console.log(age) //37
console.log(others) //{country: "Spain", gender: "M"}
请注意,赋值语句左侧使用的三个点必须适用于最后一项。您不能先使用传播语法,然后再描述各个变量:
const [...values, lastItem] = [1,2,3,4]
此代码无效。
结果
还有更多与我们今天讨论的设计相似的设计。但是,使用它们时,值得记住的是,代码越紧凑,对于那些不习惯其中所使用的缩写结构的人来说,阅读它就越困难。使用这种结构的目的不是减少代码或加快代码的速度。此目标仅是从代码中删除不必要的结构,并使那些将要阅读此代码的人更加轻松。
因此,为使每个人都满意,建议您在紧凑的结构和常规可读代码之间保持健康的平衡。永远值得记住的是,您并不是唯一阅读代码的人。
您在JavaScript和TypeScript代码中使用哪些紧凑构造?