只要学不死,就往死里学。
ECMAScript是一种能够在宿主环境中执行计算并能操做可计算对象的基于对象的程序设计语言。
ECMAScript是一种语言设计标准,虽然一般也被称为JavaScript,可是严格意义上,ECMAScript并不等于JavaScript,前者是后者的设计标准,后者是前者的具体实现和扩展(在浏览器环境中,JavaScript不只仅实现了ECMAScript,同时还基于浏览器实现了针对DOM和BOM的操做)。编程
ECMAScript有不少版本,其中ECMAScript2015最为突出。首先,其改变了传统的以版本命名的方式,改成以年份命名。其次,其明确了标准发布时间,由之前的时间不固定改成一年一个版本。最后,其增长了诸多特性,使js更像现代化编程语言。segmentfault
ES6能够特指ECMAScript2015, 也能够泛指ECMAScript2015及之后的全部版本。
ECMAScript2015新增的特性能够总结为四大类:api
在ECMAScript2015之前,js中存在两种做用域:数组
ECMAScript2015新增了块级做用域,代码只在相应的代码块中起做用。浏览器
// before if (true) { var num = 1 } console.log(num) // 输出1, 能够在块以外访问变量 // after if (true) { let num = 1 } console.log(num) // 抱错,不存在num, num只在{}构成的代码块中起做用
let,const和var同样,都是声明变量的方式,只是let,const会给变量添加做用范围,规避了var带来的诸多问题。数据结构
// before console.log(num) // 输出1, 由于浏览器编译js代码的时候,会将变量提高,所以能够在变量声明前访问变量 var num = 1 // after console.log(num) let num = 1 // 抱错,由于let变量只能先声明后使用。
// before for (var i = 0; i < 3; i++) { setTimeout(function () { console.log(i) // 输出三次 3, setTimeout在执行时,操做的都是全局做用域中的i变量。 }, 100) } // after for (let i = 0; i < 3; i++) { setTimeout(function () { console.log(i) // 输出0, 1, 2, 因为每次setTimeout在执行的时候,都使用的是其做用域内的i,不会相互影响 }, 100) }
const的用法和let相同,只不过,const声明的变量是只读的,不能修改。app
这里的只读,指的是不能修改变量指向的内存地址,可是能够修改变量的属性。
const obj = { name: 'zhangsan' } obj.name = 'lisi' // 能够修改 obj = { name: 'lisi' } // 抱错,修改了变量的内存指向地址,指向了新的地址。
解构容许你使用相似数组或对象字面量的语法将数组和对象的属性赋给各类变量。dom
// before, 用下面的方式获取数组中的每一项,并赋值给对应的变量 let arr = [1, 2, 3] let a = arr[0] let b = arr[0] let c = arr[0] // after let [a, b, c] = arr // 解构会将数组中的每一项自动赋值到对应位置的变量 let [, , c] = arr // 若是只想获取第三个,能够传入两个, let [a, b] = arr // 若是只想获取前两个,能够省略后面。 // 对象也能够解构 let obj = { name: 'zhangsan', age: 18, gender: true } let { name, age, gender } = obj // 因为对象属性没有顺序,所以须要变量和属性名相对应的方式解构。 let { address = '北京' } = obj // 能够为不存在的属性默认值 let { name: otherName = 'lisi'} = obj // 能够为某个属性对应的变量重命名
js原有"", ''两种方式表示字符串,ECMAScript2015新增了``表示字符串。异步
// 模版字符串支持多行字符串,方便包含换行符的字符串声明 let div = `<div> this is div </div>` let name = 'zhangsan' let intro = `my name is ${name}` // 能够嵌入变量等任何包含返回值的js合法语句,将被替换成返回值。
除了上述基础应用外,还包含一种特殊的带标签的模版字符串。编程语言
let obj = { name: 'zhangsan', age: 18, gender: 0 } // 定义标签方法 // strs表示用${}分割后的字符串数组,后续的参数表明${}对应的计算值 function changeGender(strs, name, gender) { console.log(strs, name, gender) // [ '我叫', ',我是', '' ] zhangsan 0 let g = gender === 0 ? '男人' : '女人' return strs[0] + name + strs[1] + g } // 使用 console.log(changeGender`我叫${obj.name},我是${obj.gender}`)
为字符串添加了常见的includes,startsWith,endsWith方法。
const str = 'my name is zhangsan' console.log(str.includes('name')) // 判断字符串中是否包含name console.log(str.startsWith('my')) // 判断字符串是否以my开头 console.log(str.endsWith('zhangsan')) // 判断字符串是否以zhangsan结尾
增强对象字面量的声明方式,简化声明代码。
let name = 'zhangsan' let person = { // 将同名的变量添加到对象上 name, // 简化对象上方法属性定义 getName() { return this.name }, // []表示计算属性,计算获得的值做为属性的属性名 [Math.random()]: 18 }
能够将多个对象的属性赋值到目标对象上,有则替换,没有则添加。
let obj = { name: 'zhangsan', age: 18 } let obj2 = { address: '北京' } let person = { name: '' } console.log(Object.assign(person, obj, obj2)) // 复制一个全新的对象 let copied = Object.assign(person)
Object.is能够用于判断值是否相等。
console.log(0 == false) // true, == 会先将值作转换,而后比较 console.log(0 === false) // false, ===会执行严格的判断 // === 没法正确识别的状况 console.log(+0 === -0) // true, console.log(NaN === NaN) // false , === 认为每一个NaN都是独立的一个值 // Object.is能够正确判断上述的两种状况 console.log(Object.is(+0, -0)) // false, console.log(Object.is(NaN, NaN)) // true
在函数声明的时候,能够用更简单的方式给参数添加默认值。
// before function intro(name, age) { name = name || 'default' console.log(`my name is ${name}`) } // after, 默认值参数必须放在非默认值参数的后面 function intro(age, name = 'default') { console.log(`my name is ${name}`) }
对于不定个数参数函数,能够用剩余参数将某个位置之后的参数放入到一个数组中。
// before function add() { // arguments获取全部变量, arguments是一个伪数组 return Array.from(arguments).reduce(function (pre, cur) { return pre + cur }, 0) } console.log(add(1, 2, 3)) // after function intro(name, ...args) { console.log(name, args) // zhangsan [ 18, '北京' ] } intro('zhangsan', 18, '北京')
和默认参数相反,参数展开能够在调用函数的时候将数组中的每一项依次赋值给函数中相应位置的参数。
function intro(name, age, address) { console.log(name, age, address) } const arr = ['zhangsan', 18, '北京'] // before // 1. 利用获取每一个位置的值实现 intro(arr[0], arr[1], arr[2]) // 2. 利用apply方法实现 intro.apply(intro, arr) // after intro(...arr)
箭头函数能够简化函数的声明,尤为是在回调函数的声明上。
const arr = ['zhangsan', 18, '北京'] // before arr.forEach(function (item) { console.log(item) }) // after arr.forEach(item => console.log(item))
箭头函数与普通的function函数的this指向不一样,function的this指向调用者的上下文,是在调用时指定,而箭头函数的this是在声明时指定,指向父级的上下文。
const obj = { name: 'lisi', getName() { console.log(this.name) } } var name = 'zhangsan' let getName = obj.getName getName() // zhangsan, window调用,this指向window obj.getName() // lisi, obj调用,this指向obj //---------------------------------------------- const obj2 = { name: 'lisi', getNameFn() { return () => { console.log(this.name) } } } obj2.getNameFn()() // lisi, 箭头函数的this指向父级上下文,即getNameFn的上下文,因为getNameFn由obj2调用,所以this指向obj2
能够利用Promise写出更优雅的异步代码,规避回调地狱。
new Promise(resolve => { setTimeout(() => { resolve(1) }, 100) }).then(value => { setTimeout(() => { resolve(value + 1) }, 100) }).then(value => { setTimeout(() => { resolve(value + 1) }, 100) })
经过Proxy代理能够实现对对象编辑获取等操做的拦截,从而在操做以前实现某种操做(例如Vue3.0就是利用Proxy实现数据双向绑定)。其和Object.defineProperty做用相似,可是相比Object.defineProperty,其语法更为简洁,并且做用范围更广(如Object.defineProperty无法监控数组项的增长和删除,Proxy能够)。
let obj = { name: 'zhangsan', age: 18 } let objProxy = new Proxy(obj, { get(target, property) { console.log(`获取${property}值`) return target[property] ? target[property] : 'default' }, set(target, property, value) { if (property === 'age') { value = value > 25 ? 25 : value } target[property] = value } }) console.log(objProxy.address) // default objProxy.age = 18 console.log(objProxy.age) // 18 objProxy.age = 30 console.log(objProxy.age) // 25
Proxy对象实例化时,第二个参数能够传入更多handler,以下图:
let arr = [1, 2, 3] let arrProxy = new Proxy(arr, { set(target, property, value) { value = value > 10 ? 10 : value return target[property] = value } }) arrProxy.push(11) console.log(arr) //[ 1, 2, 3, 10 ], 拦截成功,和push类似的shift,unshift,pop都可触发
Reflect是ES2015新增的静态工具类,包含一系列针对对象的操做API,目的是提供统一的对象操做方式,结束目前混杂的对象操做。
let obj = { name: 'zhangsan', age: 18 } // before // get console.log(obj.name) console.log(obj['name']) // set obj['address'] = '北京' // delete delete obj.address // after // get console.log(Reflect.get(obj, 'name')) // set Reflect.set(obj, 'address', '北京') // delete Reflect.deleteProperty(obj, 'address')
提供统一的操做api不只代码美观,并且更容易让新手上路。
Reflect提供的方法和Proxy的代理方法是一一对应的,若是Proxy中没有传入相应的代理方法,那么Proxy内部默认使用Reflect对应方法实现。
在ES2015以前,js可使用function和原型链实现类的声明。
function Person(name) { // 实例属性 this.name = name } // 实例方法 Person.prototype.intro = function () { console.log(`my name is ${this.name}`) } // 静态方法 Person.create = function (name) { return new Person(name) } // 使用 let zhangsan = Person.create('zhangsan') zhangsan.intro()
ES2015中添加了class关键字,能够用class关键字快速声明类。
class Person { // 静态属性 static tag = 'Person' constructor(name) { // 实例属性 this.name = name } // 实例方法 intro() { console.log(`my name is ${this.name}`) } // 静态方法, 利用static关键字 static create(name) { return new Person(name) } } // 使用 let zhangsan = Person.create('zhangsan') zhangsan.intro()
ES2015在提供快速声明类的class关键字以外,还提供了extends关键字实现类的继承
class Student extends Person { constructor(name, number) { // 调用父类的构造方法 super(name) // 声明本身的实例属性 this.number = number } say() { // 调用父类实例方法 super.intro() console.log(`个人学号:${this.number}`) } } // 使用 let zhangsan = new Student('zhangsan', '10001') zhangsan.say()
具体关于class的知识点还有不少,再也不赘述。
Set是ES2015新增的数据结构,用来表示集合的概念,特色是Set内部的值是不重复的,经常利用这个特色为数组去重。
Set基本使用以下:
// 声明集合,能够传入默认值,不传则是空集合 let s = new Set([1]) // 新增 s.add(2) // 获取集合长度 console.log(s.size) // 遍历 s.forEach(item => { console.log(item) }) // 删除 s.delete(2) // 清空集合 s.clear()
如何去重?
// 简单数据去重 let arr = [1, 2, 1, 'one', 'two', 'one'] console.log(Array.from(new Set(arr))) // 对象数组去重 let objArr = [ { name: 'zhangsan', age: 17 }, { name: 'lisi', age: 16 }, { name: 'zhangsan', age: 17 } ] function unique(arr) { // Set中判断对象是否重复是判断对象所指内存地址是否相同,因此不能直接将对象数组放入Set中 // 将对象数组转为字符串数组,方便对比。 let arrStrs = arr.map(item => JSON.stringify(item)) let uniqueStrs = Array.from(new Set(arrStrs)) return uniqueStrs.map(item => JSON.parse(item)) } console.log(unique(objArr))
Map是ES2015新增的数据结构,用来表示键值对的集合,能够弥补对象字面量的不足。对象字面量只能使用字符串做为键,即便使用计算属性传入非字符串做为键值,对象内部也会将其转为字符串,而Map没有这个限制,其键能够是任何数据。
let obj = new Map() let person = { name: 'zhangsan' } // 插入值 obj.set(person, '北京') // 判断值是否存在 console.log(obj.has(person)) obj.forEach((item, key) => { console.log(item, key) }) // 删除 obj.delete(person) // 清空 obj.clear() // 获取大小 console.log(obj.size)
Symbol是ES2015引入的新的原始数据类型(和string, number等并列),用来表示独一无二的值(只要调用Symbol(),那么生成的值就不一样)。
console.log(Symbol() === Symbol()) // Symbol能够接受一个参数做为标记值,这个值只是用于表示Symbol生成的变量的含义(描述自身),方便调试。 // 即便传入相同的参数,那么调用屡次返回的值也不相同 console.log(Symbol('bar') === Symbol('bar'))
let obj = { name: 'zhangsan', [Symbol()]: '打篮球' } console.log(obj[Symbol()]) // undefined , 使用者没法获取obj里面的Symbol键值 console.log(Object.keys(obj)) // [ 'name' ] object的key方法也获取不到 console.log(Object.getOwnPropertySymbols(obj)) // 只有使用getOwnPropertySymbols方法可以获取到对象上定义的全部Symbol类型的键
Symbol.for: 方法会根据给定的键 key
,来从运行时的 symbol 注册表中找到对应的 symbol,若是找到了,则返回它,不然,新建一个与该键关联的 symbol,并放入全局 symbol 注册表中。
const s = Symbol('bar') console.log(Symbol.for('bar') === s) // false Symbol()建立的值不会保存在注册表中 console.log(Symbol.for('foo') === Symbol.for('foo')) // true
Symbol.iterator: 能够用做对象的键的值,因为其永远是不重复的,因此不担忧被覆盖。在后面的实现Iterable迭代器接口会用到。
ES2015新增的数据遍历方式,能够用for...of遍历任何数据(只要其实现了Iterable接口)。与forEach相比,其能够在内部添加break关键字随时中止遍历。
const arr = [1, 'one', false, NaN, undefined] // 遍历数组 for (let item of arr) { console.log(item) // 终止遍历 if (!item) { break } } // 遍历Set const set = new Set(arr) for (let item of set) { console.log(item) } // 遍历Map let map = new Map() map.set({ name: 'zhangsan' }, '北京') map.set('age', 18) for (let [key, value] of map) { console.log(key, value) }
上节中提到,若是想要for...of遍历某种数据,那么该数据必须实现Iterable接口。
Iterable接口要求实现一个方法,该方法返回一个迭代器对象(iterator), 迭代器对象包含next方法,next方法返回一个包含value,done两个键值的对象,value保存下一次遍历时的数据,done用于表示迭代器是否完成。
const todos = { life: ['吃饭', '睡觉'], course: ['语文', '数学', '英语'], // 实现Iterable接口 [Symbol.iterator]: function () { let allTodos = [...this.life, ...this.course] let index = 0 return { next: function () { return { value: allTodos[index], done: index++ >= allTodos.length } } } } } for(let item of todos) { console.log(item) }
上述代码体现了编程模式中经常使用的迭代器模式。
迭代器模式指的是提供一种方法顺序访问一个聚合对象中的各类元素,而又不暴露该对象的内部表示。
也就是说在todos对象中用了life和course两个字段存储须要作的事情,而外面在使用该对象的时候,不用关心todos对象内部如何存储,只须要经过for...of遍历获取全部数据,这样就下降了数据定义和数据使用的耦合度。当todos新增一个新的字段存储须要作的事情时,只须要修改todos,而不须要需改使用者。
Generator生成器是ES2015新增的一种异步编程解决方案,用于解决异步编程中回调函数嵌套的问题,其一般使用*和yeild两个关键字。
Generator生成器生成的方法是惰性执行的,只有调用者在调用next方法后其才会执行,遇到yield关键字又中止。
function* createIdMaker() { let id = 0 while (true) { yield id++ } } const idMaker = createIdMaker() console.log(idMaker.next().value) // 0 console.log(idMaker.next().value) // 1 console.log(idMaker.next().value) // 2
const todos = { life: ['吃饭', '睡觉'], course: ['语文', '数学', '英语'], // 实现Iterable接口 [Symbol.iterator]: function* () { const all =[...this.life, ...this.course] for(let item of all) { yield item } } } for (let item of todos) { console.log(item) }
详情见上一篇异步编程
Modules是ES2015提供的标准化模块系统,模块化为你提供了一种更好的方式来组织变量和函数。你能够把相关的变量和函数放在一块儿组成一个模块。todo
等js模块化部分学完以后再补充
ES2016相对于ES2015是一个小的版本,只提供了以下的小特性。
提供includes方法方便查找数组中是否存在某一项。
const arr = [1, 'one', false, NaN, undefined] // before console.log(arr.indexOf(1) > -1) // true console.log(arr.indexOf(NaN) > -1) // false 对于NaN的查找出错 // after console.log(arr.includes(NaN))// true,能够正常查找
方便在大量的数学运算中使用。
// before console.log(Math.pow(2, 10)) // after console.log(2 ** 10)
ES2017和ES2016同样,也是小版本。
和Object.keys相对应,获取对象的全部值。
const obj = { name: 'zhangsan', age: 18 } Object.values(obj).forEach(item => { console.log(item) });
获取键值对的数组,至关于将Object.keys和Object.values组合。
const obj = { name: 'zhangsan', age: 18 } Object.entries(obj).forEach(([key, value]) => { console.log(key, value) });
用于获取对象的属性描述信息,能够用于补充解决Object.assign的问题。
const obj = { firstName: 'zhang', lastName: 'san', get fullName() { return this.firstName + ' ' + this.lastName } } let copied = Object.assign({}, obj) copied.firstName = 'li' console.log(copied.fullName) // zhangsan // Object.assign 在拷贝计算属性时,将计算属性的值拷贝过来,致使拷贝后的对象中计算属性有问题
使用getOwnPropertyDescriptors就能够避免这种拷贝问题。
const obj = { firstName: 'zhang', lastName: 'san', get fullName() { return this.firstName + ' ' + this.lastName } } let copied = {} Object.defineProperties(copied, Object.getOwnPropertyDescriptors(obj)) copied.firstName = 'li' console.log(copied.fullName)
用于在字符串的前面或者后面填充必定数量的某种字符,可使字符串显示的更加美观。
const obj = { number: '1', age: '18' } Object.entries(obj).forEach(([key, value]) => { console.log(`${key.padEnd(10, '-')}|${value.padStart(3, '0')}`) }) //number----|001 //age-------|018
容许像数组和对象那样,在声明和调用函数时在末尾加上逗号,只是为了方便部分人的书写习惯,没有实在乎义。
const arr = [1, 2, 3,] // 在数组末尾能够加入逗号,不影响数组声明 // 声明函数时能够在参数的末尾加入逗号 function add(a, b,) { return a + b } // 调用函数时能够在参数的末尾加入逗号 console.log(add(1, 2,))
新增的异步编程解决方案,一样是用于解决回调函数嵌套的问题。详情见上一篇异步编程。
和数组的展开和剩余类似,ES2018容许在对象上使用展开和剩余。
const obj = { one: 1, two: 2, three: 3, four: 4, five: 5 } const { one, four, ...rest } = obj // one => 1, four => 4 // rest => { two: 2, three: 3, five: 5} const obj2 = { foo: 'bar', ...rest } // obj2 => { foo: 'bar', two: 2, three: 3, five: 5} // 展开时,同名属性会覆盖 const obj3 = { foo: 'bar', two: 200, ...rest } // obj3 => { foo: 'bar', two: 2, three: 3, five: 5} const obj4 = { foo: 'bar', ...rest, two: 200 } // obj4 => { foo: 'bar', two: 200, three: 3, five: 5}
// 环视 const intro = '张三是张三,张三丰是张三丰,张三不是张三丰,张三丰也不是张三' // 向后否认 正向确定 只有在张三后面不是丰的时候,才会用李四替代张三 const res1 = intro.replace(/张三(?!丰)/g, '李四') // 向后确定 正向确定 只有在张三后面跟着丰的时候,才会用李四替代张三 const res2 = intro.replace(/张三(?=丰)/g, '李四') // 向前确定 反向确定 只有在00前面是A的时候,才会用88替代00 const res3 = 'A00 B00'.replace(/(?<=A)00/g, '88') // 向前否认 反向确定 只有在00前面不是A的时候,才会用88替代00 const res4 = 'A00 B00'.replace(/(?<!A)00/g, '88')
为正则组添加别名,方便查找正则组
const date = '2020-05-20' const reg = /(?<year>\d{4})-(?<mouth>\d{2})-(?<day>\d{2})/ const res = reg.exec(date) console.log(res) // 能够在groups对象下,经过别名year获取值 console.log(res.groups.year) // 2020
添加finally方法,不论Promise是resolve仍是reject,finally都会被执行。
new Promise((resolve, reject) => { setTimeout(() => { const now = Date.now() now * 2 ? resolve(now) : reject(now) }, 1000) }) .then(now => { console.log('resolved', now) }) .catch(now => { console.log('rejected', now) }) .finally(now => { console.log('finally', now) })
const arr = [ { id: 1, value: 'A' }, { id: 1, value: 'B' }, { id: 1, value: 'C' }, { id: 1, value: 'D' }, { id: 1, value: 'E' }, { id: 1, value: 'F' }, { id: 1, value: 'G' }, { id: 1, value: 'H' }, { id: 1, value: 'I' }, { id: 1, value: 'J' }, { id: 4, value: 'K' }, { id: 1, value: 'L' }, { id: 1, value: 'B' }, { id: 1, value: 'C' }, { id: 1, value: 'D' }, { id: 1, value: 'E' }, { id: 1, value: 'F' }, { id: 1, value: 'G' }, { id: 1, value: 'H' }, { id: 1, value: 'I' }, { id: 1, value: 'J' }, { id: 4, value: 'K' }, { id: 1, value: 'L' }, ] // 旧版本的 ES 排序事后的结果可能不固定 console.log(arr.sort(function (a, b) { return a.id - b.id }))
简化try...catch语法
try { throw new Error() } // catch后面能够省略参数e catch{ }
function foo(data) { // ??表示当data为undefined或者null的时候取100 let result = data ?? 100 // 简化下面的写法 let result1 = data === null || data === undefined ? 100 : data console.log(result) }
const list = [ { title: 'foo', author: { name: 'zce', email: 'w@zce.me' } }, { title: 'bar' } ] list.forEach(item => { // 若是author属性不存在,那么 item.author?.name 至关于item.author console.log(item.author?.name) }) // 还能够用下面相似的写法 // obj?.prop 获取对象属性值 // obj?.[expr] 获取对象属性值 // arr?.[index] 获取数组指定下标值 // func?.(args) 方法调用