ES6实用指南

257 阅读3分钟

1、声明

const:声明一个常量,let:声明一个变量;const/let 声明的常量/变量都只能作用于代码块(块级作用域或函数作用域)里;

if (true) {
    let name = '布兰'
}
console.log(name)  // undefined

const/let 不存在变量提升,所以在代码块里必须先声明然后才可以使用,这叫暂时性死区;

let name = 'bubuzou'
if (true) {
    name = '布兰'
    let name
}
console.log(name)

const/let 不允许在同一个作用域内,重复声明;

function setName(name) {
    let name = ''  // SyntaxError
}

const 声明时必须初始化,且后期不能被修改,但如果初始化的是一个对象,那么不能修改的是该对象的内存地址;

const person = {
    name: '布兰'
}
person.name = 'bubuzou'  
console.log(person.name)  // 'bubuzou'
person = ''  // TypeError

const/let 在全局作用域中声明的常量/变量不会挂到顶层对象(浏览器中是 window )的属性中;

var name = '布兰'
let age = 12
console.log(window.name)  // '布兰'
console.log(window.age)  // undefined
varletconst
变量提升××
全局变量××
重复声明××
重新赋值×
暂时死区×
块作用域×
只声明不初始化×

2、解构赋值

  • 字符串解构:

const [a, b, c, d, e] = "hello"

  • 对象解构

    • 形式:const { x, y } = { x: 1, y: 2 }
    • 默认:const { x, y = 2 } = { x: 1 }
    • 改名:const { x, y: z } = { x: 1, y: 2 }
  • 数组解构

    • 规则:数据结构具有Iterator接口可采用数组形式的解构赋值
    • 形式:const [x, y] = [1, 2]
    • 默认:const [x, y = 2] = [1]
  • 函数参数解构

    • 数组解构:function Func([x = 0, y = 1]) {}
    • 对象解构:function Func({ x = 0, y = 1 } = {}) {}

应用场景:

  • 交换变量值:
let x = 1, y = 2;
[x, y] = [y, x]
console.log(x, y)  // 2 1
  • 返回函数多个值:
function Func() {
    return [1, 2, 3];
}
 
const [x, y, z] = Func();
 
console.log(x); // 1
console.log(y); // 2
console.log(z); // 3
  • 定义函数参数:
let person = {
    name: '布兰',
    age: 12
}
init(person)

// 普通用法
function init(person) {
    let {name, age} = person
}

// 更简洁用法
function init({name, age}) {}
  • 提取JSON数据:
let responseData = {
    code: 1000,
    data: {},
    message: 'success'
}

let { code, data = {} } = responseData
  • 定义函数参数默认值:
function initPerson({name = '布兰', age = 12} = {}) {
    console.log(name, age)
}
initPerson()  // '布兰' 12
initPerson({age: 20})  // '布兰' 20
  • 遍历Map结构:
let map = new Map()
map.set('beijing', '北京')
map.set('xiamen', '厦门')

for (let [key, value] of map) {
    console.log(key, value)
}

3、字符串扩展

  • 可以使用 for...of 正确遍历字符串:
let str = '😀🤣😜😍🤗🤔'
for (const emoji of str) {
    console.log(emoji) // 😀🤣😜😍🤗🤔
}
for(let i = 0, l = str.length; i < l; i++) {
    console.log(str[i])  // 不能正确输出表情
}
  • 模板字符串使用两个反引号标识(``),可以用来定义多行字符串,或者使用它在字符串中插入变量:
  • 在ES6中通过${}就可以完成字符串的拼接,只需要将变量放在大括号之中。
let name = 'hero'
let tips = `Hello ${name}, 
    welcome to my world.`
alert( tips )
  • String.raw() 返回把字符串所有变量替换且对斜杠进行转义的结果:
String.raw`Hi\n${2+3}!`  // "Hi\n5!"
  • 字符串是否包含子串:

    • includes():返回布尔值,表示是否找到了参数字符串。
    • startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。
    • endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。
let s = 'Hello world!'

s.includes('o')        // true
s.startsWith('Hello')  // true
s.endsWith('!')        // true

这三个方法都支持第二个参数,表示开始搜索的位置:

let s = 'Hello world!'

s.includes('Hello', 6)   // false
s.startsWith('world', 6) // true
s.endsWith('Hello', 5)   // true

上面代码表示,使用第二个参数 n 时,endsWith 的行为与其他两个方法有所不同。它针对前 n 个字符,而其他两个方法针对从第 n 个位置直到字符串结束。

  • repeat(n) 将当前字符串重复 n 次后,返回一个新字符串:
'x'.repeat(2)         // 'xx'
'x'.repeat(1.9)       // 'x'
'x'.repeat(NaN)       // ''
'x'.repeat(undefined) // ''
'x'.repeat('2a')      // ''
'x'.repeat(-0.6)      // '',解释:0 ~ 1 之间的小数相当于 0
'x'.repeat(-2)        // RangeError
'x'.repeat(Infinity)  // RangeError

4、数值扩展

二进制(0b)和八进制(0o)表示法:

let num = 100
let b = num.toString(2)  // 二进制的1001100100
let o = num.toString(8)  // 八进制的100144
0b1100100 === 100        // true
0o144 === 100            // true

Number.isFinite() 判断一个数是否是有限的数,入参如果不是数值一律返回 false

Number.isFinite(-2.9)      // true
Number.isFinite(NaN)       // false
Number.isFinite('')        // false
Number.isFinite(false)     // true
Number.isFinite(Infinity)  // false

Number.isNaN() 判断一个数值是否为 NaN,如果入参不是 NaN 那结果都是 false

Number.isNaN(NaN)       // true
Number.isFinite('a'/0)  // true
Number.isFinite('NaN')  // false

数值转化:Number.parseInt()Number.parseFloat(),非严格转化,从左到右解析字符串,遇到非数字就停止解析,并且把解析的数字返回:

parseInt('12a')  // 12
parseInt('a12')  // NaN
parseInt('')  // NaN
parseInt('0xA')  // 10,0x开头的将会被当成十六进制数

parseInt() 默认是用十进制去解析字符串的,其实他是支持传入第二个参数的,表示要以多少进制的 基数去解析第一个参数:

parseInt('1010', 2)  // 10
parseInt('ff', 16)  // 255

Number.isInteger() 判断一个数值是否为整数,入参为非数值则一定返回 false

5、对象扩展

  • 对象属性简写:
let name = '布兰'
let person = {
    name,
    getName() {
        return this.name
    }
}
// 等同于
let person1 = {
    name: '布兰',
    getName: function() {
        return this.name
    }
}
  • 属性名表达式:在用对象字面量定义对象的时候,允许通过属性名表达式来定义对象属性:
let name = 'name'; 
let person = {//在对象定义中,使用方括号 [] 表示属性名可以是一个表达式。在这里,name 是一个变量,其值为 'name' 字符串。
    [name]: '布兰',
    ['get'+ name](){
        return this.name
    }
}
  • 方法的 name 属性,存在好几种情况,这里仅列出常见的几种:

情况一:普通对象方法的 name 属性直接返回方法名,函数声明亦是如此,函数表达式返回变量名:

let person = {
    hi(){}
}
person.hi.name  // 'hi'

情况二:构造函数的 nameanonymous

(new Function).name  // 'anonymous'

情况三:绑定函数的 name 将会在函数名前加上 bound

js 代码解读复制代码function foo() {} 
foo.bind({}).name  // 'bound foo'

情况四:如果对象的方法使用了取值函数(getter)和存值函数(setter),则 name 属性不是在该方法上面,而是该方法的属性的描述对象的 getset 属性上面:

let o = { 
    get foo(){}, 
    set foo(x){} 
}
o.foo.name  // TypeError: Cannot read property 'name' of undefined
let descriptor = Object.getOwnPropertyDescriptor(o, "foo") 
descriptor.get.name  // "get foo" 
descriptor.set.name  // "set foo"
  • 属性的可枚举性

对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为。可以通过 Object.getOwnPropertyDescriptor() 来获取对象某个属性的描述:

let person = { name: '布兰', age: 12 }
Object.getOwnPropertyDescriptor(person, 'name')
// {
//     configurable: true,
//     enumerable: true,
//     value: "布兰",
//     writable: true,
// }

这里的 enumerable 就是对象某个属性的可枚举属性,如果某个属性的 enumerable 值为 false 则表示该属性不能被枚举,所以该属性会被如下 4 种操作忽略:

  • for...in :只遍历对象自身的和继承的可枚举的属性;
  • Object.keys():返回对象自身的所有可枚举的属性的键名;
  • JSON.stringify():只串行化对象自身的可枚举的属性;
  • Object.assign(): 只拷贝对象自身的可枚举的属性。
let person = { name: '布兰' }
Object.defineProperty(person, 'age', {
    configurable: true,
    enumerable: false,
    value: 12,
    writable: true
})
person  // { name: '布兰', age: 12 }

// 以下操作都将忽略 person 对象的 age 属性
for (let x in person) {
    console.log(x)         // 'name'
}
Object.keys(person)        // ['name']
JSON.stringify(person)     // '{"name": "布兰"}'
Object.assign({}, person)  // { name: '布兰' }

Reflect.ownKeys(obj): 返回一个数组,包含对象自身的(不含继承的)所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举:

// 基于上面的代码
Reflect.ownKeys(person)  // ['name', 'age']
  • super 关键字,指向对象的原型对象,只能用于对象的方法中,其他地方将报错:
let person = {
    name: '布兰',
    getName() {
        return super.name
    }
}
Object.setPrototypeOf(person, {name: 'hello'})
person.getName()  // 'hello'

// 以下几种 super 的使用将报错
const obj1 = {
    foo: super.foo
}
const obj2 = {
    foo: () => super.foo
}
const obj3 = {
    foo: function () {
        return super.foo
    }
}
  • Object.assign() 用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target),如果有同名属性,则后面的会直接替换前面的:
let target = { a: 1 }
let source1 = { a: 2, b: 3, d: {e: 1, f: 2} }
let source2 = { a: 3, c: 4, d: {g: 3} }
Object.assign(target, source1, source2)
target  // { a: 3, b: 3, c: 4, d: {g: 3} }

Object.assign() 实现的是浅拷贝,如果源对象某个属性是对象,那么拷贝的是这个对象的引用:

let target = {a: {b: 1}}
let source = {a: {b: 2}}
Object.assign(target, source)
target.a.b = 3
source.a.b  // 3
  • __proto__ 属性是用来读取和设置当前对象的原型,而由于其下划线更多的是表面其是一个内部属性,所以建议不在正式场合使用它,而是用下面的 Object.setPrototypeOf()(写操作)、Object.getPrototypeOf()(读操作)、Object.create()(生成操作)代替。
  • Object.setPrototypeOf() 用于设置对象原型,Object.getPrototypeOf() 用于读取对象原型:
let person = {name: '布兰'}
Object.setPrototypeOf(person, {name: '动物'})
Object.getPrototypeOf(person)  // {name: '动物'}

6、数组扩展

  • 数组扩展运算符(...)将数组展开成用逗号分隔的参数序列,只能展开一层数组:
// 应用一:函数传参
Math.max(...[1, 2, 3])  // 3

// 应用二:数组合并
let merge = [...[1, 2], ...[3, 4], 5, 6]  // 1, 2, 3, 4, 5, 6

// 应用三:浅克隆
let a = [1, 2, 3]
let clone = [...a]
a === clone  // false

// 应用四:数组解构
const [x, ...y] = [1, 2, 3]
x  // 1
y  // [2, 3]
  • Array.from() 可以将类数组对象( NodeListarguments)和可迭代对象转成数组:
// 应用一:字符串转数组
Array.from('foo')  // ['f', 'o', 'o']

// 应用二:数组合并去重
let merge = [...[1, 2], ...[2, 3]]
Array.from(new Set(merge))  // ['1', '2', '3']

// 应用三:arguments 转数组
function f() {
    return Array.from(arguments)
}
f(1, 2, 3)  // [1, 2, 3]

如果 Array.from() 带第二个参数 mapFn,将对生成的新数组执行一次 map 操作:

Array.from([1, 2, 3], (x) => x * x )    // [1, 4, 9]
Array.from({length: 3}, (v, i) => ++i)  // [1, 2, 3]
  • Array.of() 将一组参数转成数组:
Array.of(1, 2, 3)  // [1, 2, 3]

// 类似于
function arrayOf(...params){
    return [].slice.call(params)
}
arrayOf(1, 2, 3)  // [1, 2, 3]
  • Array.copyWithin() 在当前数组内部,将制定位置的成员复制到其他位置(会覆盖原来位置的成员),最后返回一个新数组。接收 3 个参数,参数为负数表示右边开始计算:

    • target(必选):替换位置的索引;
    • start(可选):从该位置开始读取数据,默认为 0;
    • end(可选):从该位置结束读取数据(不包括该位置的数据),默认为原数组长度;
[1, 2, 3, 4, 5].copyWithin(-1)         // [1, 2, 3, 4, 1]
[1, 2, 3, 4, 5].copyWithin(1)          // [1, 1, 2, 3, 4]
[1, 2, 3, 4, 5].copyWithin(0, 3, 4)    // [4, 2, 3, 4, 5]
[1, 2, 3, 4, 5].copyWithin(0, -3, -1)  // [3, 4, 3, 4, 5]
  • 查找第一个出现的子成员:find()findIndex()
// 找出第一个偶数
[1, 6, 9].find((val, index, arr) => val % 2 === 0)       // 6

// 找出第一个偶数的索引位置
[1, 6, 9].findIndex((val, index, arr) => val % 2 === 0)  // 1
  • fill() 使用给定的值来填充数组,有 3 个参数:

    • value:填充值;
    • start(可选),开始索引,默认为 0;
    • end(可选):结束索引,默认为数组长度,不包括该索引位置的值;
// 初始化空数组
Array(3).fill(1)  // [1, 1, 1]

[1, 2, 3, 4].fill('a', 2, 4)  // [1, 2, 'a', 'a']
  • 通过 keys()(键名)、entries()(键值)和 values()(键值对) 获取数组迭代器对象,可以被 for...of 迭代,
let arr = ['a', 'b', 'c']
for (let x of arr.keys()) {
    console.log(x)  // 1, 2, 3
}
for (let v of arr.values()) {
    console.log(v)  // 'a' 'b' 'c'
}
for (let e of arr.entries()) {
    console.log(e)  // [0, 'a'] [0, 'b'] [0, 'c']
}
  • 数组空位,是指数组没有值,比如:[,,],而像这种 [undefined] 是不包含空位的。由于 ES6 之前的一些 API 对空位的处理规则很不一致,所以实际操作的时候应该尽量避免空位的出现,而为了改变这个现状,ES6API 会默认将空位处理成 undefined
[...[1, , 3].values()]  // [1, undefined, 3]
[1, , 3].findIndex(x => x === undefined)  // 1

7、正则扩展

  • RegExp 构造函数,允许首参为正则表达式,第二个参数为修饰符,如果有第二个参数,则修饰符以第二个为准:
let reg = new RegExp(/xYz\d+/gi, i)
reg.flags  // 'i'
  • u 修饰符:含义为 Unicode 模式,用来正确处理大于 \uFFFFUnicode 字符。也就是说,如果待匹配的字符串中可能包含有大于 \uFFFF 的字符,就必须加上 u 修饰符,才能正确处理。
// 加上 u 修饰符才能让 . 字符正确识别大于 \uFFFF 的字符
/^.$/.test('🤣')   // false
/^.$/u.test('🤣')  // true

// 大括号 Unicode 字符表示法必须加上 u 修饰符
/\u{61}/.test('a')   // false
/\u{61}/u.test('a')  // true

// 有 u 修饰符,量词才能正确匹配大于 \uFFFF 的字符 
/🤣{2}/.test('🤣🤣')  // false
/🤣{2}/u.test('🤣🤣') // true
  • y 修饰符,与 g 修饰符类似也是全局匹配;不同的是 g 是剩余字符中匹配即可,而 y 则是必须在剩余的第一个字符开始匹配才行,所以 y 修饰符也叫黏连修饰符:
// y修饰符
var s = 'aaa_aa_a';
var r1 = /a+/g;
var r2 = /a+/y;

r1.exec(s) // ["aaa"]
r2.exec(s) // ["aaa"]

r1.exec(s) // ["aa"]
r2.exec(s) // null

8、函数扩展

  • 函数参数默认值。参数不能有同名的,函数体内不能用 letconst 声明同参数名的变量:
function printInfo(name = '布兰', age = 12) {}
  • 剩余(rest) 参数(...变量名)的形式,用于获取函数的剩余参数,注意 rest 参数必须放在最后一个位置,可以很好的代替 arguments 对象:
function f(x, ...y) {
    console.log(x)  // 1
    for (let val of y) {
        coonsole.log(val)  // 2 3
    }
}
f(1, 2, 3)
  • 箭头函数语法比函数表达式更简洁,并且没有自己的 thisarguments,不能用作构造函数和用作生成器。

几种箭头函数写法:

let f1 = () => {}               // 没有参数
let f2 = (x) => {}              // 1个参数
let f3 = x => {}                // 1个参数可以省略圆括号
let f4 = (x, y) => {}           // 2个参数以上必须加上圆括号
let f5 = (x = 1, y = 2) => {}   // 支持参数默认值
let f6 = (x, ...y) => {}        // 支持 rest 参数
let f7 = ({x = 1, y = 2} = {})  // 支持参数解构

箭头函数没有自己的 this

function Person(){
    this.age = 0
    setInterval(() => {
        this.age++
    }, 1000)
}
var p = new Person()  // 1 秒后 Person {age: 1}

通过 call/apply 调用箭头函数的时候将不会绑定第一个参数的作用域:

let adder = {
    base: 1,
    add: function(a) {
        let f = v => v + this.base
        return f(a)
    }, 
    addThruCall: function(a) {
        let f = v => v + this.base
        let b = {
            base: 2
        } 
        return f.call(b, a)
    }
}
adder.add(1)          // 输出 2
adder.addThruCall(1)  // 仍然输出 2

箭头函数没有自己的 arguments 对象,不过可以使用 rest 参数代替:

let log = () => {
    console.log(arguments)  // ReferenceError
}
log(2, 3)

// 剩余参数代替写法
let restLog = (...arr) => {
    console.log(arr)  // [2, 3]
}
restLog(2, 3)

箭头函数不能用作构造器,和 new 一起用会抛出错误:

let Foo = () => {}
let foo = new Foo()
// TypeError: Foo is not a constructor

箭头函数返回对象字面量,需要用圆括号包起来:

let func2 = () => ({foo: 1})

9、Symbol

symbol 是一种基本数据类型,Symbol()函数会返回symbol类型的值,该类型具有静态属性和静态方法。它的静态属性会暴露几个内建的成员对象;它的静态方法会暴露全局的symbol注册,且类似于内建对象类,但作为构造函数来说它并不完整,因为它不支持语法:"new Symbol()"。

每个从Symbol()返回的symbol值都是唯一的。一个symbol值能作为对象属性的标识符;这是该数据类型仅有的目的。

例子如下:

const symbol1 = Symbol();
const symbol2 = Symbol(42);
const symbol3 = Symbol('foo');

console.log(typeof symbol1); // "symbol"
console.log(symbol3.toString()); // "Symbol(foo)"
console.log(Symbol('foo') === Symbol('foo')); // false

10、Set/WeakSet

Set 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用。

所以我们可以通过Set实现数组去重

const numbers = [2,3,4,4,2,3,3,4,4,5,5,6,6,7,5,32,3,4,5]
console.log([...new Set(numbers)]) 
// [2, 3, 4, 5, 6, 7, 32]

WeakSet 结构与 Set 类似,但区别有以下两点:

  • WeakSet 对象中只能存放对象引用, 不能存放值, 而 Set 对象都可以。
  • WeakSet 对象中存储的对象值都是被弱引用的, 如果没有其他的变量或属性引用这个对象值, 则这个对象值会被当成垃圾回收掉. 正因为这样, WeakSet 对象是无法被枚举的, 没有办法拿到它包含的所有元素。

所以代码如下:

var ws = new WeakSet()
var obj = {}
var foo = {}

ws.add(window)
ws.add(obj)

ws.has(window) // true
ws.has(foo)    // false, 对象 foo 并没有被添加进 ws 中 

ws.delete(window) // 从集合中删除 window 对象
ws.has(window)    // false, window 对象已经被删除了

ws.clear() // 清空整个 WeakSet 对象

11、Map/WeakMap

Map 对象保存键值对。任何值(对象或者原始值) 都可以作为一个键或一个值。

例子如下,我们甚至可以使用NaN来作为键值:

var myMap = new Map();
myMap.set(NaN, "not a number");

myMap.get(NaN); // "not a number"

var otherNaN = Number("foo");
myMap.get(otherNaN); // "not a number"

WeakMap 对象是一组键/值对的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意的。

Map的区别与SetWeakSet的区别相似,具体代码如下:

var wm1 = new WeakMap(),
    wm2 = new WeakMap(),
    wm3 = new WeakMap();
var o1 = {},
    o2 = function(){},
    o3 = window;

wm1.set(o1, 37);
wm1.set(o2, "azerty");
wm2.set(o1, o2); // value可以是任意值,包括一个对象
wm2.set(o3, undefined);
wm2.set(wm1, wm2); // 键和值可以是任意对象,甚至另外一个WeakMap对象
wm1.get(o2); // "azerty"
wm2.get(o2); // undefined,wm2中没有o2这个键
wm2.get(o3); // undefined,值就是undefined

wm1.has(o2); // true
wm2.has(o2); // false
wm2.has(o3); // true (即使值是undefined)

wm3.set(o1, 37);
wm3.get(o1); // 37
wm3.clear();
wm3.get(o1); // undefined,wm3已被清空
wm1.has(o1);   // true
wm1.delete(o1);
wm1.has(o1);   // false

12、Proxy/Reflect

Proxy 对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)。

Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与 Proxy 的方法相同。Reflect不是一个函数对象,因此它是不可构造的。

ProxyReflect是非常完美的配合,例子如下:

const observe = (data, callback) => {
      return new Proxy(data, {
            get(target, key) {
                return Reflect.get(target, key)
            },
            set(target, key, value, proxy) {
                  callback(key, value);
                  target[key] = value;
                    return Reflect.set(target, key, value, proxy)
            }
      })
}

const FooBar = { open: false };
const FooBarObserver = observe(FooBar, (property, value) => {
  property === 'open' && value 
          ? console.log('FooBar is open!!!') 
          : console.log('keep waiting');
});
console.log(FooBarObserver.open) // false
FooBarObserver.open = true // FooBar is open!!!

根据您的要求,我将继续补充和完善文档《第2周-ES实用指南(一)》的内容。以下是续写部分:

13、Class 类

但是在ES6之后,我们只需要写成以下形式,不必使用传统的方法写一个构造函数

class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }

    sayHello() {
        console.log(`Hello, my name is ${this.name}`);
    }
}

const bran = new Person('布兰', 12);
bran.sayHello(); // Hello, my name is 布兰

14、Promise

Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件更合理和更强大。

const promise = new Promise((resolve, reject) => {
    // 异步操作
    if (/* 操作成功 */) {
        resolve('Success');
    } else {
        reject('Error');
    }
});
promise.then(result => {
    console.log(result); // Success
}).catch(error => {
    console.error(error); // Error
});

15、模块化

ES6 引入了模块化的概念,使得代码组织更加清晰。

export function add(a, b) {
    return a + b;
}

export const PI = 3.14;
// main.js
import { add, PI } from './math.js';

console.log(add(2, 3)); // 5
console.log(PI); // 3.14

16、迭代器和生成器

迭代器是一种接口,为各种不同的数据结构提供统一的访问机制。

const iterable = {
    [Symbol.iterator]() {
        let step = 0;
        const iterator = {
            next() {
                step++;
                if (step === 1) {
                    return { value: 'hello', done: false };
                } else if (step === 2) {
                    return { value: 'world', done: false };
                } else {
                    return { value: undefined, done: true };
                }
            }
        };
        return iterator;
    }
};

for (let value of iterable) {
    console.log(value); // hello world
}

生成器函数使用 function* 定义,内部可以使用 yield 关键字来暂停和恢复执行。

function* generator() {
    yield 1;
    yield 2;
    yield 3;
}

const gen = generator();
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
console.log(gen.next().value); // 3