开头
随着 ES6+
和 Typescript
的席卷到来,Javascript
的世界里已经是符号满天飞的时代了,感觉现在是稍微不努力学习,就跟上不了,看别人写的代码如同看天书般。
看了最近项目中的一个函数,Em......直呼,好家伙!!!
为了更有效率的搬砖,本章目的就是探索那些可能让你看不懂的神奇符号和写法,当然,如果你有遇到更令人惊奇的操作,欢迎评论区留言抢座(没有其实也可以抢^〇^)。
(一切为了更好的摸鱼,加油!!!)
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
是不是就很简洁,真是太棒了,有没有(-^〇^-)?
链判断运算符原理是判断左侧的对象是否为 null
或 undefined
。如果是的,就不再往下运算,而是返回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判断运算符 ??
,只有运算符左侧的值为 null
或undefined
时,才会返回右侧的值,否则返回左侧的值。
console.log(value ?? 'value没值');
globalThis
JavaScript
可以运行在不同环境中,如浏览器、Worker
、Node
等等,尽管都是 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
配置项打开,这是为什么呢?主要是因为 null
和 undefined
是 TS
中的基础类型,分别具有值 null
和 undefined
,默认情况下它们是所有类型的子类型,即可以赋值给任意类型;当我们打开该选项后,它们就不能随意赋值给其他类型了,只能赋值给本身类型。
配置项未打开之前:
配置项打开之后:
如上图,nickname
有可能是 undefined
所以不能直接赋值给 realname
,否则就会报错。但如果经过一些操作使得 nickname
已经是确定有值的,例如这样:
我们要怎么告诉 TS
呢?让它不报错呢?这个时候就可以用到 非空断言操作符 了。
这样子就行了,应该也比较简单好理解吧。。。
可选属性(?:)
可选属性 这可以说是接口(interface)身上的一个性质,接口一个非常灵活的概念,我们平常可以用它来限制一个对象,例如:
图中定义了一个人类的接口,分别有姓名、年龄和爱好,它可以用来限制对象,但是有时候,有些对象没有爱好(好吧,一个人没有爱好那就太惨了︶︿︶)这一项,就会像图中的小明一样,只能报错,这要怎么办?
这个时候就可以用到接口的可选属性了,如:
链判断运算符(.?)
该运算符与 JS
版本中的可选链运算符效果是一样的,其实就是 TS
版本的实现而已。同样是判断左侧的对象是否为 null
或 undefined
。如果是的,就不再往下运算,而是返回undefined
var obj: any = undefined;
if(obj?.name) {}
// 等同于
if(obj && obj.name) {}
这样写的目的是防止控制台报错,因为 obj
类型可能我们是不确定的。
空值判断运算符(??)
该运算符与 JS
版本中的空值判断运算符效果是一样的,也是 TS
版本的实现而已。同样是只有运算符左侧的值为 null
或undefined
时,才会返回右侧的值,否则返回左侧的值。
var value: string | undefined | null = '';
console.log(value ?? 'value为空值'); // 当value为空值时,取默认值
交叉类型运算符(&)
这玩意就好比运算符且(&&
),但它是作用于类型定义上的,它也只有单个符号(&
)。使用交叉类型运算符可以将多种类型叠加在一起,形成一个新类型,新类型会包含所需的所有类型的特性,缺一不可。
它也可以用在接口上:
这就没有什么好说的了,当然,它在使用上也有需要注意的地方,例如这样子:
当交叉的两个类型都具有相同属性,但属性类型定义不一样时,所得到的类型就会变成 never
类型。这是因为 string & number
这种类型显然是不存在的,所以它会变成 never
类型。
联合类型分隔符(|)
这个也是很简单啦,就和或者(||
)作用差不多,也是老样子它只有单个符号(|
),可不要写错了哦。
这些例子很简单,就不多说了。但是,很多时候使用联合类型分隔符的时候,会遇到一类问题,比如:
图中,不管我们读取 name
还是 age
在 TS
都是不允许,TS
无法确定 obj
身上是否存在这两个属性;要解决这类问题的方式有很多,我们把这些方式统称为“类型保护”,其实简单来理解就是先确定好图中 obj
对象的类型,再进行后续操作。
(这里就顺便稍微提了一下“类型保护”,更多内容可以再去自行查阅相关资料,我们这里就点到即止了)
类型断言
类型断言 这玩意有点像是 类型转换 的意味,但也不算是,毕竟它也没去转换,只是"蒙骗"过了 TS
的类型检查。它有两种写法,下面我们分别来瞧瞧。
TS
不允许我们把一个未知的类型或者其他类型,赋值给一个明确类型的变量,但是有时我们迫不得已就是需要那么干,像图中的情况这要怎么做呢?
as 语法
<> 语法
是不是也很好理解?
装饰器(@xxx)
什么是装饰器?记住它,本质就是调用一个函数,就是一个语法糖,没什么大不了的。它的作用是允许向一个现有的对象添加新的功能,同时又不改变其结构。
要想使用它,你还是得去 tsconfig.json
中开启它的相关配置项才行,把 "experimentalDecorators": true
与 "emitDecoratorMetadata": true
打开即可。
话不多说,我们先写一个小例子来观摩观摩:
function classFn(target: any) {
console.log('类装饰器:', target);
}
@classFn
class Person{
constructor() {
console.log('实例化');
}
}
var p = new Person();
这是执行后控制台打印的结果:
呃...是不是也挺有趣,学过 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();
装饰器不仅仅只有类装饰器,它有下来分类:
- 类装饰器(Class decorators)
- 属性装饰器(Property decorators)
- 方法装饰器(Method decorators)
- 参数装饰器(Parameter decorators)
Em......这玩意涉及的内容好像不少,这里就不再一一去介绍了,不想内容太多你们看着烦,本章主旨是带你了解各种符号,以免接手项目时完全抓瞎,大致知道一下语法就差不多啦。(哈哈哈,终于水完了......逃,皆大欢喜)
至此,本篇文章就写完啦,撒花撒花。
希望本文对你有所帮助,如有任何疑问,期待你的留言哦。
老样子,点赞+评论=你会了,收藏=你精通了。
原文首发于掘金,欢迎来踩。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。