2019.12.19
1177
fish
ECMAScript是一种由Ecma国际(前身为欧洲计算机制造商协会)在标准ECMA-262中定义的脚本语言规范。这种语言在万维网上应用广泛,它往往被称为JavaScript或JScript,但实际上后两者是ECMA-262标准的实现和扩展。
至发稿日为止有九个ECMA-262版本发表。其历史版本如下:
TC39(Technical Committee 39)是一个推动JavaScript发展的委员会,它的成语来自各个主流浏览器的代表成语。会议实行多数决,每一项决策只有大部分人同意且没有强烈反对才能去实现。
TC39成员制定着ECMAScript的未来。
每一项新特性最终要进入到ECMAScript规范里,需要经历5个阶段,这5个阶段如下:
Stage 0: Strawperson
只要是TC39成员或者贡献者,都可以提交想法
Stage 1: Proposal
这个阶段确定一个正式的提案
Stage 2: draft
规范的第一个版本,进入此阶段的提案大概率会成为标准
Stage 3: Candidate
进一步完善提案细则
Stage 4: Finished
表示已准备好将其添加到正式的ECMAScript标准中
由于ES6以前的属性诞生年底久远,我们使用也比较普遍,遂不进行说明,ES6之后的语言风格跟ES5以前的差异比较大,所以单独拎出来做个记录。
ES6是一次重大的革新,比起过去的版本,改动比较大,本文仅对常用的API以及语法糖进行讲解。
在ES6以前,JS只有var一种声明方式,但是在ES6之后,就多了let跟const这两种方式。用var定义的变量没有块级作用域的概念,而let跟const则会有,因为这三个关键字创建是不一样的。
区别如下:
{ var a = 10 let b = 20 const c = 30 }
a // 10 b // Uncaught ReferenceError: b is not defined c // c is not defined let d = 40 const e = 50 d = 60 d // 60 e = 70 // VM231:1 Uncaught TypeError: Assignment to constant variable. 复制代码
var | let | const | |
---|---|---|---|
变量提升 | √ | × | × |
全局变量 | √ | × | × |
重复声明 | √ | × | × |
重新赋值 | √ | √ | × |
暂时死区 | × | √ | √ |
块作用域 | × | √ | √ |
只声明不初始化 | √ | √ | × |
在ES6之前,如果我们要生成一个实例对象,传统的方法就是写一个构造函数,例子如下:
function Person(name, age) { this.name = name this.age = age
}
Person.prototype.information = function () { return 'My name is ' + this.name + ', I am ' + this.age
} 复制代码
但是在ES6之后,我们只需要写成以下形式:
class Person { constructor(name, age) { this.name = name this.age = age
}
information() { return 'My name is ' + this.name + ', I am ' + this.age
}
} 复制代码
箭头函数表达式的语法比函数表达式更简洁,并且没有自己的this,arguments,super或new.target。这些函数表达式更适用于那些本来需要匿名函数的地方,并且它们不能用作构造函数。
在ES6以前,我们写函数一般是:
var list = [1, 2, 3, 4, 5, 6, 7] var newList = list.map(function (item) { return item * item
}) 复制代码
但是在ES6里,我们可以:
const list = [1, 2, 3, 4, 5, 6, 7] const newList = list.map(item => item * item) 复制代码
看,是不是简洁了不少
在ES6之前,如果我们写函数需要定义初始值的时候,需要这么写:
function config (data) { var data = data || 'data is empty' } 复制代码
这样看起来也没有问题,但是如果参数的布尔值为falsy时就会出问题,例如我们这样调用config:
config(0)
config('') 复制代码
那么结果就永远是后面的值
如果我们用函数参数默认值就没有这个问题,写法如下:
const config = (data = 'data is empty') => {} 复制代码
在ES6之前,如果我们要拼接字符串,则需要像这样:
var name = 'kris' var age = 24 var info = 'My name is ' + this.name + ', I am ' + this.age 复制代码
但是在ES6之后,我们只需要写成以下形式:
const name = 'kris' const age = 24 const info = `My name is ${name}, I am ${age}` 复制代码
我们通过解构赋值, 可以将属性/值从对象/数组中取出,赋值给其他变量。
比如我们需要交换两个变量的值,在ES6之前我们可能需要:
var a = 10 var b = 20 var temp = a
a = b
b = temp 复制代码
但是在ES6里,我们有:
let a = 10 let b = 20 [a, b] = [b, a] 复制代码
是不是方便很多
在ES6之前,JS并没有模块化的概念,有的也只是社区定制的类似CommonJS和AMD之类的规则。例如基于CommonJS的NodeJS:
// circle.js // 输出 const { PI } = Math exports.area = (r) => PI * r ** 2 exports.circumference = (r) => 2 * PI * r // index.js // 输入 const circle = require('./circle.js') console.log(`半径为 4 的圆的面积是 ${circle.area(4)}`) 复制代码
在ES6之后我们则可以写成以下形式:
// circle.js // 输出 const { PI } = Math export const area = (r) => PI * r ** 2 export const circumference = (r) => 2 * PI * r // index.js // 输入 import {
area
} = './circle.js' console.log(`半径为 4 的圆的面积是: ${area(4)}`) 复制代码
扩展操作符可以在函数调用/数组构造时, 将数组表达式或者string在语法层面展开;还可以在构造字面量对象时, 将对象表达式按key-value的方式展开。
比如在ES5的时候,我们要对一个数组的元素进行相加,在不使用reduce或者reduceRight的场合,我们需要:
function sum(x, y, z) { return x + y + z;
} var list = [5, 6, 7] var total = sum.apply(null, list) 复制代码
但是如果我们使用扩展操作符,只需要如下:
const sum = (x, y, z) => x + y + z const list = [5, 6, 7] const total = sum(...list) 复制代码
非常的简单,但是要注意的是扩展操作符只能用于可迭代对象
如果是下面的情况,是会报错的:
var obj = {'key1': 'value1'} var array = [...obj] // TypeError: obj is not iterable 复制代码
在ES6之前,如果我们要将某个变量赋值为同样名称的对象元素,则需要:
var cat = 'Miaow' var dog = 'Woof' var bird = 'Peet peet' var someObject = { cat: cat, dog: dog, bird: bird
} 复制代码
但是在ES6里我们就方便很多:
let cat = 'Miaow' let dog = 'Woof' let bird = 'Peet peet' let someObject = {
cat,
dog,
bird
} console.log(someObject) //{ // cat: "Miaow", // dog: "Woof", // bird: "Peet peet" //} 复制代码
非常方便
Promise 是ES6提供的一种异步解决方案,比回调函数更加清晰明了。
Promise翻译过来就是承诺的意思,这个承诺会在未来有一个确切的答复,并且该承诺有三种状态,分别是:
这个承诺一旦从等待状态变成为其他状态就永远不能更改状态了,也就是说一旦状态变为 resolved 后,就不能再次改变
new Promise((resolve, reject) => {
resolve('success') // 无效 reject('reject')
}) 复制代码
当我们在构造Promise的时候,构造函数内部的代码是立即执行的
new Promise((resolve, reject) => { console.log('new Promise')
resolve('success')
}) console.log('finifsh') // new Promise -> finifsh 复制代码
Promise实现了链式调用,也就是说每次调用then之后返回的都是一个Promise,并且是一个全新的Promise,原因也是因为状态不可变。如果你在then中 使用了return,那么return的值会被Promise.resolve()包装
Promise.resolve(1)
.then(res => { console.log(res) // => 1 return 2 // 包装成 Promise.resolve(2) })
.then(res => { console.log(res) // => 2 }) 复制代码
当然了,Promise也很好地解决了回调地狱的问题,例如:
ajax(url, () => { // 处理逻辑 ajax(url1, () => { // 处理逻辑 ajax(url2, () => { // 处理逻辑 })
})
}) 复制代码
可以改写成:
ajax(url)
.then(res => { console.log(res) return ajax(url1)
}).then(res => { console.log(res) return ajax(url2)
}).then(res => console.log(res)) 复制代码
for...of语句在可迭代对象(包括Array,Map,Set,String,TypedArray,arguments对象等等)上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句。
例子如下:
const array1 = ['a', 'b', 'c']; for (const element of array1) { console.log(element)
} // "a" // "b" // "c" 复制代码
symbol 是一种基本数据类型,Symbol()函数会返回symbol类型的值,该类型具有静态属性和静态方法。它的静态属性会暴露几个内建的成员对象;它的静态方法会暴露全局的symbol注册,且类似于内建对象类,但作为构造函数来说它并不完整,因为它不支持语法:"new Symbol()"。
每个从Symbol()返回的symbol值都是唯一的。一个symbol值能作为对象属性的标识符;这是该数据类型仅有的目的。
例子如下:
const symbol1 = Symbol(); const symbol2 = Symbol(42); const symbol3 = Symbol('foo'); console.log(typeof symbol1); // "symbol" console.log(symbol3.toString()); // "Symbol(foo)" console.log(Symbol('foo') === Symbol('foo')); // false 复制代码
迭代器(Iterator)是一种迭代的机制,为各种不同的数据结构提供统一的访问机制。任何数据结构只要内部有 Iterator 接口,就可以完成依次迭代操作。
一旦创建,迭代器对象可以通过重复调用next()显式地迭代,从而获取该对象每一级的值,直到迭代完,返回{ value: undefined, done: true }
虽然自定义的迭代器是一个有用的工具,但由于需要显式地维护其内部状态,因此需要谨慎地创建。生成器函数提供了一个强大的选择:它允许你定义一个包含自有迭代算法的函数, 同时它可以自动维护自己的状态。 生成器函数使用 function*语法编写。 最初调用时,生成器函数不执行任何代码,而是返回一种称为Generator的迭代器。 通过调用生成器的下一个方法消耗值时,Generator函数将执行,直到遇到yield关键字。
可以根据需要多次调用该函数,并且每次都返回一个新的Generator,但每个Generator只能迭代一次。
所以我们可以有以下例子:
function* makeRangeIterator(start = 0, end = Infinity, step = 1) { for (let i = start; i < end; i += step) { yield i;
}
} var a = makeRangeIterator(1,10,2)
a.next() // {value: 1, done: false} a.next() // {value: 3, done: false} a.next() // {value: 5, done: false} a.next() // {value: 7, done: false} a.next() // {value: 9, done: false} a.next() // {value: undefined, done: true} 复制代码
Set对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用。
所以我们可以通过Set实现数组去重
const numbers = [2,3,4,4,2,3,3,4,4,5,5,6,6,7,5,32,3,4,5] console.log([...new Set(numbers)]) // [2, 3, 4, 5, 6, 7, 32] 复制代码
WeakSet结构与Set类似,但区别有以下两点:
所以代码如下:
var ws = new WeakSet() var obj = {} var foo = {}
ws.add(window)
ws.add(obj)
ws.has(window) // true ws.has(foo) // false, 对象 foo 并没有被添加进 ws 中 ws.delete(window) // 从集合中删除 window 对象 ws.has(window) // false, window 对象已经被删除了 ws.clear() // 清空整个 WeakSet 对象 复制代码
Map对象保存键值对。任何值(对象或者原始值) 都可以作为一个键或一个值。
例子如下,我们甚至可以使用NaN来作为键值:
var myMap = new Map();
myMap.set(NaN, "not a number");
myMap.get(NaN); // "not a number" var otherNaN = Number("foo");
myMap.get(otherNaN); // "not a number" 复制代码
WeakMap对象是一组键/值对的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意的。
跟Map的区别与Set跟WeakSet的区别相似,具体代码如下:
var wm1 = new WeakMap(),
wm2 = new WeakMap(),
wm3 = new WeakMap(); var o1 = {},
o2 = function(){},
o3 = window;
wm1.set(o1, 37);
wm1.set(o2, "azerty");
wm2.set(o1, o2); // value可以是任意值,包括一个对象 wm2.set(o3, undefined);
wm2.set(wm1, wm2); // 键和值可以是任意对象,甚至另外一个WeakMap对象 wm1.get(o2); // "azerty" wm2.get(o2); // undefined,wm2中没有o2这个键 wm2.get(o3); // undefined,值就是undefined wm1.has(o2); // true wm2.has(o2); // false wm2.has(o3); // true (即使值是undefined) wm3.set(o1, 37);
wm3.get(o1); // 37 wm3.clear();
wm3.get(o1); // undefined,wm3已被清空 wm1.has(o1); // true wm1.delete(o1);
wm1.has(o1); // false 复制代码
Proxy对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)。
Reflect是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与Proxy的方法相同。Reflect不是一个函数对象,因此它是不可构造的。
Proxy跟Reflect是非常完美的配合,例子如下:
const observe = (data, callback) => { return new Proxy(data, {
get(target, key) { return Reflect.get(target, key)
},
set(target, key, value, proxy) {
callback(key, value);
target[key] = value; return Reflect.set(target, key, value, proxy)
}
})
} const FooBar = { open: false }; const FooBarObserver = observe(FooBar, (property, value) => {
property === 'open' && value
? console.log('FooBar is open!!!')
: console.log('keep waiting');
}); console.log(FooBarObserver.open) // false FooBarObserver.open = true // FooBar is open!!! 复制代码
当然也不是什么都可以被代理的,如果对象带有configurable: false跟writable: false属性,则代理失效。
i修饰符
// i 修饰符 /[a-z]/i.test('\u212A') // false /[a-z]/iu.test('\u212A') // true 复制代码
y修饰符
// y修饰符 var s = 'aaa_aa_a'; var r1 = /a+/g; var r2 = /a+/y;
r1.exec(s) // ["aaa"] r2.exec(s) // ["aaa"] r1.exec(s) // ["aa"] r2.exec(s) // null 复制代码
String.prototype.flags
// 查看RegExp构造函数的修饰符 var regex = new RegExp('xyz', 'i')
regex.flags // 'i' 复制代码
unicode模式
var s = '
2016.05.19
3074
微信扫一扫(公测)开放商品条码、二维码(一物一码)的连接能力。 品牌开通该功能后,可自主编辑商品主页,维护商品信息,提供相关服务,进行用户管理和数据管理。