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 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, 属性:值 }