es6
ECMAScript2015(es6)
1、项目构建
1.1、基础架构
- 业务逻辑:
- 页面
- 交互
- 自动构建:babel、webpack
- 编译
- 辅助:
- 自动刷新
- 文件合并
- 资源压缩
- 服务接口
- 数据
- 接口
1.2、项目创建
创建es6文件夹:
-
app放置前端代码
- css
- js
- class:类文件夹
- index.js:入口文件
- views:express框架使用ejs引擎所以后缀不是html而是ejs
- index.ejs:入口文件
- error.ejs:错误文件
-
server放置服务器代码
-
启动脚手架:
// express:脚手架启动命令 // -e 使用ejs引擎 // . 在当前目录执行 express -e . -
初始化:npm i
-
-
tasks放置工具
- utils:常用脚本
-
.babelrc:babel配置文件
-
gulpfile.babel.js:gulp配置文件,因为项目使用es6,需要使用babel对es6代码进行编译,所以创建gulpfile.babel.js文件,而不是创建官网推荐的gulpfile.js文件
1.3、命令行处理、创建js编译任务
-
新建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 -
新建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())) }) -
初始化:
-
初始化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、创建模板、服务任务脚本
-
新建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())) }) -
新建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())) }) -
新建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、文件自动监听
-
新建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']) }) -
新建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']) }) -
新建tasks/build.js:关联所有任务
import gulp from 'gulp' import gulpSequence from 'gulp-sequence' // 指定顺序执行 gulp.task('build', gulpSequence('clean', 'css', 'pages', 'scripts', ['browser', 'server'])) -
新建tasks/default.js:命令行执行gulp命令时,默认执行的文件
import gulp from 'gulp' // 指定顺序执行 gulp.task('default', ['build']) -
修改gulpfile.babel.js:
import requireDir from 'require-dir' // 引入tasks目录下的脚本 requireDir('./tasks') -
修改.babelrc:
{ "presets": ["es2015"] } -
如果执行命令 gulp --watch 报错 primordials is not defined:
-
删除node_modules
-
在package.json的同级目录中创建npm-shrinkwrap.json
-
在创建的npm-shrinkwrap.json中复制以下内容:
{ "dependencies": { "graceful-fs": { "version": "4.2.2" } } }
-
-
报错: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
- 相同点:
- 只在声明所在的块级作用域内有效。
- 变量不会提升,同时存在暂时性死区,只能在声明的位置后面使用。
- 不可重复声明。
- 不同点:
- let声明的变量可以改变,值和类型都可以改变;const声明的常量不可以改变,这意味着,const一旦声明,就必须立即初始化,不能以后再赋值。
2.2、解构赋值
-
数组解构赋值
-
代码演示:
// 新建一个块级作用域:数组解构赋值 { 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] } -
使用场景:
-
变量交换:
let a = 1; let b = 2; [a,b] = [b,a] console.log(a, b) //2 1 -
函数返回值取值:
{ 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] }
-
-
-
对象解构赋值
-
代码演示:
// 新建一个块级作用域:对象解构赋值 { // 声明和赋值不分开写,可以不要括号 // 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 } -
使用场景:
-
嵌套对象使用自定义变量名取值
{ 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、字符串扩展
-
repeat
repeat 方法返回新的字符串,表示字符串复制指定次数。
let str="Hello World"; let str2 = str.repeat(3) console.log(str2); //Hello WorldHello WorldHello World -
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
-
-
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、数组扩展
-
Array.of
将一组数据变量,转换成数组。如果不传参数,将返回一个空数组
let arr = Array.of(1,2,3,4,5,'6') console.log(arr); // [1, 2, 3, 4, 5, '6'] -
Array.from
将类数组对象或可迭代对象转化为数组
-
基本使用:
// 参数为数组,返回与原数组一样的数组 console.log(Array.from([1, 2])); // [1, 2] // 参数含空位 console.log(Array.from([1, , 3])); // [1, undefined, 3] -
类数组对象
let arr = Array.from({ 0: '1', 1: '2', 2: 3, length: 3 }); console.log(arr); // ['1', '2', 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"]
-
-
参数: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]
-
-
-
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 -
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 -
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 -
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、函数扩展
-
默认参数
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 -
不定参数
不定参数用来表示不确定参数个数,形如,…变量名,由…加上一个具名参数标识符组成。具名参数只能放在参数组的最后,并且有且只有一个不定参数。
function f(...values){ console.log(values.length); } f(1,2); //2 f(1,2,3,4); //4 -
箭头函数
-
基本用法:
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、对象扩展
-
对象字面量
-
属性的简洁表示法
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"
-
-
对象的拓展运算符
-
基本用法
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}
-
-
对象的新方法
-
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 ,表示独一无二的值,最大的用法是用来定义对象的唯一属性名。
-
基本用法:
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 -
使用场景
-
作为属性名
由于每一个 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 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用。
-
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 也能存储 -
类型转换
// 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 -
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 对象保存键值对。任何值(对象或者原始值) 都可以作为一个键或一个值。
-
Maps 和 Objects 的区别
- 一个 Object 的键只能是字符串或者 Symbols,但一个 Map 的键可以是任意值。
- Map 中的键值是有序的(FIFO 原则),而添加到对象中的键则不是。
- Map 的键值对个数可以从 size 属性获取,而 Object 的键值对个数只能手动计算。
- Object 都有自己的原型,原型链上的键名有可能和你自己在对象上的设置的键名产生冲突。
-
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"
-
-
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)
-
-
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
-
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"} -
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、类
-
基本定义
-
类定义
// 类表达式可以为匿名或命名 { // 匿名类 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 关键字。
- 方法间不能加分号。
-
-
继承
// 继承 { // 父类 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'} } -
类中的 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 } -
静态方法 和 静态属性
-
静态方法
静态方法 和 静态属性,通过类去调用,而不是通过类的实例去调用
// 静态方法 { 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) // 静态属性 }
-
-
原型方法
{ class Example { sum(a, b) { console.log(a + b); } } let exam = new Example(); exam.sum(1, 2); // 3 }
2.13、Promise
-
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 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
-
-
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 有很大关联的。
-
语法
async function name([param[, param[, ... param]]]) { statements }- name: 函数名称。
- param: 要传递给函数的参数的名称。
- statements: 函数体语句。
-
返回值
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 -
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 // helloAsyncawait针对所跟不同表达式的处理方式:
- Promise 对象:await 会暂停执行,等待 Promise 对象 resolve,然后恢复 async 函数的执行并返回解析值。
- 非 Promise 对象:直接返回对应的值。
-