文章目录
- let
- const
- 解构赋值
- 解构赋值的用途
- 数组的解构赋值
- 对象的解构赋值
- 字符串的解构赋值
- 函数参数的解构赋值
- 模板字符串
- 字符串新增方法
- includes(), startsWith(), endsWith()
- repeat()
- padStart(),padEnd()
- trimStart(),trimEnd()
- replaceAll()
- 属性的简洁表示法
- 数值的扩展
- 函数的扩展
- 函数参数的默认值
- 箭头函数
- rest 参数(...变量名)
- 数组的扩展
- 扩展运算符
- Array.from()
- Array.of()
- flat、flatMap
- 对象的扩展
- 属性的简洁表示法
- 解构赋值
- 对象的新增方法
- Object.is()
- Object.assign()
- Object.getOwnPropertyDescriptors()
- Object.keys(),Object.values(),Object.entries()
- Set
- 实例化对象
- ES6模块化暴露、引入模块
let
ES6 新增了let命令,用来声明变量,它的用法类似于var。
1.let所声明的变量,只在let命令所在的代码块(块级作用域:{}、if、else、while、for)内有效。
{
let a = 10;
var b = 1;
}
a // ReferenceError: a is not defined.
b // 1
2.变量不能重复声明。
let不允许在相同作用域内,重复声明同一个变量。但for循环有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。
for (let i = 0; i < 3; i++) {
let i = 'abc';
console.log(i);
}
// abc
// abc
// abc
上面代码正确运行,输出了 3 次abc。这表明函数内部的变量i与循环变量i不在同一个作用域,有各自单独的作用域(同一个作用域不可使用 let 重复声明同一个变量)。
3.不存在变量提升(即变量可以在声明之前使用,值为undefined)。
主要是为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为。
4.暂时性死区(temporal dead zone,简称 TDZ)
在代码块内,使用let命令声明变量之前,该变量都是不可用的。
var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}
typeof x; // ReferenceError
let x;
typeof undeclared_variable // "undefined"
5.不影响作用域链。
当前作用域没有的变量会向上一级寻找。
let name = "zagiee";
function fn(){
console.log(name);
}
fn();//zagiee
ES6 允许块级作用域的任意嵌套。下面代码使用了一个五层的块级作用域,每一层都是一个单独的作用域。第四层作用域无法读取第五层作用域的内部变量。内层作用域可以定义外层作用域的同名变量。
{{{{
{let insane = 'Hello World'}
console.log(insane); // 报错
}}}};
const
1.一定要赋初始值。
2.一般常量使用大写。
3.与let命令相同:只在声明所在的块级作用域内有效,const命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。
4.常量的值不能修改,但对于数组和对象的元素修改,不算对常量的修改,不会报错。
const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),储存的是一个地址,这个地址指向一个对象。不可变的只是这个地址,即不能把它指向另一个地址,但对象本身是可变的,所以依然可以为其添加新属性。
const a = [];
a.push('Hello'); // 可执行
a.length = 0; // 可执行,执行后a变成空数组了
a = ['Dave']; // 报错
解构赋值
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值。
解构赋值的用途
(1)交换变量的值
let x = 1;
let y = 2;
[x, y] = [y, x];
(2)从函数返回多个值
函数只能返回一个值,如果要返回多个值,只能将它们放在数组或对象里返回。有了解构赋值,取出这些值就非常方便。
// 返回一个数组
function example() {
return [1, 2, 3];
}
let [a, b, c] = example();
// 返回一个对象
function example() {
return {
foo: 1,
bar: 2
};
}
let { foo, bar } = example();
(3)提取 JSON 数据
解构赋值对提取 JSON 对象中的数据,尤其有用。
let jsonData = {
id: 42,
status: "OK",
data: [867, 5309]
};
let { id, status, data } = jsonData;
(4)遍历 Map 结构
const map = new Map();
map.set('first', 'hello');
map.set('second', 'world');
for (let [key, value] of map) {
console.log(key + " is " + value);
}
// first is hello
// second is world
数组的解构赋值
let [a, b, c] = [1, 2, 3];//相当于声明了三个变量,还对它们进行了赋值。
let [ , , third] = ["foo", "bar", "baz"];
third // "baz"
let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]
不完全解构,即等号左边的模式,只匹配一部分的等号右边的数组。
let [x, y] = [1, 2, 3];
x // 1
y // 2
如果解构不成功,变量的值就等于undefined。
let [x, y, ...z] = ['a'];
x // "a"
y // undefined
z // []
如果等号的右边不是数组(或者严格地说,不是可遍历的结构),那么将会报错。
事实上,只要某种数据结构具有 Iterator 接口,都可以采用数组形式的解构赋值。
let [x, y, z] = new Set(['a', 'b', 'c']);
x // "a"
解构赋值允许指定默认值。
let [x, y = 'b'] = ['a']; // x='a', y='b'
let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'
let [x = 1] = [null];//x = null,只有当一个数组成员严格等于undefined,默认值才会生效。
对象的解构赋值
对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。变量没有对应的同名属性,会导致取不到值,最后等于undefined。
let { rat, foo } = { foo: 'aaa', bar: 'bbb' };
foo // "aaa"
rat// undefined
对象的解构赋值,可以很方便地将现有对象的方法,赋值到某个变量。
let { log, sin, cos } = Math;
const { log } = console;
log('hello') // hello
也就是说,对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。
let { foo: baz, bar} = { foo: 'aaa', bar: 'bbb' };
baz // "aaa",foo是匹配的模式,baz才是变量。真正被赋值的是变量baz,而不是模式foo。
foo // error: foo is not defined
baz // "bbb"
解构赋值允许指定默认值。
var {x, y = 5} = {x: 1};
x // 1
y // 5
如果要将一个已经声明的变量用于解构赋值,必须非常小心。
// 错误的写法
let x;
{x} = {x: 1};//{x}会被理解成一个代码块
// SyntaxError: syntax error
// 正确的写法
let x;
({x} = {x: 1});
字符串的解构赋值
字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象。
const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"
类似数组的对象都有一个length属性,因此还可以对这个属性解构赋值。
let {length : len} = 'hello';
len // 5
函数参数的解构赋值
[[1, 2], [3, 4]].map(([a, b]) => a + b);
// [ 3, 7 ]
模板字符串
模板字符串是增强版的字符串,用反引号(`)标识。
1.它可以当作普通字符串使用。
2.也可以用来定义多行字符串(所有的空格和缩进都会被保留)。
3.在字符串中嵌入变量(将变量名写在${}之中,{}中可以写任意的 JavaScript 表达式)。
let name = "Bob", time = "today";
`Hello ${name},
how are you ${time}?`
4.如果在模板字符串中需要使用反引号,则前面要用反斜杠转义。
let greeting = `\`Yo\` World!`;
5.模板字符串内还可以调用函数。
function fn() {
return "Hello World";
}
`foo ${fn()} bar`// foo Hello World bar
字符串新增方法
includes(), startsWith(), endsWith()
includes():返回布尔值,表示是否找到了参数字符串。
startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。
endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。
三个方法都支持第二个参数,表示开始搜索的位置。
let s = 'Hello world!';
s.startsWith('world', 6) // true
s.endsWith('Hello', 5) // true使用第二个参数n时,endsWith的行为与其他两个方法有所不同。它针对前n个字符,而其他两个方法针对从第n个位置直到字符串结束。
s.includes('Hello', 6) // false
repeat()
repeat方法返回一个新字符串,表示将原字符串重复n次。
'hello'.repeat(2) // "hellohello"
'na'.repeat(0) // ""
padStart(),padEnd()
如果某个字符串不够指定长度,会在头部(padStart())或尾部(padEnd())补全。
'x'.padStart(5, 'ab') // 'ababx'
'x'.padStart(4, 'ab') // 'abax'
'x'.padEnd(5, 'ab') // 'xabab'
'x'.padEnd(4, 'ab') // 'xaba'
'12'.padStart(10, '0') // "0000000012"
'09-12'.padStart(10, 'YYYY-MM-DD') // "YYYY-09-12"
第一个参数是字符串补全生效的最大长度,第二个参数是用来补全的字符串。
如果原字符串的长度,等于或大于最大长度,则字符串补全不生效,返回原字符串。
如果用来补全的字符串与原字符串,两者的长度之和超过了最大长度,则会截去超出位数的补全字符串。
如果省略第二个参数,默认使用空格补全长度。
trimStart(),trimEnd()
它们的行为与trim()一致,trimStart()消除字符串头部的空格,trimEnd()消除尾部的空格。它们返回的都是新字符串,不会修改原始字符串。
const s = ' abc ';
s.trim() // "abc"
s.trimStart() // "abc "
s.trimEnd() // " abc"
replaceAll()
字符串的实例方法replace()只能替换第一个匹配。replaceAll()方法,可以一次性替换所有匹配。用法与replace()相同,返回一个新字符串,不会改变原字符串。
'aabbcc'.replaceAll('b', '_')// 'aa__cc'
属性的简洁表示法
ES6 允许在大括号里面,直接写入变量和函数,作为对象的属性和方法。
//属性简写
const foo = 'bar';
const baz = {foo};
baz // {foo: "bar"}
// 等同于const baz = {foo: foo};
//方法简写
const o = {
method() {//method: function() {
return "Hello!";
}
};
数值的扩展
用Number方法将0b和0o前缀的二进制和八进制字符串数值转为十进制
Number('0b111') // 7
Number('0o10') // 8
新的数据类型 BigInt只用来表示整数,没有位数的限制,任何位数的整数都可以精确表示。为了与 Number 类型区别,BigInt 类型的数据必须添加后缀n。
0b1101n // 二进制
0o777n // 八进制
0xFFn // 十六进制
42n === 42 // false,BigInt 与普通整数是两种值,它们之间并不相等。
typeof 123n // 'bigint'
JavaScript 原生提供BigInt函数,可以用它生成 BigInt 类型的数值。转换规则基本与Number()一致,将其他类型的值转为 BigInt。
BigInt(123) // 123n
BigInt('123') // 123n
BigInt('123n') // SyntaxError
BigInt(1.5) // RangeError
函数的扩展
函数参数的默认值
ES6 之前,不能直接为函数的参数指定默认值,只能采用变通的方法。
if (typeof y === 'undefined') {
y = 'World';
}
ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面。
function log(x, y = 'World') {
console.log(x, y);
}
log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello
function foo({x, y = 5}) {
console.log(x, y);
}
foo({}) // undefined 5
foo({x: 1}) // 1 5
foo({x: 1, y: 2}) // 1 2
foo() // TypeError: Cannot read property 'x' of undefined:解决方法:第一行换成function foo({x, y = 5} = {}) {
与解构赋值相结合。
箭头函数
对于普通函数来说,内部的this指向函数运行时所在的对象。而箭头函数没有自己的this对象,this始终指向定义时上层作用域中的this,由于箭头函数没有自己的this,所以当然也就不能用call()、apply()、bind()这些方法去改变this的指向。
var f = v => v;//当形参只有一个时可以省略小括号,当代码体只有一句时可以省略花括号和return
// 等同于
var f = function (v) {
return v;
};
window.num='e'
var sum = (num) => {
return this.num;
}
sum()//"e"
箭头函数的一个用处是简化回调函数。
// 普通函数写法
[1,2,3].map(function (x) {
return x * x;
});
// 箭头函数写法
[1,2,3].map(x => x * x);
不可以当作构造函数,也就是说,不可以对箭头函数使用new命令,否则会抛出一个错误。
不能使用argument,下面代码会报argument is not defined
箭头函数的应用场景:
使用箭头函数后,
箭头函数适用于与this无关的回调:定时器,数组的方法回调;不适用于与this有关的回调:事件回调,对象的方法。
rest 参数(…变量名)
用于获取函数的多余参数,这样就不需要使用arguments对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
function test(...values) {
return values;
}
test(2, 5, 3) // [2, 5, 3]
注意,rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。
数组的扩展
扩展运算符
扩展运算符(spread)是三个点(…)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。
console.log(1, ...[2, 3, 4], 5)
// 1 2 3 4 5
该运算符主要用于函数调用。
function add(x, y) {
return x + y;
}
const numbers = [4, 38];
add(...numbers) // 42
其它应用:
//求数组最大元素:
Math.max.apply(null, [14, 3, 77])// ES5 的写法
Math.max(...[14, 3, 77])// ES6 的写法
// 等同于
Math.max(14, 3, 77);//由于 JavaScript 不提供求数组最大元素的函数,所以只能套用Math.max()函数,将数组转为一个参数序列,然后求最大值。
//通过push()函数,将一个数组添加到另一个数组的尾部:
let arr1 = [0, 1, 2];
let arr2 = [3, 4, 5];
arr1.push(...arr2);
复制数组,直接复制的话,a2并不是a1的克隆,而是指向同一份数据的另一个指针。修改a2,会直接导致a1的变化。
const a1 = [1, 2];
const a2 = a1;
a2[0] = 2;
a1 // [2, 2]
扩展运算符提供了复制数组的简便写法。下面的两种写法,a2都是a1的克隆。
const a1 = [1, 2];
// 写法一
const a2 = [...a1];
// 写法二
const [...a2] = a1;
扩展运算符提供了数组合并的新写法。
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' ]
//两种方法都是对原数组成员的引用(浅拷贝)。如果修改了引用指向的值,会同步反映到新数组。
扩展运算符可以与解构赋值结合起来,用于生成数组。将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。
const [first, ...rest] = [1, 2, 3, 4, 5];
first // 1
rest // [2, 3, 4, 5]
扩展运算符还可以将字符串转为真正的数组。
[...'hello']// [ "h", "e", "l", "l", "o" ]
Array.from()
Array.from()方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)。
let arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
};//一个类似数组的对象,Array.from()将它转为真正的数组。
// ES5 的写法
var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']
// ES6 的写法
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
字符串和 Set 结构都具有 Iterator 接口,因此可以被Array.from()转为真正的数组。
Array.from('hello')// ['h', 'e', 'l', 'l', 'o']
let namesSet = new Set(['a', 'b'])
Array.from(namesSet) // ['a', 'b']
Array.of()
Array.of()方法用于将一组值,转换为数组。如果没有参数,就返回一个空数组。
Array.of(3, 11, 8) // [3,11,8]
Array.of(3) // [3]
这个方法的主要目的,是弥补数组构造函数Array()的不足。因为参数个数的不同,会导致Array()的行为有差异。
Array() // []
Array(4) // [, , , ,]参数只有一个正整数时,实际上是指定数组的长度。
Array(3, 11, 8) // [3, 11, 8]只有当参数个数不少于 2 个时,Array()才会返回由参数组成的新数组。
flat、flatMap
flat()将多维数组转化为低维数组:(参数是深度,默认为1,降1维)
console.log([1,2,3,[4,5],6].flat());//[1, 2, 3, 4, 5, 6]
console.log([1,2,3,[4,5,[7,8]],6].flat());//[1, 2, 3, 4, 5, [7,8], 6]
console.log([1,2,3,[4,5,[7,8]],6].flat(2));//[1, 2, 3, 4, 5, 7,8, 6]
flatMap()相当于两个操作的结合:如果Map返回的结果是多维数组,可用flat降维。
对象的扩展
属性的简洁表示法
ES6 允许在大括号里面,直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁。
const foo = 'bar';
const baz = {foo};
baz // {foo: "bar"}
// 等同于
const baz = {foo: foo};
解构赋值
对象的解构赋值用于从一个对象取值,相当于将目标对象自身的所有可遍历的、但尚未被读取的属性,分配到指定的对象上面。所有的键和它们的值,都会拷贝到新对象上面。
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x // 1
y // 2
z // { a: 3, b: 4 }
对象的新增方法
Object.is()
ES5 比较两个值是否相等,只有两个运算符:相等运算符(==)
和严格相等运算符(===)
。它们都有缺点,前者会自动转换数据类型,后者的NaN不等于自身,以及+0等于-0。
Object.is()用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。不同之处只有两个:一是+0不等于-0,二是NaN等于自身。
Object.is('foo', 'foo')// true
Object.is({}, {})// false
+0 === -0 //true
NaN === NaN // false
Object.is(+0, -0) // false
Object.is(NaN, NaN) // true
Object.assign()
Object.assign()方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。
const target = { a: 1, b: 1 };
const source1 = { b: 2, c: 2 };
const source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
Object.assign()方法的第一个参数是目标对象,后面的参数都是源对象。注意,如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。
Object.getOwnPropertyDescriptors()
返回指定对象所有自身属性(非继承属性)的描述对象。
const obj = {
foo: 123,
get bar() { return 'abc' }
};
Object.getOwnPropertyDescriptors(obj)
// { foo:
// { value: 123,
// writable: true,
// enumerable: true,
// configurable: true },
// bar:
// { get: [Function: get bar],
// set: undefined,
// enumerable: true,
// configurable: true } }
该方法的引入目的,主要是为了解决Object.assign()无法正确拷贝get属性和set属性的问题。
const source = {
set foo(value) {
console.log(value);
}
};
const target1 = {};
Object.assign(target1, source);
Object.getOwnPropertyDescriptor(target1, 'foo')
// { value: undefined,
// writable: true,
// enumerable: true,
// configurable: true }
上面代码中,source对象的foo属性的值是一个赋值函数,Object.assign方法将这个属性拷贝给target1对象,结果该属性的值变成了undefined。这是因为Object.assign方法总是拷贝一个属性的值,而不会拷贝它背后的赋值方法或取值方法。
这时,Object.getOwnPropertyDescriptors()方法配合Object.defineProperties()方法,就可以实现正确拷贝。
const source = {
set foo(value) {
console.log(value);
}
};
const target2 = {};
Object.defineProperties(target2, Object.getOwnPropertyDescriptors(source));
Object.getOwnPropertyDescriptor(target2, 'foo')
// { get: undefined,
// set: [Function: set foo],
// enumerable: true,
// configurable: true }
Object.keys(),Object.values(),Object.entries()
Object.keys()返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历属性的键名。
var obj = { foo: 'bar', baz: 42 };
Object.keys(obj)// ["foo", "baz"]
Object.values()返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历属性的键值。
const obj = { foo: 'bar', baz: 42 };
Object.values(obj)// ["bar", 42]
Object.values('foo')// ['f', 'o', 'o']
Object.entries()将对象转化为一个二维数组,成员是参数对象自身的(不含继承的)所有可遍历属性的键值对数组。
const obj = { foo: 'bar', baz: 42 };
Object.entries(obj)// [ ["foo", "bar"], ["baz", 42] ]
Object.fromEntries()方法是Object.entries()的逆操作,用于将一个键值对数组转为对象。该方法的主要目的,是将键值对的数据结构还原为对象,因此特别适合将 Map 结构转为对象。
const entries = new Map([
['foo', 'bar'],
['baz', 42]
]);
Object.fromEntries(entries)// { foo: "bar", baz: 42 }
Set
去除数组的重复成员
[...new Set([1, 2, 2, 2, 5, 3, 4, 5])]//方法1
Array.from(new Set([1, 2, 2, 2, 5, 3, 4, 5]));//方法2
实例化对象
ES5通过构造函数实例化一个对象的语法:
function Phone (brand, price){
this.brand = brand;
this.price = price;
}
// 添加方法
Phone.prototype.call = function () {
console.log("打电话!");
}
// 实例化对象
let Oneplus = new Phone('一加','4000');
console.log(Oneplus);
Oneplus.call();
ES6通过class实例化对象的实现:
class Phone{
constructor(brand, price) {//构造方法名字不能改
this.brand = brand;
this.price = price;
}
call(){//不能用es5 的方法写call:function(){},因为call内部的this会指向window对象。
console.log("打电话!!");
}
}
let Huawei = new Phone('华为',5000);
console.log(Huawei);
Huawei.call();
静态属性属于类而不属于实例对象:
class Phone{
static name = 'phone';
}
Phone.price = 1000;
Phone.prototype.tag = 'fashion';
let Huawei = new Phone('华为',5000);
console.log(Huawei.name);//undefined
console.log(Phone.name);//phone
console.log(Huawei.price);//undefined
console.log(Huawei.tag);//fashion
ES6模块化暴露、引入模块
m2中使用m1的功能:
m1.js:暴露
//1.分别暴露
//export let name = 'zagiee';
//export function eat(){
// console.log("eat");
//}
//2.统一暴露
//let name = 'zagiee';
//function eat(){
// console.log("eat");
//}
//export {name, eat};
//3.默认暴露
export default {
name : 'zagiee',
eat : function(){
console.log('eat');
}
}
m2.html:引入
<script type="module">
//1.通用的导入方式
// import * as m1 from "m1.js";
// console.log(m1.name);
//m1.eat();//若用的是默认暴露时,要写成m1.default.eat();
//2.解构赋值方式
// import {name, eat} from "m1.js";
// console.log(name);//直接用即可,无需前缀
// import {default as m1} from "m1.js";//只针对默认暴露
// console.log(m1.name);
// 3.简便方式,只针对默认暴露
//import m1 from "m1.js";
//console.log(m1.name);
//4.动态import,用的时候再导入提高执行效率。
const btn = document.getElementById('btn');
btn.onclick = function(){
import('./m1.js').then(module=>{//返回结果是一个promise对象,成功的值就是m1.js暴露出来的对象。
module.eat();
})
}
</script>