1.ES6是什么,和ES5的联系和区别?
ES6在2015年6月发布的javascript的新版的标准,正式名称是ECMAScript2015标准。
我们现在使用的js语法规范,是ES5制定的,而ES6是在ES5版本的基础上,扩展了一些 新的可以更方便高效来使用的方法,形成的javascript的新标准的版本就是ES6。
2.ES6常用的API
2.1 let ,const与 var 声明变量的差异
-
块级作用域的概念
在ES5中,只有全局作用域和函数作用域。有时会导致函数作用域覆盖了全局作用域;或者循环中的变量泄露为全局变量。
ES6中新增了块级作用域。 块作用域由 { } 包括,也包括if语句和for语句里面的{ }
var声明变量,没有块级作用域的概念
var testOne = 100;
function fn1() {
if (100 > 10) {
testOne = 200;
console.log(testOne);
}
}
fn1(); //200 函数作用域覆盖了全局作用域
console.log(testOne); //200
for (var i = 0; i < 10; i++) {
console.log(i);
}
console.log(i); //10 循环中的变量泄露为全局变量
let 声明的变量 具有块级作用域。
{
{
let testTwo = 200;
var testThree = 300;
console.log(testTwo); //200 let声明的变量只在它所在的代码块有效。
}
let testTwo = 500;
console.log(testTwo); // 500 外层作用域无法获取到内层作用域。即使外层和内层都使用相同变量名,也都互不干扰
console.log(testThree); //300
}
var和let声明变量对比典型实例
for (var t = 0; t < 3; t++) {
setTimeout(() => {
console.log(t); // 3 3 3
}, 100);
} //变量t是var声明的,在全局范围内都有效。所以每一次循环,新的值都会覆盖旧值,导致最后输出的是最后一轮的t的值。
for (let t = 0; t < 3; t++) {
setTimeout(() => {
console.log(t); //0 1 2
}, 100);
} //let关键字声明的变量具有块级作用域,每次循环所生成的块级作用域包含着当时t的值,这些块级作用域相互独立,互不影响.
for (var t = 0; t < 3; t++) {
(function(t) {
setTimeout(() => {
console.log(t); // 0 1 2
}, 100);
})(t);
} //这种可以实现同样的效果 每次循环函数的作用域都保存了当时t的值
-
变量的声明提升
JS代码的执行过程分为两个阶段:
1.预解析过程:找到所有的变量和函数声明,提升到作用域的最前面
2.执行过程
JS程序执行前,会将使用var声明的变量提升到所在作用域的最前边;
var 声明的变量 会有变量的声明提升
console.log(testFour, 'testFour'); //undefined
var testFour = 100;
console.log(testFour, 'testFour'); //100
//因为var声明的变量会有变量的声明提升,所以整个过程可以翻译如下
// var testFour;
// console.log(testFour, 'testFour'); //只声明未赋值,undefined
// testFour = 100;
// console.log(testFour, 'testFour'); //100 完成赋值
变量的声明提升和函数的声明提升
console.log(testFive, 'testFive'); //function
var testFive = 500;
function testFive() {
console.log('testFive');
}
console.log(testFive, 'testFive'); //500
//函数声明方式创建的函数的声明提升高于var创建的变量的声明提升,所以以上解析过程如下
/*function testFive() {
console.log('testFive');
}
var testFive;
console.log(testFive, 'testFive'); //Function testFive
testFive = 500;
console.log(testFive, 'testFive'); //500
*/
变量作用域指变量起作用的范围。变量分为全局变量和局部变量。全局变量在全局都拥有定义;而局部变量只能在函数内有效。
在函数体内,同名的局部变量或者参数的优先级会高于全局变量。
console.log(testSix, 'testSix'); //undefined
var testSix = 200;
console.log(testSix, 'testSix'); //200
function testSixFn() {
console.log(testSix, 'testSix'); //undefined
var testSix = 300; //当函数内部声明了变量testSix时,函数内所有的testSix取值都不会取函数外的值
console.log(testSix, 'testSix'); //300
}
testSixFn();
console.log(testSix, 'testSix');
//经过变量和函数的声明提升实际过程如下
/*
function testSixFn() {
var testSix; //局部变量只在当前作用域可访问
console.log(testSix, 'testSix'); //undefined
testSix = 300;
console.log(testSix, 'testSix'); //300
}
var testSix; //全局变量
console.log(testSix, 'testSix');//undefined
testSix = 200;
console.log(testSix, 'testSix');//200
testSixFn();
console.log(testSix, 'testSix'); //200
*/
console.log(testSeven, 'testSeven'); //undefined
var testSeven = 200;
console.log(testSeven, 'testSeven'); //200
function testSevenFn() {
console.log(testSeven, 'testSeven'); //200 当函数内部没有声明变量testSeven时,函数内所有的testSeven取值取函数外的值
testSeven = 700;
}
testSevenFn();
console.log(testSeven, 'testSeven'); //700
//经过变量和函数的声明提升实际过程如下
/*
function testSevenFn() {
console.log(testSeven, 'testSeven'); //200
testSeven = 700;
}
var testSeven;
console.log(testSeven, 'testSeven'); //undefined
testSeven = 200;
console.log(testSeven, 'testSeven'); //200
testSevenFn();//执行函数 此时全局变量testSeven已经赋值
console.log(testSeven, 'testSeven'); //700 全局变量被修改
*/
let 创建变量不存在变量的声明提升
try {
console.log(testEight, 'testEight');
} catch (e) {
console.log(e);
} //报错 testEight is not defined let声明的变量,只能在声明后才能使用否则报错
let testEight = 800;
console.log(testEight, 'testEight'); //800
let声明变量的暂时性死区的问题
ES6规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。
在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区
if (true) {
try {
testNine = 900; //报错
} catch (e) {
console.log(e);
}
let testNine;
testNine = 9;
console.log(testNine); //9
}
同一个作用域内,let不能声明两个同样的变量
let testTen = 1000;
//let testTen = 1100; //报错
const与var创建变量的比较
1.const声明的变量时就要进行赋值,进行初始化,否则报错;
2.const声明的是只读的常量,一旦声明,声明的常量的值是不允许修改的;
3.同let一样,不能创建两个相同的变量(重复声明);
4.const声明的变量具有块级作用域,不存在变量的声明提升,存在暂时性死区;
const声明的变量如果是复合类型变量,const命令只是保证变量名指向的地址不变,并不保证该地址的数据不变,所以将一个对象声明为常量是比较特殊的
const test11 = {
number: 11
};
test11.number = 110;
console.log(test11); //{ number: 110 }
//const test11 = []; //报错,已经声明过同样的变量
冻结对象 Object.freeze
const test12 = Object.freeze({
name: 'trump',
person: {
age: 20
}
});
test12.name = 'lili'; //不会生效,严格模式下,报错
console.log(test12); //{ name: 'trump', person: { age: 20 } }
test12.person.age = 30; //修改test12中复合类型person的属性
console.log(test12); //{ name: 'trump', person: { age: 30 } }
如果想要将一个对象彻底冻结,可以使用Object.freeze递归调用来实现
let freezeObject = (obj) => {
Object.freeze(obj);
Object.keys(obj).map((key, value) => {
if (typeof obj[key] === 'object') {
freezeObject(obj[key]);
}
});
};
const test13 = {
name: 'trump',
person: {
age: 20
}
};
freezeObject(test13);
test13.name = 'tom';
test13.person.age = 80;
console.log(test13); //{name: 'trump',person: {age: 20}}
关于深拷贝和浅拷贝的含义以及两者区别
浅拷贝就是拷贝指向对象的指针,意思就是说:拷贝出来的目标对象的指针和源对象的指针指向的内存空间是同一块空间,
浅拷贝只是一种简单的拷贝,是源对象地址的引用,并非获取了一个对象的复制实体。浅拷贝后的对象(假设为A),如果
被拷贝对象(假定为B)的数据发生变化,则A也会跟这发生变化。因此也被称为浅拷贝,,不是一种真正的复制。
深拷贝,就是将需要拷贝的对象的所有内容进行复制一份放入自身对象当中。其中自身对象独立占用一块空间,和被拷贝
的对象存储大小无关,并且被拷贝的对象所进行的任何操作都不会影响到自身对象。两个对象之间就是完全独立的关系。
深拷贝和浅拷贝最根本的区别在于是否是真正获取了一个对象的复制实体,而不是引用。深拷贝在计算机中开辟了一块
内存地址用于存放复制的对象,而浅拷贝仅仅是指向被拷贝的内存地址。
浅拷贝
const objOne = {
name: 'tom',
age: 20
};
const objTwo = objOne;
objOne.age = 50;
console.log(objOne, objTwo); //{ name: 'tom', age: 50 } { name: 'tom', age: 50 }
使用Json.Parse来实现一个深拷贝
const objThree = {
name: 'trump',
age: 30,
person: {
weight: '60kg'
}
};
const objFour = JSON.parse(JSON.stringify(objThree));
objThree.name = 'lucky';
objThree.age = 60;
console.log(objThree, objFour); //{ name: 'lucky', age: 60, person: { weight: '60kg' } } { name: 'trump', age: 30, person: { weight: '60kg' } }
使用JSON来进行深拷贝的局限性
当要拷贝的对象存在函数,正则,undefined或NAN等时,使用JSON来深拷贝会出现问题
const objFive = {
name: 'trump',
age: 65,
say: function() {
'我的中文名字叫川建国';
},
phoneNumber: /^1[3456789]\d{9}$/,
sex: undefined,
bankNumber: NaN,
object_Five: {
name: 'tom',
actions: function() {
console.log(actions);
}
}
};
const objSix = JSON.parse(JSON.stringify(objFive));
console.log(objFive, objSix); //{ name: 'trump',age: 65,say: [Function: say],phoneNumber: /^1[3456789]\d{9}$/,sex: undefined,bankNumber: NaN } { name: 'trump', age: 65, phoneNumber: {}, bankNumber: null }
使用JSON来实现一个对象的深拷贝时,会把函数和undefined丢失,正则深拷贝为空对象,NAN深拷贝为null。
使用JSON通过序列化和反序列化进行深拷贝的局限性的原因
一般情况下,如果要深拷贝的对象不涉及以上情况,使用JSON来实现深拷贝即可,如果出现了以上情况,需要考虑多种情况,实现一个对象的深拷贝
function deepClone(copyData) {
let copyDataType = judgeDataType(copyData);
let obj;
if (copyDataType == 'array') {
obj = [];
} else if (copyDataType == 'object') {
obj = {};
} else {
return copyData;
}
if (copyDataType == 'array') {
for (let i = 0; i < copyData.length; i++) {
obj.push(deepClone(copyData[i]));
}
} else if (copyDataType == 'object') {
for (let key in copyData) {
obj[key] = deepClone(copyData[key]);
}
}
return obj;
}
//检测对象的具体类型
function judgeDataType(obj) {
/*
每个对象都有一个 toString() 方法,默认情况下,toString()
方法被每个 Object 对象继承。如果此方法在自定义对象中未被覆盖,toString()
返回 "[object type]",其中 type 是对象的类型
*/
const toString = Object.prototype.toString;
const map = {
'[object Boolean]': 'boolean',
'[object Number]': 'number',
'[object String]': 'string',
'[object Function]': 'function',
'[object Array]': 'array',
'[object Date]': 'date',
'[object RegExp]': 'regExp',
'[object Undefined]': 'undefined',
'[object Null]': 'null',
'[object Object]': 'object'
};
return map[toString.call(obj)];
}
//深拷贝objectFive
let objSeven = deepClone(objFive);
console.log(objSeven, 'objectSeven');
如果深拷贝时不涉及时间,正则等兼容的要求,可以直接用以下方式实现对一个对象的深拷贝
function deepCloneSimple(data) {
if (data && typeof data === 'object') {
let obj = Array.isArray(data) ? [] : {};
for (let key in data) {
if (data.hasOwnProperty(key)) {
if (data[key] && typeof data[key] === 'object') {
obj[key] = deepCloneSimple(data[key]);
} else {
obj[key] = data[key];
}
}
}
return obj;
} else {
console.error('输入参数为空或不为对象');
return '输入参数为空或不为对象';
}
}
let objEight = deepCloneSimple(objFive);
console.log(objEight, 'objEight');
//修改原对象
objFive.name = 'lily';
objFive.object_Five.name = 'lucky';
console.log(objEight, 'objectEight');
2.2 解构和赋值
-
数组的解构赋值
只要等号两边的模式相同,左边的变量就会被赋予对应的值
let [ a, b, c ] = [ 1, 2, 3 ];
console.log(a, b, c); //1,2,3
let [ A, [ [ B ], C ] ] = [ 1, [ [ 10 ], 20 ] ];
console.log(A, B, C); //1,10,20
let [ , , third ] = [ 'foo', 'bar', 'baz' ];
console.log(third); //baz
let [ x, , y ] = [ 1, 2, 3 ];
console.log(x, y); //1,3
let [ head, ...tail ] = [ 1, 2, 3, 4 ];
console.log(head, tail); //1,[2,3,4]
let [ X, Y, ...Z ] = [ 'a' ];
console.log(X, Y, Z); //a,undefined,[]
//解构不成功,变量的值就等于undefined
另一种情况是不完全解构,即等号左边的模式,只匹配一部分的等号右边的数组。
let [ One, Three ] = [ 1, 2, 3 ];
console.log(One, Three); //1,2
let [ One_1, [ Two_2 ], Three_3 ] = [ 10, [ 20, 30 ], 40 ];
console.log(One_1, Two_2, Three_3); //10,20,40
解构赋值允许指定默认值
ES6内部使用严格相等运算符(===),判断一个位置是否有值。所以,如果一个数组成员不严格等于undefined,默认值是不会生效的。
let [ foo = true ] = [];
console.log(foo);
let [ AA = 100 ] = [ undefined ];
console.log(AA);
let [ BB = 200 ] = [ null ];
console.log(BB);
-
对象的结构赋值
对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
let { bar, fooo } = { foo: 'aaa', bar: 'bbb' };
console.log(bar, fooo); //bbb,aaa
let { baz } = { fooo: 'aaa', bar: 'bbb' }; //变量没有对应的属性名,undefined
console.log(baz); //undefined
let { Bar: myBar } = { Bar: 'Bar' }; //Bar是匹配的模式,myBar才是变量。真正被赋值的是变量myBar,而不是模式Bar
console.log(myBar); //Bar
console.log();
let obj = { first: 'hello', last: 'world' };
let { first: f, last: l } = obj;
console.log(f, l); //hello,world 对象解构赋值的实质,是先找到与变量同名的属性,然后再赋值给对应的变量,真正被赋值的是后者
//let { bar, foo } = { foo: 'aaa', bar: 'bbb' };可以看作是let { bar:bar, foo:foo } = { foo: 'aaa', bar: 'bbb' };的简写
对象的解构也可以指定默认值。
let { message: msg = 'Something went wrong' } = {};
console.log(msg); //Something went wrong
2.3 Promise对象
-
什么是Promise,有什么用?
Promise是异步编程的一种解决方案,让回调函数变成了规范的链式写法,程序流程可以看的很清楚,可以很好的解决回调函数嵌套的问题 - 新建一个Promise对象
// resolve代表成功 reject失败 都是一个函数
let p = new Promise(function(reslove, reject) {
reslove('成功'); //状态由等待变为成功,传的参数作为then函数中成功函数的实参
//reject('失败'); //状态由等待变为失败,传的参数作为then函数中失败函数的实参
});
Promise实例生成以后,可以用then方法分别指定Resolve状态和Reject状态的回调函数,then中有2个参数,第一个参数是状态变为成功后应该执行的回调函数,第二个参数是状态变为失败后应该执行的回调函数。
p.then(
(data) => {
console.log('成功' + data);
},
(err) => {
console.log('失败' + err);
}
);
Promise承诺:默认情况下是等待状态pending,如果有一天状态转变为成功就成功了,如果状态变成失败就失败了。状态一旦改变了就不能再改变了。
如果then中返回了一个promise 会将promise的结果继续传给第二then中,如果结果是将状态改成成功就走下一个then的成功回调,状态改为失败就走下一个then的失败回调。
function read(content) {
return new Promise(function(reslove, reject) {
setTimeout(function() {
if (content > 4) {
resolve(content);
} else {
reject('小于4');
}
}, 1000);
});
}
read(1)
.then(
(data) => {
console.log(data);
},
(err) => {
console.log(err); //小于4
return read(2); //将状态改为了失败
}
)
.then(
(data) => {
console.log('data', data);
},
(err) => {
console.log(err); //小于4
}
);
Promise实例具有then方法,也就是说,then方法是定义在原型对象Promise.prototype上的。它的作用是为Promise实例添加状态改变时的回调函数。
then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。
第一个then不管是走成功还是失败的回到函数,只要返回一个普通值(不抛出错误或者返回promise),都会执行下一个then的成功的回调。
let p2 = new Promise(function(reslove, reject) {
reject('失败1');
});
p2
.then(
(data) => {
console.log('成功' + data);
},
(err) => {
console.log('失败' + err); //失败失败1
}
)
.then(
(data) => {
console.log('成功1' + data); //成功1undefined
},
(err) => {
console.log('失败1' + err);
}
);
如果执行过程抛出错误执行下一个then的失败;
let p3 = new Promise(function(reslove, reject) {
reject('失败1');
});
p3
.then(
(data) => {
console.log('成功' + data);
},
(err) => {
console.log('失败' + err); //失败失败1
}
)
.then(
(data) => {
console.log('成功1' + data); //成功1undefined
throw Error('下一个失败');
},
(err) => {
console.log('失败1' + err);
}
)
.then(
(data) => {
console.log('成功2' + data);
},
(err) => {
console.log('失败2' + err); //失败2Error: 下一个失败
}
);
catch可以实现错误的捕获 一般写在最后
p3
.then((data) => {
console.log('成功', data);
})
.catch((e) => {
console.log(e, '888888');
});
-
Promise的常用方法
Promise.all方法执行后返回的依旧是promise, all两个全成功才表示成功。
function readOne(content) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve(content);
}, 1000);
});
}
let result = Promise.all([ readOne(1), readOne(2) ]);
result.then((data) => {
console.log(data); //[ 1, 2 ]
});
race的用法 如果先成功了那就成功了, 如果先失败了那就失败了
function readTwo(content) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
if (content > 4) {
resolve(content);
} else {
reject(content);
}
}, 1000 * content);
});
}
let result1 = Promise.all([ readTwo(5), readTwo(2) ]);
result1.then(
(data) => {
console.log('成功' + data);
},
(err) => {
console.log('失败' + err); //失败2
}
);
使用 await/async 用同步的思维去处理异步的代码
function Fn1() {
return new Promise((resolve, reject) => {
let sino = parseInt(Math.random() * 6 + 1);
setTimeout(() => {
resolve(sino);
}, 3000);
});
}
/*Fn1().then((res)=>{
console.log(res,'res')
})*/
async function test() {
let n = await Fn1();
console.log(n, 'nnnnnnnnn');
}
test();