一、ECMAScript的发展过程与简介
-
ES6是2015年的新标准,要搞清楚ES6是泛指还是特指。特指的话是ES2015,泛指的话是指ES2015之后的版本
1.1 ES6概述
-
解决原有语法上的一些问题或者缺陷(作用域问题)
-
对原有语法进行增强(解构、展开、模板字符串)
-
全新的对象、全新的方法、全新的功能(Promise、proxy、Object.asgn...)
-
全新的数据类型和数据结构(Symbol、Set、Map)
1.2 学习ES2015准备工作
-
准备一个支持es6的环境,如chrome最新版本或者nodejs
-
可以在VScode软件中使用Chrome调试
-
需要安装的插件
-
① Debugger for Chrome
-
② Live Server
-
③ Browser Preview
-
-
查看右下角port
-
点击左上角运行-启动调试-chrome
-
然后修改配置文件的端口为右下角port一致,在网页里面选择对应文件即可
-
二、ECMAScript2015 的新特性
2.1 let、const、块级作用域
-
举一个例子,常见的for循环循环变量污染问题
var arr = []
for (var i = 0; i < 5; i++) {
arr[i] = function () {
console.log(i)
}
}
arr[0]() // 5
arr[1]() // 5
-
因为在ES6之前只有函数作用域和全局作用域,所以循环定义的i会暴露到全局也就是window里面,arr存的函数输出自然就是循环完之后的i值了
(1)块级作用域
-
讲let与const之前不得不介绍块级作用域
-
如:if语句中使用let跟var变量是不一样的
-
可以把一个{}里面看成是块级作用域
-
继续用for循环来举例
-
const arr = []
for (let i = 0; i < 5; i++) {
arr[i] = function () {
console.log(i)
}
}
arr[0]() // 0
arr[1]() // 1
-
-
说明在函数里面是有两层作用域的,所以可以把for循环看为
-
{
let temp = 0,
end = 5
if (temp < end) {
let i = temp
arr[i] = function () {
console.log(i)
}
temp++
}
if (temp < end) {
let i = temp
arr[i] = function () {
console.log(i)
}
temp++
}
//...
}
arr[0]()
arr[1]()
-
(2)let与const
-
① 他们不会有变量提升
-
② 后面声明了但是前面引用会有暂时性死区
-
const声明的变量指向的地址不可变,复杂数据类型可以继续修改属性,相当于let的只读效果
-
最佳用法
-
不用var,朱永const,配合let
-
2.2 数组与对象的解构
(1)数组的解构
-
① 下面foo1写在赋值符左端的数组的第一个位置,代表把赋值符右边数组第一个下标的值传给foo1
-
② ...语法是代表把剩余的参数全部放进一个数组中,如果后面没有参数了会返回一个空数组
-
③ foo2中设置了默认值,即右边没有匹配的值才会使得foo2 = 1000
-
const arr = [100, 200, 300, 400]
const [foo1, foo2 = 1000, -
-
(2)对象的解构
-
使用方法跟数组解构类似,不同的是设置变量的命名问题
-
设置变量命名需要与原函数的key值相等,但是会产生命名冲突(这个名字原先被声明过了)
-
可以使用 : 符号获取这个符号左边的值然后声明 : 右边的变量,设置默认值如果有:则在右边用=来设置
-
const obj = {
name: 'zs'
}
const name = 'ls'
const { name: newName = 'ww' } = obj
console.log(newName) // 'zs' -
-
2.3 模板字符串、模板字符串标签函数
(1)模板字符串
-
相对于普通的字符串模板字符串使用的是``来定义
-
在这里面可以让字符串换行
-
原本的字符串插值要用几个字符串拼接在一起,而模板字符串可以用插值表达式
${}
,在这里面可以放变量,表达式,甚至调用函数 -
ex:
const name = 'foo'
const str = `hello, ${name}, ${1 + 1}`
// 'hello, foo, 2
(2)模板字符串标签函数
-
模板字符串标签函数可以看成是用``代替函数调用符(),并传入参数
-
ex:
const name = 'zs'
const gender = true
function foo (strings, name, sex) {
sex = sex ? 'man' : 'woman' // 可以在里面进行操作
console.log(strings) // ['hi, ', ', ', '']
console.log(name) // 'zs'
console.log(sex) // ’man‘
}
foo`hi, ${name}, ${gender}`
-
上面的例子表示模板字符串会被${}分割并组成一个数组,如果${}在开头或者结尾,则默认会多出一个空字符串 ‘’
-
然后第二个参数开始表示插值表达式传入的值${},设置形参的时候不用名字相同
2.4 字符串扩展方法
-
includes():表示字符串是否包含某字符串
-
startsWith():表示字符串开头是否是某字符串
-
endWith():表示字符串结尾是否是某字符串
2.5 参数默认值
-
我们平时为函数形参赋予默认值的时候是使用
value = value || true
其实是不严谨,比如如果传入一个false也会进行默认值赋值操作 -
正确的做法是判断是否严格等于undefined
value = value === undefined ? true : value
-
而ES6可以在定义形参的时候就设置好默认值
-
不过设置默认值的形参建议放在参数列表的后面
-
function foo (value1 = 'value1', value2 = 'value2') {
console.log(value1, value2)
}
foo('haha') // 'haha' 'value2'
2.6 ...操作符
-
主要有两种作用,作剩余操作符和展开操作符
(1)剩余操作符
-
用作于获取的时候使用,使用特点是必须要放在最后一位,
-
ex:获取函数的参数
function foo (a, ...rest) { console.log(rest) } foo(1, 2, 3, 4) // [2, 3, 4]
(2)展开操作符
-
用作于传递数组的每一项作为参数,特点也是放在最后,可以传入伪数组
-
ex:传入函数参数并将arguments转换为数组
var arr = [1, 2, 3] function foo () { console.log([...arguments]) } foo(...arr) // [1, 2, 3]
2.7 箭头函数
-
箭头函数只能作为匿名函数,可以用函数表达式声明,建议用在传递的参数是函数上面
(1)箭头函数的语法
// 普通函数 const foo1 = function (a, b) { return false } // 箭头函数 // ① 当代码段只有一条表达式的时候 const foo2 = (a, b) => return false // ② 当参数只有一个的时候 const foo2 = a => return false const foo2 = (a, b) => { console.log(a, b) return false }
(2)this指向
-
不会指向调用者,会指向定义时候所在的this
-
若想使用4种可以改变this的函数调用方法的话(构造函数调用、对象方法调用、事件绑定方法、定时器函数),建议先用普通函数包裹一个子调用的箭头函数,如下
var age = 20 const obj = { age: 18, foo1: () => { // 这里面的this指向的是foo1所在的this,因为obj没有块级作用域,所以指向window,而window.age = 20 console.log(this.age) }, foo2: function () { // 这里面的this被普通匿名函数给锁住了,在调用的时候只想调用者也就是obj,而obj.age = 18 ;(() => { console.log(this.age) })(); } } obj.foo1() // 20 obj.foo2() // 18
2.8 对象字面量加强与计算属性名
(1)对象字面量加强
-
可以理解为ES5对象写法的语法糖
-
属性值与方法的语法糖:
-
注意:下面的sayHi实质是普通函数的简写,不是箭头函数
-
let name = 'zs' const obj = { name, sayHi () { console.log(this.name) } } obj // { name: 'zs', sayHi: f }
-
(2)计算属性名
-
在定义对象属性值或者调用的时候(在ES6之前只能调用使用)可以使用 [] 包裹的里面用表达式计算出属性名
-
如果是全局变量的函数,需要用window[]来计算变量名调用
-
let fnName = 'foo' const obj = { [fnName] () { console.log(`我的函数名叫做${arguments.callee.name}`) } } // obj[fnName]也可以是ES6之前就可以实现的 obj.foo() function foo () { console.log('我是foo函数调用的') } // [fnName]()这种方法是错误的 window[fnName]()
2.9 Object新增方法
-
接下来介绍一下Object.assign()和Object.is()
(1)Object.assgin()
-
这个是用于对象的浅拷贝的,下面我会介绍几种应用场景
-
语法:
-
const result Object.assign(obj1, obj2) result === obj1 // true // obj2的值会覆盖到obj1, 有同名字的key就会 // 即使不使用返回值, obj1也已经发生改变了
-
-
① 当传入参数是引用类型而且不想函数内部引用会修改函数外部的值
-
但是如果里面的值还有引用类型的话,需要深拷贝才可而已
-
const obj = { name: 'zs', oobj: { aaa: 1 } } function foo (obj) { const newObj = Object.assign({}, obj) newObj.name = 'ls' newObj.oobj.aaa = 2 console.log(obj) console.log(newObj) } foo(obj) // 下面oobj.aaa都改成了2 // obj --- { name: 'zs', oobj: { aaa: 2 } } // newObj --- { name: 'ls', oobj: { aaa: 2 } }
-
② 构造函数传入参数的时候,将参数的值传入到this中
function Student (options) { Object.assign(this, options) }
(2)Object.is()
-
这个用于更精确的全等===,但还是推荐是用===
-
① 比如 -0 与 +0 使用全等符号是相等的,但是我们期望它不等
-
console.log(-0 === +0) // true console.log(Object.is(-1, +0)) // false
-
-
② NaN与NaN是不全等的,也不等于,在这个方法里面会返回true
-
console.log(NaN === NaN, NaN == NaN) // false false console.log(Object.is(NaN, NaN))
-
2.10 class类、静态方法、类的继承
(1)class类
-
可以当成是构造函数的语法糖,在一个class里面设置完
-
语法:
-
constructor相当于定义构造函数的代码段
-
里面的方法比如sayHi是设置在prototype里面的
-
class Person { constructor (name, age) { this.name = name this.age = age } sayHi () { console.log(`Hi, my name is ${this.name}`) } }
(2)静态方法
-
构造函数有静态方法和实例方法,静态方法是构造函数对象调用的。实例方法,是实例对象调用的
-
语法:
-
静态方法里面的this指向的是构造函数对象
-
甚至可以直接new this()调用
-
class Person { constructor (name, age) { this.name = name this.age = age } sayHi () { console.log(`Hi, my name is ${this.name}`) } static create (name. age) { console.log(this) return new Person(name, age) // return new this(name, age) 也是正确的 } }
(3)类的继承
-
类似于组合继承,不过有一个super方法可以根据我们的需求赋值
-
语法:
-
主要是使用super来继承父类的属性添加到实例对象中,super是一定要调用的
-
class Person { constructor (name, age) { this.name this.age } sayHi () { console.log(`Hi, my name is ${this.name}`) } } // 继承 class Student extends Person { constructor (name, age, number) { super(name, age) // 一定要调用 this.number = number } hello () { console.log() } }
-
其中用super继承属性是存在于实例对象中
-
继承的方法是存在于__proto__.__proto__之中
-
定义的方法存在于__proto__之中
2.11 Set
-
Set是一种新的数据结构,叫做集合
-
它可以像数组一样存储变量、赋值。可以用来作数据去重
const s = new Set() s.add(1).add(2).add(3).add(4) console.log(s) // Set(4) {1, 2, 3, 4}
Set的属性和方法
s.add() // 添加值 s.forEach() // 与数组的同理 s.size // 相当于length s.has() // 是否含有某一项 s.delete // 删除某一项
-
里面有遍历器,可以使用for of遍历
-
也可以转换成数组运算
-
① Array.from()
-
② [...s]
-
2.12 Map
-
Map是一种新的数据结构,叫做字典
-
它跟对象的用法非常相像,他的key可以是任意类型的数据而object会把数据都变成string类型再传入到key
Map的属性和方法
const map = new Map() const a = { a: 1} map.set(a, 100) console.log(map) // Map(1) {{a: 1} => 100} console.log(map.get(a)) // 100 // 方法与Set类似 map.has() map.delete() map.clear() map.forEach() ...
2.13 Symbol
-
是ES2015的一种新的基本数据类型
-
可以优秀的解决命名冲突问题,为对象添加私有变量(外部访问不到)
-
语法:
-
① Symbol()
-
② Symbol(参数)
-
const s = Symbol('123') const b = Symbol('123') s === b // false
Symbol的一些用法
-
① 传入字符串识别是否为同一Symbol(Symbol.for)
-
传入的如果不是字符串,会转换成字符串再传入
-
要区分是否是for方法,即使传入的字符串相同但不都是for方法的话不会返回同一个值
-
const s1 = Symbol.for('123') const s2 = Symbol.for(123) const s3 = Symbol(’123‘) const console.log(s1 === s2) // true console.log(s2 === s3) // false
-
② 作为对象的私有变量
-
即使使用JSON转化,也会识别不出来,所以非常隐蔽
-
可以使用Object.getOwnPropertySymbols()来获取,但是只能获取到symbol值
-
const obj = { foo: 1, [Symbol()]: 'symbol' } console.log(obj) // {foo: 1} Object.keys(obj) // ['foo'] JSON.stringify(obj) // {"foo":"1"} Object.getOwnPropertySymbols(obj) // [Symbol()]
-
③ 为object类型的toString添加标识
-
使用到了Symbol.toStringTag
-
如果传入的value还是object类型则还是[object object]
-
const obj1 = {} console.log(obj1.toString()) // [object object] const obj2 = {[Symbol.toStringTag]: 'hahaha'} console.log(obj2.toString()) // [object hahaha]
2.14 for of 遍历
-
for if的出现是为了作为遍历所有数据结构的统一方式
-
在次之前我们遍历的方式有
-
对象:for in
-
数组:forEach...
-
-
这些方法都有一定的局限性只适用于局部数据
-
比如forEach不能用break打断
-
for in 不能遍历集合Set
-
-
for of可以遍历大部分数据类型包括set、map等,且相对于forEach可以设置break打断
-
但是目前obj还是能适配for of遍历
const s = new Set(['foo1', 'foo2' ,'foo3']) for (const item of s) { console.log(item) } // foo1 foo2 foo3 const m = new Map() m.set('a', 1) m.set('b', 2) for (const item of m) { console.log(item) } // 会变成数组形式来表现Map的对应关系 // ['a', 1] ['b', 2]
2.15 ES2015的其它内容
-
可迭代接口
-
迭代器模式
-
生成器
-
Proxy 代理对象
-
Reflect 统一的对象操作API
-
Promise 异步解决方法
-
ES Modules 语言层面的模块化标准
2.16 ES2016概述
-
主要增加了两个新特性数组的includes,指数运算符
(1)Array.prototype.includes
-
类似于ES2015中String的includes如果含有则返回true
-
它比indexOf的优点之一在于可以检测是否含有NaN
const arr = [1, 2, 3, NaN, 5] console.log(arr.indexOf(NaN)) // -1 console.log(arr.includes(NaN)) // true
(2)**指数运算符
-
在这之前,指数运算是多个相乘或者Math.pow()实现的
console.log(Math.pow(2, 3)) // 8 console.log(2 ** 3) // 8