let与const详解
let
变量声明:let、var、直接使用(在window对象上创建了一个属性,会污染全局环境)
let和var的主要区别:let声明的变量只在当前(块级)作用域内有效。let声明的变量不能被重复声明。不存在变量提升(不会被预解析)。
ES6之前的作用域:全局作用域、函数作用域、eval作用域。
块级作用域:一对花括号中的区域{}
,块级作用域可以嵌套,声明对象的花括号不是块级作用域。
{
let a = 1;
{
console.log(a);//1
let b = 2;
}
console.log(b);//报错
}
for(let i = 0; i < 3; i++){
console.log(i);//0 1 2
}
console.log(i);//报错
暂存死区
let name = 'gg';
{
console.log(name);//报错
let name = 'mm';
}
console.log(name);
报错原因:由于当前块级作用域中存在该变量,所以不能向上层查询,而且let没有预解析,查询不到当前作用域的变量,所以会报错,这就是暂存死区。
const
使用const:和声明变量一样,只是关键字的区别。常量必须在声明的时候赋值,且不能修改。
与let类似的特性:不能重复声明、不存在提升、只在当前(块级)作用域内有效。
常量为引用类型的时候可以修改该引用类型
const mm = {
age:20,
name:xm
}
console.log(mm);//{age:20,name:xm}
mm.age = 30;
console.log(mm);//{age:30,name:xm}
mm = {};//报错
const只能保证声明的引用类型(数组、对象、函数)的地址不变,不能保证里面的值不变。
那么怎么防止常量为引用类型的时候能被修改的问题出现?Object.freeze()
const mm = {
age:20,
name:xm
}
Object.freeze(mm);
console.log(mm);//{age:20,name:xm}
mm.age = 30;
console.log(mm);//{age:20,name:xm}
ES6之前怎么声明常量?
Object.defineProperty()
可以在为对象添加属性的基础上加入限制条件(可枚举、可修改)
var CST = {};
Object.defineProperty(CST,'name',{
value:'mm',//属性的值
writable:false//只读
})
Object.seal()
表示不可以添加引用类型中的属性,但是可以修改已有的属性,需要配合Object.defineProperty()
来实现ES6中的Object.freeze()
的效果。
var CST = {a:1};
Object.defineProperty(CST,'a',{
writable:false//只读
})
Object.seal(CST);
用上面两个函数实现Object.freeze()
的功能
Object.defineProperty(Object,'freezePolyfill',{
value: function(obj){
var i;
for(i in obj){
if(obj.hasOwnProperty(i)){//判断属性是不是对象自身的属性
if(obj[i] instanceof Object){
Object.freezePolyfill(obj[i]);//如果属性值中有对象则使用该函数递归
}else{
Object.defineProperty(obj,i,{
writable:false
});
}
}
}
Object.seal(obj);
}
})
解构赋值
数组的解构赋值
const arr = [1,2,3,4];
let [a,b,c,d] = arr;
更复杂的匹配规则
const arr = ['a','b',['c','d',['e','f','g']]];
const [ , , [ , , [ , , g ]]] = arr;
扩展运算符
const arr1 = [1,2,3];
const arr2 = ['a','b'];
const arr3 = [1,'x'];
const arr4 = [...arr1, ...arr2, ...arr3];//[1,2,3,'a','b',1,'x']
const arr = [1,2,3,4,5,6];
const [a,b,...c] = arr;//c = [3,4,5,6]
默认值(只有是undefined的时候才会选择默认值)
const arr = [1,null,undefined];
const [a,b=2,c,d='x'] = arr;//[1,null,undefined,'x']
交换变量
let a = 1;
let b = 2;
[a,b] = [b,a];//a = 2,b = 1
接收多个返回值
function getUserInfo(id){
//..ajax
return[
true,
{
name:'mm',
sex:'male',
id:id
},
'success'
];
};
const [status,data,msg] = getUserInfo(123);
对象的解构赋值
const person = {
age:20,
name:'mm'
};
const { age, name } = obj;//对象的属性值必须相同,否则接收不到,返回undefined
对象的解构赋值和数组不同的是对象属性是无序的,不用给不需要的属性空出位置。
更复杂的解构规则
const person = {
age:20,
name:'mm',
friend:[{
name:'qq',
age:19
},{
name:'pp',
age:18
}]
}
const { age } = person;
const { friend: [ friend1, { name }, { name: nam } ] } = person;
外面用对象的解构规则拿到friend,第一项用数组的解构规则拿到,第二项中的name
属性使用对象的解构规则拿到,第三项中的name
属性不能再使用相同的名字接收,所以{ name: nam }
是把属性name
中的值赋给nam
。
扩展运算符
const person = {
name: 'mm',
age: 20,
sex: 'male',
hobby: 'football'
};
const { name, ...oth } = person;//oth中包含剩下的所有没有匹配到的对象
//对象的合并
const person = {
age: 20,
sex: 'male'
}
const student = {
name: 'mm',
...person,
}
对已经声明的变量进行对象的解构赋值
let age;
cosnt obj = {
name: 'mm',
age: 20
}
({ age } = obj);
默认值
let friend = {
name: 'mm',
age: 22
}
let { name, age = 24, hobby = {'football'}} = friend;//当匹配不到或者匹配到undefined就会用到默认值
提取对象属性
const { name, hobby: [hobby1], hobby } = {
name: 'mm',
hobby: ['study']
}
hobby: [hobby1]
提取属性值的内容就是数组中的第一项,即hobby1 = 'study'
;hobby
提取到属性值,即hobby = ['study']
使用对象传入乱序的函数参数
function AJAX({
url,
data,
type = 'get'
}){
console.log(type);//'get'
};
AJAX({
url: '/getinfo',
data: {
a: 1
},
});
获取多个函数的返回值
function getUserInfo(){
//...ajax
return{
status: true,
data: {
name: 'mm'
},
msg: 'success'
};
};
const { status, data, msg } = getUserInfo(123);
字符串的解构赋值
const str = 'I am Tom';
const [a,b,v,...oth] = str;//a='I',b=' ',oth=['a','m',' ','T','o','m']
//三种获取字符串中字符的方式
const [...spStr1] = str;
const spStr2 = str.split('');
const spStr3 = [...str];
数值与布尔值的解构赋值
const { valueOf } = 1;
const { toString } = false;
数值和布尔值本身没有属性,查找属性时先会返回其包装对象再查找。
函数参数的解构赋值
function swap([x,y]){
return [y,x];
};
let arr = [1,2];
arr = swap(arr);
扩展
字符串扩展
const xm = {
name: 'mm',
age: 15,
say1: function(){
console.log('我叫' + this.name + ', 我今年' + this.age + '岁!');
}
say2: function(){
console.log(`我叫${ this.name }, 我今年${ this.age }岁!`);//say2与say1输出相同
}
}
使用${}
省去了+连接字符串,同时不需要把每段字符串单独用引号包裹,整体使用一个反引号即可(`)。
//padStart padEnd
let str = 'i';
let str1 = str.padStart(6,'mooc');//moocmi
let str2 = str.padEnd(5,'mooc');//imooc
//repeat
'i'.repeat(10);/iiiiiiiiii 不能带负数,小数会取整
//startsWith endsWith
const str = 'A promise is a promise';
str.startsWith('A pro');//true
str.endsWith(promise);//true
//includes
const str = 'A promise is a promise';
if(str.indexOf('promise') !== -1){//indexOf的方法
console.log('存在');
}
if(str.includes('a promise')){
console.log('存在';)
}
for-of
遍历字符串
let str = 'PROMISE';
//ES6之前遍历字符串的方法
//1.使用for循环
for(var i = 0;i < str.length;i ++){
console.log(str[i]);
console.log(str.charAt(i));
}
//2.转成数组后遍历
var oStr = str.split('');
const oStr = [...str];
const [...oStr] = str;
oStr.foreach(function(word){
console.log(word);
});
//for-of遍历字符串
for(let word of str){
console.log(word);//P R O M I S E
}
正则扩展
ES6之前的修饰符只有g、i、m
,ES6新加入了两个修饰符。
//u:unicode
console.log(/^\ud83d/.test('\ud83d\udc36'));//true
console.log(/^\ud83d/u.test('\ud83d\udc36'));//false 加入u把整个正则看作一个整体unicode编码
//y 黏连修饰符
const r1 = /imooc/g;
const r2 = /imooc/y;
const str = 'imoocimooc-imooc';
console.log(r1.test(str));//imoocimoocimooc
console.log(r2.test(str));//imoocimooc 必须是紧邻的符合规则的符号,否则不匹配
数值扩展
//新的数值表示法
0o 0O 八进制
0b 0B 二进制
//新的方法和安全数
Number.parseInt(2.23);//2 原来是window下的方法,现在是Number下的方法
Number.isNaN(NaN);//true 是不是NaN
Number.isNaN(1);//false
//实现isNaN()
function isNaN(value){
return value !== value;
}
Number.isFinite(Infinity);//false 是不是有理数
Number.isFinite(1111);//true
Number.MAX_SAFE_INTEGER;//最大安全数 2^53-1
Number.MIN_SAFE_INTEGER;//最小安全数 -2^53+1
Number.isSafeInteger();//判断是否在安全范围
//幂运算
let a = 5**5;//25
let a = 5**5**0;//5 从右往左计算
函数扩展
//函数参数的默认值
function add(a,b = 1+a){//参数的默认值不能出现变量自身或者后面的变量
console.log(a,b);//10,11
}
add(10);
//扩展运算符(把数组中内容展开)
[1,2...[1,2,3]]//[1,2,1,2,3]
//剩余参数(把参数聚合成一个数组)
function sum(...args){//剩余参数必须在最后一位
console.log(args);//[1,2,3,4,5]
}
sum(1,2,3,4,5);
//箭头函数
const add1 = (a,b) => a + b;//函数体为单行
const add2 = function(a,b){
return a + b;
}
console.log(add1(2,2));//4
console.log(add2(2,2));//4
const add1 = (a,b) => {//函数体为多行
a += 1;
return a + b;
}
const pop = arr => void arr.pop();//函数体不需要返回值
箭头函数与普通函数的区别
//1.箭头函数中没有代表实参的arguments,可以用剩余参数代替
const num = (...args) => {
console.log(arguments);//报错
console.log(args);//[1,2,3]
};
num(1,2,3);
//2.箭头函数没有自己的this
const xiaoming = {
name:'小明',
age:null,
getAge:function(){
let _this = this;
//...ajax
setTimeout(function(){
_this.age = 14;//this.age中this指向window,无法获取到age
console.log(_this);
},1000);
}
};
const xiaoming = {
name:'小明',
age:null,
getAge:function(){
//...ajax
setTimeout(() => {
this.age = 14;
console.log(this);
},1000);
}
};
xiaoming.getAge();
1.定时器是谁调用的与定时器中使用普通函数和箭头函数无关。即定时器就是window调用的。
2.普通函数中,谁调用了函数,this执行谁。由于window调用了定时器,所以this执行window。
箭头函数中的this是在函数创建时就会绑定,例如本代码中,定时器是在getAge方法中创建的,所以它里面的this绑定的就是getAge方法中的this。
对象扩展
//简洁表示法
const getUserInfo = (id = 1) => {
const name = 'xiaoming',
const age = 10;
return {
name,//name:name
age,/age:age
say(){//say: function()
console.log(this.name + this.age);
}
};
};
const xiaoming = getUserInfo();
//属性名表达式
const key = 'age';
const obj = {
name:'xm',
[key]:14;//age:14
['daf'+'dsfds'+789]:111;
}
//扩展运算符复制对象(浅拷贝)
const obj = {
a:1,
b:2,
c:{
aa:1,
bb:2
}
}
const obj2 = {
a:3,
b:4,
z:10
}
const cObj1 = { ...obj1 };
console.log(cObj1.d.aa);//1
cObj1.d.aa = 999;
console.log(cObj1.d.aa);//999
console.log(Obj1.d.aa);//999
//合并对象
const newObj = {
...obj1,
...obj2//a:3,b:4,... 合并对象中有相同的属性,谁在后面取谁的值
}
//部分新的方法和属性
Object.is(+0,-0)//false
+0 === -0//true
Object.is(NaN,-NaN)//true(其他是否相等的比较与===的结果相同)
NaN === NaN//false
const obj = Object.assign({a:1},{b:2},{c:3},{d:4},{e:5});//{a:1,b:2,c:3,d:4,e:5}(注意是浅拷贝)
const obj = {
a:1,
b:2,
c:3,
d:4
}
console.log(Object.keys(obj));//['a','b','c','d'] 返回属性组成的数组
console.log(Object.values(obj));//[1,2,3,4] 返回属性值组成的数组
console.log(Object.entries(obj));//[['a',1],['b',2],['c',3],['d',4]] 返回每一对键值对
//super
const obj = {name:'xm'};
const cObj = {
say(){//必须使用对象的简写方式才能使用super
console.log(`My name is ${super.name}`);
}
}
Object.setPrototypeOf(cObj,obj);//把obj设置为cObj的原型对象
cObj.say();//super可以获取到原型上的属性
console.log(cObj.__proto__ === Object.getPrototypeOf(cObj));//true 获取原型
数组扩展
//扩展运算符的使用
const user = ['小明',14,['eat','play'],'我有女朋友'];
function say(name,age,hobby,desc){
console.log(`我叫${name},我今年${age}岁,我喜欢${hobby.join('and')},${desc}`);
}
say(user[0],user[1],user[2],user[3]);//一个一个传太麻烦了
say(...user);
say.apply(null,user);//apply传入的数据是数组形式
//扩展运算符合并字符数组
const arr1 = [1,2,3.4];
const arr2 = [4,3,2,1];
const arr3 = [1.2,'123',true];
const cArr1 = [1,2,3,...arr2];//在已有数据中合并数组
const cArr2 = [...arr1];//数组的复制
const [...cArr3] = arr3;//数组的复制
const cArr4 = [...arr1,...arr2,...arr3];//合并几个数组
//将类数组对象转换成数组的方法(总结)
const obj = {
0: 1,
1: 'asd',
2: false,//类数组对象的属性名必须是数值型或字符串型的数字
length: 2
};
Array.from(obj, item => item * 2);//将类数组转换成数组,并且执行后面的回调
Array.prototype.slice.call();
[].slice.call();
[...]
//新的方法
//Array.of
console.log(Array.of(1,2,'123',true));//[1,2,'123',true];
//Array#fill
let arr = new Array(5).fill(0);//[0,0,0,0,0] 如果数组中有值会被覆盖掉
let arr = [1,2,3,4,5].fill(0,0,3);//[0,0,0,4,5] 用0覆盖0,1,2位置上的值
//Array.includes
let arr = [1,2,3];
console.log(arr.includes(1));//true 判断数组中有没有某个值
//keys values entries
const arr = [10,11,12];
for (let i of arr.keys()){
console.log(i);//0,1,2
}
for (let i of arr.values()){
console.log(i);//10,11,12
}
for (let [i,v] of arr.entries()){
console.log(i,v);//0 10,1 11,2 12
}
//find 根据条件(回调)按顺序遍历数组 当回调返回true时 就返回当前遍历到的值
const res = [1,2,3,4].find((value,index,arr) => value % 2 === 0);
console.log(res);//2
//findIndex 根据条件(回调)按顺序遍历数组 当回调返回true时 就返回当前遍历到的下标
const res = [1,2,3,4].find((value,index,arr) => value % 2 === 0);
console.log(res);//1
promise
promise对象中存放着我们未来要做的事,比如异步操作,事情完成后会返回相应的结果,结果成功或失败都有对应的反应。
promise基本结构
function fn(){
//函数中返回Promise的实例,传入resolve作为参数代表事件成功后的反应
return new Promise(resolve => {
//以定时器为例表示异步操作
setTimeout(function(){
resolve();
},1000);
})
}
fn.then(console.log(1));//1秒后打印1
promise
相比于以回调函数的方式执行异步操作,首先解决了在有多个异步操作的情况下,回调函数存在嵌套的问题;其次执行异步操作的过程中,回调函数可能出现被用户更改指向等问题。promise
可以解决以上回调函数中的问题。
错误处理
promise
判断resolve
和reject
都是异步执行的,所以无法使用try catch
语句
function fn(val){
return new Promise((resolve,reject) => {
if(val){
resolve();
}else{
reject();
}
});
}
fn(false).then(() => {
console.log('成功');
},() => {
console.log('失败');
})
then
方法中的第二个回调是失败时做的事,resolve
和reject
中只可以传入一个参数,第一个之后的参数接收不到。
//使用实例的catch方法可以捕获错误
fn(true).then(data => {
console.log(data);
return fn(false);
})
.then( () => {
//上面返回的是失败,这里没有对失败的处理函数,所以会向下执行
console.log('我永远不会被输出');
})
//前面如果有对失败的处理函数就不会再执行catch
.catch(e => {
console.log(e);
})
//不论成功还是失败 finally中的内容一定会执行
fn(true).then(data => {
console.log(data);
return fn(false);
})
.catch(e => {
console.log(e);
})
.finally(() =>{
console.log(100);
});
promise
有三种状态,进行中、成功和失败状态,调用resolve
和reject
会将promise
变为成功和失败状态,并且状态的改变不可逆。
promise的其他方法
//Promise.all方法可以把多个promise实例包装成一个新的promise实例
//Promise.all([promise1,promise2,promise3]) 返回Promise
//Promise.all中的所有promise都返回成功,Promise.all返回成功,有任何一个失败都会返回失败。
//Promise.all里面是空数组会返回成功
function getData1(){
return new Promise((resolve,reject) => {
setTimeout(() => {
console.log('第一条数据加载成功');
resolve('data1');
},1000);
});
}
function getData2(){
return new Promise((resolve,reject) => {
setTimeout(() => {
console.log('第二条数据加载成功');
resolve('data2');
},1000);
});
}
function getData3(){
return new Promise((resolve,reject) => {
setTimeout(() => {
console.log('第三条数据加载成功');
resolve('data3');
},1000);
});
}
let p = Promise.all([getData1(),getData2(),getData3()]);
p.then(arr => {
console.log(arr);//[data1,data2,data3] 在所有决议成功后返回该数组
});