大前端-阶段1-模块2-ES6新特性

ES6新特性

一、ECMAScript 2015

1.ES2015共有三种作用域

什么是作用域?某个成员能够起作用的范围。

  • 全局作用域
  • 函数作用域
  • 块级作用域(es2015新增): 在块级作用域内定义的成员,外部是无法访问的。
2.变量声明:let const
  • let const都是块级作用域,let是变量,const是常量
  • for点击事件
var element = [{}, {}, {}]
for(var i = 0; i < element.length; i++) {
  element[i].onclick = function () {
    // i是全局变量,已经变成了3
    console.log(i)
  }
}
element[2].onclick() // 3

var element = [{}, {}, {}]
for(var i = 0; i < element.length; i++) {
  element[i].onclick = (function (i) {
  // 闭包实现
    return function () {
      console.log(i)
    }
  })(i)
}
element[2].onclick() // 2

var element = [{}, {}, {}]
for(let i = 0; i < element.length; i++) {
  // let定义的变量是块级作用域
  element[i].onclick = function () {
    console.log(i)
  }
}
element[2].onclick() // 2

  • for生成两层块级作用域
for(let i = 0; i < 3; i ++) {
  let i = 'foo'
  console.log(i) // foo foo foo
}

let i = 0

if (i < 3) {
  let i = 'foo'
  console.log(i)
}

i++

if (i < 3) {
  let i = 'foo'
  console.log(i)
}

i++

if (i < 3) {
  let i = 'foo'
  console.log(i)
}

i++

  • let const 不会变量提升
console.log(foo) // undefined
var foo = 'jal'

console.log(foo2) // ReferenceError
let foo2 = 'jal'
  • 编码建议:不用var,主用const,搭配let

let 和var的区别:

  • 相同点:声明后未赋值,表现相同。都返回undefined
  • 不同点:
    1.let 先声明再使用,否则会报错。var 先使用再声明,返回undefined
  1. 重复声明同一个变量时:var不会报错,let会报错。
  2. 作用范围不同。let属于块块用域。在块外使用报错。var块内外使用都不会报错。
  • const 关键字,用于声明创建一个值得只读引用。
    const = {name: ‘a’} ,可以修改name的值,因为是引用类型,存储的是引用类型的内存地址。
3.数组的解构

方括号[]中的变量按顺序匹配数组元素

const arr = [1, 2, 3]
const a = arr[0]
const b = arr[1]
const c = arr[2]
console.log(a, b ,c) // 1 2 3
const [a, b, c] = arr
console.log(a, b ,c) // 1 2 3

const [, , c] = arr
console.log(c) // c

const [a] = arr
console.log(a) // 1

const [a, b, c = 123, d = 'defaultValue'] = arr
console.log(c, d) // 3 defaultValue

const path = 'a/b'
const [, b] = path.split('/')
console.log(b) // b
4. 对象的解构
const obj = {name: 'yibo', age: 22}
const {name} = obj
console.log(name) // yibo
const name = 'jal'
const {name} = obj
console.log(name) // SyntaxError: Identifier 'name' has already been declared
const name = 'jal'
const {name: objName, sex = 'boy'} = obj
console.log(objName, sex) // yibo boy
6. 模板字符串标签函数
const str = console.log`hello world` // [ 'hello world' ]

const name = 'tom'
const gender = true
function myTagFunc (str, name, gender) {
  console.log(str, name, gender)  // [ 'hey, ', ' is a ', '.' ] tom true
  return str[0] + name + str[1] + gender + str[2]
}

const result = myTagFunc`hey, ${name} is a ${gender}.`
console.log(result) // hey, tom is a true.

7. 字符串方法
  • .includes()
  • startsWith()
  • endsWith()
const message = 'Error: foo is not undefined.'
console.log(
  message.startsWith('Error'),
  message.endsWith('undefined.'),
  message.includes('foo')
)
// true true true

8. 函数默认值
// 带有默认值的参数要放在最后
function foo(enable = true) {
  console.log(enable)
}
foo(true) //true
foo(false) //false
foo() // true

9. 剩余参数

剩余参数只能出现在形参的最后一位,而且只能使用一次

// function foo(){
//   console.log(arguments)
// }
// foo(1, 2, 3) // [Arguments] { '0': 1, '1': 2, '2': 3 }


// ...args只能出现在形参的最后一位,而且只能使用一次
function foo(...args) {
  console.log(args)
}
foo(1, 2, 3) // [ 1, 2, 3 ]

10. 展开数组
const arr = ['foo', 'bar', 'baz']

console.log.apply(console, arr) // foo bar baz
console.log(...arr) // foo bar baz

  1. 箭头函数
const inc = n => n + 1
console.log(inc(1)) // 2

const sum = (a, b) => {
  return a + b
}
console.log(sum(1, 2)) // 3

  • 箭头函数写的代码简短易读
  • 箭头函数不会改变this指向,this为上层作用域的this
const person = {
  name: 'tom',
  sayHi: function () {
    // this 是 person
    console.log(`hi, my name is ${this.name}`)
  },
  sayHiAync: function () {
    setTimeout(function () {
      //node.js: this 是 {}
      console.log(` sayHiAync: hi, my name is ${this.name}`)
    }, 1000);
  }
}
person.sayHi() // hi, my name is tom
person.sayHiAync() // sayHiAync: hi, my name is undefined

箭头函数:
不会改变函数的this。在箭头函数中没有this的机制,因此不会改变this的指向。在箭头函数外部this是什么,在里面拿到的就是什么。箭头函数中的this指的都是当前作用域中的this。

  • 箭头函数和普通函数的区别:
  1. 箭头函数没有自己的this对象。
  2. 不可以当作构造函数,也就是说,不可以对箭头函数使用new命令,否则会抛出一个错误。
  3. 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
  4. 不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
const person = {
  name: 'tom',
  sayHi:  () => {
    // node.js: this是window
    console.log(`hi, my name is ${this.name}`)
  },
  sayHi2: function() {
  // this: person
  console.log(`hi, my name is ${this.name}`) // 
  },
  sayHiAync: function () {
  //在箭头函数中没有this的机制,因此不会改变this的指向。
  // 在箭头函数外部this是什么,在里面拿到的就是什么。箭头函数中的this指的都是当前作用域中的this。
    setTimeout(() => {
      // this是person
      console.log(` sayHiAync: hi, my name is ${this.name}`)
    }, 1000);
  }
  sayHiAync2: function() {
  // 这个setTimeout里面的函数会放到全局对象上被调用,
  // 里面是拿不到当前的this对象的,拿到的是window。
  setTimeout(function () {},2000)
  },
}
person.sayHi() // hi, my name is undefined
person.sayHi2() // hi, my name is tom
person.sayHiAync() // sayHiAync: hi, my name is tom

12. 对象字面量增强
  • 属性名和属性值相同时可以省略,只写属性名
  • 对象方法可以直接写函数形式:method1(){}
  • 使用方括号的方式计算动态属性名
const bar = 111
const obj = {
  foo: 123,
  // bar: bar,
  bar, // 同上一行效果
  // method1: function () {
  //   console.log(`method1: ${this}`)
  // },
  method2 () {
    // 直接写一个方法,同上面的冒号属性
    console.log(`method2: ${this}`)
  },
  [Math.random()]: 123, // 计算属性名

}
console.log(obj) // { foo: 123, bar: 111, method2: [Function: method2], '0.13076137144987743': 123 }
13. 对象扩展方法
  • Object.assign(target, source):将多个源对象中的属性复制到一个目标对象中
// Object.assign 用第二个参数的对象属性覆盖第一个参数的对象。返回结果为第一个对象
const source1 = {
  a: 123,
  b: 456
}
const source2 = {
  a: 333,
  c: 33
}
const target = {
  a: 11,
  b: 22
}

const result = Object.assign(target, source1, source2)
console.log(result) // { a: 333, b: 456, c: 33 }
console.log(result === target) // true

function fun(obj) {
  // obj.name = 'function.obj'
  // console.log(obj)
  const funObj = Object.assign({}, obj)
  funObj.name = 'function.obj'
  console.log(funObj)
}

const obj = {
  name: 'global obj'
}

fun(obj)
console.log(obj)

  • Object.is: 严格判断2个对象是否相等。 对===的比较,+0不等于-0, NAN等于NAN
// Object.is 
console.log(
  0 === false, // false
  0 == false, // true
  +0 ===-0, // true
  NaN === NaN, // false
  Object.is(+0, -0), // false
  Object.is(NaN, NaN) // true
)

  1. 代理对象:Proxy
  • ES5中有一个Object.defineProperty,Vue2就是通过这个实现数据双向绑定
  • ES6提供了Proxy,可以监视对象的读写过程,Vue3.0通过Proxy实现数据绑定

// Proxy 
const person = {
  name: 'jal',
  age: 20
}

const personProxy = new Proxy(person, {
  // 参数为目标对象、属性名
  get (target, property) {
    return property in target ? target[property]: 'default'
    // console.log(target, property) // { name: 'jal', age: 20 } name
    // return 100
  },
  // 参数为目标对象、属性名、属性值
  set (target, property, value) {
    if(property === 'age') {
      if(!Number.isInteger(value)) {
        throw new TypeError(`${value} is not an int`)
      }
    }
    console.log(target, property, value) // { name: 'jal', age: 20 } gender true
  }
})

personProxy.gender = true
// personProxy.age = '11' // TypeError: 11 is not an int

personProxy.age = 11

// console.log(personProxy.name) // 100
console.log(personProxy.name) // jal
console.log(personProxy.xxx) // default

Proxy对比Object.defineProperty:

  • Object.defineProperty只能监听属性的读写
  • Proxy能监视更多对象操作:delete
const person = {
  name: 'jal',
  age: 20
}
const personProxy = new Proxy(person, {
  deleteProperty(target, property) {
    console.log('delete', property) // delete age
    delete target[property]
  }
})
delete personProxy.age

  • Proxy更好的支持数组对象的监视(VUE重写数组的操作方法,劫持方法调用过程)
const list = []
const listProxy = new Proxy(list, {
  set(target, property, value) {
    console.log('set', property, value)
    target[property] = value
    return true // 表示设置成功
  }
})
listProxy.push(100)
// set 0 100
// set length 1

在这里插入图片描述

15. Reflect 统一的对象操作API
  • Reflect属于静态类(如Math),不能new,只能调用静态方法:Reflect.get()。Reflect内部封装了一系列对对象的底层操作。Reflect成员方法就是Proxy处理对象的默认实现
const proxy = new Proxy(obj, {
  get(target, property) {
    // 不写get逻辑,相当于调用Reflect.get(target, property)。
    return Reflect.get(target, property)
  }
})

  • Reflect:统一提供一套用于操作对象的API(13个)
    Reflect成员方法就是proxy处理对象的默认实现。
const obj = {
  foo: '111',
  bar: 'rrr',
  age: 18
}
// console.log("age" in obj)
// console.log(delete obj['bar'])
// console.log(Object.keys(obj))

console.log(Reflect.has(obj, 'name')) // false
console.log(Reflect.deleteProperty(obj, 'bar')) // true
console.log(Reflect.ownKeys(obj)) // [ 'foo', 'age' ]

16. Promise

一种更优的异步编程解决方案。解决了传统异步编程中回调函数嵌套过深的问题

  1. 类 关键词 Class
// function Person(name) {
//   this.name = name
// }
// Person.prototype.say = function() {
//   console.log(`hi, my name is ${this.name}`)
// }
// const p = new Person('jal')
// p.say() // hi, my name is jal

class Person {
  constructor(name) {
    this.name = name
  }
  say () {
  console.log(`hi, my name is ${this.name}`)
  }
}
const p = new Person('jal')
p.say() // hi, my name is jal

  • 静态方法, this 指向当前类,而不是实例
class Person {
  constructor(name) {
    this.name = name
  }
  say () {
  console.log(`hi, my name is ${this.name}`)
  }
  static create(name) {
    // this 指向当前类型,而不是实例
    console.log(this) // [Function: Person]
    return new Person(name)
  }
}

const tom = Person.create('tom')
tom.say() // hi, my name is tom

  • 继承,关键词 extends
class Student extends Person {
  constructor(name, number){
    super(name)
    this.number = number
  }

  hello () {
    super.say()
    console.log(`my school number is ${this.number}`)
  }
}

const s = new Student('jack', 100)
s.hello()
// hi, my name is jack
// my school number is 100

  1. 数据结构 Set
// Set 数据结构
const s = new Set()
s.add(1).add(2).add(3).add(4).add(2)
console.log(s) // Set(4) { 1, 2, 3, 4 }

s.forEach(i => console.log(i)) // 1 2 3 4

for(let i of s) {
  console.log(i)
}
// 1 2 3 4

console.log(s.size) // 4

console.log(s.delete(3)) // true
console.log(s) // Set(3) { 1, 2, 4 }

const arr = [1, 2, 1, 3, 4 ,1]
const result = new Set(arr)
console.log(result) // Set(4) { 1, 2, 3, 4 }
const arr2 = Array.from(result)
console.log(arr2) // [ 1, 2, 3, 4 ]
const arr3 = [...result]
console.log(arr3) // [ 1, 2, 3, 4 ]
  1. 数据结构 Map
  • Map 映射任意类型之间的关系. Map可以用任意对象作为键,而对象只能用字符串作为键
// Map 映射任意类型之间的关系. Map可以用任意对象作为键,而对象只能用字符串作为键
const obj = {}
obj[true] = 'value'
obj[1] = '11'
obj[{a: 1}] = '33'
console.log(Object.keys(obj)) // [ '1', 'true', '[object Object]' ]

const m = new Map()
const tom = {name: 'tom'}
m.set(tom, 90)
console.log(m) // Map(1) { { name: 'tom' } => 90 }
console.log(m.get(tom)) // 90

object和Map的区别:

  • object和Map存储的都是键值对组合。但是:object的键的类型是 字符串;map的键的类型是 可以是任意类型;
  • 另外注意,object获取键值使用Object.keys(返回数组);Map获取键值使用 map变量.keys() (返回迭代器)。
  1. 原始数据类型 Symbol
  • 最主要的作用就是为对象添加独一无二的属性名
const s = Symbol()
console.log(s) // Symbol()
console.log(typeof s) // symbol

console.log(Symbol() === Symbol()) // false

console.log(Symbol('foo')) // Symbol(foo)
console.log(Symbol('bar')) // Symbol(bar)
console.log(Symbol('baz')) // Symbol(baz)

const obj = {}
obj[Symbol()] = 111
obj[Symbol()] = 2
console.log(obj) // { [Symbol()]: 111, [Symbol()]: 2 }


const name = Symbol()
const person = {
  [name]: 'jal', // 作为私有成员防止被访问
  say(){
    console.log(this[name])
  }
}
person.say()// jal
console.log(person[Symbol()]) // undefined
// console.log(person[name]) // jal

截止到ES2019一共定义了6种原始类型,和一个object类型,未来还会增加一个bigint的原始类型(stage-4阶段)标准化过后就是8种数据类型了
boolean symbol number string undefined null object bigint

const s1 = Symbol.for('foo')
const s2 = Symbol.for('foo')
console.log(
  s1 === s2, // true
// Symbol.for的参数会被转化为字符串
Symbol.for(true) === Symbol.for('true'), // true
)
const obj2 = {
  // 为对象实现迭代器时会用到
  [Symbol.toStringTag]: 'XObject'
}
console.log(obj2.toString()) // [object Object] [object XObject]

const obj3 = {
  [Symbol()]: 'symbol value',
  foo: 'normal value'
}
for(var key in obj3) {
  console.log(key)
}
// foo

console.log(Object.keys(obj3)) // [ 'foo' ]
console.log(JSON.stringify(obj3)) // {"foo":"normal value"}

console.log(Object.getOwnPropertySymbols(obj3)) // [ Symbol() ]

21. for … of 作为遍历所有数据结构的统一方式
// for ... of 循环, 可以使用break
const arr = [1, 2, 3, 4]
for (const item of arr) { // item为每个对象实例
  console.log(item)
}
// 相当于
// arr.forEach(item => {
//   console.log(item)
// })

可以使用break终止循环

// arr.forEach ,但是这个方法不能终止遍历
// 为了终止遍历,我们之前,我们曾使用
// arr.some() 返回true
// arr.every() 返回false

for(const item of arr) {
  console.log(item) 
  if(item > 1)break
}

遍历集合Set

const s = new Set(['foo', 'bar'])
for(const item of s) {
  console.log(item)
}
// foo bar

遍历集合Map

const m = new Map()
m.set('foo', '123')
m.set('bar', '34')

for(const item of m) {
  console.log(item)
}
// [ 'foo', '123' ]  [ 'bar', '34' ]

// 解构键和值
for(const [key, value] of m) {
  console.log(key,value)
}
// foo 123
// bar 34

遍历对象,报错了:TypeError: obj is not iterable

const obj = {name: 'jal', age: 22}

for(const item of obj) {
  console.log(item) // TypeError: obj is not iterable
}

22. Iterable接口(可迭代接口)

实现Iterable结构就是for…of的前提
只要这个接口实现了Iterable就可以被for of 遍历。

  • 实现可迭代接口
// 迭代器 iterator 
const set = new Set(['foo', 'bar', 'baz'])

const iterator = set[Symbol.iterator]()

// 这就是for... of 循环实现的工作原理
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
// { value: 'foo', done: false }
// { value: 'bar', done: false }
// { value: 'baz', done: false }
// { value: undefined, done: true }

  • 实现迭代器原理:
    1.在内部维护了一个数据指针,每调用一次next,指针就往后移一位。
    2.具有[Symbol.iterator]方法
// obj 实现可迭代接口 Iterable
const obj = {
  // iterator 方法
  [Symbol.iterator]: function () {
    // 迭代器接口 iterator 
    return {
      // 必须要有next方法
      next: function () {
        // 迭代结果接口 IterationResult
        return {
          value: 1,
          done: true
        }
      }
    }
  }
}

  • 具体实现:
const obj = {
  store: ['foo', 'bar', 'baz'],

  [Symbol.iterator]: function () {
    let index = 0
    const self = this

    return {
      next: function () {
        const result = {
          value: self.store[index],
          done: index >= self.store.length
        }
        index++
        return result
      }
    }
  }
}

for( const item of obj) {
  console.log(item)
}
// foo
// bar
// baz

小案例:

const todos = {
  life: ['吃饭', '睡觉', '打豆豆'],
  learn: ['语文', '数学', '英语'],
  work: ['喝茶'],
  each: function (callback) {
    const all = [].concat(this.life, this.learn, this.work)
    for (const item of all) {
      callback (item)
    }
  },
  // 实现迭代器接口
  [Symbol.iterator]: function () {
    const all = [...this.life, ...this.learn, ...this.work]
    let index = 0
    return {
      next: function () {
        return {
          value: all[index],
          done: index++ >= all.length
        }
      }
    }
  }
}

todos.each(function (item) {
  console.log(item)
})
console.log('---------')
for(const item of todos) {
  console.log(item)
}
// 吃饭
// 睡觉
// 打豆豆
// 语文
// 数学
// 英语
// 喝茶
// ---------
// 吃饭
// 睡觉
// 打豆豆
// 语文
// 数学
// 英语
// 喝茶

23. 生成器函数 Generator

避免异步编程中回调函数嵌套过深,提供更好的异步编程解决方案.

function * foo () {
  console.log('zce')
  return 100
}
// 这个foo就是一个Generator函数

const result = foo()
console.log(result)// Object [Generator] {}
console.log(result.next())
// zce
// { value: 100, done: true }
// 可以看出生成器对象实现了Iterator接口

配合yield关键词使用。

  • 生成器函数会返回一个生成器对象,调用这个生成器对象的next方法,才会让函数体执行,一旦遇到了yield关键词,函数的执行则会暂停下来,而且yield后面的值将会作为我们的结果返回。next函数的参数作为yield结果返回,如果继续调用函数的next函数,则会在上一次暂停的位置继续执行,直到函数体执行完毕,next返回的对象的done就变成了true。

  • 最大的特点就是惰性执行

function * fn () {
  console.log(111)
  yield 100
  console.log(222)
  yield 200
  console.log(333)
  yield  300
}

const generator = fn()

console.log(generator.next())
// 111
// { value: 100, done: false }
console.log(generator.next())
// 222
// { value: 200, done: false }
console.log(generator.next())
// 333
// { value: 300, done: false }

案例:
实现一个发号器:

// Generator 应用: 发号器

function * createIdMaker () {
  let id = 1
  while(true) {
    yield id++
  }
}
const idMaker = createIdMaker()

console.log(idMaker.next())
console.log(idMaker.next())
console.log(idMaker.next())
console.log(idMaker.next())
console.log(idMaker.next())
// { value: 1, done: false }
// { value: 2, done: false }
// { value: 3, done: false }
// { value: 4, done: false }
// { value: 5, done: false }

案例2:

Generator函数实现迭代器Iterator
const todos = {
  life: ['吃饭', '睡觉', '打豆豆'],
  learn: ['语文', '数学', '英语'],
  work: ['喝茶'],

  // 实现迭代器接口
  [Symbol.iterator]: function * () {
    const all = [...this.life, ...this.learn, ...this.work]
    for (const item of all) {
      yield item
    }
  }
}

for(const item of todos) {
  console.log(item)
}
// 吃饭
// 睡觉
// 打豆豆
// 语文
// 数学
// 英语
// 喝茶

22. Module

ES6 模块与 CommonJS 模块的差异 :

  • CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
  • CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
  • CommonJS 模块的require()是同步加载模块,ES6 模块的import命令是异步加载,有一个独立的模块依赖的解析阶段。
24 ECMAScript 2016
  1. 数组的includes方法
const arr = ['foo', 1, false, NaN]
// 以前使用indexOf, 存在则返回下标,不存在则返回-1, 缺点是无法判断NaN
// includes,则可以判断,存在返回true,不存在返回false
console.log(arr.indexOf('foo')) // 0
console.log(arr.indexOf(1)) // 1
console.log(arr.indexOf(false)) // 2
console.log(arr.indexOf(NaN)) // -1

console.log(arr.includes('foo')) // true
console.log(arr.includes(1)) // true
console.log(arr.includes(false)) // true
console.log(arr.includes(NaN)) // true

  1. 指数运算符:**
// 以前的需要用Math.pow
console.log(Math.pow(2, 10)) // 1024
console.log(2 ** 10) // 1024

25.ES2017 概述

对obj的三个扩展方法

  1. Object.values(obj)
    获取对象所有的值数组
const obj = {
  name: 'jal',
  age: 20
}
// 对象的值组成的数组
console.log(Object.values(obj)) // [ 'jal', 20 ]

  1. Object.entries(obj)
    获取对象的键值数组
// 对象的键值数组, 可以for...of 这个对象了
console.log(Object.entries(obj)) // [ [ 'name', 'jal' ], [ 'age', 20 ] ]
for (const [key, value] of Object.entries(obj)) {
  console.log(key, value)
}
// name jal
// age 20

console.log(new Map(Object.entries(obj))) // Map(2) { 'name' => 'jal', 'age' => 20 }

  1. Object.getOwnPropertyDescriptors(obj)
    获取对象的详细描述
const p1 = {
  firstName: 'Ji',
  lastName: 'Ailing',
  get fullName() {
    return this.firstName + ' '+ this.lastName
  }
}

const p2 = Object.assign({}, p1)
p2.firstName = 'zce'
console.log(p2) // { firstName: 'zce', lastName: 'Ailing', fullName: 'Ji Ailing' }
const descriptors = Object.getOwnPropertyDescriptors(p1)
console.log(descriptors)
/*
{
  firstName: { value: 'Ji', writable: true, enumerable: true, configurable: true },
  lastName: {
    value: 'Ailing',
    writable: true,
    enumerable: true,
    configurable: true
  },
  fullName: {
    get: [Function: get fullName],
    set: undefined,
    enumerable: true,
    configurable: true
  }
}
*/

const p3 = Object.defineProperties({}, descriptors)
p3.firstName = 'zce'
console.log(p3.fullName) // zce Ailing

  1. padEnd/padStart
    用指定字符串填充目标字符串的头部或者尾部,直到达到指定的长度为止。
const books ={
  html:5,
  css: 16,
  javascript: 128
}
for (const [key, value] of Object.entries(books)) {
  console.log(value.toString().padEnd(3, '0'))
}
// 005
// 016
// 128
for (const [key, value] of Object.entries(books)) {
  console.log(value.toString().padEnd(3, '0'))
}
// 500
// 160
// 128
  1. 在函数参数中添加尾逗号
function foo (
 bar, 
 baz,
) {
  
}

const arr = [
  10,
  20,
  30,
]

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值