JavaScript初探系列(十一)——ES6
一、前言
ECMAScript 6.0(以下简称 ES6)是 JavaScript 语言的下一代标准,已经在 2015 年 6 月正式发布了。它的目标,是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。这句话基本涵盖了为什么会产生ES6这次更新的原因——编写复杂的大型应用程序。回顾近两年的前端开发,复杂度确实在快速增加,近期不论从系统复杂度还是到前端开发人员数量应该达到了一个饱和值,换个方式说,没有ES6我们的前端代码依旧可以写很多复杂的应用,而ES6的提出更好的帮我们解决了很多历史遗留问题,另一个角度ES6让JS更适合开发大型应用,而不用引用太多的库了。本文,简单介绍几个ES6核心概念,个人感觉只要掌握以下新特性便能愉快的开始使用ES6做代码了!这里的文章,请配合着阮老师这里的教程,一些细节阮老师那边讲的好得多:http://es6.ruanyifeng.com/#docs/class-extends
除了阮老师的文章还参考:http://www.infoq.com/cn/articles/es6-in-depth-arrow-functions
二、模块Module的引入
都说了复杂的大型应用了,所以我们第一个要讨论的重要特性就是模块概念,我们做一个复杂的项目必定需要两步走:
① 分得开,并且需要分开
② 合得起来
我们普遍认为没有复杂的应用,只有分不开的应用,再复杂的应用,一旦可以使用组件化、模块化的方式分成不同的小单元,那么其难度便会大大降低,模块化是大型、复杂项目的主要拦路虎。为了解决这个问题,社区制定了一些模块加载方案,对于浏览器开发来说,我们用的最多的是AMD规范,也就是大家熟知的requireJS,而ES6中在语音标准层面实现了模块功能,用以取代服务端通信的CommonJS和AMD规范,成为了通用的规范,多说无益,我们这里上一段代码说明:
1 /* 2 validate.js 多用于表单验证 3 */ 4 export function isEmail (text) { 5 var reg = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; 6 return reg.test(text); 7 } 8 9 export function isPassword (text) { 10 var reg = /^[a-zA-Z0-9]{6,20}$/; 11 return reg.test(text); 12 }
那么我们现在想在页面里面使用这个工具类该怎么做呢:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 </head> 7 <body> 8 <!-- 请注意这里type=module才能运行 --> 9 <script type="module"> 10 import {isEmail} from './validate.js'; 11 var e1 = 'dddd'; 12 var e2 = '[email protected]' 13 console.log(isEmail(e1)) 14 console.log(isEmail(e2)) 15 </script> 16 </body> 17 </html>
ES6中的Module提出,在我这里看来是想在官方完成之前requireJS干的工作,这里也有一些本质上的不一样:
① requireJS是使用加载script标签的方式载入js,没有什么限制
② import命令会被js引擎静态分析,先于模块其他语句执行
以上特性会直接给我们带来一些困扰,比如原来我们项目控制器会有这么一段代码:
1 var viewId = ''; //由浏览器获取试图id,url可能为?viewId=booking|list|... 2 //如果不存在则需要构建,记住构建时需要使用viewdata继承源view 3 requirejs(viewId, function(View) { 4 //执行根据url参数动态加载view逻辑 5 })
前面说过了,import命令会被js引擎静态分析,先于模块其他语句执行,所以我们在根本不能将import执行滞后,或者动态化,做不到的,这种写法也是报错的:
if (viewId) { import view from './' + viewId; }
这种设计会有利于提高编译器效率,但是之前的动态业务逻辑就不知道如何继续了?而ES6如果提供import的方法,我们变可以执行逻辑:
1 import(viewId, function() { 2 //渲染页面 3 })
事实上他也提供了:
现在看起来,JS中的模块便十分完美了,至于其中一些细节,便可以用到的时候再说了
三、ES6中的类Class
我们对我们的定位一直是非常清晰的,我们就是要干大项目的,我们是要干复杂的项目,除了模块概念,类的概念也非常重要,我们之前用的这种方式实现一个类,我们来温故而知新。
当一个函数被创建时,Function构造函数产生的函数会隐式的被赋予一个prototype属性,prototype包含一个constructor对象
而constructor便是该新函数对象(constructor意义不大,但是可以帮我们找到继承关系)
每个函数都会有一个prototype属性,该属性指向另一对象,这个对象包含可以由特定类型的所有实例共享的属性和方法
每次实例化后,实例内部都会包含一个[[prototype]](__proto__)的内部属性,这个属性指向prototype
① 我们通过isPrototypeOf来确定某个对象是不是我的原型
② hasOwnPrototype 可以检测一个属性是存在实例中还是原型中,该属性不是原型属性才返回true
var Person = function (name, age) { this.name = name; this.age = age; }; Person.prototype.getName = function () { return this.name; }; var y = new Person('叶小钗', 30);
为了方便,使用,我们做了更为复杂的封装这里写一个demo:
var arr = []; var slice = arr.slice; function create() { if (arguments.length == 0 || arguments.length > 2) throw '参数错误'; var parent = null; //将参数转换为数组 var properties = slice.call(arguments); //如果第一个参数为类(function),那么就将之取出 if (typeof properties[0] === 'function') parent = properties.shift(); properties = properties[0]; function klass() { this.initialize.apply(this, arguments); } klass.superclass = parent; klass.subclasses = []; if (parent) { var subclass = function () { }; subclass.prototype = parent.prototype; klass.prototype = new subclass; parent.subclasses.push(klass); } var ancestor = klass.superclass && klass.superclass.prototype; for (var k in properties) { var value = properties[k]; //满足条件就重写 if (ancestor && typeof value == 'function') { var argslist = /^\s*function\s*\(([^\(\)]*?)\)\s*?\{/i.exec(value.toString())[1].replace(/\s/i, '').split(','); //只有在第一个参数为$super情况下才需要处理(是否具有重复方法需要用户自己决定) if (argslist[0] === '$super' && ancestor[k]) { value = (function (methodName, fn) { return function () { var scope = this; var args = [function () { return ancestor[methodName].apply(scope, arguments); } ]; return fn.apply(this, args.concat(slice.call(arguments))); }; })(k, value); } } klass.prototype[k] = value; } if (!klass.prototype.initialize) klass.prototype.initialize = function () { }; klass.prototype.constructor = klass; return klass; }
var AbstractView = create({ initialize: function (opts) { opts = opts || {}; this.wrapper = opts.wrapper || $('body'); //事件集合 this.events = {}; this.isCreate = false; }, on: function (type, fn) { if (!this.events[type]) this.events[type] = []; this.events[type].push(fn); }, trigger: function (type) { if (!this.events[type]) return; for (var i = 0, len = this.events[type].length; i < len; i++) { this.events[type][i].call(this) } }, createHtml: function () { throw '必须重写'; }, create: function () { this.root = $(this.createHtml()); this.wrapper.append(this.root); this.trigger('onCreate'); this.isCreate = true; }, show: function () { if (!this.isCreate) this.create(); this.root.show(); this.trigger('onShow'); }, hide: function () { this.root.hide(); } }); var Alert = create(AbstractView, { createHtml: function () { return '<div class="alert">这里是alert框</div>'; } }); var AlertTitle = create(Alert, { initialize: function ($super) { this.title = ''; $super(); }, createHtml: function () { return '<div class="alert"><h2>' + this.title + '</h2>这里是带标题alert框</div>'; }, setTitle: function (title) { this.title = title; this.root.find('h2').html(title) } }); var AlertTitleButton = create(AlertTitle, { initialize: function ($super) { this.title = ''; $super(); this.on('onShow', function () { var bt = $('<input type="button" value="点击我" />'); bt.click($.proxy(function () { alert(this.title); }, this)); this.root.append(bt) }); } }); var v1 = new Alert(); v1.show(); var v2 = new AlertTitle(); v2.show(); v2.setTitle('我是标题'); var v3 = new AlertTitleButton(); v3.show(); v3.setTitle('我是标题和按钮的alert');
ES6中直接从标准层面解决了我们的问题,他提出了Class关键词让我们可以更好的定义类,我们这里用我们ES6的模块语法重新实现一次:
1 export class AbstractView { 2 constructor(opts) { 3 opts = opts || {}; 4 this.wrapper = opts.wrapper || $('body'); 5 //事件集合 6 this.events = {}; 7 this.isCreate = false; 8 } 9 on(type, fn) { 10 if (!this.events[type]) this.events[type] = []; 11 this.events[type].push(fn); 12 } 13 trigger(type) { 14 if (!this.events[type]) return; 15 for (var i = 0, len = this.events[type].length; i < len; i++) { 16 this.events[type][i].call(this) 17 } 18 } 19 createHtml() { 20 throw '必须重写'; 21 } 22 create() { 23 this.root = $(this.createHtml()); 24 this.wrapper.append(this.root); 25 this.trigger('onCreate'); 26 this.isCreate = true; 27 } 28 show() { 29 if (!this.isCreate) this.create(); 30 this.root.show(); 31 this.trigger('onShow'); 32 } 33 hide() { 34 this.root.hide(); 35 } 36 } 37 export class Alert extends AbstractView { 38 createHtml() { 39 return '<div class="alert">这里是alert框</div>'; 40 } 41 } 42 export class AlertTitle extends Alert { 43 constructor(opts) { 44 super(opts); 45 this.title = ''; 46 } 47 createHtml() { 48 return '<div class="alert"><h2>' + this.title + '</h2>这里是带标题alert框</div>'; 49 } 50 setTitle(title) { 51 this.title = title; 52 this.root.find('h2').html(title) 53 } 54 } 55 export class AlertTitleButton extends AlertTitle { 56 constructor(opts) { 57 super(opts); 58 this.on('onShow', function () { 59 var bt = $('<input type="button" value="点击我" />'); 60 bt.click($.proxy(function () { 61 alert(this.title); 62 }, this)); 63 this.root.append(bt) 64 }); 65 } 66 }
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 </head> 7 <body> 8 <script type="text/javascript" src="zepto.js"></script> 9 10 <!-- 请注意这里type=module才能运行 --> 11 <script type="module"> 12 import {Alert, AlertTitle, AlertTitleButton} from './es6class.js'; 13 var v1 = new Alert(); 14 v1.show(); 15 var v2 = new AlertTitle(); 16 v2.show(); 17 v2.setTitle('我是标题'); 18 var v3 = new AlertTitleButton(); 19 v3.show(); 20 v3.setTitle('我是标题和按钮的alert'); 21 </script> 22 </body> 23 </html>
这里的代码完成了与上面一样的功能,而代码更加的清爽了。
四、ES6中的函数
我们这里学习ES6,由大到小,首先讨论模块,其次讨论类,这个时候理所当然到了我们的函数了,ES6中函数也多了很多新特性或者说语法糖吧,首先我们来说一下这里的箭头函数
(一)、箭头函数
//ES5 $('#bt').click(function (e) { //doing something }) //ES6 $('#bt').click(e => { //doing something })
有点语法糖的感觉,有一个很大不同的是,箭头函数不具有this属性,箭头函数直接使用的是外部的this的作用域,这个想不想用看个人习惯吧。
(二)、参数新特性
ES6可以为参数提供默认属性
1 function log(x, y = 'World') { 2 console.log(x, y); 3 } 4 5 log('Hello') // Hello World 6 log('Hello', 'China') // Hello China 7 log('Hello', '') // Hello
至于不定参数撒的,我这里没有多过多的使用,等项目遇到再说吧,如果研究的太细碎,反而不适合我们开展工作。
五、let、const和var
之前的js世界里,我们定义变量都是使用的var,别说还真挺好用的,虽有会有一些问题,但是对于熟悉js特性的小伙伴都能很好的解决,一般记住:变量提升会解决绝大多数问题。
就能解决很多问题,而且真实项目中,我们会会避免出现变量出现重名的情况所以有时候大家面试题中看到的场景在实际工作中很少发生,只要不刻意臆想、制造一些难以判断的场景,其实并不会出现多少BUG,不能因为想考察人家对语言特性的了解,就做一些容易容易忘掉的陷阱题。
无论如何,var 声明的变量受到了一定诟病,事实上在强类型语言看来也确实是设计BUG,但是完全废弃var的使用显然不是js该做的事情,这种情况下出现了let关键词。
let与var一致用以声明变量,并且一切用var的地方都可以使用let替换,新的标准也建议大家不要再使用var了,let具有更好的作用域规则,也许这个规则是边界更加清晰了:
一、前言
ECMAScript 6.0(以下简称 ES6)是 JavaScript 语言的下一代标准,已经在 2015 年 6 月正式发布了。它的目标,是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。这句话基本涵盖了为什么会产生ES6这次更新的原因——编写复杂的大型应用程序。回顾近两年的前端开发,复杂度确实在快速增加,近期不论从系统复杂度还是到前端开发人员数量应该达到了一个饱和值,换个方式说,没有ES6我们的前端代码依旧可以写很多复杂的应用,而ES6的提出更好的帮我们解决了很多历史遗留问题,另一个角度ES6让JS更适合开发大型应用,而不用引用太多的库了。本文,简单介绍几个ES6核心概念,个人感觉只要掌握以下新特性便能愉快的开始使用ES6做代码了!这里的文章,请配合着阮老师这里的教程,一些细节阮老师那边讲的好得多:http://es6.ruanyifeng.com/#docs/class-extends
除了阮老师的文章还参考:http://www.infoq.com/cn/articles/es6-in-depth-arrow-functions
二、模块Module的引入
都说了复杂的大型应用了,所以我们第一个要讨论的重要特性就是模块概念,我们做一个复杂的项目必定需要两步走:
① 分得开,并且需要分开
② 合得起来
我们普遍认为没有复杂的应用,只有分不开的应用,再复杂的应用,一旦可以使用组件化、模块化的方式分成不同的小单元,那么其难度便会大大降低,模块化是大型、复杂项目的主要拦路虎。为了解决这个问题,社区制定了一些模块加载方案,对于浏览器开发来说,我们用的最多的是AMD规范,也就是大家熟知的requireJS,而ES6中在语音标准层面实现了模块功能,用以取代服务端通信的CommonJS和AMD规范,成为了通用的规范,多说无益,我们这里上一段代码说明:
1 /* 2 validate.js 多用于表单验证 3 */ 4 export function isEmail (text) { 5 var reg = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; 6 return reg.test(text); 7 } 8 9 export function isPassword (text) { 10 var reg = /^[a-zA-Z0-9]{6,20}$/; 11 return reg.test(text); 12 }
那么我们现在想在页面里面使用这个工具类该怎么做呢:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 </head> 7 <body> 8 <!-- 请注意这里type=module才能运行 --> 9 <script type="module"> 10 import {isEmail} from './validate.js'; 11 var e1 = 'dddd'; 12 var e2 = '[email protected]' 13 console.log(isEmail(e1)) 14 console.log(isEmail(e2)) 15 </script> 16 </body> 17 </html>
ES6中的Module提出,在我这里看来是想在官方完成之前requireJS干的工作,这里也有一些本质上的不一样:
① requireJS是使用加载script标签的方式载入js,没有什么限制
② import命令会被js引擎静态分析,先于模块其他语句执行
以上特性会直接给我们带来一些困扰,比如原来我们项目控制器会有这么一段代码:
1 var viewId = ''; //由浏览器获取试图id,url可能为?viewId=booking|list|... 2 //如果不存在则需要构建,记住构建时需要使用viewdata继承源view 3 requirejs(viewId, function(View) { 4 //执行根据url参数动态加载view逻辑 5 })
前面说过了,import命令会被js引擎静态分析,先于模块其他语句执行,所以我们在根本不能将import执行滞后,或者动态化,做不到的,这种写法也是报错的:
if (viewId) { import view from './' + viewId; }
这种设计会有利于提高编译器效率,但是之前的动态业务逻辑就不知道如何继续了?而ES6如果提供import的方法,我们变可以执行逻辑:
1 import(viewId, function() { 2 //渲染页面 3 })
事实上他也提供了:
现在看起来,JS中的模块便十分完美了,至于其中一些细节,便可以用到的时候再说了
三、ES6中的类Class
我们对我们的定位一直是非常清晰的,我们就是要干大项目的,我们是要干复杂的项目,除了模块概念,类的概念也非常重要,我们之前用的这种方式实现一个类,我们来温故而知新。
当一个函数被创建时,Function构造函数产生的函数会隐式的被赋予一个prototype属性,prototype包含一个constructor对象
而constructor便是该新函数对象(constructor意义不大,但是可以帮我们找到继承关系)
每个函数都会有一个prototype属性,该属性指向另一对象,这个对象包含可以由特定类型的所有实例共享的属性和方法
每次实例化后,实例内部都会包含一个[[prototype]](__proto__)的内部属性,这个属性指向prototype
① 我们通过isPrototypeOf来确定某个对象是不是我的原型
② hasOwnPrototype 可以检测一个属性是存在实例中还是原型中,该属性不是原型属性才返回true
var Person = function (name, age) { this.name = name; this.age = age; }; Person.prototype.getName = function () { return this.name; }; var y = new Person('叶小钗', 30);
为了方便,使用,我们做了更为复杂的封装这里写一个demo:
var arr = []; var slice = arr.slice; function create() { if (arguments.length == 0 || arguments.length > 2) throw '参数错误'; var parent = null; //将参数转换为数组 var properties = slice.call(arguments); //如果第一个参数为类(function),那么就将之取出 if (typeof properties[0] === 'function') parent = properties.shift(); properties = properties[0]; function klass() { this.initialize.apply(this, arguments); } klass.superclass = parent; klass.subclasses = []; if (parent) { var subclass = function () { }; subclass.prototype = parent.prototype; klass.prototype = new subclass; parent.subclasses.push(klass); } var ancestor = klass.superclass && klass.superclass.prototype; for (var k in properties) { var value = properties[k]; //满足条件就重写 if (ancestor && typeof value == 'function') { var argslist = /^\s*function\s*\(([^\(\)]*?)\)\s*?\{/i.exec(value.toString())[1].replace(/\s/i, '').split(','); //只有在第一个参数为$super情况下才需要处理(是否具有重复方法需要用户自己决定) if (argslist[0] === '$super' && ancestor[k]) { value = (function (methodName, fn) { return function () { var scope = this; var args = [function () { return ancestor[methodName].apply(scope, arguments); } ]; return fn.apply(this, args.concat(slice.call(arguments))); }; })(k, value); } } klass.prototype[k] = value; } if (!klass.prototype.initialize) klass.prototype.initialize = function () { }; klass.prototype.constructor = klass; return klass; }
var AbstractView = create({ initialize: function (opts) { opts = opts || {}; this.wrapper = opts.wrapper || $('body'); //事件集合 this.events = {}; this.isCreate = false; }, on: function (type, fn) { if (!this.events[type]) this.events[type] = []; this.events[type].push(fn); }, trigger: function (type) { if (!this.events[type]) return; for (var i = 0, len = this.events[type].length; i < len; i++) { this.events[type][i].call(this) } }, createHtml: function () { throw '必须重写'; }, create: function () { this.root = $(this.createHtml()); this.wrapper.append(this.root); this.trigger('onCreate'); this.isCreate = true; }, show: function () { if (!this.isCreate) this.create(); this.root.show(); this.trigger('onShow'); }, hide: function () { this.root.hide(); } }); var Alert = create(AbstractView, { createHtml: function () { return '<div class="alert">这里是alert框</div>'; } }); var AlertTitle = create(Alert, { initialize: function ($super) { this.title = ''; $super(); }, createHtml: function () { return '<div class="alert"><h2>' + this.title + '</h2>这里是带标题alert框</div>'; }, setTitle: function (title) { this.title = title; this.root.find('h2').html(title) } }); var AlertTitleButton = create(AlertTitle, { initialize: function ($super) { this.title = ''; $super(); this.on('onShow', function () { var bt = $('<input type="button" value="点击我" />'); bt.click($.proxy(function () { alert(this.title); }, this)); this.root.append(bt) }); } }); var v1 = new Alert(); v1.show(); var v2 = new AlertTitle(); v2.show(); v2.setTitle('我是标题'); var v3 = new AlertTitleButton(); v3.show(); v3.setTitle('我是标题和按钮的alert');
ES6中直接从标准层面解决了我们的问题,他提出了Class关键词让我们可以更好的定义类,我们这里用我们ES6的模块语法重新实现一次:
1 export class AbstractView { 2 constructor(opts) { 3 opts = opts || {}; 4 this.wrapper = opts.wrapper || $('body'); 5 //事件集合 6 this.events = {}; 7 this.isCreate = false; 8 } 9 on(type, fn) { 10 if (!this.events[type]) this.events[type] = []; 11 this.events[type].push(fn); 12 } 13 trigger(type) { 14 if (!this.events[type]) return; 15 for (var i = 0, len = this.events[type].length; i < len; i++) { 16 this.events[type][i].call(this) 17 } 18 } 19 createHtml() { 20 throw '必须重写'; 21 } 22 create() { 23 this.root = $(this.createHtml()); 24 this.wrapper.append(this.root); 25 this.trigger('onCreate'); 26 this.isCreate = true; 27 } 28 show() { 29 if (!this.isCreate) this.create(); 30 this.root.show(); 31 this.trigger('onShow'); 32 } 33 hide() { 34 this.root.hide(); 35 } 36 } 37 export class Alert extends AbstractView { 38 createHtml() { 39 return '<div class="alert">这里是alert框</div>'; 40 } 41 } 42 export class AlertTitle extends Alert { 43 constructor(opts) { 44 super(opts); 45 this.title = ''; 46 } 47 createHtml() { 48 return '<div class="alert"><h2>' + this.title + '</h2>这里是带标题alert框</div>'; 49 } 50 setTitle(title) { 51 this.title = title; 52 this.root.find('h2').html(title) 53 } 54 } 55 export class AlertTitleButton extends AlertTitle { 56 constructor(opts) { 57 super(opts); 58 this.on('onShow', function () { 59 var bt = $('<input type="button" value="点击我" />'); 60 bt.click($.proxy(function () { 61 alert(this.title); 62 }, this)); 63 this.root.append(bt) 64 }); 65 } 66 }
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 </head> 7 <body> 8 <script type="text/javascript" src="zepto.js"></script> 9 10 <!-- 请注意这里type=module才能运行 --> 11 <script type="module"> 12 import {Alert, AlertTitle, AlertTitleButton} from './es6class.js'; 13 var v1 = new Alert(); 14 v1.show(); 15 var v2 = new AlertTitle(); 16 v2.show(); 17 v2.setTitle('我是标题'); 18 var v3 = new AlertTitleButton(); 19 v3.show(); 20 v3.setTitle('我是标题和按钮的alert'); 21 </script> 22 </body> 23 </html>
这里的代码完成了与上面一样的功能,而代码更加的清爽了。
四、ES6中的函数
我们这里学习ES6,由大到小,首先讨论模块,其次讨论类,这个时候理所当然到了我们的函数了,ES6中函数也多了很多新特性或者说语法糖吧,首先我们来说一下这里的箭头函数
(一)、箭头函数
//ES5 $('#bt').click(function (e) { //doing something }) //ES6 $('#bt').click(e => { //doing something })
有点语法糖的感觉,有一个很大不同的是,箭头函数不具有this属性,箭头函数直接使用的是外部的this的作用域,这个想不想用看个人习惯吧。
(二)、参数新特性
ES6可以为参数提供默认属性
1 function log(x, y = 'World') { 2 console.log(x, y); 3 } 4 5 log('Hello') // Hello World 6 log('Hello', 'China') // Hello China 7 log('Hello', '') // Hello
至于不定参数撒的,我这里没有多过多的使用,等项目遇到再说吧,如果研究的太细碎,反而不适合我们开展工作。
五、let、const和var
之前的js世界里,我们定义变量都是使用的var,别说还真挺好用的,虽有会有一些问题,但是对于熟悉js特性的小伙伴都能很好的解决,一般记住:变量提升会解决绝大多数问题。
就能解决很多问题,而且真实项目中,我们会会避免出现变量出现重名的情况所以有时候大家面试题中看到的场景在实际工作中很少发生,只要不刻意臆想、制造一些难以判断的场景,其实并不会出现多少BUG,不能因为想考察人家对语言特性的了解,就做一些容易容易忘掉的陷阱题。
无论如何,var 声明的变量受到了一定诟病,事实上在强类型语言看来也确实是设计BUG,但是完全废弃var的使用显然不是js该做的事情,这种情况下出现了let关键词。
let与var一致用以声明变量,并且一切用var的地方都可以使用let替换,新的标准也建议大家不要再使用var了,let具有更好的作用域规则,也许这个规则是边界更加清晰了:
一、前言
ECMAScript 6.0(以下简称 ES6)是 JavaScript 语言的下一代标准,已经在 2015 年 6 月正式发布了。它的目标,是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。这句话基本涵盖了为什么会产生ES6这次更新的原因——编写复杂的大型应用程序。回顾近两年的前端开发,复杂度确实在快速增加,近期不论从系统复杂度还是到前端开发人员数量应该达到了一个饱和值,换个方式说,没有ES6我们的前端代码依旧可以写很多复杂的应用,而ES6的提出更好的帮我们解决了很多历史遗留问题,另一个角度ES6让JS更适合开发大型应用,而不用引用太多的库了。本文,简单介绍几个ES6核心概念,个人感觉只要掌握以下新特性便能愉快的开始使用ES6做代码了!这里的文章,请配合着阮老师这里的教程,一些细节阮老师那边讲的好得多:http://es6.ruanyifeng.com/#docs/class-extends
除了阮老师的文章还参考:http://www.infoq.com/cn/articles/es6-in-depth-arrow-functions
二、模块Module的引入
都说了复杂的大型应用了,所以我们第一个要讨论的重要特性就是模块概念,我们做一个复杂的项目必定需要两步走:
① 分得开,并且需要分开
② 合得起来
我们普遍认为没有复杂的应用,只有分不开的应用,再复杂的应用,一旦可以使用组件化、模块化的方式分成不同的小单元,那么其难度便会大大降低,模块化是大型、复杂项目的主要拦路虎。为了解决这个问题,社区制定了一些模块加载方案,对于浏览器开发来说,我们用的最多的是AMD规范,也就是大家熟知的requireJS,而ES6中在语音标准层面实现了模块功能,用以取代服务端通信的CommonJS和AMD规范,成为了通用的规范,多说无益,我们这里上一段代码说明:
1 /* 2 validate.js 多用于表单验证 3 */ 4 export function isEmail (text) { 5 var reg = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; 6 return reg.test(text); 7 } 8 9 export function isPassword (text) { 10 var reg = /^[a-zA-Z0-9]{6,20}$/; 11 return reg.test(text); 12 }
那么我们现在想在页面里面使用这个工具类该怎么做呢:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 </head> 7 <body> 8 <!-- 请注意这里type=module才能运行 --> 9 <script type="module"> 10 import {isEmail} from './validate.js'; 11 var e1 = 'dddd'; 12 var e2 = '[email protected]' 13 console.log(isEmail(e1)) 14 console.log(isEmail(e2)) 15 </script> 16 </body> 17 </html>
ES6中的Module提出,在我这里看来是想在官方完成之前requireJS干的工作,这里也有一些本质上的不一样:
① requireJS是使用加载script标签的方式载入js,没有什么限制
② import命令会被js引擎静态分析,先于模块其他语句执行
以上特性会直接给我们带来一些困扰,比如原来我们项目控制器会有这么一段代码:
1 var viewId = ''; //由浏览器获取试图id,url可能为?viewId=booking|list|... 2 //如果不存在则需要构建,记住构建时需要使用viewdata继承源view 3 requirejs(viewId, function(View) { 4 //执行根据url参数动态加载view逻辑 5 })
前面说过了,import命令会被js引擎静态分析,先于模块其他语句执行,所以我们在根本不能将import执行滞后,或者动态化,做不到的,这种写法也是报错的:
if (viewId) { import view from './' + viewId; }
这种设计会有利于提高编译器效率,但是之前的动态业务逻辑就不知道如何继续了?而ES6如果提供import的方法,我们变可以执行逻辑:
1 import(viewId, function() { 2 //渲染页面 3 })
事实上他也提供了:
现在看起来,JS中的模块便十分完美了,至于其中一些细节,便可以用到的时候再说了
三、ES6中的类Class
我们对我们的定位一直是非常清晰的,我们就是要干大项目的,我们是要干复杂的项目,除了模块概念,类的概念也非常重要,我们之前用的这种方式实现一个类,我们来温故而知新。
当一个函数被创建时,Function构造函数产生的函数会隐式的被赋予一个prototype属性,prototype包含一个constructor对象
而constructor便是该新函数对象(constructor意义不大,但是可以帮我们找到继承关系)
每个函数都会有一个prototype属性,该属性指向另一对象,这个对象包含可以由特定类型的所有实例共享的属性和方法
每次实例化后,实例内部都会包含一个[[prototype]](__proto__)的内部属性,这个属性指向prototype
① 我们通过isPrototypeOf来确定某个对象是不是我的原型
② hasOwnPrototype 可以检测一个属性是存在实例中还是原型中,该属性不是原型属性才返回true
var Person = function (name, age) { this.name = name; this.age = age; }; Person.prototype.getName = function () { return this.name; }; var y = new Person('叶小钗', 30);
为了方便,使用,我们做了更为复杂的封装这里写一个demo:
var arr = []; var slice = arr.slice; function create() { if (arguments.length == 0 || arguments.length > 2) throw '参数错误'; var parent = null; //将参数转换为数组 var properties = slice.call(arguments); //如果第一个参数为类(function),那么就将之取出 if (typeof properties[0] === 'function') parent = properties.shift(); properties = properties[0]; function klass() { this.initialize.apply(this, arguments); } klass.superclass = parent; klass.subclasses = []; if (parent) { var subclass = function () { }; subclass.prototype = parent.prototype; klass.prototype = new subclass; parent.subclasses.push(klass); } var ancestor = klass.superclass && klass.superclass.prototype; for (var k in properties) { var value = properties[k]; //满足条件就重写 if (ancestor && typeof value == 'function') { var argslist = /^\s*function\s*\(([^\(\)]*?)\)\s*?\{/i.exec(value.toString())[1].replace(/\s/i, '').split(','); //只有在第一个参数为$super情况下才需要处理(是否具有重复方法需要用户自己决定) if (argslist[0] === '$super' && ancestor[k]) { value = (function (methodName, fn) { return function () { var scope = this; var args = [function () { return ancestor[methodName].apply(scope, arguments); } ]; return fn.apply(this, args.concat(slice.call(arguments))); }; })(k, value); } } klass.prototype[k] = value; } if (!klass.prototype.initialize) klass.prototype.initialize = function () { }; klass.prototype.constructor = klass; return klass; }
var AbstractView = create({ initialize: function (opts) { opts = opts || {}; this.wrapper = opts.wrapper || $('body'); //事件集合 this.events = {}; this.isCreate = false; }, on: function (type, fn) { if (!this.events[type]) this.events[type] = []; this.events[type].push(fn); }, trigger: function (type) { if (!this.events[type]) return; for (var i = 0, len = this.events[type].length; i < len; i++) { this.events[type][i].call(this) } }, createHtml: function () { throw '必须重写'; }, create: function () { this.root = $(this.createHtml()); this.wrapper.append(this.root); this.trigger('onCreate'); this.isCreate = true; }, show: function () { if (!this.isCreate) this.create(); this.root.show(); this.trigger('onShow'); }, hide: function () { this.root.hide(); } }); var Alert = create(AbstractView, { createHtml: function () { return '<div class="alert">这里是alert框</div>'; } }); var AlertTitle = create(Alert, { initialize: function ($super) { this.title = ''; $super(); }, createHtml: function () { return '<div class="alert"><h2>' + this.title + '</h2>这里是带标题alert框</div>'; }, setTitle: function (title) { this.title = title; this.root.find('h2').html(title) } }); var AlertTitleButton = create(AlertTitle, { initialize: function ($super) { this.title = ''; $super(); this.on('onShow', function () { var bt = $('<input type="button" value="点击我" />'); bt.click($.proxy(function () { alert(this.title); }, this)); this.root.append(bt) }); } }); var v1 = new Alert(); v1.show(); var v2 = new AlertTitle(); v2.show(); v2.setTitle('我是标题'); var v3 = new AlertTitleButton(); v3.show(); v3.setTitle('我是标题和按钮的alert');
ES6中直接从标准层面解决了我们的问题,他提出了Class关键词让我们可以更好的定义类,我们这里用我们ES6的模块语法重新实现一次:
1 export class AbstractView { 2 constructor(opts) { 3 opts = opts || {}; 4 this.wrapper = opts.wrapper || $('body'); 5 //事件集合 6 this.events = {}; 7 this.isCreate = false; 8 } 9 on(type, fn) { 10 if (!this.events[type]) this.events[type] = []; 11 this.events[type].push(fn); 12 } 13 trigger(type) { 14 if (!this.events[type]) return; 15 for (var i = 0, len = this.events[type].length; i < len; i++) { 16 this.events[type][i].call(this) 17 } 18 } 19 createHtml() { 20 throw '必须重写'; 21 } 22 create() { 23 this.root = $(this.createHtml()); 24 this.wrapper.append(this.root); 25 this.trigger('onCreate'); 26 this.isCreate = true; 27 } 28 show() { 29 if (!this.isCreate) this.create(); 30 this.root.show(); 31 this.trigger('onShow'); 32 } 33 hide() { 34 this.root.hide(); 35 } 36 } 37 export class Alert extends AbstractView { 38 createHtml() { 39 return '<div class="alert">这里是alert框</div>'; 40 } 41 } 42 export class AlertTitle extends Alert { 43 constructor(opts) { 44 super(opts); 45 this.title = ''; 46 } 47 createHtml() { 48 return '<div class="alert"><h2>' + this.title + '</h2>这里是带标题alert框</div>'; 49 } 50 setTitle(title) { 51 this.title = title; 52 this.root.find('h2').html(title) 53 } 54 } 55 export class AlertTitleButton extends AlertTitle { 56 constructor(opts) { 57 super(opts); 58 this.on('onShow', function () { 59 var bt = $('<input type="button" value="点击我" />'); 60 bt.click($.proxy(function () { 61 alert(this.title); 62 }, this)); 63 this.root.append(bt) 64 }); 65 } 66 }
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 </head> 7 <body> 8 <script type="text/javascript" src="zepto.js"></script> 9 10 <!-- 请注意这里type=module才能运行 --> 11 <script type="module"> 12 import {Alert, AlertTitle, AlertTitleButton} from './es6class.js'; 13 var v1 = new Alert(); 14 v1.show(); 15 var v2 = new AlertTitle(); 16 v2.show(); 17 v2.setTitle('我是标题'); 18 var v3 = new AlertTitleButton(); 19 v3.show(); 20 v3.setTitle('我是标题和按钮的alert'); 21 </script> 22 </body> 23 </html>
这里的代码完成了与上面一样的功能,而代码更加的清爽了。
四、ES6中的函数
我们这里学习ES6,由大到小,首先讨论模块,其次讨论类,这个时候理所当然到了我们的函数了,ES6中函数也多了很多新特性或者说语法糖吧,首先我们来说一下这里的箭头函数
(一)、箭头函数
//ES5 $('#bt').click(function (e) { //doing something }) //ES6 $('#bt').click(e => { //doing something })
有点语法糖的感觉,有一个很大不同的是,箭头函数不具有this属性,箭头函数直接使用的是外部的this的作用域,这个想不想用看个人习惯吧。
(二)、参数新特性
ES6可以为参数提供默认属性
1 function log(x, y = 'World') { 2 console.log(x, y); 3 } 4 5 log('Hello') // Hello World 6 log('Hello', 'China') // Hello China 7 log('Hello', '') // Hello
至于不定参数撒的,我这里没有多过多的使用,等项目遇到再说吧,如果研究的太细碎,反而不适合我们开展工作。
五、let、const和var
之前的js世界里,我们定义变量都是使用的var,别说还真挺好用的,虽有会有一些问题,但是对于熟悉js特性的小伙伴都能很好的解决,一般记住:变量提升会解决绝大多数问题。
就能解决很多问题,而且真实项目中,我们会会避免出现变量出现重名的情况所以有时候大家面试题中看到的场景在实际工作中很少发生,只要不刻意臆想、制造一些难以判断的场景,其实并不会出现多少BUG,不能因为想考察人家对语言特性的了解,就做一些容易容易忘掉的陷阱题。
无论如何,var 声明的变量受到了一定诟病,事实上在强类型语言看来也确实是设计BUG,但是完全废弃var的使用显然不是js该做的事情,这种情况下出现了let关键词。
let与var一致用以声明变量,并且一切用var的地方都可以使用let替换,新的标准也建议大家不要再使用var了,let具有更好的作用域规则,也许这个规则是边界更加清晰了: