定义
提供一种方法顺序访问一个聚合对象种的各个元素,而又不需要暴露该对象的内部表示
把迭代过程从业务逻辑中分离出来,不用关心对象的内部构造,可以按顺序访问其中的每个元素
用途/场景
查找可用组件,进行数据处理
主要思想
内部迭代器
函数内部定义好迭代规则,完全接手整个迭代过程,外部只需要一次初始调用
外部迭代器
需要调用进行下一轮迭代的函数,可以手工控制迭代的过程或者顺序
1 | //使用内部迭代器实现 |
learn and share
提供一种方法顺序访问一个聚合对象种的各个元素,而又不需要暴露该对象的内部表示
把迭代过程从业务逻辑中分离出来,不用关心对象的内部构造,可以按顺序访问其中的每个元素
查找可用组件,进行数据处理
内部迭代器
函数内部定义好迭代规则,完全接手整个迭代过程,外部只需要一次初始调用
外部迭代器
需要调用进行下一轮迭代的函数,可以手工控制迭代的过程或者顺序
1 | //使用内部迭代器实现 |
定义
解决俩个软件实体间接口不兼容的问题,使用适配器模式之后,原本由于接口不兼容而不能工作的两个软件实体,可以一起工作
用途/场景
1.调用第三方(地图/支付)接口时,不同平台提供的相同功能的接口函数名不同,或者调用时需要进行的操作过程不同,
将这些过程封装/分类,通过适配器决定如何使用
2.处理数据,将不符合使用要求的数据结构转换成预期的样子
主要思想
将处理过程封装在适配器函数中,直接在适配器函数中进行处理,或者让适配器返回处理后的可适用结果
调用适配器传入需要处理的数据/条件,适配器进行处理或返回处理结果
注意事项
那些繁琐的处理代码始终是存在的,只不过在写业务的时候看不到它罢了
1 | 1.转换数据结构 |
对于SDK提供方,仅仅需要知道自己所需要的一些参数,然后按照自己的方式进行数据返回。
对于SDK调用方,仅仅需要我们约定好的通用的参数,以及按照约定的方式进行监听回调处理。
整合多个第三方SDK的任务交由适配器来做,然后将适配器的代码压缩,混淆,放在一个看不见的角落里去,这样的代码逻辑就会变得很清晰了
参考资料:适配器在JavaScript中的体现
保护代理
代理B可以帮助A过滤掉一些请求,可以直接在代理B中拒绝掉
用于控制不同权限的对象对目标对象的访问
虚拟代理
把一些开销很大的对象,延迟到真正需要它的时候才去创建
就一个类(通常也包括对象和函数)而言,应该仅有一个引起它变化的原因。
A想调用C,要经过B,B在中间做C的代理,用于判断什么时候可以执行C
比如多次调用缓存起来再一起执行C;传入相同参数只计算一次,将结果保存,以后传入相同参数直接返回结果,不再计算
1.虚拟代理合并 HTTP请求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//可以通过一个代理函数 proxySynchronousFile 来收集一段时间之内的请求, 后一次性发送给服务器。
var synchronousFile = function( id ){
console.log( '开始同步文件,id 为: ' + id );
};
var proxySynchronousFile = (function(){
var cache = [], // 保存一段时间内需要同步的 ID
timer; // 定时器
return function( id ){
cache.push( id ); //每次进来收集
if ( timer ){
// 保证不会覆盖已经启动的定时器
return;
}
timer = setTimeout(function(){
synchronousFile( cache.join( ',' ) ); // 2 秒后向本体发送需要同步的 ID 集合 到时间统一处理 clearTimeout( timer ); // 清空定时器
timer = null;
cache.length = 0; // 清空 ID 集合
}, 2000 );
}
})();
var checkbox = document.getElementsByTagName( 'input' );
for ( var i = 0, c; c = checkbox[ i++ ]; ){
c.onclick = function(){
if ( this.checked === true ){
proxySynchronousFile( this.id );
}
}
};
2.缓存代理1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22var mult = function(){
console.log( '开始计算乘积' );
var a = 1;
for ( var i = 0, l = arguments.length; i < l; i++ ){
a = a * arguments[i];
}
return a;
};
// 创建缓存代理的工厂
var createProxyFactory = function( fn ){
var cache = {};
return function(){
var args = Array.prototype.join.call( arguments, ',' );
if ( args in cache ){
return cache[ args ];
}
return cache[ args ] = fn.apply( this, arguments );
}
};
var proxyMult = createProxyFactory( mult ),
proxyMult( 1, 2, 3, 4 ); // 输出:24
proxyMult( 1, 2, 3, 4 ); // 输出:24
扩展:用于ajax异步请求数据,下次请求相同数据时,是否可以取消请求,直接用之前缓存的数据,适用固定数据
定义一系列的算法,把它们一个个封装起来,通过逻辑函数使他们可以相互替换
避免if-else多分支判断
定义一些系列算法(具体处理过程),把他们各自封装成策略类,算法被封装在策略类内部方法里。在客户对Context(如何调用算法的处理函数)发起请求时,Context总是把请求委托给这些策略对象中间的某一个进行计算
编写代理函数时,注意如何调用,让算法得到执行1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24let strategies = { //算法集合
"S": function( salary ){
return salary * 4;
},
"A": function( salary ){
return salary * 3;
},
"B": function( salary ){
return salary * 2;
}
};
var calculateBonus = function( level, salary ){ //Context,委托函数,负责调用哪个算法
return strategies[ level ]( salary );
};
console.log( calculateBonus( 'S', 20000 ) );//80000
console.log( calculateBonus( 'A', 10000 ) );//30000
变形:
var S = function( salary ){ return salary * 4; };
var A = function( salary ){ return salary * 3; };
var B = function( salary ){ return salary * 2; };
var calculateBonus = function( func, salary ){ return func( salary ); };
calculateBonus( S, 10000 )
1 | //具体策略方法 |
单例模式定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点
主要思想<通用惰性单例>
管理单例逻辑(保证实现单例的过程)和创建过程(具体业务逻辑)分开,创建过程函数作为参数给到管理单例逻辑函数中,
通过调用单例逻辑函数,业务逻辑函数只执行一次
用途
1.创建单一对象
2.利用业务逻辑仅执行一次,进行事件挂载
注意事项
业务逻辑函数一定要return 一个真正
1 | var getSingle = function(fn){ |
把不变的部分隔离出来,把可变的部分封装起来
多态最根本的作用就是通过把过程化的条件分支语句转化为对象的多态性,从而消除这些条件分支语句
(每个对象应该做什么,已成为了该对象的一个方法,被安装在对象的内部,每个对象负责它们自己的行为,所以这些对象可以根据同一个消息,有条不紊的分别进行各自的工作 ====> 将行为分布在各个对象中,并让对象各自负责自己的行为<—面向对象设计优点)
对象以方法的形式包含了过程,而闭包则是在过程中以环境的形式包含了数据
1.原型继承过程:对象把请求委托给它的构造器的原型
1 | var A = function(){}; |
2.用new 调用构造器时,如果构造器显示返回了一个object类型的对象,那么此次运算结果最终会返回这个对象,而不是this;
若不显示返回任何数据,或者是返回一个非对象类型的数据,则会正常返回this
3.使用Dom 方法时,注意是否会造成this 丢失1
2
3
4<script>
let getId = document.getElementById; //原来 getElementById this指向document
getId('DIV1') //现在指向window
</script>
4.使用call/apply时,如果第一个参数传入null,那么函数体内的this会指向window
5.闭包作用:封装变量;延续局部变量寿命
闭包和内存泄露有关系的地方是,使用闭包的同时比较容易形成循环引用,如果闭包的作用域链中保存着一些DOM节点,就比较可能造成内存泄露。因为IE浏览器中,由于BOM和DOM对象是使用C++以COM对象的方式实现的,而COM对象的垃圾收集机制采用引用计数策略,这时,如果两个对象之间形成了循环引用,那么这两个对象都无法被回收。
解决办法是将循环引用中的变量设为null,方便回收。
1 | var getSingle = function(fn){ |
1 | Function.prototype.before = function(beforeFn){ |
一个curring的函数首先会接受一些参数,接受了这些参数之后,该函数并不会立即求值,而是继续返回另外一个函数,刚才传入的参数在函数形成的闭包中被保存起来,待到函数被真正需要求值的时候,之前传入的所有参数都会被一次性用于求值。
1 | var currying = function(fn){ |
多次重复调用,按时间限制调用次数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18var throttle = function(fn,interval){
var _self = fn,timer,firstTime = true;
return fucntion(){
var args = arguments,_me = this;
if(fiestTime){
_self.apply(_me,args);
return firstTime = false;
}
if(timer){
return false;
}
timer = setTimeout(function(){
clearTimeout(timer);
timer = null;
_self.apply(_me,args);
},interval||500)
}
}
大批量数据要执行同一函数,分批次执行任务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 var timeChunk = function(ary,fn,count){
var obj,t;
var len = ary.length;
var start = function(){
for(var i = 0;i<Math.min(count||1,ary.length);i++){
var obj = ary.shift();
fn(obj);
}
}
return function(){
t=setInterval(function(){
if(ary.length === 0){
return clearInterval(t)
}
start();
},200)
}
}
1 | var addEvent = function(elem,type,handler){ |
1 | <edit-modal dismiss="dismiss()" config="model.config" after-save="model.afterSave()" aaa="model.hello"></edit-modal> |
1 | export function editModalDirective() { |
link函数仅在编译时执行一次,三个参数:scope, ele, attrs
即是当前的指令DOM对象,因此可以在ele上绑定触发事件,
但谨慎在数据上利用事件订阅触发机制进行事件绑定,
因为如果在外层包react组件的情况下,可能会导致数据刷新,但没有事件绑定,从而无法触发事件
注意在获取深层子dom时可能会获取不到
指ele所在dom上的属性集合,没有在scope属性中声明的属性也能看到
但要注意,属性值就是标签上变量的名,不是标签上变量指代的值
标签上属性赋值变量的值在参数scope中
link函数参数scope指向当前指令的作用域
参数scope的ctrl属性(controllerAs属性的值)即EditModalCtrl
因为这里配置了controller,bindToController,controllerAs,
所以在scope属性中声明的属性(config)方法(dismiss,after)会被归并到controller中,会以ctrl的属性形式出现,
即如果我想拿到属性scope中声明的config,可以这样:scope.ctrl.config
同时可以在EditModalCtrl中访问获取config,即this.config
另外如果没有在属性scope中声明的属性aaa,但在标签中进行了配置(aaa=”model.hello”),是无效的,ctrl上是不会有aaa属性的,即传递不进来
因此可以在link函数中拿到父作用域传递过来的值,分情况进行不同处理
建立子域,声明所在标签的属性名,以及传值方式,controler存在情况下被合并
@ 在 directive 中使用 xxx 属性绑定父 scope 中的属性。当改变父 scope 中属性的值的时候,directive 会同步更新值,当改变 directive 的 scope 的属性值时,父 scope 无法同步更新值。使用{{}}引用绑定值1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24app.controller("myController", function ($scope) {
$scope.name = "hello world";
}).directive("isolatedDirective", function () {
return {
scope: {
name: "@"
},
template: 'Say:{{name}} <br>改变隔离scope的name:<input type="buttom" value="" ng-model="name" class="ng-pristine ng-valid">'
}
})
<div ng-controller="myController">
<div class="result">
<div>父scope:
<div>Say:{{name}}<br>改变父scope的name:<input type="text" value="" ng-model="name"/></div>
</div>
<div>隔离scope:
<div isolated-directive name="{{name}}"></div>
</div>
<div>隔离scope(不使用{{name}}):
<div isolated-directive name="name"></div>
</div>
</div>
= 无论是改变父 scope 还是隔离 scope 里的属性,父 scope 和隔离 scope 都会同时更新属性值,因为它们是双向绑定的关系,使用“”引用绑定值1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25app.controller("myController", function ($scope) {
$scope.user = {
name: 'hello',
id: 1
};
}).directive("isolatedDirective", function () {
return {
scope: {
user: "="
},
template: 'Say:{{user.name}} <br>改变隔离scope的name:<input type="buttom" value="" ng-model="user.name"/>'
}
})
<div ng-controller="myController">
<div>父scope:
<div>Say:{{user.name}}<br>改变父scope的name:<input type="text" value="" ng-model="user.name"/></div>
</div>
<div>隔离scope:
<div isolated-directive user="user"></div>
</div>
<div>隔离scope(使用{{name}}):
<div isolated-directive user="{{user}}"></div>
</div>
</div>
& 用来绑定函数,在directive中调用父域中的函数
1 | app.controller("myController", function ($scope) { |
参考: https://blog.coding.net/blog/angularjs-directive-isolate-scope?type=early
类似于VUE的slot,但不如slot的灵活强,更倾向于定制插入,在固定位置插入
设置属性 transclude:true
在模板中将 <ng-transclude></ng-transclude>放到要插入的位置
应用时<my-labe><span>1233</span></my-label>;
<span>1233</span> 会自动插入<ng-transclude></ng-transclude>之间
设置属性 transclude :{模板中flag:页面应用时标签名的驼峰式,}
在模板中制定位置放置flag :<div ng-transclude=”flag”></div>
应用时 <my-label><my-title>123</my-title></my-label>
123 会自动插入<div ng-transclude=”flag”></div>之间
参考:https://segmentfault.com/a/1190000004586636
使用transcludeFn给transclude进来的Dom手动制定scope,
transcludeFn可来自compile,link的参数,或者controller的注入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.directive("todo", function(){
return {
restrict:"E",
transclude:"true",
template:"<header>{{header}}</header><div><span>这里是自定义区域</span><content-transclude></content-transclude></div>"
scope:{
header:"@"
},
controller:["$transclude",function(transcludeFn){
this.transcludeFn = transcludeFn;
}]
};
})
.directive("contentTransclude",funtion(){
return {
restrict:"E",
require:"^todo",
link:function(scope,element,attr,todoController){
todoController.transcludeFn(scope.$parent, function(transcludeContent){
element.append(transcludeContent);
});
}
};
})
1.V8占用操作系统内存很少,为什么?
类似于体育场办演唱会,演唱会结束后,清完垃圾才能开下一场,
如果场子小,2小时搞定,如果场子大,人多产生垃圾多,清理时间就会变长
在64位操作系统中占用1.4G,32位操作系统中占0.7G
表面原因是JS设计之初是用于给浏览器跑脚本,脚本跑完一次就结束了,不需要长期运行占用内存,以上这些空间足够
实际原因是垃圾回收机制在进行时,会停掉正在运行的整个程序,回收结束后继续运行,
而一次小型垃圾回收几MB会消耗50ms,程序运行就会停止50ms
如果说给的内存太大,那么跑的程序肯定会多来,产生的垃圾也会随之增多,回收时间就变长,结果就会导致程序运行间歇性执行
这肯定是不能让人接受的
2.垃圾回收算法
V8引擎将所占内存分成两块,
一个叫新生代空间,用于存放生命周期较短的变量,比如一个函数里的局部变量,函数运行完即释放
一个叫老生代空间,占引擎内存的90%以上,用于存放生命周期较长的变量,比如全局变量,从程序运行开始就存在
新生代空间垃圾回收算法就是复制
新生代空间又分两部分,一个叫from一个叫to
回收时会将from中活着的变量复制到TO空间,然后将from空间清空,再将from和to空间内容对调
原理是用空间换取时间,有一半新生代空间处于空置状态,而对调减少了清理磁盘空间的过程,从而节省了时间
老生代垃圾回收算法 就是标记删除整理
标记死掉的没有生命周期的变量,边删除标记的变量,边整理碎片(内存空间),将删除导致的磁盘碎片用活着的变量通过移动补齐
3.新生代晋升老生代条件
新生代空间的变量经历过一次复制,即经过一次垃圾回收,而且TO空间已经使用了25%,那么这个变量就会被安排到老生代空间
4.利用node查看内存情况
cmd 敲击node命令
使用process.memoryUsage()查看,返回包含以下属性对象
rss:V8占用的总内存(占用比使用会多一些)
heapTotal:当前程序被分配到的总内存
heapUsed:当前程序使用的内存
external:调用的部分C++内存
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 function getMB(){
var mem = process.memoryUsage()
var format = function(bytes){
return (bytes/1024/1024).toFixed(2)+'MB'
}
console.log('heapTotal:'+format(mem.heapTotal)+' heapUsed:'+format(mem.heapUsed))
}
let a = ()=>{
let b = new Array(1024*1024)
return b
}
let b = a()
let d = a()
let e = a()
let f = a()
let g = a()
// let h = a()
// let i = a()
// let j = a()
// let k = a()
// let l = a()
// let m = a()
// let n = a()
// let o = a()
// let p = a()
// let q = a()
getMB()
在cmd中转到文件所在目录,node 包含以上代码的js文件,即可打印内存使用情况
5.优化内存技巧
尽量不要定义全局变量
全局变量记得销毁掉
用匿名自执行函数变全局为局部
尽量避免闭包
在使用立即执行函数实现闭包时,
函数内代码,为同步操作时,占用内存与不使用闭包相同
但如果包含异步操作,则函数内用到的外界变量会一直处于被引用状态
得不到释放从而导致内存增加
解决:使用完释放;避免使用闭包
6.导致内存泄露
滥用缓存
js中导致内存泄露的常见场景
闭包函数
全局变量
对象属性循环使用
Dom节点删除时未解绑事件
Map和Set数据类型的数据属性直接删除,导致指向的数据没有被删除
按照网站指引,配置服务器相关参数,提交订单,付钱
需要注意的是镜像就是将来在服务上安装的操作系统
如果这里选错,稍后还可以更换
相当于更换操作系统
更换操作系统
更换系统盘(公共镜像)
在云端服务器搭建nodejs开发环境
远程连接ECS实例
分盘
部署Node.js项目(CentOS)
安装git(仅供参考)
安装好开发环境后就可以像在本地运行项目一样克隆项目运行项目
我们在购买域名后,还要对域名进行备案
域名备案系统
按照流程进行备案即可
我们需要购买云盾证书服务
购买页面
在专业版OV SSL下选择品牌Symantec,
然后保护类型选择1个域名
会发现证书类型出现‘免费型DV SSL’,选择这个就可以买到免费证书
购买后根据申请流程填写资料等待流程走完就可以拿到证书
注意1.购买的域名在云解析DNS服务中又绑定,DNS会自动生成映射,2.选择系统生成CSR
拿到证书后
在CA证书管理页面可以下载到xxxxx.key,xxxx.pem文件
因为我这里用nodejs搭建服务器,所以只需将文件放入项目,修改项目文件为1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19const Koa = require('koa');
const fs = require('fs');
const https = require('https');
const http = require('http');
var enforceHttps = require('koa-sslify');
const app = new Koa();
//将http请求强制转换成https请求
app.use(enforceHttps());
var options = {
key: fs.readFileSync('./ssl/214645895910665.key'),
cert: fs.readFileSync('./ssl/214645895910665.pem')
};
http.createServer(app.callback()).listen(80);
https.createServer(options, app.callback()).listen(443);
console.log('server is running....');
还要注意安全组有没有设置放行https的443端口
进入云服务器控制台
网络和安全下安全组, 点击配置规则, 进入规则列表
如果没有443端口,则点击添加安全组规则,端口设置为443即可
generator函数即生成器函数,可以理解为用于产生生成器的函数
yield 关键字是只能用于generator函数,作为异步断点,相当于continue+return功能
next 关键字是生成器用于执行生成器函数的关键字,
即每调用一次,执行到yield定义的语句,把yield 后面的表达式结果当做返回值返回来
如果调用时传递了参数,则覆盖上次yield 的结果1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17function* helloworld(){
yield 'hello';
yield 'world';
let aa = 0;
let a = yield aa++; //yield赋值不用小括号
console.log(a);
yield 123+1;
console.log('Hello' + (yield 123)); //使用yield表达式用小括号
return 'endig';
}
let obj = helloworld();//并不会执行generator函数,只是生成生成器
obj.next();//{value: "hello", done: false}
obj.next();//{value: "world", done: false}
obj.next();//{value: 0, done: false}
obj.next('456');// 456 {value: 124, done: false} 如果不传值的话,a的值为undefined
obj.next();//{value: 123, done: false}
obj.next();// Helloundefined {value: "endig", done: true}; 不传参,yield返回undefined
next()是将yield表达式替换成一个值。
throw()是将yield表达式替换成一个return语句。
return()是将yield表达式替换成一个return语句。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19const g = function* (x, y) {
let result = yield x + y;
return result;
};
const gen = g(1, 2);
gen.next(); // Object {value: 3, done: false}
gen.next(1); // Object {value: 1, done: true}
// 相当于将 let result = yield x + y
// 替换成 let result = 1;
gen.throw(new Error('出错了')); // Uncaught Error: 出错了
// 相当于将 let result = yield x + y
// 替换成 let result = throw(new Error('出错了'));
gen.return(2); // Object {value: 2, done: true}
// 相当于将 let result = yield x + y
// 替换成 let result = return 2;
如果yield表达式后面跟的是一个遍历器对象,需要在yield表达式后面加上星号,表明它返回的是一个遍历器对象。这被称为yield表达式
yield后面的 Generator 函数(没有return语句时),等同于在 Generator 函数内部,部署一个for…of循环。
for…of循环可以自动遍历Generator函数时生成的Iterator对象,且此时不再需要调用next方法。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例1
function* inner() {
yield 'hello!';
}
function* outer1() {
yield 'open';
yield inner();
yield 'close';
}
var gen = outer1()
gen.next().value // "open"
gen.next().value // 返回一个遍历器对象
gen.next().value // "close"
function* outer2() {
yield 'open'
yield* inner()
yield 'close'
}
var gen = outer2()
gen.next().value // "open"
gen.next().value // "hello!"
gen.next().value // "close"
例2
let delegatedIterator = (function* () {
yield 'Hello!';
yield 'Bye!';
}());
let delegatingIterator = (function* () {
yield 'Greetings!';
yield* delegatedIterator;
yield 'Ok, bye.';
}());
for(let value of delegatingIterator) {
console.log(value);
}
// "Greetings!
// "Hello!"
// "Bye!"
// "Ok, bye."
如果yield*后面跟着一个数组,由于数组原生支持遍历器,因此就会遍历数组成员。1
2
3
4
5function* gen(){
yield* ["a", "b", "c"];
}
gen().next() // { value:"a", done:false }
上面代码中,yield命令后面如果不加星号,返回的是整个数组,加了星号就表示返回的是数组的遍历器对象。
Generator 函数返回的遍历器对象,可以继承prototype,但不能当做普通构造函数,不会返回this1
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
57function* g() {
this.a = 11;
}
let obj = g();
obj.next();
obj.a // undefined
//可以进行改造
//执行的是遍历器对象f,但是生成的对象实例是obj
function* F() {
this.a = 1;
yield this.b = 2;
yield this.c = 3;
}
var f = F.call(F.prototype);
f.next(); // Object {value: 2, done: false}
f.next(); // Object {value: 3, done: false}
f.next(); // Object {value: undefined, done: true}
f.a // 1
f.b // 2
f.c // 3
function* F() {
this.a = 1;
yield this.b = 2;
yield this.c = 3;
}
var f = F.call(F.prototype);
f.next(); // Object {value: 2, done: false}
f.next(); // Object {value: 3, done: false}
f.next(); // Object {value: undefined, done: true}
f.a // 1
f.b // 2
f.c // 3
function* gen() {
this.a = 1;
yield this.b = 2;
yield this.c = 3;
}
function F() {
return gen.call(gen.prototype);
}
var f = new F();
f.next(); // Object {value: 2, done: false}
f.next(); // Object {value: 3, done: false}
f.next(); // Object {value: undefined, done: true}
f.a // 1
f.b // 2
f.c // 3
1 | var clock = function*() { |
IMPORTANT!!!
Generator函数的暂停执行的效果,意味着可以把异步操作写在yield语句里面,等到调用next方法时再往后执行。这实际上等同于不需要写回调函数了,因为异步操作的后续操作可以放在yield语句下面,反正要等到调用next方法时再执行。所以,Generator函数的一个重要实际意义就是用来处理异步操作,改写回调函数。
只有当yield后面跟的函数先执行完,无论执行体里面有多少异步回调,都要等所有回调先执行完,才会执行等号赋值,以及再后面的操作。这也是yield最大的特性。
1 | function request(url) { |
第1步:将所有异步代码的每一步都封装成一个普通的、可以有参数的函数,比如上面的request函数。上面例子三个异步代码却只定义了一个request函数,因为request函数能复用。如果不能复用的话,请老老实实定义三个普通函数,函数内容就是需要执行的异步代码。
第2步:定义一个生成器函数,把流程写进去,完全的同步代码的写法。生成器函数可以有参数。
第三步:定义一个变量,赋值为迭代器对象。迭代器对象可以加参数,参数通常将作为流程所需的初始值。
第四步:变量名.next()。不要给这个next()传参数,传了也没用,因为它找不到上一个yield语句。
1 | //在上述每一步异步中间,都间隔3秒 |
async 用于定义异步函数,相当于generator的*号,只不过async函数不需要像generator那样用next()去出发,只需要调用就能实现异步
await 用于async函数内部,作用是等待await 后面的异步流程结束后,执行asyn函数接下来的语句,相当于暂停符
async返回promise对象,因此可以连接then()或者catch()继续进行异步操作或者捕获错误
async函数内部return语句返回的值,会成为then方法回调函数的参数。
async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,
除非遇到return语句或者抛出错误。
也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。
正常情况下,await命令后面是一个 Promise 对象。如果不是,会被转成一个立即resolve的 Promise 对象。即可以等任意表达式的结果
因此 await 返回异步函数return的结果,或者异步函数返回的promise的resolve的参数1
2
3如果它等到的不是一个 Promise 对象,那 await 表达式的运算结果就是它等到的东西。
如果它等到的是一个 Promise 对象,await 就忙起来了,它会阻塞后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果。
只要一个await语句后面的 Promise 变为reject,那么整个async函数都会中断执行。
解决中断执行的方法有两种,
一种是把await放在try…catch…里面,例5
另一种是给await的promise链接catch{},例6
优化:如果两个异步操作没有任何联系,不存在继发关系,最好让他们同时触发 例8,例9
1 | 例1. |