ECMAScript
第一章 ECMAScript
1.1 ECMA介绍
ECMA(European Computer Manufactures Association)中文名称为欧洲计算机制造协会,这个组织的目标是评估、开发和认可电信和计算机标准。1994年后该组织改名为ECMA国际。
换而言之,JavaScript的三个包含 ECMAScript、DOM、BOM。其中最核心的内容是,ECMAScript,它规定了语言的组成部分,语法、类型、语句、关键字、保留字、操作符、对象,是我们使用频率最高的部分,在我们刚接触JavaScript时,使用的核心版本为ES5。个人觉得ES5的语法还是要严谨一些,而这篇博客是要对ES6-ES11版本新增特性做一个理解。值的一提的是新的版本是在保证向下兼容的前提下,提供大量的新特性。
第二章 ECMAScript6 新特性
2.1 let的使用
不使用let时
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>let案例</title>
<style>
.item{
background-color: #fff;
border: 1px solid #000;
width: 100px;
height: 100px;
display: inline-block;
margin-left: 20px;
}
</style>
</head>
<body>
<div class="container">
<h2 class="page-header">点击切换颜色</h2>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div>
<script>
// 获取div的元素对象
let items = document.getElementsByClassName('item');
// 遍历并绑定事件
for(var i = 0;i<items.length;i++)
{
items[i].onclick = function(){
// 修改元素的背景颜色
this.style.background = 'pink';
}
}
</script>
</body>
</html>
当我们点击div时会自动将其背景变为粉色。但是如下图所示将代码修改,则无法更改div的背景色。
当我们将代码改成如下图所示的情形时,更改背景色的操作可以实现。这是因为let的作用域是块级作用域,它只在声明的块中起作用,因此循环体中的每个i值都可以区分开。而第一次写的代码内容也能实现是因为用到了this指针明确了事件对象。
注意js代码的解析时,除了一些特殊的声明提前外,会顺序执行代码,因此循环是先与事件结束的。
2.2 const关键字:
const 是用来声明常量的,常量就是值不能修改的量。
一定要赋初始值。
一般常量使用大写。(全大写)
常量值不能修改
块级作用域
对于数组和对象的元素修改,不算做对常量的修改,不会报错。
2.3 es6的变量解构赋值:
1.数组的解构:
2.对象的解构:
// 2.对象的解构赋值
const z={
name:'早早',
age:'12',
zaoshui:function(){
console.log("我喜欢早睡");
}
};
// 变量个数与对象中的属性及方法个数相同时,一一赋值.但是对于所设的变量名在对象的内容中没有相同名称时,不可以进行赋值
let {name,age,sleep} = z;
console.log(name,age,sleep);
// 变量个数不同时,如果所设的变量名与对象中某个内容的名称相同时,可以进行赋值
let {zaoshui} = z;
console.log(zaoshui);
// 变量个数不同时,如果所设的变量名在对象的内容中没有相同名称时,不可以进行赋值
let {zao} = z;
console.log(zao);
2.4 es6 中的模板字符串
// 1.声明
let str = `今天也很开心`;
console.log(str,typeof str); //-->今天也很开心 string
// 2.内容中可以直接出现换行符,不同于单双引号
let str1=`<ul>
<li>菜月昂</li>
<li>蕾姆</li>
<li>艾米丽娅糖糖</li>
</ul>`;
document.body.append(str1);
// 3.可以直接进行变量拼接
let str2 = '异界生活';
let str3 = `${str2} 完结撒花!`;
console.log(str3); //-->异界生活 完结撒花!
2.5 es6简化对象的写法
//es6 允许在大括号里面,直接写入变量和函数,作为对象的属性和 方法
// 让书写更加简洁
let name="汉中";
let nickname = function()
{
console.log('别名小江南');
}
const cityH = {
// 属性名和变量值是一样的,可以直接用变量
//es5写法--》name:name
name,
nickname,
// developing:function(){ 在es6中可简写为如下形式
developing(){
console.log('发展旅游经济');
}
}
console.log(cityH);
打印结果如图所示:
2.6 箭头函数:
基础知识:
// ES6 允许使用箭头(=>)定义函数
// 非箭头函数定义的方法
let fn=function(){
};
// 箭头定义的函数,如下
// 其中,括号内是形参,大括号内是代码体
let fn1=(x,y)=>{
return x+y;
};
console.log(fn1(2,3)); //--->5
箭头函数的特性:
1.this 是静态的,this始终指向函数声明时所在作用域下的this的值。
2.不能作为构造函数实例化对象
3.不能使用arguments变量,该变量使用来保存实参的。
4.箭头函数的简写
// 箭头函数的特性
// 1.this 是静态的,this始终指向函数声明时所在作用域下的this的值。
function getName(){
console.log(this.name);
}
let getName2 = () =>{
console.log(this.name);
}
// 设置 window 对象的name属性
window.name = '早早';
const school ={
name:'zaozao!'
}
getName(); //-->早早
getName2(); //-->早早
// 由于函数是在全局作用域中直接调用,因此,他们的this指针都是指向window的
// call方法调用,call可以替换普通函数的指向对象,这里再一次证明了箭头函数是静态的
getName.call(school); //-->早早
getName2.call(school); //-->早早
// 2.不能作为构造函数实例化对象
// let Person = (name,sex) =>{
// this.name = name;
// this.age = age;
// }
// let ant = new Person('简','女');
// console.log(me); //-->报错
// 3.不能使用arguments变量,该变量使用来保存实参的。
// let fn3=()=>{
// console.log(arguments);
// }
// fn3(4,8,6); //-->报错
//4.箭头函数的简写
// (1)省略小括号,当且仅当形参只有一个的时候
let add = n =>{
return n+n;
}
console.log(add(9));
// (2)省略花括号,当代码体只有一条语句的时候,此时return必须省略
// 而语句的执行结果就是函数的返回值
let pow = (n) => n*n;
console.log(pow(9));
案例:
其一:点击修改元素背景颜色
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>箭头函数的实践</title>
<style>
div{
width: 200px;
height:200px;
background-color: bisque;
}
</style>
</head>
<body>
<div id="ad"></div>
<script>
// 获取元素
let ad = document.getElementById('ad');
// 绑定事件
ad.addEventListener('click',function(){
//定时器
setTimeout(()=>{
// 修改背景颜色
// 由于使用的是箭头函数,它的this指向它的父级
this.style.background='pink';
},2000)
});
</script>
</body>
</html>
//从数组中返回偶数的元素
const result=arr.filter(item%2===0);
console.log(result);//-->(2) [2, 100]
/箭头函数适合与this无关的问题,定时器,数组的方法回调; 箭头函数不适合与this有关的回调,如事件回调、对象的方法、
2.7 rest 参数
引入rest参数,用于获取函数的实参,用来代替arguments
rest参数的形式‘…’+参数名,在使用过程中必须放在函数参数列表的最后。
rest 参数最适合不定个数参数函数的场景。
function date(){
console.log(arguments);
}
date('12','13','965');
// rest参数
function add(...arg){
console.log(arg);
}
add('临夏','楼月','舒舒');
2.8 spread扩展运算符
形如‘…’(三个点),扩展运算符能将数组转换为逗号分隔的参数序列。
浅拷贝是指将对象中的数值类型的字段拷贝到新的对象中,而对象中的引用型字段则指复制它的一个引用到目标对象。如果改变目标对象 中引用型字段的值他将反映在原始对象中,也就是说原始对象中对应的字段也会发生变化。深拷贝与浅拷贝不同的是对于引用的处理,深拷贝将会在新对象中创建一 个新的和原始对象中对应字段相同(内容相同)的字段,也就是说这个引用和原始对象的引用是不同的,我们在改变新对象中的这个字段的时候是不会影响到原始对 象中对应字段的内容。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>扩展运算符</title>
</head>
<body>
<div></div>
<div></div>
<div></div>
<script>
// [...]扩展运算符能将【数组】转换为逗号分隔的【参数序列】
// 扩展运算符的应用
// 1.数组的合并
const chichi = ['素文','鹊羽'];
const zaozao = ['明月','清秋'];
const chizao = chichi.concat(zaozao);
const chizao0 = [...chichi,...zaozao];
console.log(chizao);//--> ["素文", "鹊羽", "明月", "清秋"]
console.log(chizao0);//--> ["素文", "鹊羽", "明月", "清秋"]
// 2.数组的克隆
const zero = ['4','8','6'];
const rezero = [...zero];//如果这里拷贝的内容中有引用类型时,它就是一个浅拷贝
console.log(rezero);//--> ["4", "8", "6"]
//3.将伪数组转为正真的数组
const divs= document.querySelectorAll('div');
const divArr = [...divs];
console.log(divArr);//--》[div, div, div]
</script>
</body>
</html>
2.9 Symbol
2.9.1 Symbol基本使用
ES6 引入了一种新的原始数据类型 Symbol,表示独一无二的值。它是
JavaScript 语言的第七种数据类型,是一种类似于字符串的数据类型。
Symbol 特点 :
①Symbol的值是唯一的,用来解决命名冲突的问题。
②Symbol值不能与其他数据进行运算。
③Symbol定义的对象属性不能使用for…in循环遍历,但是可以使用reflect.ownKeys来获取对象的所有键名。
可以参考Symbol
2.10 迭代器
基础知识:
迭代器(iterator)是一种接口(就是对象里面的一个属性),为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署iterator接口,就可以完成遍历操作。
(1)ES6 创造了一种新的遍历命令for…of循环,iterator接口主要供for…of使用。
(2)原生具备iterator接口的数据(可用for of遍历)。如;Array、Arguments、Set、Map、String、TypedArray、NodeList
(3)工作原理:①创建一个指针对象,指向当前数据结构的起始位置。②第一次调用对象的next方法,指针自动指向数据结构的第一个成员。③接下来不断调用next方法,指针一直往后移动,直到指向最后一个成员。④每调用next方法返回一个包含value和done属性的对象。
<script>
// 声明一个数组
const xiyou = ['雁塔区','长安区','未知区域'];
// 数组中有迭代器,使用for...of遍历数组
for(let v of xiyou){
console.log(v);//--> 结果依次是,雁塔区 长安区 未知区域
// 这里的v保存的是键值
}
for(let v in xiyou){
console.log(v);//--> 结果依次是,0 1 2
// 这里的v保存的是键名
}
// 原理的解释:(结果如下图)
let iterator = xiyou[Symbol.iterator]();
console.log(iterator);
// 调用对象的next方法
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
</script>
实例:
<script>
// 迭代器实现自定义的数据的遍历
const mixue ={
location:'雁塔区',
juices:['杨枝甘露','柠檬水','芝士盖四季春','燕麦奶茶'],
//自定义迭代器
[Symbol.iterator](){
// 索引变量
let index = 0;
let _this = this;
return {
next:function(){
if(index<_this.juices.length){
const result = {value:_this.juices[index],done:false};
index++;
return result;
}
else{
return {value:undefined,done:true};
}
}
};
}
}
// 用for...of 遍历该对象,且只取出juices的数据
// 用for...of遍历的前提是有迭代器
for(let v of mixue){
console.log(v);//-->结果依次是 杨枝甘露 柠檬水 芝士盖四季春 燕麦奶茶
}
for(let i=0;i<mixue.juices.length;i++){
console.log(mixue.juices[i]);//-->结果依次是 杨枝甘露 柠檬水 芝士盖四季春 燕麦奶茶
}
// 虽然上述的两种方式都可以达到相同的结果,但是有迭代器的第一种,存在封装效果,不会让适用对象的用户知道
// 对象中所有的内容,只是部分可见。而第二种方式,用户在使用对象时,是知道对象中有什么东西存在的,
2.11 生成器函数声明与调用:
生成器函数是是提供一种异步编程解决方案的函数。它的返回结果是迭代器对象。
基础知识:
-注意:
- (1) “*”的位置没有限制。
- (2)生成函数返回的结果是迭代器对象,调用迭代器对象的next方法可以得到yield语句后的值。
- (3)yield相当于函数的暂停标记,也可以认为是函数的分隔符,每调用一次next方法,执行一段代码。例如;如果在函数中写有3个yield就意味着,函数体在执行时被分成了4块,通过迭代器的next()方法可逐个执行。
- (4)next方法可以传递实参,作为yield语句的返回值。
关于注意中前三点的代码解释:
<script>
// 生成器就是一种特殊的函数,该函数可用来做异步编程
// 异步编程可用纯回调函数处理,在node的fs模块、ajax、mongodb(都是异步的,所用方式都是回调函数)
// 生成器是对异步的一种新的解决方案
// yield将函数代码切分成几块
function * gen(){
console.log("hello generator");
yield '公无渡河';
yield '公欲渡河';
yield '渡河而死';
console.log("我在渡河而死后执行,在将奈公何之前!");
yield '将奈公何';
}
let iterator = gen();
console.log(iterator);//输出的是迭代器
//iterator.next();//输出hello generator,也就是该句代码将函数的执行推进到‘公无渡河’处,至于语句本身能够运行能返回一个对象
//函数体中有四个yield,则代码被分成了5段
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());//--》此时打印:我在渡河而死后执行,在将奈公何之前!
console.log(iterator.next());//将奈公何后没有内容了,因此next调用后返回的对象中done为true
</script>
关于注意中最后一点的代码解释:
function * test(arg){
console.log(arg);
let one=yield '111';
console.log(one);
let two=yield '222';
console.log(two);
let three=yield '333';
console.log(three);
}
// 生成器函数返回一个迭代器
let iterator = test('aaa');
console.log(iterator.next('AAA'));
console.log(iterator.next('BBB'));
console.log(iterator.next('CCC'));
第二次调next并给next传入参数,则参数会作为上一次的yield的返回值。
实例一:(解决回调地域)
<script>
// 异步编程 文件操作,网络操作(ajax、request)数据库操作
// 1s 后控制后台输出111;2S后输出222;3s后输出333
// 不断地嵌套回调,也被称为回调地域
setTimeout(()=>{
console.log(111);
setTimeout(()=>{
console.log(222);
setTimeout(()=>{
console.log(333);
},3000)
},2000);
},1000);
// 用生成器来写
function one(){
setTimeout(()=>{
console.log(111);
iterator.next();
},1000);
}
function two(){
setTimeout(()=>{
console.log(222);
iterator.next();
},2000);
}
function three(){
setTimeout(()=>{
console.log(333);
iterator.next();
},3000);
}
function * gen(){
yield one();
yield two();
yield three();
}
// 调用生成器函数
let iterator = gen();
iterator.next();
</script>
实例二:有参数的回调
<script>
// 虚拟数据获取 用户数据 订单数据 商品数据
function getUsers(){
setTimeout(()=>{
let data = '用户数据';
iterator.next(data);//在此处已经是第二次调用next()
// 第二次调用next时的参数是第一次yield语句的返回结果
},1000);
}
function getOrders(){
setTimeout(()=>{
let data = '订单数据';
iterator.next(data);
},1000);
}
function getGoods(){
setTimeout(()=>{
let data = '商品数据';
iterator.next(data);
},1000);
}
//创建生成器
function * gen(){
let users = yield getUsers();
console.log(users);
let orders = yield getOrders();
console.log(orders);
let goods = yield getGoods();
console.log(goods);
}
// 调用生成器函数
let iterator = gen();
iterator.next();//-->输出结果依次是:用户数据、订单数据、商品数据
</script>
2.12 Promise
Promise是ES6引入的异步编程的新解决方案,**语法上Promise是一个构造函数**,用来封装异步操作并可以获取其成功或失败的结果。
(1)Promise构造函数:Promise(excutor){}
(2)Promise.prototype.then 方法
(3)Promise.prototype.catch 方法
实例化Promise对象后,实例化时接收一个函数,函数的参数为‘resolve’,‘reject’。函数的内部封装一个异步操作。
promise可能有三种状态:等待(pending)、已完成(fulfilled)、已拒绝(rejected)。
resolve和reject函数能够改变函数的状态。对应实例化对象是接收的函数中的两个参数。
Promise对象有一个then方法,then接收两个函数,第一个函数(参数通常为value)在对象的状态为成功时执行,第二个函数(参数通常为reason)在对象的状态为失败时执行。
<script>
const p = new Promise(function(resolve,reject){
setTimeout(function(){
let data = '数据库中的用户数据';
//改变状态为成功
resolve(data);
let err = '数据读取失败';
//改变状态为失败
reject(err);
//改变状态的两个函数顺序执行,且状态只能改变一次,所以在这个例子中会执行then内的第一个函数
},1000);
});
// 调用Promise对象的then方法
p.then(function(value){
console.log(value);
},function(reason){
console.error(reason);
})
</script>
2.12.1 封装Ajax:
未封装的ajax:参考ajax
// 接口地址;https://api.apiopen.top/getJoke
// 这个ajax接口要选一个允许你的客户端访问的地址,或者就是直接找一个满足“Access-Control-Allow-Origin: *”,当然也可以自己写个服务来测试
// ajax请求写法
// 1.创建对象
const xhr = new XMLHttpRequest();
// 2.初始化
xhr.open("GET","https://api.apiopen.top/getJoke");
// 3.发送
xhr.send();
// 4.绑定事件,处理响应结果
if(xhr.readyState===4){
// 判断响应状态
if(xhr.status>=200&&xhr.status<=299)
{
// 表示成功
console.log(xhr.response);
}
else{
// 如果失败
console.error(xhr.status,xhr.statusText);
}
}
封装的ajax:
const p =new Promise((resolve,reject)=>{
// 封装ajax请求操作
// 1.创建对象
const xhr = new XMLHttpRequest();
// 2.初始化
xhr.open("GET","https://api.apiopen.top/getJoke");
// 3.发送
xhr.send();
// 4.绑定事件,处理响应结果
xhr.onreadystatechange=function(){
if(xhr.readyState===4){
// 判断响应状态码 200-299之间是成功
if(xhr.status>=200&&xhr.status<=299)
{
// 表示成功
resolve(xhr.response);
}else{
// 如果失败
reject(xhr.status);
}
}
}
});
p.then(function(value){
console.log(value);
},function(reason){
console.error(reason);
});
2.12.2 then方法的链式调用:
Promise对象的then方法返回的是一个Promise对象,因此可以对它进行链式调用,从而解决回调地域的问题。
<script>
const p = new Promise((resolve,reject)=>{
setTimeout(()=>{
reject('出错了');
},1000)
});
// 调用then方法
// then 方法的返回结果是Promise对象,对象状态由回调函数的执行结果决定
// 先由p对象的状态选择进入then中的哪一个回调函数,在相应的回调函数里。
// 1.如果回调函数中,返回的结果是非promise类型的属性,状态为成功,返回值为对象的成功的值。
const result = p.then(value =>{
console.log(value);
return 235;
},reason => {
console.warn(reason);
// return 123;
// 2.返回的结果是promise对象,则返回状态的成功与否取决于 返回值中的对象的状态
// return new Promise((resolve,reject)=>{
// resolve('ok');
// });
// 3.抛出错误,状态为失败,返回值为错误信息
throw '出错了';
});
console.log(result);
</script>
2.12.3 catch方法:(语法糖)
用来指示Promise对象失败的回调。
<script>
const p = new Promise(function(resolve,reject){
setTimeout(()=>{
// 设置p对象的状态为失败,并设置失败的值
reject("出错了");
},1000)
});
p.then(function(value){
},function(reason){
console.error(reason);
})
p.catch(function(reason){
console.error(reason);
})
</script>
在上述代码的例子中,调用then或者catch可以实现相同的效果。
语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得·约翰·兰达(Peter J. Landin)发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。通常来说使用语法糖能够增加程序的可读性,从而减少程序代码出错的机会。
2.13 Set(集合):
ES6 提供了新的数据结构 Set(集合)。它类似于数组,但成员的值都是唯
一的,集合实现了 iterator 接口,所以可以使用『扩展运算符(…)』和『for…of…』进
行遍历,集合的属性和方法:
(1)size 返回集合的元素个数
(2)add 增加一个新元素,返回当前集合
(3)delete 删除元素,返回boolean值
(4)has 检测集合中是否包含某个元素,返回boolean值。
(5)clear 清空集合,返回undefined
<script>
let arr=[4,5,6,9,3,3,1,2];
let arr2=[7,5,6,3,0,2,2,4,9,5];
// 1.数组去重
let result_01 = [...new Set(arr)];
let result_02 = [...new Set(arr2)];
console.log(result_01,result_02);
// 2.交集
let result_03 = result_01.filter(item => {
let s2 = new Set(arr2);
if(s2.has(item)){
return true;
}else{
return false;
}
});
console.log(result_03);//-->[4, 5, 6, 9, 3, 2]
// 简写
//let result_033=[...new Set(arr)].filter(item => new Set(arr2).has(item));
// 3.并集
let result_04 = [...new Set(arr),...new Set(arr2)];
console.log(result_04);//-->[4, 5, 6, 9, 3, 1, 2, 7, 5, 6, 3, 0, 2, 4, 9]
// 4.差集
let result_05=[...new Set(arr)].filter(item => !new Set(arr2).has(item));
console.log(result_05);//-->[1]
</script>
2.14 Map:
ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合。但是“键”
的范围不限于字符串,各种类型的值(包括对象)都可以当作键。Map 也实现了
iterator 接口,所以可以使用『扩展运算符』和『for…of…』进行遍历。Map 的属
性和方法:
(1)size 返回Map的元素个数
(2)set 增加一个新元素,返回当前Map
(3)get 返回键名对象的键值
(4)has 检测Map中是否包含某个元素,返回boolean值。
(5)clear 清空集合,返回undefined
2.15 class类:
ES6中的class更像是一个语法糖(class类的绝大部分功能,es5都可以做到),它所提供的class类写法更接近传统语言的写法,引入了Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。
(1)class声明类及constructor定义构造函数初始化。
<script>
class Cat{
// 构造方法,名字不能修改
constructor(name,type){
this.name = name;
this.type=type;
}
// 方法的创建不能使用ES5的对象完整形式
types(){
console.log(this.name + "是" + this.type );
}
}
let littlecat = new Cat("小黑","无限的猫");
littlecat.types();
console.log(littlecat);
</script>
(2)实例对象的属性和函数对象的属性是不相通的(调用会报错),实例对象和函数对象的原型对象是相通的,可以用实例对象来调用。
function Phone(){
}
Phone.price='1999';
Phone.prototype.size = '6.5inch';
let nokia = new Phone();
console.log(Phone.price);//--》1999
console.log(nokia.price);//-->undefined
console.log(nokia.size);//-->6.5inch
console.log(Phone.size);//-->undefined
(3)对于static标注的属性,它属于类,而不属于实例对象。
class Phone{
//静态属性
static name = '手机';
static call(){
console.log("我可以呼叫马克队长!");
}
}
let nokia = new Phone();
console.log(nokia.name);//-->undefined
console.log(Phone.name);//-->手机
(4)类继承
<script>
class Phone{
constructor(brand,price){
this.brand=brand;
this.price=price;
}
// 父类的成员属性
call(){
console.log("我可以打电话");
}
}
class SmartPhone extends Phone{
constructor(brand,price,color,size){
super(brand,price);
this.color = color;
this.size = size;
}
phone(){
console.log("拍照");
}
playGame(){
console.log("玩游戏");
}
// 子类是不能直接调用与父类同名的方法的。因此当子类中有与父类同名的方法时会显示子类的方法
//call(){
//console.log("我可以进行视频通话!");
//}
}
let iplus = new SmartPhone("iplus","1999","金色","5.6inchs");
iplus.call();
iplus.phone();
iplus.playGame();
console.log(iplus);
</script>