ES6新特性(1)
目录
ECMAScript6新特性
命令行工具
Nodejs环境安装
Babel转码器
Let命令
let不存在变量提升
let不允许重复声明
const 命令
变量的解构赋值
数组的解构赋值
对象的解构赋值
字符串的解构赋值
变量的解构赋值_用途
交换变量的值
从函数返回多个值
提取 JSON 数据
字符串扩展
字符串遍历器接口
模板字符串
字符串新增方法
includes(), startsWith(), endsWith()
padStart(),padEnd()
trimStart(),trimEnd()
at()
数值的扩展
Number.isFinite(), Number.isNaN()
Number.parseInt(), Number.parseFloat()
Number.isInteger()
Math函数扩展
Math.trunc()
Math.sign()
Math.cbrt()
对数方法
双曲函数方法
数组扩展_扩展运算符
Array.from()
Array.of()
数组实例的 copyWithin()
数组实例的 find() 和 findIndex()
数组实例的 fill()
数组实例的 includes()
对象的扩展
属性的简洁表示法
属性名表达式
方法的 name 属性
对象的扩展运算符
对象的新增方法-Object.is()
对象的新增方法-Object.assign()
运算符的扩展
指数运算符
链判断运算符
Null 判断运算符
函数的扩展
函数参数的默认值
rest 参数
name 属性
函数的 length 属性
箭头函数
Symbol
实例属性description
属性名的遍历
Symbol.for()
ECMAScript6新特性
命令行工具
常用命令行工具有两种
- CMD 命令行工具
- PowerShell 命令行工具
CMD 命令行 1 打开命令行窗口 1 win :左下角开始,找到运行,点击,输入 cmd ,回车 2 win : win+r 快速打开命令行窗口 3 mac : command + 空格,输入 terminal 2 选择盘符:盘符名加冒号 E: 3 查看盘符及目录下文件与文件夹: win:dir mac:ls 4 清空命令行信息: win:cls mac:clear 5 进入文件夹或目录: cd 文件夹名称 6 返回到上一级目录: cd ../ 7 快速补全目录或文件夹名称: tab 8 创建文件夹: mkdir 文件夹名称 9 查看历史输入过的命令:上下按键
PowerShell 1 打开方式
- 1 在开始位置搜索 PowerShell 打开
- 2 在对应目录按住 shift +右键,打开
其他保持一致
Nodejs环境安装
Nodejs 官网 https://nodejs.org/en/ npm 镜像 由于服务器在国外,所以下载速度比较慢,我们可以用国内的镜像 阿里镜像地址 https://npmmirror.com/ 在命令行运行如下命令即可 npm install -g cnpm -- registry = https://registry.npmmirror.com 看到如下信息,代表安装成功
Babel转码器
Babel 是一个广泛使用的 ES6 转码器,可以将 ES6 代码转为 ES5 代 码,从而在老版本的浏览器执行。这意味着,你可以用 ES6 的方式 编写程序,又不用担心现有环境是否支持
浏览器支持性查看 https://caniuse.com/
转码示例 原始代码用了箭头函数, Babel 将其转为普通函数,就能在不支持 箭头函数的 JavaScript 环境执行了
// 转码前input.map(item => item + 1);// 转码后input.map(function (item) { return item + 1;});
Babel 安装流程 第一步:安装 Babel(在具体的 写代码的文件夹,即根目录 中安装)
npm install --save-dev @babel/core
第二步:配置文件 .babelrc Babel 的配置文件是 .babelrc ,存放在项目的 根目录 下(在根目录下建一个 .babelrc 文件)。使用 Babel 的第一步,就是配置这个文件。 该文件用来设置转码规则和插件,基本格式如下
{ "presets": [], "plugins": []}
第三步:转码规则 presets字段设定转码规则,官方提供以下的规则集,你可以根据需要在 根目录 下安装
npm install --save-dev @babel/preset-env
第四步:将规则加入 .babelrc(覆盖之前的)
{ "presets": [ "@babel/env" ], "plugins": []}
最后一步
Babel 命令行转码 Babel 提供命令行工具 @babel/cli ,用于命令行转码 安装 Babel命令转码工具: npm install -- save - dev @babel/cli
npm install --save-dev @babel/cli
基本用法如下
# 转码结果输出到控制台输出 npx babel example.js# 转码结果写入一个文件# --out-file 或 -o 参数指定输出文件 npx babel example.js --out-file compiled.js# 或者$ npx babel example.js -o compiled.js# 整个目录转码# --out-dir 或 -d 参数指定输出目录$ npx babel src --out-dir lib# 或者$ npx babel src -d lib
Let命令
ES6 新增了let命令,用来声明变量。它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效。
{ let a = 10; var b = 1;}a // ReferenceError: a is not defined.b // 1
上面代码在代码块之中,分别用let和var声明了两个变量。然后在代码块之外调用这两个变量,结果let声明的变量报错,var声明的变量返回了正确的值。这表明,let声明的变量只在它所在的代码块有效。
for循环的计数器,就很合适使用let命令。
for (let i = 0; i < 10; i++) { // ...}console.log(i);// ReferenceError: i is not defined
上面代码中,计数器i只在for循环体内有效,在循环体外引用就会报错。
下面的代码如果使用var,最后输出的是10。
var a = [];for (var i = 0; i < 10; i++) { a[i] = function () { console.log(i); };}a[6](); // 10
上面代码中,变量i是var命令声明的,在全局范围内都有效,所以全局只有一个变量i。每一次循环,变量i的值都会发生改变,而循环内被赋给数组a的函数内部的console.log(i),里面的i指向的就是全局的i。也就是说,所有数组a的成员里面的i,指向的都是同一个i,导致运行时输出的是最后一轮的i的值,也就是 10。
如果使用let,声明的变量仅在块级作用域内有效,最后输出的是 6。
var a = [];for (let i = 0; i < 10; i++) { a[i] = function () { console.log(i); };}a[6](); // 6
上面代码中,变量i是let声明的,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量,所以最后输出的是6。你可能会问,如果每一轮循环的变量i都是重新声明的,那它怎么知道上一轮循环的值,从而计算出本轮循环的值?这是因为 JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量i时,就在上一轮循环的基础上进行计算。
另外,for循环还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。
for (let i = 0; i < 3; i++) { let i = 'abc'; console.log(i);}// abc// abc// abc
上面代码正确运行,输出了 3 次abc。这表明函数内部的变量i与循环变量i不在同一个作用域,有各自单独的作用域
let不存在变量提升
var命令会发生“变量提升”现象,即变量可以在声明之前使用,值为undefined。这种现象多多少少是有些奇怪的,按照一般的逻辑,变量应该在声明语句之后才可以使用。
为了纠正这种现象,let命令改变了语法行为,它所声明的变量一定要在声明后使用,否则报错。
// var 的情况console.log(foo); // 输出undefinedvar foo = 2;// let 的情况console.log(bar); // 报错ReferenceErrorlet bar = 2;
let不允许重复声明
let不允许在相同作用域内,重复声明同一个变量。
// 报错function func() { let a = 10; var a = 1;}// 报错function func() { let a = 10; let a = 1;}
/** * 1. var关键字的作用域:var关键字是函数级别作用域 * 2. let关键字是块级作用域,理解为{}级别的作用域 * 3. let不存在变量提升 * 4. let不允许重复声明 */ // var name = "itbaizhan"; // console.log(name); // if(true){ // var age = 20; // } // console.log(age); // 20 // function fn(){ // var sex = "男"; // } // fn(); // console.log(sex); // 报错 // let name = "itbaizhan"; // console.log(name); // if(true){ // let age = 20; // } // console.log(age); // 报错 // for(let i = 0;i<5;i++){ // console.log(i); // } // var arr = []; // for(var i = 0;i<10;i++){ // 并不是创建了10个i,而是只创建了一个i,i一直在被重新赋值 // arr[i] = function(){ // console.log(i); // } // } // // 相当于每一个arr= [10个数据],每个数据都是一个函数 // arr[6](); // 10 // var arr = []; // for(let i = 0;i<10;i++){ // 每次循环独立的创建一个i,有10个i // arr[i] = function(){ // console.log(i); // } // } // arr[3](); // 6 // console.log(names); // let names = "itbaizhan"; // let age = 20; // let age = 30; // console.log(age);
const 命令
const声明一个只读的常量。一旦声明,常量的值就不能改变。
const PI = 3.1415;PI // 3.1415PI = 3;// TypeError: Assignment to constant variable.
上面代码表明改变常量的值会报错。
const声明的变量不得改变值,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值。
const foo;// SyntaxError: Missing initializer in const declaration
上面代码表示,对于const来说,只声明不赋值,就会报错。
const的作用域与let命令相同:只在声明所在的块级作用域内有效。
if (true) { const MAX = 5;}MAX // Uncaught ReferenceError: MAX is not defined
const命令声明的常量也是不提升
if (true) { console.log(MAX); // ReferenceError const MAX = 5;}
const声明的常量,也与let一样不可重复声明。
var message = "Hello!";let age = 25;// 以下两行都会报错const message = "Goodbye!";const age = 30;
变量的解构赋值
数组的解构赋值
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。
以前,为变量赋值,只能直接指定值。
let a = 1;let b = 2;let c = 3;
ES6 允许写成下面这样。
let [a, b, c] = [1, 2, 3];
上面代码表示,可以从数组中提取值,按照对应位置,对变量赋值。
本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。下面是一些使用嵌套数组进行解构的例子。
let [foo, [[bar], baz]] = [1, [[2], 3]];foo // 1bar // 2baz // 3let [ , , third] = ["foo", "bar", "baz"];third // "baz"let [x, , y] = [1, 2, 3];x // 1y // 3let [head, ...tail] = [1, 2, 3, 4];head // 1tail // [2, 3, 4]let [x, y, ...z] = ['a'];x // "a"y // undefinedz // []
如果解构不成功,变量的值就等于undefined。
解构赋值允许指定默认值。
let [foo = true] = [];foo // truelet [x, y = 'b'] = ['a']; // x='a', y='b'let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'
注意,ES6 内部使用严格相等运算符(===),判断一个位置是否有值。所以,只有当一个数组成员严格等于undefined,默认值才会生效。
对象的解构赋值
解构不仅可以用于数组,还可以用于对象。
let { foo, bar } = { foo: 'aaa', bar: 'bbb' };foo // "aaa"bar // "bbb"
对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
let { bar, foo } = { foo: 'aaa', bar: 'bbb' };foo // "aaa"bar // "bbb"let { baz } = { foo: 'aaa', bar: 'bbb' };baz // undefined
上面代码的第一个例子,等号左边的两个变量的次序,与等号右边两个同名属性的次序不一致,但是对取值完全没有影响。第二个例子的变量没有对应的同名属性,导致取不到值,最后等于undefined。
对象的解构赋值,可以很方便地 将现有对象的方法,赋值到某个变量
let { random,floor } = Math; let { log } = console;
指定默认值
var {x = 3} = {};
注意事项,如果要将一个 已经声明的变量 用于解构赋值,必须非常小心
let hello = "Hello";let { hello } = {hello:"hello"}; // 报错let hello = "Hello";({ hello } = {hello:"hello"}); // 正确
字符串的解构赋值
字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象
const [a, b, c, d, e] = 'hello';a // "h"b // "e"c // "l"d // "l"e // "o"
类似数组的对象都有一个length属性,因此还可以对这个属性解构赋值。
变量的解构赋值_用途
交换变量的值
let x = 1;let y = 2;[x, y] = [y, x];
交换变量 x 和 y 的值,这样的写法不仅简洁,而且易读,语义非常清晰
从函数返回多个值
函数只能返回一个值,如果要返回多个值,只能将它们放在数组或对象里返回。有了解构赋值,取出这些值就非常方便。
// 返回一个数组function example() { return [1, 2, 3];}let [a, b, c] = example();// 返回一个对象function example() { return { foo: 1, bar: 2 };}let { foo, bar } = example();
提取 JSON 数据
解构赋值对提取 JSON 对象中的数据,尤其有用
let jsonData = { id: 42, status: "OK", data: ["iwen", "itbaizhan"] }; let { id, status, data } = jsonData;
字符串扩展
字符串 Unicode 表示法 ES6 加强了对 Unicode 的支持,允许采用 \uxxxx 形式表示一个字符,其中 xxxx 表示字符的 Unicode 码点。
"\u0061"// "a"
字符串遍历器接口
for...of 循环遍历
for (let i of 'itbaizhan') { console.log(i);//打印每个字符}
for (let codePoint of 'foo') { console.log(codePoint)}// "f"// "o"// "o"
模板字符串
模板字符串( template string )是增强版的字符串,用反引号(` )标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。
let url = "www.itbaizhan.com"let h1 = "itbaizhan"let h2 = `itbaizhan`
字符串新增方法
includes(), startsWith(), endsWith()
传统上,JavaScript 只有indexOf方法,可以用来确定一个字符串是否包含在另一个字符串中。ES6 又提供了三种新方法。
- includes():返回布尔值,表示是否找到了参数字符串。
- startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。
- endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。
let s = 'Hello world!';s.startsWith('Hello') // trues.endsWith('!') // trues.includes('o') // true
这三个方法都支持第二个参数,表示开始搜索的位置。
let s = 'Hello world!';s.startsWith('world', 6) // trues.endsWith('Hello', 5) // 注意 trues.includes('Hello', 6) // false
实例方法:repeat()
repeat方法返回一个新字符串,表示将原字符串重复n次。(包含自身)
'x'.repeat(3) // "xxx"'hello'.repeat(2) // "hellohello"'na'.repeat(0) // ""
padStart(),padEnd()
ES2017 引入了字符串补全长度的功能。如果某个字符串不够指定长度,会在头部或尾部补全。padStart()用于头部补全,padEnd()用于尾部补全。
'x'.padStart(5, 'ab') // 'ababx''x'.padStart(4, 'ab') // 'abax''x'.padEnd(5, 'ab') // 'xabab''x'.padEnd(4, 'ab') // 'xaba'
trimStart(),trimEnd()
ES2019 对字符串实例新增了trimStart()和trimEnd()这两个方法。它们的行为与trim()一致,trimStart()消除字符串头部的空格,trimEnd()消除尾部的空格。它们返回的都是新字符串,不会修改原始字符串。
const s = ' abc ';s.trim() // "abc"s.trimStart() // "abc "s.trimEnd() // " abc"
at()
at() 方法接受一个整数作为参数,返回参数指定位置的字符,支持负索引(即倒数的位置)。
const str = 'hello';str.at(1) // "e"str.at(-1) // "o"如果参数位置超出了字符串范围, at() 返回 undefined
数值的扩展
Number.isFinite(), Number.isNaN()
ES6 在Number对象上,新提供了Number.isFinite()和Number.isNaN()两个方法。
Number.isFinite()用来检查一个数值是否为有限的(finite),即不是Infinity。
Number.isFinite(15); // trueNumber.isFinite(0.8); // trueNumber.isFinite(NaN); // falseNumber.isFinite(Infinity); // falseNumber.isFinite(-Infinity); // falseNumber.isFinite('foo'); // falseNumber.isFinite('15'); // falseNumber.isFinite(true); // false
注意,如果参数类型不是数值,Number.isFinite一律返回false。
Number.isNaN()用来检查一个值是否为NaN。
Number.isNaN(NaN) // trueNumber.isNaN(15) // falseNumber.isNaN('15') // falseNumber.isNaN(true) // falseNumber.isNaN(9/NaN) // trueNumber.isNaN('true' / 0) // trueNumber.isNaN('true' / 'true') // true
如果参数类型不是NaN,Number.isNaN一律返回false
Number.parseInt(), Number.parseFloat()
ES6 将全局方法parseInt()和parseFloat(),移植到Number对象上面,行为完全保持不变。
// ES5的写法parseInt('12.34') // 12parseFloat('123.45#') // 123.45// ES6的写法Number.parseInt('12.34') // 12Number.parseFloat('123.45#') // 123.45 自动去调非数值
这样做的目的,是逐步减少全局性方法,使得语言逐步模块化。
Number.parseInt === parseInt // trueNumber.parseFloat === parseFloat // true
Number.isInteger()
Number.isInteger()用来判断一个数值是否为整数。
Number.isInteger(25) // trueNumber.isInteger(25.1) // false
Math函数扩展
ES6 在 Math 对象上新增了 17 个与数学相关的方法。所有这些方法都是静态方法,只能在 Math 对象上调用
Math.trunc()
Math.trunc 方法用于去除一个数的小数部分,返回整数部分
Math.trunc(4.1) // 4Math.trunc(4.9) // 4Math.trunc(-4.1) // -4Math.trunc(-4.9) // -4
Math.sign()
Math.sign 方法用来判断一个数到底是正数、负数、还是零。对于非数值,会先将其转换为数值 它会返回五种值
- 参数为正数,返回 +1
- 参数为负数,返回 -1
- 参数为 0,返回 0
- 参数为-0,返回 -0
- 其他值,返回 NaN
Math.sign(-5) // -1Math.sign(5) // +1Math.sign(0) // +0Math.sign(-0) // -0Math.sign(NaN) // NaNMath.sign('itbaizhan') // NaNMath.sign(undefined) // NaN
Math.cbrt()
Math.cbrt() 方法用于计算一个数的立方根
如果一个数的立方等于 a ,那么这个数叫 a 的立方根,也称为三 次方根。也就是说,如果 x³=a ,那么 x 叫做 a 的立方根
Math.cbrt('8') // 2Math.cbrt('hello') // NaN
对数方法
ES6 新增了 4 个对数相关方法
- 1 Math.expm1()
- 2 Math.log1p()
- 3 Math.log10()
- 4 Math.log2()
双曲函数方法
ES6 新增了 6 个双曲函数方法。
- Math.sinh(x) 返回 x 的双曲正弦(hyperbolic sine)
- Math.cosh(x) 返回 x 的双曲余弦(hyperbolic cosine)
- Math.tanh(x) 返回 x 的双曲正切(hyperbolic tangent)
- Math.asinh(x) 返回 x 的反双曲正弦(inverse hyperbolic sine)
- Math.acosh(x) 返回 x 的反双曲余弦(inverse hyperbolic cosine)
- Math.atanh(x) 返回 x 的反双曲正切(inverse hyperbolic tangent)
数组扩展_扩展运算符
扩展运算符( spread )是三个点( ... )。将一个数组转为用逗号分隔的参数序列
console.log(...[1, 2, 3])// 1 2 3console.log(1, ...[2, 3, 4], 5)// 1 2 3 4 5[...document.querySelectorAll('div')]// [, , ]
该运算符主要用于函数调用
function push(array, items) { array.push(...items);}function add(x, y) { return x + y; }const numbers = [4, 38];add(...numbers) // 42
替代函数的 apply 方法 由于扩展运算符可以展开数组,所以不再需要apply方法,将数组转为函数的参数了。
// ES5 的写法function f(x, y, z) { // ...}var args = [0, 1, 2];f.apply(null, args);// ES6的写法function f(x, y, z) { // ...}let args = [0, 1, 2];f(...args);
下面是扩展运算符取代apply方法的一个实际的例子,应用Math.max方法,简化求出一个数组最大元素的写法。
// ES5 的写法Math.max.apply(null, [14, 3, 77])// ES6 的写法Math.max(...[14, 3, 77])// 等同于Math.max(14, 3, 77);
扩展运算符的应用-合并数组 扩展运算符提供了数组合并的新写法。
const arr1 = ['a', 'b'];const arr2 = ['c'];const arr3 = ['d', 'e'];// ES5 的合并数组arr1.concat(arr2, arr3);// [ 'a', 'b', 'c', 'd', 'e' ]// ES6 的合并数组[...arr1, ...arr2, ...arr3]// [ 'a', 'b', 'c', 'd', 'e' ]
Array.from()
Array.from方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)。
下面是一个类似数组的对象,Array.from将它转为真正的数组。
let arrayLike = { '0': 'a', '1': 'b', '2': 'c', length: 3};// ES5的写法var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']// ES6的写法let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
实际应用中,常见的类似数组的对象是 DOM 操作返回的 NodeList 集合,以及函数内部的arguments对象。Array.from都可以将它们转为真正的数组
// Array.from()把伪数组变为真的数组 function add(){ arguments = Array.from(arguments) arguments.push(50) console.log(arguments); } add(10,20,30,40); var divs = document.querySelectorAll("div"); console.log(Array.from(divs)); let arrayLike = { 0:"iwen", 1:"ime", 2:"demo", length:3 } console.log(Array.from(arrayLike)); // Array.of():把一组数变为数组 console.log(Array.of(10,20,30,40)); var arr1 = new Array(3); console.log(arr1); // [,,] var arr2 = new Array(10,20,30); console.log(arr2); // [10,20,30] console.log(Array.of(3)); // [3] console.log(Array.of(3,3,4,5)); // [3]
Array.of()
Array.of方法用于将一组值,转换为数组。
Array.of(3, 11, 8) // [3,11,8]Array.of(3) // [3]Array.of(3).length // 1
这个方法的主要目的,是弥补数组构造函数Array()的不足。因为参数个数的不同,会导致Array()的行为有差异。
Array() // []Array(3) // [, , ,]Array(3, 11, 8) // [3, 11, 8]
数组实例的 copyWithin()
数组实例的copyWithin()方法,在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。也就是说,使用这个方法,会修改当前数组
Array.prototype.copyWithin(target, start = 0, end = this.length)
它接受三个参数。
target(必需):从该位置开始替换数据。如果为负值,表示倒数。
start(可选):从该位置开始读取数据,默认为 0。如果为负值,表示从末尾开始计算。
end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示从末尾开始计算。
这三个参数都应该是数值,如果不是,会自动转为数值。
[1, 2, 3, 4, 5].copyWithin(0, 3)// [4, 5, 3, 4, 5]
数组实例的 find() 和 findIndex()
数组实例的find方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为true的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined。
[1, 4, -5, 10].find((n) => n < 0)// -5
上面代码找出数组中第一个小于 0 的成员。
[1, 5, 10, 15].find(function(value, index, arr) { return value > 9;}) // 10
上面代码中,find方法的回调函数可以接受三个参数,依次为当前的值、当前的位置和原数组
数组实例的 fill()
fill方法使用给定值,填充一个数组。
['a', 'b', 'c'].fill(7)// [7, 7, 7]new Array(3).fill(7)// [7, 7, 7]
数组实例的 includes()
Array.prototype.includes方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的includes方法类似。ES2016 引入了该方法。
[1, 2, 3].includes(2) // true[1, 2, 3].includes(4) // false[1, 2, NaN].includes(NaN) // true
数组其他方法
// indexOf // var arr = [10,20,30]; // if(arr.indexOf(20) > -1){ // console.log("存在"); // }else{ // console.log("不存在"); // } // console.log(arr.includes(20)); // true // // NaN的问题 indexOf是无法验证NaN // var arr1 = [10,20,30,NaN] // console.log(arr1.indexOf(NaN)); // -1 // console.log(arr1.includes(NaN)); // true // var arr = [10,20,[30,40]]; // console.log(arr.flat()); // [10,20,30,40] // console.log(arr); // [10,20,[30,40]] // var arr1 = [10,20,[30,40,[50,60]]]; // // 可以传递参数,参数的数字代表拉平的维度 // console.log(arr1.flat(2)); // [10,20,30,40,50,60] var arr = [10,20,30,40]; console.log(arr[2]); console.log(arr.at(-3)); // 20
对象的扩展
属性的简洁表示法
ES6 允许在大括号里面,直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁。
const foo = 'bar';const baz = {foo};baz // {foo: "bar"}// 等同于const baz = {foo: foo};
上面代码中,变量foo直接写在大括号里面。这时,属性名就是变量名, 属性值就是变量值。下面是另一个例子。
function f(x, y) { return {x, y};}// 等同于function f(x, y) { return {x: x, y: y};}f(1, 2) // Object {x: 1, y: 2}
下面是一个实际的例子。
let birth = '2000/01/01';const Person = { name: '张三', //等同于birth: birth birth, // 等同于hello: function ()... hello() { console.log('我的名字是', this.name); }};
属性名表达式
JavaScript 定义对象的属性,有两种方法。
// 方法一obj.foo = true;// 方法二obj['a' + 'bc'] = 123;
上面代码的方法一是直接用标识符作为属性名,方法二是用表达式作为属性名,这时要将表达式放在方括号之内。 ES6 允许字面量定义对象时,用方法二(表达式)作为对象的属性名,即把表达式放在方括号内。
let propKey = 'foo';let obj = { [propKey]: true, ['a' + 'bc']: 123};
方法的 name 属性
函数的name属性,返回函数名。对象方法也是函数,因此也有name属性。
const person = { sayName() { console.log('hello!'); },};person.sayName.name // "sayName"
上面代码中,方法的name属性返回函数名(即方法名)
对象的扩展运算符
《数组的扩展》一章中,已经介绍过扩展运算符(...)。ES2018 将这个运算符引入了对象。 对象的解构赋值用于从一个对象取值,相当于将目标对象自身的所有可遍历的(enumerable)、但尚未被读取的属性,分配到指定的对象上面。所有的键和它们的值,都会拷贝到新对象上面。
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };x // 1y // 2z // { a: 3, b: 4 }
var job = ["itbaizhan","sxt"] var user = { name:"iwen", age:20, sex:"男", job, sayHello(){ console.log("Hello"); } } user.sayHello() function getPosition(){ var x = 100; var y = 100; return{ x, y } } var result = getPosition() console.log(result.x); console.log(result.y); let propKey = 'itbaizhan'; var info = { address:"地址", [propKey]:"haha" // 属性名表达式 } console.log(info[propKey]); var hello = {a:"a",b:"b"}; console.log({...hello}); console.log({...{b:1}, a: 1});
对象的新增方法-Object.is()
ES5 比较两个值是否相等,只有两个运算符:相等运算符(==)和严格相等运算符(===)。它们都有缺点,前者会自动转换数据类型,后者的NaN不等于自身,以及+0等于-0。JavaScript 缺乏一种运算,在所有环境中,只要两个值是一样的,它们就应该相等。 ES6 提出“Same-value equality”(同值相等)算法,用来解决这个问题。Object.is就是部署这个算法的新方法。它用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。
Object.is('foo', 'foo')// trueObject.is({}, {})// false
不同之处只有两个:一是+0不等于-0,二是NaN等于自身。
+0 === -0 //trueNaN === NaN // falseObject.is(+0, -0) // falseObject.is(NaN, NaN) // true
对象的新增方法-Object.assign()
Object.assign()方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。
const target = { a: 1 };const source1 = { b: 2 };const source2 = { c: 3 };Object.assign(target, source1, source2);target // {a:1, b:2, c:3}
同名属性的替换 对于这种嵌套的对象,一旦遇到同名属性, Object.assign() 的处理方法是替换,而不是添加
const target = { a: "hello" }const source = { a: "world" }console.log(Object.assign(target, source));
数组的处理 Object.assign() 可以用来处理数组,但是会把数组视为对象
Object.assign([1, 2, 3], [4, 5])// [4, 5, 3]
Object.assign() 把数组视为属性名为 0 、 1 、 2 的对象,因此源数组的 0号属性 4 覆盖了目标数组的 0 号属性 1
console.log(Object.is("hello","hello")); // true console.log(Object.is("hello","world")); // false console.log(NaN === NaN); // false console.log(+0 === -0); // true console.log(Object.is(NaN,NaN)); // true console.log(Object.is(+0,-0)); // false var obj1 = { name:"iwen", age:10 } var obj2 = { age:20 } var obj3 = { sex:"男" } console.log(Object.assign(obj1,obj2,obj3)); var arr1 = [1,2,3] // var arr2 = [4,5] console.log(Object.assign(arr1,arr2));// [4,5,3] let {keys,values,entries} = Object; // 对象的解构赋值 var user = { username:"iwen", password:"123" } for(let item of keys(user)){ console.log(item); // username password } for(let item of values(user)){ console.log(item); // iwen 123 } for(let item of entries(user)){ console.log(item); // ['username', 'iwen'] ['password', '123'] }
运算符的扩展
指数运算符
ES2016 新增了一个指数运算符( ** )
2 ** 2 // 42 ** 3 // 8
这个运算符的一个特点是右结合,而不是常见的左结合。多个指数运算符连用时,是从最右边开始计算的
// 相当于 2 ** (3 ** 2)2 ** 3 ** 2// 512
指数运算符可以与等号结合,形成一个新的赋值运算符( **= )
let a = 1.5; a **= 2;// 等同于 a = a * a;let b = 4; b **= 3;// 等同于 b = b * b * b;
链判断运算符
编程实务中,如果读取对象内部的某个属性,往往需要判断一下,属性的上层对象是否存在。比如,读取 message.body.user.firstName 这个属性,安全的写法是写成下面这样
// 错误的写法const firstName =message.body.user.firstName || 'default';// 正确的写法const firstName = (message && message.body && message.body.user && message.body.user.firstName) ||'default';
这样的层层判断非常麻烦,因此 ES2020 引入了 “ 链判断运算符 ”(optional chaining operator ) ?. ,简化上面的写法
const firstName = message?.body?.user?.firstName || 'default';
Null 判断运算符
读取参数的时候,如果某个参数的值是 null 或 undefined ,有时候需要为它们指定默认值。常见做法是通过 || 运算符指定默认值。
function add(x,y){ x = x || 100; y = y || 100; console.log(x+y);}add(0,0); // 200
上面的三行代码都通过 || 运算符指定默认值,但是这样写是错的。开发者的原意是,只要属性的值为 null 或 undefined ,默认值就会生效,但是属性的值如果为空字符串或 false 或 0 ,默认值也会生效 为了避免这种情况,ES2020 引入了一个新的 Null 判断运算符 ?? 。 它的行为类似 || ,但是只有运算符左侧的值为 null 或 undefined 时,才 会返回右侧的值。
function add(x,y){ x = x ?? 100; y = y ?? 100; console.log(x+y);}add(0,0);
逻辑赋值运算符 ES2021 引入了三个新的逻辑赋值运算符( logical assignment operators),将逻辑运算符与赋值运算符进行结合
// 或赋值运算符x ||= y// 等同于x || (x = y)// 与赋值运算符x &&= y// 等同于x && (x = y)// Null 赋值运算符x ??= y// 等同于x ?? (x = y)
它们的一个用途是,为变量或属性设置默认值
// 老的写法user.id = user.id || 1;// 新的写法user.id ||= 1;
user.id 属性如果不存在,则设为 1 ,新的写法比老的写法更紧凑一些
函数的扩展
函数参数的默认值
ES6 之前,不能直接为函数的参数指定默认值,只能采用变通的方法。
function log(x, y) { y = y || 'World'; console.log(x, y);}log('Hello') // Hello Worldlog('Hello', 'China') // Hello Chinalog('Hello', '') // Hello World
上面代码检查函数log的参数y有没有赋值,如果没有,则指定默认值为World。这种写法的缺点在于,如果参数y赋值了,但是对应的布尔值为false,则该赋值不起作用。就像上面代码的最后一行,参数y等于空字符,结果被改为默认值。
为了避免这个问题,通常需要先判断一下参数y是否被赋值,如果没有,再等于默认值。
if (typeof y === 'undefined') { y = 'World';}
ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面。
function log(x, y = 'World') { console.log(x, y);}log('Hello') // Hello Worldlog('Hello', 'China') // Hello Chinalog('Hello', '') // Hello
可以看到,ES6 的写法比 ES5 简洁许多,而且非常自然。下面是另一个例子
function Point(x = 0, y = 0) { this.x = x; this.y = y;}const p = new Point();p // { x: 0, y: 0 }
rest 参数
ES6 引入 rest 参数(形式为...变量名),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
function add(...values) { let sum = 0; for (var val of values) { sum += val; } return sum;}add(2, 5, 3) // 10
上面代码的add函数是一个求和函数,利用 rest 参数,可以向该函数传入任意数目的参数
注意, rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错
function add(a, ...b,c) {} // 报错
函数的 length 属性,不包括 rest 参数
function add(a, ...b) {}console.log(add.length); // 1
严格模式 从 ES5 开始,函数内部可以设定为严格模式
function doSomething(a, b) { 'use strict'; // code}
ES2016 做了一点修改,规定只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。
// 报错function doSomething(a, b = a) { 'use strict'; // code}
name 属性
函数的name属性,返回该函数的函数名。
function foo() {}foo.name // "foo"
参数默认值的位置
function add(x = 1, y) { console.log(x+y);}add(10) // NaN
函数的 length 属性
指定了默认值以后,函数的 length 属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后, length 属性将失真
function fn1(x){}console.log(fn1.length); // 1function fn2(x=1){}console.log(fn2.length); // 0
应用 利用参数默认值,可以指定某一个参数不得省略,如果省略就抛出一个错误
function missingParameter() { throw new Error('Missing parameter');}function add(x = missingParameter()) { console.log(x);}add() // Missing parameter
箭头函数
ES6 允许使用“箭头”(=>)定义函数
var f = v => v;// 等同于var f = function (v) { return v;};
如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分
var f = () => 5;// 等同于var f = function () { return 5 };var sum = (num1, num2) => num1 + num2;// 等同于var sum = function(num1, num2) { return num1 + num2;};
如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回
var sum = (num1, num2) => { return num1 + num2; }
由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错
// 报错let getTempItem = id => { id: id, name: "Temp" };// 不报错let getTempItem = id => ({ id: id, name: "Temp" });
箭头函数的一个用处是简化回调函数。
// 正常函数写法[1,2,3].map(function (x) { return x * x;});// 箭头函数写法[1,2,3].map(x => x * x);
使用注意点 箭头函数有几个使用注意点。
(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
上面四点中,第一点尤其值得注意。this对象的指向是可变的,但是在箭头函数中,它是固定的
function foo() { setTimeout(() => { console.log('id:', this.id); }, 100);}var id = 21;foo.call({ id: 42 });// id: 42
箭头函数里面根本没有自己的 this ,而是引用外层的 this
var name = "itbaizhan"var user = { name:"iwen", getName(){ setTimeout(() =>{ console.log(this.name); // iwen }) }}user.getName()
Symbol
基础知识 ES5 的对象属性名都是字符串,这容易造成属性名的冲突。比如,你使用了一个他人提供的对象,但又想为这个对象添加新的方法 (mixin 模式),新方法的名字就有可能与现有方法产生冲突。如 果有一种机制,保证每个属性的名字都是独一无二的就好了,这样 就从根本上防止属性名的冲突。这就是 ES6 引入 Symbol 的原因。 ES6 引入了一种新的原始数据类型 Symbol ,表示独一无二的值。它属于 JavaScript 语言的数据类型之一,其他数据类型是: undefined 、 null 、布尔值( Boolean )、字符串( String )、数值 ( Number )、对象( Object )
let s = Symbol();typeof s// "symbol"
注意, Symbol 函数前不能使用 new 命令,否则会报错。这是因为 生成的 Symbol 是一个原始类型的值,不是对象。也就是说, 由于 Symbol 值不是对象,所以不能添加属性。基本上,它是 一种类似于字符串的数据类型。
Symbol 函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分
let s1 = Symbol('itbaizhan'); let s2 = Symbol('sxt'); s1 // Symbol(itbaizhan) s2 // Symbol(sxt)
s1 和 s2 是两个 Symbol 值。如果不加参数,它们在控制台的输出都是 Symbol() ,不利于区分。有了参数以后,就等于为它们加上了描述,输出的时候就能够分清,到底是哪一个值
注意, Symbol 函数的参数只是表示对当前 Symbol 值的描述,因 此相同参数的 Symbol 函数的返回值是不相等的。
// 没有参数的情况let s1 = Symbol();let s2 = Symbol();s1 === s2 // false// 有参数的情况let s1 = Symbol('itbaizhan');let s2 = Symbol('itbaizhan');s1 === s2 // false
s1 和 s2 都是 Symbol 函数的返回值,而且参数相同,但是它们是不相等的
实例属性description
ES2019 提供了一个实例属性 description ,直接返回 Symbol 的描述
const sym = Symbol('itbaizhan');sym.description // "itbaizhan"
作为属性名的 Symbol 由于每一个 Symbol 值都是不相等的,这意味着 Symbol 值可以作为标识符,用于对象的属性名,就能保证不会出现同名的属性。这对于一个对象由多个模块构成的情况非常有用,能防止某一个键被不小心改写或覆盖
let mySymbol = Symbol("name");var user = { name:"iwen"}var info = { [mySymbol]:"itbaizhan", }var newUser = Object.assign(user,info)console.log(newUser[mySymbol]);
属性名的遍历
Symbol 作为属性名,遍历对象的时候,该属性不会出现在 for...in 、 for...of 循环中,也不会被 Object.keys() 返回但是,它也不是私有属性,有一个 Object.getOwnPropertySymbols() 方法,可 以获取指定对象的所有 Symbol 属性名。该方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值
let mySymbol = Symbol("age");var user = { name:"iwen", [mySymbol]:20}for(let item in user){ console.log(item); // name}const objectSymbols = Object.getOwnPropertySymbols(user);console.log(objectSymbols); // [Symbol(age)]
Symbol.for()
有时,我们希望重新使用同一个 Symbol 值, Symbol.for() 方法可以做到这一点。
let s1 = Symbol.for('itbaizhan');let s2 = Symbol.for('itbaizhan');console.log(s1 === s2); // true
var user = { name:"iwen" } /* Symbol: 1. 新的原始数据类型:字符串和数字 2. 他不能使用new关键字去生成 3. 他是独一无二的 4. Symbol可以接受一个参数,参数的目的是为了方便识别不同的Symbol 5. Symbol的参数只是描述,哪怕描述内容相同,两个值也是不同的 6. 可以使用description获取一个Symbol的描述 7. Symbol作为对象的属性名,可以保证不重复 8. Symbol作为对象的属性名,读取的时候要用数组的方式 9. Symbol作为对象的属性名,读取的时候只能使用getOwnPropertySymbols() 10. 如果要创建重复的Symbol的值,需要使用Symbol.for() */ var s = Symbol(); console.log(typeof s); // symbol // number string object var s1 = Symbol("itbaizhan"); var s2 = Symbol("sxt"); console.log(s1,s2); var s3 = Symbol("iwen"); var s4 = Symbol("iwen"); console.log(s3 === s4); // false console.log(s1.description); // itbaizhan var info2Symbol = Symbol("name") var info1 = { name:"iwen" } var info2 = { [info2Symbol]:"ime" } var newInfo = Object.assign(info1,info2); // {name:'ime', [info2Symbol]:"ime"} console.log(newInfo.name); // iwen // 用数组的方式读取Symbol属性 console.log(newInfo[info2Symbol]); // ime var info3Symbol = Symbol("job") var info3 = { name:"iwen", age:20, [info3Symbol]:"itbaizhan" } for(let key in info3){ console.log(key); } console.log(Object.getOwnPropertySymbols(info3)); // Symbol("job") var itSymbol1 = Symbol.for("itbaizhan") var itSymbol2 = Symbol.for("itbaizhan") console.log(itSymbol1 === itSymbol2); // true