ES6: (ECMAScript第六个版本)
1. 模板字符串
在旧 js 中,拼接字符串只能用+,这样极容易和算术计算的加法计算混淆,所以就需要用到模板字符串,它是一种支持换行、动态拼接内容的特殊字符串格式。
模板字符串的使用:
a. 整个字符串用一对儿反引号` `包裹;
b. 在反引号中可以写单引号,双引号,换行等;
c. 在反引号中凡是动态拼接的变量或 js 表达式都要放在 ${ } 中。
在模板字符串的 ${ } 中,可以放变量、算术计算、三目、对象属性、创建对象、调用函数、访问数组元素等有返回值的合法的js表达式;但不能放没有返回值的js表达式,也不能放分支、循环等程序结构,比如: if else for while...等。
举例:使用模板字符串动态拼接各种各样字符串;
<script>
var uname = "卫国";
console.log(`姓名:${uname}`);
var sex = 1;
console.log(`性别:${sex = 1 ? "男" : "女"}`);
var price = 12.5;
var count = 5;
console.log(`
单价:${price}
数量:${count}
================
小计:${price * count}
`);
var time = 1622773370370;
console.log(`下单时间:${new Date(time).toLocaleString()}`);
var day = new Date().getDay();
var arr = ["日", "一", "二", "三", "四", "五", "六"];
console.log(`今天星期${arr[day]}`);
</script>
打印结果如图:
2. let
在以往的 js 程序中,我们都是用 var 关键字来声明变量,但是这样会有两个问题:
首先是会声明提前,打乱程序正常的执行顺序;其次没有块级作用域,导致代码块内的变量会超出代码块的范围,影响外部的变量。
块级作用域:JS 中没有,在其他语言中指除了对象 { } 和 function 的 { } 之外,其余 if else、for、等程序结构的 {} 范围。但是,在 js 中这些 { },都不是作用域,所以拦不住内部的局部变量声明被提前。
解决以上两个问题的方法就是使用 let 关键字代替 var 关键字来声明变量。如下举例:
<script>
// 定义全局变量
var t = 0;
function fun1() {
//var t;//undefined
console.log(`函数一用时6s`);
t += 6;
// 此处添加如下代码(不执行)
if (false) {
var t = new Date(); //此处的t被提前到了当前函数fun1的首部,待执行t+=6时,t的值为undefined+6;无法计算,所以结果却少了6秒,且因为if(false)该语句不执行。
//-----------------------------------------------------------------------------
// 解决方式:
// 将var改为使用let声明变量,第一种写法:
// let t =new Date();
// let底层相当于匿名函数自调,所以第二种写法:
// (function (true) {
// var t = new Date();
// console.log(`上线时间:${t.toLocaleString()}`)
// })();
//------------------------------------------------------------------------------
console.log(`上线时间:${t.toLocaleString()}`)
}
}
function fun2() {
console.log(`函数二用时4s`);
t += 4;
}
fun1(); //函数一用时6s
fun2(); //函数一用时4s
console.log(`共耗时:${t}秒`); //使用var时的打印结果:共耗时4秒 缺少了函数一的6秒
</script>
let 关键字与 var 相比,不会被声明提前,可以保证程序顺序执行;还可以让程序块也变成了块级作用域(并没有真正变成块级,只是采用了匿名函数自调的方式),保证块内的变量不会影响块外的变量。
注意:let 关键字底层的本质其实就是匿名函数自调。
let 关键字的三个特点:
a. 因为不会声明提前,所以不能在声明变量之前,提前使用该变量。
b. 在相同作用域内,禁止声明两个同名的变量!
c. let 底层相当于匿名函数自调,所以,即使在全局创建的 let 变量,在 window 中也找不到!
<script>
console.log(a);
// 1.禁止在声明之前,提前使用该变量
//console.log(b); //报错:Cannot access 'b' before initialization
let b = 100;
console.log(b);
// 2.相同作用域内,不允许重复声明同名变量
var a = 10;
//let a = 100; //报错:Identifier 'a' has already been declared
// 3.即使在全局let的变量,在window也找不到
var c = 10; 默认保存在window对象中
console.log(c); //10
console.log(window.c); //10
console.log(window["c"]); //10
(function () {
let d = 100;
console.log(d); //100
})();
console.log(window.d); //undefined
console.log(window["d"]); //undefined
</script>
3. 箭头函数
箭头函数是对绝大多数匿名函数的简写,今后几乎所有匿名函数都可用箭头函数简化。箭头函数的简化方法遵循三个原则:
a. 去掉 function,在()和{}之间加=>
b. 如果只有一个形参,则可以省略( )
c. 如果函数体只有一句话,则可以省略{ };函数体仅剩的一句话是 return,则必须去掉 return。
举例:将各种 function 改为箭头函数;
<script>
// -------------------------------------------------
// function add(a, b) {
// return a + b;
// }
var add = (a, b) => a + b; //箭头函数写法
console.log(add(3, 5)); //8
// -------------------------------------------------
var arr = [12, 123, 23, 1, 3, 2];
// arr.sort(function (a, b) {
// return a - b
// });
arr.sort((a, b) => a - b); //箭头函数写法
console.log(arr);
// -------------------------------------------------
var arr = ["亮亮", "楠楠", "东东"];
// arr.forEach(function (elem) {
// console.log(`${elem} - 到!`)
// })
arr.forEach(elem => console.log(`${elem} - 到!`)); //箭头函数写法
// -------------------------------------------------
var arr = [1, 2, 3];
// var arr2 = arr.map(function (elem) {
// return elem * 2;
// })
var arr2 = arr.map(elem => elem * 2); //箭头函数写法
console.log(arr2);
// -------------------------------------------------
var arr = [1, 2, 3, 4, 5];
// var sum = arr.reduce(function (box, elem) {
// return box + elem;
// }, 0)
var sum = arr.reduce((box, elem) => box + elem, 0); //箭头函数写法
console.log(sum);
// -------------------------------------------------
// (function () {
// var t = new Date();
// console.log(`页面内容加载完成,at:${t.toLocaleString()}`);
// })();
(() => { //箭头函数写法
var t = new Date();
console.log(`页面内容加载完成,at:${t.toLocaleString()}`);
})();
// -------------------------------------------------
var t = 5;
// var timer = setInterval(function () {
// t--;
// console.log(t);
// if (t == 0) {
// console.log("boom!!!")
// clearInterval(timer);
// }
// }, 1000);
var timer = setInterval(() => { //箭头函数写法
t--;
console.log(t);
if (t == 0) {
console.log("boom!!!")
clearInterval(timer);
}
}, 1000)
</script>
箭头函数与 this
在常规的写法中,当对象中方法需要通过 this 调用属性时,会出现一定的问题,如下代码:
<script>
var lilei = {
sname: "卫国",
friends: ["宝国", "爱国", "建国", "护国"],
intr() {
this.friends.forEach(
function (n) {
console.log(`${this.sname}是${n}的哥哥!`);
}
)
}
}
lilei.intr();
</script>
this.friends.forEach 语句中的 this 指代对象 lilei,可以正常调用,但是函数 function 在打印时仍然用到了 this.sname,需要注意,此处的this已经不再指代对象 lilei,由于它处于 function 函数当中,默认指代 window(全局),而全局中是不存在 sname 属性的。以上代码打印效果如下:
uname 显示为 undefined。
为了解决此问题,我们便可用箭头函数,箭头函数可让函数内的this与函数外的 this 保持一致。所以将以上对象中的函数改用箭头函数:
<script>
var lilei = {
sname: "卫国",
friends: ["宝国", "爱国", "建国", "护国"],
intr() {
this.friends.forEach(
// 使用箭头函数
n => console.log(`${this.sname}是${n}的哥哥!`)
)
}
}
lilei.intr();
</script>
显示效果如下:
所以,我们可以得出结论:
a:如果函数中不包含 this,或希望函数内的this与外部 this 保持一致时,就可以改为箭头函数;
b:如果不希望函数内的 this 与函数外的 this 保持一致时,就都不能改为箭头函数。
4. for of
遍历数字下标的数组或者类数组对象 arguments 时,可以用到多种循环;普通 for 循环既可遍历索引数组,又可以遍历类数组对象,但没有可简化的空间;forEach 循环可以配合 ES6 的箭头函数,很简化,但无法用于遍历类数组对象。
ES6 中提供了 for of 循环,只要遍历数字下标,都可用 for of 代理普通 for 循环和 forEach;格式如下:
for(var 变量 of 索引数组/类数组对象){
//of会依次取出数组或类数组对象中每个属性值
//自动保存of前的变量中
}
举例:使用 for of 点名,并实现计算任意多个数字的和;
<script>
// 点名
var arr = ["小红", "小兰", "小绿", "王刚"]
// for循环遍历
// for (var i = 0; i < arr.length; i++) {
// console.log(`${arr[i]} - 到!`);
// }
// for-of遍历
for (var t of arr) {
console.log(`${t} - 到!`);
}
// 定义函数求任意多个数之和
function add() {
var result = 0;
//for循环遍历
// for (j = 0; j < arguments.length; j++) {
// result += arguments[j]
// }
// for-of遍历
for (var n of arguments) {
result += n;
}
return result;
}
console.log(add(1, 4, 5)); //10
</script>
然而 for of 也存在以一些问题,无法获得下标位置i,只能获得元素值;更无法控制遍历的顺序或步调。但是绝大多数循环都是从头到尾,一个接一个遍历的,且绝大多数循环不太关心下标位置,只关心元素值,所以 for of 将来用的还是非常多的!
各类循环区分:
|
|
普通for |
forEach |
for of |
for in |
数字下标 |
索引数组 |
√ |
√ |
√ |
× |
类数组对象 |
√ |
× |
√ |
× |
|
自定义下标 |
关联数组 |
× |
× |
× |
√ |
对象 |
× |
× |
× |
√ |
总结起来就是:下标为数字选 for of,下标为自定义字符串选 for in。
5. 参数增强
- 参数默认值
调用函数时,如果不传入实参值,虽然语法不报错,但是形参会默认为 undefined,而Undefined 极容易造成程序错误!所以在调用函数时,不传入实参值时,为了使形参变量也有默认值可用,不至于是 undefined,就用到默认值。格式如下:
function 函数名(形参1=默认值1, 形参2=默认值2, ...){
//调用函数时,给形参传了实参值,则首选用户传入的实参值。如果没有给形参传是实参值,则形参默认启用=右边的默认值。
}
举例:使用参数默认值解决订套餐问题;
<script>
// 定义一个点套餐的函数
function order(
zhushi = "香辣鸡腿堡",
xiaochi = "烤鸡翅",
yinliao = "可乐"
) {
console.log(`
您点的套餐为:
主食:${zhushi}
小吃:${xiaochi}
饮料:${yinliao}
`);
}
// a点默认套餐
order();
// b自定
order("牛肉汉堡", "鸡米花", "雪碧");
// c只换主食
order("烤鸡");
</script>
- 剩余参数(rest)
箭头函数虽然好用,但不支持类数组对象 arguments,如果箭头函数遇到参数个数不确定时,就需要用剩余参数语法来代替 arguments。格式:
var 函数名=( ...数组名 )=>{
//将来传入函数的所有实参值,都会被...收集起来,保存到...会指定的数组中。
}
剩余参数的优点是支持箭头函数,生成的数组是纯正的数组类型,所以可使用数组家所有函数自定义数组名,比 arguments 简单的多。而且还可以和其它形参配合使用,只获得其它形参不要的剩余参数。格式如下:
var 函数名=(形参1, 形参2,...数组名)=>{ }
举例:使用剩余参数语法计算不同员工的总工资;
<script>
// 定义计算员工总工资的函数
function add(ename, ...arr) {//计算除ename之外的剩余参数
console.log(arr);
var total = arr.reduce(
function (m, n) {
return m + n;
},
0
);
console.log(`${ename}的总工资为:${total}`);
}
add("李雷", 10000, 200, 3000);
add("韩梅梅", 5000, 500, 300, 1200, 200);
</script>
- 展开运算符(spread)
apply 拆散数组时,强迫我们必须提供一个替换 this 的对象,那是因为 apply() 本职工作不是拆散数组,而是替换 this,是在替换 this 同时,顺便拆散数组。所以,今后若希望单纯拆散数组,都用...展开运算符。格式:
函数名(...数组);
展开运算符的原理是...先将数组拆散为多个实参值,再依次分别传给函数的每个形参变量。
举例:获取数组中的最大值
<script>
// 获取数组中的最大值
var arr = [1, 5, 6, 8];
// 错误写法
console.log(Math.max(1, 5, 6, 8));
console.log(Math.max(arr));
// 正确,但必须提供一个替换this的对象
console.log(
Math.max.apply(null, arr),
Math.max.apply(arr, arr),
Math.max.apply("", arr),
Math.max.apply(Math, arr)
);
// 使用展开运算符
console.log(Math.max(...arr));
</script>
语法糖(拓展):
i. 复制一个数组: var arr2=[...arr1];
ii. 合并多个数组和元素值: var arr3=[...arr1,值,...arr2,值];
iii. 复制一个对象: var obj2={ ... obj1 }
iv. 合并多个对象和属性: var obj3={ ...obj1, 属性:值, ...obj2, 属性:值 }