函数增强
默认参数
// ES6之前编写默认参数
function sum(num1, num2, num3, num4) {
// 编写默认参数方式一
num1 = num1 ? num1 : 0
// 编写默认参数方式二
num2 = num2 || 0
// 编写默认参数方式三
// 前两种编写方式是具有缺陷的,因为类似于空字符串或0之类的转换为boolean后为false的值
// 其也会触发对应的默认值,所以使用编写方式三更为的严谨
num3 = [null, undefined].includes(num3) ? 0 : num3
// 空值合并运算符是ES6后提供的一种新的默认值编写方式
// 其本质是编写方式三的语法糖
num4 = num4 ?? 0 // => 只有当num4的值为null或undefined的时候,才会使用对应的默认值
console.log(num1 + num2 + num3 + num4)
}
sum()
// 当函数参数的值为undefined的时候,就会触发对应函数参数的默认值
function sum(num1 = 0, num2 = 0) {
console.log(num1, num2)
console.log(num1 + num2)
}
sum()
注意事项
函数参数默认值的注意事项:
- 默认参数的放置位置应该在剩余参数之前,没有默认值的参数之后
// 默认值参数推荐放置到没有默认值的参数后边 - 约定俗成 - 否则对应的参数位需要使用undefined进行占位
// 有默认值的参数需要方式到剩余参数之前 - 语法强制规范
function sum(num1, num2 = 0, ...nums) {
console.log(num1, num2, nums)
}
-
有默认值的参数和剩余参数是不计算在函数的length属性值中的
且自第一个有默认值的参数开始后边所有的参数全部都记入函数的length属性中
即使存在没有默认值的属性
function sum(num1 = 0, num2, ...args) {
}
console.log(sum.length) // => 0
默认参数和解构结合
function getInfo({name = 'Klaus', age = 23} = {}) {
console.log(name, age)
}
getInfo()
剩余参数
ES6中引用了rest parameter,可以将不定数量的参数放入到一个数组中
如果最后一个参数是 ... 为前缀的,那么它会将剩余的参数放到该参数中,并且作为一个数组
剩余参数必须放到最后一个位置,否则会报错
剩余参数和arguments的区别
剩余参数只包含那些没有对应形参的实参,而 arguments 对象包含了传给函数的所有实参
arguments对象不是一个真正的数组,而rest参数是一个真正的数组,可以进行数组的所有操作
剩余参数的产生目的是为了取代arguments,所以在ES6中推荐使用剩余参数取代arguments
箭头函数补充
-
箭头函数是没有显式原型prototype的,所以不能作为构造函数,使用new来创建对象
如果真的需要使用new关键字,推荐使用class进行类的封装
-
箭头函数也不绑定this、arguments、super参数
- 如果需要使用this,arguments,super参数,会去其上层作用域进行查找
- 箭头函数不绑定super,所以构造函数不推荐写成箭头函数
- 箭头函数作为Funtion的实例,依旧是存在自己的隐式原型的,其值为Function.prototype
展开运算符
展开语法(Spread syntax)可以在函数调用/数组构造时,将数组表达式或者string在语法层面展开,也可以在构造字面量对象时, 将对象表达式按key-value的方式展开
基本使用
function foo(...args) {
console.log(args)
}
const name = 'Klaus'
const users = ['Klaus', 'Alex', 'Jhon']
const info = {
name: 'Klaus',
age: 23
}
// 如果在函数调用中使用展开运算符
// 其内部会迭代对应的内容,并依次取出对应的值作为参数传入
// 所以其对应的值必须是可迭代对象,例如:字符串,数组,arguments
// 对象默认不是可迭代的,所以默认情况下,函数参数中的展开运算符不可以用在对象上
foo(...name) // => [ 'K', 'l', 'a', 'u', 's' ]
foo(...users) // => [ 'Klaus', 'Alex', 'Jhon' ]
foo(...info) // error
const name = 'Klaus'
const users = ['Klaus', 'Alex', 'Jhon']
const info = {
name: 'Klaus',
age: 23
}
// 构造数组字面量的时候,可以使用展开运算符
console.log([...users, 'Steven']) // => [ 'Klaus', 'Alex', 'Jhon', 'Steven' ]
// 在构建对象字面量时,也可以使用展开运算符,这个是在ES2018(ES9)中添加的新特性
// 该行为中,会自动对Object.keys(info)的结果进行遍历,并依次进行浅拷贝赋值
// 所以 展开运算符其实是一种浅拷贝
const userInfo = {
...info,
address: 'shanghai'
}
console.log(userInfo) // => { name: 'Klaus', age: 23, address: 'shanghai' }
数值表示
ES6开始,JS支持十进制表示,二进制表示,八进制表示和十六进制表示
// 十进制
console.log(100) // => 100
// 八进制
// tips: 浏览器控制台在输出的时候为了方便我们进行调试
// 会自动将非十进制数值转换为十进制后在进行输出
console.log(0o100) // => 64
// 十六进制
console.log(0x100) // => 256
// 二进制
console.log(0b100) // => 4
在ES2021新增特性:数字过长时,可以使用_作为连接符
// 为了方便我们进行阅读,可以使用_对长数字进行分割
let num = 10_000_000
// 分割的位数是自定义的
// 因为其功能只是为了方便阅读,在进行实际处理的时候,并不会起到任何的实际作用
num = 1_0_00_000
Symbol
Symbol是ES6中新增的一个基本数据类型,翻译为符号
在ES6之前,对象的属性名都是字符串形式,那么很容易造成属性名的冲突
比如原来有一个对象,我们希望在其中添加一个新的属性和值,但是我们在不确定它原来内部有什么内容的情况下,很容易造成冲突,从而覆盖掉它内部的某个属性
比如开发中我们使用混入,那么混入中出现了同名的属性,必然有一个会被覆盖掉
Symbol就是为了解决上面的问题,用来生成一个独一无二的值
- Symbol值是通过Symbol函数来生成的,生成后可以作为属性名
- 也就是在ES6中,对象的属性名可以使用字符串,也可以使用Symbol值
// Symbol是函数 不是构造函数,不需要通过new关键字来进行调用
const s1 = Symbol() // => Symbol()
const s2 = Symbol()
// Symbol即使多次创建值,它们也是不同的:Symbol函数执行后每次创建出来的值都是独一无二的
console.log(s1 == s2) // => false
console.log(s1 === s2) // => false
// 我们也可以在创建Symbol值的时候传入一个描述description
// 默认情况下,打印symbol值 输出结果皆为 Symbol()
// 所以为了方便我们进行区分,我们可以为Symbol添加对应的description
// 描述符只能是字符串或数字
// 如果是其它类型的值会被转换为字符串再作为其描述符
// 其实数字实际上也会被转换为字符串,但是TS中允许描述符的类型是数字或字符串
const s1 = Symbol('name')
console.log(s1) // => Symbol(name)
const user = {
[s1]: 'Klaus'
}
console.log(user[s1]) // => Klaus
// Symbol值不能进行运算
const symbol = Symbol()
// console.log(symbol + 2) => error
// Symbol值可以转换为字符串和布尔值
console.log(symbol.toString()) // => 'Symbol()'
console.log(!!symbol) // => true
// Symbol的目的是为了创建一个独一无二的值
// 但有的时候,我们就希望创建相同的Symbol,这个时候可以使用Symbol.for方法
// 当Symbol.for所传入的key一致时,即认为这2个symbol值是相等的
// 如果不传参数,也会被认为是相同的key
// ps: Symbol.for方法的key其实就是其对应的参数
// 在Symbol方法中 其对应的参数名称为 description
// 他们虽然名称不一致,但是本质是一样的
// Symbol.for方法会先根据key去进行查找
// 如果对应的key已经被创建就直接返回对应的symbol值
// 如果对应的key没有被创建,那么会新建一个新的symbol值,并将其返回
const s1 = Symbol.for()
const s2 = Symbol.for()
console.log(s1 === s2) // => true
// 凡是使用Symbol方法创建出来的symbol值一定是独一无二的
// 只有使用Symbol.for方法创建出来的key才可能相同
const s = Symbol('foo')
const s1 = Symbol.for(s.description)
const s2 = Symbol.for(s.description)
console.log(s === s1) // => false
console.log(s1 === s2) // => true
// 对于description, 可以通过symbol的description属性去进行获取
let s = Symbol('bar')
console.log(s.description) // => bar
// 对于key, 可以通过symbol的keyFor方法进行获取
s = Symbol.for('baz')
console.log(Symbol.keyFor(s)) // => baz
const user = {
name: 'Klaus',
age: 23,
[Symbol('address')]: 'shanghai',
[Symbol('height')]: 1.88
}
// for-of 和 Object.keys,Object.getOwnPropertyNames 遍历出来的key只能是可迭代的非symbol属性
// JSON.stringify 方法将对象转换为字符串的时候,也会自动过滤symbol属性
console.log(Object.keys(user)) // => [ 'name', 'age' ]
// 如果我们需要获取到Symbol属性值,需要使用Object.getOwnPropertySymbols方法
console.log(Object.getOwnPropertySymbols(user)) // => [ Symbol(address), Symbol(height) ]
// Object.getOwnPropertySymbols方法的返回结果也是一个可迭代对象
for (const key of Object.getOwnPropertySymbols(user)) {
console.log(user[key])
/*
=>
shanghai
1.88
*/
}
// 使用Reflect.ownKeys 可以获取对象自身的所有的属性名,包括symbol属性名,可迭代属性名和不可迭代属性名
console.log(Reflect.ownKeys(user)) // => [ 'name', 'age', Symbol(address), Symbol(height) ]
几个内置Symbol值
Symbol.toStringTag
const user = {
name: 'Klaus',
age: 23,
// Symbol.toStringTag的值如果是一个函数,则该函数需要是一个get访问器函数
get [Symbol.toStringTag]() {
// Symbol.toStringTag方法需要返回一个字符串类型的值
// 如果返回的值的类型不是字符串类型,js会直接使用默认的type类型
// 例如 如果这里的Symbol.toStringTag方法返回值为2, 是一个number类型值
// 那么user.toString()的结果为 [object Object]
// 也就是直接使用默认的type值 Object, 而不会尝试将2转换为'2'
return this.name
}
}
console.log(user.toString()) // => [object Klaus]
const user = {
name: 'Klaus',
age: 23,
// 如果Symbol.toStringTag的值是字符串,会自动作为get方法的返回值使用
[Symbol.toStringTag]: 'Klaus'
}
console.log(user.toString()) // => [object Klaus]
Symbol.toPrimative
const obj = {
name: '',
// 将对象转换为基本数据类型时,会调用Symbol.toPrimitive指向的方法
// 1. 当hint值为number, 表示 对象 -> number
// 2. 当hint值为string, 表示 对象 -> string
// 3. 当hint值为default, 表示 对象 -> string | number
// + 加号可能是数字相加 也可能是字符串拼接 --- hint的值就是default
// + 判等操作(即 == 和 != --> 全等操作不会进行任何隐式类型转换) --- hint的值也是default
// 对象 -> boolean --- 不会调用Symbol.toPrimitive指向的方法
[Symbol.toPrimitive]: hint => {
console.log(hint)
if (!hint) {
console.log('boolean')
}
if (hint === 'number') {
return 0
} else if (hint === 'string') {
return ''
} else {
return false
}
}
}
console.log(!!obj)