ES6新特性

179 阅读5分钟

笔记来源:拉勾教育 - 大前端就业集训营

文章内容:学习过程中的笔记、感悟、和经验

ES6新特性

ECMAScript(ES)概述

  • ES只提供最基本的语法
  • js语言本身指的就是ES
  • 2015年开始每年都会发布新版本,2020年发布11
  • 2015之后按照年份发布,例如ES2015,也叫ES6

es6特指es2015,泛指所有2015年和以后的版本

www.ecma-international.org/ecma-262/6.…

新特性

  • 解决原有语法上的一些问题和缺陷
  • 对原有语法进行增强
  • 全新的对象、方法、功能
  • 全新的数据类型和数据结构

准备工作

任何支持ES205的环境都可以

可以借助vscode中的chrome调试

  • Debugger for Chrome
  • Live Server
  • Browser Preview

let与块级别作用域

let只能在块级作用域内部使用,非常适合写在for循环中创建变量

let不会进行变量声明提升

// 使用let创建块级作用域变量
// if (true) {
//   var x2 = 2;
//   let x1 = 1;
// }
// console.log(x2); //2
// console.log(x1); //报错,因为在if内部使用let创建的变量在外部是无法访问到的

// 常规使用var参与的循环,给多个目标添加事件
// var obj = [{}, {}, {}];
// for (var i = 0; i < obj.length; i++) {
//   obj[i].onclick = function () {
//     console.log(i);
//   }
// }
// obj[0].onclick(); //无论触发第几个成员的onclick事件,输出结果都是3
// obj[1].onclick(); //无论触发第几个成员的onclick事件,输出结果都是3
// obj[2].onclick(); //无论触发第几个成员的onclick事件,输出结果都是3

//使用let参与for循环
// var obj = [{}, {}, {}];
// for (let i = 0; i < obj.length; i++) {
//   obj[i].onclick = function () {
//     console.log(i);
//   }
// }
// // 使用let可以封住作用域,结果就正常了
// obj[0].onclick(); //0
// obj[1].onclick(); //1
// obj[2].onclick(); //2

// let不会进行变量声明提升
console.log(y);
let y = 3;  //报错,因为let不会产生变量声明提升,所以前面访问不到y

const--常量

  • 相当于在let的基础上增加了只读的特性,一旦变量声明之后无法更改
  • 在声明变量的时候必须给予初始值,因为后期不能修改了
  • const定义的变量只是固定了指针地址,指针内部的内容是可以更改的
// const创建变量

// const x = 'zs';
// x = 'ls'; //会报错,因为const创建的变量是无法直接更改的

//const创建的变量只要指针(指向内存中的位置)不变,内容是可以更改的
const obj = {};
// 在这里不更改指针位置,我们修改指针指向的内容
obj.name = 'zs';
console.log(obj); //{name: 'zs'}

变量创建建议

不用var,主用const,配合let(如果需要修改)

数组解构

适用于获取数组元素的场景,es6直接提供给我们数组解构方法

// 数组解构
const x = [100, 200, 300];


// 直接创建一个变量数组,接收x数组的值,按照顺序每一项接收相应位置的值
const [a1, b1, c1] = x;
console.log(a1, b1, c1); //100 200 300

// 假如我们创建的变量多于原数组或者少于原数组
// 多于原数组
const [a2, b2, c2, d2] = x;
// 少于原数组
const [, , c3] = x;
console.log(d2); //undefined  因为x没有第四项,所以多出来的项获取不到值,就是undefined
console.log(c3); //300 如果我们只需要获取一个元素,而原数组成员较多,我们可以以逗号把多于出来的空出来


// 默认值
const [a4, b4, c4, d4 = 400] = x;
console.log(d4); //400  我们给变量添加默认值,如果获取不到,他就会把默认值当作赋值 


// 创建数组(注意只能最后一个变量才能使用...)
const [a5, ...b5] = x;
console.log(b5); //200 300  使用...可以获取剩下的数组所有的值形成数组,赋值给后面的变量

const [a6] = x;
console.log(a6); //100  如果没有写占位逗号,会默认获取第一个值





// 实际应用:获取字符串固定位置
//创建字符串
const str = 'zs/ls/ww';
//解构str分割后的数组,把第三项赋值给ww
const [, , ww] = str.split('/');
console.log(ww); //ww 

对象解构

同数组解构一样,同样适用于需要获取对象内容的场景

// 对象解构
// 创建对象
const x = {
  name: 'zs',
  age: 19
};
// 解构创建变量
const {
  //直接书写需要获取对象成员属性名,会直接创建同名的变量
  name,
  // 可以设置默认值,x里面没有sex,所以可以设置一个默认值,如果获取不到会使用这个默认值
  sex = 'nan',
  // 如果我们不想创建和属性名同名的变量,可以使用:新名字的方法创建,创建之后会使用新的名字而不是属性名
  age: zsAge
} = x;
console.log(name, sex, zsAge); //zs nan 19



// 实际应用:封装console.log()方法
const {
  log
} = console;
// 测试
log('我成功了');

摸板字符串

使用反引号``把需要包裹的字符串包裹起来,模版字符串提供了很多功能

  • 支持换行
  • 支持插值表达式
// 模板字符串
const str = `我是一串\`,如果里面需要再写特殊符号需要转译一下
字符串,我可以换行`
console.log(str)


const name = `zs`
// 使用${}方法可以在字符串模版中进行插值,只要内部又返回值就可以把结果插入到字符串中,支持引入变量、运算、甚至支持完整的js标准代码
const str2 = `我叫${name},${1+1},${Math.random()}`
console.log(str2);

模版字符串标签函数

可以对模版字符串进行加工,之后变量获得的是标签函数返回的结果,如果函数没有返回,变量就会没有值

模版字符串参数:

  • 参数1:被插值分隔开的字符串组成的数组
  • 其他参数:按照插值出现的顺序的插值
// 模板字符串标签函数
const myName = 'sz',
  age = 19,
  sex = true;

// 创建一个函数,他可以处理一段数据,并返回相应数据
// 参数str:一个字符串数组
function go(str, x, y, z) {
  const sex = z ? 'nan' : 'nv';
  return str[0] + x + str[1] + y + str[2] + sex;
}

// 创建一个变量,并且使用模版字符串,使用go函数处理这段字符串
// 最后say得到的值是go函数返回的值
// 带入标签函数的参数分别是
// str:【‘hi my name is’,‘, my age is’,‘,my sex is’】
// x:'sz'
// y:19
// z:true
const say = go `hi my name is ${myName} , my age is ${age},my sex is ${sex}`
console.log(say);

字符串扩展方法

es6为字符串提供了一些新方法:

  • includes(‘内容):判断字符串是否含有内容
  • startsWith(’内容‘):判断字符串是否以这个内容开头
  • endWith(‘内容’):判断字符串是否以这个内容结尾

三个方法返回布尔值

// 字符串扩展方法
const str = 'hello , how air you ?'
// 判断是否以hello开头
console.log(str.startsWith('hello')) //true
// 判断是否以?结尾
console.log(str.endsWith('?')) //true
//判断是否含有how字符
console.log(str.includes('how')) //true

参数默认值

一般用于函数的形参,可以设置默认的值,如果没有传实参会使用默认值

// 参数默认值
// 可以在这六设置一个参数的默认值,再调用函数的时候如果没有给这个值传递实参,那么函数会使用这个值的默认值参与函数
// 注意:含有默认值的参数要往后放,否则会出现错误
function fn(x, y = 2) {
  const a = x;
  const b = y;
  console.log(a + b);
}
fn(1);  //这里只传入了1,而b使用了默认值2

剩余参数操作符...

当调用函数时的实参个数超过了形参个数,那么可以使用剩余操作符接受多出来的所有实参

// 剩余操作符

//以前接受参数方法:使用arguments获得全部的实参
function fn1() {
  console.log(arguments);
}
fn1(1, 2, 3, 4); //[1,2,3,4]


// 使用剩余操作符
// 使用...加形参的方式接收溢出形参个数的实参并组成一个数组
function fn2(n, ...shengyu) {
  console.log(shengyu);
}
fn2(1, 2, 3, 4); //[2,3,4]  n接收了1,剩下三个都被shengyu接收了

展开操作符...

操作于数组,可以吧数组按照顺序进行展开

// 展开操作符
const arr = [1, 2, 3, 4]

// 现在我们想把数组中的每一项打印出来
//最笨的方法
console.log(arr[0], arr[1], arr[2], arr[3])

// 使用aplly方法展开
console.log.apply('', arr)

// 使用展开操作符
console.log(...arr)

箭头函数=>

es6提供箭头函数极大的节省了书写代码量和结构

语法:const 变量= 参数=>{函数体}

// 普通函数
// const fn = function (a, b) {
//   return a + b
// }

//改写成箭头函数
// 箭头两侧分别是参数和函数体
// 可以省略function不写
// 如果只有一个参数可以省略括号不写
//如果函数题只有一行可以不写打括号和return
const fn = (a, b) => a + b
console.log(fn(1, 2))


// 创建一个完整的箭头函数
const fn2 = (a, b) => {
  const he = a + b;
  return he;
}

箭头函数内部this指向问题

箭头函数内部没有this的概念,所以无法调用this,所以可以使用外部this

// 箭头函数this
const obj = {
  name: 'zs',
  age: 19,
  // 使用常规函数表达式,可以正常输出name
  // sayHi: function () {
  //   console.log(this.name);
  // }
  // 使用箭头函数无法输出name,因为在箭头函数内部,没有this的存在
  // sayHi: () => {
  //   console.log(this.name);
  // }
  // 当我们在对象内部创建定时器的时候,因为定时器的this指向window,而window没有this,所以还是找不到,所以我们需要在外面手动获取this存到变量中再调用变量
  // sayHi: function () {
  //   const _this = this
  //   setTimeout(function () {
  //     console.log(_this.name);
  //   }, 1000)
  // }
  // 使用箭头函数可以解决,因为箭头函数本身没有this,所以需要可以使用外部的this,而这个方法的this指向对象本身,所以可以直接获取属性
  sayHi: function () {
    setTimeout(() => {
      console.log(this.name);
    }, 1000)
  }
}
obj.sayHi();

在工作中,当需要使用_this=this的情景,可以使用箭头函数解决

对象字面量的增强

ES5中对对象字面量定义对象的方式进行了增强

  • 当属性值为变量且变量名和属性名一致的时候,可以直接写属性名不写属性值
  • 对象方法可以省略:function
  • 可以使用计算属性名,即属性名可以是动态的
// 对象字面量的增强
const bar = 1;
const me = 'my';
const x = {
  // 如果属性名和要引用的变量名相同,可以只写一个
  bar,
  // 方法可以直接省略 :function,直接写方法,这个方法使用的是传统字面量方式创建,不是箭头函数,可以使用this
  say() {
    console.log(this)
  },
  // 可以使用计算属性名,使用【】包裹,里面可以是一个表达式,表达式的结果即为属性名,也可以直接引入变量名
  [1 + 2]: 3, //属性名为表达式
  [me]: 'wo' //属性名为变量的值
}
x.say()

对象扩展方法

Object.assign(复制、合并)

Object.assign方法可以将一个或者多个对象中的属性和方法复制合并到另一个对象中,已经拥有的属性会发生覆盖

语法:Object.assign(目标对象, 源对象,源对象,源对象....)

// 对象字方法增强(Objhect.assign - 复制合并)
const obj1 = {
  a: 1,
  b: 2
}
const obj2 = {
  a: 2,
  c: 3
}
const obj3 = {
  a: 4,
  d: 3,
  say() {
    console.log(1)
  }
}
// 将obj1、obj3里面的属性和方法复制到obj2中,同名的方法和属性会按照顺序进行覆盖,类似CSS层叠性
Object.assign(obj2, obj1, obj3)
console.log(obj2); //{a: 4, c: 3, b: 2, d: 3, say: ƒ}


// 应用:可以复制一个对象而且不更改其内容
const x = {
  a: 1,
  b: 2
}
//创建一个新变量,完全复制x的属性和方法
const y = Object.assign({}, x)
// 修改y的属性值源对象不会发生改变,说明两个对象指针不同,指向的是两个不同的内存地址
y.b = 3;
console.log(y === x, y, x);


// 实际应用:在构造函数内部直接使用用户传入的对象数据
function Block(opts) {
  // 以前的写法,每一个属性都要手动写一遍
  // this.width = opts.width
  // this.height = opts.height
  // 使用es6增强方法,直接获取传入参数对象的属性和方法
  Object.assign(this, opts);
}
const block1 = new Block({
  width: 100,
  height: 100,
  x: 0,
  y: 0
})
console.log(block1) // {width: 100, height: 100, x: 0, y: 0}

Object.is() - 相等判断

Object.is()可以传入两个参数,可以进行是否相等的判断(但是日常工作中一般不会使用)

语法:Object.is(x,y)

// Object.is() - 相等判断
const {
  log
} = console
// 常规方法
log(+0 == -0) //true
// 使用Object.is方法会有一些不同
log(Object.is(+0, -0)) //flase
log(NaN === NaN) //flase
log(Object.is(NaN, NaN)) //true

// 虽然Object.is提供了判断方法,但是日常工作中我本还是建议使用==/===的方法进行判断

class类(构造函数)

在Es2015中吗,可以使用class关键字创建构造函数(类)

语法:class 构造函数名 { constructor (参数){} 方法}

// class关键字创建类

// 普通字面量创建构造函数
function One(x, y) {
  this.x = x
  this.y = y
}
One.prototype.sayX = function () {
  console.log(this.x)
}
const one1 = new One(1, 2)
one1.sayX()

// 使用class关键字,结构更清晰
class Tow {
  // 属性直接写在constructor里面
  constructor(x, y) {
    this.x = x
    this.y = y
  }
  // 原型方法直接写在后面
  sayX() {
    console.log(this.x)
  }
}
const tow1 = new Tow(1, 2)
tow1.sayX()

class静态方法(static)

可以在class内部使用static关键字创建静态方法,只能构造函数自己调用,实例对象无法调用

语法:static 方法名(参数){ 函数体 }

// class使用static创建静态方法
class Tow {
  constructor(x, y) {
    this.x = x
    this.y = y
  }
  sayX() {
    console.log(this.x)
  }
  // 使用static关键字创建静态方法
  static certer(x, y) {
    // 把创建实例对象的代码放进去,最终返回实例对象
    return new Tow(x, y)
  }
}
// 直接调用构造函数方法,直接创建实例对象
const tow1 = Tow.certer(1, 2)
tow1.sayX()

注意:静态方法中的this指向此构造函数,因为函数也是对象

class继承(extends和super)

在class继承中,使用extends继承父类,如果想要使用父类的构造函数哦或者方法使用super对象即可

//语法:
class 子类 extends 父类{
      constructor(参数){
        super(父类参数)
        this.子类属性 = 属性值
        ....
      }
      子类方法(){
        super.父类方法()
        ....
      }
    }
// 变量接收console.log方法
const {
  log
} = console
// class继承
// 创建父类
class Person {
  constructor(name, age) {
    this.name = name
    this.age = age
  }
  sayName() {
    log(`我的名字是${this.name}`)
  }
}
// Student继承Person,extends关键字
class Student extends Person {
  constructor(name, age, number) {
    // 使用super()直接调用父类的构造函数实现继承父类构造函数
    super(name, age)
    // 子类自己的属性
    this.number = number
  }
  // 子类的方法
  sayMy() {
    //使用super直接调用父类的方法
    super.sayName()
    log(`我的学号是${this.number}`)
  }
  // 子类书写一个静态方法用于构建实例
  static newS(name, age, number) {
    return new Student(name, age, number)
  }
}
// 创建实例
const s1 = Student.newS('zs', 19, 101)
// 输出一下实例
log(s1)
// 测试子类是否调用了父类的方法
s1.sayMy() //我的名字是zs 我的学号是101

Set数据类型

set数据类型和数组非常像,介于数组和对象之间,特点就是内部所有项都不允许重复

set常用方法和属性

方法和属性说明
size返回长度,类似于length
add()添加数据
delete()删除数据
clear()清空全部数据
has()判断某个数据是否在set中
// 变量接收console.log方法
const {
  log
} = console
const set = new Set();
// 使用add方法向内部添加数据,如果发现重复疏浚会被忽略
// 重复添加了一个1
set.add(1).add(2).add(3).add(4).add(5).add(1)
log(`添加后看一下`, set)
log(`看一下数据长度`, set.size)
//删除数据
set.delete(2)
log(`删除了2`, set)
// 清空全部数据
// set.clear()
// log(`清空了全部数据`, set)
// 判断数据是否存在于set中
log(set.has(3))



// 遍历set
// 使用forEach方法,使用箭头函数
set.forEach(i => log(i))
// 使用for of遍历啊
for (i of set) {
  log(i)
}


//应用:数组去重
const arr = [1, 2, 4, 5, 6, 3, 6, 6]
// const arr2 = Array.from(new Set(arr))
const arr2 = [...new Set(arr)]
log(arr2)

map数据类型

map数据类型可以看做是Object的升级版,map可以把人一数据类型的数据当作属性名,而对象不可以

map常用方法

方法说明
set(属性名,属性值)添加键值对
get(属性名)查看属性名的属性值
has()检查是否存在某个属性
delete()删除某个键值对
clear()清空对象
// 变量接收console.log方法
const {
  log
} = console
// map数据类型

// 普通对象类型数据
const obj = {
  true: 1,
  123: 2
}
// 使用对象的keys静态方法可以打印对象中所有的key的值
// 发现所有key都是字符串格式,多亿说明对象中无论传入什么类型的key值都会被强制转换成字符串
log(Object.keys(obj)) //['123', 'true']



// 使用map数据类型
const map = new Map()
const a = {
  a: 1
}
// 添加1个键值对
map.set(a, 1)
log(map) //{{a: 1} => 1} 可以发现 map里面可以使用其他类型作为key值
// 查看某个键值对
log(map.get(a));
// 判断map里面是否存在a键值对
log(map.has(a))
// 删除某个键值对
// map.delete(a)
// log(map)
// 清空全部键值对
map.clear()
log(map)

Symbol基础类型数据

symbol是一个基础类型数据,和Number、String一样

Symbol类型数据的特点就是所有创建的数据都是独一无二的,两个Symbol数据一定是不相等的

最主要的作用就是为对象添加独一无二的属性标识符(但我觉得没啥用)

// 变量接收console.log方法
const {
  log
} = console


//symbol类型数据(基础类型)
// 任意两个smybol类型数据都是不相等的
log(Symbol() === Symbol()) //flase


// 应用:对象两个symbol属性名不会发生冲突
const obj = {
  [Symbol()]: 1,
  [Symbol()]: 2,
}
log(obj) //{Symbol(): 1, Symbol(): 2} 可以发现,有两个属性,而且同名,说明在计算机中,两个Symbol()被认为是不一样的
log(obj.Symbol()  //找不到。因为任意两个Symbol()都不一样,没办法调用
    
// 支持添加一个参数作为标识符
const a = Symbol('off')

Symbol补充

  • 虽然任意两个Symbol创建的实例都不相等,但是使用Symbol.for('标签名')创建的两个Symbol可以想等,因为他们两是相同得指向
  • Symbol创建时候的标签名会被强制转换成字符串
  • 当我们把对象转换成字符串的时候,会被默认转成[Object Object],但是如果该对象有[Symbol.toStringTag]:值则会被转换成[Object 值]
  • 在对象内部,Symbol创建的属性在大多数情况下会被忽略,除非使用Object.getOwnPropertySymbols(对象)才能查看
// 变量接收console.log方法
const {
  log
} = console

// Symbol补充

// 1、使用Symbol.for方法创建可以相等
log(Symbol.for() === Symbol.for())

// 2、symbol创建的时候内部会被强制转换成字符串格式
log(Symbol.for(true) === Symbol.for('true'))

// 3、Symbol格式可以更改对象转字符串时候的标签名
const a = {
  a: 1
}
const b = {
  //使用toStringTag修改转换时候的名字
  [Symbol.toStringTag]: 'newName',
  b: 1
}
log(a.toString(), b.toString())

// 4、一般方法无法访问Symbol属性
const c = {
  [Symbol()]: 1,
  b: 1
}
// 使用for in循环只能找到除了symbol之外的属性
for (let k in c) {
  log(k)
}
// 只能通过这种方法访问
log(Object.getOwnPropertySymbols(c))

For-of循环遍历方法

For-in循环是ES2015提供的一种遍历方法,可以变遍历大多数的对象、数组、map、set等等

特点:

  • 可以遍历大多数结构
  • 可以在其中添加breck跳出循环
  • 可以替代for循环

注意:目前for-of遍历普通对象还有些问题,暂时不要用,后期会学到解决方法

// 变量接收console.log方法
const {
  log
} = console

// for of循环遍历方法
// 遍历数组
const arr = [1, 2, 3, 4]
for (const i of arr) {
  log(i)
}

// 遍历set结构和数组相同,这里不做演示

// 遍历map
const map = new Map()
map.set('a', 1)
map.set('b', 2)
for (const [key, value] of map) {
  log(key, value)
}

// 遍历普通对象会出现问题,会报错,所以暂时不要使用for of遍历对象明显使用for in
const obj = {
  a: 1,
  b: 2
}
for (const item of obj) {
  log(item)
}

ES2015其他内容

  • 可迭代接口
  • 迭代器模式
  • 生成器
  • Proxy代理对象
  • Reflect统一的对象操作API
  • Promise异步解决方案
  • ESModules语言层面的模块化标准

会在后面学习中逐渐学习到

ES2016新增

新增方法:includes() - 判断数组中是否存在某个数据

新增指数运算符:x**y,可以计算x的y次方

// 变量接收console.log方法
const {
  log
} = console

//includes判断是否包含存在方法
const arr = [1, 2, 3, 4, NaN, true]
// 普通arr的indexOf无法检测NaN的存在,只能返回-1
log(arr.indexOf(true)) //5
log(arr.indexOf(NaN)) //-1
// 使用includes可以判断一个数据是否存在于数组中,返回布尔值
log(arr.includes(NaN)) //true


//指数运算符**
log(2 ** 10)