模板字符串
小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
在ES6之前,如果我们想要将字符串和一些动态的变量(标识符)拼接到一起,是非常麻烦和丑陋的
ES6允许我们使用字符串模板来嵌入JS的变量或者表达式来进行拼接:
- 首先,我们会使用 `` 符号来编写字符串,称之为模板字符串
- 其次,在模板字符串中,我们可以通过 ${expression} 来嵌入动态的内容
const age = 23
// 基本使用
console.log(`age is ${age}`) // => age is 23
// ${expression}中可以使用合法的js表达式
console.log(`age is ${age * 2}`) // => age is 46
function doubleAge() {
return age * 2
}
// 函数调用也是一个合法的js表达式
console.log(`age is ${doubleAge()}`) // => age is 46
标签模板字符串
模板字符串还有另外一种用法:标签模板字符串(Tagged Template Literals)
模板字符串可以认为是函数调用的一种特殊形式
function foo() {
console.log('foo 被调用了')
}
// 传统的函数调用
foo()
// 标签模板字符串调用
foo``
标签模板字符串在被调用的时候,会根据传任入的模板字符串自动进行字符串的分割和参数的传递
- 第一个参数是数组,是被模块字符串拆分的字符串组合
- 后边的参数是一个个模块字符串传入的内容
function foo(...args) {
console.log(...args)
}
foo`` // 默认会接收一个参数,类型为数组 --> ['']
function foo(...args) {
console.log(...args)
}
const lang = 'JS'
const username = 'Kluas'
foo`Hello ${lang}, my name is ${username}`
/*
=>
['Hello ',', my name is','']
JS,
Klaus
*/
函数参数
默认参数
ES5
function sum(m, n) {
// ES5中设置默认值的方式,但是这么设置其实是有bug的
// 如果m或n的值是0或者空字符串的时候
// 他们转换为boolean类型的时候,也是false,会触发使用默认值
// 但是很明显,他们之间是不应该使用默认值的
m = m || 'm'
n = n || 'n'
console.log(m + n)
}
function sum(m, n) {
// 所以我们可能会这么书写函数的默认值
m = m === undefined ? 'm' : m
n = n === undefined ? 'n' : n
console.log(m + n)
}
但是这么书写函数的默认值是十分繁琐的,所以ES6为我们提供了一种新的函数默认值的书写方式
function sum(m = 'm', n = 'n') {
console.log(m + n)
}
sum() // => mn
sum(0, 0) // => 0
如果函数参数是一个对象的时候,函数参数的默认值和对象的解构结合在一起使用,会十分的方便
function print({ name, age } = { name: 'Klaus', age: 23 }) {
console.log(name, age)
}
print()
print({
name: 'Alex',
age: 18
})
上述的代码还有另外一种书写方式
function print({ name = 'Klaus', age = 23 } = {}) {
console.log(name, age)
}
print()
print({
name: 'Alex',
age: 18
})
注意事项
- 参数的默认值我们通常会将其放到最后
function print(m = 23, n) {
console.log(m, n)
}
// 如果需要使用默认值
print(undefined, 23)
- 默认值会改变函数的length的个数,默认值以及后面的参数都不计算在length之内了
function foo(m, n = 2) {}
console.log(foo.length) // => 1
function baz(m, n = 2, x, y) {}
// 从默认值开始包括之后的参数都不再被计算到arguments中
console.log(baz.length) // => 1
剩余参数
ES6中引用了rest parameter,可以将不定数量的参数放入到一个数组中
以... 为前缀的,那么它会将剩余的参数放到该参数中,并且作为一个数组
Tips:
-
剩余参数必须作为最后一个参数进行传递
否则剩余参数后边的参数将永远无法获取实际值,因此会报错
-
剩余参数中的
...是前缀,不是展开运算符
function foo(m, n, ...args) {
console.log(args) // => [4, 5, 6]
}
foo(2, 3, 4, 5, 6)
arguments vs 剩余参数
- 剩余参数只包含那些没有对应形参的实参,而 arguments 对象包含了传给函数的所有实参
- arguments对象不是一个真正的数组,而rest参数是一个真正的数组,可以进行数组的所有操作
rest参数是ES6中提供的一种替代arguments的来获取函数参数的方式,所以在开发中推荐使用剩余参数来替换arguments
箭头函数
箭头函数内部没有this,如果需要使用,会沿着作用域链去使用上层作用域中的this
箭头函数内部是没有arguments的,如果需要使用,可以使用剩余参数进行替换
箭头函数内部是没有显示原型对象prototype,这也就意味着箭头函数只能作为普通函数被调用,是无法使用new关键字来进行调用的
const foo = () => {
// 箭头函数内部没有this,所以去上层作用域查找
// 在这里上层作用域就是全局作用域
console.log(this) // => globalThis
// 箭头函数内部没有arguments, 直接打印会报错
// 在浏览器端,会直接报错
// 但是在node端,因为模块化解析需要,会存在默认值,但是也并不是我们所需要的那个arguments
console.log(arguments) // error
}
console.log(foo.prototype) // => undefined
foo()
展开运算符
可以在函数调用/数组构造和创建对象字面量的时候,将数组表达式或者string在语法层面展开
展开运算符其实是一种浅拷贝
// 函数调用
function foo(...args) {
console.log(...args)
}
foo(...['Alex', 'Klaus'])
foo(...'Steven')
// apply的第一个参数是null,表示的是不指定函数调用内部的this,依旧使用原本的那个this
// 第二个参数是一个数组,apply在实际调用的时候,会自动将第二个参数解构出来并依次作为函数的参数被传入
foo.apply(null, [1, 2])
// 数组构造
const users = ['Klaus', 'Alex', 'Steven']
const name = 'coderwxf'
const newUsers = [...users]
const newName = [...name]
console.log(newUsers, newName)
// 构建对象字面量 --- 数组
const users = ['Klaus', 'Alex', 'Steven']
const userObj = { ...users }
console.log(userObj) // => { '0': 'Klaus', '1': 'Alex', '2': 'Steven' }
// 构建对象字面量 --- 对象
const info = {
name: 'Klaus',
age: 23
}
const newObj = {...info}
console.log(newObj) // => { name: 'Klaus', age: 23 }
数值表示
// 十进制
let num = 100
// 二进制
num = ob100
// 八进制
num = 0o100
// 十六进制
num = 0x100
// 对于一些大的数值,看起来不是很方便
// 在生活中,我们会使用逗号进行分割 100,000,000,000
// 在ES2021中可以使用下划线来模拟这里的逗号
const num = 100_000_000_000
// 注意:这仅仅只是一个方便阅读和标识的方式,并不影响数值的实际使用和表示
console.log(num) // => 100000000000
Symbol
Symbol是ES6中新增的一个基本数据类型,翻译为符号
在ES6之前,对象的属性名都是字符串形式,那么很容易造成属性名的冲突
比如原来有一个对象,我们希望在其中添加一个新的属性和值,
但是我们在不确定它原来内部有什么内容的情况下, 很容易造成冲突,从而覆盖掉它内部的某个属性
Symbol就是为了解决上面的问题,用来生成一个独一无二的值
Symbol值是通过Symbol函数来生成的,生成后可以作为属性名
在ES6中,对象的属性名可以使用字符串,也可以使用Symbol值
// Symbol即使多次创建值,它们也是不同的:Symbol函数执行后每次创建出来的值都是独一无二的
const s1 = Symbol()
const s2 = Symbol()
console.log(s1 === s2) // => false
// 可以在创建Symbol值的时候传入一个描述description, 用以起到标记的功能
const s = Symbol('foo')
console.log(s) // => Symbol(foo)
// 获取到symbol的描述符
console.log(s.description) // => foo
// 可以使用Symbol在对象中表示唯一的属性名
let s1 = Symbol()
let s2 = Symbol()
let s3 = Symbol()
// 设置方式1
const obj = {
[s1]: 'Klaus'
}
// 设置方式2
obj[s2] = 'Alex'
// 设置方式3
Object.defineProperty(obj, s3, {
value: 'Steven',
enumerable: true,
configurable: true,
writable: true
})
// 获取symbol属性值
// 获取Symbol值只能使用中括号语法,不可以使用点语法
// 如果使用点语法,例如obj.s1, 浏览器在解析的时候会将s1作为字符串类型的值去进行查找
console.log(obj[s1])
console.log(obj[s2])
console.log(obj[s3])
// 在普通的遍历中,无法遍历到以symbol值作为key的属性
// Object.keys获取属性名数组的时候,不包含以symbol值作为key的属性
console.log(Object.keys(obj)) // => []
// for ... of 循环中是无法打印出以symbol值作为key的属性
for (const k in obj) {
// 没有任何的输出
console.log(k)
}
// 无法使用Object.getOwnPropertyNames, 获取以symbol值作为key的属性
console.log(Object.getOwnPropertyNames(obj)) // => []
// 可以使用Object.getOwnPropertySymbols方法获取以symbol值作为key的属性
// Object.getOwnPropertySymbols方法返回的数据类型是数组类型
// 可以通过遍历的方式去获取到每一个symbol类型的属性名及其对应的属性值
console.log(Object.getOwnPropertySymbols(obj)) // => [ Symbol(), Symbol(), Symbol() ]
// 也可以使用Reflect.ownKeys方法获取对象上所有的属性
//(包括所有Symbol类型的属性和所有普通属性)
console.log(Reflect.ownKeys(obj)) // => [ Symbol(), Symbol(), Symbol() ]
创建Symbol的目的是为了创建一个独一无二的值
但是在某些情况下,就是需要创建建相同的Symbol值
此时可以使用Symbol.for方法
const s1 = Symbol.for()
const s2 = Symbol.for()
console.log(s1 === s2) // => true
// ---------------------------------
// 使用Symbol.for来创建symbol类型的值的时候
// 可以传入一个参数作为标识符
// 在创建的时候,会优先去寻找之前是否存在相同标识符的symbol值
// 如果存在就返回之前所创建过的symbol值,如果没有,则新建一个symbol值并返回
const s3 = Symbol.for('Klaus')
const s4 = Symbol.for('Klaus')
console.log(s3 === s4) // => true
// ---------------------------------------
// 可以通过Symbol.keyFor方法来获取对应的key
// Symbol.keyFor方法去获取一个Symbol类型值的标识符的时候
// 如果创建的时候传入了标识符,则将标识符返回
const s1 = Symbol.for('foo')
console.log(Symbol.keyFor(s1)) // => foo
// 如果创建的时候没有传入标识符,则返回undefined
// 注意: 这里返回的undefined是字符串类型的值
const s2 = Symbol.for()
console.log(Symbol.keyFor(s2)) // => undefined
console.log(typeof Symbol.keyFor(s2)) // => string
// 如果symbol在创建的时候,不是使用Symbol.keyFor方法创建的
// 那么无论该symbol值在被创建的时候,有没有传入标识符
// 其返回的结果都是undefined,且值的类型也是undefined
const s3 = Symbol('baz')
console.log(Symbol.keyFor(s3)) // => undefined
console.log(typeof Symbol.keyFor(s3)) // => undefined
// 此时需要通过属性description来获取
console.log(s3.description) // => baz