Web的大杂烩马上就要告一段落,我也想弄点好东西出来,不然就成了只会复制没有思考的废物了。“Web瞎捣鼓”系列还会继续,只是不搞这种几千字的不知所云的东西,尽量弄点硬核的东西出来。这不是终章,而是新的起点。别揍我,这篇还会继续的有点虚头巴脑!
新特性
1、先上场和变量有关,前一篇已经写了let和const命令,就不再赘言。这里要提到的是数组和对象的解构赋值,就是按照一定的模式从数组和对象中提取值对变量进行赋值。它可以是不完全解构,只要模式匹配就能正确解构,可以使用reset参数来进行模式匹配。
let [foo, [[bar], baz]] = [1, [[2], 3]];
foo // 1
bar // 2
baz // 3
let [ , , third] = ["foo", "bar", "baz"];
third // "baz"
let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]
let { foo, bar } = { foo: 'aaa', bar: 'bbb' };
foo // "aaa"
bar // "bbb"
解构赋值允许默认值,解构目标匹配位置必须严格等于undefined,默认值才会有效。
let [x = 1] = [undefined];
x // 1
let [y = 1] = [null];
y // 1
// 对象的结构赋值,m和n是匹配模式最终赋值给a和b
let {m: a = 3} = {m: undefined};
a // 3
let {n: b = 3} = {n: null};
b // null
2、新增的数据类型Symbol,表示独一无二的值。
let s = Symbol();
typeof s
// "symbol"
3、新增数据结构Set和Map:
- Set类似于数组,成员的值都是唯一的,没有重复的值;对一维数组参数的值转换为自身成员。
- Map类似于对象,键值对集合(Hash结构);对二维数组参数的值转换为自身成员。
Map的键若是一个简单数据类型,严格相等则视为一个键;若是复合型数据结构内存地址一样可视为同一个键;undefined和null视为同一个键。
与它们相似的还有WeakSet和WeakMap:
- WeakSet 的成员只能是对象,而不能是其他类型的值;对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用。
- WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名;键名所指向的对象,不计入垃圾回收机制。
4、对字符串、正则、数值、函数、数组、对象进行扩展
- 字符串扩展:加强了对Unicode的支持;增加模版字符串`${}`省去用+连接;新增标签模版。另外还新增了一些属性方法和实例方法,如includes()、startsWith()等。
- 正则扩展:增加u修饰符、y修饰符、s修饰符、unicode属性、sticky属性、flags属性、具名匹配、正则索引等。
- 数值扩展:为二进制和八进制提供新的写法;扩展Number、Math对象;新增指数运算符、BigInt数据类型。
- 函数扩展:函数参数增加缺省参数和reset参数;新增箭头函数;catch参数允许省略。
- 数组扩展:新增扩展运算符;新增find()、findIndex()、includes()、entries(),keys()、values()等方法。
- 对象扩展:简洁表示法允许直接写入变量和函数;新增super关键字指向原型对象;扩展运算符可用于对象;新增链判断符?.判断对象是否存在、空运算符??进行判空设置默认值。另外还新增了一些属性方法,如assign()、fromEntries()、entries()、keys()、values()等。
Iterator的遍历过程:创建一个指针对象指向数据结构的起始位置,调用next()方法,指向成员,返回具有velue和done属性的对象,直到done为true表示结束。除了next()方法,还有报错调用的return()、配合Generator函数的throw()。
Iterator 的作用有三个:
- 为各种数据结构,提供一个统一的、简便的访问接口;
- 使得数据结构的成员能够按某种次序排列;
- ES6 创造了一种新的遍历命令for...of循环,Iterator 接口主要供for...of消费。
只要数据结构部署了Iterator就被认为可遍历,默认Itrator部署在Symbol.iterator属性上。具有Iterator的数据结构有Array、Map、Set、String、TypedArray、arguments、NodeList。除了这些数据结构,对象、数组、Map、Set调用对应的entries()、keys()、values()后会返回遍历器对象。
for...of与for...in的区别:
- for...in主要是针对对象设计,会遍历到手动添加的其他键值,甚至会遍历到原型链上的键值。遍历数组时键值是索引的字符串。
- for...of可以遍历所有具有Iterator的数据结构,对象本身不具备Iterator不能直接遍历,也就没有for...in的缺点。
for...of 用于遍历同步的Iterator,为了遍历异步Iterator,ES6增加了 for await...of。
7、异步与同步
众所周知JavaScript是单线程语言,就是一次只能执行一个任务,前一个任务执行完毕才去执行后一个任务。这个模式在设计上规避了一些问题,但也制造了新的麻烦。举例来说,我们用程序来描述做家务,当前有扫地、拖地、烧开水、泡茶。扫地、拖地不能同时执行(同步任务),然而我们期望,先烧开水,然后挂起这个任务去扫地、拖地(异步任务)。为了解决这个冲突充分利用CPU,于是出现了“事件循环”机制。
当然这不能改变JavaScript单线程运行的本质:运行时将同步任务压栈处理,异步任务放入消息队列,等执行栈任务为空再执行消息队列中的可执行的任务,如此形成事件循环。(ps:JavaScript只是在一个主线程上运行,还有其他线程进行辅助处理)
传统的异步解决方案有定时器、事件、回调等。值得一提的是0延迟:定时器延迟为0并不代表它就能马上执行,它同样会进入消息队列等待。定时器的执行取决于队列里待处理的任务数量,所以定时器并不是会守时。
在ES6中引入了新的解决方案Promise,它比传统的解决方案更强大更合理。拿回调回调来说,在逻辑复杂的情况下,通常会一层回调嵌套另一层回调形成成回调地狱,造成代码复杂不易维护。再拿事件来说,事件一旦触发,再监听是不能再次得到结果的。Promise也并不是完美的解决方案,也有缺点:Promise一旦创建就会立即执行就无法取消;有时Promise内部抛错不会传递到外部;当promise处于pending状态,无法得知目前处于哪个阶段。
const promise = new Promise(function(resolve, reject) {
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
Generator函数是ES6提供的另一种异步解决方案,它与传统的函数完全不同。从形式上它是一个普通函数使用function定义,但必须在function后跟一个星号。调用Generator函数会返回一个Iterator,也就是说需要执行next方法才能执行下一个状态,下一个状态在yield表达式或者return语句或者函数结束符停下。Generator函数也只有在next方法调用后才开始执行。
function* helloGenerator() {
yield 'hello';
yield 'generator';
return 'ending';
}
var hg= helloGenerator();
hg.next(); // {value:'hello',done:false}
hg.next(); // {value:'generator',done:false}
hg.next(); // {value:'ending',done:true}
hg.next(); // {value:undefined,done:true}
yield可以在Generator函数中多次使用,相当于多次return,但只能在Generator函数中使用,且必须是yield所在函数块。yield返回值是undefined,next方法可以带一个参数当作上一个yield的返回值。Generator函数中指针移动不会改变上下文,那么这个next传入的返回值可以缓存下来,不受yield间隔影响。也就是说,next()的执行不单纯是yield后边表达式的结果,要注意前后关联。
const arr = [1,2]
function* testGenerator () {
arr.forEach(() => {
yield 'test'; // SyntaxError
});
}
function* testYield (x) {
// yield不在=旁边须加()
const y = 4 + (yield);
yield y;
yield y + x;
}
const tg = testGenerator(4);
const tg1 = tg.next(1); // {value:undefined,done:false}
const tg2 = tg.next(2); // {value:6,done:false}
const tg3 = tg.next(3); // {value:10,done:false}
Generator函数运行时是Iterator,因此它可以使用for...of进行遍历不用next方法取到值,但是当next方法返回done为true时就会中止(即:return返回的值会被忽略)。
如果Generator函数内部调用一个Generator函数(也就是yield Iterator),内层遍历器对象不会执行。需要使用yield*,如return有返回值需要处理。另外,它也可以遍历其他具有Iterator部署的数据结构。
再来看看async函数,事实上它就是Generator函数的语法糖,星号换成了async关键字,yeild换成了await。只不过它粘了别的糖——内置执行器并将执行结果以Promise返回。async函数虽然是异步解决方案,但是await具有同步效果,await会等其后异步操作执行完成后才会接着执行后面的的操作。如果遇到错误就会中断后面的执行,所以在使用过程中需要注意错误的处理。如果不存在继发关系,最好同时触发。
async function getStockPriceByName(name) {
const symbol = await getStockSymbol(name);
const stockPrice = await getStockPrice(symbol);
return stockPrice;
}
getStockPriceByName('goog').then(function (result) {
console.log(result);
});
8、Reflection
Proxy用于修改某系操作的默认行为,属于一种元编程。
Reflect可以获取语言内部方法,使Object的操作更合理,与Proxy的方法一一对应完成默认行为。
9、Class定义一个类,这相当于一个语法糖,让语义更加清晰、写法更加简洁。使用static关键字定义静态方法,在新的提案中可以修饰属性。定义属性时将变量定义在类的顶层,可以不用在属性前加this。在新的提案中使用#表示,目前可以使用Symbol来定义,但是私有属性和方法过多就会显得十分笨重。还可在Class外部定义,这就与Class的设计有点尴尬。
// ES5
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.toString = function () {
return '(' + this.x + ', ' + this.y + ')';
};
// ES6
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
使用extends进行继承,这比ES5修改原型链清晰方便。super作为函数调用时代表父类构造函数,子类的构造函数必须一次super函数;作为对象时指向父类。
** 注意 **
使用class关键字构造的方法应特别注意this的指向:当使用static关键字时this指向构造函数;当使用用new关键字构造实例对象,this指向实例;当通过解构实例对象后的this是undefined;extends子类在super()调用父类之前不能使用this。
10、ES6新语法不止这些,还有模块体系、globalThis、ArrayBuffer等。还有一些尚是提案,比如管道运算符、数值分隔符、双冒号运算符、装饰器等。
比较
1、数组的遍历:数组的遍历方法有很多种,其中最容易想到的就是循环(while/for/for...of)。循环与其它方法相比,最突出的就是它可以被break/return打断,这里要对比的是其他方法。
在这之前要先说一个问题,之前有人问我map方法会改变原数组吗?我的回答是也许会,这得看怎么操作。当然也要看数组里是什么数据,基础数据类型肯定不会,对象就会。数组遍历的回调函数的参数是对数组每个对象的内存地址的引用,然后对参数修改就会影响原数组。所以,我认为不管哪种遍历方式都会是一样的结果,影响这个结果的还有操作手法。
这里还有一个情况,有时候我们需要一个数组副本,然后对副本进行操作。网上查深拷贝数组,最多的方法就是扩展运算符。事实上,这个方法不对,它的错和上面的差不多,扩展运算符对对象的拷贝是浅拷贝。因此,只要对数组副本操作,也会直接影响原数据。
数组遍历方法的区别主要在功能上:
- forEach() - 遍历数组中的每一个元素
- every() - 根据回调函数的条件,每个元素都满足返回true,否则返回false
- some() - 根据回调函数的条件,至少有一个满足返回true,否则返回false
- filter() - 满足回调函数的条件的元素放进新数组并返回
- map() - 返回由回调函数返回值组成的新数组
- reduce() - 从左到右执行回调函数,回调函数的返回值传给下一个回调,返回最后一次回调函数的值
- reduceRight() - 从右到左执行回调函数,回调函数的返回值传给下一个回调,返回最后一次回调函数的值
2、Map和Set已经作过比较,Set和Array区别主要表现在API上,接下来主要比较Object和Array、Map和Object。
Object Vs Array:
- Object是无序键值对,Array是有序值;
- Object是可以伪装成Array,但需要自己设置长度,Aarry不需要;
- Array有Iterator,Object需要手动部署。
Map Vs Object:
- Map没有默认键值,Object有一个原型链;
- Map键可以是任意类型,Object必须是String或Symbol;
- Map是有序的,Object是无序的;
- Map可以获取元素个数,Object只能手动计算;
- Map有Iterator,Object需要手动部署。
(ps:记得好像还有好些个,一下全忘了,后面想起了再加吧)
API
JavaScript的组成部分有两部分都是API:DOM和BOM。对于浏览器来说,API实在太多了:文档对象模型(Document Object Model)、设备APIs(Device APIs)、通信APIs(Communication APIs)、数据管理 APIs(Data management APIs)、数据管理 APIs(Data management APIs)等。下面从MDN上接了个图,就不细说了:
最后
这文章越往后写越心虚的要紧,越写越觉得自己对于JavaScript了解得不够,尤其是每次看完阮一峰的书之后,感觉自己一直翻来覆去在和语法打架。还有好多没涉及到:Web存储、Web SQL、应用程序缓存、Web Workers、SSE、WebSocket、WebAssembly、HTTP……快数不过来了……
接下来还有很长的路要走,前端工程化:前端变革主导者Node;动态样式SCSS/LESS;前端三大框架VUE/React/Angular;打包工具Webpack/Grunt;单元测试Jest/Mocha;可以编译成JavaScript的TypeScript/Dart;Hybird开发框架Flutter/UniApp;小程序框架Wepy/Taro……
当然,不能再以这样的状态和思路去写文章,毫无意义。且行且看且折腾吧!
来源:oschina
链接:https://my.oschina.net/ly81/blog/4479600