var 、let、const的区别
(1)块级作用域:var不存在块级作用域,而let和const有块级作用域;
(2)变量提升:var存在变量提升,let、const不存在;
(3)给全局添加属性:使用var声明的变量会挂载在window上,而let和const声明的变量不会;
(4)重复声明:在同一作用域下,var声明变量时可以重复声明,后者会覆盖前者,而let和const不允许重复声明;
(5)暂时性死区(声明变量之前变量不可用):var声明的变量不存在暂时性死区,而let和const则会存在;
(6)初始值设置:在声明变量时,var和let可以不用设置初始值,而const声明的变量必须设置初始值;
(7)指针指向:var、let声明的变量可以更改指针的指向(可以重新赋值),const声明的变量不允许指针的指向
解构赋值
解构赋值是ES6提供的一种新的提取数据的方式,这种方式能够从对象或者数组中有针对性的拿到自己想要的数据。
数组的解构赋值
let [a, b, c] = [1, 2, 3];
a //1
b //2
c //3
这样的写法a、b、c分别被赋予了数组的第0、1、2个索引值,即a为1,b为2,c为3
使用嵌套数组进行解构
let [foo, [[bar], baz]] = [1, [[2], 3]];
foo // 1
bar // 2
baz // 3
let [ , , third] = ["foo", "bar", "baz"];
third // "baz"
let [x, , y] = [1, 2, 3];
x // 1
y // 3
let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]
let [x, y, ...z] = ['a'];
x // "a"
y // undefined // 如果解构不成功,变量的值就等于undefined
z // []
不完全解构
同时还可以通过给左侧的变量数组设置空占位的方式,实现数组对某几个元素的精准提取:
let [x, y] = [1, 2, 3];
x // 1
y // 2
let [a, [b], d] = [1, [2, 3], 4];
a // 1
b // 2
d // 4
对象的解构赋值
对象的解构赋值相比较数组来说稍微复杂,但是也更显得强大。在解构对象的时候,是以属性名为匹配条件,来提取想要的数据。
例
const stu = {
name: '张三',
age: 20,
}
// 不使用对象解构
let stuName = stu.name;
let stuAge = stu.age;
console.log(stuName, stuAge); //张三 20
使用完整的对象解构;
let { name: stuName, age: stuAge } = stu;
console.log(stuName, stuAge); //张三 20
// 使用简写的对象解构
let { name, age } = stu;
console.log(name, age); //张三 20
两种情况
const stu = {
name: "张三",
age: 20,
};
// 不匹配的情况
let { name, job } = stu;
console.log(name, job); //张三 undefined
// 默认赋值
let { name, job = "student" } = stu;
console.log(name, job); //张三 student
提取高度嵌套的对象里的指定属性
比如下面这个嵌套比较深的对象:
const school = {
classes: {
stu: {
name: 'Bob',
age: 24,
}
}
}
当我们需要里面的name属性时,如果使用老办法来提取它:
const { name } = school
这样显然是不奏效的,因为school中没有name这个属性,它是属于school对象的"儿子的儿子"对象里面的。想要把name提取出来,一种笨的办法就是逐层解构:
const { classes } = school
const { stu } = classes
const { name } = stu
name // 'Bob'
但是还有一种更标准的做法,可以用一行代码来解决这个问题:
const { classes: { stu: { name } }} = school
console.log(name) // 'Bob'
可以在解构出来的变量名右侧,通过冒号+{目标属性名}这种形式,进一步解构它,一直解构到拿到目标数据为止。
模板字符串
模板字符串(template string)是增强版的字符串,用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。
例:
//ES5 Version
var greetOne = 'Hi I\'m Mark';
//ES6 Version
let greetTwo = `Hi I'm Mark`;
//使用模板字符串达到多行的效果
//ES5 Version
var lastWordsOne = '\n'
+ ' I \n'
+ ' Am \n'
+ 'Iron Man \n';
//ES6 Version
let lastWordsTwo = `
I
Am
Iron Man
`;
//使用模板字符串中的 ${} 带入变量值
//ES5 Version
function fnOne(name) {
return 'Hello ' + name + '!';
}
//ES6 Version
function fnTwo(name) {
return `Hello ${name} !`;
}
函数的扩展
rest参数
ES6引入了rest参数(形式...变量名
),用于获取函数的多余的参数,这样就不需要使用argument
对象了。
function add(...value) {
let sum = 0;
for (var val of value) {
sum += val;
}
return sum
}
const result = add(1, 2, 3, 4, 5)
console.log(result); //15
注意:rest参数
要写到最后一个参数的位置上,否则会报错。
function f(a, ...b, c) {
// ...
}
箭头函数
ES6中允许使用"箭头" (=>
) 定义函数
var f = () => 5;
// 等同于
var f = function () { return 5 };
var sum = (num1, num2) => num1 + num2;
// 等同于
var sum = function (num1, num2) {
return num1 + num2;
};
- 当参数只有一个的时候可以省略外面的
括号()
- 当返回的语句只有一句的时候可以省略外面的
大括号{}
var f = (n) => {
return n * n;
}
//上面的代码可以简写成这样
var f = n => n * n;
箭头函数与普通函数的区别
(1)箭头函数比普通函数更加简洁
-
如果没有参数,就直接写一个空括号即可;
-
如果只有一个参数,可以省去参数的括号;
-
如果有多个参数,用逗号分割;
-
如果函数体的返回值只有一句,可以省略大括号;
-
如果函数体不需要返回值,且只有一句话,可以给这个语句前面加一个void关键字。
(2)箭头函数没有自己的this
箭头函数不会创建自己的this,它只会在自己作用域的上一层继承this。所以箭头函数中this的指向在它在定义时就已经确定了,之后不会改变。
(3)箭头函数继承来的this指向永远不会改变
var id = 'GLOBAL';
var obj = {
id: 'OBJ',
a: function(){
console.log(this.id);
},
b: () => {
console.log(this.id);
}
};
obj.a(); // 'OBJ'
obj.b(); // 'GLOBAL'
new obj.a() // undefined
new obj.b() // Uncaught TypeError: obj.b is not a constructor
对象obj的方法b是用箭头函数定义的,这个箭头函数中的this就会永远指向它定义时所处的作用域的上一层非箭头函数作用域中的this(在这里this指向了全局作用域中的this),即使这个函数是由obj这个对象调用,this依旧指向上一层非箭头函数作用域中的this。
(4)call()、apply()、bind()等方法不能改变箭头函数中this的指向
var id = 'Global';
let fun1 = () => {
console.log(this.id)
};
fun1(); // 'Global'
fun1.call({id: 'Obj'}); // 'Global'
fun1.apply({id: 'Obj'}); // 'Global'
fun1.bind({id: 'Obj'})(); // 'Global'
(5)箭头函数不能作为构造函数使用
由于箭头函数没有自己的this,且this指向外层的执行环境,且不能改变执行,因此不能将this指向new出来的对象,所以箭头函数不能当做构造函数来使用
(6)箭头函数没有自己的arguments
箭头函数中访问arguments实际上是它外层函数的arguments值
(7)箭头函数没有prototype
(8)箭头函数不能用作Generator函数,也不能使用一两点关键字
扩展运算符(...)
- 在对象中,扩展运算符会将参数对象中的所有可遍历属性,拷贝到当前对象中。
let bar = { a: 1, b: 2 };
let baz = { ...bar }; // { a: 1, b: 2 }
上述的方法等价于
let bar = { a: 1, b: 2 };
let baz = Object.assign({}, bar); // { a: 1, b: 2 }
注意:扩展运算符对对象实例的拷贝是属于浅拷贝。
- 在数组中,可以将一个数组转变为用逗号分隔的参数序列,且每次只能展开一层数组。
console.log(...[1, 2, 3]) // 1 2 3
console.log(...[1, [2, 3, 4], 5]) // 1 [2, 3, 4] 5
Set和Map数据结构
Set
Set是一种新的数据结构,它类似于数据,但是里面的成员都是惟一的,没有重复的值。
Set本身是一个构造函数,用来生成Set数据结构的
const set = new Set();
[2, 3, 5, 4, 5, 2, 2].forEach(x => set.add(x));
for (let i of set) {
console.log(i);
}
console.log(set);
Set实例的属性和方法
属性:
Set.prototype.constructor
:构造函数,默认就是Set函数;Set.prototype.size
:返回Set实例的成员总数;
操作方法:
Set.prototype.add(value)
:添加某个值,返回Set结构本身;Set.prototype.delete(value)
:删除某个值,返回一个Boolean,表示删除成功;Set.prototype.has(value)
:返回一个Boolean值,表示该值是否为Set成员;Set.prototype.clear()
:清除所有的成员,没返回值;
遍历方法:
Set.prototype.keys()
:返回键名的遍历器Set.prototype.values()
:返回键值的遍历器Set.prototype.entries()
:返回键值对的遍历器Set.prototype.forEach()
:使用回调函数遍历每个成员
由于Set结构中没有键名,只有键值(或者说键名和键值是同一个值),所以keys方法和values方法的结构是一致的;
entries方法每次会返回一个数组,其包括了键名和键值,并且它们的值相等;
forEach方法则是遍历set并且可以对每一个成员进行某种操作,它没有返回值;
const set = new Set()
// 添加了两次2,但因为set是不能重复的,所以只有一个2
set.add(1).add(2).add(2)
set.size // 2
set.has(1) // true
set.has(2) // true
set.has(3) // false
set.delete(2) //true 表示将2从set中删除
set.has(2) // false
const setFor = new Set(['red', 'green', 'blue']);
for (const item of setFor.keys()) {
console.log(item); // red, green, blue
}
for (const item of setFor.values()) {
console.log(item); // red, green, blue
}
for (const item of setFor.entries()) {
console.log(item); // [ 'red', 'red' ], [ 'green', 'green' ], [ 'blue', 'blue' ]
}
setFor.forEach((value, key, set) => {
console.log(value, key, set); // red, red, [ "red", "green", "blue" ] green, green, [ "red", "green", "blue" ] blue, blue, [ "red", "green", "blue" ]
})
Map
Map,类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。
const m = new Map()
const o = { p: 'Hello World' };
m.set(o, 'content') // 将o映射到'content'
m.get(o) // "content"
m.has(o) // true
m.delete(o) // true
m.has(o) // false
上面使用了set方法将对象o当做m的一个键,值为content,然后 使用get方法读取这个键,接着使用delete方法删除了这个键。
Map实例的属性和操作方法
属性:
size
:返回Map结构的成员总数;
操作方法:
Map.prototype.set(key,value)
:设置键名key
和其对应的键值value
,然后返回整个Map结构;Map.prototype.get(key)
:读取key
对应的键值,如果找不到key,则返回undefined;Map.prototype.has(key)
:返回一个Boolean值,表示某个键是否在当前Map对象之中;Map.prototype.delete(key)
:删除某个键,返回true。如果删除失败,返回falseMap.prototype.clear()
:清除所有的成员,没有返回值;
遍历方法:
Map.prototype.keys()
:返回键名的遍历器。Map.prototype.values()
:返回键值的遍历器。Map.prototype.entries()
:返回所有成员的遍历器。Map.prototype.forEach()
:遍历 Map 的所有成员。
const m = new Map()
m.set('name', '张三') //键是name,值为张三
m.set('age', 18) //键是age,值是
m.size //2
m.get('name') //'张三'
m.get('age') //18
m.has('name') //true
m.has('age') //true
m.delete('address') //false
m.delete('name') //true
m.has('name') //false
m.clear() //清空
const map = new Map([ //用数组形式创建,每一个数组就是一个键值对
['name', '张三'],
['age', 18],
['F', 'no'],
['T', 'yes'],
]);
for (const key of map.keys()) {
console.log(key); //name, age, F, T
}
for (const value of map.values()) {
console.log(value); //张三, 18, no, yes
}
for (const item of map.entries()) {
console.log(item[0], item[1]); //name, 张三 age, 18 F, no T, yes
}
// 或者
for (const [key, value] of map.entries()) {
console.log(key, value); //name, 张三 age, 18 F, no T, yes
}
//等同于使用map.entries()
for (const [key, value] of map) {
console.log(key, value); //name, 张三 age, 18 F, no T, yes
}
模块
模块功能主要由两个命令构成:export
和import
export命令
export
命令用于规定模块的对外接口
一个模块就是一个独立的文件。该文件的内部的所有变量,外部都无法获取。当希望外部能获取到模块内部的某个变量,就必须使用export关键字输出该变量。
分别暴露:
// 暴露变量
export var firstName = 'Zhang'
export var lastName = 'san'
export var year = 2022
// 暴露函数
export function add(x, y) {
return x + y
}
统一暴露:
//暴露变量
var firstName = 'Zhang'
var lastName = 'san'
var year = 2022
//暴露函数
function add(x, y) {
return x + y
}
export { firstName, lastName, year, add }
as重命名:
// as关键字可用来重命名
export { add as myAdd }
import命令
在上面使用了import命令导出之后,当然也需要有导入的命令,即import,在其他的js文件中就可以通过import
命令来加载这个模块。
整体加载:
import * as mmm form '文件名'
mmm
是重命名的名字,后续代码中就可以使用mmm
来使用其导出模块中的变量或方法
例:
暴露.js
export var firstName = "张"
export var lastName = "三"
export var year = 2022
export function add(x, y) {
return x + y
}
代码.js
import * as mmm from "./暴露.js"
console.log(mmm.firstName); //张
console.log(mmm.lastName); //三
console.log(mmm.year); //2022
console.log(mmm.add(7, 9)); //16
解构赋值形式(按需引入)
// 导入变量
import { firstName, lastName, year } from "导出模块的文件名";
// 导入函数
import { add } from "文件名";
// as关键字重命名
import { add as myAdd } from "导出模块的文件名";
例:
暴露.js
var firstName = "张"
var lastName = "三"
var year = 2022
function add(x, y) {
return x + y
}
export { firstName, lastName, year, add }
代码.js
import { firstName, lastName, year, add } from "./暴露.js"
console.log(firstName); //张
console.log(lastName); //三
console.log(year); //2022
console.log(add(7, 9)); //16
注意:导入的变量都是只读的,无法改变其值;如果该值为对象,那么改写该对象中的属性是允许的,但是改写之后,其他模块可以读到改写后的值。
export default 命令
在前面的例子中可以看出,使用export导出,import导入时,还需要知道变量名或函数名,否则将会无法加载。
而使用export default
命令,则不需要,它可以为模块指定默认输出
export default function () {
console.log('hello world');
}
// 或者
function Hello() {
console.log('hello world');
}
export default Hello;
// 导入时可以为导出的重命名,重命名之后可以直接使用该名
import Hello form '导出模块的文件名';
// 使用
Hello(); //hello world
注意: 一个模块中只能有一个export default