SegmentFault 前端线索最新的文章
2019-03-17T16:53:10+08:00
https://segmentfault.com/feeds/blogs
https://creativecommons.org/licenses/by-nc-nd/4.0/
ES6 生成器简单运用
https://segmentfault.com/a/1190000018535431
2019-03-17T16:53:10+08:00
2019-03-17T16:53:10+08:00
长颈鹿
https://segmentfault.com/u/newbird_007
0
<h2>概念</h2>
<p>生成器是由生成器函数( <em>generator function</em> )运行后得到的,是可迭代的。</p>
<pre><code>function* gen() {
yield 'a';
yield 'b';
yield 'c';
}
let g = gen();
// "Generator { }"
</code></pre>
<h2>原理及简单运用</h2>
<p>生成器有一个很大的特点,它可以暂停内部代码运行,返回一个值给外部函数。(暂停后不会阻止其他代码运行)当外部调用其 <em>next</em> 方法后,会继续执行剩下的代码,并接受外部传来的一个参数。这个实现主要依赖于关键字 <em>yield</em> 。</p>
<blockquote>
<em>yield</em> 关键字使生成器函数执行暂停,<em>yield</em> 关键字后面的表达式的值返回给生成器的调用者。它可以被认为是一个基于生成器的版本的 <em>return</em> 关键字。</blockquote>
<pre><code>function* g(){
var a = yield 2;
console.log(a);
}
var it = g(); // 返回一个可迭代的生成器对象
console.log(it.next()); // 执行生成器函数内部代码,第一次返回 {done: false, value: 2}
it.next(3); // 继续执行生成器函数内部代码,同时向生成器传递参数3,最后返回 {done: true, value: undefined}
</code></pre>
<p><strong>一个简单的计数器</strong></p>
<pre><code>function* count(){
var n = 1;
while(true){
yield n++;
}
}
var it = count();
it.next(); // 1
it.next(); // 2
it.next(); // 3
</code></pre>
<h2>用同步方式写异步代码</h2>
<p>以前处理异步 <em>ajax</em> 请求结果,一般采用传递回调函数的方式。一旦遇到多层回调嵌套,代码的可读性会降低,并且调试起来也不方便。有了生成器之后,我们就可以用同步的方式写异步的代码。这听上去非常的有意思。我们的代码将会是这样的</p>
<pre><code>function foo(){
var result = asyncFun(); // asyncFun 是异步函数,result 是异步返回的结果
console.log(result);
}
</code></pre>
<p>当然,上面的代码并不能得到正确的结果,它只是一个设想。我们正打算用生成器来实现,而且这是可行的。想想生成器有哪些特点:</p>
<ol>
<li>它能暂停,向外部返回值</li>
<li>能继续执行剩下的代码,并接受外部传来的参数</li>
</ol>
<p>这就足够了。有了生成器函数,现在我们重新来设计代码:</p>
<pre><code>function* foo(){
// 这里遇到了异步方法,必须停下来。
// 等待异步方法执行完毕,并返回结果,继续运行代码。当然,同步 ajax 不能算,它不是异步
// 输出结果
}
</code></pre>
<p>静下来想一想有哪些关键字,与暂停、继续有关。停下来...继续...停下来...继续...停下来...继续...<em>Don't...Stop...Don't...Stop...Don't...Stop</em>......这两个词就是 <em>yield</em>、<em>next</em>.</p>
<pre><code>function *foo(){
var result = yield asyncFun(next);
console.log(result);
}</code></pre>
<p>当代码遇到 <em>yield</em> 会暂停,这个时候 <em>asyncFun</em> 函数是不会暂停的,会执行,等执行完毕,再调用生成器的 <em>next</em> 方法,并将返回结果作为参数传给 <em>next</em>。由于在生成器函数内部我们拿不到 <em>next</em>,必须借助于全局变量来传递 <em>next</em>。</p>
<pre><code>var next, gn;
function asyncFun(next){
// 模拟异步请求
setTimeout(function(){
// 返回一个随机数
next(Math.random())
}, 1000)
}
function* foo(){
var result = yield asyncFun(next);
console.log(result);
}
gn = foo();
next = gn.next.bind(gn);
next(); // 打印随机数
</code></pre>
<p>这样写,运行看上去有些繁重。可以写一个包装函数运行含有异步代码的生成器函数。</p>
<pre><code>function asyncFun(next){
// 模拟异步请求
setTimeout(function(){
// 返回一个随机数
next(Math.random())
}, 1000)
}
function* foo(){
var result = yield function(next){asyncFun(next)};
console.log(result);
}
function wrapFun (gFn){
var gn = foo(),
next = gn.next.bind(gn);
next().value(next);
}
wrapFun(foo);
</code></pre>
<p><a href="https://link.segmentfault.com/?enc=B4eA9IZYzEAeYiWOUXHJ9A%3D%3D.zAYZ83Eyf85xmy7UI99IiYE7CE4ol0lCg5QfiFiteWFPRsWKI9cEIJ%2FZ4QmCMMjr" rel="nofollow">演示地址</a></p>
<p>不过,自从出了 <em>Promise</em> 和 <em>await</em> 之后,更多的是用这个组合,其使用也更简单,范围也更广。</p>
答案——腐烂的橘子算法题目
https://segmentfault.com/a/1190000018511353
2019-03-14T23:13:29+08:00
2019-03-14T23:13:29+08:00
长颈鹿
https://segmentfault.com/u/newbird_007
0
<p><a href="https://segmentfault.com/q/1010000018509959?utm_source=tag-newest">题目要求请戳</a></p>
<p>假如一个<em>M</em> x <em>M</em> 格子的盒子里有 <em>n</em> (<em>n</em> > 0)个新鲜橘子,有 <em>m</em> 个烂橘子。每隔一分钟我们去这个盒子里面数一数,直到烂橘子没有增加。结果就是:</p>
<p>1.并且还有新鲜的橘子,返回 -1。</p>
<p>2.没有新鲜的橘子,返回分钟数。</p>
<p>第一步将二维数组初始化为一维数组:</p>
<pre><code>function initData(a){
var
result = [],
n = 0,
m = 0,
j = 0;
for(j = 0; j < M; j++) {
result[j * M + 0] = { status: a[j][0], willBletOthers: a[j][0] === 2 };
result[j * M + 1] = { status: a[j][1], willBletOthers: a[j][1] === 2 };
result[j * M + 2] = { status: a[j][2], willBletOthers: a[j][2] === 2 };
if (a[j][0] == 1) {
n += 1;
}
if (a[j][1] == 1) {
n += 1;
}
if (a[j][2] == 1) {
n += 1;
}
if (a[j][0] == 2) {
m += 1;
}
if (a[j][1] == 2) {
m += 1;
}
if (a[j][2] == 2) {
m += 1;
}
}
return {
result: result,
n: n,
m: m
};
}
</code></pre>
<p>每隔一分钟,一个放在 <em>x</em> 位置的格子的烂橘子,其他四个位置 <em>x + 1</em>、<em>x - 1</em>、<em>x + M</em> 、<em>x - M</em>,都会腐烂(注意边界)。如果这四个位置部分位置有新鲜的橘子,那么腐烂还会继续。然后继续观察其他位置,直到最后一个格子。下一分钟再来看</p>
<pre><code>function blet(index, result){
var bletNum = 0;
if(-1< index + 1 && index + 1 < M*M && result[index + 1].status == 1){
bletNum += 1;
result[index + 1] = {status: 2, willBletOthers: false}
}
if(-1< index + M && index + M < M*M && result[index + M].status == 1){
bletNum += 1;
result[index + M] = {status: 2, willBletOthers: false}
}
if(-1< index - 1 && index - 1 < M*M && result[index - 1].status == 1){
bletNum += 1;
result[index - 1] = {status: 2, willBletOthers: true}
}
if(-1< index - M && index - M < M*M && result[index - M].status == 1){
bletNum += 1;
result[index - M] = {status: 2, willBletOthers: true}
}
return bletNum;
}
var
M = 3,
rawData = [[2, 1, 1], [1, 1, 0], [0, 1, 1]];
function letGo(rawData){
var data = initData(rawData),
result = data.result,
n = data.n,
m = data.m,
k,
mins = 0,
sum;
if(m == 0 && n > 0){
return -1;
}
if(m == 0 && n == 0){
return 0;
}
while (n > 0 && m > 0) {
mins += 1;
sum = 0;
for (k = 0; k < result.length; k++) {
if (result[k].status == 2) {
if (result[k].willBletOthers) {
sum += blet(k, result);
} else {
result[k].willBletOthers = true;
}
}
}
if (sum === 0) {
break;
} else {
n -= sum;
m += sum;
}
}
return mins;
}
console.log(letGo(rowData));
</code></pre>
前端笔试题:数组转树形结构
https://segmentfault.com/a/1190000018510046
2019-03-14T21:17:46+08:00
2019-03-14T21:17:46+08:00
长颈鹿
https://segmentfault.com/u/newbird_007
5
<p>初始化数据</p>
<pre><code>var data = [
{ parentId: 0, id: 1, value: '1' },
{ parentId: 3, id: 2, value: '2' },
{ parentId: 0, id: 3, value: '3' },
{ parentId: 1, id: 4, value: '4' },
{ parentId: 1, id: 5, value: '5' }
]</code></pre>
<p>输出结果</p>
<pre><code>[
{
id:1,
value:'1',
children:[
{id:4,value:'4',children:[]},
{id:5,value:'5',children:[]}
]
},{
id:3,
value:'3',
children:[
{id:2,value:'2',children:[]}
]
}
]
</code></pre>
<p>转换函数</p>
<pre><code>function convertArrayToTree(arr){
let
idsMap = {},
result = [],
node,
parentNode,
item,
j,
leng = arr.length;
for(j = 0; j <= leng; j++ ){
item = arr[j];
if(!idsMap['$'+ item.id]){
node = {id: item.id, children: []}
idsMap['$'+ item.id] = node;
}else{
node = idsMap['$'+ item.id];
}
node.value = item.value;
if(item.parentId === 0){
result.push(node);
}else{
if(!idsMap['$'+ item.parentId]){
parentNode = {id: item.parentId, children: []}
idsMap['$'+ item.parentId] = parentNode;
}else{
parentNode = idsMap['$'+ item.parentId];
}
parentNode.children.push(node);
}
}
return result;
}
</code></pre>
<p>这个转换函数不受数据关系层级的限制,无论有多少层关系,只要一次循环就能搞定。假如一群人都是爷爷、父亲、儿子、孙子的关系,每个人只知道自己的位置和他父亲的位置。所有人拿一根线,从他自己的位置开始,走到他父亲的位置,那么这个树形结构就完成了。</p>
<p>在这里,我们用一个 <em>idsMap</em> 比喻这样的集合,<em>id</em> 作位健值。每个节点有自己和他父节点 <em>id</em>,找到父节点,然后把他自己 <em>push</em> 到父节点的 <em>children</em> 中。</p>
<p><img src="/img/bVbpPq2?w=1232&h=518" alt="图片描述" title="图片描述"></p>
<p>只要每个节点都做完了,树形就出来了。</p>
HTML 标签语义
https://segmentfault.com/a/1190000018450222
2019-03-10T15:20:36+08:00
2019-03-10T15:20:36+08:00
长颈鹿
https://segmentfault.com/u/newbird_007
8
<h2>什么是HTML</h2>
<p><strong>HTML</strong>全称是HyperText Markup Language,翻译过来就是<strong>超文本标记语言</strong>。它定义了网页的内容和结构。</p>
<h2>为什么要使用语义化标签</h2>
<ul><li>可读性、可维护性</li></ul>
<p><em>html</em>有很多标签,每个标签都有它的语义,比如 <em>h1</em>~<em>h6</em> 表示不同重要程度的标题。虽然平时很多同学用无语义的标签 <em>div</em> 、<em>span</em> 等也能实现相同的效果,但是可读性就差了很多。也许有同学觉得可读性没有变差啊,那是因为我们当中很多人平时就没有使用语义标签的习惯,全都是 <em>div</em>。</p>
<p>由于 <em>html</em> 是标记语言,是声明式写法,不具有可编程性,逻辑表达能力不强。<em>html</em> 标签就像其他可编程语言例如:<em>Java</em> 、<em>C</em> 、<em>Javascript</em> 等的变量,如果一位程序员随意给变量命名,代码的可读性会非常差,后期维护成本也会非常高。</p>
<ul><li>SEO</li></ul>
<p>由于绝大多数网站的域名难以记住,很多人上网都是通过门户网站上网。但是,搜索引擎出来以后,使用搜索引擎上网越来越成为一种趋势。搜索引擎可以根据你输入的关键字,直接搜出相关网站和内容,免去人工一个一个去查找。</p>
<p>可是搜索引擎是如何知道网站是我们要找的内容呢,搜索引擎派出<em>爬虫</em>会时时刻刻的去访问网络上的各种网站,当它获取到一个个网站的 <em>html</em> 文档,会根据文档头部内容里面的 <em>meta</em> 数据判断出网站类型以及内容,并保存到数据库。(恶意诱导爬虫另当别论)</p>
<p><em>seo</em> 重要吗?当然非常的重要。<em>seo</em> 能给网站带来流量,流量就像网站的血液一般,没有访问量,网站就等于不存在。<em>seo</em> 现在已经成为了互联网的一个垂直行业。做得好的,年入百万不是梦。但是,<em>seo</em>运营并仅仅包含这些。</p>
<ul><li>可访问性</li></ul>
<p>一个好的网站应该尽可能让更多的人无障碍浏览。例如盲人借助屏幕阅读器,也可以上网购物。如果网站的购物车按钮是用 <em>div</em> 写的,那么屏幕阅读器识别起来会非常的困难。我们为什么要让盲人也可以上网呢,这是一个非常有意思的问题。因为我们每天要花许多的时间用手机或其他设备浏览网页,万一以后眼瞎了,我们以后还可以冲浪啊。(不仅如此,这个世界上还有盲人游戏、盲人程序员)</p>
<h2>常见HTML标签语义</h2>
<p>关于 <em>html</em> 标签语义的讨论和文章,已经数不胜数,也有很多网站开发人员想竭尽全力去遵守,可是一旦到了开发的时候,就想不起该用什么标签了。于是,就开始挠头皮,时间久了,就秃了。</p>
<p>下面列举一些标签的用法:</p>
<p><em>article</em>: 翻译过来就是文章的意思,其内容应当是一篇独立完整的文章。可以是杂志、报纸、技术或学术文章、论文或报告、博客或其他社交媒体文章。<em>article</em> 可以嵌套,但是其内容必须是相关的。每个 <em>article</em> 都应该有一个标题(<em>h1</em>~<em>h6</em>)。</p>
<p><em>section</em>: 段落。比如一篇文章(<em>article</em>)的片段。每个段落应该有一个标题(<em>h1</em>~<em>h6</em>),当<em>section</em> 作为 <em>arcticle</em> 的子元素时,标题字号不要大于文章的标题(否则看起来很怪异)。也不建议对 <em>section</em> 使用样式或者作为脚本切入点(大致意思,就是不要用js操作它),应该使用 <em>div</em> 来替代它干这些事。它和 <em>p</em> 标签有区别。我们平时写文章换行、起头空两个字,这个表示一个段落,可以用<em>p</em> 表示。而 <em>section</em> 可以是一个或多个 <em>p</em> 段落。这几个段落表达的意思相近或者构成一个完整的意思。类似于中小学语文课,老师让学生分段一样。</p>
<p><em>nav</em>: 导航块。比如包含跳转到其他页面或者本页面的链接。<em>nav</em> 的内容可以是列表也可以不是。例如:</p>
<pre><code><nav>
<ul>
<li><a href="#">...</a></li>
<li><a href="#">...</a></li>
</ul>
</nav>
<nav>
<h1></h1>
<p>
<a href="#">...</a>
</p>
</nav></code></pre>
<p>不过,在网站的底部一般都有很多链接,例如地址、联系方式等。这个时候一般没必要用 <em>nav</em>。</p>
<p><em>aside</em>: 与当前位置段落相关的独立内容。例如相关文字、段落、广告、导航</p>
<p><em>h1</em> ~ <em>h6</em>: 重要程度不一的标题。</p>
<p><em>header</em>、<em>footer</em>: <em>body</em>、<em>article</em>、 <em>aside</em>、 <em>nav</em>、 <em>section</em>都可以有一个头部或者脚部。<em>header</em> 通常用来包裹顶部的搜索框、目录、logo等,也可以包裹标题 <em>h1</em> ~ <em>h6</em>,但不是必须的。</p>
<p><em>figure</em>、<em>figure-caption</em>: 两个结合使用,可用于注释插图、图表、照片、代码列表等。例如:</p>
<pre><code><figure id="l4">
<figcaption>Listing 4. The primary core interface API declaration.</figcaption>
<pre><code>interface PrimaryCore {
boolean verifyDataLine();
void sendData(in sequence&lt;byte> data);
void initSelfDestruct();
}</code></pre>
</figure></code></pre>
<p><em>div</em>: 没有任何语义。可以用来包裹一系列相关的子节点,以及将相关内容定位布局。</p>
<p><em>ol</em>、<em>ul</em>、<em>dl</em>: 前面两个使用更为平常,分别是有序列表和无序列表。<em>dl</em> 有点像<strong>二维</strong>的无序列表。例如:</p>
<pre><code><dl>
<dt>得分A</dt>
<dd>赵xx</dd>
<dd>钱xx</dd>
<dd>孙xx</dd>
<dd>李xx</dd>
<dt>得分B</dt>
<dd>周xx</dd>
<dd>吴xx</dd>
<dd>郑xx</dd>
<dd>王xx</dd>
</dl>
</code></pre>
<p><em>blockquote</em>、<em>q</em>: 都是引用的意思。一篇文章里面引用了另一篇的内容。如果需要展示引用的地址或者相关信息,可以配合使用 <em>cite</em> 标签。不同的地方是,<em>blockquote</em> 将包含 <em>cite</em> 标签在底部;<em>q</em> 引用内容更短小一些,像短语或者词语,且 <em>cite</em> 会作为其兄弟节点出现在它前面。虽然 <em>q</em> 的表现与双引号一样。但是,却有不同的使用场景。例如:说过的话,可以用引号包裹强调,可能不适合 <em>q</em>。</p>
<p><em>s</em>、<em>del</em>: 两者默认外观差不多,都有中贯线。但是, <em>s</em> 表示没那么重要了,而 <em>del</em> 意味着已经废除。</p>
<p><em>strong</em>、<em>em</em>: 两者都有加强的意思。<em>strong</em> 着重强调相关内容,而 <em>em</em> 强调的是语气,像英语口语中的重音。</p>
<p><em>i</em>: 外来或者专有名词,或交替出现的心情或者声音等情景。前半句比较好理解,后半句比较难理解。先看一个例子:</p>
<pre><code><p>雷曼开始睡觉了.</p>
<p><i>这艘船周二离港的</i>, 他梦见. <i>船上有许多人, 其中有一个叫凯莉的公主. 他看着凯莉,日复一日,希望她能够注意到他,可是,她从来都没有.</i></p>
<p><I>终于,有一天晚上,雷曼鼓足了勇气对她说——</i></p>
<p>这时,一段火警发出的报警声把雷曼吵醒了.</p>
</code></pre>
<p>上面的例子,雷曼睡觉和醒来是现实的描写,而梦境是虚拟的,是另一种场景,都用 <em>i</em> 扩起来了。有时候 <em>i</em> 可以被其他标签替代,我们可以 <em>em</em> 表示语气的不同,用 <em>dfn</em> 包裹那些外来或者特殊意义的名词。</p>
<p><em>b</em>: 强调,引起注意,但不一定是重要的,对此,无须做出任何解释。比如,搜索高亮、一段文字的开头。下面是一个反例:</p>
<pre><code><p><b>注意!</b> 不要在巴比纳吐口水!</p>
</code></pre>
<p>这里更适合用 <em>strong</em>。</p>
<p><em>u</em>: 很难辨认(发音)、拼写错误或者中国名字。</p>
<p><em>time</em>: 与时间相关的内容,例如时间、时区等。</p>
<p><em>code</em>: 代码片段。</p>
<p><em>var</em>: 数学或者编程及其他地方用到的变量。例如:</p>
<pre><code><p> <var>x</var>的值是5</p>
</code></pre>
<p><em>samp</em>: 表示一个例子或者对计算结果输出的引用。</p>
<p><em>span</em>: 与 <em>div</em> 一样,也是没有任何的语义的。可以用来包裹行内元素或者文本,进行样式改造。</p>
<p>未完......</p>
花样招聘面试题
https://segmentfault.com/a/1190000016527159
2018-09-26T18:27:10+08:00
2018-09-26T18:27:10+08:00
长颈鹿
https://segmentfault.com/u/newbird_007
9
<h2>残缺的地图</h2>
<p>今天在微信群里面看到一张招聘图片,如下</p>
<p><img src="/img/bVbhvww?w=600&h=600" alt="图片描述" title="图片描述"></p>
<h2>寻找钥匙</h2>
<p>被玷污的残缺的婀娜多<del>汁</del>姿的二维码,蕴藏着通往神秘国度大门的钥匙。利用微信自带截图工具,凭借着我手绘天赋,很快就还原了二维码。</p>
<p><img src="/img/bVbhvwL?w=590&h=307" alt="图片描述" title="图片描述"></p>
<p>然后查看图片,长按。居然是可以识别的二维码,扫一扫,得到下面的摩斯密码</p>
<pre><code>.--..-......
</code></pre>
<h2>找到答案</h2>
<p>于是,百度摩斯密码对照表。</p>
<p><img src="/img/bVbhvxg?w=580&h=443" alt="图片描述" title="图片描述"></p>
<p>由于我们得到摩斯密码没有空格隔开,所以解密后有可能不止一种。这个时候,身为攻城虱当然想到是用程序解决了。</p>
<p>既然,图片中说到是4个字的单词,等于这段密码可以分为4段。</p>
<pre><code>[part1][part2][part3][part4]
</code></pre>
<p>如果4段密码分别都能匹配上,那么连起来整个密码也能匹配上(废话)。最先想到是用4层循环穷举法</p>
<pre><code>for(){
for(){
for(){
for(){
}
}
}
}
</code></pre>
<p>可是如何把这段密码分成四段呢?很简单,如果电码符号与密码前几个字符能完全匹配,这里就分一段,然后继续匹配下一段。</p>
<pre><code>for(){
// 匹配成功
if([part1][part2][part3][part4].indexOf(xcode[i]) === 0 ){
for(){
if([part2][part3][part4].indexOf(xcode[j]) === 0 ){
}
}
}
}
</code></pre>
<p>第四段,也就是最后一段匹配不能再用[indexOf],而是用[===]。</p>
<p>核心匹配代码</p>
<pre><code>for(i = 0; i < morseCodeNum; i ++){
if(mission1.indexOf(morseCodeMap[i]) === 0 ){
firstMatchPart = morseCodeMap[i];
mission2 = secretCode.slice(firstMatchPart.length);
for(j = 0; j < morseCodeNum; j ++){
if(mission2.indexOf(morseCodeMap[j]) === 0 ){
secondMatchPart = morseCodeMap[j];
mission3 = secretCode.slice(firstMatchPart.length+secondMatchPart.length);
for(k = 0; k < morseCodeNum; k ++){
if(mission3.indexOf(morseCodeMap[k]) === 0 ){
thirdMatchPart = morseCodeMap[k];
mission4 = secretCode.slice(firstMatchPart.length+secondMatchPart.length+thirdMatchPart.length);
for(n = 0; n < morseCodeNum; n ++){
if(mission4 === morseCodeMap[n]){
fourthMatchPart = morseCodeMap[n];
console.log([firstMatchPart, secondMatchPart, thirdMatchPart, fourthMatchPart].join(''));
result.push([letters[i], letters[j], letters[k], letters[n]].join(''));
}
}
}
}
}
}
}
}
</code></pre>
<p>注: <code>mission1</code>表示处于匹配第一段密码,以此类推。</p>
<p>完整的代码</p>
<pre><code>var morseCodeMap = [
'.-', '-...', '-.-.', '-..',
'.', '..-.', '--.', '....',
'..', '.---', '-.-', '.-..',
'--', '-.', '---', '.--.',
'--.-', '.-.', '...', '-',
'..-', '...-', '.--', '-..-',
'-.--', '--..'
],
secretCode = '.--..-......',
morseCodeNum = morseCodeMap.length,
letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
var result = [],
firstMatchPart,
secondMatchPart,
thirdMatchPart,
fourthMatchPart,
mission1,
mission2,
mission3,
mission4;
function deCodeMorse(){
var i, j, k, n;
mission1 = secretCode;
for(i = 0; i < morseCodeNum; i ++){
if(mission1.indexOf(morseCodeMap[i]) === 0 ){
firstMatchPart = morseCodeMap[i];
mission2 = secretCode.slice(firstMatchPart.length);
for(j = 0; j < morseCodeNum; j ++){
if(mission2.indexOf(morseCodeMap[j]) === 0 ){
secondMatchPart = morseCodeMap[j];
mission3 = secretCode.slice(firstMatchPart.length+secondMatchPart.length);
for(k = 0; k < morseCodeNum; k ++){
if(mission3.indexOf(morseCodeMap[k]) === 0 ){
thirdMatchPart = morseCodeMap[k];
mission4 = secretCode.slice(firstMatchPart.length+secondMatchPart.length+thirdMatchPart.length);
for(n = 0; n < morseCodeNum; n ++){
if(mission4 === morseCodeMap[n]){
fourthMatchPart = morseCodeMap[n];
console.log([firstMatchPart, secondMatchPart, thirdMatchPart, fourthMatchPart].join(''));
result.push([letters[i], letters[j], letters[k], letters[n]].join(''));
}
}
}
}
}
}
}
}
}
deCodeMorse();
console.log(result);
</code></pre>
<p>得到结果如下,根据图片中的提示,该单词与面试有关,那么应该是<code>pass</code>无疑。<br><img src="/img/bVbhvzM?w=1718&h=93" alt="图片描述" title="图片描述"></p>
<p>上面的代码我们用了4层for嵌套循环,确实有点多,但是只有if条件成立,才会进入深层的循环。因为要求所有解,是避免不了的.</p>
<h2>延伸</h2>
<p>假如,我们得到的密码可能不是4段,不确定是几段,这个时候就不能用嵌套for循环了,可以用递归。<br><em>核心代码逻辑</em></p>
<pre><code>function deCodeMorse(mission[j]){
for (var i = 0; i < morseCodeNum; i++) {
if( morseCode.indexOf(morseCodeMap[i]) === 0 ){
if( morseCode === morseCodeMap[i]){
// 输出匹配结果
}else{
deCodeMorse(mission[j+1]);
}
}
}
}
</code></pre>
<p>使用递归的完整代码</p>
<pre><code>var morseCodeMap = [
'.-', '-...', '-.-.', '-..',
'.', '..-.', '--.', '....',
'..', '.---', '-.-', '.-..',
'--', '-.', '---', '.--.',
'--.-', '.-.', '...', '-',
'..-', '...-', '.--', '-..-',
'-.--', '--..'
],
secretCode = '.--..-......',
morseCodeNum = morseCodeMap.length,
letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
result = [],
function deCodeMorse(morseCode, deCodeResult, stack){
stack = stack || [];
for (var i = 0; i < morseCodeNum; i++) {
if( morseCode.indexOf(morseCodeMap[i]) === 0 ){
if( morseCode === morseCodeMap[i]){
deCodeResult.push(stack.concat(letters[i]).join(''));
}else{
deCodeMorse(morseCode.slice(morseCodeMap[i].length), deCodeResult, stack.concat(letters[i]));
}
}
}
}
deCodeMorse(secretCode, result);
console.log(result);
</code></pre>
<p>我们还可以传一个数字参数给函数deCodeMorse,表示密码由几段组成,不传则没有限制。那么<code>deCodeMorse</code>函数可以这样写</p>
<pre><code>function deCodeMorse(morseCode, deCodeResult, limit, stack){
stack = stack || [];
for (var i = 0; i < morseCodeNum; i++) {
if( morseCode.indexOf(morseCodeMap[i]) === 0 ){
if( morseCode === morseCodeMap[i]){
if(limit){
stack.length +1 === limit && deCodeResult.push(stack.concat(letters[i]).join(''));
}else{
deCodeResult.push(stack.concat(letters[i]).join(''));
}
}else{
if(limit){
if(stack.length < limit){
deCodeMorse(morseCode.slice(morseCodeMap[i].length), deCodeResult, limit, stack.concat(letters[i]));
}
}else{
deCodeMorse(morseCode.slice(morseCodeMap[i].length), deCodeResult, limit, stack.concat(letters[i]));
}
}
}
}
}
deCodeMorse(secretCode, result, 4);
</code></pre>
实现简易 ES6 Promise 功能 (二)
https://segmentfault.com/a/1190000004358563
2016-01-24T21:45:12+08:00
2016-01-24T21:45:12+08:00
长颈鹿
https://segmentfault.com/u/newbird_007
3
<p>上一篇文章【<a href="http://segmentfault.com/a/1190000004344928">实现简易 ES6 Promise 功能 (一)</a>】实现了基本的异步功能。今天我们接着上次的内容继续扯,如何实现【数据传递】以及当【回调函数返回一个新的promise】</p>
<p>上篇已完成的代码</p>
<pre><code>function Promise(func){
this.state = 'pending';
this.doneList = [];
func(this.resolve.bind(this));
}
Promise.prototype = {
resolve: function(){
while(true){
if( this.doneList.length === 0 ){
this.state = 'done';
break;
}
this.doneList.shift().apply(this);
}
},
then: function(callback){
this.doneList.push(callback);
if( this.state === 'done'){
this.state = 'pending';
this.resolve();
}
return this;
}
}</code></pre>
<p>测试代码</p>
<pre><code>new Promise(function(resolve){
resolve(1)
}).then(function(data){
console.log(data); // 1
return 2;
}).then(function(data){
console.log(data); // 2
});</code></pre>
<p>上面的结果,就是我们要实现的。resolve的参数(只传递第一个参数,如果有)传递给第一个then里面函数作为参数,第一个then里面的函数返回值传递给第二个then里面的函数作为参数,以此类推。</p>
<p>关键代码</p>
<pre><code>resolve: function(){
// arguments[0]是resolve的第一个参数
while(true){
if( this.doneList.length === 0 ){
this.state = 'done';
break;
}
// 这里是运行then里面回调函数的地方
this.doneList.shift().apply(this);
}
}
</code></pre>
<p>如何修改呢?除了第一次传递参数,是把resolve的参数往下传递,其余的都是把上次的结果作为下次开始(参数)。<br>于是,我们可以先把上次doneList里面的函数运行结果保存起来。然后,等到下次需要的时候,再传给下一个回调函数。<br>代码修改:</p>
<pre><code>resolve: function(){
// arguments[0]是resolve的第一个参数
var arg = arguments[0];
while(true){
if( this.doneList.length === 0 ){
this.state = 'done';
break;
}
// 这里是运行then里面回调函数的地方
// 以数组形式传给下一个函数,然后保存新的值
// 判断传递的参数是否为undefined,是的话,就不用传了
if( typeof arg === 'undefined' ){
arg = this.doneList.shift().apply(this);
}else{
arg = this.doneList.shift().apply(this, [arg]);
}
}
// 保存最后的arg,保证后续的回调能继续得到参数
this.arg = arg;
}
// 还需要修改then方法
then: function(callback){
this.doneList.push(callback);
if( this.state === 'done'){
this.state = 'pending';
this.resolve(this.arg); // 注意这里也要传递参数
}
return this;
}</code></pre>
<p>第一次修改完善的代码,及测试结果</p>
<pre><code>function Promise(func){
this.state = 'pending';
this.doneList = [];
func(this.resolve.bind(this));
}
Promise.prototype = {
resolve: function(){
// arguments[0]是resolve的第一个参数
var arg = arguments[0];
while(true){
if( this.doneList.length === 0 ){
this.state = 'done';
break;
}
// 这里是运行then里面回调函数的地方
// 以数组形式传给下一个函数,然后保存新的值
// 判断传递的参数是否为undefined,是的话,就不用传了
if( typeof arg === 'undefined' ){
arg = this.doneList.shift().apply(this);
}else{
arg = this.doneList.shift().apply(this, [arg]);
}
}
// 保存最后的arg,保证后续的回调能继续得到参数
this.arg = arg;
},
then: function(callback){
this.doneList.push(callback);
if( this.state === 'done'){
this.state = 'pending';
this.resolve(this.arg); // 注意这里也要传递参数
}
return this;
}
}
// 测试
new Promise(function(resolve){
resolve(1)
}).then(function(data){
console.log(data); // 1
return 2;
}).then(function(data){
console.log(data); // 2
});</code></pre>
<p>结果截图:<br><img src="/img/bVsrVx" alt="图片描述" title="图片描述"></p>
<p>今天的第一个功能已经完了,那么现在开始开发第二个功能。</p>
<p>先看一个测试</p>
<pre><code>new Promise(function(resolve){
resolve(1)
}).then(function(data){
console.log(data, 2); // 1,2
return new Promise(function(resolve){
window.setTimeout(function(){
resolve(3);
}, 1000);
}).then(function(data){
console.log(data, 4); // 3,4
return 5;
});
}).then(function(data){
console.log(data, 6); // 5, 6
});</code></pre>
<p>测试结果<br><img src="/img/bVsrWU" alt="图片描述" title="图片描述"></p>
<p>我在上面测试例子中,then回调函数中返回的promise中故意使用了延迟函数。但是,输出结果中5往后传了,并且[5,6]是在[3,4]之后,且都有一秒中的延迟。</p>
<p><img src="/img/bVsrYJ" alt="图片描述" title="图片描述"><br>如果没有回调返回一个promise,程序会一直按照第一行走下去,就算回调中有其他promise(只要不return),也是两条并行的线。一旦返回promise,新的promise会在这个点插入,并且原来还没有执行的回调,也会排到新的回调列表后面了。</p>
<p>先来修改resolve方法,因为回调函数都是在这里运行的。</p>
<pre><code>resolve: function(){
// arguments[0]是resolve的第一个参数
var arg = arguments[0];
while(true){
if( this.doneList.length === 0 ){
this.state = 'done';
break;
}
/*************************/
if( arg instanceof Promise ){
// 把新的promise保存起来,待会要用
this.promise = arg;
// 本promise没有执行完的回调全部加入到新的回调列表
arg.doneList = arg.doneList.concat(this.doneList);
// 改变回调及状态
this.doneList.length = 0;
this.state = 'done';
// 跳出循环
break;
}
/*************************/
// 这里是运行then里面回调函数的地方
// 以数组形式传给下一个函数,然后保存新的值
// 判断传递的参数是否为undefined,是的话,就不用传了
if( typeof arg === 'undefined' ){
arg = this.doneList.shift().apply(this);
}else{
arg = this.doneList.shift().apply(this, [arg]);
}
}
// 保存最后的arg,保证后续的回调能继续得到参数
this.arg = arg;
}
then: function(callback){
this.doneList.push(callback);
if( this.state === 'done'){
this.state = 'pending';
this.resolve(this.arg); // 注意这里也要传递参数
}
// 这里不能在返回this了,而是一个promise对象
return this.promise;
}
// 如果then没有返回promise,那么this.promise = this;
function Promise(func){
this.state = 'pending';
this.doneList = [];
func(this.resolve.bind(this));
// 默认指向本身
this.promise = this;
}</code></pre>
<p>本期完整代码</p>
<pre><code>function Promise(func){
this.state = 'pending';
this.doneList = [];
func(this.resolve.bind(this));
this.promise = this;
}
Promise.prototype = {
resolve: function(){
// arguments[0]是resolve的第一个参数
var arg = arguments[0];
while(true){
if( this.doneList.length === 0 ){
this.state = 'done';
break;
}
if( arg instanceof Promise ){
this.promise = arg;
arg.doneList = arg.doneList.concat(this.doneList);
this.doneList.length = 0;
this.state = 'done';
break;
}
// 这里是运行then里面回调函数的地方
// 以数组形式传给下一个函数,然后保存新的值
// 判断传递的参数是否为undefined,是的话,就不用传了
if( typeof arg === 'undefined' ){
arg = this.doneList.shift().apply(this);
}else{
arg = this.doneList.shift().apply(this, [arg]);
}
}
// 保存最后的arg,保证后续的回调能继续得到参数
this.arg = arg;
},
then: function(callback){
this.doneList.push(callback);
if( this.state === 'done'){
this.state = 'pending';
this.resolve(this.arg); // 注意这里也要传递参数
}
return this.promise;
}
}</code></pre>
<p>结束。谢谢大家阅读,如有错误或建议请给我留言或者发私信。</p>
实现简易 ES6 Promise 功能 (一)
https://segmentfault.com/a/1190000004344928
2016-01-21T15:08:31+08:00
2016-01-21T15:08:31+08:00
长颈鹿
https://segmentfault.com/u/newbird_007
5
<blockquote><p>Promise 对象用于延迟(deferred) 计算和异步(asynchronous ) 计算。一个Promise对象代表着一个还未完成,但预期将来会完成的操作。</p></blockquote>
<p>先来一个例子 A 熟悉下语法</p>
<pre><code> var p = new Promise(function(resolve, reject){
console.log('1');
// 运行后续的 callback 列表,例如:then,when等。否则,不会执行then里面的函数
resolve('go next');
});
// 只考虑成功
p.then(function(){
console.log(2, arguments);
return 'next';
}, null).then(function(){
console.log(3, arguments)
}, null);
// 输出
1
2 ['go next']
3 ['next']</code></pre>
<p>最初的设想</p>
<pre><code> function Promise(func){ // 接收一个函数作为参数
}
Promise.prototype = {
then: function(callback){ // 接收函数作为参数
return this; // 支持链式调用
}
} </code></pre>
<p>观察例 A,resolve是一个函数,并且不是用户传的,所有Promise自身应该有一个resolve方法,并且这个方法要传递给Promise构造器里面的函数作为参数。</p>
<pre><code> function Promise(func){ // 接收一个函数作为参数
**func(this.resolve);**
}
Promise.prototype = {
**resolve: function(){
}**,
then: function(callback){ // 接收函数作为参数
return this; // 支持链式调用
}
}</code></pre>
<p>Promise是按照顺序来执行callback的,并且由resolve触发。</p>
<pre><code> function Promise(func){ // 接收一个函数作为参数
**this.doneList = []; // callback 列表**
**func(this.resolve.bind(this)); // 顺带绑定this对象**
}
Promise.prototype = {
resolve: function(){
**//执行回调列表
while(true){
if( this.doneList.length === 0 ){
break;
}
this.doneList.shift().apply(this);
}**
},
then: function(callback){ // 接收函数作为参数
**this.doneList.push(callback); // 加入到回调队列**
return this; // 支持链式调用
}
}</code></pre>
<p>好,现在写一个测试用例</p>
<pre><code> var p =new Promise(function(resolve){
console.log('a');
resolve();
}).then(function(){
console.log('b');
});
// 输出
'a'</code></pre>
<p>什么鬼?打印下p</p>
<pre><code> console.log(p);</code></pre>
<p><img src="/img/bVsomT" alt="图片描述" title="图片描述"></p>
<p>我们发现原来doneList里面还有一个函数没有运行,再运行下这个函数</p>
<pre><code> p.doneList[0].call(this);
//结果
console.log('b'); // 打印 b</code></pre>
<p>打断点跟踪,发现Promise先执行resolve方法,然后执行then,把函数push到doneList。但是,再也没有执行过doneList里面的函数了。怎么办呢?我们可以给Promise加一个属性(state)来描述当前状态,分为【未完成】(pending)和【完成】(done)。如果执行then时,状态已经是完成态,那么切换到未完成态,并执行resolve方法。</p>
<pre><code> function Promise(func){ // 接收一个函数作为参数
**this.state = 'pending'; // 初始化状态**
this.doneList = []; // callback 列表
func(this.resolve.bind(this)); // 顺带绑定this对象
}
Promise.prototype = {
resolve: function(){
//执行回调列表
while(true){
if( this.doneList.length === 0 ){
**this.state = 'done'; // 回调列表为空,改变状态**
break;
}
this.doneList.shift().apply(this);
}
},
then: function(callback){ // 也是接收函数
this.doneList.push(callback); // 加入到回调队列
if( this.state === 'done'){
this.state = 'pneding';
this.resolve();
}
return this; // 支持链式调用
}
}</code></pre>
<p>用和上面类似的例子再次测试</p>
<pre><code> var p =new Promise(function(resolve){
console.log('a');
resolve();
}).then(function(){
console.log('b');
}).then(function(){
console.log('c');
});</code></pre>
<p>结果截图<br><img src="/img/bVsoqq" alt="图片描述" title="图片描述"></p>
<p>这下,我们自定义的Promise基础功能完成了最核心的部分了。也许有人会疑问,你这写的什么鬼?下面的代码更简单,也能实现相同的功能</p>
<pre><code> function Promise(func){ // 接收一个函数作为参数
func(this.resolve.bind(this)); // 顺带绑定this对象
}
Promise.prototype = {
resolve: function(){
//什么也不干
},
then: function(callback){ // 也是接收函数
callback.call(this); // 直接运行
return this; // 支持链式调用
}
}</code></pre>
<p>测试用例</p>
<pre><code> var p =new Promise(function(resolve){
console.log('d');
resolve();
}).then(function(){
console.log('e');
}).then(function(){
console.log('f');
});</code></pre>
<p>结果:<br><img src="/img/bVsorJ" alt="图片描述" title="图片描述"></p>
<p>但是,文章最前面说过</p>
<blockquote><p>Promise 对象用于延迟(deferred) 计算和异步(asynchronous ) 计算</p></blockquote>
<p>并且会顺序执行回调列表(doneList)。最终代码及测试</p>
<pre><code> function Promise(func){
this.state = 'pending';
this.doneList = [];
func(this.resolve.bind(this));
}
Promise.prototype = {
resolve: function(){
while(true){
if( this.doneList.length === 0 ){
this.state = 'done';
break;
}
this.doneList.shift().apply(this);
}
},
then: function(callback){
this.doneList.push(callback);
if( this.state === 'done'){
this.state = 'pending';
this.resolve();
}
return this;
}
}
var p = new Promise(function(resolve){
window.setTimeout(function(){
console.log('d');
resolve();
}, 1000);
}).then(function(){
console.log('e');
}).then(function(){
console.log('f');
});
console.log('被阻塞了吗?');
</code></pre>
<p>输出:<br><img src="/img/bVsos7" alt="图片描述" title="图片描述"></p>
<p>先输出'被阻塞了吗?',一秒钟后相继输出 d、e、f 。可以看出,不但没有阻塞后面的代码执行,而且回调也是按照顺序执行的。</p>
<p>结束。</p>
<p>下篇继续完善,例如数据传递以及then中函数返回一个Promise时,如何处理。欢迎大家有疑问或者建议,一起来交流。</p>