一、新增 let 和 const
ES5只有两种声明变量的方法 var命令和function命令;ES6添加了let命令和const命令,还有import命令和class命令。
1、let:
1.不存在变量提升
// var a; (此处为js预解析,隐含存在。在下方代码执行中就隐藏着这个。)
console.log(a);
var a = 'abc';
// 若 var,执行结果为undefined,不是“abc”也不报错
// 若 let ,则会报错,let不存在预解析【error】
2.同一作用域下不能重复定义同一个名称
var b = 1;
var b = 2;
console.log(b);
// 若 var , 执行结果为2,var 声明的同名变量会覆盖之前的
// 若 let,执行结果报错,不可再次定义【error】
3.有着严格的作用域,var 属于函数作用域,let 属于块级作用域
function fun_test(){
var num = 1;
if(true){
var num = 2;
}
console.log(num);
}
fun_test();
// var 执行结果为2;在function类中var声明的同名变量均会后面的覆盖前面的;同时,使用var的时候注意不要和全局变量命名相同,否则局部的会覆盖掉全局的。
// let 执行结果为1;以{}为一个块级作用域,此处两个同名变量不报错是因为二者不在同一作用域下,分别执行。
Ps: js预解析功能,按照浏览器的功能会提前将命名的变量提前解析,然后再逐步加载代码执行。
2、const
1. const 声明一个只读的常量,一旦声明,常量的值就不能改变。
const TEMP = 11;
TEMP = 22;
console.log(TEMP);
// 执行报错【error】
2.const 一定要初始化,不能只声明不赋值
const RL;
// 报错【error】
3、当声明的常量是数组、对象等引用类型时,相当于只是定义了一个内存地址,对象所包含的值可以被修改,但对象指向的地址不能被修改。
const p = {name:"OldName", age:18};
p.age = 23; // 不报错,--- 修改包含的值,person的age属性变为23
p = {name:"NewName",age:19}; // 报错, --- 修改对象执行的地址
ps: 关于let 和 const的小 tips:
(1) let 关键词声明的变量不具备变量提升特性;
(2)let 和 const 声明只在最靠近的一个块中有效,也是块级作用域;
(3)当使用常量 const 声明时,请使用大写变量,如:CAPITAL_CASING;
(4)使用let会出现暂时性死区,原因是let所声明的变量会锁在它所在的作用域里,不允许访问,就是说,它也会先扫描一遍,把let声明的变量保存起来,但是不允许使用,这时候你访问a,由于此作用域中有a,就不用访问外面的a了,但是你不能在它声明之前访问它。
var a = 1;
if(true){
console.log(a); //ReferrenceError
let a = 1;
}
(5)使用let和const时不能重复声明。
(6) 使用var声明的全局变量或者未声明的变量都会归属到window对象下,但是使用let和const声明全局变量时不会发生这种情况。
(7)const本质:const命令声明的变量的值并不是不得改动的,而是变量执行那个的那个内存地址所保存的数据不得改动 对于数值、字符串、boolean这种类型的数据的值就保存在变量指向的那个内存地址,因此是常量。
拓展: 如何使用ES5标准来实现let,换言之就是如何实现块级作用域。(答:使用匿名函数)
二、模板字符串
在过去我们想要将字符串和变量拼接起来,只能通过运算符“+”来实现,若内容过多还要用“\”来表示换行,如:
var p1 = {name:"p1",age:19};
$(".introduction").html("Hello,my name is "+ p1.name +", and my \
age is "+ p1.age +".");
而在ES6中,可以将反引号(``)将内容括起来,在反引号中,可以使用${}来写入需要引用到的变量。如:
var person = {name: "Peter", age: 22, career: "student"};
$(".introduction").html(`Hello, my name is ${person.name}, and my career is ${person.career}.`);
三、箭头函数
1.不需要用关键字function来定义函数;
2.一般情况下可以省略return
3.在箭头函数内部,this并不会跟其他函数一样指向调用它的对象,而是继承上下文的this指向的对象
/*
** 对应上面所说的第3点
** 在箭头函数中,this的指向与它的上下文有关
** 在本例中,箭头函数fun的上下文是window,所以this指向的也是window
*/
window.val = 12;
let fun = () => {
let val = 13;
console.log(val);// 13
console.log(this.val);// 12
console.log(this== window);// true
}
fun();
// 当参数只有一个,{}中的代码只有一行时,可以省略()和{},如果有return也可省略。
let f = v=>v; // 变量名=参数=>返回值(函数体)
// 等同于
var f = function(v){
return v;
}
四、数组、对象解构
可以将对象中的属性,数组值进行解构。以便快速获取数组和对象的值。
1、数组解构就是能快速提取数组中的指定成员(数组的某一项值或所有的值)
1. 解构赋值都是一一对应的,按照顺序
const arr = [200,300,400];
const [a,b,c] = arr;
console.log(a,b,c) ; // 200,300,400;
2. 也可以去数组的某一项值(解构必须保持一致)
const arr = [200,300,400];
const [,,c] = arr;
console.log(c) // 400
3. 还可以用“...”的方式提取所有的成员(注意的是:这种...的写法只能在解构成员的最后一个成员使用)
const arr = [200,300,400];
const [a,...all] = arr;
console.log(all) // [300,400] 会返回得到一个最后所有的数组
4. 如果提取的解构成员小于数组的长度,会从前到后的顺序来提取
const arr = [200,300,400]
const [a] = arr;
console.log(a) // 200 按顺序提取第一个
5. 如果提取成员大于数组长度,那么最后的提取的是undefined.
const arr = [200,300,400];
const [a,b,c,d] = arr;
console.log(d); // undefined;
2、对象解构和数组解构基本类似,只不过对象解构的取值方式是根据对象的属性名来取值
const obj = {name:"aaa",age:30};
const {name} = obj;
console.log(name); // aaa
ps: 对象里面的属性名和其他自定义的变量名称如果重名会报错。
const obj = {name:"aaa",age:10};
const name = "bbb";
const {name} = obj
console.log(name); // 会报错
// 解决方法:要么重新命名,要么可以按照下面的写法来避免
const obj = {name:"aaa",age:10};
const name = "bbb";
const {name:nameObj} = obj; // 对象属性名称的重新指定
console.log(nameObj) // aaa
借鉴于:https://blog.csdn.net/xuanzhuan365/article/details/109481552
五、新增数据类型:set 、map
1、set 类似于数组,成员是唯一的
const s = new Set();
s.add(1);
s.add(2).add(3).add(4); // 支持链式添加
s.add(2).add(3).add(4).add(2); // 这样的情况下不会打印最后的2。因为成员唯一,进行了去重处理
eg: 问: 对 var arr= [2,3,4,5,6,6,3,4,5] 去重处理
答:var arr2 = [...new Set(arr)]; 即可
2、map 类似于对象,键并不是单纯的字符串,类型更多样
// map 以键值对的形式添加值
const m = new Map();
m.set("name","bob").set("age",18);
for(let [key,value] of m){}
六、函数设置默认参数值
以前设置默认值:
function f_1(txt) {
var txt = txt || "hello world 1";
console.log(txt);
}
function f_2(txt="hello world 2"){ console.log(txt); }
f_1(); // 输出:hello world 1
f_2(); // 输出:hello world 2
f_1("elseString1"); // 输出:elseString1
f_2("elseString2"); // 输出:elseString2
七、迭代器 iterator, for...of , for...in
1、Iterator: 是ES6中的一种新的遍历机制;
1. 遍历器(iterator)它是一种接口,为各种不同的数据结构提供统一的访问机制
2.iterator的作用有三个:
a、为各种数据结构,提供一个统一的、简便的访问接口;
b、使数据接口的成员能够按某种次序排列
c、ES6创造了一种新的遍历命令for...of循环,iterator接口主要供for...of消费
3.iterator遍历的过程
(1) 创建一个指针对象,指向当前数据结构的起始位置,Iterator的本质就是一个指针对象;
(2)当第一次调用指针对象的next()方法时,指针指向数据结构的第一个元素;
(3)当第二次调用指针对象的next()方法时,指针指向数据结构的第二个元素;
(4)不断地调用指针对象的next()方法,直到它指向当前数据结构的结束位置;
每一次调用next()方法时,会返回两个属性的对象,一个时value和done。value属性表示当前数据里元素的值,done返回一个boolean值,表示是否遍历完成。举以下为例:
function iIterator(array){
var index = 0;
return {
next: function() {
if(index<array.length){
return {
value:array[index++],
done:false
}
} else {
return {
value:undefined,
done:true
}
}
}
}
};
var it = new iIterator([1,2,3,4]);
it.next(); // value:1,done:false
it.next(); // value:2,done:false
it.next(); // value:3,done:false
it.next(); // value:4,done:false
it.next(); // value:undefined,done:true
4.iterator 可以遍历的数据类型
Array、Map、Set、String、TypedArray、函数的arguments对象、NodeList对象
2、for...of循环
可用于替换for...in 和forEach()
for...of 是ES6新引用的循环,一个数据结构只要部署了Symbol.iterator属性,就被视为具有iterator接口,就可以用for...of循环遍历它的成员。也就是说,for...of循环内部调用的是数据结构的Symbor.iterator方法。
常用for...of遍历的数据类型:Map、Set、Array、String等
eg:以Array为例
var arr1 = ["a","b","c","d"];
arr1.forEach((element,index)=>{
console.log(element); // a,b,c,d
console.log(index); // 0,1,2,3
});
for(let i in arr1) {
console.log(i); // 0,1,2,3
}
for(let i of arr1) {
console.log(i); // a,b,c,d
}
有上述可知:for...in遍历的是键名,不能直接获得键值。for...of遍历的是键值
八、class类的支持
Es6提供了更接近传统语言的写法,引入了Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。
基本上,ES6的class可以看作只是一个语法糖,它的绝大部分功能,ES5都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。在ES5中,我们更多的是使用原型链来实现继承。
class Person {
constructor(name) {
this.name = name;
// 不推荐以下写法,不能实现共享,且会造成内存浪费
this.getName = function() {
return this.name
};
}
// Person.prototype.toGetName = function(){} == toGetName true
toGetName(name){
return this.name;
}
}
var p = new Person("张三");
p.getName(); // 张三
p.toGetName(); // 张三
九、对象的简化赋值
ps: 只有当对象的属性名和变量名一致时
const person = { "name":"name","age":"age" };
可简化为: const person = {name,age};
十、Spread / Rest 操作符
Rest运算符用于获取函数调用时传入的参数。
let fun = function(...args){
console.log(args);
}
const list = ["p1","p2","p3"];
fun(list); // ["p1","p2,"p3"]
Spread运算符用于数组的构造、析构,以及在函数调用时使用数组填充参数列表。
// 使用Spread运算符合并数组
const list1 = ["p1","p2"];
const list2 = ["p3","p4"];
const list = [...list1,...list2];
console.log(list); // ["p1","p2,"p3","p4"]
// 使用Spread运算符析构数组
const [person,...list3] = list;
console.log(person); // p1
console.log(list3); // ["p2,"p3","p4"]
了解更多
十一、二进制和八进制的字面量
ES6支持二进制和八进制的字面量,通过在数字签名增加0o或者0O就可以将数字转换为八进制。
let num1 = 0o10;
console.log(num1); // 8, 八进制的0o10对应十进制的8
let num2 = 0b10;
console.log(num2); // 2, 二进制的0b10对应十进制的2
十二、允许在对象中使用super方法
super方法在java中用来代表调用父类的构造函数。由于js不是面向对象语言,所以也没有继承这类说法。但是在ES6中,可以通过调用setPrototypeOf()方法来设置一个对象的prototype对象,与面向对象语言中的继承有相似之处,所以也可以理解成这是js中用来实现继承的方法。所以,在ES6中,通过使用super可以调用某个对象的prototype对象的方法或获取参数。
var father = {
text:"Hello from the Father.",
foo() {
console.log("Hello from the Father 2.");
}
}
var son = {
foo() {
super.foo();
console.log(super.text);
console.log("Hello from the Son.");
}
}
// 将father设置成son的prototype,当在son中调用super时,可以直接调用到它的prototype对象,即father的方法和变量.
Object.setPrototypeOf(son,father);
son.foo();
// Hello from the Father 2.
// Hello from the Father.
// Hello from the Son.