本章知识梳理图
ECMAScript 6.0(以下简称 ES6)是 JavaScript 语言的下一代标准,已经在 2015 年 6 月正式发布了。它的目标,是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。
1.ECMAScript 和 JavaScript 的关系
ECMAScript 和 JavaScript 到底是什么关系?
1996 年 11 月,JavaScript 的创造者 Netscape 公司,决定将 JavaScript 提交给标准化组织 ECMA,希望这种语言能够成为国际标准。次年,ECMA 发布 262 号标准文件(ECMA-262)的第一版,规定了浏览器脚本语言的标准,并将这种语言称为 ECMAScript,这个版本就是 1.0 版。
该标准从一开始就是针对 JavaScript 语言制定的,但是之所以不叫 JavaScript,有两个原因。一是商标,Java 是 Sun 公司的商标,根据授权协议,只有 Netscape 公司可以合法地使用 JavaScript 这个名字,且 JavaScript 本身也已经被 Netscape 公司注册为商标。二是想体现这门语言的制定者是 ECMA,不是 Netscape,这样有利于保证这门语言的开放性和中立性。
因此,
ECMAScript 和 JavaScript 的关系是:
前者是后者的规格,后者是前者的一种实现。
2.ES6基本语法
ES标准中不包含 DOM 和 BOM的定义,只涵盖基本数据类型、关键字、语句、运算符、内建对象、内建函数等通用语法。
任务一:let和const命令
一、let命令
ES6新增了let命令,用来声明变量。它的用法类似于var,但其声明的变量只在let命令所在的代码块内有效。
1.let命令作用域只局限于当前代码块
ES6新增了let命令,用来声明变量。它的用法类似于var,但其声明的变量只在let命令所在的代码块内有效。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>let命令作用域</title>
</head>
<body>
</body>
</html>
<script>
{
let a=10;
var b=1;
}
console.log(a);//ReferenceError: a is not defined
console.log(b);
</script>
2.使用let声明的变量作用域不会被提前
var命令支持“变量提升”,即变量可以在声明之前使用,值为undefined。let命令不支持“变量提升”,声明的变量一定要在声明之后才可以使用,否则会报错。
//var的情况
console.log(foo);
var foo=2;
//let的情况,会报错
console.log(bar);
let bar=2;
3.在相同的作用域下不能声明相同的变量
let命令不允许在相同作用域内重复声明同一个变量。也不能在函数内部重复声明参数,声明的参数也不能与形参同名,如果声明的参数是在另一个作用域下,则可以进行重复声明。
//会报错
function func(){
let a=10;
var a=1;
}
//报错
function func(){
let a=10;
let a=1;
}
//报错
function func(arg){
let arg;
}
//不报错
function func(arg){
{
let arg;
}
}
4.for循环体中let的父子作用域
有5个按钮,当单击某个按钮时,控制台可以打印输出当前单机的时第几个按钮,按照常规实现思路,代码实现如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>for循环体中let命令</title>
</head>
<body>
<button>按钮</button>
<button>按钮</button>
<button>按钮</button>
<button>按钮</button>
<button>按钮</button>
</body>
</html>
<script>
var btns=document.querySelectorAll('button');
for(var i=0;i < btns.length;i++){
btns[i].onclick=function(){
console.log("这是第"+i+"个按钮");
}
}
</script>
运行效果图:
无论单击哪一个按钮,最后打印的都是“这是第5个按钮”。因为for是同步操作,直接将i++到i=5,而for循环内部的函数执行的是异步操作,当函数执行的时候找不到i的值,就会往上面的作用域查找i的值为5。
上机训练1——let命令使用:解决上面按钮例子,最终实现页面效果图如下:
实现代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>for循环体中let命令</title>
</head>
<body>
<button>按钮</button>
<button>按钮</button>
<button>按钮</button>
<button>按钮</button>
<button>按钮</button>
</body>
</html>
<script>
var btns=document.querySelectorAll('button');
for(let i=0;i < btns.length;i++){
btns[i].onclick=function(){
console.log("这是第"+i+"个按钮");
}
}
</script>
二、const命令
基本用法
1.const
声明一个只读的常量。一旦声明,常量的值就不能改变。
1.const
const PI = 3.1415;
PI // 3.1415
PI = 3;
// TypeError: Assignment to constant variable.
2.
const
一旦声明变量,就必须立即初始化,不能留到以后赋值。
const
const foo;
// SyntaxError: Missing initializer in const declaration
3.
const
的作用域与
let
命令相同:只在声明所在的块级作用域内有效。
const
let
if (true) {
const MAX = 5;
}
MAX // Uncaught ReferenceError: MAX is not defined
4.
const
命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。
下面代码在常量
MAX
声明之前就调用,结果报错。
const
MAX
if (true) {
console.log(MAX); // ReferenceError
const MAX = 5;
}
5.
const
声明的常量,也与
let
一样不可重复声明。
const
let
var message = "Hello!";
let age = 25;
// 以下两行都会报错
const message = "Goodbye!";
const age = 30;
6.const定义复合类型的数据(对象或数组)时,常量指向的内存地址保存的只是一个指针。
const foo={};
//为foo添加一个属性,可以成功
foo.prop=123;
console.log(foo.prop);
//将foo指向另一个对象,会报错
foo={};
常量foo存储的是一个地址,这个地址指向一个对象。地址不可更改,对象本身内容可以改变。
const a=[];
a.push('Hello'); //可执行
a.length=0; //可执行
a=['Dave']; //报错
常量a是一个数组,数组本身是可以更改,但是将另一个数组赋值给a,修改了地址,就会报错。
小结:
|
任务二:变量的解构赋值
一、数组的解构赋值
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为
解构
(Destructuring)。以前,为变量赋值,只能直接指定值。
let a = 1;
let b = 2;
let c = 3;
ES6 允许写成下面这样:
let [a, b, c] = [1, 2, 3];
console.log(a); //1
console.log(b); //2
console.log(c); //3
本质上,这种写法属于“
模式匹配
”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。
不完全解构赋值:
let[x,y]=['a'];
console.log(x); //a
console.log(y); //undefined
数组解构赋值:
var arr = [1,2,3];
// 传统的js
let a = arr[0];
let b = arr[1];
let c = arr[2];
console.log(a,b,c);
// es6的解构
var [x,y,z] = arr;
console.log(x,y,z);
总结:
- 解构赋值是对赋值运算符的扩展。
-
它针对
数组
或者
对象
进行模式匹配,然后对其中的变量进行赋值。 - 解构,顾名思义,就是将集合型数据进行分解,拆分,把里面的值逐一遍历获取。
- 在代码书写上简洁且易读,语义更加清晰明了;也方便了复杂对象中数据字段的获取。
二、对象的解构赋值
解构不仅可以用于数组,还可以用于对象。
对象
的解构与数组的解构有一个重要的不同:
数组
的元素是按次序排列的,变量的取值由它的位置决定;而
对象
的属性没有次序,变量必须与属性同名,才能取到正确的值。
var user = {
username : "吕布",
weapon:"方天画戟",
horse:"赤兔马"
};
// 传统的js
let mingzi = user.username;
let wuqi = user.weapon;
let zuoji = user.horse;
console.log("姓名:"+mingzi+",武器:"+wuqi+",坐骑:"+zuoji);
// es6的解构
let {username,weapon,horse} = user; // 注意:解构的变量名必须是对象中的属性
console.log("姓名:"+username+",武器:"+weapon+",坐骑:"+horse);
对象的解构赋值:
let {bar,foo}={foo:"aaa",bar:"bbb"}
console.log(foo); //aaa
console.log(bar); //bbb
let {baz}={foo:"aaa",bar:"bbb"}
console.log(baz); //赋值失败,没有对应的属性名,变量的值等于undefined
对象的解构赋值简写:
let obj={first:'hello',last:'world'};
let{first:f,last:l}=obj;
console.log(f); //hello
console.log(l); //world
等同于:
let{first:f,last:l}={first:’hello’,last:’world’}
上机训练2——对象的解构赋值使用
需求
:假设有一段Json对象数据,利用对象的解构赋值来快速提取JSON数据的值。
var jsonData={
id:42,
status:"OK",
data:[123,542]
};
实现代码:
var jsonData={
id:42,
status:"OK",
data:[123,542]
};
let {id,status,data}=jsonData;
console.log(id);
console.log(status);
console.log(data);
任务三:使用箭头函数
1.箭头函数的起因
示例代码:
const Person={
username:"小暖",
age:18,
sayHello:function(){
setInterval(function(){
console.log("我叫"+this.username+"我今年"+this.age+"岁")
},1000)
}
};
Person.sayHello();
没有输出“我叫小暖我今年18岁”的原因:
setInterval函数是在全局作用域下执行的(
对象的花括号是不能封闭作用域的
),所以此时this指向的是全局window,window对象没有username、age,所以输出的是undefined。
是否可以用箭头函数来解决这个问题??
2.箭头函数的定义
ES6使用 => 箭头 定义函数
2.1.函数没有参数
传统函数写法:
var say=function(){
console.log("Hello World!");
}
箭头函数写法
:当花括号里面只有一句话时可以去掉花括号。
let say=()=>{
console.log("Hello World!");
}
let say=()=>console.log("Hello World!");
say();
2.2 函数有1个参数
传统函数写法:
var hello=function(name){
console.log("Hello,"+name);
}
hello("小莉");
箭头函数写法:
只有一个参数时,可以去掉圆括号
let hello=name=>console.log("Hello,"+name);
hello("小莉");
2.3函数有多个参数
传统写法:
var sum=function(a,b,c){
console.log(a+b+c);
}
箭头函数写法:
let sum=(a,b,c)=>console.log(a+b+c);
sum(1,2,3);
3.箭头函数和普通函数的区别
3.1不绑定this
箭头函数的this在定义的时候就确定了,箭头函数的this是静态的,始终指向函数声明时所在作用域下的this的值,以后不管如何调用箭头函数,箭头函数的this始终为定义时的this。
const Person={
username:"小暖",
age:18,
sayHello:function(){
setInterval(()=>{
console.log("我叫"+this.username+"我今年"+this.age+"岁")
},1000)
}
};
效果图:
解决了开头的问题
3.2不绑定arguments
例如 打印arguments参数的长度,会报错。
传统函数:
let arrowfunc=function(){console.log(arguments.length)}
arrowfunc();
箭头函数:
let arrowfunc=()=>console.log(arguments.length);
arrowfunc(); //报错
4.箭头函数不适用场景
4.1 不建议在对象的方法中使用
在对象的方法中不建议使用箭头函数。例如:
const Person={
username:"小暖",
age:18,
sayHello:()=>{
setInterval(()=>{
console.log("我叫"+this.username+"我今年"+this.age+"岁")
},1000)
}
};
Person.sayHello();
原因:
因为方法写在了对象里面,而对象的括号是不能封闭作用域的,所以此时的this还是指向全局作用域对象window,而全局对象没有username和age属性。
4.2 不能作为构造函数
由于箭头函数的this具有
不绑定
的特点,不能使用箭头函数作为构造函数,否则会报错。
传统构造函数:
let Person=function(name,age){
this.name=name;
this.age=age;
}
箭头函数进行构造:
报错!
let Person=(name,age)=>{
this.name=name;
this.age=age;
}
let p1=new Person("夏明",23);
console.log("我叫"+p1.name+",今年"+p1.age);
4.3 定义原型方法时不建议使用
function Person(){
this.username="小暖"
}
Person.prototype.sayHello=()=>{
console.log(this.username)
}
var p1=new Person();
p1.sayHello();
运行效果图:
总结:
箭头函数由于代码的简洁性和不绑定调用者this的特点,建议在非方法函数中使用,而在方法函数中使用,需要特别注意它的this绑定问题,如果需要动态地修改this,建议不要使用箭头函数。
|
任务四:Map数据结构
ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合。但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。
1.如何创建Map
创建一个空Map:
const map=new Map();
创建一个非空Map:
const map=new Map([
['name','张三'],
['title','Author']
]);
2.Map常用属性及方法
2.1 size属性:返回Map结构的成员总数
console.log(map.size);
2.2 set方法和get方法
set方法:
增加一个新元素,设置键名key对应的键值value,返回当前 Map。
map.set('friends',['大花','小花']);
//键可以为对象类型
let key={
school:'咸宁职业技术学院'
};
map.set(key,'咸宁市')
get方法:
读取key对应的键值,如果找不到key,则返回undefined。
const map=new Map([
['name','张三'],
['title','Author']
]);
console.log(map.get('name'));//张三
console.log(map.get('sex')); //undefined
2.3 has方法:返回一个布尔值,判断某个键是否在当前Map对象中。
const map=new Map([
['name','张三'],
['title','Author']
]);
console.log(map.has('title')); //true
console.log(map.has('year')); //false
2.4 delete方法:删除某个键,如果删除成功返回true,否则返回false。
const map=new Map([
['name','张三'],
['title','Author']
]);
console.log(map.delete('title')); //true
2.5 遍历方法
- keys() :返回键名的遍历器;
- values():返回键值的遍历器;
- entries():返回所有成员的遍历器;
- forEach()遍历Map所有的成员;
const map=new Map([
['name','张三'],
['title','Author']
]);
// keys():返回键名的遍历器
for (let key of map.keys()){
console.log(key);
}
// values():返回键值的遍历器
for (let value of map.values()){
console.log(value);
}
// entries():返回所有成员的遍历器
for (let[k,v] of map.entries()){
console.log(k,v);
}
// forEach() 遍历Map的所有成员
map.forEach(function(value,index){
console.log(index+":"+value);
})
任务五:Module(模块化)的语法
模块化是指将一个大的程序文件,拆分成许多小的文件,然后将小文件组合起来。
模块化的优势有以下几点:
1. 防止命名冲突;
2. 代码复用;
3. 高维护性;
模块功能主要由两个命令构成:
export
和
import
;
export 命令用于规定模块的对外接口(
导出模块
);
import 命令用于输入其他模块提供的功能(
导入模块
);
1.export命令
一个模块就是一个独立的文件,文件内部的所有变量外部无法获取。如果希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量。
1.1 输出变量
第一种写法:
export var firstName='Michael';
export var lastName='Jackson';
export var year=1958;
第二种写法:
var firstName='Michael';
var lastName='Jackson';
var year=1958;
export {firstName,lastName,year};
1.2 输出函数
输出单个函数:
export function multiply(x,y){
return x*y;
};
输出多个函数:
function v1(){ };
function v2(){ };
export{
v1,v2
};
2. import 命令
使用export命令定义模块的对外接口以后,其他JS文件就可以通过import命令加载这个模块,并从中输入变量。
示例:
//main.js
import {firstName,lastName,year} from './profile.js';
function setName(element){
element.textContent=firstName+''+lastName;
}
注意:
import命令输入的变量都是
只读
的,因为它的本质是输入接口,不允许在加载模块的脚本中改写接口内容。
3.export default命令
从上面可以看出,使用import命令时,用户需要知道所要加载的变量名或函数名,否则无法加载。为了给用户提供方便,不用阅读文档就可以加载模块,此时需要用到export default命令,为模块指定默认输出。
示例:
//export-default.js
export default function(){
console.log('foo');
}
//import-default.js
import customName from './export-default';
customName();
export-default.js是一个模块文件,它的默认输出是一个函数,import-default.js为另一个模块,在加载export-default.js模块时,import命令可以为匿名函数指定任意名字(customName),而不需要知道原模块输出的函数名。
注意:
- 一个模块只能有一个默认输出,export default只能使用一次;
- import命令后面可以不用加大括号;
- export default命令用在非匿名函数前也是可以的;
任务六:Promise对象
Promise 是 ES6 引入的异步编程的新解决方案。语法上 Promise 是一个构造函数,用来封装异步操作并可以获取其成功或失败的结果。
Promise对象有3种状态
:pending(进行中)、fulfilled(已成功)、rejected(已失败)。
基本用法示例:
//实例化promise对象
const p=new Promise(function(resolve,reject){
setTimeout(function(){
//成功,则调用resolve方法,promise对象的状态就会是成功
// let data='用户的数据';
// resolve(data);
//失败,则调用reject方法,promise对象的状态就会是失败
let err='数据读取失败';
reject(err);
},1000)
});
//根据promise对象的状态分别调用 Promise 对象的then方法,两个参数为函数
p.then(function(value){ //promise对象的状态为成功
console.log(value);
},function(reason){//promise对象的状态为失败
console.error(reason);
});
promise构造函数接受一个函数作为形参,该函数的两个参数分别是resolve和reject。他们是两个函数,由JavaScript引擎提供,不用我们部署。
resolve函数的作用
是将promise对象的状态变为成功,并将异步操作的结果作为参数传递出去,然后调用then方法的一个回调函数;
reject函数的作用
是将promise对象的状态变为失败,并将异步操作的报错作为参数传递出去,然后调用then方法的第二个回调函数;