let和const
回顾 var和function
var会把变量声明提前,但是定义没有提前;function会把声明和定义都提前;全局作用域下:使用var和function声明的变量会给window增加属性
getA();
console.log(a);
var a=1;
function getA(){
console.log("方法执行");
}
输出:"方法执行" underfined
let
区别:① 没有变量声明提升 ② 不可以重复定义 ③ 不会把该变量添加到window上面
console.log(a);
let a=0;
console.log(window.a);
let a=0;
说明:都会报错,分别针对上面的几种情况
const
区别:也满足let的三者区别,还包含① 一旦声明必须赋值
② 一旦赋值不可以修改(但是内部的引用数据可以修改)
const a={"a":1,"arr":[1,2,3]};
a.arr[2]=5;
console.log(a.arr); //[1,2,5]
作用域
es6之前只有全局作用域和私有作用域,es6添加了块级作用域
块级作用域
凡是{}都是块级作用域,在块级作用域下,var和function声明的变量依然是全局的,但是let和const声明的变量是私有的
{
var a=0;
let b=0;
}
console.log(a); //0
console.log(b); // b is not define
块级作用域使用
注意:{}如果想表示一个对象,不能放在行首,否则就要用()包裹或者变量接收,不然会被当成一个块级作用域
({"name":"info"});
块级作用域的:if(){}
if(){}中的函数只会提前声明不会定义,当条件成立才会定义
console.log(getA,a); //undefined undefined
if(0){
var a=1;
function getA(){}
}
console.log(getA,a); //undefined undefined
console.log(getB,b); //undefined undefined
if(1){
console.log(getB,b); //ƒ getB(){} undefined
var b=1;
function getB(){}
}
console.log(getB,b); //ƒ getB(){} 1
说明:块级作用域内部的函数声明和定义被提升到块级作用域的最上部;但是块级作用域内部的变量只是声明被提升到块级作用域的最上部。
块级作用域的:for(){}
let func=[];
for(let i=0;i<10;i++){
func.push(function(){
console.log(i);
})
}
func[5](); //5
var funcv=[];
for(var i=0;i<10;i++){
funcv.push(function(){
console.log(i);
})
}
funcv[5](); //10
说明:对比上面两套代码得出结论,let在for(){}中使用,
每一次循环都是一个私有的作用域,互不影响;
解构赋值
数组的解构
设置默认值,如果后面是underfined则会指定默认值的相关操作,如果不是underfined(即使是null等也不行)则执行默认值操作
let a=[1,2,3,4];
let [b,c,...d]=a;
console.log(b,c,d);//1 2 [3, 4]
let [x1,x2=10]=[1,2]
console.log(x1,x2); //1,2
//省略赋值
let [,y]=[1,2];
console.log(y);//2
对象的解构赋值
数组解构赋值有顺序,对象解构赋值可以没有顺序
// let {name,age=10}={name:"qiang",age:undefined};
//{name:name}第一个name是属性值,第二个name才是变量名
//使用的都是变量名,而不是属性值
//值是undefined时候,使用默认值
let {name:name,age=10}={name:"qiang",age:undefined};
console.log(name,age); //qiang 10
//嵌套
let {list:[a,b,c]}={list:[1,2,3]};
console.log(a,b,c);//1 2 3 此时list是属性名而不是变量名,a,b,c才是变量名
其他数据类型解构赋值
//使用数组的解构赋值的形式,如果等号右边不是一个数组,默认将其转换为类数组(类似数组的对象,必须有length属性)
let [x,y]="123";
console.log(x,y);//1 2 都是字符串
//使用对象的解构赋值的形式,如果等号右边不是对象,默认转为对象
console.log(Object(1)); //Number {1}__proto__: Number[[PrimitiveValue]]: 1
let {a}=1;
console.log(a);//underfined
let {__proto__:b}=1;
console.log(b);//输出一个Number类型的对象
let {length:c}="123";//其实这个length就是 Object("123")打印之后对象的一个属性
console.log(c);//3
函数参数的赋值
function getA({name,age=10}){
console.log(name,age);
}
function getA1({name,age=10}={}){
console.log(name,age);
}
getA("qiang");//undefined 10
//这样直接调用getA函数,就相当于传递了underfined,但是underfined是没有name和age属性的
//解决方案是如getA1={}一样赋一个空对象当作默认值
getA();//报错
function getA2({name="qiang",age=20}={}){
console.log(name,age);
}
function getA3({name,age}={name:"qiang",age:20}){
console.log(name,age);
}
getA2();//qiang 20
getA3();//qiang 20
getA2({});//qiang 20
getA3({});//undefined undefined
说明:对比这四个结果,分析区别。
字符串扩展
//1.includes 返回值布尔
//includes("指定字符",开始查找的位置(可选))
//第二个参数不是一个数字的话,就使用Number转换,例如null=>0
console.log("abs".includes('a',1));//false
//2.startswith endswith
//startswith("指定字符",开始查找的位置(可选)),参数二如果不是数字同上
console.log("anc".startsWith('a',0));//true
//endswith("指定字符",从前几个查看(可选)),参数二同上
console.log("anc".endsWith('c',2));//false
//3.repeat 重复,参数必须取整,不可以负数或者Infinity
console.log("abc".repeat(4));//abcabcabcabc
//4.padStart padEnd ES7
//按照指定,补全字符串的指定长度
//参数一:长度,参数二:指定字符
let str="ab";
console.log(str.padStart(5,'g'));//gggab
console.log(str.padEnd(5,'gg'));//abggg
//5.模板字符串
let a="abc";
console.log(`${a}测试`);//abc测试
数组的扩展
Array类上的扩展
//1.Array 是一个函数,返回一个数组
console.log(Array("abc"));//["abc"]
console.log(Array(1,2,3));//[1,2,3]
//如果只有一个参数,且是数字,返回有N个空位的数组
console.log(Array[7]);//[empty*7] 得到七个空位
//Array.of和Array等效,除了在只有一个数字参数时候返回的还是数组
console.log(Array.of(7));//[7]
//Array.from(数组/类数组(有length属性)) 返回一个数组
console.log(Array.from("abc"));//["a","b","c"]
数组原型上的扩展
//1.copyWith 从原数组中读取内容,替换数组的指定位置的内容,修改的是原数组
//(替换目标的起始位置target,查找的起始位置begin,查找的结束位置end(不包括))
let arr=[1,2,3,4,5,6,7,8];
// console.log(arr.copyWithin(4,2,4))// [1, 2, 3, 4, 3, 4, 7, 8]
// console.log(arr)// [1, 2, 3, 4, 3, 4, 7, 8]
//参数三:省略,会直接查找到最后,如果有超出部分,自动截取,原数组length不变
console.log(arr.copyWithin(4,5)) //[1, 2, 3, 4, 6, 7, 8, 8]
//2.fill 按照指定字符填充数组,修改原数组
console.log(arr.fill("呵呵"));// ["呵呵", "呵呵", "呵呵", "呵呵", "呵呵", "呵呵", "呵呵", "呵呵"]
console.log(arr.fill("111",3));//["呵呵", "呵呵", "呵呵", "111", "111", "111", "111", "111"]
//参数三:不包含
console.log(arr.fill("222",3,6));// ["呵呵", "呵呵", "呵呵", "222", "222", "222", "111", "111"]
//3.filter,过滤,返回一个新的数组
let arr1=[1,2,3,4,5,6];
let a1=arr1.filter((value,index,arr)=>{
return value>4;
});
console.log(arr1);//[1,2,3,4,5,6]
console.log(a1);//[5,6]
//4.find 先遍历数组,一旦参数函数返回true,停止查找,返回当前项
//finindex 先遍历数组,一旦参数函数返回true,停止查找,返回当前项索引
let arr2=[1,2,3,4,5,6];
let a2=arr2.find(item=>{
return item>4;
})
console.log(a2);//5
console.log(arr2.findIndex(item=>{
return item>2;
}));//2
//5.includes 判断数组中有没有某一项
let arr3=[1,2,3];
console.log(arr3.includes(1));//true
console.log(arr3.includes(1,1));//false 参数二,是开始查找的位置
//6.reduce 迭代,回调函数参数一:上一次的值,参数二:当前值
console.log(arr3.reduce((pre,cur)=>{
return pre+cur;
}));//6 第一次没有pre 会自动使用0
//7.reduceRight 和reduce一样,只是顺序从右边开始
//8.keys 遍历每一项的接口,可以使用for of/in中
//9.entries 遍历接口,可以遍历到索引和每一项
//10.补充
let arr=["a","b","c"];
arr.filter(function(item,index){
console.log(this);//window
});
arr.filter(function(item,index){
console.log(this);//arr
},arr);
arr.filter((item,index)=>{
console.log(this);//window
});
说明:这种遍历类型的,即使使用箭头函数,也会this指向错误,所以可以通过第二种方式解决(recude和reduceRight不可行,因为这俩方法的参数二是指定默认值的)。
数组空位
数组空位:当前数组的某个索引位置没有任何值,underfined不是空位。判断空位,可以用in方法
//in 判断数组索引位置上有没有值
let arr=[,,,,];
console.log(arr.length);//4 此处是4而不是5,一个逗号代表一个空位
console.log(1 in arr);//false 1是索引
//es5中数组方法对空位的处理一般直接跳过空位 ,例如filter
//es6中方法奖空位处理为underfined
//题目:得到一个7个1的数组
console.log(Array(7).fill(1));
函数的扩展
//length指的是形参的个数,如果形参有默认值,就变成了形参没有默认值的个数
//一般参数的默认值放在最后面
function fn(x,y=4){}
console.log(fn.length)//1
//参数
function fn1(...arg){
console.log(arg);//数组
console.log(arguments);//类数组
}
fn1([1,2,3,4])
函数作用域问题
函数执行的时候先给形参赋值,形参也是私有变量,如果给形参的默认值也是一个变量,先看是不是自己的私有变量,不是自己的再找全局中是否有这个变量,没有就报错。
私有作用域:形参也是私有变量
let m=100,n=10;
function fn(x,y=x){
console.log(x,y);
}
fn(1);//1 1
扩展运算符
- 将非数组(类数组 length)变成数组
- 将数组变成非数组
// 非数组=》数组
let str="123";
console.log([...str]); //['1','2','3']
// 数组=》非数组
let arr1=[1,2,3];
let arr2=[1,2,3];
console.log(arr1.concat(arr2)); //[1,2,3,1,2,3]
console.log([...arr1,...arr2]);//[1,2,3,1,2,3]
//求最大值
let arr3=[1,2,3,4,5];
console.log(Math.max(...arr3));//5
console.log(Math.max.apply(null,arr3)); //5
箭头函数
箭头函数都是匿名函数,通常函数当作参数情况下使用箭头函数
- 箭头函数没有this指向,里面的this是上一级的作用域下的this,如果上一级仍然是箭头函数,继续向上找.
- 箭头函数没有arguments
- 箭头函数不可以用作构造函数,因为不可以用作new执行
let ojb={
fn:function(){
let fn=()=>{
console.log(this);//{fn:f}
console.log(arguments);//arguments is not defined
};
fn();
}
}
ojb.fn()
let ojb={
fn:function(){
let fn=function(){
console.log(this);//windows
};
fn();
}
}
ojb.fn()
对象的扩展
对象的简洁方式
let name="qiang",age=20;
let info={name,age};
let str="hehe";
let obj={
//fn:function(){}
fn(){}, //等效于上面
//属性名是字符串,属性名使用[]里面可以写变量
[str]:name,
["my"+str]:name,
"str":name
}
console.log(obj.str,obj.hehe,obj.myhehe);//qiang qiang qiang
Object的方法扩展
//1.Object ()将参数变成对象
console.log(Object(1));//Number {1}
console.log(Object(true));//Boolean {true}
//2.Object.is 判断两个值是否相等,除了下面两种,其他和===相同
//=== NaN跟NaN不相等 -0===0 true
console.log(Object.is(NaN,NaN))//true
console.log(Object.is(-0,0))//false
//3.Object.assign(obj1,obj2) 合并对象 把obj1合并到obj2上,返回obj1
let obj1={name:"qiang"};
let obj2={age:12};
let obj=Object.assign(obj1,obj2);
console.log(obj); //{name: "qiang", age: 12}
console.log(obj1);//{name: "qiang", age: 12}
//ES7提供了对象的扩展运算符... 如果属性重复,只保留一份
let a1={name:"hehe"};
let a2={info:"ascaa"};
let a={...a1,...a2};
console.log(a); //{name: "hehe", info: "ascaa"}
//4. Obejct.getOwnPropertyDescriptor 获取一个对象某个属性的描述
console.log(Object.getOwnPropertyDescriptor("123","length"));//{value: 3, writable: false, enumerable: false, configurable: false}
对象的get和set
let obj={
_name:"AA",
get name(){
// console.log("获取");
return this._name;
},
set name(val){
// console.log("设置");
// console.log(this==obj);//true
this._name=val;
}
}
console.log(obj.name); //AA
obj.name="测试";
console.log(obj.name);//测试
错误案例:
let obj={
name:"AA",
get name(){
// console.log("获取");
return this.name;
},
set name(val){
// console.log("设置");
// console.log(this==obj);//true
this.name=val;
}
}
obj.name="测试";
说明:这样会无限死循环,set利用是自己设置自己
Symbol
ES6新增的数据类型
//Symbol是一个新的基本数据类型,而且是一个值类型
//使用Symbol函数执行得到一个Symbol数据类型
//跟字符串差不多,但是使用symbol函数得到的数据任意一个都是独一无二完全不同的。
//symbol可以接受一个参数()是对symbol数据的一个描述
//symbol的值不可以跟其他值计算(加减乘除,凭借字符串等等,但是可以转为布尔值)
//作用:一般当作对象的属性
let sym1=Symbol();
let sym2=Symbol();
console.log(typeof sym1);//symbol
console.log(sym1===sym2);//false
let sym3=Symbol("foo");
console.log(sym3);//Symbol(foo)
// console.log(Number(sym3));//会报错
console.log(!Symbol());//false
console.log(Symbol().toString());//Symbol() 字符串
//获取
//Symbol.for() 如果之前有有相同描述的symbol找到这个值,如果么有就创建一个新的symbol
console.log(Symbol.for("foo"));//Symbol(foo)
//使用Symbol.for参数相同值就相同
let q1=Symbol.for("q");
let q2=Symbol.for("q");
console.log(q1===q2);//true,因为第一次是创建,第二次是得到,所以会相等
//Symbol.keyFor(symbol值):找到利用Symbol.for创建的值的描述
//如果使用的是Symbol创建的,利用Symbol.keyFor是得不到的,返回Underfined
console.log(Symbol.keyFor(q1));
Set
类似数组,只有值没有键.通过构造函数方式创建set实例,会默认去重(有size属性)
//参数是一个数组或者类数组(只要有iterable接口的数据:数组,arguments,元素集合,set,map,字符串)
console.log(new Set([1,1,2,3]));//Set(3) {1, 2, 3}
console.log(new Set(["111"]));//Set(1) {"111"}
let s=new Set([1,2,3]);
//size set实例的大小的个数
//add 增加,如果之前没有加上,有的话不加,返回增加后的set实例
//参数,一次只能加一个,不能添加多个
console.log(s.add(2)); //Set(3) {1, 2, 3}
//clear 清空
// console.log(s.clear());//underfined
//delete 删除,成功返回true,失败返回false
//has 判断有没有此项,返回布尔值
s.forEach((v1,v2,set)=>{
console.log(v1); //v1 v2都是当前项,因为set没有key,set就是实例
});
//keys :遍历的接口 和for in/of搭配
//entries :遍历的接口 和for in/of搭配
Map
构造函数方式创建一个Map
参数是一个数组,数组每一项也是数组,有key和value
map实例的key可以是任意数据类型,这点和对象不同(对象的key必须是字符串,不是字符串会自动转成字符串)
// 一个对象,属性名必须是字符串,如果不是字符串,默认也会转成字符串
let m=new Map([[1,2],[3,4]]);
console.log(m);//Map(2) {1 => 2, 3 => 4}
//1.size
//2.get set has delete clear
//3.forEach keys values entries
let m1=new Map([[true,[{name:"name"},{age:12}]],[[1,2],"字符串"]])
console.log(m1);// {true => Array(2), Array(2) => "字符串"}
WeakMap和WeakSet
WeakSet 结构与 Set 类似,也是不重复的值的集合。但是,它与 Set 有两个区别。首先,WeakSet 的成员只能是对象,而不能是其他类型的值。
WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。
这是因为垃圾回收机制依赖引用计数,如果一个值的引用次数不为0,垃圾回收机制就不会释放这块内存。结束使用该值之后,有时会忘记取消引用,导致内存无法释放,进而可能会引发内存泄漏。WeakSet 里面的引用,都不计入垃圾回收机制,所以就不存在这个问题。因此,WeakSet 适合临时存放一组对象,以及存放跟对象绑定的信息。只要这些对象在外部消失,它在 WeakSet 里面的引用就会自动消失。
由于上面这个特点,WeakSet 的成员是不适合引用的,因为它会随时消失。另外,由于 WeakSet 内部有多少个成员,取决于垃圾回收机制有没有运行,运行前后很可能成员个数是不一样的,而垃圾回收机制何时运行是不可预测的,因此 ES6 规定 WeakSet 不可遍历。
WeakMap和WeakSet类似,只能key是对象而不是能是其他数据类型
说明:内存泄漏:例如dom在html页面上面,如果把dom节点从页面上面移除,其他地方也没有引用,这时候如果使用set/map进行相关存储,则该dom节点引用还是不为0,相当于内存浪费,此时使用weakset/weakmap的方式即可避免内存泄漏。
Proxy(拦截,代理)
对目标对象默认操作的拦截/改写
//new Proxy({目标对象},{拦截的方法})
let obj={name:"qiang",age:20};
//p1代理了obj.需要通过p1去操作代理的obj
let p1=new Proxy(obj,{
//获取就会触发
get(target,key ,proxy){
//target:目标对象
//key:属性名
//proxy:当前实例
return target[key];
},
set(target,prokey,value,receiver){
//target:目标对象,此时就是obj
//prokey:设置的属性名
//value:设置的属性值
//receiver:当前实例
target[prokey]=value;
return true;//如果设置成功return true否则false
},
//in 时候触发
has(target,proKey){
// 可以通过arguments查看传进来的参数
if(proKey.startsWith("_")){
return false;
}
return proKey in target;//如果有返回成功return true否则false
},
});
console.log(p1.name);//qiang
function getObj(){
return {name:"qiang"};
}
let p2=new Proxy(getObj,{
//拦截作为函数操作时候的行为
apply(target,object,args){
//函数直接执行,call/apply方式执行,都会触发
//args :表示所有参数
//object:给函数修改this的
if(object){
object.fn=target;
object.fn(...args);
delete object.fn;
}else{
target(...args);
}
}
})
p2();
class
- 静态方法可以被子类继承
- 原型上的方法不可枚举
- class没有变量提升
// let a=new A(10); class没有变量提升的,所以class定义要在前面
class A{
//本身的属性
constructor(x){
//类本身
//this :当前实例
this.x=x;//增加的私有属性
}
//普通方法,相当于写在原型上
getA(){}
//静态方法,相当于写在原型上
static getB(){
console.log("静态方法");
}
}
let a=new A(10);//如果没参数,可以省略()
for (const item in a) {
//只输出x,验证了,class中,原型上的方法不可枚举
console.log(item);//x
}
class B extends A{
constructor(x){
//super()必须写,因为子类没有this,this继承父类,
//super()完毕之后才有this
super(x);//就是父类的constructor
}
get A(){
super.getA();//指向父类A的原型
}
static getBB(){
super.getB();//super调用父类的静态的方法
}
}
B.getBB();//静态方法,实例是获取不到该方法的
//采用class表达式让类直接执行
let a1=new class{
constructor(name){
console.log(name);
}
}("信息");
Promise
//实例一开始的状态就是pending状态,一旦new后,立马执行函数
//执行顺序 new Promise中的代码 ===> 当前队列中的同步代码 ===> then(异步)里面的回调函数
let pro1 = new Promise((resolve,reject)=>{
//resolve 参数都是函数
//reject 该参数是函数
//当前两个函数只能执行一个,要么成功要么失败
//如果在new Promise中有错误,那么会直接执行then中的第二个回调函数,并且把错误信息传给函数,把下方的注释代码去掉注释,可以看到控制台的效果
// let; //使报错代码
resolve("success");//成功Fulfilled
reject("error");//失败Rejected
});
//then方法有两个回调函数
pro1.then((res)=>{
//resolve 成功的回调
console.log(res);
},(e)=>{
//失败的回调
console.log(e);
});//成功打印出了success,如果我们把resolve函数注释掉,那么就会打印e
console.log("因为then方法是异步的,所以不会等待,跳过直接进行这里的代码,所以这里先执行");
异步加载图片
Title
catch
function loadImg(url) {
return new Promise((resolve,reject)=>{
let img = new Image();//创建一个img实例
img.src = url;
img.onload = function () {//img一旦触发onload事件,就表示成功
resolve(img);//成功的话,就拿到这个img,并且执行then方法的第一个回调函数
};
img.onerror = function (e) {//img一旦触发onerror事件,就表示失败
reject(e);//失败的话,就拿到错误信息,并且执行then方法的第二个回调函数
}
})
}
//catch方法用来捕获错误信息,在then方法中不写第二个回调函数,而是用catch来捕获错误信息
//删掉url的部分字段,在浏览器控制台来看catch到的错误信息
loadImg("https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/" +
"it/u=528967148,228759238&fm=27&gp=0.jpg").then((img)=>{
box.appendChild(img)
//catch也可以找到then方法中的错误 去掉下面两句的注释控制台查看错误
// let a;
// let a;
}).catch((e)=>{
//捕获错误的,如果new promise中有错误会被捕获,如果then中的回调函数有错误也会被捕获
console.log(e);
})
all
//console.dir(Promise) 查看Promise的方法
//Promise.all([每一项都是Promise对象,如果不是Promise对象默认转为Promise])
//数组中每一项都必须成功状态,才会执行Promise.all的成功的回调函数
//默认将每一个项的参数放在数组中传回给回调函数,只要一个有错误,就会走报错的回调函数(将第一个错误参数传给回调,之后的都不会再执行)
let p1 = new Promise((resolve, reject) => {
resolve("OK");
});
let p2 = new Promise((resolve, reject) => {
// resolve("OK");
reject("p2Error");
});
let p3 = new Promise((resolve, reject) => {
resolve("OK");
});
let pAll = Promise.all([p1,p2,p3]);
console.log(pAll);//发现他也是Promise对象
pAll.then((res)=>{
console.log(res);//如果三个对象都是resolve成功的话,那么这里打印["OK", "OK", "OK"]
}).catch((e)=>{
console.log(e);//将p2改为 reject("p2Error") ,那么只打印"p2Error" 因为一旦有一个错误的,那么立马执行catch
})
race
//只要数组中有一个对象的状态最先改变,此时当前实例的状态就跟着变
//修改计时器延迟时间先后,查看状态改变
//状态只跟最先改变的对象改变一次
let p1 = new Promise((resolve, reject) => {
setTimeout(()=>{
resolve("OK1");
},3000);
});
let p2 = new Promise((resolve, reject) => {
setTimeout(()=>{
resolve("OK2");
},2200);
});
let p3 = new Promise((resolve, reject) => {
setTimeout(()=>{
resolve("OK3");
},5000);
});
Promise.race([p1,p2,p3]).then((res)=>{
console.log(res);
}).catch((e)=>{
console.log(e);
})
async await
//async函数默认返回一个promise对象
async function getA() {
//return出的内容就是成功回调的参数
//如果这里出现错误,就会被catch捕获到
// throw new Error("出错了");
return "Cyan";
}
getA().then((res)=>{
console.log(res);//打印出了"Cyan"
}).catch((e)=>{
console.log(e);//如果getA函数抛错,那么不会打印"Cyan",而是会打印错误
});
//await
let p = new Promise((resolve, reject) =>{
resolve("Cyan");
});
async function getA() {
//await后跟Promise实例,如果不是也会默认转为Promise实例
//直接让promise实例的回调执行 返回执行时的参数
// console.log(await p);//"Cyan"
//await是一个语法糖 不用通过then就可以直接拿到resolve或者reject的参数
let a = await p;
//等await后面的异步完成之后再去执行后面的代码
console.log(a);
}
getA().then((res)=>{
console.log(res);//这里是undefined是因为没有return
}).catch((e)=>{
console.log(e);
})