let与const
let命令
ES6新增了let命令,用来声明变量。他的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效。通过let声明的变量也不能再次被声明,否则会报错。
块级作用域:
function f1(){
let n=5;
if(true){
let n=10;
}
console.log(n);//5
}
const命令
const用于声明一个只读常量。一旦声明,常量的值就不能改变且必须立即初始化,不能留到以后赋值。常量像let一样不存在变量提升,同样存在暂时性死区,只能在声明的位置后面使用。
字符串的扩展
ES6加强了对Unicode的支持,并且扩展了字符串对象,但在实际应用中常用的是模板字符串。模板字符串(template string)是增强版的字符串,用反引号(`)标识。它可以当作普通的字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量,下面通过传统字符串定义来理解模板字符串。
//传统的JavaScript语言,使用连接字符串
var str=
"<h1>前端开发常用技术</h1>"
"<ul>"
"<li>HTML5</li>"
"<li>CSS</li>"
"<li>JavaScript</li>"
"</ul>";
上面这种写法相当烦琐不方便,ES6引入了模板字符串解决这个问题。
var str=`
<h1>前端开发常用技术</h1>
<ul>
<li>HTML5</li>
<li>CSS</li>
<li>JavaScript</li>
</ul>`
使用模板字符串可以方便的换行,不用使用+号连接。如果需要拼接上变量,那么拼接的格式是使用${}包裹变量即可。
let myName=`雪儿` //使用反引号
let age=18
let=`${myName}年龄是${age}`//雪儿年龄是18
反引号内可以放JavaScript表达式,也可调用函数,将显示函数执行后的返回值都写在${}里面fn()。代码如下:
var a=3;
var b=5;
var c=`${a}+${b}=${a+b}`;
console.log(c);//结果:3+5=8
使用模板字符串注意以下两点:
(1)如果拼接的变量没有声明,会报错。
(2)如果${}里面放的是字符串,则输出还是字符串。
对象那个的扩展
1.简写的属性初始化
function createPerson(name,age){
//返回一个对象:属性名和参数名相同
return{
name:name,
age:age
}
}
console.log(createPerson("李丽",18)); //{name:"李丽",age:18}
在ES6中,上面的写法可以简化成如下格式。
function createPerson(name,age){
//返回一个对象:属性名和参数名相同
return{
name,//当对象属性名和本地变量名相同时,可以省略冒号和值
age
}
}
console.log(createPerson("李丽",18));//{name:"李丽",age:18}
该项扩展在使得对象字面两的初始化变得简明的同时,也清楚了命名错误。对象属性被同名变量赋值在JavaScript中是一种普通的编程模式,所以这项扩展的添加非常受欢迎。
2.简写的方法声明
var person={
name:"李丽",
sayHello:function(){
console.log("我的名字是:"+this.name);
}
}
person.sayHello()
在ES6中,上面的写法可以简化成如下格式。
var person={
name:'李丽',
sayHello(){
console.log("我的名字时:"+this.name);
}
}
person.sayHello()
3.扩展运算符
对象的扩展运算符(...)用于取出参数对象的所有课遍历属性,复制到当前对象之中
let z={a:3,b:4};
let n={...z};
//n的值为{a:3,b:4}
这个方法对于数组也通用。
let foo={...['a','b','c']};
箭头函数
1.箭头函数基本用法
ES6标准新增了箭头函数Arrow Function,箭头函数是一种更加简洁的函数书写方法。代码如下:
参数=>函数体
ES6之前通常的函数定义方法:
var fn1=function(a,b){
return a+b
}
function fn2(a,b){
return a+b
}
使用ES6箭头函数语法定义函数,将原函数的‘function’关键字和函数名都删掉,并使用‘=>'连接参数列表和函数体。
当函数参数只有一个,括号可以省略;但是没有参数时,括号不可以省略,代码如下:
//无参
var fn1=function(){} //ES6之前定义
var fn1=()=>{} //ES5箭头函数
//单个参数
var fn2=function(a){}
var fn2=a=>{}
//多个参数
var fn3=function(a,b){}
var fn3=(a,b)=>{}
//可变参数
var fn4=function(a,b,...args){}
var fn4=(a,b,...args)=>{}
箭头函数相当于匿名函数,并且简化了函数定义。箭头函数有两种格式,一种只包含一个表达式,省略了{...}和return。还有一种可以包含多条语句,这时候就不能省略{...}和return,如下:
()=>return 'hello'
(a,b)=>(a+b)
(a)=>{
a=a+1;
return a;
}
箭头函数返回一个对象。需要特别注意,如果是单表达式要返回自定义对象,不写括号会报错,因为和函数体的{...}由语法冲突。
x=>({key:x})
2.this问题
箭头函数看上去是匿名函数的一种简写,但实际上,箭头函数和匿名函数有个明显的区别:箭头函数内部的this是词法作用域,由上下文确定。词法作用域就是定义在词法阶段的作用域。换句话说,词法作用域是由在写代码时将变量和块作用域写在哪里来决定的,因此当词法分析器处理代码时会保持作用域不变。在ES5中,this:执行函数的上下文;在ES6中,this:定义函数的上下文。
注意:用小括号包含大括号时对象的定义,而非函数主体
箭头函数没有自己的this,函数体内的this对象就是定义时所在的函数上下文,而不是使用时(执行时)所在的函数上下文。
document.onclick=function(){
console.log(this);//输出document
let f=()=>{
console.log(this);//下面语句调用输出document
};
f();
};
其中,document。οnclick=function(){}的这个函数执行时,函数内部的this是执行时的函数上下文document,而第二的console时箭头函数定义时所在环境为document
document.onclick=()=>{
console.log(this);
};
箭头函数中的this会去赵当前定义这个函数的环境中的this为window,因此输出的对象为window。
function fn(){
setTimeout(()=>{
console.log(this);
},1000);
}
fn();
document.onclick=fn;
当这个函数执行document.οnclick=fn时,那么fn()函数中的this为document,使用setTimeout就会去找定义时所在环境中的this为document;当直接执行fn()函数时,fn()函数中的this为window,那么就会输出window
const cat={
lives:9,
jumps:()=>{
return this;
},
};
console.log(cat.jumps(),"this的指向");
因为对象不构成单独的作用域(在里面没有this指向),导致jumps箭头函数(想上找)定义时的作用域,因此输出结果为window
function fn(){
const cat={
lives:9,
jumps:()=>{
return this;
},
};
console.log(cat.jumps(),"this的指向");
}
document.onclick=fn;
给cat对象套上一个函数时执行document.οnclick=fn,由于这个函数中的this为document,因此,jumps中的this在定义时所指向的为document
类基本语法
JavaScript语言的传统方法是通过构造函数,定义并生成新对象,例如:
//传统对象原型写法
function Point(x,y){
this.x=x;
this.y=y;
console.log(x,y);
}
Point.prototype.toString=function(){
return '('+this.x+','+this.y+')';
};
var p=new Point(1,2);
这种写法与传统的面向对象语言差异很大,很容易让新学习这门语言的程序员感到困惑。ES6提供了更接近传统语言的写法,引入了Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。基本上ES6的class的绝大部分功能在ES5中也可以做到,新的class写法只是让对象圆形的写法只是让对象原型的写法更加清晰、更像面向对象的编程的语法而已。上面的代码用ES6的“类”改写,代码如下:
class Point{
constructor(x,y){
this.x=x;
this.y=y;
console.log(x,y);
}
toString(){
return '('+this.x+','+this.y+')';
}
}
上面代码定义了一个“类”,可以看到里面有个constructor()方法,这就是构造方法,而this关键字则代表实例对象。也就是说,ES的构造函数Point对应ES的Point类的构造方法。Point类除了构造方法,还定义了toString()方法。constructor()方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须由constructor()方法,如果没有显示定义,会默认添加一个空constructor()方法。
类本质上就是一个函数,类自身指向的就是构造函数,类完全可以看作构造函数的另一种写法。
console.log(Person===Person.prototype.constructor);
实际上类的所有方法都定义在类的prototype属性上,可以通过prototype属性对类添加方法。
Person.prototype.addFn=function(){
return "我是通过prototype新增加的方法,名字叫addFn";
}
var point=new Point(2,3);
console.log(point.addFn());//我是通过prototype新增加的方法,名字叫addFn
还可以通过Object.assign()方法来为对象动态增加方法 :
Object.assign(Point.prototype,{
getX: function(){
return this.x;
},
getY: function(){
return this.y;
},
});
var point=new Point(2,3);
console.log(point.getX());//输出2
console.log(point.getY());//输出3
类创建好后,生成类的实例对象时要使用new命令。如果忘记加上new,像ES5函数那样调用class将会报错。
let point=new Point(2,3);
class Point{
constructor(x,y){
this.x=x;
this.y=y;
console.log(x,y);
}
toString(){
return "("+this.x+","+this.y+")";
}
}
var point=new Point(2,3);
console.log(point.toString());//(2,3)
console.log(point.hasOwnProperty("x"));//true
console.log(point.hasOwnProperty("y"));//true
//x,y都是构造函数本身的属性,因此返回true
console.log(point.hasOwnProperty("toString"));//false
//toString是原型上的属性,因此返回false
console.log(point.__proto__.hasOwnProperty("toString"));//true
其中,hasOwnProperty()方法返回一个布尔值,判断对象是否包含特定的自身(非继承)属性,效果图如下:
class的继承
可以使用extends关键字实现继承。子类继承父类,相当于继承了家产一样,继承了父类所有属性和方法。并且,每次extends后面只能跟一个父类,代码格式如下:
class 父类名{
//父类的属性和方法
}
//子类继承父类
class 子类名 extends 父类名{
//子类的属性和方法
}
例如:
class Father{
constructor(){
this.name="father";
this.money=60000;
}
say(){
console.log(`${this.name} say hello`);
}
myMoney(){
console.log(`${this.name} has ${this.money}元`);
}
}
class Son extends Father{
constructor(){
super();//注意这个一定要写
this.name="son";
this.addmoney=40000;
}
addMonth(){
console.log(`${this.name}共有${this.addmoney}+${this.money}=${this.money+this.addmoney}元`);
}
}
var son1=new Son();
son1.say();//son say hello
son1.addMonth();
运行结果:
继承用extends。继承后,需要用super()来接收父类的constructor构造函数,否则会报错。当new一个子类时,先把参数传入子类构造函数,在通过super()将父类的构造函数引入,就可以调用父类。
class静态方法
类(class)通过static关键字定义静态方法,所有在类中定义的方法都会被实例继承,但定义成静态方法后,就表示该方法不会被实例继承,而是直接通过类来调用。
语法格式:
class 类名{
static 方法名(){
//属性
}
}
在类中也可以静态属性,语法格式如下:
class 类名{
static 属性;
}
解构赋值
1.数组的解构赋值
在ES6中,添加了一种名为解构复制特性,能从数组和对象中提取内容和赋值给变量。结构赋值,也称模式匹配,即赋值的左右两边模式相同,则等号右边的值赋给左边的变量。解构赋值适用于let、var、const声明变量/常量。
//在解构赋值前,声明多个变量
let a=1;
let b=2;
let c=3;
//解构赋值
Let[a,b,c]=[1,2,3];
这种写法属于模式匹配,即只要等号(=)两边的模式相同,等号左边的变量就会被赋予对应的值。如果解构失败,那么变量的值会被设置为默认值undefined。
2.对象的解构赋值
对象的解构赋值将对象属性的值赋给多个变量。在ES6之前,可以这样把对象的属性赋值给多个变量,代码如下:
var obJect={name:"john",age:23};
var name=object.name;
var age=object.age;
console.log(name,age);//john 23
在ES6中,可以使用对象解构表达式,在单行里给多个变量赋值,代码如下:
let object={name:"john",age:23};
let name age;
({name,age}=object);
console.log(name,age);//john 23
对昂解构赋值时的左侧为解构赋值表达式,右侧为对应要分配赋值的对象。当先定义变量,后使用解构语句时,两边千万不要遗漏左右括号(),否则会报错。
在解构对象针对未分配值的变量,可以为变量提供默认值,代码如下:
let {a,b,c=3}={a:"1",b:"2"};
console.log(c);//输出3
数组的扩展
1.扩展运算符
扩展运算符(spread)是三个点(...),像rest参数的逆运算,将一个数组转为用逗号分隔的参数序列。
var arr=[33,1,34,35,8,9]
console.log('arr:',...arr);
数组是复合的数据类型,直接复制数组会指向底层数据解构的指针,而不是数组。使用扩展运算符来复制数组更方便。
//ES5的解决办法
const a1=[1,2];
const a2=a1.concat();
a2[0]=2;
console.log('a1:',a1);//[1,2]
//ES6的写法
const a1=[1,2];
const a2=[...a1]
a2[0]=2;
console.log('a1:',a1);//[1,2]
console.log('a2:',a2);//[2,2]
合并数组:
var a1=[1,2];
var a2=['aa','bb','cc'];
var a3=['张三','李四'];
//ES5操作方法
const new1=a1.concat(a2).concat(a3);
//ES6操作方法
const new2=[...a1,...a2,...a3];
console.log('new1:',new1);
console.log('new2:',new2);
使用扩展运算符将字符串转为数组:
var str='hello';
var arr=[...str];
console.log('arr:',arr);//["h","e","l","l","o"];
2.新增的数组方法
(1)Array.from()方法。Array.from()方法用于将两类对象转为真正的数组,类似数组的对象(arrayLike object)和可遍历(iterable)的对象(包括ES6新增的数据结构Set和Map)。
let arrayLike={
'0':'a',
'1':'b',
'2':'c',
length:3
};
//ES5的写法
var arr1=[].style.call(arrayLike);//['a','b','c']
//ES6的写法
var arr2=Array.from(arrayLike);//['a','b','c']
(2)Array.of()方法。Array.of()方法用于将一组值转换为数组。这个方法的主要目的是弥补数组构造函数Array()的不足。因为参数个数的不同,会导致Array()的行为有差异。
Array.of(3,11,8) //[3,11,8]
Array.of(3) //[3]
Array.of(3).lenght //1
Array() //[]
Array(3) //[,,,]
Array(3,11,8) //[3,11,8]
(3)find()与findindex()方法。数组实例的find()方法用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为true的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined。
[1,5,10,15].find(function(value,index,arr){
return value>9;
})//10
find()方法的回调函数可以接受三个参数,以此为当前的值、当前的位置和原数组。数组的实例的findIndex()方法的用法与find()方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1.
[1,5,10,15].findIndex(function(value,index,arr){
return value>9;
})//2
这两个方法都可以发现NaN,弥补了数组的indexOf()方法的不足。
(4)find()方法。find()方法使用给定值,填充一个数组。
['a','b','c'].fill(7)//[7,7,7]
new Array(3).fill(7)//[7,7,7]
fill()方法用于空数组的初始化非常方便。数组中已有的元素会被抹去,方法还可以接受第2个和第3个参数,用于指定填充的起始位置和结束位置。
['a','b','c'].fill(7,1,2) //['a',7,'c']
(5)遍历。ES6提供entries()、keys()、values()等3个新方法,用于遍历数组。他们都返回一个遍历器对象,可以用for...of循环进行遍历,唯一的区别是keys()是对键名的遍历,values()是对键值的遍历,entries()是对键值对的遍历。
例如:
let arr=['小红','小明','小芳']
for(let index of arr.keys()){
console.log('index:',index);
//*index:0
//*index:1
//*index:2
}
for(let value of arr.values()){
console.log('value:'values);
//*value:小红
//*value:小明
//*value:小芳
}
for(let [index,value] of arr.entries()){
console.log(index+":"+value);
//*0:小红
//*1:小明
//*2:小芳
}
(6)includes()方法。includes()方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的includes()方法类似。
[1,2,3].include(3);//true
[1,2,3].include(3,3);//true
[1,2,3].include(3,-1);//true
该方法的第2个参数表示搜索的起始位置,默认为0.如果第2个参数为负数,则表示倒数的位置,如果这时它大于数组的长度(如第2个参数为-4,但数组长度为3),则会重置为从0开始。
(7)flat()方法。flat()方法用于将嵌套的数组“拉平”,变成一堆的数组。该方法返回一个新数组,对原数据没有影响。可以将flat()方法的参数写成一个整数,表示想要拉平的层数,默认为1.
[1,2,[3,[4,5]]].flat()//[1,2,3,[4,5]]
Set/Map数据结构
在ES6中添加了两种新的数据结构及其对应的方法,这两种结构在编程中可以提高开发效率。
1.ES6中Set
ES6中提供了Set数据容器,这是一个能够存储无重复值的有序列表,通过new Set()可以创建Set,然后通过add()方法能够向Set中添加数据项。
方法名或属性 | 描述 |
size属性 | 返回Set实例的成员总数 |
add(value) | 添加值,返回Set结构本身 |
delete(value) | 删除值,返回布尔值,表示删除是否成功 |
has(value) | 返回布尔值,表示该值是否为Set的成员 |
clear() | 清除所有成员,没有返回值 |
keys()/values() | 返回键名/键值的遍历器(无差别) |
entries() | 返回键值对应的遍历器 |
forEach() | 使用回调函数遍历每个成员 |
Set结构的遍历顺序与插入顺序相同,但是他没有键名只有键值,因此keys()和values()方法的行为一致,也可以使用for...of的方法直接遍历。
2.ES6中的Map
Map是一种键值对的对象,相比较对象来说,他的键可以是各种类型的,如果需要在一个地方使用键值对应的数据结构,Map比起Object会更合适。
方法 | 描述 |
size() | 返回Map结构的成员总数 |
set(key,value) | 设置key对应的值为value,返回整个结构,可以使用链式写法 |
get(key) | 读取key对应的值,找不到则返回undefined |
has(key) | 返回布尔值,表示键是否在Map对象中 |
delete(key) | 返回布尔值,表示键是否删除成功 |
clear() | 清除所有成员,没有返回值 |
keys() | 返回键名的遍历器 |
values() | 返回键值的遍历器 |
entries() | 返回所有成员的遍历器 |
forEach() | 遍历Map的所有成员 |