许久没有写总结博客了,这阵子都是在Git上做学习笔记。马上就到年底了,也是时候做一些知识总结了。(承前启后)
JavaScript是一门很有意思的语言,如名字里带Java,但它和Java并没有什么关系。JS本身又是弱语言类型的(数据类型的隐式转换),不像Java、C等对数据类型有严格的要求。这个特性的优点就是数据操作和使用很灵活,缺点就是可能由此带来一些问题和会有很多基于这个特点的技巧需要记,也就使得用起来相对复杂一些。且相对来说JS的发展属于起步晚一些的,一些规范和功能都在不停得制定和完善。而且最近的新功能和新版本更新越来越快。
JS的发展历程,20世纪末的从开始到慢慢规范。1997年第一版发布,1998年修改为国际标准格式,1999年正则、作用域链、新的控制指令、异常处理、错误定义、数据输出格式化等,JS开始快速广泛使用。2009年,添加了严格模式以对前面模糊的概念进行规范和修正,增加了getters,setters,JSON等。2011年发布ES5标准。这里就从这个版本开始总结。其实关于JS的版本有很多不同的叫法,比如这里说的ES是ECMAScript的缩写,ES5又叫ECMAScript5.1,甚至还有ISO/IECxxx的国际标准版本号,甚至还可以叫ES2011。叫法太多了,想记反而会把自己搞的头疼,所以索性大家近年来都只认ES5这一种叫法。
ES5
ES5是2011年发布的,主要新增的特性是(其实2009年的版本不是标准正式版本,所以2009年版本的那几个特性也可以算作是ES5的特性,比如严格模式,JSON等,这里不细说了):
- JSON,主要是parse和stringify两个方法比较常用;
- Object扩展,Object.create(prototype,[descriptors]),Object.defineProperties(object,descriptors)(defineProperty单个), 和 getPrototypeOf 、getOwnPropertyDescriptor 、getOwnPropertyNames 、seal、freeze、preventExtensions (这3个是一套)、keys等常用属性和方法;
- Array原型扩展,Array的prototype原型增加了isArray、indexOf、lastIndexOf、every、some、forEach、map、filter、reduce、reduceRight(这7个是遍历相关)等方法;
- Function原型扩展,新增bind方法,参考call和apply进行对比记忆;(三者都用于将this的指向做改变,第一个参数都是目标对象,bind和call的参数形式一样,可以多个参数,bind是将函数返回,call和apply是立即执行,apply是两个参数,第二个参数是后边的参数的数组)
ES6
ES6较ES5更新变化较大较多,是2015年发布。主要部分如下:
1.块级作用域声明,let和const,功能如var,但属于块级作用域,比较容易区分;
2.类Class,ES6之前都是使用构造函数结合原型链等其他类似方法来实现一个实例对象,现可直接使用class来实现,示例如下;
1 // Old 2 function Person(name, age) { 3 this.name = name 4 this.age = age 5 } 6 Person.prototype.say = function() { 7 return 'Name:' + this.name + ',age:' +this.age 8 } 9 // New 10 class Person { 11 constructor(name, age) { 12 this.name = name 13 this.age = age 14 } 15 say() { 16 return 'Name:' + this.name + ',age:' +this.age 17 } 18 }
3.箭头函数,即将匿名函数的定义方式进行缩减,=>代替function关键字,主要用于匿名函数的地方,没有自己的this,arguments,super,new.target,如arr.forEach(item=>{});
4.默认参数值,即在定义方法时可以很方便的为参数指定默认值,如 const funcA = (age=0)=>{};
5.模板字符串,主要用于字符串拼接,形势是使用``结合${},如const name='xxx';alert(`Name:${name}`);
6.解构赋值,通过解构赋值,将属性或值 从 对象或数组中取出赋值给其他变量,解释比较晦涩,例如交换两个变量的值[a,b]=[b,a],就不需要再使用定义第三个中间值的传统处理方法了,可以算得上是颠覆传统了;
7.Module模块化,在ES6之前,模块化主要是CommonJS和AMD定制的一类规则,ES6增加了标准的模块化,示例如下;
1 // Old 2 // circle.js 3 const {PI} = Math 4 exports.area = (r) => PI * r ** 2 5 exports.circumference = (r) => PI * r * 2 6 7 // index.js 8 const circle = require('./circle.js') 9 console.log(`圆形面积:${circle.area(2)}`) 10 11 // New 12 // circle.js 13 const {PI} = Math 14 export const area = (r) => PI * r ** 2 15 export const circumference = (r) => PI * r * 2 16 17 // index.js 18 import {area} = './circle.js' 19 console.log(`圆形面积:${area(2)}`)
8.扩展运算符(三个点),只用于可迭代对象,如现有一个将3个数相加的方法,如果要将一个数组的3个数相加,传统写法是使用 方法.apply(null, arr),使用扩展运算符则可以直接 方法(...arr) 来执行;
9.对象属性简写,如有const a = '1';const obj = {a:a},可写作const obj = {a},属性名与变量名 的字符相同的情况使用;
10.Promise,这就比较好玩了,在之前实现异步的一般方法是使用回调,有了Promise之后,异步和链式调用有了新的方向。说到这里就很容易扯到执行顺序上,因为JS是顺序执行的,而又有Promise、回调甚至await/async来打乱执行顺序实现异步,很多时候都很喜欢问。此外Promise解决了回调地域的写法即多层嵌套,比如最常见的ajax请求需要依赖前边的请求返回的时候,传统写法就是嵌套的ajax,现可写作Promise.then,then里的入参即是前边一个请求的返回,这也是为什么Promise经常和then出现在一起,而且then其实return的时候返回的也是一个Promise,形如Promise.resolve()包装,如:Promise.resolve(1).then(res => {//res是1 return 2}).then(res => {// res是2});// TODO -- 总结awite/async和Promise
11.for...of,可迭代对象(Array,Map,Set,String,TypedArray,arguments)迭代,如const arr = [1,2,3];for(const el of arr){xxx};
12.symble类型,一种新的基本数据类型,如const a = Symble(22),不支持使用new生成,作用是生成唯一值,如Symbol(1) == Symbol(1)是false的;
13.迭代器(Interator)/生成器(Generator),主要功能是实现自定义迭代,真正使用到的地方目前见到并不多,见过有人结合promise实现类似await/async的效果,但是还不如直接使用何必绕来绕去,所以这里不详细介绍,等以后有适用场景再做详细对比,简单介绍,访问下一项next(),关键字yield和*,举例如下;
// stage1 function *makeRangeIterator(start = 0, end = Infinity, step = 1) { for (let i = start; i < end; i += step) { yield i; } }// 实测*贴着function和贴着方法名没有区别;yield起着类似return的效果 var a = makeRangeIterator(1,10,2) a.next() // {value: 1, done: false} a.next() // {value: 3, done: false} // stage2 function *createIterator(items){ for (let i = 0; i< items.length; i++){ yield items[i] } } let iterator = createIterator([1,2,3]) console.log(iterator.next()); // { value:1, done: false } // stage3 function *createIterator(){ yield 1 yield 2 yield 3 } let iterator = createIterator() console.log(iterator.next().value) //1
14.Set/WeakSet,Set类型对象允许存储任意类型的唯一值,因其唯一性,可用来数组去重如[...new Set(arr)],WeakSet类似但只能存放对象引用,且元素如果没有其他对象或属性引用就会被释放回收,且对象无法被枚举,两者有add,has,delete,clear等方法;
15.Map/WeakMap,Map类型保存键值对,键和值都可以是任意值或对象,Map类型方法有set(key, value)和get,has,delete,clear等,WeakMap与Map的区别与14类似,key只能是对象;
16.Proxy/Reflect,代理反射,效果是建立监听和watcher的感觉,实现拦截js,元编程,语法 -- var proxy = new Proxy(target, handler),handler可以包含set,get,apply,has等等,Reflect与其一一对应如 - Reflect.has(Object, 'assign'),示例实现数据监听;
1 const observe = (data, callback) => { 2 return new Proxy(data, { 3 get(target, key) { 4 return Reflect.get(target, key) 5 }, 6 set(target, key, value, proxy) { 7 callback(key, value); 8 target[key] = value; 9 return Reflect.set(target, key, value, proxy) 10 } 11 }) 12 } 13 14 const FooBar = { open: false }; 15 const FooBarObserver = observe(FooBar, (property, value) => { 16 property === 'open' && value 17 ? console.log('FooBar is open!!!') 18 : console.log('keep waiting'); 19 }); 20 console.log(FooBarObserver.open) // false 21 FooBarObserver.open = true // FooBar is open!!!
17.Regex对象的扩展,正则新增修饰符i,iu,y,(u是unicode模式),新增属性 - flag属性查看修饰符、sticky属性对应y,字符串方法的实现改为调用RegExp
方法(?)-- TODO 正则需要稳固;
18.Math扩展,0b/0B表示二进制,0o/0O表示8进制,Number.EPSILON(最小精度)等一些常量,Number的扩展方法(.parseInt(),parseFloat(),isFinite(),isNaN(),isInteger(),isSafeInteger()),Math方法扩展,(trunc()-返回整数部分,sign()返回数值类型正负0,cbrt()立方根,clz32()32位无符号整数,fround()32位单精度浮点数形式,imul()返回两个数相乘,hypot()所有数值平方和的平方根,expm1()e的n次方-1,log1p()等于Math.log(1 + n),log10(),log2(),三角函数全部的双曲正弦 -- 在原三角函数名后加h如sinh());
19.Array扩展,form(如console.log(Array.from([1, 2, 3], x => x + x)) // [2, 4, 6])(console.log(Array.from('foo')) // ["f", "o", "o"])参数为源和handler,of(如Array.of(3)//[3];Array.of(1,2)//[1,2]),copyWithin - array.copyWithin(target, start, end),后两个参数可选,默认首到尾,不改变数组长度,只替换值,如
var fruits = ["Banana", "Orange", "Apple", "Mango",'???'];
fruits.copyWithin(2, 0);
// ["Banana", "Orange", "Banana", "Orange", "Apple"]
find(),findIndex()(均是只返回第一个),fill(),keys()(索引),values()(这两个方法同样适用于对象),entries() 返回key value的遍历器对象 -- next的value是存放key和value即这里是index和值的数组,数组空位 -- 将空位自动转化为undefined或empty;
ES7
ES6的变动真的是太多了,后边的版本相对来说就没有那么多了,也就是趋于稳定了。2016年发布,ES7主要如下:
- Array的includes()方法;
- 幂运算符**,等同于Math.pow();
- 模板字符串转义;
ES8
2017年发布:
1.async/await,解决前边ES6的promise的then嵌套太多的问题,当然并没有明显的优劣,只是写法和阅读性的不同async/await是成对出现的,示例:
1 async function myFetch() { 2 let response = await fetch('coffee.jpg') 3 return await response.blob() 4 } 5 6 myFetch().then((blob) => { 7 let objectURL = URL.createObjectURL(blob) 8 let image = document.createElement('img') 9 image.src = objectURL 10 document.body.appendChild(image) 11 })
2.Object.values(),前边已介绍;
3.Object.entries(),前边已介绍;
4.padStart()/padEnd(),给字符串进行扩展,参数是目标长度和要用来填充长度不够的部分的字符串,如号码的使用星号加密,如下;
1 function encryStr(str, len, aimFlag = '*') { 2 const lastDigits = str.slice(-len) 3 const maskedNumber = lastDigits.padStart(str.length, aimFlag) 4 return maskedNumber 5 }
5.尾逗号,ShareArrayBuffer(谷歌火狐被禁,安全性),Atomics对象(ShareArrayBuffer对象的原子操作);
6.Object.getOwnPropertyDescriptors(),获取对象属性的描述符,如浅拷贝:
1 // 浅拷贝一个对象 2 Object.create( 3 Object.getPrototypeOf(obj), 4 Object.getOwnPropertyDescriptors(obj) 5 )
ES9
2018年发布:
1.for await of,for of配合迭代生成器的使用,使得可以遍历异步,示例如下,asyncGenerator的定义省略;
1 (async function() { 2 for await (num of asyncGenerator()) { 3 console.log(num) 4 } 5 })()
2.模板字符串转义的部分语法调整;
3.正则表达式反向断言,断言的定义:又叫非消耗性匹配或非获取匹配,是一个对当前匹配位置之前或之后的字符的测试, 它不会实际消耗任何字符,之前的都是正向断言。(?=pattern)
零宽正向肯定断言,(?!pattern)
零宽正向否定断言,(?<=pattern)
零宽反向肯定断言,(?<!pattern)
零宽反向否定断言;
4.正则Unicode转义,s/dotAll 模式,命令捕获组(后边单独再详细总结);
5.Object Rest Spread,即...rest,在ES9之前,...多用于数组的操作,比如实现复制拆分等,在ES9里给对象也扩展了这种语法,比如,实现对象的复制,而且是类似深复制的效果,示例如下,
1 const input = { 2 a: 1, 3 b: 2, 4 c: 4 5 } 6 const output = { 7 ...input, 8 c: 3 9 } 10 input.a=0 11 console.log(input,output) // {a: 0, b: 2, c:4} {a: 1, b: 2, c: 3}
值得注意的是,如果对象的属性是一个对象,则对象是浅复制的,可以理解为只深复制一层,结合rest的使用示例如下;
1 const input = { 2 a: 1, 3 b: 2, 4 c: 3 5 } 6 let { a, ...rest } = input 7 console.log(a, rest) // 1 {b: 2, c: 3}
6.Promise.prototype.finally(),不论Promise是成功还是失败都会执行这里的内容;
ES10
1.Array.prototype.flat() / flatMap(),flat是按参数指定的深度降维,flatMap()与 map() 方法和深度为1的 flat()效果差不多,可以代替reduce() 与 concat(),示例如下;
1 var arr1 = [1, 2, 3, 4] 2 arr1.flatMap(x => [[x * 2]]) // [[2], [4], [6], [8]] 3 arr1.flatMap(x => [x, x * 2]) // [1, 2, 2, 4, 3, 6, 4, 8] 4 arr.reduce((acc, x) => acc.concat([x, x * 2]), []) // [1, 2, 2, 4, 3, 6, 4, 8]
2.String.prototype.trimStart() / trimLeft() / trimEnd() / trimRight(),去掉单边空格,start/end才是标准名称,left/right是别名;
3.Object.fromEntries(),Object.fromEntries([['a',1]])//{'a':1} ,是Object.entries()的反函数,参数是一个形如二维数组的map对象;
4.Symbol.prototype.description,Symbol对象是描述符,只读,其实字符上来说就是其new的时候的参数;
5.String.prototype.matchAll,参照match,很好理解,正则匹配;
6.Function.prototype.toString() 返回注释与空格;
7.try-catch的catch的参数非必须,可以直接try{}catch{}了;
8.BigInt,内置对象,表示大于2的53次方-1的整数,即Number的最大数之外的整数,定义一个大整数可以用整数字面量后边加n表示,或者用BigInt(整数字面量)来表示,值得关注的是BigInt不能和别的类型的数值进行运算,否则会抛出异常,在BigInt出来以后,JS的原始类型便增加到了7个,如下:•Boolean•Null•Undefined•Number•String•Symbol (ES6)•BigInt (ES10);
9.globalThis,全局对象的this值;
10.import(),动态引入,可以接then;
11.私有元素与方法,关键字是#,表示私有,示例如下;
1 class Counter extends HTMLElement { 2 #xValue = 0 3 4 get #x() { 5 return #xValue 6 } 7 set #x(value) { 8 this.#xValue = value 9 window.requestAnimationFrame(this.#render.bind(this)) 10 } 11 12 #clicked() { 13 this.#x++ 14 } 15 16 constructor() { 17 super(); 18 this.onclick = this.#clicked.bind(this) 19 } 20 21 connectedCallback() { 22 this.#render() 23 } 24 25 #render() { 26 this.textContent = this.#x.toString() 27 } 28 } 29 window.customElements.define('num-counter', Counter)
扩展
此外,还有几个对我们平常代码效率提升很有用的特性:
1.可选链操作符
比如,我们平常在请求接口的时候,可能接口的数据处理比较简单,就可能会有多层嵌套的对象是数据,我们要取比较深的数据的时候,就得一层一层判断以免有外层为null导致抛出异常,如let nestedProp = obj && obj.first && obj.first.second,有了该特性之后,可以直接简写为let nestedProp = obj?.first?.second,使用问号代替原来需要重复写的部分;
2.空位合并操作符
比如,我们平常在做三元运算或使用||来取值时,由于0,false和空会被当做false处理,因而会取到后边的值,而如果想使这些值作为有效项,则就要改成if判断,比较冗长,现可以直接使用??来实现即两个问号,表示除了null和undefined都有效,如let c = a ?? b;
3.static关键字
类似于java中的static,表示静态字段,也可结合#私有来使用;
4.await的扩展
ES8新增的await/async中只允许await在async函数内使用,现可在外部使用,如const strings = await import(`/i18n/${navigator.language}`),还有在http请求或者数据库连接等方面也很方便;
5.弱引用WeakRef
与WeakMap,WeakSet类似,这两者实现了部分弱引用,WeakRef则是真正的弱引用,WeakRef 实例具有一个方法 deref,该方法返回被引用的原始对象,如果原始对象已被收集,则返回undefined对象。
以上几个扩展属性目前还没有广泛支持,如需使用需要谨慎。