本篇记录部分es6相关内容和我的理解。javascript
======================相关文章和开源库=======================前端
系列文章vue
1.前端知识梳理-HTML,CSS篇
java
2.前端知识梳理-ES5篇
node
3.前端知识梳理-ES6篇
es6
4.前端知识梳理-VUE篇
sql
我的维护的开源组件库编程
1.bin-ui,一个基于vue的pc端组件库json
2.树形组织结构组件
数组
3.bin-admin ,基于bin-ui的后台管理系统集成方案
4.bin-data ,基于bin-ui和echarts的数据可视化框架
5.其他生态连接
========================================================
var 会变量提高;let 声明的变量只在它所在的代码块有效;
它们是继 var 以后, 新的变量定义方法。与 let 相比,const 更容易被理解,用于定义常量,即不可变量
没有块级做用域。这个问题之因此为人所熟知,是由于它引起了诸如历遍监听事件须要使用闭包解决等问题。
<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)
}
// ...
复制代码
继 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
}
// ...
复制代码
它的出现可让很是多的字符串使用变得尤其轻松。
模板字符串要求使用 ` 代替本来的单/双引号来包裹字符串内容。它有两大特色:
支持变量注入
模板字符串之因此称之为“模板”,就是由于它容许咱们在字符串中引用外部变量,而不须要像以往须要不断地相加、相加、相加……
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; `
复制代码
方法属性省略 function
这个新特性能够算是比较有用但并非很显眼的一个。
let obj = {
// before
foo: function() {
return 'foo'
},
// after
bar() {
return 'bar'
}
}
复制代码
来了来了来了,至关有用的一个特性。
// 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})
复制代码
这个特性有很是高的使用频率,一个简单的语法糖解决了从前须要一两行代码才能实现的功能。
默认参数值
这个特性在类库开发中至关有用,好比实现一些可选参数:
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
复制代码
ES2015 以前,JavaScript 中有哪些基本的数据结构。
其中又分为值类型和引用类型,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
复制代码
类,做为自 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)等的概念
在 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()复制代码
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('出错了');
});
复制代码