如果将JavaScript比作英语,那么ECMAScript标准可以理解为美式英语,TypeScript可理解为英式英语。
ES5于2009年发布;ES6于2015年发布,也称ES2015;ES7于2016年发布。
下面会简述几个版本中重要的地方。
ES5
内容主要包括严格模式、JSON对象、新增Object接口、新增Array接口和Function.prototype.bind。最重要的一条可能就是严格模式的提出。
严格模式
严格模式限制了不规范的写法,让不合理的语法直接报错,提高了代码安全性与规范性。
列举几个会报错的语法/写法:
● 为声明全局变量赋值 : ReferenceError
● with、eval语句:SyntaxError
● arguments.callee()和arguments.caller():Uncaught ReferenceError > ● 函数参数重名:Uncaught SyntaxError
● 不建议使用arguments变量 : Uncaught SyntaxError
JSON
json对象解析不是伴随着JavaScript的出现而产生的。在IE8更低的版本的浏览器中不能直接使用JSON解析方法。
不过,可以在浏览器中添加es5-shim.js来增加浏览器对ES5功能的支持。
Functiion.prototype.bind
ES中bind()方法比较常用。bind()方法会创建一个新函数——绑定函数。
当调用绑定函数,绑定函数会以创建它时传入bind()方法的第一个参数作为this,以传入bind()方法的第二个以及以后的参数和绑定函数运行时本身的参数按照顺序作为原函数的参数来调用。
const fun = function(param) { console.log(this+ ':' + param) }; let funNew = fun.bind('name','zhang'); funNew(); // 'name:zhang'
ES6
关于es6->es5的方法,附上webpack方式的链接
以及你在运行过程中可能会遇上的坑
ES6对ES5进行了补充于增强,形成了一个完整的特性集合。
例如,
● 字符串模板、集合、箭头函数、Promise、for…of循环等均是借鉴其他语言的优秀特性;
● class类和import/export模块规范 是对原有ES标准缺失特性的补充;
● 迭代器、生成器、解构赋值、函数参数等 是对原有标准特性的增强;
块级作用域变量声明关键字 let、const
let a =1; const b = 'hello'; var A = 2; { let c ='c'; const d; //uncaught syntaxError } console.log(c); //uncaught ReferenceError : c is nor derined 作用域问题 b = 'world' ;// Uncaught TypeError : const声明的变量不能再次赋值 console.log(window.a || global.a) ; undefined let、const在全局作用域下声明的变量不会作为属性添加到全局作用域对象里面 console.log(window.A || global.A);//2
几点注意:
● let、const都只能作为块级作用域变量的声明,且只能在会计作用域内生效;
● const声明的变量必须进行初始化,不能被再次修改赋值;
● let、const在全局作用域下声明的变量不会作为属性添加到全局作用域对象里面,与var不同;
● let、const的执行速度比var快65%左右;
使用场景:
● 模块内不变的引用和常量,一般使用const;
● 可变的变量或引用使用let声明;
● var仅用于声明函数整个作用域内需要使用的变量;
字符串模板
当有字符串内容和变量混合连接时,可以使用字符串模板进行更高效的代码书写并保持代码的格式和整洁性。
let name = 'zhang'; let str = '<h2> hi ,${name}</h2>';
注意:字符串模板不会压缩内部的换行与空格,而是按照实际格式输出。实际项目中,用ES6转译工具将ES6代码转为ES5,格式可能会丢失。因为在ES5中没有字符串模板格式。
箭头函数
这个短函数的声明更加方面。
**注意:**箭头函数没有完整的执行上下文,因为其this和外层的this相同。它的执行上下文只有变量对象和作用域链,没有this值。
js中代码的执行上下文由变量对象、作用域链和this值组成。但箭头函数与外层执行上下文共享this值。如果需要创建具有独立上下文的函数,就不要使用箭头函数。
类
有了类,就有extends,对于开发者来说,使用class很大的好处是实现一个类的代码模块只能在一个地方定义。而以前是在代码中的任意位置去扩展基类的prototype属性。
class Animal{ constructor() { //...... } } class People extends Animal { constructor(contents={}) { super(); this.name = contents.name; this.family = contents.family; } sayHi() { console.log('Hello ${this.name} ${this.family}'); } } let boy = new People({ name: 'imagine', family: 'xie' }); boy.sayHi(); //Hello imagine xie
模块 module
JavaScript的模块化规范比较多,包括AMD、CMD、CommonJs,现在又多了ES6的import/export。
为了统一,选择模块开发规范时,应尽量遵循语言标准的特性。
● import/export规范
import {sayHi} from ‘./people’; //导入
export default satHi ; //导出
循环与迭代器Iterator
循环
ES6中,除了do…while、for循环,还有for…in遍历对象(不要使用其来遍历数组,因为遍历出来的键不是数字,而且在部分浏览器器会产生乱序)
遍历数组上,可以使用for…of、map、forEach。但是遍历数组最佳的方式是for…of。另外其也能用来遍历Map 、 Set 集合。
迭代器
Interator迭代器让遍历数组、对象和集合的方式更加灵活。并且,Interator能控制每次单步循环触发的时机,不用一次遍历所有的循环。
循环
const numbers = [1,2,3,4,5];
for(let number of numbers) {
console.log(number);
}
迭代器
const numbers = [1,2,3,4,5];
let iterator = numbersSymbol.iterator;
//Symbol.iterator相当于迭代器的接口,标识该数组/对象是可迭代的
let result = iterator.next();
console.log(result.done); //1
result = iterator.next();
console.log(result.done); //2
可以看出,当循环迭代中每次单步循环操作都不一样时,使用Interator就很有用了。
可以把Interator 理解为数组或对象上的一个根据偏移来访问内存内容的游标对象,每次调用next(),遍历游标会向后移动一个地址。
生成器Generator
Generator 不是针对对象上内容的遍历控制,而是针对函数内代码块的执行控制。
我们可以使用yield关键字来分割一个函数的代码,使其成为多个不同的代码段。
每次Generator 调用next()都只会执行yield关键字之间的一段代码。
声明Generator的方式:在函数名后面加上*来与普通函数区分。
const generator = function* () { const numbers = [1,2,3,4,5]; for(let number of numbers) { yield console.log(number); } } let result = generator(); result.next(); //1 result.next(); //2
用Generator处理异步操作
const generator = function* () { const numbers = [1,2,3,4,5]; for(let number of numbers){ yield setTimeout(function(){ console.log(number); },3000); } } let result = generator(); let done = result.next(); while(!done.done) { done = result.next(); } console.log('finish'); 输出结果: finish 1 2 3 4 5
内存泄漏情况
- Map、Set 中存储的属性元素被直接移除;
- 闭包函数;
- 全局变量;
- 对象属性循环引用;
- DOM节点删除时未解绑事件;
promise增强类型
Promise 代表一个一部操作的执行返回状态,这个执行返回状态在Promise对象创建时是未知的,它允许为异步操作的成功或失败指定处理方法。
一般将Promise实现规范分为Promise /A+ 和 Promise /A 规范。前者是对后者的修正与增强。
如何区分两者?
- 符合P/A+ 规范的promise实现一般以then方法为交互核心。
- P/A+ 规范要求onFulfilled或onRejected返回promise后的处理过程必须是作为函数来调用,而且调用过程必须是异步的。
- P/A + 规范严格定义了then方法链式调用时 onFulfilled或onRejected的调用顺序。
判断P/A+规范的主要方法是看Promise方法是否含有new Promise(function(resolve,reject){})、then、resolve、all等方法。
示例:
Promise有三种状态:
- Fulfilled:Promise执行成功
- Rejected:Promise执行失败
- Pending:Promise正在执行中
let status = 1; let promise = new Promise(function(resolve,reject){ if(status == 1) { resolve('Fulfilled'); }else { reject('Rejected'); } }); promise.then(function(msg){ console.log('success1:'+msg); return msg;//将返回的状态返回给第2个then方法处理 },function(msg){ console.log('fail1:'+msg); return msg; }).then(function(msg){ console.log('success2:'+msg); },function(msg){ console.log('fail2:'+msg); });
异步嵌套
then方法可以将传入参数的返回值一直传递下去,如果是异步的场景,就可以用这种方法来解决多层回调嵌套问题。
let promise = new Promise(function(resolve){ setTimeout(function(){ console.log('A'); resolve(); },3000); }); //使用then来链式处理流程 promise.then(function(){ return new Promise(function(resolve,reject){ setTimeout(function(){ console.log('B'); resolve(); },2000); }); }).then(function(){ return new Promise(function(resolve,reject){ setTimeout(function(){ console.log('C'); resolve(); },1000); }); }).then(function(){ return new Promise(function(resolve,reject){ console.log('D'); }); });
该例子中,会按照顺序依次异步输出A、B、C、D,这种情况可以通过在不同的then处理方法中返回一个新的Promise来解决。返回的Promise里面具有resolve()和reject()方法,只有当它的resolve或reject()被调用时,Promise方法才会继续执行,进入下一个then方法中操作。
Symbol
ES6中,将原本的6种基本数据类型扩展到了7种:其中Symbol就是其扩展的:null、undefined、boolean、string、number、symbol、object。
Symbol一般用作属性键值,并且能避免对象属性键的命名冲突。
let object = {}; let name = Symbol(); let family = Symbol(); object[name] = 'xie'; object[family] = 'imagine'; console.log(object); console.log(typeof name);
输出:
因为对象的键是Symbol变量,而Symbol变量是不能被重复声明的,这种情况下对象属性定义时属性键就不会被重复定义了。
Proxy
Proxy可以用来拦截某个对象的属性访问方法,然后重载对象的"."运算符。这种方法也叫**“对象劫持”**。一部分的MVVM框架的数据更变检测就是通过该手段实现的。
let object = new Proxy({},{ get : function(target,key,receiver) { console.log('getting ${key}'); return Reflect.get(target,key,receiver); }, set : function(target,key,value,receiver) { console.log('setting ${key}'); return Reflect.set(target,key,value,receiver); } }); object['value'] = 3;//赋值操作,会自动触发setter方法 console.log(object['value']);//取值操作,会自动触发getter方法
Reflect
Reflect 是原有对象上的一个引用代理,用于对原有对象进行赋值或取值操作。但不会触发对象属性的getter或setter调用,而是直接通过对对象进行赋值或取值操作会自动触发getter或setter方法。 具体可以看上例。
ES7
Array.prototype.includes
这是个数组方法。主要用来判断数组中是否包含某个元素。
let colors = ['red','blue','green']; console.log(colors.includes('green'));//true
异步函数 async/await
异步函数被大家关注的比较多。因为在ES6发布时,并没有发布该函数。
const sleep = async function(para) { return new Promise(function(resolve, reject) { setTimeout(function() { resolve(para * para); }, 1000) }) } const errorSleep =async function(para) { return new Promise(function(resolve, reject) { setTimeout(function() { reject(' ErrorSleep'); }, 1000) }) } const asyncFunction = async function() { const numbers = [1,2,3,4,5]; for(let number of numbers) { try{ await sleep(3000); console.log(number); }catch(err){ console.log(err); } } } let result = asyncFunction(); console.log('finished');
异步函数的写法与Generator写法相似。区别在于async函数将Generator函数的星号 替换成async, 将yeild 替换成 await,并且少了next()的调用控制。*
我们可以认为async/await是专门用于处理Generator中异步的场景。因为Generator可以使用next()来灵活地控制整个程序流程的执行,而异步场景只是一种使用情况。
实现异步的方法
- setTimeout
- 事件监听
- 观察者模式
- $Deferred
- promise
- generator
- async/await
- 第三方async库
…
就此,我们就简要地把JavaScript的标准进行了简述。