ES6语法小记
简略记录工作中常用的es6语法及其相关知识
var let const
- 变量提升
- var声明的变量会变量提示,在定义前就可以使用,但是值为undefined
- let与const声明的变量只有声明之后才能使用,否则会报错。
//var声明之前使用
console.log(name);//undefined
var name = "姓名"
//let声明之前使用
console.log(age);//Uncaught ReferenceError: age is not defined
let age = 18
//const同let一样
- 块级作用域。
- 在es6之前,作用域只有全局作用域与函数作用域,es6新增块级作用域,块级作用域用{}包裹
//var声明的变量不会生成块级作用域
if(true){
var name = "姓名"
}
console.log(name);//姓名
//let与const声明的变量会形成块级作用域,age只能在if里面使用
if(true){
let age = 18
}
console.log(age);//ReferenceError: age is not defined
- let const的块级作用域的应用场景。
//双重for循环计数器使用相同的变量名,会出问题
for(var i = 0;i<3;i++){
for(var i = 0;i<3;i++){
console.log(i)
}
}
//只打印一次打印 0 1 2,这是因为var声明的变量在全局作用域下,当第一次内部循环结束之后i=3,不满足外部循环的i<3条件,既不会继续进行循环。
for(let i = 0;i<3;i++){
for(let i = 0;i<3;i++){
console.log(i)
}
}
//打印3次 0 1 2 因为let声明的变量具有块级作用域,外部与内部循环定义的变量i不再统一作用域下,不会相互影响。虽然let可以解决上面的同一变量名的问题,但是还是不建议使用同名变量,不容易调试。
//另外for循环具有两层作用域,()里面是一个单独的作用域。{}里面是一个单独的作用域
- const与let的区别。
- const声明的变量是一个常量,声明之后不能改变,这里的不能改变是值对应的内存地址不能再改变,也就是不能重新赋值,如果是一个对象的话,可以操作对象属性,但是不能重新赋值为一个新的对象。
- 优先使用const,确定要改变的变量使用let,尽量不用var声明变量。
数组解构
平常获取数组元素需要通过下表获取再赋值给某一变量,使用数组解构赋值可以更加方便
//之前获取数组元素
const arr = ["张三",18,true];
const name =arr[0];
const age = arr[1];
const sex = arr[2];
//使用解构赋值
const arr = ["张三",18,true];
const [name,age,sex] = arr;
//数组的解构赋值需要注意顺序要一一对应,如果只想取特定位置的元素,则前面要留空,用,分割,比如获取最后一个元素
const [,,sex] = arr;
//数组解构也可以使用默认值,即数组中该位置没有数据,则会使用默认值。
const[,,,height = 180] = arr;
console.log(height);//180
对象解构
通过解构赋值快速得到对象的值。
const obj = {
name:"张三",
age:18
}
//变量名和对象属性名要一致
const {name,age} = obj;
console.log(name,age);//张三 18
//如果出现属性名和之前已有的变量名重复的情况,可以在解构的时候取一个别名。
const name = "李四";
const obj = {
name:"张三",
age:18
}
//:前面的是对象里面的属性名,:后面的是要赋值给的变量名。
const {name:objName,age} = obj;
console.log(objName,age);//张三 18
//对象解构赋值同样可以使用默认值
const obj = {
name:"张三",
age:18
}
//:前面的是对象里面的属性名,:后面的是要赋值给的变量名。
const {name:objName,age,sex='男'} = obj;
console.log(objName,age,sex);//张三 18 男
模板字符串
定义字符串一半使用单引号'或者是双引号"现在可以使用反单引号`。反单引号包括的字符串为模板字符串。模板字符串可以使用${}的方式获取变量值。
const name = "张三"
const str = `hi,${name}`
console.log(str);//hi,张三
字符串扩展方法。
- includes判断字符串中是否包含某个字串,返回true或者false
- startsWith判断字符串是否以某个字串开头,返回true或者false
- endsWith判断字符串是否以某个字串结尾,返回true或者false
const str = "hello world"
console.log(str.includes("llo"));//true
console.log(str.startsWith("hello"));//true
console.log(str.endsWith("world"));//true
参数默认值
可以给不一定会传入的参数定义一个默认值,在没有传入的时候使用。
function sayHi(word,name){
word = word||"hello"
console.log(word,name)
};
//一般我们使用上面的||逻辑或在处理可能没有传入的情况,给他一个默认值。
//但是他是不严格的。没有传入的话,word应该是undefined,如果调用sayHi(false,"张三"),实际传入了false,
//他认为没有传入。正确的应该判断word是否严格等于undefined
function sayHi(word,name){
word = word === 'undefined' ? "hello" : word
console.log(word,name)
};
//形参默认值的写法,要把有默认值的写着最后,不然不会生效。
function sayHi(name,word=undefined){
console.log(word,name)
}
剩余参数
在函数种可以通过arguments获取参入函数的所有参数,他是一个类数组对象,如果要对他进行数组操作,则要线转换为数组,在进行操作。使用剩余参数的写法,可以直接把所有参数转换为数组格式,也可以指定接受剩下的所有参数。 注意:剩余参数只能放在参数末尾。
//写一个求所有传入的数字的和的函数,arguments对象获取参数,
function add(){
//转换arguments为数组。
arguments = Array.from(arguments);
let result = arguments.reduce((prev,current)=>prev+current,0)
return result
}
console.log(add(1,3,4));//8
//直接使用剩余参数写法
function add(...args){
//这里的args就是传入的所有参数的数组
let result = args.reduce((prev,current)=>prev+current,0)
return result
}
console.log(add(1,3,4));//8
//如果要获取除了第一个参数之外的所有参数的和,使用arguments的话
//要先将其转换为数组,然后再截取数组。之后在对截取之后的数组进行求和操作
//但是使用剩余参数,则只需要先定义一个形参接受第一个参数,把剩余参数以...收集起来进行求和即可
function add(a,...rest){
//这里的rest就是传入的除了第一个参数外的所有参数的数组
let result = rest.reduce((prev,current)=>prev+current,0)
return result
}
console.log(add(1,3,4));//7
展开数组
...运算符也可以用于展开数组,比如call的传参,可以把数组的每一项当做独立的参数传入
function sum(a,b){
console.log("姓名:"+this.name,"薪资:"+(a+b))
}
const salary = [1000,2000];
const person = {
name:"张三"
}
sum.call(person,...salary);//姓名:张三 薪资:3000
箭头函数
函数的简写方式,已经一下其他特性。
//普通的方式定义一个函数,
const add = function(a,b){
return a+b
}
//使用箭头函数的形式,箭头左边的是函数形参,右边是函数体
const add = (a,b)=>{
return a+b
}
//如果函数体只有一句代码,且返回值就是这一句代码的结果,那么可以省略{}与return
const add = (a,b)=> a+b
//如果函数的形参只有一个,那么可以省略()
const add = a => a*a;
//实际应用,筛选出数组种的偶数项
const arr = [1,2,3,4,5,6];
arr.filter(item=>item % 2)
箭头函数没有自己的this,他里面的this是定义时所在的上层函数中的this。如
const person = {
age:18,
sayAge:function(){
console.log(this.age)
}
}
person.sayAge();//18
//箭头函数改写
const person = {
age:18,
sayAge:()=>{
console.log(this,this.age)
}
}
//因为箭头函数中的this是他定义时确定的,值为上层函数的this,这里他的上层是全局。所以this为window对象。
person.sayAge();//window对象,undefined
可以用箭头函数来处理一些需要this的地方。如经常在代码中看到_this或that这种方式来存储this的情况
const person = {
age:18,
sayAge:function(){
setTimeout(function(){
console.log(this.age)
},1000)
}
}
person.sayAge();//undefined
//定时器里面的函数会被放到异步任务队列,执行时所在的环境为全局。此时的age为undefined
//可以在sayAge调用时,接受一些this,然后再定时器里面的函数使用(闭包)
const person = {
age:18,
sayAge:function(){
let that = this
setTimeout(function(){
console.log(that.age)
},1000)
}
}
person.sayAge();//18
//也可以使用箭头函数,让定时器中的函数的this直接指向外层的sayAge的this
const person = {
age:18,
sayAge:function(){
setTimeout(()=>{
console.log(this.age)
},1000)
}
}
person.sayAge();//18
对象字面量的增强
对象的简写方式
//再es5中,定义一个对象
let age = 18;
const person = {
name:"张三",
age:age,
sayName:function(){
console.log(this.name)
}
}
//如果需要给对象一个动态属性,则只有定义对象之后再根据下标来给对象添加
person[Math.random()] = "随机属性名";
//es6中的简写方法,属性名和变量名相同可简写,对象方法可省略:function,可以直接定义动态属性
let age = 18
const person = {
name:"张三",
age,
sayName(){
console.log(this.name)
},
[Math.random()]:"随机属性名"
}
Object.assign()可以讲源对象的属性全部复制给目标对象,最后返回目标对象。如果源对象存在与目标对象同名属性,则源对象属性会覆盖目标对象的同名属性。
const source1 = {
a:1,
b:1
}
const source2 = {
a:2,
c:2
}
const target = {
a:0,
b:0
}
const newObj = Object.assign(target,source1,source2)
console.log(target);//{a: 2, b: 1, c: 2}
console.log(newObj === target);//true
可以利用这个特性来对对象进行浅拷贝操作。前提是目标对象要是和拷贝对象无关的一个对象
const oldObj = {
name:"张三"
}
const newObj = Object.assign({},oldObj);
console.log(oldObj,newObj,oldObj === newObj);//{name: '张三'} {name: '张三'} false
//虽然看起来两者一致,但是他们不是同一个对象,不会相互影响。
Object.is用于判断数据是否相等。 平时判断数据是否相等用==或者===等号。==等号比较时会自动进行类型转换之后进行比较。===不会类型转换只有当数据和类型一致才会返回true,但是+0与-0则被认为是相等的。NaN不等于NaN。Object.is()可以来处理这两个特殊情况。
console.log('1' == 1);//true
console.log('1' === 1);//false
console.log(+0 === -0);//true
console.log(NaN === NaN);//false
console.log(Object.is(+0,-0));//false
console.log(Object.is(NaN,NaN));//true
Proxy
proxy可以对对象属性的读写设置代理,vue3的数据双向绑定就用到了proxy,vue2中用的是Object.defineProperty()。
//Proxy接受两个参数,第一个参数是要代理的目标对象,第二个参数是代理处理对象。
const person = {
name:"张三",
age:18
}
const proxyPerson = new Proxy(person,{
//get方法用于获取对象属性时的处理逻辑,接受两个参数,代理目标对象与访问的属性
//除了返回属性值之外,可以进行一些其他的逻辑处理。
get(target,property){
//看目标对象是否存在该属性,存在则返回该目标属性,不存在则返回一个默认值。
return property in target ? target[property]:'defaultValue'
},
//set方法为对象属性设置一个属性值,接受三个参数,分别是目标对象,对象属性,已经接受到的属性值。
set(target,property,value){
//可以做一些设置值额外的操作,比如限制类型必须为数字。
if(!Number.isInteger(value)){
throw Error(`${value} in not a Number`)
}
else{
target[property] = value
}
}
})
console.log(proxyPerson.name);//张三
console.log(proxyPerson.other);//defaultValue
//proxyPerson.height = "字符串";//Uncaught Error: 字符串 in not a Number
proxyPerson.height = 180;
console.log(proxyPerson.height);//180
proxy比defineProperty更加强大,defineProperty只能监听对象的读写操作,proxy可以监听更多对象属性操作,如delete in。proxy也可以直接代理数组对象。不需要重写数组方法。
const arr = [];
const proxyArr = new Proxy(arr,{
set(target,property,value){
console.log("监听到了数组变化")
target[property] = value;
//表示编辑完成
return true
}
})
proxyArr.push(0)
Reflect
Reflect是一个静态类,封装了一系列底层的对对象的操作Reflect内部的方法就是proxy内部方法的默认实现
//没有定义get方法相当于下面的代码
const proxyObj = new Proxy(obj,{
get(target,property){
return Reflect.get(target,property)
}
})
//也就是说,我们在使用代理对象时,应该在上面写自己添加的逻辑,最后调用相应的Reflect方法。
作用:提供了一套统一的操作对象的Api
const obj = {
name:"张三",
age:18
}
//对对象的一些操作
console.log(name in obj)
delete obj.name;
Object.keys(obj);
//现在可以统一使用Reflect的内置方法
Reflect.has(obj,'name');
Reflect.deleteProperty(obj,'name')
Reflect.ownKeys(obj)
Promise
异步编程的一种方案。见Promise从入门开始手写
class
es6之前,使用构造函数加原型的方式实现继承关系,es6新增class,更加方便的实现继承关系。
//ES6之前,定义一个Person构造函数。
function Person(name){
this.name = name;
}
//给实例对象添加方法。可以通过给Person函数的原型对象添加公用方法
Person.prototype.say = function(){
console.log(`My name is ${this.name}`)
}
const person = new Person("张三");
person.say()
//使用class定义一个构造函数
class Person{
//在构造器里面初始化实例对象
constructor(name){
this.name = name
}
//直接添加实例方法
say(){
console.log(`My name is ${this.name}`)
}
}
//同样使用new关键字生成一个实例对象
const person = new Person("张三")
person.say();
class有静态方法,可以直接通过类名访问,使用static关键字标识。
class Person{
constructor(name){
this.name = name
}
say(){
console.log(`My name is ${this.name}`)
}
//定义一个静态方法
static create(name){
return new Person(name)
}
}
//直接通过类名访问静态方法
const person = Person.create("张三")
person.say();
extends关键字实现继承
class Person{
constructor(name){
this.name = name
}
say(){
console.log(`My name is ${this.name}`)
}
static create(name){
return new Person(name)
}
}
//定义一个student类继承person类
class Student extends Person{
constructor(name,number){
//必须在子类中利用super调用父类的构造方法
super(name);
//子类自己的初始化逻辑
this.number = number;
}
sayNumber(){
//可以利用super来调用父类的方法
super.say();
console.log(`My number is ${this.number}`)
}
}
const student = new Student("张三",1233);
student.sayNumber()
//父类的静态方法依然可以通过子类的类名访问
console.log(Student.create("xxxx"))
set
set是一种没有重复元素的数据的集合
//通过new关键字创建set集合
const set = new Set()
//通过add方法为集合添加元素,返回的是集合本身,所以可以链式调用,多次添加
set.add(1).add(2).add(2).add(3);
console.log(set);//Set(3) {1, 2, 3}
//通过has方法判断集合中是否包含某元素,返回true或false
console.log(set.has(100));//false
//通过size属性获取集合元素个数,类似数组的length
console.log(set.size);//3
//通过delete方法删除集合中某一元素
set.delete(2);
console.log(set);//Set(2) {1, 3}
//通过clear方法情况集合
set.clear()
console.log(set);//Set(0) {size: 0}
//可以使用forEach for of来遍历set集合
set.forEach(i=>console.log(i))
for(let i of set){
console.log(i)
}
//通常可以同set结构来为数组去重
let arr = [1,2,3,3];
arr = [...new Set(arr)];
console.log(arr);//(3) [1, 2, 3]
map
map是与对象类似的键值对的集合,不同的是对象的键只能是字符串,但是map的键可以使任意类型的
let person = {}
//用数字作为键
person[123] = 123
//用boolean值作为键
person[true] = true
//用对象作为键
person[{name:"zs"}] = "张三"
//获取person对象的所有健名,得到的都是字符串
console.log(Object.keys(person));//(3) ['123', 'true', '[object Object]']
//用new关键字创建一个map
let map = new Map()
//通过set方法为map设置键值对
map.set(true,true);
let person = {name:"zc"}
map.set(person,"zc")
console.log(map);//Map(2) {true => true, {…} => 'zc'}
//通过has方法确定是否含有某个键
console.log(map.has(true));//true
//通过delete方法删除某个键
map.delete(true)
console.log(map);//Map(1) {{…} => 'zc'}
//通过clear方法清空map
map.clear()
console.log(map);//Map(0) {size: 0}
Symbol
Symbol是一种基本数据类型,用于创建一个独一无二的值,主要用于放在对象属性重名
//加入有两个文件给同一对象取了相同的属性名,那么后面的会覆盖前面的
let obj = {}
//a.js___________________
obj.name = "张三"
//b.js__________________
obj.name = "李四"
console.log(obj);//{name: '李四'}
//可以通过约定取名方式规避这种问题,如
//a.js___________________
obj.a_name = "张三"
//b.js__________________
obj.b_name = "李四"
console.log(obj);//{name: '李四', a_name: '张三', b_name: '李四'}
//symbol的用发
//使用Symbol函数创建一个symbol
let symbol1 = Symbol()
console.log(symbol1);//Symbol()
//每一个symbol都是不一样的
console.log(Symbol()===Symbol());//
//可以给Symbol传一个标识参数,方便调试
console.log(Symbol('foo'),Symbol('bar'));//Symbol(foo) Symbol(bar)
//用Symbol作为对象的属性名
let name = Symbol()
let person = {
[name]:"张三",
say(){
console.log(this[name])
}
}
//通过这种方式来实现访问symbol属性,实现私有属性的访问。
person.say();//张三
//要创建两个相同的Symbol,可以使用Symbol.for
console.log(Symbol.for('foo') === Symbol.for('foo'));//true
for of
for of一种新的遍历方式,可以遍历任意可迭代对象。普通对象不是可迭代对象,不可以直接用for of变量,数组,类数组,set,map结构都可以直接使用for of 来进行遍历。
//遍历数组
let arr = [1,3,4];
for(const item of arr){
console.log(item);//1 3 4
}
//遍历set结构
let set = new Set(arr);
for(const item of set){
console.log(item)//1 3 4
}
//变量map结构
let map = new Map();
map.set("1",1);
map.set("2",2);
for(const item of map){
console.log(item);// ['1', 1] ['2', 2]
}
//map结构使用for of获得的是一个[key,vlaue]数组,可以使用解构来获取key与value
for(const [key,value] of map){
console.log(key,value);//1 1 2 2
}
可迭代接口
任何实现了可迭代接口的数据解构都可以使用for of来进行遍历。for of的本质就是调用了数据结构内部的Symbol.iterator方法。
//以set结构为例
let set = new Set([1,2,3]);
//直接调用set的Symbol.iterator方法。该方法返回一个迭代器,该迭代器中含有一个next方法
const iterator = set[Symbol.iterator]();
//调用迭代器的next方法会返回一个对象,该对象包括当前值以及是否遍历完成的标识。
console.log(iterator.next());//{value: 1, done: false}
console.log(iterator.next());//{value: 2, done: false}
console.log(iterator.next());//{value: 3, done: false}
console.log(iterator.next());//{value: undefined, done: true}
console.log(iterator.next());//{value: undefined, done: true}
是对象变得可迭代,就需要给对象添加一个可迭代接口,也就是给对象添加一个Symbol.iterator方法。
const obj = {
//为对象添加Symbol.iterator方法
[Symbol.iterator]:function(){
//该方法返回一个迭代器对象
return {
//迭代器对象中有一个next方法
next:function(){
//调用next方法会返回迭代结果对象
return {
//该对象包含当前迭代结果value,以及是否迭代完成的done
value:"测试数据",
done:true
}
}
}
}
}
for(const item of obj){
console.log("迭代")
}
//执行上述代码,发现没有任何效果,但是也不会报错,因为我们上面只是思路分析,把next方法的返回值写死了,第一次的done就是true,for of以为迭代完成,不会进入迭代。改写如下。
const obj = {
name:"张三",
age:18,
[Symbol.iterator]:function(){
//要迭代的是obj,首先调用Symbol.iterator方法时this的值就是obj
//假设我们的迭代器要实现的时获取obj的所有键值。
const values = Object.values(this);
//每一次迭代对应当前迭代次数的值,定义一下递增的下标
let index = 0;
return {
next:function(){
//next方法返回的结果
const result = {
//value值为当前迭代次数对应的键值。
value:values[index],
//当index>=values的长度时,说明迭代完成
done:index>=values.length
}
//没完成一次迭代index+1
index++
//最后返回迭代结果
return result
}
}
}
}
for(const item of obj){
console.log(item);//张三 18
}
生成器函数
生成器函数是一种异步任务嵌套过深的优化方案,一半和yield配合使用,可以控制代码的执行,调用生成器函数会生成一个生成器对象,调用生成器对象的next方法,函数体里面的内容才会开始执行,碰到yield之后就会暂停执行,且返回一个结构对象,与迭代器的next方法一样,该对象包含两个属性,value与done,value的值为yield后面的值,done则为true或者false。
//声明一个生成器函数,只需要在函数声明与函数名之间加一个*
function* gen(){
console.log("生成器函数执行了")
}
const g = gen();
console.log(g);//gen {<suspended>}
//只有调用了next方法,函数体才会开始执行
g.next();//生成器函数执行了 {value: undefined, done: true}
//__________________
function * gen(){
console.log(100);
yield 100;
console.log(200);
yield 200;
console.log(300);
yield 300;
}
const g = gen();
//每调用一次next方法,就会继续向下执行,直到碰到下一个yield命令,暂停执行,并返回yield后面的值。直到最后返回done为true
console.log(g.next());//100 {value: 100, done: false}
console.log(g.next());//200 {value: 200, done: false}
console.log(g.next());//300 {value: 300, done: false}
console.log(g.next());//{value: undefined, done: true}