JS与TS那些奇奇怪怪的符号,你知道有那些?

1,288 阅读6分钟

本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。

开头

随着 ES6+Typescript 的席卷到来,Javascript 的世界里已经是符号满天飞的时代了,感觉现在是稍微不努力学习,就跟上不了,看别人写的代码如同看天书般。

看了最近项目中的一个函数,Em......直呼,好家伙!!!

caea601d95a745f426c14895e10b045.jpg

为了更有效率的搬砖,本章目的就是探索那些可能让你看不懂的神奇符号和写法,当然,如果你有遇到更令人惊奇的操作,欢迎评论区留言抢座(没有其实也可以抢^〇^)。

(一切为了更好的摸鱼,加油!!!)

image.jpg

JS

解构赋值(写法)

变量的解构赋值ES6 带来的一个新特性,这玩意是个好东西,用起来那是相当的爽,总之就很Nice!!!相信各位用得都熟能生巧了,这里就不再讲解它的基本用法班门弄斧,我们主要来看看下面这些有意思的写法,看看能不能给你一些新的体会,学到一点新技巧。

console大法

调试代码是个很关键的环节,虽然现在调试的方式琳琅满目,但是用来用去,还是 console 大法最简单、实用。然而不知道你会不会有那么一瞬间觉得写 console.log() 很繁琐呢? 那么你可以稍微尝试这样子使用看看:

({log: window.log, error: window.error, warn: window.warn} = console);
log('普通');
warn('提醒');
error('危险');

好不好用自行品尝食用 ⊙ω⊙ 逃~

数组是特殊的对象

数组用对象来解构,这是有多闲啊?(T_T)

var arr = ['L', 'O', 'V', 'E'];
var {0: l, 1: o, 2: v, 3: e} = arr;
console.log(l, o, v, e); // L O V E

取数组第一项和最后一项

var arr = ['first', 'second', 'last'];
var {[0]: first, [arr.length - 1]: last} = arr;
console.log(first, last); // first last

这叫啥?这叫不走寻常路!!!等等,这里细品我们能发现一点小知识,比如:

var arr = ['first', 'second', 'last'];
var {[arr.length <= 1 ? 0 : arr.length - 1]: last} = arr;
console.log(last); // last

Em...这其中能用表达式,这能做的事情就多了吧?嘿嘿!!!

字符串也解构

var [a1, a2, a3, a4] = 'YYDS';
console.log(a1, a2, a3, a4); // Y Y D S
var [lastname, ...name] = '橙某人前端';
console.log(lastname, name); // 橙 某,人,前,端

虽然使用场景可能少,但是说不定哪天能有用上呢。

变量值交换

var x = 1;
var y = 2;
[x, y] = [y, x];

这就没啥好说的,反正就再也不用定义临时变量了。

默认值之默认值

这是我真实在项目中看到的,大致简化下来就是这样子,各位看官大老爷就细细品。

var upperValue = ''; // 某个接口的值
let {value: newValue = upperValue || '上一个接口没有值'} = {value: undefined}; // 另一接口可能会返回的值
console.log(newValue)

莎士比亚说过:There are a thousand Hamlets in a thousand people's eyes.(一千个观众眼中有一千个哈姆雷特)

我想写代码也是如此吧。。。(⊙o⊙)

模板字符串之标签模板(fn``)

console.log`橙某人`; // ['橙某人']
// 等同于
console.log(['橙某人']); // ['橙某人']

这其实是函数的一种特殊调用形式,虽然可能这辈子你都未必会用到,但也不能排除其他人会怎么写,知己知彼,更多用法请点击 文档

数值分隔符(_)

ES6 中允许的数值使用下划线(_)作为分隔符,有了这个东西就再也不用担心自己数零数晕圈了。

let num1 = 137_9083_7051; // 手机号码
console.log(num1); // 13790837050
let num2 = 1_000_000_000; // 大额数字
console.log(num2); // 1000000000
let num3 = 1.000_000_000_1; // 多位小数
console.log(num3); // 1.0000000001

rest参数(...)

rest 参数(形式为 ...变量名 ),用于接收函数的多余参数,该参数以数组的形式存放多余的参数。

function fn(val, ...vals) {
  console.log(val, vals);
}
fn(1, 2, 3, 4, 5); // 1 [2, 3, 4, 5]

它更好的替换了 arguments 参数,arguments 参数的不透明性、隐藏性以及它是以伪数组形式存在的,不能直接使用数组相关方法等这些因素都给人带来了较多麻烦;当然,更重要的是它能服务于箭头函数,我们知道箭头函数内部是没有所谓的 this 的,更加不会有 arguments 参数。

它也不仅仅只能放在函数参数上使用,也可以配合解构赋值一起玩耍:

var [a, ...rest] = [1, 2, 3, 4];
console.log(a); // 1
console.log(rest); // [2, 3, 4]

rest 参数使用注意点:

  • rest 参数只能放在所有参数的最后一位,否则会报错。
  • rest 参数不计入函数的 length 属性。
(function(a) {}).length  // 1
(function(...a) {}).length  // 0
(function(a, ...b) {}).length  // 1

扩展运算符(...)

扩展运算符 也是个好玩意,但要注意它和 rest 参数是不一样的,不要搞混了哦。

console.log(...[1, 2, 3]); // 1 2 3
console.log({...{a: 1, b: 2, c: 3}}); // {a: 1, b: 2, c: 3}
console.log([...document.querySelectorAll('div')]); // [div, div, div]
console.log(...new Set([1, 2, 3])); // 1 2 3
console.log(...new Map([['name', '橙某人'], ['age', 18]])); // ["name", "橙某人"] ["age", 18]

扩展运算符算是比较好理解、好使用的,可读性也非常的棒,但是即使这样,也架不住各位大神千奇百怪的骚操作,很容易就写出令人揪脑袋的写法。

console.log({...['橙', '某', '人']}); // {0: "橙", 1: "某", 2: "人"}
console.log({...'橙某人'}); // {0: "橙", 1: "某", 2: "人"}
function fn(...[a, b, c]) {
  console.log(a, b, c);
}
fn(1, 2, 3); // 1, 2, 3
fn(1, 2, 3, 4); // 1, 2, 3  这里只是扩展运算符,可不是 rest 参数哦

指数运算符(**)

指数运算符(** 这玩意和 Math.pow() 是一样的,不过就是写法变简洁了。

console.log(2 ** 2); // 2*2=4
console.log(2 ** 3); // 2*2*2=8
等同于
console.log(Math.pow(2, 2)); // 4
console.log(Math.pow(2, 3)); // 3

不过要稍微注意一下,这家伙是从右边开始计算的:

console.log(2 ** 3 ** 2); // 2 ** (3*3) = 2 ** 9 = 2*2... = 512

链判断运算符(.?)

不知道你是否有写过这样的语句:

var response = {}
console.log(response && response.data && response.data.name); // undefined

我们为了在读取对象内部的某个属性控制台不报错,往往我们需要判断一下,属性的上层对象是否存在,这样写完全没有问题,但是一旦读取属性数量多了,写起来就繁琐了。这个时候它来了,ES2020 带着链判断运算符向我们走来了。

console.log(response?.data?.name); // undefined

是不是就很简洁,真是太棒了,有没有(-^〇^-)?

链判断运算符原理是判断左侧的对象是否为 nullundefined 。如果是的,就不再往下运算,而是返回undefined

空值判断运算符(??)

有时我们需要判断一个变量是否为空,如果为空,则设置默认值,否则则为原值。那我们大概会怎么写:

// var value = '';
// var value = 0;
// var value = undefined;
// var value = null;

console.log(value ? value : 'value为空值'); // '' 和 0 也会算成空
console.log(value !== undefined || value !== null ? value : 'value为空值'); // 写法比较繁琐

ES2020 引入了一个新的 Null判断运算符 ?? ,只有运算符左侧的值为 nullundefined 时,才会返回右侧的值,否则返回左侧的值。

console.log(value ?? 'value没值');

9e241f23f4df499bec73e72ebf55eca.jpg

globalThis

JavaScript 可以运行在不同环境中,如浏览器、WorkerNode 等等,尽管都是 JS,语法基本也都相同,但它们却存在不同的全局对象。

  • 浏览器全局对象是 window
  • Web Worker 全局对象是 self
  • Node 全局对象是 global

对于这种情况,为了在不同环境中都使用统一的全局对象,ES2020 标准引入了 globalThis

// browser
console.log(globalThis);    // => Window {...}

// node
console.log(globalThis);    // => Object [global] {...}

// web worker
console.log(globalThis);    // => DedicatedWorkerGlobalScope {...}

TS

了解完 JS 中的各种符号,接下来就是 TS 中的一些符号了,不过 TS 奇怪的符号不多,大多也是和 JS 一样或者演变过来的,下面我们就继续来观摩观摩。

(为了展示报错效果,下面代码基本都会使用截图来替代,应该也不用代码了吧,都是很简单的代码,目的就是为了说明每个符号的作用和意义)

非空断言操作符(!)

先来介绍第一个符号 非空断言操作符,但是要展示该符号的作用,我们还需要对 tsconfig.json 进行一些修改,这个文件是 TS 的配置文件,一般的 TS 项目你都能在根目录下看到它,你也可以通过 npx tsc --init 的命令来主动生成它。

然后我们需要把文件的 "strictNullChecks": true 配置项打开,这是为什么呢?主要是因为 nullundefinedTS 中的基础类型,分别具有值 nullundefined,默认情况下它们是所有类型的子类型,即可以赋值给任意类型;当我们打开该选项后,它们就不能随意赋值给其他类型了,只能赋值给本身类型。

配置项未打开之前:

image.png

配置项打开之后:

image.png

如上图,nickname 有可能是 undefined 所以不能直接赋值给 realname,否则就会报错。但如果经过一些操作使得 nickname 已经是确定有值的,例如这样:

image.png

我们要怎么告诉 TS 呢?让它不报错呢?这个时候就可以用到 非空断言操作符 了。

image.png

这样子就行了,应该也比较简单好理解吧。。。

可选属性(?:)

可选属性 这可以说是接口(interface)身上的一个性质,接口一个非常灵活的概念,我们平常可以用它来限制一个对象,例如:

image.png

图中定义了一个人类的接口,分别有姓名、年龄和爱好,它可以用来限制对象,但是有时候,有些对象没有爱好(好吧,一个人没有爱好那就太惨了︶︿︶)这一项,就会像图中的小明一样,只能报错,这要怎么办?

这个时候就可以用到接口的可选属性了,如:

image.png

链判断运算符(.?)

该运算符与 JS 版本中的可选链运算符效果是一样的,其实就是 TS 版本的实现而已。同样是判断左侧的对象是否为 nullundefined 。如果是的,就不再往下运算,而是返回undefined

var obj: any = undefined;
if(obj?.name) {}
// 等同于
if(obj && obj.name) {}

这样写的目的是防止控制台报错,因为 obj 类型可能我们是不确定的。

空值判断运算符(??)

该运算符与 JS 版本中的空值判断运算符效果是一样的,也是 TS 版本的实现而已。同样是只有运算符左侧的值为 nullundefined 时,才会返回右侧的值,否则返回左侧的值

var value: string | undefined | null = '';
console.log(value ?? 'value为空值'); // 当value为空值时,取默认值

交叉类型运算符(&)

这玩意就好比运算符&&),但它是作用于类型定义上的,它也只有单个符号(&)。使用交叉类型运算符可以将多种类型叠加在一起,形成一个新类型,新类型会包含所需的所有类型的特性,缺一不可。

image.png

它也可以用在接口上:

image.png

这就没有什么好说的了,当然,它在使用上也有需要注意的地方,例如这样子:

image.png

当交叉的两个类型都具有相同属性,但属性类型定义不一样时,所得到的类型就会变成 never 类型。这是因为 string & number 这种类型显然是不存在的,所以它会变成 never 类型。

联合类型分隔符(|)

这个也是很简单啦,就和或者||)作用差不多,也是老样子它只有单个符号(|),可不要写错了哦。

image.png

这些例子很简单,就不多说了。但是,很多时候使用联合类型分隔符的时候,会遇到一类问题,比如:

image.png

图中,不管我们读取 name 还是 ageTS 都是不允许,TS 无法确定 obj 身上是否存在这两个属性;要解决这类问题的方式有很多,我们把这些方式统称为“类型保护”,其实简单来理解就是先确定好图中 obj 对象的类型,再进行后续操作。

image.png

(这里就顺便稍微提了一下“类型保护”,更多内容可以再去自行查阅相关资料,我们这里就点到即止了)

类型断言

类型断言 这玩意有点像是 类型转换 的意味,但也不算是,毕竟它也没去转换,只是"蒙骗"过了 TS 的类型检查。它有两种写法,下面我们分别来瞧瞧。

image.png

TS 不允许我们把一个未知的类型或者其他类型,赋值给一个明确类型的变量,但是有时我们迫不得已就是需要那么干,像图中的情况这要怎么做呢?

as 语法

image.png

<> 语法

image.png

是不是也很好理解?

微信图片_20201229101857.png

装饰器(@xxx)

什么是装饰器?记住它,本质就是调用一个函数,就是一个语法糖,没什么大不了的。它的作用是允许向一个现有的对象添加新的功能,同时又不改变其结构。

要想使用它,你还是得去 tsconfig.json 中开启它的相关配置项才行,把 "experimentalDecorators": true"emitDecoratorMetadata": true 打开即可。

话不多说,我们先写一个小例子来观摩观摩:

function classFn(target: any) {
  console.log('类装饰器:', target);
}

@classFn
class Person{
  constructor() {
    console.log('实例化');
  }
}

var p = new Person(); 

这是执行后控制台打印的结果:

image.png

呃...是不是也挺有趣,学过 Java 的小伙伴可能就比较熟了,这和 Java 的装饰器模式差不多。。。(⊙o⊙)

装饰器也能传递参数,而且是顺序执行的:

function classFn(target: any) {
  console.log('类装饰器:', target);
  return function(params: any) {
    console.log('自定义类装饰器参数:', params)
  }
}

@classFn('橙某人-1')
@classFn('小明同学-2')
class Person{
  constructor() {
    console.log('实例化');
  }
}

var p = new Person();

image.png

装饰器不仅仅只有类装饰器,它有下来分类:

  • 类装饰器(Class decorators)
  • 属性装饰器(Property decorators)
  • 方法装饰器(Method decorators)
  • 参数装饰器(Parameter decorators)

Em......这玩意涉及的内容好像不少,这里就不再一一去介绍了,不想内容太多你们看着烦,本章主旨是带你了解各种符号,以免接手项目时完全抓瞎,大致知道一下语法就差不多啦。(哈哈哈,终于水完了......逃,皆大欢喜)






至此,本篇文章就写完啦,撒花撒花。

image.png

希望本文对你有所帮助,如有任何疑问,期待你的留言哦。
老样子,点赞+评论=你会了,收藏=你精通了。