模板字符串

基本使用

在ES6之前,如果我们想要将字符串和一些动态的变量(标识符)拼接到一起,需要使用一堆的 + 号,非常不方便。
ES6允许我们使用字符串模板来嵌入JS的变量或者表达式来进行拼接:

  • 使用``` 符号来包裹的字符串,称之为模板字符串;
  • 在模板字符串中,可以通过 ${expression}来嵌入动态的内容 ```javascript // ES6之前拼接字符串和其他标识符 const name = “why” const age = 18 const height = 1.88

// console.log(“my name is “ + name + “, age is “ + age + “, height is “ + height)

// ES6提供模板字符串 ` const message =my name is ${name}, age is ${age}, height is ${height}` // 简单取值 console.log(message)

const info = age double is ${age * 2} // 取值并计算 console.log(info)

function doubleAge() { return age * 2 }

const info2 = double age is ${doubleAge()} // 调用函数 console.log(info2)

  1. <a name="ZS1tl"></a>
  2. ## 标签模板字符串使用
  3. 模板字符串还有另外一种用法:标签模板字符串(Tagged Template Literals)。<br />````可以调用函数,并且可以把模板字符串以**数组**的形式当参数传入函数中,如果模板字符串中有`${}`表达式,则会将字符串**分割成几份**。<br />被分割的字符串会将形成的数组传给函数的第一个参数,`${}`表达式的值会传给函数第二个参数,多个表达式就依次往后面的参数传。
  4. ```javascript
  5. function foo1(m) {
  6. console.log(m);
  7. }
  8. foo1`123456` // [ '123456' ]
  9. function foo2(m, n) {
  10. console.log(m, n);
  11. }
  12. const name = 'zs'
  13. foo2`123${name}456` // [ '123', '456' ] zs
  14. function foo3(m, n, t) {
  15. console.log(m, n, t);
  16. }
  17. const name1 = 'zs'
  18. const name2 = 'ls'
  19. foo3`123${name}456${name2}789` // [ '123', '456', '789' ] zs ls

我们一般不会主动编写这样的代码,但是有些框架内部实现就会用到标签目标字符串,比如 react。

函数的默认参数

之前设置函数参数的默认值我们会使用异或写法,但是这种写法有两个缺点,繁琐和逻辑考虑不全产生bug。

  1. function foo(m, n) {
  2. m = m || "aaa"
  3. n = n || "bbb"
  4. console.log(m, n)
  5. }
  6. // 0 和 null 布尔运算都是 false,导致无法做参数
  7. foo(0, null) // aaa bbb

ES6 可以让默认值直接赋值写在参数上,并且还可以和解构连用,并且有两种写法。

  1. // 1.ES6可以给函数参数提供默认值
  2. function foo(m = "aaa", n = "bbb") {
  3. console.log(m, n)
  4. }
  5. foo(0, "") // 0
  6. // 2.对象参数和默认值以及解构,默认要解构的对象
  7. function printInfo({name, age} = {name: "why", age: 18}) {
  8. console.log(name, age)
  9. }
  10. printInfo({name: "kobe", age: 40})
  11. // 另外一种写法:不默认对象,直接默认解构后的参数值
  12. function printInfo1({name = "why", age = 18} = {}) {
  13. console.log(name, age)
  14. }
  15. printInfo1()

有多个参数情况下,规范要求把有默认值的参数写到最后面,这样当想要使用参数默认值的时候就可以省略写最后一个参数,调用更方便。
另外,foo.length属性表示函数参数的个数。但是参数定义默认值后,该参数及其之后的参数将不会被统计到 length 属性中。

  1. // 3.有默认值的形参最好放到最后
  2. function bar(x, y, z = 30) {
  3. console.log(x, y, z)
  4. }
  5. bar(10, 20) // 使用参数默认值,调用时可省略写最后的参数
  6. bar(undefined, 10, 20) // 假设 z 定义在第一个位置,则不能省略
  7. // 4.有参数默认值的函数 length 属性
  8. function baz(x, y, z, m = 20, n) {
  9. console.log(x, y, z, m, n)
  10. }
  11. console.log(baz.length) // 3,后面两个不统计

参数作用域

一般来讲函数只有一个作用域,参数和函数内部都属于同一个作用域。但是如果参数设置了默认值,则函数作用域会被更加细分参数作用域和函数内部作用域。

  1. var x = 0
  2. // 当函数的参数有默认值时, 会形成一个新的作用域, 这个作用域用于保存参数的值
  3. // 这个x=3是赋值给了同为参数的x
  4. function foo(x, y = function() { x = 3; console.log(x) }) {
  5. console.log(x) // 参数 x 未赋值,为 undefined
  6. var x = 2 // 在函数内部作用域中声明了变量 x = 2
  7. y() // 执行参数函数,并打印了同为参数的 x = 3
  8. console.log(x)
  9. }
  10. foo()
  11. console.log(x) // 全局的 x 并未更改,依然为 0
  12. // undefined
  13. // 3
  14. // 2
  15. // 0

函数的剩余参数

ES6中引用了rest parameter,可以将不定数量的参数放入到一个数组中:如果最后一个参数是...为前缀的,那么它会将剩余的参数放到该参数中,并且作为一个数组;

那么剩余参数和 arguments 有什么区别呢?

  • 剩余参数只包含那些没有对应形参的实参,而 arguments 对象包含了传给函数的所有实参;
  • arguments 对象不是一个真正的数组,而rest参数是一个真正的数组,可以进行数组的所有操作;

arguments 是早期的 ECMAScript 中为了方便去获取所有的参数提供的一个数据结构,而 rest 参数是ES6中提供并且希望以此来替代 arguments 的;

注意:剩余参数必须放到最后一个位置,否则会报错。

  1. function foo(m, n, ...args) {
  2. console.log(m, n) // 20 30
  3. console.log(args) // [ 40, 50, 60 ]
  4. console.log(arguments) // [Arguments] { '0': 20, '1': 30, '2': 40, '3': 50, '4': 60 }
  5. }
  6. foo(20, 30, 40, 50, 60)
  7. function fn(...args, n) {} // Rest parameter must be last formal parameter

箭头函数补充

前面讲了箭头函数没有 this 和 argument,它也没有显式原型。所以不能当构造函数来 new,因为没办法完成 new 关键的第二步,将函数显式原型赋值给空对象的隐式原型。

  1. var bar = () => { }
  2. console.log(bar.prototype) // undefined
  3. // bar is not a constructor
  4. const b = new bar()

展开语法

展开语法也是...和剩余参数需要注意区分,剩余参数是在定义参数时来存值的,展示语法是来取值的。
展开语法(Spread syntax):

  • 可以在函数调用/数组构造时,将 数组表达式 或者 string 在语法层面展开,然后取值。
    • 数组是按索引顺序展开取值
    • 字符串是按字符一个一个展开
  • 还可以在构造字面量对象时, 将对象表达式按 key-value 的方式展开;

展开语法的场景:

  • 在函数调用时使用;
  • 在数组构造时使用;
  • 在构建对象字面量时,也可以使用展开运算符,这个是在ES2018(ES9)中添加的新特性; ```javascript const arr = [“abc”, “cba”, “nba”] const str = “hi”

const info = {name: “zs”, age: 18}

// 函数调用时使用 function foo(…args) { console.log(args); }

// 1. 函数调用时,展开取值放入剩余参数数组中 foo(…arr) // [ ‘abc’, ‘cba’, ‘nba’ ] foo(…str) // [ ‘h’, ‘i’ ]

// 2. 构造数组时,展开取值初始化数组 const newarr = […arr, …str] console.log(newarr) // [ ‘abc’, ‘cba’, ‘nba’, ‘h’, ‘i’]

// 3. 展开取值放入字面量对象 const obj1 = {…info} const obj2 = {…info, …arr} const obj3 = {…info, …arr, …str} console.log(obj1); // 对象按 key-value 展开 console.log(obj2); // 数组按索引当属性初始化到对象中 console.log(obj3); // 字符串也是按索引,因为数组也是按索引,所以会互相覆盖属性值

// { name: ‘zs’, age: 18 } // { ‘0’: ‘abc’, ‘1’: ‘cba’, ‘2’: ‘nba’, name: ‘zs’, age: 18 } // { ‘0’: ‘h’, ‘1’: ‘i’, ‘2’: ‘nba’, name: ‘zs’, age: 18 }

  1. 注意:**展开运算符其实是一种浅拷贝**。如果要展开的数组或对象中有引用类型时,修改引用类型中的值要注意。
  2. 浅拷贝就是复制的时候只会复制引用类型的内存地址,而不是真的在堆内存中再新建一个一模一样的复杂类型。所以改了复杂类型中的值,改动将会影响所有对该复杂类型的引用。
  3. ```javascript
  4. const info = {
  5. name: "why",
  6. friend: { name: "kobe" }
  7. }
  8. const obj = { ...info, name: "coderwhy" }
  9. console.log(obj) // { name: 'coderwhy', friend: { name: 'kobe' } }
  10. // 修改引用类型 friend 中 name 的值
  11. obj.friend.name = "james"
  12. // info 对象的值也被改了
  13. console.log(info.friend.name) // james

数值的表示

在ES6中规范了二进制和八进制的写法,另外在ES2021新增特性:数字过长时,可以使用_作为连接符

  1. const num1 = 100 // 十进制
  2. // b -> binary
  3. const num2 = 0b100 // 二进制
  4. // o -> octonary
  5. const num3 = 0o100 // 八进制
  6. // x -> hexadecimal
  7. const num4 = 0x100 // 十六进制
  8. console.log(num1, num2, num3, num4) // 100 4 64 256
  9. // 大的数值的连接符(ES2021 ES12)
  10. const num = 100_000_000
  11. console.log(num) // 100000000

Symbol 的基本使用

Symbol是什么呢?Symbol是 ES6 中新增的一个基本数据类型,翻译为符号。可当函数一样使用。

那么为什么需要 Symbol 呢?

在ES6之前,对象的属性名都是字符串形式,那么很容易造成属性名的冲突;

  • 比如原来有一个对象,我们希望在其中添加一个新的属性和值,但是我们在不确定它原来内部有什么内容的情况下,很容易造成冲突,从而覆盖掉它内部的某个属性;
  • 比如开发中我们使用混入,那么混入中出现了同名的属性,必然有一个会被覆盖掉;

Symbol 就是为了解决上面的问题,用来生成一个独一无二的值。
Symbol 值是通过Symbol 函数来生成的,生成后可以作为属性名;也就是在ES6中,对象的属性名可以使用字符串,也可以使用 Symbol 值;
Symbol 函数执行后每次创建出来的值都是独一无二。

  1. // 1.ES6中Symbol的基本使用
  2. const s1 = Symbol()
  3. const s2 = Symbol()
  4. console.log(s1, s2) // Symbol() Symbol()
  5. console.log(s1 === s2) // false
  6. // ES2019(ES10)中, 还可以给 Symbol 添加一个描述(description)
  7. // 因为表明看Symbol都是一样,这个描述可以做个区分
  8. const s3 = Symbol("aaa")
  9. console.log(s3.description) // aaa
  10. console.log(s3); // Symbol(aaa)

对象中定义了 Symbol 属性,通过 遍历 / Object.keys 等,是无法获取这些属性的。

  • Object.getOwnPropertySymbols(对象)

需要使用Object.getOwnPropertySymbols(对象),它将会返回一个Symbol 属性组成的数组。
当然Object.getOwnPropertyDescriptors()获取所有属性描述符的方式也能遍历出来Symbol。

  1. // 2.Symbol值作为属性,三种添加属性的方式都适用
  2. // 2.1.在定义对象字面量时使用
  3. const key1 = Symbol()
  4. const obj = {
  5. [key1]: "abc",
  6. [Symbol('key2')]: 666 // 不要这样添加 Symbol 值属性,因为每次执行结果都不一样
  7. }
  8. console.log(obj[Symbol('key2')]); // undefined,找不到原来的属性
  9. // 2.2.新增属性
  10. const key3 = Symbol()
  11. obj[key3] = "nba"
  12. // 2.3.Object.defineProperty方式
  13. const key4 = Symbol('key4')
  14. Object.defineProperty(obj, key4, {
  15. enumerable: true,
  16. configurable: true,
  17. writable: true,
  18. value: "mba"
  19. })
  20. // 读取 Symbol 值的属性
  21. console.log(obj[key1])
  22. // 注意: 不能通过.语法获取,.语法只会去找字符串属性,[]才有计算表达式的能力
  23. console.log(obj.key1) // undefined
  24. // 3.使用Symbol作为key的属性名,通过 遍历 / Object.keys 等,是无法获取这些属性的
  25. console.log(Object.keys(obj)) // [ ] 一个空数组,好像没有属性
  26. console.log(Object.getOwnPropertyNames(obj)) // [ ]
  27. // 需要 Object.getOwnPropertySymbols 来获取所有Symbol 属性
  28. console.log(Object.getOwnPropertySymbols(obj))
  29. // 遍历对象中 Symbol 属性的属性值
  30. const sKeys = Object.getOwnPropertySymbols(obj)
  31. for (const sKey of sKeys) {
  32. console.log(obj[sKey])
  33. }

相同值的Symbol

如果我们现在就是想创建相同的 Symbol ,我们可以使用Symbol.for(key)方法,并且这个 key 也会成为这个 Symbol 的描述。
同时我们也可以通过Symbol.keyFor(Symbol值)方法来获取对应的 key。

  1. const sa = Symbol.for("aaa")
  2. const sb = Symbol.for("aaa")
  3. console.log(sa === sb) // true
  4. console.log(sa); // Symbol(aaa) 这个 key 也会成为这个 Symbol 的描述
  5. const key = Symbol.keyFor(sa)
  6. console.log(key) // aaa
  7. const sc = Symbol.for(key)
  8. console.log(sa === sc) // true