ES6
Babel 转码器:可以将 ES6 代码转为 ES5 代码,从而在老版本的浏览器执行。这意味着,可以用 ES6 的方式编写程序,又不用担心现有环境是否支持。
一、let 与 const
- var定义的变量,没有块的概念,可以跨块访问, 不能跨函数访问。
- let定义的变量,只在let命令所在的代码块内有效即只能在块作用域里访问,不能跨块访问,也不能跨函数访问。
- const用来定义常量,使用时必须初始化(即必须赋值),只能在块作用域里访问,而且不能修改。(存放的是地址,可以当作指针使用),但允许在不重新赋值的情况下修改它的值
- 重复声明:已经存在的变量或常量,又声明了一遍,var 允许重复声明,let、const 不允许
- 变量提升:var 会提升变量的声明到当前作用域的顶部,let、const 不存在变量提升
- 只要作用域内存在 let、const,它们所声明的变量或常量就自动“绑定”这个区域,不再受到外部作用域的影响,let、const 存在暂时性死区,var不存在
- window 对象的属性和方法:全局作用域中,var 声明的变量,通过 function 声明的函数,会自动变成 window 对象的属性或方法,let、const 不会
- 块级作用域:var没有,let和const有,作用域链:内层作用域->外层作用域->…->全局作用域
<script type="text/javascript">
// 块作用域
{
var a = 1;
let b = 2;
const c = 3;
// c = 4; // 报错
var aa;
let bb;
// const cc; // 报错
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3
console.log(aa); // undefined
console.log(bb); // undefined
}
console.log(a); // 1
// console.log(b); // 报错
// console.log(c); // 报错
// 函数作用域
(function A() {
var d = 5;
let e = 6;
const f = 7;
console.log(d); // 5
console.log(e); // 6
console.log(f); // 7
})();
// console.log(d); // 报错
// console.log(e); // 报错
// console.log(f); // 报错
</script>
for循环的计数器,就很合适使用let命令。
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 10
变量i是var命令声明的,在全局范围内都有效,所以全局只有一个变量i。每一次循环,变量i的值都会发生改变,而循环内被赋给数组a的函数内部的console.log(i),里面的i指向的就是全局的i。也就是说,所有数组a的成员里面的i,指向的都是同一个i,导致运行时输出的是最后一轮的i的值,也就是 10
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 6
变量i是let声明的,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量,所以最后输出的是6。
const实际上保证的并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。
const foo = {};
// 为 foo 添加一个属性,可以成功
foo.prop = 123;
foo.prop // 123
// 将 foo 指向另一个对象,就会报错
foo = {}; // TypeError: "foo" is read-only
ES6 声明变量的六种方法:var
命令和function
命令、let
命令和const
命令、import
命令和class
命令
顶层对象
,在浏览器
环境指的是window对象
,在 Node
指的是`global对象
总结:
以后声明变量使用 let 就对了
声明对象类型使用 const,非对象类型声明选择 let
当遇到字符串与变量拼接的情况使用模板字符串(``)
二、⭐解构赋值
从数组和对象中提取值,对变量进行赋值,这被称为解构
1、嵌套数组进行解构
属于“模式匹配”
,只要等号两边的模式相同,左边的变量就会被赋予对应的值
let [foo, [[bar], baz]] = [1, [[2], 3]];
foo // 1
bar // 2
baz // 3
let [ , , third] = ["foo", "bar", "baz"];
third // "baz"
let [x, , y] = [1, 2, 3];
x // 1
y // 3
let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]
let [x, y, ...z] = ['a'];
x // "a"
y // undefined
z // []
如果解构不成功,变量的值就等于undefined
2、不完全解构
即等号左边的模式,只匹配一部分的等号右边的数组。这种情况下,解构依然可以成功。
(1)数组
对于Set
结构,也可以使用数组的解构赋值。
let [x, y, z] = new Set(['a', 'b', 'c']);
x // "a"
事实上,只要某种数据结构具有Iterator
接口,都可以采用数组形式的解构赋值。
let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'
let [x = 1] = [undefined]; x // 1
let [x = 1] = [null]; x // null
如果默认值是一个表达式,那么这个表达式是惰性求值的,即只有在用到的时候,才会求值。
function f() {
console.log('aaa');
}
let [x = f()] = [1];
因为x能取到值,所以函数f根本不会执行
默认值可以引用解构赋值的其他变量,但该变量必须已经声明。
let [x = 1, y = x] = []; // x=1; y=1
let [x = 1, y = x] = [2]; // x=2; y=2
let [x = 1, y = x] = [1, 2]; // x=1; y=2
let [x = y, y = 1] = []; // ReferenceError: y is not defined
报错原因:x用y做默认值时,y还没有声明
(2)对象:对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量,没有次序
// 例一
let { log, sin, cos } = Math;
将Math对象的对数、正弦、余弦三个方法,赋值到对应的变量上
// 例二
const { log } = console;
log('hello') // hello
变量名与属性名不一致
let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
baz // "aaa"
// 错误的写法
let x;
{x} = {x: 1}; // SyntaxError: syntax error
// 正确的写法
let x;
({x} = {x: 1});
3、字符串
字符串被转换成了一个类似数组的对象
const [a, b, c, d, e] = 'hello';
["h","e","l","l","o"]
4、数值和布尔值
let {toString: s} = 123;
s === Number.prototype.toString // true
let {toString: s} = true;
s === Boolean.prototype.toString // true
5、函数参数
[[1, 2], [3, 4]].map(([a, b]) => a + b); // [ 3, 7 ]
(1)函数move的参数是一个对象,通过对这个对象进行解构,得到变量x和y的值
function move({x = 0, y = 0} = {}) {
return [x, y];
}
move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, 0]
move({}); // [0, 0]
move(); // [0, 0]
(2)为函数move的参数指定默认值,而不是为变量x和y指定默认值
function move({x, y} = { x: 0, y: 0 }) {
return [x, y];
}
move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, undefined]
move({}); // [undefined, undefined]
move(); // [0, 0]
三、箭头函数
let fn = (arg1, arg2, arg3) => {
return arg1 + arg2 + arg3;
}
箭头函数的注意点:
- 如果形参只有一个,则小括号可以省略
- 函数体如果只有一条语句,则花括号可以省略,函数的返回值为该条语句的 执行结果
- 箭头函数 this 指向声明时所在作用域下 this 的值(箭头函数不会更改 this 指向,用来指定回调函数会非常合适)
- 箭头函数不能作为构造函数实例化
- 不能使用 arguments
四、rest 参数
ES6 引入 rest 参数,用于获取函数的实参,用来代替 argument
function add(a,b,…args){ console.log(args); } add(1,2,3,4,5);
rest 参数必须是最后一个形参
rest 参数非常适合不定个数参数函数的场景
五、spread 扩展运算符
好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列,对数组进行解包。
let tfboys = ['德玛西亚之力','德玛西亚之翼','德玛西亚皇子'];
function fn(){
console.log(arguments);
}
fn(...tfboys)
//展开对象
let skillOne = {
q: '致命打击',
};
let skillTwo = {
w: '勇气'
};
let skillThree = {
e: '审判'
};
let skillFour = {
r: '德玛西亚正义'
};
let gailun = {...skillOne, ...skillTwo,...skillThree,...skillFour};
六、Symbol
Symbol 特点 :
- Symbol 的值是唯一的,用来解决命名冲突的问题
- Symbol 值不能与其他数据进行运算
- Symbol 定义的对象属性不能 使 用 for…in 循 环遍 历 ,但 是可 以 使 用 Reflect.ownKeys 来获取对象的所有键名
//创建 Symbol
let s1 = Symbol();
console.log(s1, typeof s1); // Symbol() ,'symbol'
//添加标识的 Symbol
let s2 = Symbol('me');
let s2_2 = Symbol('me');
console.log(s2 === s2_2); // false
//使用 Symbol for 定义
let s3 = Symbol.for('me');
let s3_2 = Symbol.for('me');
console.log(s3 === s3_2); // true
注: 遇到唯一性的场景时要想到 Symbol
Symbol 内置值
- Symbol.hasInstance 当其他对象使用
- instanceof 运算符,判断是否为该对 象的实例时,会调用这个方法
- Symbol.isConcatSpreadable 对象的 Symbol.isConcatSpreadable 属性等于的是一个 布尔值,表示该对象用于 Array.prototype.concat()时, 是否可以展开。
- Symbol.species 创建衍生对象时,会使用该属性 Symbol.match 当执行 str.match(myObject) 时,如果该属性存在,会 调用它,返回该方法的返回值。
- Symbol.replace 当该对象被 str.replace(myObject)方法调用时,会返回 该方法的返回值。
- Symbol.search 当该对象被 str.search (myObject)方法调用时,会返回 该方法的返回值。
- Symbol.split 当该对象被 str.split(myObject)方法调用时,会返回该 方法的返回值。
- Symbol.iterator 对象进行 for…of 循环时,会调用 Symbol.iterator 方法, 返回该对象的默认遍历器 Symbol.toPrimitive 该对象被转为原始类型的值时,会调用这个方法,返 回该对象对应的原始类型值。
- Symbol. toStringTag 在该对象上面调用 toString 方法时,返回该方法的返回值
- Symbol. unscopables 该对象指定了使用 with 关键字时,哪些属性会被 with 环境排除。
七、迭代器
遍历器(Iterator)就是一种机制。它是一种接口,为各种不同的数据结构提 供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作.
原生具备 iterator 接口的数据(可用 for of 遍历) :Array 、Arguments 、 Set 、 Map 、 String 、 TypedArray 、NodeList
工作原理:
- 创建一个指针对象,指向当前数据结构的起始位置
- 第一次调用对象的 next 方法,指针自动指向数据结构的第一个成员
- 接下来不断调用 next 方法,指针一直往后移动,直到指向最后一个成员
- 每调用 next 方法返回一个包含 value 和 done 属性的对象
注: 需要自定义遍历数据的时候,要想到迭代器。
八、生成器
生成器函数是一种异步编程解决方案
function * gen(){
yield '一只没有耳朵';
yield '一只没有尾巴';
return '真奇怪';
}
let iterator = gen();
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next())
代码说明:
- *的位置没有限制
- 生成器函数返回的结果是迭代器对象,调用迭代器对象的 next 方法可以得到 yield 语句后的值
- yield 相当于函数的暂停标记,也可以认为是函数的分隔符,每调用一次 next 方法,执行一段代码
- next 方法可以传递实参,作为 yield 语句的返回值
九、Promise
Promise 是 ES6 引入的异步编程的新解决方案。
- 1.js的执行顺序,先同步后异步
- 2.异步中任务队列的执行顺序: 先微任务microtask队列,再宏任务macrotask队列
- 3.调用Promise 中的resolve,reject属于微任务队列,setTimeout属于宏任务队列
注意以上都是 队列,先进先出。
微任务包括 process.nextTick
,promise
,MutationObserver
。
宏任务包括 script
, setTimeout
,setInterval
,setImmediate
,I/O
,UI rendering
。
语法上 Promise 是一个构造函数, 用来封装异步操作并可以获取其成功或失败的结果。
- Promise 构造函数: Promise (excutor) {}
- Promise.prototype.then 方法
- Promise.prototype.catch 方法
- Promise.prototype.finally方法
从语法上说,Promise是一个对象,从它可以获取异步操作的消息,可以解决回调地狱(回调地狱嵌套回调函数)
Promise的含义:本身不是异步,是封装异步操作容器,统一异步的标准
Promise对象的特点:对象的状态不受外界影响,一旦状态改变,就不会再变,任何时候都可以得到这个结果
js中的异步操作
ajax请求
var xhr = new XMLHttpRequest();
xhr.onreadystatechange=function(){}
xhr.addEventListener('readystatechange',function(){})
浏览器事件
ele.onclick=function(){}
定时
setTime(function(){
},1000);
对原生ajax封装
function aa(method='get',path='1.php'){
return new Promise((f1,f2)=>{
let request = new XMLHttpRequest();
request.open(method,path);
request.send();
request.onreadystatechange=()=>{
if(request.readyState ==4){
if(request.status ==200){
f1.call(null,request.responseText)
}else{
f2.call(undefined,request.status)
}
}
}
})
}
aa('get','1.php').then(function(da){
console.log(da)
}).then(function(da){
aa('get','2.php').then(function(da){
console.log(da)
})
})
fetch("https://jsonplaceholder.typicode.com/post/1")
.then((res) => res.json())
.then((json) => {
console.log(json);
})
...
.catch((err) => {
// 之前任意一个阶段发生错误都会触发catch,之后的then()将不会执行
console.log(err);
})
.finally(() => {
// 执行清理操作
stopLoadingAnimation();
})
使用async和await
async function f(){
const response = await fetch("http://...")
const json = await response.json();
console.log(json)
}
f();
async function f(){
// const a = await fetch("http://...")
// const b = await fetch("http://...")
// 打破fetch()操作的并行
// 修改后
const promiseA = fetch("http://.../post/1");
const promiseB = fetch("http://.../post/2");
const[a,b] = await Promise.all([promiseA,promiseB])
}
//使用循环时要用for循环,不采用forEach
async function f(){
const promises = [
someAsyncOperation(),
someAsyncOperation(),
someAsyncOperation(),
];
for await (let result of promise){
// ...
}
console.log("done");
}
f();
十、Set和Map
1、Set
数据结构 Set(集合)类似于数组,但成员的值都是唯 一的,集合实现了 iterator 接口,所以可以使用『扩展运算符』和『for…of…』进 行遍历。
集合的属性和方法:
- size 返回集合的元素个数
- add 增加一个新元素,返回当前集合
- delete 删除元素,返回 boolean 值
- has 检测集合中是否包含某个元素,返回 boolean 值
- clear 清空集合,返回 undefined
2、Map
Map 数据结构类似于对象,也是键值对的集合。但是“键” 的范围不限于字符串,各种类型的值(包括对象)都可以当作键。Map 也实现了 iterator 接口,所以可以使用『扩展运算符』和『for…of…』进行遍历。
Map 的属 性和方法:
- size 返回 Map 的元素个数
- set 增加一个新元素,返回当前 Map
- get 返回键名对象的键值
- has 检测 Map 中是否包含某个元素,返回 boolean 值
- clear 清空集合,返回 undefined
十一、class 类
通过 class 关键字,可以定义类。
//父类
class Phone {
//构造方法
constructor(brand, color, price) {
this.brand = brand;
this.color = color;
this.price = price;
}
//对象方法
call() {
console.log('我可以打电话!!!')
}
}
//子类
class SmartPhone extends Phone {
constructor(brand, color, price, screen, pixel) {
super(brand, color, price);
this.screen = screen;
this.pixel = pixel;
}
//子类方法
photo(){
console.log('我可以拍照!!');
}
playGame(){
console.log('我可以玩游戏!!');
}
//方法重写
call(){
console.log('我可以进行视频通话!!');
}
//静态方法
static run(){
console.log('我可以运行程序')
}
static connect(){
console.log('我可以建立连接')
}
}
//实例化对象
const Nokia = new Phone('诺基亚', '灰色', 230);
const iPhone6s = new SmartPhone('苹果', '白色', 6088, '4.7inch','500w');
//调用子类方法
iPhone6s.playGame();
//调用重写方法
iPhone6s.call();
//调用静态方法
SmartPhone.run();
十二、模块化
ES6 模块化语法 模块功能主要由两个命令构成:export 和 import。
- export 命令用于规定模块的对外接口
- import 命令用于输入其他模块提供的功能
优势:1) 防止命名冲突 2) 代码复用 3) 高维护性