es6

ECMAScript2015(es6)

1、项目构建

1.1、基础架构

  1. 业务逻辑:
    • 页面
    • 交互
  2. 自动构建:babel、webpack
    • 编译
    • 辅助:
      • 自动刷新
      • 文件合并
      • 资源压缩
  3. 服务接口
    • 数据
    • 接口

1.2、项目创建

创建es6文件夹:

  1. app放置前端代码

    • css
    • js
      • class:类文件夹
      • index.js:入口文件
    • views:express框架使用ejs引擎所以后缀不是html而是ejs
      • index.ejs:入口文件
      • error.ejs:错误文件
  2. server放置服务器代码

    1. 启动脚手架:

      // express:脚手架启动命令
      // -e 使用ejs引擎
      // . 在当前目录执行
      express -e . 
      
    2. 初始化:npm i

  3. tasks放置工具

    • utils:常用脚本
  4. .babelrc:babel配置文件

  5. gulpfile.babel.js:gulp配置文件,因为项目使用es6,需要使用babel对es6代码进行编译,所以创建gulpfile.babel.js文件,而不是创建官网推荐的gulpfile.js文件

1.3、命令行处理、创建js编译任务

  1. 新建tasks/utils/args.js:对命令行参数进行解析的构建脚本

    import yargs from 'yargs'
    
    
    const args = yargs
        // 区分命令行有没有这个参数,用于区分是线上环境还是开发环境
        .option('production', {
            // 这个选项是一个布尔值
            boolean: true,
            // 默认值为false,也就是如果没有production这个参数,默认为开发环境
            default: false,
            // 描述,机器不识别
            describe: 'min all scripts'
        })
        // 监听开发环境中修改文件后,需不需要自动编译
        .option('watch', {
            boolean: true,
            default: false,
            describe: 'watch all files'
        })
        // 需不需要命令行详细输出目录日志
        .option('verbose', {
            boolean: true,
            default: false,
            describe: 'watch all files'
        })
        .option('sourcemaps', {
            describe: 'force the creation of sourcemaps'
        })
        // 设置端口
        .option('port', {
            string: true,
            default: 8080,
            describe: 'server port'
        })
        // 对输入的命令行,以字符串进行解析
        .argv
    
    export default args
    
  2. 新建tasks/scripts.js:对js进行处理的构建脚本

    import gulp from 'gulp'
    // gulp中对语句进行if判断
    import gulpif from 'gulp-if'
    // gulp中对文件进行拼接
    import concat from 'gulp-concat'
    // 打包
    import webpack from 'webpack'
    // gulp处理的都是一些文件流,他是基于stream,所以对webpack的处理要结合webpack-stream
    import gulpWebpack from 'webpack-stream'
    // 对文件重命名做标志
    import named from 'vinyl-named'
    // 文件修改后,对浏览器自动刷新
    import livereload from 'gulp-livereload'
    // 处理文件信息流
    import plumber from 'gulp-plumber'
    // 对文件重命名
    import rename from 'gulp-rename'
    // 处理js压缩和css压缩
    import uglify from 'gulp-uglify'
    // 在命令行进行输出
    import { log, colors } from 'gulp-util'
    // 对命令行参数进行解析
    import args from './utils/args'
    
    // 创建一个脚本编译的任务
    gulp.task('scripts', () => {
        // 打开app/js/index.js
        return gulp.src(['app/js/index.js'])
        // 处理常规的错误逻辑,每一个pipe的时候,出现错误都要抛出异常
        .pipe(plumber({
            // 集中处理错误,改正默认处理错误的机制
            errorHandle(){
    
            }
        }))
        // 对文件重新命名
        .pipe(named())
        // 对js进行编译
        .pipe(gulpWebpack({
            module:{
                loaders:[{
                    test:/\.js$/,
                    loader:'babel'
                }]
            }
        }), null, (err,states) => {
            // 处理错误
            log(`Finished '${colors.cyan('scripts')}'`, states.toString({
                chunks:false
            }));
        })
        // 编译好的文件放入server/public/js
        .pipe(gulp.dest('server/public/js'))
        // 对编译好的文件文件进行重命名
        .pipe(rename({
            basename:'cp',
            extname:'.min.js'
        }))
        // 对编译好的文件文件进行压缩
        .pipe(uglify({compress:{properties:false}, output:{'quote_keys':true}}))
        // 压缩后的文件放入server/public/js
        .pipe(gulp.dest('server/public/js'))
        // 监听文件变化后自动刷新
        .pipe(gulpif(args.watch, livereload()))
    })
    
  3. 初始化:

    • 初始化pages.json:npm init

    • 安装需要使用包,修改pages.json中代码,然后直接安装:npm i

      {
        "name": "es6",
        "version": "1.0.0",
        "description": "",
        "main": "gulpfile.babel.js",
        "devDependencies": {
          "babel-core": "^6.24.0",
          "babel-loader": "^6.4.1",
          "babel-plugin-transform-decorators-legacy": "^1.3.4",
          "babel-polyfill": "^6.23.0",
          "babel-preset-env": "^1.2.2",
          "babel-preset-es2015": "^6.24.0",
          "babel-register": "^6.26.0",
          "connect-livereload": "^0.6.0",
          "del": "^2.2.2",
          "gulp": "^3.9.1",
          "gulp-concat": "^2.6.1",
          "gulp-if": "^2.0.2",
          "gulp-live-server": "0.0.30",
          "gulp-livereload": "^3.8.1",
          "gulp-plumber": "^1.1.0",
          "gulp-rename": "^1.2.2",
          "gulp-sequence": "^0.4.6",
          "gulp-uglify": "^2.1.0",
          "gulp-util": "^3.0.8",
          "require-dir": "^0.3.2",
          "vinyl-named": "^1.1.0",
          "webpack": "^2.2.1",
          "webpack-stream": "^3.2.0",
          "yargs": "^7.0.2"
        },
        "scripts": {
          "test": "echo \"Error: no test specified\" && exit 1"
        },
        "author": "",
        "license": "ISC"
      }
      

1.4、创建模板、服务任务脚本

  1. 新建tasks/pages.js:

    import gulp from 'gulp'
    // gulp中对语句进行if判断
    import gulpif from 'gulp-if'
    // 文件修改后,对浏览器自动刷新
    import livereload from 'gulp-livereload'
    // 对命令行参数进行解析
    import args from './utils/args'
    
    // 创建一个脚本编译的任务
    gulp.task('pages', () => {
        // 打开app下所有ejs文件
        return gulp.src(['app/**/*.ejs'])
        // 将模板文件拷贝到server文件中
        .pipe(gulp.dest('server'))
        // 监听文件变化后自动刷新
        .pipe(gulpif(args.watch, livereload()))
    })
    
  2. 新建tasks/css.js:

    import gulp from 'gulp'
    // gulp中对语句进行if判断
    import gulpif from 'gulp-if'
    // 文件修改后,对浏览器自动刷新
    import livereload from 'gulp-livereload'
    // 对命令行参数进行解析
    import args from './utils/args'
    
    // 创建一个脚本编译的任务
    gulp.task('css', () => {
        // 打开app下所有css文件
        return gulp.src(['app/**/*.css'])
        // 将模板文件拷贝到server/public文件中
        .pipe(gulp.dest('server/public'))
        // 监听文件变化后自动刷新
        .pipe(gulpif(args.watch, livereload()))
    })
    
  3. 新建tasks/server.js:

    import gulp from 'gulp'
    // gulp中对语句进行if判断
    import gulpif from 'gulp-if'
    // 启动服务器
    import liveserver from 'gulp-live-server'
    // 对命令行参数进行解析
    import args from './utils/args'
    
    // 创建一个脚本编译的任务
    gulp.task('server', (cb) => {
        // 如果不是处于监听状态下,直接返回回调函数
        if(!args.watch) return cb()
        
        // 如果是处于监听状态下,创建一个服务器
        // --harmony:在当前目录行下,执行后面的脚本
        // server/bin/www:脚本
        var server = liveserver.new(['--harmony', 'server/bin/www'])
        // 启动服务器
        server.start()
    
        // 监听server/public目录下的所有js文件和server/views目录下的所有ejs文件的改变
        gulp.watch(['server/public/**/*.js','server/views/**/*.ejs'], function(file){
            server.notify.apply(server, [file])
        })
    
        // 监听需要重启服务才能生效的文件
        gulp.watch(['server/routes/**/*.js', 'server/app.js'], function(){
            // 重启服务
            server.start.bind(server())
        })
    })
    

1.5、文件自动监听

  1. 新建tasks/browser.js:

    import gulp from 'gulp'
    // gulp中对语句进行if判断
    import gulpif from 'gulp-if'
    // gulp常用的工具函数集合
    import gutil from 'gulp-util'
    // 对命令行参数进行解析
    import args from './utils/args'
    
    // 创建一个脚本编译的任务
    gulp.task('browser', (cb) => {
        // 如果不是处于监听状态下,直接返回回调函数
        if(!args.watch) return cb()
    
        // app下所有js文件发生改变时,执行scripts.js脚本
        gulp.watch('app/**/*.js', ['scripts'])
        
        // app下所有ejs文件发生改变时,执行pages.js脚本
        gulp.watch('app/**/*.ejs', ['pages'])
    
        // app下所有css文件发生改变时,执行css.js脚本
        gulp.watch('app/**/*.css', ['css'])
    })
    
  2. 新建tasks/clean.js:清空指定文件

    import gulp from 'gulp'
    // 删除
    import del from 'del'
    // 对命令行参数进行解析
    import args from './utils/args'
    
    // 创建一个脚本编译的任务
    gulp.task('clean', (cb) => {
        return del(['server/public', 'server/views'])
    })
    
  3. 新建tasks/build.js:关联所有任务

    import gulp from 'gulp'
    
    import gulpSequence from 'gulp-sequence'
    
    // 指定顺序执行
    gulp.task('build', gulpSequence('clean', 'css', 'pages', 'scripts', ['browser', 'server']))
    
  4. 新建tasks/default.js:命令行执行gulp命令时,默认执行的文件

    import gulp from 'gulp'
    
    // 指定顺序执行
    gulp.task('default', ['build'])
    
  5. 修改gulpfile.babel.js:

    import requireDir from 'require-dir'
    
    // 引入tasks目录下的脚本
    requireDir('./tasks')
    
  6. 修改.babelrc:

    {
        "presets": ["es2015"]
    }
    
  7. 如果执行命令 gulp --watch 报错 primordials is not defined:

    • 删除node_modules

    • 在package.json的同级目录中创建npm-shrinkwrap.json

    • 在创建的npm-shrinkwrap.json中复制以下内容:

      {
         "dependencies": {
             "graceful-fs": {
                 "version": "4.2.2"
             }
         }
      }
      
  8. 报错:Failed to lookup view “error” in views directory

    • 修改server/app.js:

      var createError = require('http-errors');
      var express = require('express');
      var path = require('path');
      var cookieParser = require('cookie-parser');
      var logger = require('morgan');
      
      var indexRouter = require('./routes/index');
      var usersRouter = require('./routes/users');
      
      var app = express();
      
      // view engine setup
      app.set('views', path.join(__dirname, 'views'));
      app.set('view engine', 'ejs');
      
      app.use(logger('dev'));
      app.use(express.json());
      app.use(express.urlencoded({ extended: false }));
      app.use(cookieParser());
      app.use(express.static(path.join(__dirname, 'public')));
      app.use(require('connect-livereload')())
      
      app.use('/', indexRouter);
      app.use('/users', usersRouter);
      
      // catch 404 and forward to error handler
      app.use(function(req, res, next) {
        next(createError(404));
      });
      
      // error handler
      app.use(function(err, req, res, next) {
        // set locals, only providing error in development
        res.locals.message = err.message;
        res.locals.error = req.app.get('env') === 'development' ? err : {};
      
        // render the error page
        res.status(err.status || 500);
        res.render('error');
      });
      
      module.exports = app;
      
    • 新增app/views/error.ejs

2、es6语法

2.1、let 和 const

  1. 相同点:
    • 只在声明所在的块级作用域内有效。
    • 变量不会提升,同时存在暂时性死区,只能在声明的位置后面使用。
    • 不可重复声明。
  2. 不同点:
    • let声明的变量可以改变,值和类型都可以改变;const声明的常量不可以改变,这意味着,const一旦声明,就必须立即初始化,不能以后再赋值。

2.2、解构赋值

  1. 数组解构赋值

    • 代码演示:

      // 新建一个块级作用域:数组解构赋值
      {
          let a, b, rest;
          [a, b] = [1, 2] //a=1,b=2
          console.log(a, b); //1 2
      }
      
      // 新建一个块级作用域:数组解构赋值的默认值
      {
          let a, b, rest;
          [a, b, rest=3] = [1, 2] //a=1,b=2,rest=3
          // 如果不写默认值,rest打印为undefined
          console.log(a, b, rest); //1 2 3
          [a, b, rest=3] = [1, 2, 4] //a=1,b=2,rest=4
          console.log(a, b, rest); //1 2 4
      }
      
      // 新建一个块级作用域:数组解构赋值 + 展开运算符
      {
          let a, b, rest;
          // 注意:展开运算符只能放在最后
          [a, b, ...rest] = [1, 2, 3, 4, 5, 6, 7]
          console.log(a, b, rest); //1 2 [3, 4, 5, 6, 7]
      }
      
    • 使用场景:

      1. 变量交换:

        let a = 1;
        let b = 2;
        [a,b] = [b,a]
        console.log(a, b) //2 1
        
      2. 函数返回值取值:

        {
            function f(){
        	    return [1,2]
            }
            let a,b
            [a,b] = f()
            console.log(a, b) //1 2
        }
        
        // 取返回值数组中指定的值
        {
            function f(){
                return [1,2,3,4,5,6,7]
            }
            let a,b
            [a,,,b] = f()
            console.log(a, b) //1 4
        }
        
        // 配合展开运算符使用
        {
            function f(){
                return [1,2,3,4,5,6,7]
            }
            let a,b
            [,,,a,...b] = f()
            console.log(a, b) //4 [5,6,7]
        }
        
  2. 对象解构赋值

    • 代码演示:

      // 新建一个块级作用域:对象解构赋值
      {	
          // 声明和赋值不分开写,可以不要括号
          // let { a, b } = {a: 1, b: 2})
          
          // 声明和赋值分开写,需要加括号
          let a, b;
          ({a, b} = {a: 1, b: 2})
          console.log(a, b); //1 2
      }
      
      // 新建一个块级作用域:对象解构赋值的默认值
      {	
          // 声明和赋值不分开写,可以不要括号
          // let { a, b } = {a: 1, b: 2})
          
          // 声明和赋值分开写,需要加括号
          let { a = 1, b = 3 } = {a: 2}
          console.log(a, b); //2 3
      }
      
    • 使用场景:

      1. 嵌套对象使用自定义变量名取值

        {
            let obj = {
                title: 'obj-text',
                arr: [
                    {
                        title: 'arr-text'
                    }
                ]
            }
            
            let { title: obj_title, arr: [{title: arr_title}] } = obj
            console.log(obj_title, arr_title) //obj-text arr-text
        }
        

2.3、正则扩展

es5和es6正则写法以及使用方式:

// i 修饰符
{
    // es5
    // 匹配所有 xyz 字符,i:忽略大小写
    // 第一种写法
    let regex = new RegExp('xyz','i')
    // 第二种写法
    let regex2 = new RegExp(/xyz/i) //es5中直接传入表达式,只能传一个参数
    // test:一个在字符串中测试是否匹配的RegExp方法,它返回 true 或 false
    console.log(regex.test('xyz123cjijXYz'), regex2.test('1561551XYz')) // true true

    //es6中直接传入表达式,可以传两个参数
    //第二参数传入的修饰符 i 会覆盖第一个参数的 i g 修饰符
    let regex3 = new RegExp(/xyz/ig, 'i') 
    //flags:es6新增的属性,用来获取正则表达式修饰符
    console.log(regex3.flags) // i
}

// g 和 y 修饰符
{
    // g 和 y 都是全局匹配 a+ 字符,也就是匹配一个或多个 a 字符
    let text = 'a_aa_aaa_aaaa'
    // g 修饰符:全局搜索,在第一次调用之后,第二次调用不会从头开始匹配,而是从第一次调用匹配到的位置后面开始匹配
    // 也就是说,第一次调用,从索引为 0 的位置匹配到了 a ,第二次调用就会从索引为 1 开始匹配直到匹配到 aa ,第三次就会从索引 4 开始匹配直到匹配到 aaa ,以此类推...
    let a1 = /a+/g
    // y 修饰符:执行“粘性( sticky )”搜索,匹配从目标字符串的当前位置开始
    let a2 = /a+/y
    
    // exec:一个在字符串中执行查找匹配的RegExp方法,它返回一个数组(未匹配到则返回 null)
    console.log('第一次调用', a1.exec(text), a2.exec(text))
    // ['a', index: 0, input: 'a_aa_aaa_aaaa', groups: undefined] ['a', index: 0, input: 'a_aa_aaa_aaaa', groups: undefined]
    console.log('第二次调用', a1.exec(text), a2.exec(text))
    // ['aa', index: 2, input: 'a_aa_aaa_aaaa', groups: undefined] null
    
    // sticky:检查正则表达式中是否开启了 y 修饰符
    console.log(a1.sticky, a2.sticky)
}

2.4、字符串扩展

  1. repeat

    repeat 方法返回新的字符串,表示字符串复制指定次数。

    let str="Hello World";
    
    let str2 = str.repeat(3)
    console.log(str2); //Hello WorldHello WorldHello World
    
  2. includes、startsWith、endsWith

    • includes:返回布尔值,判断是否找到参数字符串。

    • startsWith:返回布尔值,判断参数字符串是否在原字符串的头部。

    • endsWith:返回布尔值,判断参数字符串是否在原字符串的尾部。

    • 以上三个方法都可以接受两个参数,需要搜索的字符串,和可选的搜索起始位置索引

      let string = "apple,banana,orange";
      string.includes("banana");     // true
      string.startsWith("apple");    // true
      string.endsWith("apple");      // false
      string.startsWith("banana",6)  // true
      
  3. padStart、padEnd

    • padStart:返回新的字符串,表示用参数字符串从头部(左侧)补全原字符串。

    • padEnd:返回新的字符串,表示用参数字符串从尾部(右侧)补全原字符串。

      let str = '1'
      let str2 = str.padStart(3,'0')
      let str3 = str.padEnd(3,'0')
      console.log(str2); // 001
      console.log(str3); // 100
      

2.5、数组扩展

  1. Array.of

    将一组数据变量,转换成数组。如果不传参数,将返回一个空数组

    let arr = Array.of(1,2,3,4,5,'6')
    console.log(arr); // [1, 2, 3, 4, 5, '6']
    
  2. Array.from

    将类数组对象或可迭代对象转化为数组

    1. 基本使用:

      // 参数为数组,返回与原数组一样的数组
      console.log(Array.from([1, 2])); // [1, 2]
       
      // 参数含空位
      console.log(Array.from([1, , 3])); // [1, undefined, 3]
      
    2. 类数组对象

      let arr = Array.from({
        0: '1',
        1: '2',
        2: 3,
        length: 3
      });
      console.log(arr); // ['1', '2', 3]
      
    3. 可迭代对象

      • 转换map

        let map = new Map();
        map.set('key0', 'value0'); // Map(1) {'key0' => 'value0'}    
        map.set('key1', 'value1'); // Map(2) {'key0' => 'value0', 'key1' => 'value1'}
        console.log(Array.from(map)); // [['key0', 'value0'],['key1', 'value1']]
        
      • 转换 set

        let arr = [1, 2, 3];
        let set = new Set(arr); // Set(3) {1, 2, 3}
        console.log(Array.from(set)); // [1, 2, 3]
        
      • 转换字符串

        let str = 'abc';
        console.log(Array.from(str)); // ["a", "b", "c"]
        
    4. 参数:Array.from(arrayLike[, mapFn[, thisArg]])

      • arrayLike:想要转换的类数组对象或可迭代对象

        console.log(Array.from([1, 2, 3])); // [1, 2, 3]
        
      • mapFn:可选,map 函数,用于对每个元素进行处理,放入数组的是处理后的元素

        console.log(Array.from([1, 3, 5], (item) => item * 2)); // [2, 6, 10]
        
      • thisArg:可选,用于指定 map 函数执行时的 this 对象

        let map = {
            do: function(item) {
                return item * 2;
            }
        }
        let arrayLike = [1, 2, 3];
        console.log(Array.from(arrayLike, function (item){
            return this.do(item);
        }, map)); // [2, 4, 6]
        
  3. find

    查找数组中符合条件的元素,若有多个符合条件的元素,则返回第一个元素。没有则返回 undefined

    let arr = Array.of(1, 2, 3, 4);
    console.log(arr.find(item => item > 2)); // 3
    
    let arr2 = [, 1]
    // 数组空位处理为 undefined
    console.log(arr2.find(item => true)); // undefined
    
  4. findIndex

    查找数组中符合条件的元素索引,若有多个符合条件的元素,则返回第一个元素索引。没有则返回 -1

    let arr = Array.of(1, 2, 1, 3);
    // 参数1:回调函数
    console.log(arr.findIndex(item => item == 2)); // 1
    
    // 参数1:回调函数
    // 参数2(可选):指定回调函数中的 this 值
    let map = {
        do: function (item){
            return item == 2
        }
    }
    
    let index = arr.findIndex(function(item){
        return this.do(item)
    }, map)
    
    console.log(index); // 1
    
    let arr2 = [, 1]
    // 数组空位处理为 undefined
    console.log(arr2.findIndex(item => true)); //0
    
  5. includes

    数组是否包含指定值。

    // 参数1:包含的指定值
    [1, 2, 3].includes(1);    // true
     
    // 参数2:可选,搜索的起始索引,默认为0
    // 从索引为2开始查找 1
    [1, 2, 3].includes(1, 2); // false
     
    // NaN 的包含判断
    [1, NaN, 3].includes(NaN); // true
    
  6. flat

    嵌套数组转一维数组

    console.log([1 ,[2, 3]].flat()); // [1, 2, 3]
     
    // 指定转换的嵌套层数
    console.log([1, [2, [3, [4, 5]]]].flat(2)); // [1, 2, 3, [4, 5]]
     
    // 不管嵌套多少层
    console.log([1, [2, [3, [4, 5]]]].flat(Infinity)); // [1, 2, 3, 4, 5]
     
    // 自动跳过空位
    console.log([1, [2, , 3]].flat()); // [1, 2, 3]
    

2.6、函数扩展

  1. 默认参数

    function fn(name,age=17){
     console.log(name+","+age);
    }
    fn("Amy",18);  // Amy,18
    fn("Amy","");  // Amy,
    fn("Amy");     // Amy,17
    
    // 只有在未传递参数,或者参数为 undefined 时,才会使用默认参数,null 值被认为是有效的值传递。
    fn("Amy",null); // Amy,null
    
  2. 不定参数

    不定参数用来表示不确定参数个数,形如,…变量名,由…加上一个具名参数标识符组成。具名参数只能放在参数组的最后,并且有且只有一个不定参数。

    function f(...values){
        console.log(values.length);
    }
    f(1,2);      //2
    f(1,2,3,4);  //4
    
  3. 箭头函数

    • 基本用法:

      let f = v => v;
      // 等价于
      // let f = function(a){
      //  return a;
      // }
      f(1);  //1
      
    • 当箭头函数没有参数或者有多个参数,要用 () 括起来。

      var f = (a,b) => a+b;
      f(6,2);  //8
      
    • 当箭头函数函数体有多行语句,用 {} 包裹起来,表示代码块,当只有一行语句,并且需要返回结果时,可以省略 {} , 结果会自动返回。

      var f = (a,b) => {
       let result = a+b;
       return result;
      }
      f(6,2);  // 8
      
    • 当箭头函数要返回对象的时候,为了区分于代码块,要用 () 将对象包裹起来

      // 报错
      var f = (id,name) => {id: id, name: name};
      f(6,2);  // SyntaxError: Unexpected token :
       
      // 不报错
      var f = (id,name) => ({id: id, name: name});
      f(6,2);  // {id: 6, name: 2}
      

2.7、对象扩展

  1. 对象字面量

    • 属性的简洁表示法

      const age = 12;
      const name = "Amy";
      const person = {age, name}; // 等同于 const person = {age: age, name: name}
      console.log(person) //{age: 12, name: "Amy"}
      
    • 方法名简写

      const person = {
        sayHi(){
          console.log("Hi");
        }
      }
      person.sayHi();  //"Hi"
      //等同于
      const person = {
        sayHi:function(){
          console.log("Hi");
        }
      }
      person.sayHi();//"Hi"
      
  2. 对象的拓展运算符

    • 基本用法

      let person = {name: "Amy", age: 15};
      let someone = { ...person };
      console.log(someone)  //{name: "Amy", age: 15}
      
    • 可用于合并两个对象

      let age = {age: 15};
      let name = {name: "Amy"};
      let person = {...age, ...name};
      console.log(person)  //{age: 15, name: "Amy"}
      
    • 注意点

      自定义的属性和拓展运算符对象里面属性的相同的时候:自定义的属性在拓展运算符后面,则拓展运算符对象内部同名的属性将被覆盖

      let person = {name: "Amy", age: 15};
      let someone = { ...person, name: "Mike", age: 17};
      console.log(someone);  //{name: "Mike", age: 17}
      

      自定义的属性在拓展运算度前面,则变成设置新对象默认属性值。

      let person = {name: "Amy", age: 15};
      let someone = {name: "Mike", age: 17, ...person};
      console.log(someone);  //{name: "Amy", age: 15}
      

      拓展运算符后面是空对象,没有任何效果也不会报错。

      let a = {...{}, a: 1, b: 2};
      console.log(a);  //{a: 1, b: 2}
      

      拓展运算符后面是null或者undefined,没有效果也不会报错

      let b = {...null, ...undefined, a: 1, b: 2};
      console.log(b);  //{a: 1, b: 2}
      
  3. 对象的新方法

    • Object.assign(target, source_1, ···)

      用于将源对象的所有可枚举属性复制到目标对象中。

      基本用法:

      let target = {a: 1};
      let object2 = {b: 2};
      let object3 = {c: 3};
      Object.assign(target,object2,object3);  
      // 第一个参数是目标对象,后面的参数是源对象
      console.log(target);  // {a: 1, b: 2, c: 3
      

      注意点

      • assign 的属性拷贝是浅拷贝:

        let sourceObj = { a: { b: 1}};
        let targetObj = {c: 3};
        Object.assign(targetObj, sourceObj);
        targetObj.a.b = 2;
        console.log(sourceObj.a.b);  // 2
        
      • 同名属性替换

        let targetObj = { a: { b: 1, c:2}};
        let sourceObj = { a: { b: "hh"}};
        Object.assign(targetObj, sourceObj);
        console.log(targetObj);  // {a: {b: "hh"}}
        
      • 数组的处理

        // 会将数组处理成对象,所以先将 [2,3] 转为 {0:2,1:3} ,然后再进行属性复制,所以源对象的 0 号属性覆盖了目标对象的 0。
        Object.assign([2,3], [5]);  // [5,3]
        
    • Object.is(value1, value2)

      用来比较两个值是否严格相等,与(===)基本类似。

      基本用法:

      Object.is("q","q");      // true
      Object.is(1,1);          // true
      Object.is([1],[1]);      // false
      Object.is({q:1},{q:1});  // false
      

      与(===)的区别

      //第一点:+0不等于-0
      Object.is(+0,-0);  //false
      +0 === -0  //true
      //第二点:NaN等于本身
      Object.is(NaN,NaN); //true
      NaN === NaN  //false
      

2.8、Symbol

ES6 引入了一种新的原始数据类型 Symbol ,表示独一无二的值,最大的用法是用来定义对象的唯一属性名。

  1. 基本用法:

    Symbol 函数栈不能用 new 命令,因为 Symbol 是原始数据类型,不是对象。可以接受一个字符串作为参数,为新创建的 Symbol 提供描述,用来显示在控制台或者作为字符串的时候使用,便于区分。

    let sy = Symbol("KK");
    console.log(sy); // Symbol(KK)
    console.log(typeof sy) // "symbol"
    
    // 相同参数 Symbol() 返回的值不相等
    let sy1 = Symbol("kk");
    console.log(sy === sy1); // false
    
  2. 使用场景

    • 作为属性名

      由于每一个 Symbol 的值都是不相等的,所以 Symbol 作为对象的属性名,可以保证属性不重名。

      let sy = Symbol("key1");
       
      // 写法1
      let syObject1 = {};
      syObject1[sy] = "kk";
      console.log(syObject1);    // {Symbol(key1): "kk"}
      
      // 写法2
      let syObject2 = {
          [sy]: "kk"
      };
      console.log(syObject2);    // {Symbol(key1): "kk"}
      
      // 写法3
      let syObject3 = {};
      Object.defineProperty(syObject3, sy, {value: "kk"});
      console.log(syObject3);   // {Symbol(key1): "kk"}
      

      Symbol 作为对象属性名时不能用.运算符,要用方括号。因为.运算符后面是字符串,所以取到的是字符串 sy 属性,而不是 Symbol 值 sy 属性。

      let syObject = {};
      syObject[sy] = "kk";
       
      console.log(syObject[sy]);  // "kk"
      console.log(syObject.sy);   // undefined
      
    • 注意点

      Symbol 值作为属性名时,该属性是公有属性不是私有属性,可以在类的外部访问。但是不会出现在 for…in 、 for…of 的循环中,也不会被 Object.keys() 、 Object.getOwnPropertyNames() 返回。如果要读取到一个对象的 Symbol 属性,可以通过 Object.getOwnPropertySymbols() 和 Reflect.ownKeys() 取到。

      let sy = Symbol('key')
      let syObject = {};
      syObject[sy] = "value";
      console.log(syObject); // {Symbol(key): 'value'}
      
      for (let i in syObject) {
          console.log(i);
      } // 无输出
      
      console.log(Object.keys(syObject)); // []
      console.log(Object.getOwnPropertySymbols(syObject)); // [Symbol(key)]
      console.log(Reflect.ownKeys(syObject)); // [Symbol(key)]
      

2.9、Set对象

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

  1. Set 中的特殊值

    Set 对象存储的值总是唯一的,所以需要判断两个值是否恒等。有几个特殊值需要特殊对待:

    • +0 与 -0 在存储判断唯一性的时候是恒等的,所以不重复;
    • undefined 与 undefined 是恒等的,所以不重复;
    • NaN 与 NaN 是不恒等的,但是在 Set 中只能存一个,不重复。
    let mySet = new Set();
    mySet.add(1); // Set(1) {1}
    mySet.add(5); // Set(2) {1, 5}
    mySet.add(5); // Set(2) {1, 5} 这里体现了值的唯一性
    mySet.add("some text"); // Set(3) {1, 5, "some text"} 这里体现了类型的多样性
    
    console.log(mySet); // Set(3) {1, 5, 'some text'}
    
    var o = {a: 1, b: 2}; 
    mySet.add(o);
    console.log(mySet); // Set(4) {1, 5, 'some text', {…}}
    mySet.add({a: 1, b: 2}); 
    console.log(mySet); // Set(5) {1, 5, "some text", {…}, {…}}
    // 这里体现了对象之间引用不同不恒等,即使值相同,Set 也能存储
    
  2. 类型转换

    // Array 转 Set
    var mySet = new Set(["value1", "value2", "value3"]);
    console.log(mySet); // Set(3) {'value1', 'value2', 'value3'}
    
    // 用...操作符,将 Set 转 Array
    var myArray = [...mySet];
    console.log(myArray); // (3) ['value1', 'value2', 'value3']
    
    // String 转 Set
    var mySet = new Set('hello');
    console.log(mySet); // Set(4) {"h", "e", "l", "o"}
    // 注:Set 中 toString 方法是不能将 Set 转换成 String
    
  3. Set 对象作用

    • 数组去重

      var mySet = new Set([1, 2, 3, 4, 4]);
      let arr = [...mySet]
      console.log(arr); // (4) [1, 2, 3, 4]
      
    • 并集

      var a = new Set([1, 2, 3]);
      var b = new Set([4, 3, 2]);
      var union = new Set([...a, ...b]);
      console.log(union); // {1, 2, 3, 4}
      
    • 交集

      var a = new Set([1, 2, 3]);
      var b = new Set([4, 3, 2]);
      var intersect = new Set([...a].filter(x => b.has(x))); 
      console.log(intersect); // {2, 3}
      
      // Set.has(x) 是 set 中的一个方法。即判断当前 set 中是否含有 x,如果有返回 true,没有返回 false
      // 所以这段程序也可以写成:
      var a = new Set([1, 2, 3]); 
      var b = new Set([4, 3, 2]); 
      var arr = [...a];            //将a转换成数组
      var fArr = arr.filter(function(x){    //使用filter过滤数组,并将新数组返回到fArr
          return b.has(x);        //判断b中是否含有a中的元素,没有则返回false
      })
      var intersect = new Set(fArr);          //将fArr转换成set
      console.log(fArr);
      
    • 差集

      var a = new Set([1, 2, 3]);
      var b = new Set([4, 3, 2]);
      var difference = new Set([...a].filter(x => !b.has(x)));
      console.log(difference); // {1}
      
      // Set.has(x) 是 set 中的一个方法。即判断当前 set 中是否含有 x,如果有返回 true,没有返回 false
      // 所以这段程序也可以写成:
      var a = new Set([1, 2, 3]); 
      var b = new Set([4, 3, 2]); 
      var arr = [...a];            //将a转换成数组
      var fArr = arr.filter(function(x){    //使用filter过滤数组,并将新数组返回到fArr
          return !b.has(x);        //判断b中是否含有a中的元素,没有则返回false
      })
      var intersect = new Set(fArr);          //将fArr转换成set
      console.log(fArr);
      

2.10、Map对象

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

  1. Maps 和 Objects 的区别

    • 一个 Object 的键只能是字符串或者 Symbols,但一个 Map 的键可以是任意值。
    • Map 中的键值是有序的(FIFO 原则),而添加到对象中的键则不是。
    • Map 的键值对个数可以从 size 属性获取,而 Object 的键值对个数只能手动计算。
    • Object 都有自己的原型,原型链上的键名有可能和你自己在对象上的设置的键名产生冲突。
  2. Map 中的 key

    • key 是字符串

      var myMap = new Map();
      var keyString = "a string"; 
      
      myMap.set(keyString, "和键'a string'关联的值");
      console.log(myMap); // Map(1) {'a string' => "和键'a string'关联的值"}
      
      console.log(myMap.get(keyString));  // "和键'a string'关联的值"
      console.log(myMap.get("a string")); // "和键'a string'关联的值",因为 keyString === 'a string'
      
    • key 是对象

      var myMap = new Map();
      var keyObj = {}
      
      myMap.set(keyObj, "和键 keyObj 关联的值");
      console.log(myMap); // Map(1) {{…} => '和键 keyObj 关联的值'}
      
      console.log(myMap.get(keyObj)); // "和键 keyObj 关联的值"
      console.log(myMap.get({})); // undefined, 因为 keyObj !== {}
      
    • key 是函数

      var myMap = new Map();
      var keyFunc = function () {}; // 函数
      
      myMap.set(keyFunc, "和键 keyFunc 关联的值");
      console.log(myMap); // Map(1) {ƒ => '和键 keyFunc 关联的值'}
      
      console.log(myMap.get(keyFunc)); // "和键 keyFunc 关联的值"
      console.log(myMap.get(function() {})) // undefined, 因为 keyFunc !== function () {}
      
    • key 是 NaN

      虽然 NaN 和任何值甚至和自己都不相等(NaN !== NaN 返回true),NaN作为Map的键来说是没有区别的。

      var myMap = new Map();
      myMap.set(NaN, "not a number");
      console.log(myMap); // Map(1) {NaN => 'not a number'}
      
      console.log(myMap.get(NaN)); // "not a number"
      
      var otherNaN = Number("foo");
      console.log(myMap.get(otherNaN)); // "not a number"
      
  3. Map 的迭代

    对 Map 进行遍历,以下两个最高级。

    • for…of

      var myMap = new Map();
      myMap.set(0, "zero");
      myMap.set(1, "one");
      console.log(myMap); // Map(2) {0 => 'zero', 1 => 'one'}
      
      // 将会显示两个 console.log。一个是 "0 = zero" 另一个是 "1 = one"
      for (var [key, value] of myMap) {
          console.log(key + " = " + value);
      }
      
      // 将会显示两个 console.log。一个是 "0 = zero" 另一个是 "1 = one"
      for (var [key, value] of myMap.entries()) {
          console.log(key + " = " + value);
      }
      /* 这个 entries 方法返回一个新的 Iterator 对象,它按插入顺序包含了 Map 对象中每个元素的 [key, value] 数组。 */
      console.log(myMap.entries()) // MapIterator {0 => 'zero', 1 => 'one'}
      
      // 将会显示两个 console.log。一个是 "0" 另一个是 "1"
      for (var key of myMap.keys()) {
          console.log(key);
      }
      /* 这个 keys 方法返回一个新的 Iterator 对象, 它按插入顺序包含了 Map 对象中每个元素的键。 */
      console.log(myMap.keys()) // MapIterator {0, 1}
      
      // 将会显示两个 console.log。一个是 "zero" 另一个是 "one"
      for (var value of myMap.values()) {
          console.log(value);
      }
      /* 这个 values 方法返回一个新的 Iterator 对象,它按插入顺序包含了 Map 对象中每个元素的值。 */
      console.log(myMap.values()) // MapIterator {'zero', 'one'}
      
    • forEach()

      var myMap = new Map();
      myMap.set(0, "zero");
      myMap.set(1, "one");
      console.log(myMap); // Map(2) {0 => 'zero', 1 => 'one'}
      
      // 将会显示两个 console.log。一个是 "0 = zero" 另一个是 "1 = one"
      myMap.forEach(function(value, key) {
          console.log(key + " = " + value);
      }, myMap)
      
  4. Map 对象的操作

    • Map 与 Array的转换

      var kvArray = [["key1", "value1"], ["key2", "value2"]];
      
      // Map 构造函数可以将一个 二维 键值对数组转换成一个 Map 对象
      var myMap = new Map(kvArray);
      console.log(myMap); // Map(2) {'key1' => 'value1', 'key2' => 'value2'}
      
      // 使用 Array.from 函数可以将一个 Map 对象转换成一个二维键值对数组
      var outArray = Array.from(myMap);
      console.log(outArray); // [Array(2), Array(2)]
      
    • Map 的克隆

      var original = new Map([["key1", "value1"], ["key2", "value2"]]);
      console.log(original); // Map(2) {'key1' => 'value1', 'key2' => 'value2'}
      
      var clone = new Map(original);
      console.log(clone); // Map(2) {'key1' => 'value1', 'key2' => 'value2'}
      
      console.log(original === clone); // 打印 false。 Map 对象构造函数生成实例,迭代出新的对象。
      
    • Map 的合并

      var first = new Map([[1, 'one'], [2, 'two'], [3, 'three'],]);
      console.log(first); // Map(3) {1 => 'one', 2 => 'two', 3 => 'three'}
      
      var second = new Map([[1, 'uno'], [2, 'dos']]);
      console.log(second); // Map(2) {1 => 'uno', 2 => 'dos'}
      
      // 合并两个 Map 对象时,如果有重复的键值,则后面的会覆盖前面的,对应值即 uno,dos, three
      var merged = new Map([...first, ...second]);
      console.log(merged); //  {1 => 'uno', 2 => 'dos', 3 => 'three'}
      

2.11、Proxy 和 Reflect

  1. Proxy

    Proxy 可以对目标对象的读取、函数调用等操作进行拦截,然后进行操作处理。它不直接操作对象,而是像代理模式,通过对象的代理对象进行操作,在进行这些操作时,可以添加一些需要的额外操作。

    一个 Proxy 对象由两个部分组成: target 、 handler 。在通过 Proxy 构造函数生成实例对象时,需要提供这两个参数。 target 即目标对象, handler 是一个对象,声明了代理 target 的指定行为。

    let obj = {
        name: 'Tom',
        age: 24
    }
    let handler = {
        // target:是obj;
        // key:是读取的key值
        // receiver:表示原始操作行为所在对象,一般是 Proxy 实例本身
        get(target, key, receiver){
            return target[key]
        },
        // target:是obj;
        // key:是修改的key值
        // value:是要修改的属性值
        // receiver:表示原始操作行为所在对象,一般是 Proxy 实例本身
        set(target, key, value, receiver){
            return target[key] = value
        }
    }
    // 通过构造函数新建实例时其实是对目标对象进行了浅拷贝,因此目标对象与代理对象会互相
    let proxy = new Proxy(obj, handler)
    proxy.name     // 实际执行 handler.get
    proxy.age = 25 // 实际执行 handler.set
    
    // target 可以为空对象
    let targetEpt = {}
    // 通过构造函数新建实例时其实是对目标对象进行了浅拷贝,因此目标对象与代理对象会互相
    let proxyEpt = new Proxy(targetEpt, handler)
    // 调用 get 方法,此时目标对象为空,没有 name 属性
    proxyEpt.name 
    // 调用 set 方法,向目标对象中添加了 name 属性
    proxyEpt.name = 'Tom'
    // 再次调用 get ,此时已经存在 name 属性
    proxyEpt.name
     
    // handler 对象也可以为空,相当于不设置拦截操作,直接访问目标对象
    let targetEmpty = {}
    // 通过构造函数新建实例时其实是对目标对象进行了浅拷贝,因此目标对象与代理对象会互相
    let proxyEmpty = new Proxy(targetEmpty,{})
    proxyEmpty.name = "Tom"
    console.log(targetEmpty) // {name: "Tom"}
    
  2. Reflect

    Reflect 可以用于获取目标对象的行为,它与 Object 类似,但是更易读,为操作对象提供了一种更优雅的方式。它的方法与 Proxy 是对应的。

    ES6 中将 Object 的一些明显属于语言内部的方法移植到了 Reflect 对象上(当前某些方法会同时存在于 Object 和 Reflect 对象上),未来的新方法会只部署在 Reflect 对象上。

    Reflect 对象对某些方法的返回结果进行了修改,使其更合理。

    Reflect 对象使用函数的方式实现了 Object 的命令式操作。

    • Reflect.get(target, name, receiver)

      查找并返回 target 对象的 name 属性。

      let exam = {
          name: "Tom",
          age: 24,
          get info(){
              return this.name + this.age;
          }
      }
      Reflect.get(exam, 'name'); // "Tom"
      
      // 当 target 对象中存在 name 属性的 getter 方法, getter 方法的 this 会绑定 // receiver
      let receiver = {
          name: "Jerry",
          age: 20
      }
      console.log(Reflect.get(exam, 'info', receiver)); // Jerry20
      
      // 当 name 为不存在于 target 对象的属性时,返回 undefined
      console.log(Reflect.get(exam, 'birth')); // undefined
      
      // 当 target 不是对象时,会报错
      console.log(Reflect.get(1, 'name')); // TypeError: Reflect.get called on non-object
      
    • Reflect.set(target, name, value, receiver)

      let exam = {
          name: "Tom",
          age: 24,
          set info(value){
              return this.age = value;
          }
      }
      console.log(Reflect.set(exam, 'age', 25)); // true
      console.log(exam.age); // 25
      
      // value 为空时会将 name 属性清除
      console.log(Reflect.set(exam, 'age', )); // true
      console.log(exam.age); // undefined
      
      // 当 target 对象中存在 name 属性 setter 方法时,setter 方法中的 this 会绑定 // receiver , 所以修改的实际上是 receiver 的属性,
      let receiver = {
          age: 18
      }
      console.log(Reflect.set(exam, 'info', 1, receiver)); // true
      console.log(Reflect.set(exam, 'age', 25)); // true
      console.log(receiver.age); // 1
      console.log(exam.age); // 25
      
      let receiver1 = {
          name: 'oppps'
      }
      console.log(Reflect.set(exam, 'info', 1, receiver1)); // true
      console.log(receiver1.age); // 1
      console.log(exam.age); // 25
      
    • Reflect.has(obj, name)

      是 name in obj 指令的函数化,用于查找 name 属性在 obj 对象中是否存在。返回值为 boolean。如果 obj 不是对象则会报错 TypeError。

      let exam = {
          name: "Tom",
          age: 24
      }
      console.log(Reflect.has(exam, 'name')); // true
      
    • Reflect.deleteProperty(obj, property)

      是 delete obj[key] 的函数化,用于删除 obj 对象的 key 属性,返回值为 boolean。如果 obj 不是对象则会报错 TypeError。

      let exam = {
          name: "Tom",
          age: 24
      }
      Reflect.deleteProperty(exam , 'name'); // true
      console.log(exam) // {age: 24} 
      // property 不存在时,也会返回 true
      Reflect.deleteProperty(exam , 'name'); // true
      

2.12、类

  1. 基本定义

    • 类定义

      // 类表达式可以为匿名或命名
      {
          // 匿名类
          let Example = class {
              constructor(a) {
                  this.a = a;
              }
          }
          // 命名类
          let Example = class Example {
              constructor(a) {
                  this.a = a;
              }
          }
      }
      
    • 类声明

      {   
          // 基本定义和生成实例
          class Parent{
              // 定义一个构造函数
              constructor(name='默认值'){
                  // 通过构造函数给实例添加属性
                  this.name = name
              }
          }
      
          let v_parent = new Parent('a')
          console.log(v_parent)
          // Parent {name: 'a'}
      }
      
    • 注意要点

      • 类定义不会被提升,这意味着,必须在访问前对类进行定义,否则就会报错。
      • 类中方法不需要 function 关键字。
      • 方法间不能加分号。
  2. 继承

    // 继承
    {   
        // 父类
        class Parent{
            // 定义一个构造函数
            constructor(name='默认值'){
                // 通过构造函数给实例添加属性
                this.name = name
            }
        }
    
        // 子类
        class Child extends Parent{
            constructor(name='child'){
                // 给父类传参,如果不传,则使用父类的默认值
                // super 必须放在第一行
                super(name)
                this.type = 'child_type'
            }
        }
    
        console.log('父类实例:', new Parent()) // 父类实例: _Parent {name: '默认值'}
        console.log('子类实例:', new Child()) // 子类实例: Child {name: 'child', type: 'child_type'}
    }
    
  3. 类中的 getter 和 setter

    // getter 和 setter
    {   
        // 父类
        class Parent{
            // 定义一个构造函数
            constructor(name='默认值'){
                // 通过构造函数给实例添加属性
                this.name = name
            }
    
            // 读取
            get longName(){
                return 'getter-' + this.name
            }
    
            // 赋值
            set longName(value){
                this.name = value
            }
        }
    
        let x = new Parent()
        console.log(x.longName) // getter-默认值
    
        x.name = 'hello word'
        console.log(x.longName) // getter-hello word
    }
    
  4. 静态方法 和 静态属性

    • 静态方法

      静态方法 和 静态属性,通过类去调用,而不是通过类的实例去调用

      // 静态方法
      {   
          class Parent{
              // 定义一个构造函数
              constructor(name='默认值'){
                  // 通过构造函数给实例添加属性
                  this.name = name
              }
      
              static fun(){
                  console.log('fun')
              }
          }
      
          Parent.fun() // fun
      }
      
    • 静态属性

      // 静态属性
      {   
          class Parent{
              // 定义一个构造函数
              constructor(name='默认值'){
                  // 通过构造函数给实例添加属性
                  this.name = name
              }
          }
      
          Parent.type = '静态属性'
          console.log(Parent.type) // 静态属性
      }
      
  5. 原型方法

    {
        class Example {
            sum(a, b) {
                console.log(a + b);
            }
        }
        let exam = new Example();
        exam.sum(1, 2); // 3
    }
    

2.13、Promise

  1. Promise 状态

    • 状态的特点

      • Promise 异步操作有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)。除了异步操作的结果,任何其他操作都无法改变这个状态。

      • Promise 对象只有:从 pending 变为 fulfilled 和从 pending 变为 rejected 的状态改变。只要处于 fulfilled 和 rejected ,状态就不会再变了即 resolved(已定型)。

        const p1 = new Promise(function(resolve,reject){
            resolve('success1');
            resolve('success2');
        }); 
        const p2 = new Promise(function(resolve,reject){  
            resolve('success3'); 
            reject('reject');
        });
        p1.then(function(value){  
            console.log(value); // success1
        });
        p2.then(function(value){ 
            console.log(value); // success3
        });
        
    • 状态的缺点

      • 无法取消 Promise ,一旦新建它就会立即执行,无法中途取消。
      • 如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。
      • 当处于 pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
  2. then 方法

    then 方法接收两个函数作为参数,第一个参数是 Promise 执行成功时的回调,第二个参数是 Promise 执行失败时的回调,两个函数只会有一个被调用。

    • then 方法的特点

      在 JavaScript 事件队列的当前运行完成之前,回调函数永远不会被调用。

      const p = new Promise(function(resolve,reject){
        resolve('success');
      });
       
      p.then(function(value){
        console.log(value);
      });
       
      console.log('first');
      // first
      // success
      

      通过 .then 形式添加的回调函数,不论什么时候,都会被调用。

      通过多次调用 .then ,可以添加多个回调函数,它们会按照插入顺序并且独立运行。

      const p = new Promise(function(resolve,reject){
        resolve(1);
      }).then(function(value){ // 第一个then // 1
        console.log(value);
        return value * 2;
      }).then(function(value){ // 第二个then // 2
        console.log(value);
      }).then(function(value){ // 第三个then // 因为第二个.then没有使用 return 或 resolve,所以value=undefined
        console.log(value);
        return Promise.resolve('resolve'); 
      }).then(function(value){ // 第四个then // resolve
        console.log(value);
        return Promise.reject('reject'); 
      }).then(function(value){ // 第五个then //reject:reject
        console.log('resolve:' + value);
      }, function(err) {
        console.log('reject:' + err);
      });
      

2.14、async 函数

async 是 ES7 才有的与异步操作有关的关键字,和 Promise , Generator 有很大关联的。

  1. 语法

    async function name([param[, param[, ... param]]]) { statements }
    
    • name: 函数名称。
    • param: 要传递给函数的参数的名称。
    • statements: 函数体语句。
  2. 返回值

    async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数。

    async function helloAsync(){
        return "helloAsync";
      }
      
    console.log(helloAsync())  // Promise {<resolved>: "helloAsync"}
     
    helloAsync().then(v=>{
       console.log(v);         // helloAsync
    })
    

    async 函数中可能会有 await 表达式,async 函数执行时,如果遇到 await 就会先暂停执行 ,等到触发的异步操作完成后,恢复 async 函数的执行并返回解析值。

    await 关键字仅在 async function 中有效。如果在 async function 函数体外使用 await ,你只会得到一个语法错误。

    function testAwait(){
       return new Promise((resolve) => {
           setTimeout(function(){
              console.log("testAwait");
              resolve();
           }, 1000);
       });
    }
     
    async function helloAsync(){
       await testAwait();
       console.log("helloAsync");
     }
    helloAsync();
    // testAwait
    // helloAsync
    
  3. await

    await 操作符用于等待一个 Promise 对象, 它只能在异步函数 async function 内部使用。

    • 语法

      [return_value] = await expression;
      
      • expression: 一个 Promise 对象或者任何要等待的值。
    • 返回值

      返回 Promise 对象的处理结果。如果等待的不是 Promise 对象,则返回该值本身。

      如果一个 Promise 被传递给一个 await 操作符,await 将等待 Promise 正常处理完成并返回其处理结果。

      function testAwait (x) {
        return new Promise(resolve => {
          setTimeout(() => {
            resolve(x);
          }, 2000);
        });
      }
       
      async function helloAsync() {
        var x = await testAwait ("hello world");
        console.log(x); 
      }
      helloAsync ();
      // hello world
      

      正常情况下,await 命令后面是一个 Promise 对象,它也可以跟其他值,如字符串,布尔值,数值以及普通函数。

      function testAwait(){
         console.log("testAwait");
      }
      async function helloAsync(){
         await testAwait();
         console.log("helloAsync");
      }
      helloAsync();
      // testAwait
      // helloAsync
      

      await针对所跟不同表达式的处理方式:

      • Promise 对象:await 会暂停执行,等待 Promise 对象 resolve,然后恢复 async 函数的执行并返回解析值。
      • 非 Promise 对象:直接返回对应的值。

3、参考文档:es6教程文档


es6
http://localhost:8090//archives/es6
作者
龟龟
发布于
2022年12月14日
更新于
2024年08月28日
许可协议