javascript异步发展史,30分钟ES6从陌生到熟悉

作者: 计算机前端  发布:2019-10-20

30分钟ES6从陌生到熟悉

2018/07/30 · JavaScript · es6

原文出处: 叶小钗   

js中的异步是指一个函数在执行过程中,其中一部分不能马上执行完毕,然后执行函数体中另外一部分。等到第一部分得到返回值再执行第二部分。

1.回调函数callback

无法捕获错误 try catch

不能return

回调地狱

  function personInfo(callback){

    $.ajax({

          type: "GET",

          url: "test.json", 

          data: {

                username:username,

                content:content

          },

        dataType: "json",

        success: function(data){

              if(data.length>0){

                    callback&&callback();

              }

        }

  });

}

2.事件发布/订阅模型

给一个事件,订阅几个方法,方法依次执行。

function Event() {

    this.event = {};

}

Event.prototype.on = function (type,callBack) {

    if(this.event[type]){

        this.event[type].push(callBack);

    }else{

        this.event[type] = [callBack];

    }

};

Event.prototype.emit = function (type,...data) {

    this.event[type].forEach((item)=>item(...data));

};

let event = new Event();

function fn1(){

  console.log('吃饭');

}

function fn2(){

    console.log('工作');

}

event.on('我的一天',fn1);

event.on('我的一天',fn2);

event.emit('我的一天');

3.Promise异步函数解决方案

  A执行完执行B,B执行完执行C。把A的返回值给B再给C

每一次执行,返回一个新的Promise实例(链式调用)

  代码易读

let p1 = new Promise(function(resolve,reject){

  reject(10000000);

});

p1.then(function(value){

  console.log('成功1=',value);

},function(reason){

  console.log('失败1=',reason);

});

p1.then(function(value){

  console.log('成功2=',value);

},function(reason){

  console.log('失败2=',reason);

});

4.Generator生成器函数

调用一个生成器函数它不会立刻执行

它返回一个迭代器函数,每调用一次next就可以返回一个值对象

function *go(a){

    console.log(1);

    let b =  yield a;

    console.log(2);

    let c = yield b;

    console.log(3);

    return c;

}

let it = go("a值");

let r1 = it.next();

let r2 = it.next('B值');

5.Co

co是一个为Node.js和浏览器打造的基于生成器的流程控制工具,借助于Promise,你可以使用更加优雅的方式编写非阻塞代码。

let fs = require('fs');

function readFile(filename) {

  return new Promise(function (resolve, reject) {

    fs.readFile(filename, function (err, data) {

      if (err)

        reject(err);

      else

        resolve(data);

    })

  })

}

function *read() {

  let template = yield readFile('./template.txt');

  let data = yield readFile('./data.txt');

  return template + '+' + data;

}

co(read).then(function (data) {

  console.log(data);

}, function (err) {

  console.log(err);

});

function co(gen) {

  let it = gen();

  return new Promise(function (resolve, reject) {

    !function next(lastVal) {

      let {value, done} = it.next(lastVal);

      if (done) {

        resolve(value);

      } else {

        value.then(next, reason => reject(reason));

      }

    }();

  });

}

6.Async/ await

可以实现和co一样的功能

结构简单,可读性强

let fs = require('fs');

function readFile(filename) {

*  return new Promise(function (resolve, reject) {*

*    fs.readFile(filename, 'utf8', function (err, data) {*

*      if (err)*

*        reject(err);*

*      else*

*        resolve(data);*

*    })*

*  })*

}

async function read() {

*  let template = await readFile('./template.txt');*

*  let data = await readFile('./data.txt');*

*  return template + '+' + data;*

}

let result = read();

result.then(data=>console.log(data));

前言

ECMAScript 6.0(以下简称 ES6)是 JavaScript 语言的下一代标准,已经在 2015 年 6 月正式发布了。它的目标,是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。

这句话基本涵盖了为什么会产生ES6这次更新的原因——编写复杂的大型应用程序。

回顾近两年的前端开发,复杂度确实在快速增加,近期不论从系统复杂度还是到前端开发人员数量应该达到了一个饱和值,换个方式说,没有ES6我们的前端代码依旧可以写很多复杂的应用,而ES6的提出更好的帮我们解决了很多历史遗留问题,另一个角度ES6让JS更适合开发大型应用,而不用引用太多的库了。

本文,简单介绍几个ES6核心概念,个人感觉只要掌握以下新特性便能愉快的开始使用ES6做代码了!

这里的文章,请配合着阮老师这里的教程,一些细节阮老师那边讲的好得多:

除了阮老师的文章还参考:

PS:文中只是个人感悟,有误请在评论提出

模块Module的引入

都说了复杂的大型应用了,所以我们第一个要讨论的重要特性就是模块概念,我们做一个复杂的项目必定需要两步走:

① 分得开,并且需要分开

② 合得起来

我们普遍认为没有复杂的应用,只有分不开的应用,再复杂的应用,一旦可以使用组件化、模块化的方式分成不同的小单元,那么其难度便会大大降低,模块化是大型、复杂项目的主要拦路虎。为了解决这个问题,社区制定了一些模块加载方案,对于浏览器开发来说,我们用的最多的是AMD规范,也就是大家熟知的requireJS,而ES6中在语音标准层面实现了模块功能,用以取代服务端通信的CommonJS和AMD规范,成为了通用的规范,多说无益,我们这里上一段代码说明:

/* validate.js 多用于表单验证 */ export function isEmail (text) { 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,}))$/; return reg.test(text); } export function isPassword (text) { var reg = /^[a-zA-Z0-9]{6,20}$/; return reg.test(text); }

1
2
3
4
5
6
7
8
9
10
11
12
/*
validate.js 多用于表单验证
*/
export function isEmail (text) {
    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,}))$/;
    return reg.test(text);
}
 
export function  isPassword (text) {
    var reg = /^[a-zA-Z0-9]{6,20}$/;
    return reg.test(text);
}

那么我们现在想在页面里面使用这个工具类该怎么做呢:

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <!-- 请注意这里type=module才能运行 --> <script type="module"> import {isEmail} from './validate.js'; var e1 = 'dddd'; var e2 = 'yexiaochai@qq.com' console.log(isEmail(e1)) console.log(isEmail(e2)) </script> </body> </html>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<!-- 请注意这里type=module才能运行 -->
<script type="module">
    import {isEmail} from './validate.js';
    var e1 = 'dddd';
    var e2 = 'yexiaochai@qq.com'
    console.log(isEmail(e1))
    console.log(isEmail(e2))
</script>
</body>
</html>

ES6中的Module提出,在我这里看来是想在官方完成之前requireJS干的工作,这里也有一些本质上的不一样:

① requireJS是使用加载script标签的方式载入js,没有什么限制

② import命令会被js引擎静态分析,先于模块其他语句执行

以上特性会直接给我们带来一些困扰,比如原来我们项目控制器会有这么一段代码:

var viewId = ''; //由浏览器获取试图id,url可能为?viewId=booking|list|... //如果不存在则需要构建,记住构建时需要使用viewdata继承源view requirejs(viewId, function(View) { //执行根据url参数动态加载view逻辑 })

1
2
3
4
5
var viewId = ''; //由浏览器获取试图id,url可能为?viewId=booking|list|...
//如果不存在则需要构建,记住构建时需要使用viewdata继承源view
requirejs(viewId, function(View) {
    //执行根据url参数动态加载view逻辑
})

前面说过了,import命令会被js引擎静态分析,先于模块其他语句执行,所以我们在根本不能将import执行滞后,或者动态化,做不到的,这种写法也是报错的:

if (viewId) { import view from './' + viewId; }

1
2
3
if (viewId) {
  import view from './' + viewId;
}

图片 1

这种设计会有利于提高编译器效率,但是之前的动态业务逻辑就不知道如何继续了?而ES6如果提供import的方法,我们变可以执行逻辑:

import(viewId, function() { //渲染页面 })

1
2
3
import(viewId, function() {
    //渲染页面
})

事实上他也提供了:

图片 2

现在看起来,JS中的模块便十分完美了,至于其中一些细节,便可以用到的时候再说了

ES6中的类Class

我们对我们的定位一直是非常清晰的,我们就是要干大项目的,我们是要干复杂的项目,除了模块概念,类的概念也非常重要,我们之前用的这种方式实现一个类,我们来温故而知新。

当一个函数被创建时,Function构造函数产生的函数会隐式的被赋予一个prototype属性,prototype包含一个constructor对象

而constructor便是该新函数对象(constructor意义不大,但是可以帮我们找到继承关系)

每个函数都会有一个prototype属性,该属性指向另一对象,这个对象包含可以由特定类型的所有实例共享的属性和方法

每次实例化后,实例内部都会包含一个[[prototype]](__proto__)的内部属性,这个属性指向prototype

① 我们通过isPrototypeOf来确定某个对象是不是我的原型 ② hasOwnPrototype 可以检测一个属性是存在实例中还是原型中,该属性不是原型属性才返回true

1
2
① 我们通过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);

1
2
3
4
5
6
7
8
var Person = function (name, age) {
    this.name = name;
    this.age = age;
};
Person.prototype.getName = function () {
    return this.name;
};
var y = new Person('叶小钗', 30);

图片 3

为了方便,使用,我们做了更为复杂的封装:

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*functions*(([^()]*?))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; }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
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*functions*(([^()]*?))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;
}

View Code

这里写一个demo:

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');

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
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');

图片 4

ES6中直接从标准层面解决了我们的问题,他提出了Class关键词让我们可以更好的定义类,我们这里用我们ES6的模块语法重新实现一次:

export class AbstractView { constructor(opts) { opts = opts || {}; this.wrapper = opts.wrapper || $('body'); //事件集合 this.events = {}; this.isCreate = false; } on(type, fn) { if (!this.events[type]) this.events[type] = []; this.events[type].push(fn); } trigger(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() { throw '必须重写'; } create() { this.root = $(this.createHtml()); this.wrapper.append(this.root); this.trigger('onCreate'); this.isCreate = true; } show() { if (!this.isCreate) this.create(); this.root.show(); this.trigger('onShow'); } hide() { this.root.hide(); } } export class Alert extends AbstractView { createHtml() { return '<div class="alert">这里是alert框</div>'; } } export class AlertTitle extends Alert { constructor(opts) { super(opts); this.title = ''; } createHtml() { return '<div class="alert"><h2>' + this.title

  • '</h2>这里是带标题alert框</div>'; } setTitle(title) { this.title = title; this.root.find('h2').html(title) } } export class AlertTitleButton extends AlertTitle { constructor(opts) { super(opts); this.on('onShow', function () { var bt = $('<input type="button" value="点击我" />'); bt.click($.proxy(function () { alert(this.title); }, this)); this.root.append(bt) }); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
export class AbstractView {
    constructor(opts) {
        opts = opts || {};
        this.wrapper = opts.wrapper || $('body');
        //事件集合
        this.events = {};
        this.isCreate = false;
    }
    on(type, fn) {
        if (!this.events[type]) this.events[type] = [];
        this.events[type].push(fn);
    }
    trigger(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() {
        throw '必须重写';
    }
    create() {
        this.root = $(this.createHtml());
        this.wrapper.append(this.root);
        this.trigger('onCreate');
        this.isCreate = true;
    }
    show() {
        if (!this.isCreate) this.create();
        this.root.show();
        this.trigger('onShow');
    }
    hide() {
        this.root.hide();
    }
}
export class Alert extends AbstractView {
    createHtml() {
        return '<div class="alert">这里是alert框</div>';
    }
}
export class AlertTitle extends Alert {
    constructor(opts) {
        super(opts);
        this.title = '';
    }
    createHtml() {
        return '<div class="alert"><h2>' + this.title + '</h2>这里是带标题alert框</div>';
    }
    setTitle(title) {
        this.title = title;
        this.root.find('h2').html(title)
    }
}
export class  AlertTitleButton extends AlertTitle {
    constructor(opts) {
        super(opts);
        this.on('onShow', function () {
            var bt = $('<input type="button" value="点击我" />');
            bt.click($.proxy(function () {
                alert(this.title);
            }, this));
            this.root.append(bt)
        });
    }
}

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <script type="text/javascript" src="zepto.js"></script> <!-- 请注意这里type=module才能运行 --> <script type="module"> import {Alert, AlertTitle, AlertTitleButton} from './es6class.js'; var v1 = new Alert(); v1.show(); var v2 = new AlertTitle(); v2.show(); v2.setTitle('我是标题'); var v3 = new AlertTitleButton(); v3.show(); v3.setTitle('我是标题和按钮的alert'); </script> </body> </html>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script type="text/javascript" src="zepto.js"></script>
 
<!-- 请注意这里type=module才能运行 -->
<script type="module">
import {Alert, AlertTitle, AlertTitleButton} from './es6class.js';
var v1 = new Alert();
v1.show();
var v2 = new AlertTitle();
v2.show();
v2.setTitle('我是标题');
var v3 = new AlertTitleButton();
v3.show();
v3.setTitle('我是标题和按钮的alert');
</script>
</body>
</html>

这里的代码完成了与上面一样的功能,而代码更加的清爽了。

ES6中的函数

我们这里学习ES6,由大到小,首先讨论模块,其次讨论类,这个时候理所当然到了我们的函数了,ES6中函数也多了很多新特性或者说语法糖吧,首先我们来说一下这里的箭头函数

箭头函数

//ES5 $('#bt').click(function (e) { //doing something }) //ES6 $('#bt').click(e => { //doing something })

1
2
3
4
5
6
7
8
//ES5
$('#bt').click(function (e) {
    //doing something
})
//ES6
$('#bt').click(e => {
    //doing something
})

有点语法糖的感觉,有一个很大不同的是,箭头函数不具有this属性,箭头函数直接使用的是外部的this的作用域,这个想不想用看个人习惯吧。

本文由澳门新萄京app发布于计算机前端,转载请注明出处:javascript异步发展史,30分钟ES6从陌生到熟悉

关键词: