本篇记录部分es6相关内容和个人理解。
======================相关文章和开源库=======================
系列文章
个人维护的开源组件库
2.树形组织结构组件
3.bin-admin ,基于bin-ui的后台管理系统集成方案
4.bin-data ,基于bin-ui和echarts的数据可视化框架
5.其余生态链接
========================================================
1、let,const
var 会变量提升;let 声明的变量只在它所在的代码块有效;
它们是继 var 之后, 新的变量定义方法。与 let 相比,const 更容易被理解,用于定义常量,即不可变量
2、块级作用域
没有块级作用域。这个问题之所以为人所熟知,是因为它引发了诸如历遍监听事件需要使用闭包解决等问题。
<button>一</button>
<button>二</button>
<button>三</button>
<button>四</button>
<div id="output"></div>
<script>
var buttons = document.querySelectorAll('button')
var output = document.querySelector('#output')
for (var i = 0; i < buttons.length; i++) {
buttons[i].addEventListener('click', function() {
output.innerText = buttons[i].innerText
})
}
</script>
从直观的角度看这段代码并没有语义上的错误,但是当我们点击任意一个按钮时,就会报出这样的错误信息:
出现这个错误的原因是因为 buttons[i] 不存在,即为 undefined。
为什么会出现按钮不存在结果呢?通过排查,我们可以发现,每次我们点击按钮时,事件监听回调函数中得到的变量 i 都会等于 buttons.length, 也就是这里的 4。而 buttons[4] 恰恰不存在,所以导致了错误的发生。
再而导致 i 得到的值都是 buttons.length 的原因就是因为 JavaScript 中没有块级作用域,而使对 i 的变量引用(Reference)一直保持在上一层作用域(循环语句所在层)上, 而当循环结束时 i 则正好是buttons.length。
而在 ES6 中,我们只需做出一个小小的改动,便可以解决该问题(假设所使用的浏览器已经支持所需要的特性):
for (/* var */ let i = 0; i < buttons.length; i++) {
// ...
}
通过把 for 语句中对计数器 i 的定义语句从 var 换成 let,即可。因为 let 语句会使该变量处于一个块级作用域中, 从而让事件监听回调函数中的变量引用得到保持。我们不妨看看改进后的代码经过 babel 的编译会变成什么样子:
// ...
var _loop = function (i) {
buttons[i].addEventListener('click', function () {
output.innerText = buttons[i].innerText
})
}
for (var i = 0; i < buttons.length; i++) {
_loop(i)
}
// ...
3、箭头函数(Arrow Function)
继 let 和 const 之后,箭头函数就是使用率最高的新特性了。
箭头函数,顾名思义便是使用箭头(=>)进行定义的函数,属于匿名函数(Lambda)一类。
箭头函数有好几种使用语法:
// 1.means return `foo + ' world'`
foo => foo + ' world'
// 2.
(foo, bar) => foo + bar
// 3.
foo => {
return foo + ' world'
}
// 4.
(foo, bar) => {
return foo + bar
}
箭头函数与上下文绑定
事实上,箭头函数在 ES2015 标准中,并不只是作为一种新的语法出现。用于对函数内部的上下文 (this)绑定为定义函数所在的作用域的上下文。
let obj = {
hello: 'world',
foo() {
let bar = () => {
return this.hello
}
return bar
}
}
window.hello = 'ES6'
window.bar = obj.foo()
window.bar() //=> 'world'
上面代码中的 obj.foo 等价于:
// ...
foo(){
let bar = (function() {
return this.hello
}).bind(this)
return bar
}
// ...
4、模板字符串
它的出现可以让非常多的字符串使用变得尤为轻松。
模板字符串要求使用 ` 代替原本的单/双引号来包裹字符串内容。它有两大特点:
- 支持变量注入
- 支持换行
支持变量注入
模板字符串之所以称之为“模板”,就是因为它允许我们在字符串中引用外部变量,而不需要像以往需要不断地相加、相加、相加……
let name = 'Will Wen Gunn'
let title = 'Founder'
let company = 'LikMoon Creation'
let greet = `Hi, I'm ${name}, I am the ${title} at ${company}`
支持换行
在 Node.js 中,如果我们没有支持换行的模板字符串,若需要拼接一条SQL,则很有可能是这样的:
var sql =
"SELECT * FROM Users " +
"WHERE FirstName='Mike' " +
"LIMIT 5;"
或者是这样的:
var sql = [
"SELECT * FROM Users",
"WHERE FirstName='Mike'",
"LIMIT 5;"
].join(' ')
无论是上面的哪一种,都会让我们感到很不爽。但若使用模板字符串,仿佛打开了新世界的大门~
let sql = `
SELECT * FROM Users
WHERE FirstName='Mike'
LIMIT 5;
`
5、对象字面量扩展语法
方法属性省略 function
这个新特性可以算是比较有用但并不是很显眼的一个。
let obj = {
// before
foo: function() {
return 'foo'
},
// after
bar() {
return 'bar'
}
}
6、表达式解构
来了来了来了,相当有用的一个特性。
// Matching with object
function search(query) {
// ...
// let users = [ ... ]
// let posts = [ ... ]
// ...
return {
users: users,
posts: posts
}
}
let { users, posts } = search('iwillwen')
// Matching with array
let [ x, y ] = [ 1, 2 ]
// missing one
[ x, ,y ] = [ 1, 2, 3 ]
function g({name: x}) {
console.log(x)
}
g({name: 5})
7、函数参数表达、传参
这个特性有非常高的使用频率,一个简单的语法糖解决了从前需要一两行代码才能实现的功能。
默认参数值
这个特性在类库开发中相当有用,比如实现一些可选参数:
import fs from 'fs'
import readline from 'readline'
import path from 'path'
function readLineInFile(filename, callback = noop, complete = noop) {
let rl = readline.createInterface({
input: fs.createReadStream(path.resolve(__dirname, filename))
})
rl.on('line', line => {
//... do something with the current line
callback(line)
})
rl.on('close', complete)
return rl
}
function noop() { return false }
readLineInFile('big_file.txt', line => {
// ...
})
后续参数
我们知道,函数的 call 和 apply 在使用上的最大差异便是一个在首参数后传入各个参数,一个是在首参数后传入一个包含所有参数的数组。 如果我们在实现某些函数或方法时,也希望实现像 call 一样的使用方法,在 ES2015 之前,我们可能需要这样做:
function fetchSomethings() {
var args = [].slice.apply(arguments)
// ...
}
function doSomeOthers(name) {
var args = [].slice.apply(arguments, 1)
// ...
}
而在 ES2015 中,我们可以很简单的使用 … 语法糖来实现:
function fetchSomethings(...args) {
// ...
}
function doSomeOthers(name, ...args) {
// ...
}
要注意的是,...args
后不可再添加
虽然从语言角度看,arguments
和 ...args
是可以同时使用 ,但有一个特殊情况则不可:arguments
在箭头函数中, 会跟随上下文绑定到上层,所以在不确定上下文绑定结果的情况下,尽可能不要再箭头函数中再使用 arguments
,而使用 ...args
。
虽然 ECMA 委员会和各类编译器都无强制性要求用 ...args
代替 arguments
,但从实践经验看来,...args
确实可以在绝大部份场景下可以代替 arguments 使用, 除非你有很特殊的场景需要使用到 arguments.callee
和 arguments.caller
。所以我推荐都使用 ...args
而非 arguments
。
解构传参
在 ES2015 中,... 语法还有另外一个功能:无上下文绑定的 apply。什么意思?看看代码你就知道了。
function sum(...args) {
return args.map(Number)
.reduce((a, b) => a + b)
}
console.log(sum(...[1, 2, 3])) //=> 6
8、新的数据结构
ES2015 之前,JavaScript 中有哪些基本的数据结构。
- String 字符串
- Number 数字(包含整型和浮点型)
- Boolean 布尔值
- Object 对象
- Array 数组
其中又分为值类型和引用类型,Array 其实是 Object 的一种子类。
Set 和 WeakSet
在 ES2015 中,ECMA 委员会为 ECMAScript 增添了集(Set)和“弱”集(WeakSet)。它们都具有元素唯一性,若添加了已存在的元素,会被自动忽略。
let s = new Set()
s.add('hello').add('world').add('hello')
console.log(s.size) //=> 2
console.log(s.has('hello')) //=> true
在实际开发中,我们有很多需要用到集的场景,如搜索、索引建立等。
WeakSet 在 JavaScript 底层作出调整(在非降级兼容的情况下),检查元素的变量引用情况。如果元素的引用已被全部解除,则该元素就会被删除, 以节省内存空间。这意味著无法直接加入数字或者字符串。另外 WeakSet 对元素有严格要求,必须是 Object,当然了,你也可以用 new String('...') 等形式处理元素。
let weaks = new WeakSet()
weaks.add("hello") //=> Error
weaks.add(3.1415) //=> Error
let foo = new String("bar")
let pi = new Number(3.1415)
weaks.add(foo)
weaks.add(pi)
weaks.has(foo) //=> true
foo = null
weaks.has(foo) //=> false
Map 和 WeakMap
从数据结构的角度来说,映射(Map)跟原本的 Object 非常相似,都是 Key/Value 的键值对结构。 但是 Object 有一个让人非常不爽的限制:key 必须是字符串或数字。在一般情况下,我们并不会遇上这一限制,但若我们需要建立一个对象映射表时,这一限制显得尤为棘手。
而 Map 则解决了这一问题,可以使用任何对象作为其 key,这可以实现从前不能实现或难以实现的功能,如在项目逻辑层实现数据索引等。
map.set(object, 'hello')
map.set('hello', 'world')
map.has(object) //=> true
map.get(object) //=> hello
而 WeakMap 和 WeakSet 很类似,只不过 WeakMap 的键和值都会检查变量引用,只要其一的引用全被解除,该键值对就会被删除。
let weakm = new WeakMap()
let keyObject = { id: 1 }
let valObject = { score: 100 }
weakm.set(keyObject, valObject)
weakm.get(keyObject) //=> { score: 100 }
keyObject = null
weakm.has(keyObject) //=> false
9、类(Classes)
类,作为自 JavaScript 诞生以来最大的痛点之一,终于在 ES2015 中得到了官方的妥协,“实现”了 ECMAScript 中的标准类机制。 为什么是带有双引号的呢?因为我们不难发现这样一个现象:
// $ node
> class Foo {}
// [Function: Foo]
回想一下在 ES2015 以前的时代中,我们是怎么在 JavaScript 中实现类的?
function Foo() {}
var foo = new Foo()
是的,ES6 中的类只是一种语法糖,用于定义原型(Prototype)的。
语法
与大多数人所期待的一样,ES2015 所带来的类语法确实与很多 C 语言家族的语法相似。
class Person {
constructor(name, gender, age) {
this.name = name
this.gender = gender
this.age = age
}
isAdult() {
return this.age >= 18
}
}
let me = new Person('iwillwen', 'man', 19)
console.log(me.isAdult()) //=> true
与 JavaScript 中的对象字面量不一样的是,类的属性后不能加逗号,而对象字面量则必须要加逗号。
然而,让人很不爽的是,ES2015 中对类的定义依然不支持默认属性的语法:
// 理想型 但是没实现
class Person {
name: String
gender = 'man'
// .
}
继承
ES2015 的类继承总算是为 JavaScript 类继承之争抛下了一根定海神针了。
class Animal {
yell() {
console.log('yell')
}
}
class Person extends Animal {
constructor(name, gender, age) {
super() // must call `super` before using `this` if this class has a superclass
this.name = name
this.gender = gender
this.age = age
}
isAdult() {
return this.age >= 18
}
}
class Man extends Person {
constructor(name, age) {
super(name, 'man', age)
}
}
let me = new Man('iwillwen', 19)
console.log(me.isAdult()) //=> true
me.yell()
同样的,继承的语法跟许多语言中的很类似,ES2015 中若要是一个类继承于另外一个类而作为其子类,只需要在子类的名字后面加上 extends {SuperClass}
即可。
ES2015 中的类机制支持 static 类型的方法定义,比如说 Man 是一个类,而我希望为其定义一个 Man.isMan() 方法以用于类型检查,我们可以这样做:
class Man {
// ...
static isMan(obj) {
return obj instanceof Man
}
}
let me = new Man()
console.log(Man.isMan(me)) //=> true
就目前来说,ES2015 的类机制依然很鸡肋:
1.不支持私有属性(private)
2.不支持前置属性定义,但可用 get 语句和 set 语句实现
3.不支持多重继承
4.没有类似于协议(Protocl)或接口(Interface)等的概念
10、原生的模块化
在 JavaScript 的发展历史上,曾出现过多种模块加载库,如 RequireJS、SeaJS、FIS 等,而由它们衍生出来的 JavaScript 模块化标准有 CommonJS、AMD、CMD 和 UMD 等。
其中最为典型的是 Node.js 所遵循的 CommonJS 和 RequireJS 的 AMD。
基本用法
import name from "module-name"
import * as name from "module-name"
import { member } from "module-name"
import { member as alias } from "module-name"
import { member1 , member2 } from "module-name"
import { member1 , member2 as alias2 , [...] } from "module-name"
import defaultMember, { member [ , [...] ] } from "module-name"
import defaultMember, * as alias from "module-name"
如上所示,ES2015 中有很多种模块引入方式,我们可以根据实际需要选择一种使用。
全局引入
全局引入是最基本的引入方式,这跟 CommonJS、AMD 等模块化标准并无两样,都是把目标模块的所有暴露的接口引入到一个命名空间中。
import name from 'module-name'
import * as name from 'module-name'
这跟 Node.js 所用的 CommonJS 类似:
var name = require('module-name')
局部引入
与 CommonJS 等标准不同的是,ES2015 的模块引入机制支持引入模块的部份暴露接口,这在大型的组件开发中显得尤为方便,如 React 的组件引入便是使用了该特性。
import { A, B, C } from 'module-name'
A()
B()
C()
接口暴露
ES2015 的接口暴露方式比 CommonJS 等标准都要丰富和健壮,可见 ECMA 委员会对这一部份的重视程度之高。
ES2015 的接口暴露有几种用法:
暴露单独接口
// module.js
export function method() { /* ... */ }
基本的 export 语句可以调用多次,单独使用可暴露一个对象到该模块外。
暴露复盖模块
若需要实现像 CommonJS 中的 module.exports = {} 以覆盖整个模块的暴露对象,则需要在 export 语句后加上 default。
// module.js
export default {
method1,
method2
}
// main.js
import M from './module'
M.method1()
11、Promise
Promise的含义
Promise是异步编程的一种解决方案,比传统的解决方案(回调函数和事件)更合理更强大。
所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件 (通常是一个异步操作)的结果。从语法上说,Promise是一个对象,从它可以获取异步操作的消息。
Promise对象有以下2个特点:
1.对象的状态不受外界影响。Promise
对象代表一个异步操作,有三种状态:Pending
(进行中)、 Resolved
(已完成)和Rejected
(已失败)。只有异步操作的结果,可以决定当前是哪一种状态, 任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”, 表示其他手段无法改变。
2.一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise
对象的状态改变,只有两种可能: 从Pending
变为Resolved
;从Pending
变为Rejected
。只要这两种情况发生,状态就凝固了,不会再变了, 会一直保持这个结果。就算改变已经发生了,你再对Promise
对象田静回调函数,也会立即得到这个结果。 这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
有了Promise对象,就可以把异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外, Promise对象提供了统一的接口,使得控制异步操作更加容易。
基本用法
ES6规定,Promise对象是一个构造函数,用来生成Promise实例
var promise = new Promise(function(resolve,reject){
// ... some code
if(/* 异步操作成功 */){
resolve(value);
}else{
reject(error);
}
});
1
Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve
和reject
。它们是两个函数,由JavaScript引擎提供,不是自己部署。
2
resolve
函数的作用,将Promise对象的状态从“未完成”变成“成功”(即从Pending变为Resolved),在异步操 作成功时调用,并将异步操作的结果,作为参数传递出去;
3
reject
函数的作用是,在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
Promise实例生成以后,可以用then方法分别制定Resolved状态和Rejected状态的回调函数:
promise.then(function(value){
// sucess
},function(error){
// failure
});
then方法可以接受2个回调函数作为参数,第二个函数是可选的,不一定要提供。这两个函数都接受Promise对象传出的值作为参数。
下面是一个Promise对象的简单例子:
function timeout(ms){
return new Promise((resolve,reject)=>{
setTimeout(resolve,ms,'done');
});
}
timeout(100).then((value)=>{
console.log(value);
});
上面代码中,timeout方法返回一个Promise实例,表示一段事件以后才会发生的结果。过了指定的时间(ms参数)以后,Promise实例的状态变为Resolved, 就会触发then方法绑定的回调函数。
Promise新建后就会立即执行
let promise = new Promise(function(resolve,rejeact){
console.log('Promise');
resolve();
});
promise.then(function(){
console.log('Resolved');
});
console.log('Hi');
// Promise
// Hi
// Resolved
上面代码中,Promise新建后立即执行,所以首先输出的是”Promise”,然后then方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行,所以”Resolved”最后输出。
下面是异步加载图片的例子:
function loadImageAsync(url){
return new Promise(function(resolve,reject){
var image = new Image();
image.onload = function(){
resolve(image);
};
image.onerror = function(){
reject(new Error('Could not load image at' + url));
};
image.src = url;
});
}
下面是一个用Promise对象实现Ajax操作的例子:
var getJSON = function(url){
var promise = new Promise(function(resolve,reject){
var client = new XMLHttpRequest();
client.open('GET',url);
client.onreadystatechange = handler;
client.responseType = 'json';
client.setRequestHeader('Accept','application/json');
client.send();
function handler(){
if(this.readyState !== 4){
return;
}
if(this.status === 200){
resolve(this.response);
}else{
reject(new Error(this.statusText));
}
}
});
return promise;
};
getJSON('/posts.jons').then(function(json){
consoloe.log(json);
},function(error){
console.log('出错了');
});