niaogege

niaogege 查看完整档案

其它编辑  |  填写毕业院校sn  |  前端开发 编辑 niaogege.cn 编辑
编辑

片刻之欢愉,不如须臾之宁静!

个人动态

niaogege 赞了文章 · 2月25日

防抖和节流啥区别,实现一个防抖和节流

防抖

在一定时间内执行一次,如果在此时间内再次触发,则重新计时

const debounce = (func, timeout, immediate = false) => {
  let timer = null;
  return function (...args) {
    if (!timer && immediate) {
      func.apply(this, args);
    }
    if (timer) {
      clearTimeout(timer);
    }
    timer = setTimeout(() => {
      func.apply(this, args);
    }, timeout);
  }
}

节流

在一定时间内执行一次,如果在此时间内再次触发,则会拦截不执行

const throttle = (func, timeout) => {
  let timer = null;
  return function (...args) {
    if (!timer) {
      timer = setTimeout(() => {
        timer = null;
        func.apply(this, args);
      }, timeout);
    }
  }
}
查看原文

赞 2 收藏 1 评论 0

niaogege 赞了文章 · 2月25日

手动实现 Promise

Promise 代表了一个异步操作的最终完成或者失败

定义状态

const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

构造函数定义


function MyPromise(excutor){
  // 初始状态为 pending
  this.status = PENDING;
  // 成功的值
  this.value = undefined;
  // 失败的原因
  this.reason = undefined;
  // then 的回调函数集合,后面会用到
  fulfilledCallback = [];
  rejectedCallback = [];

  const resolve = (value) => {
    if (this.status === PENDING) {
      this.status = FULFILLED;
      this.value = value;
      // 状态变更后执行 then 的回调函数
      this.fulfilledCallback.forEach(fn => fn(value))
    }
  }

  const reject = (reason) => {
    if (this.status === PENDING) {
      this.status = rejected;
      this.reason = reason;
      this.rejectedCallback.forEach(fn => fn(reason))
    }
  }

  try {
    excutor(resolve, reject);
  } catch (error) {
    reject(error);
  }
}

原型方法

MyPromise.prototype.then = function(onResolve, onReject){

  onResolve = onResolve === undefined ? value => value : onResolve,
  onReject = onReject === undefined ? error => { throw error } : onReject;

  return new MyPromise((resolve, reject) => {

    function handle(func, value){
      try {
        const result = func(value);
        // 如果返回一个 Promise
        if (result instanceof MyPromise) {
            result.then(resolve, reject);
        } else {
          resolve(result);
        }
      } catch (err) {
          reject(err);
      }
    }

    if (this.status === FULFILLED) {
      queueMicrotask(() => {
        handle(onResolve, this.value);
      });
    }

    if (this.status === REJECTED) {
      queueMicrotask(() => {
        handle(onReject, this.reason);
      });
    }

     // 状态为 pending,将回调函数保存到前面定义的函数集合中
    if (this.status === PENDING) {
      // 保存到 fulfilledCallback
      this.fulfilledCallback.push(value => {
        queueMicrotask(() => {
          handle(onResolve, value);
        });
      });

      // 保存到 rejectedCallback
      this.rejectedCallback.push(reason => {
        queueMicrotask(() => {
          handle(onReject, reason)
        });
      });
    }
  })
};

  MyPromise.prototype.catch = function (onReject) {
    return this.then(undefined, onReject);
  };

  MyPromise.prototype.finally = function (cb) {
    return this.then(
      (value) => MyPromise.resolve(cb()).then(() => value),
      (reason) =>
        MyPromise.resolve(cb()).then(() => {
          throw reason;
        })
    );
  };

静态方法

MyPromise.resolve = function (value) {
  // 如果参数是MyPromise实例,直接返回这个实例
  if (value instanceof MyPromise) return value;
  return new MyPromise((resolve) => resolve(value));
};

MyPromise.reject = function (value) {
  // 如果参数是MyPromise实例,直接返回这个实例
  if (value instanceof MyPromise) return value;
  return new MyPromise((resolve, reject) => reject(value));
};

MyPromise.race = function (promises) {
  return new Promise((resolve, reject) => {
    promises.forEach((p) => {
      MyPromise.resolve(p).then(resolve, reject);
    });
  });
};

MyPromise.all = function (promises) {
  let result = [];
  const len = promises.length;
  return new MyPromise((resolve, reject) => {
    promises.forEach((p, index) => {
      MyPromise.resolve(p).then(
        (res) => {
          result[index] = res;
          if (result.length === len) {
            resolve(result);
          }
        },
        (error) => {
          reject(error);
        }
      );
    });
  });
};

MyPromise.allSettled = function (promises) {
  let result = [];
  const len = promises.length;
  return new MyPromise((resolve, reject) => {
    promises.forEach((p, index) => {
      MyPromise.resolve(p).then(
        (value) => {
          result[index] = {
            status: FULFILLED,
            value,
          };
          if (result.length === len) {
            resolve(result);
          }
        },
        (reason) => {
          result[index] = {
            status: REJECTED,
            reason,
          };
          if (result.length === len) {
            resolve(result);
          }
        }
      );
    });
  });
};

最后的最后

上述代码用到了queueMicrotask()来执行微任务
mdn上也提供了queueMicrotask()的polyfill实现
它通过使用立即 resolve 的 promise 创建一个微任务(microtask)
如下:
if (typeof window.queueMicrotask !== "function") {
  window.queueMicrotask = function (callback) {
    Promise.resolve()
      .then(callback)
      .catch(e => setTimeout(() => { throw e; }));
  };
}
查看原文

赞 2 收藏 1 评论 2

niaogege 发布了文章 · 2月10日

计算个人所得税(新版)

题目:

员工2015年入职,2019年每月应发工资均为30000元,每月减除费用5000元,“三险一金”等专项扣除为4500元,享受子女教育、赡养老人两项专项附加扣除共计2000元,没有减免收入及减免税额等情况,以前三个月为例,应当按照以下方法计算各月应预扣预缴税额:

1月份:(30000–5000-4500-2000)×3%=555元

2月份:(30000×2-5000×2-4500×2-2000×2)×10%-2520-555=625元

3月份:(30000×3-5000×3-4500×3-2000×3)×10%-2520-555-625=1850元

结论:上述计算结果表明,由于2月份累计预扣预缴应纳税所得额为37000元,已适用10%的税率,因此2月份和3月份应预扣预缴有所增高

新个人所得税税率表

image

代码化

输入: 应发工资/每月减除费用/五险一金扣除/专项附加扣除
输出:根据个人所得税预扣率表计算每个月的税额,也阔以计算年度税额

主要代码

<!--
 * @Description: 
 * @Date: 2020-12-14 15:37:07
 * @lastTime: 2020-12-19 10:13:34
 * @LastEditors: cpp
-->
<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>个人计算税收</title>
  <meta name="viewport" content="width=device-width, initial-scale=1.0, 
maximum-scale=1.0, user-scalable=no">
  <style>
    * {
      margin: 0;
      padding: 0
    }
  </style>
</head>
<body>
  <div id = 'app'></div>
  <script data-original="https://cdn.bootcdn.net/ajax/libs/vue/2.6.12/vue.js"></script>
  <script>
    // 本期应预扣预缴税额=(累计预扣预缴应纳税所得额×预扣率-速算扣除数)-累计减免税额
    // 累计预扣预缴应纳税所得额=累计免税收入-累计减除费用-累计专项扣除-累计专项附加扣除-累计依法确定的其他扣除
    new Vue({
      el: '#app',
      template: `
      <div>
        <div>
          <label for="taxFreeIncome">
            每月应发工资(税前){{taxFreeIncome}} <input v-model='taxFreeIncome' />
          </label>
        </div>
        <div>
          <label for="countDeduction">
            每月减除费用 {{countDeduction}} <input v-model='countDeduction' />
          </label>
        </div>
        <div>
          <label for="insurance">
            每月五险一金{{insurance}} <input v-model='insurance' />
          </label>
        </div>
        <div>
          <label for="specialAddOn">
            每月专项抵扣 {{specialAddOn}} <input v-model='specialAddOn' />
          </label>
        </div>
        <div>
          <label for="monthNum">
            累积 {{monthNum}} 月 <input v-model='monthNum' />
          </label>
        </div>
        <ul >
          <li v-for = '(item, i) in monthTax'> 第{{i + 1}}月 应缴费 {{item}}</li>
          </ul>
          <div>
            <h3> 年终 {{yearEndMonth}} 系数 <input v-model='yearEndMonth' /></h3>
            <h3> 年度总应付工资(税前) {{yearTotal}} 元 </h3>
            <h3> 年度总税额 {{totalTax}} 元 </h3>
            <h3> 年度到手工资 {{yearTotal - totalTax}} </h3>
            <h3> 平均月度到手工资 {{(yearTotal - totalTax) / 12 }} </h3>

          </div>
          <button @click='count'> 计算 </button>
      </div>
      `,
      data() {
        return {
          taxFreeIncome: 30000, // 应收工资
          countDeduction: 5000,// 起征税
          insurance: 4500, // 五险一金
          specialAddOn: 2000, // 专项抵扣
          monthNum: 12, // 累积几个月
          monthTax: [], // 累积几个月显示
          totalTax: 0, // 年度总共税收
          yearTotal: 0, // 年度总收益税前
          yearEndMonth: 0, // 年终系数
          yearEndAward: 0, // 年终奖
        }
      },
      methods: {
        count() {
          const arr = []
          let prepaidIncome = 0; // 每月缴税
          let totalTax = 0
          for (let i = 1; i <= this.monthNum; i ++) {
            const taxableIncome = (this.taxFreeIncome - this.countDeduction - this.insurance - this.specialAddOn) * i;
            if (taxableIncome < 36000) {
              prepaidIncome = Number((taxableIncome * 0.03) - totalTax);
            } else if ( 36000 < taxableIncome <= 144000) {
              prepaidIncome = Number(( taxableIncome * 0.1) - 2520 - totalTax);
            } else if (144000 < taxableIncome <= 300000) {
              prepaidIncome = ( taxableIncome * 0.2) - 16920 - totalTax;
            } else if (300000 < taxableIncome <= 420000) {
              prepaidIncome = ( taxableIncome * 0.25) - 31920 - totalTax;
            } else if (420000 < taxableIncome <= 660000) {
              prepaidIncome = ( taxableIncome * 0.3) - 52920 - totalTax;
            } else if (660000 < taxableIncome <= 960000) {
              prepaidIncome = ( taxableIncome * 0.35) - 85920 - totalTax;
            } else if (taxableIncome > 960000) {
              prepaidIncome = ( taxableIncome * 0.45) - 181920 - totalTax;
            }
            arr.push(prepaidIncome);
            totalTax = arr.reduce((cur, total) => (cur + total), 0)
          }
          this.monthTax = arr
          this.totalTax = totalTax;
          this.yearTotal = this.taxFreeIncome * (12 + Number(this.yearEndMonth));
          totalTax = 0
        }
      }
    })
  </script>
</html>
查看原文

赞 0 收藏 0 评论 0

niaogege 赞了文章 · 2月10日

我的2020, 潮起浪平的这一年

20210209171143

简直直接的总结

2020 最惹人醒目的词: 无疑是新冠。虽然没直接打过交道,但间接影响还是很大的。过年过成家里蹲、成天带着口罩让老鼻炎又加重了、梦寐以求的在家办公成为现实、老东家的货运滴滴业务因为疫情几近停滞、新东家新平台新业务搞得风生水起;

融合

上半年做货车帮与运满满技术体系的融合,下半年做客如云与本地生活域内业务的融合;这个世界大鱼吃小鱼现象与巨头效应越发明显,作为技术人也就越需要习惯业务与技术融合的现实,这也是资本推波助澜的必然。

经历两次融合,发现不论业务还是技术其相似性极大;业务融合其根本还是账号体系的融合,只要账号体系打通了,其他业务都可以通过算法逻辑来打通;技术的共同点就是数据怎么映射,谁为主,谁为辅;站在前端的角度更简单,你只需要关心技术栈适配、鉴权、跳转的逻辑。

而对于鉴权,虽说JWT推行了这么多年,但很多公司还是在依赖cookie做身份标识鉴权,而SameSite新政的推出,着实抢了不少镜,也让我对cookie有了一个全新且深度的认识。

Cook Cookie, 我把 SameSite 给你炖烂了

微前端

微前端自2018年兴起,2019年框架层出不穷;2020年,在老东家技术融合的背景下,我在金融中后台运营系统落地了该项技术。在对Single-Spa, 乾坤等微前端框架调研后,发现与自己的所需有出入,业务逻辑改动较大。所以在借鉴其思想后,自己从底层手写了一个狭义的微前端方案,上线后,不管是用户体验,还是开发体验,都得到了提升。

也许这才是你想要的微前端方案

进入新东家后,发现新团队也有自己的微前端框架,其底层是基于Single-Spa。在融合的背景下,为了降低融合难度,自己又深度接触了这一套框架,最后在上一家学到的又用上了,主应用只负责加载子应用,子应用具备路由自治,控制具体页面渲染。最后发现不论是我的简版还是新团队的方案,其核心都是从三个方面下手: 构建(builder),加载(loader),渲染(render)。

但新团队的方案确实高于我自研的一大截,也和市面上的前端框架不一样,他不只是解决了主子应用的渲染,还解决了怎样建立主子应用的关联关系,让主应用知道自己关联哪些子应用,子应用每次发布都会通知主应用,同时子应用上线时像普通前端应用一样具备灰度能力。

平台的力量

刚刚说的微前端方案,支撑这个方案的不只是一个框架,其配套还有一个平台。这个平台解决了主子应用的创建、关联关系、发布部署、消息推送等问题,而这样一个平台是完全由前端团队独立完成;而且这个平台现在不只服务于前端,也服务于整个研发团队。

在阿里像这种因为要解决一个问题,而开发一个平台对的设计比比皆是,进来半年多,我非常惊讶于这个公司的平台的搭建能力;比如def这种发布部署平台,在我看来已经把前端工程化已做到极致;还有aone这种项目应用管理平台,从需求设计、应用建立、发布部署、测试回归等一站式的开发体验设计;其中最惊艳我的,就是多迭代的同时部署能力, 这解决了很多团队快速迭代分支管理困难的问题。像这样的平台还有好多好多,以前期望的或还没实现的,在这里都成为了现实。

在阿里,我们如何管理代码分支?

但好平台,并不是什么都好,如果你习惯了依赖和按部就班,可能两年后,你就什么都不会了。俗话说: 水能载舟,亦能覆舟。

面试

从老东家去新东家,自然避免不了面试的过程。我基本上从2020年初就开始了面试,先裸面了两家,发现虽然过了,但结果并不理想。就像大学中的考试,如果你听过一些课,稍微聪明点,考及格是没有问题,但奖学金...是不可能的,这最直接的后果就是职级评定不高,薪资不理想。

后面因为疫情,把面试的过程放缓,自己先系统性的整理了一遍知识,然后又打渔似的做了几十道LeetCode。然后陆陆续续面完了成都所有的一二线前端团队,面蚂蚁腾讯这种级别的,基本三面就倒下,普遍给出的反馈是前端基础扎实,但大项目经验缺乏。低一点级别的,基本都能收下offer,最后因为情怀与项目复杂度,选择了新东家,6月入职。(进来接触的第一个项目是一个30多个前端同时开发一个小程序项目,这种多人协作的级别,是我从未想过的,但由于底层设计与业务系分合理,其协作非常顺滑,无代码冲突,无分支管理困难)。

最后说一下我为什么要离开老东家,不是因为疫情业务停滞,也不是因为工资太低。促使我离开的理由大概有三个方面:

  • 技术天花板,在2019年述职时,我的述职对象是五个后端大佬,这怎么看前后端都是失衡的。你讲的,他都说好,但就是不给你升。今年在新东家述职,面对的是3个P8,你说的他们能听懂,能看明白你做这件事的价值与你设计方案的缺陷,能给你后续的职业规划做建议,这体验完全不一样;
  • 项目复杂度,我在老东家做微前端,是在融合的背景下做的,因为单独做,其收益比太低,其问题就在于项目体量与复杂度还不够。这个问题,在后面面试中也不断凸显;
  • 平台,虽说现在开源项目百花齐放,但要真正引入自己的业务,还是需要花费不少代价,踩不少坑。但好的平台很多路,别人已走过,你可以借鉴,你能学到真正的核心,而不是在门槛前花费太多时间;

如果你也考虑换东家,你需要考虑清楚你想要什么,不要裸面,不要打没有准备的仗(刷面试题不是准备,而是系统去整理你的知识),这是一个过来者给你的最好的忠告;作为一个面试官,我要告诉你的是:对于大厂,你的每一次面试都被做好了记录与评价,有可能一次的草率,就让你与你期望的团队成为遥不可及的梦想(提炼你的项目与经验,第一次面大厂通过率是最高的,被捞的通过率反而折半,如果你没准备好,被捞了也要拒绝,没有面试评价就是最好的评价)。

生活

今年生活因为疫情过的异常寡淡,缺少惊喜:婚礼被迫延后了、和同学朋友的聚餐变少了、5月因为脚裸重伤提前加入了养生篮球大队。

但值得惊喜的是,媳妇的户口终于落在了成都,这样我那告老还乡,种菜卖水果的农场主梦想得以保留;最近,我们又花了1000多块钱,把小家装饰了下,下班的动力更充足了。

20210209170802

2021, 愿生活如意,身体健康,职业奋进,🐂起来!!!

祝大家新春快乐,大吉大利,牛的飞起!!!
公众号:前端黑洞

查看原文

赞 1 收藏 0 评论 0

niaogege 赞了文章 · 2月10日

思否官方祝各位社区开发者 2021 春节快乐

伴随着气温的回升,春天和假期的脚步都近了。上周五是 SegmentFault 团队春节假期前的最后一天的工作日,不过这几天团队小伙伴依旧处于在家办公状态,同时也紧锣密鼓筹备着迎接一年一度最重要的节日。

2 月 10 日(明天)起,我们将正式进入春节假期,春节假期社区部分管理工作会稍有调整,第一次内容发布审核,审核时间可能会比平时会慢一些,但是每天都会有管理员进行一些审核。同时我们也鼓励开发者,看到不规范的社区内容及时举报。

最后值此辞旧迎新之际,感谢过去的一年,社区里热爱技术的开发者和各位合作伙伴霸霸对 SegmentFault 思否的支持。新的一年,我们将继续努力为开发者服务,也做好科技企业和开发者沟通的桥梁,帮助更多合作伙伴更好地和开发者对话。

祝大家春节快乐,牛转乾坤!

image

查看原文

赞 16 收藏 0 评论 6

niaogege 收藏了文章 · 2月4日

50道CSS基础面试题(附答案)

1 介绍一下标准的CSS的盒子模型?与低版本IE的盒子模型有什么不同的?

标准盒子模型:宽度=内容的宽度(content)+ border + padding + margin
低版本IE盒子模型:宽度=内容宽度(content+border+padding)+ margin

2 box-sizing属性?

用来控制元素的盒子模型的解析模式,默认为content-box
context-box:W3C的标准盒子模型,设置元素的 height/width 属性指的是content部分的高/宽
border-box:IE传统盒子模型。设置元素的height/width属性指的是border + padding + content部分的高/宽

3 CSS选择器有哪些?哪些属性可以继承?

CSS选择符:id选择器(#myid)、类选择器(.myclassname)、标签选择器(div, h1, p)、相邻选择器(h1 + p)、子选择器(ul > li)、后代选择器(li a)、通配符选择器(*)、属性选择器(a[rel="external"])、伪类选择器(a:hover, li:nth-child)

可继承的属性:font-size, font-family, color

不可继承的样式:border, padding, margin, width, height

优先级(就近原则):!important > [ id > class > tag ]
!important 比内联优先级高

4 CSS优先级算法如何计算?

元素选择符: 1
class选择符: 10
id选择符:100
元素标签:1000

  1. !important声明的样式优先级最高,如果冲突再进行计算。
  2. 如果优先级相同,则选择最后出现的样式。
  3. 继承得到的样式的优先级最低。

5 CSS3新增伪类有那些?

p:first-of-type 选择属于其父元素的首个元素
p:last-of-type 选择属于其父元素的最后元素
p:only-of-type 选择属于其父元素唯一的元素
p:only-child 选择属于其父元素的唯一子元素
p:nth-child(2) 选择属于其父元素的第二个子元素
:enabled :disabled 表单控件的禁用状态。
:checked 单选框或复选框被选中。

6 如何居中div?如何居中一个浮动元素?如何让绝对定位的div居中?

div:

border: 1px solid red;
margin: 0 auto; 
height: 50px;
width: 80px;

浮动元素的上下左右居中:

border: 1px solid red;
float: left;
position: absolute;
width: 200px;
height: 100px;
left: 50%;
top: 50%;
margin: -50px 0 0 -100px; 

绝对定位的左右居中:

border: 1px solid black;
position: absolute;
width: 200px;
height: 100px;
margin: 0 auto;
left: 0;
right: 0; 

还有更加优雅的居中方式就是用flexbox,我以后会做整理。

7 display有哪些值?说明他们的作用?

inline(默认)--内联
none--隐藏
block--块显示
table--表格显示
list-item--项目列表
inline-block

8 position的值?

static(默认):按照正常文档流进行排列;
relative(相对定位):不脱离文档流,参考自身静态位置通过 top, bottom, left, right 定位;
absolute(绝对定位):参考距其最近一个不为static的父级元素通过top, bottom, left, right 定位;
fixed(固定定位):所固定的参照对像是可视窗口。

9 CSS3有哪些新特性?

  1. RGBA和透明度
  2. background-image background-origin(content-box/padding-box/border-box) background-size background-repeat
  3. word-wrap(对长的不可分割单词换行)word-wrap:break-word
  4. 文字阴影:text-shadow: 5px 5px 5px #FF0000;(水平阴影,垂直阴影,模糊距离,阴影颜色)
  5. font-face属性:定义自己的字体
  6. 圆角(边框半径):border-radius 属性用于创建圆角
  7. 边框图片:border-image: url(border.png) 30 30 round
  8. 盒阴影:box-shadow: 10px 10px 5px #888888
  9. 媒体查询:定义两套css,当浏览器的尺寸变化时会采用不同的属性

10 请解释一下CSS3的flexbox(弹性盒布局模型),以及适用场景?

该布局模型的目的是提供一种更加高效的方式来对容器中的条目进行布局、对齐和分配空间。在传统的布局方式中,block 布局是把块在垂直方向从上到下依次排列的;而 inline 布局则是在水平方向来排列。弹性盒布局并没有这样内在的方向限制,可以由开发人员自由操作。
试用场景:弹性布局适合于移动前端开发,在Android和ios上也完美支持。

11 用纯CSS创建一个三角形的原理是什么?

首先,需要把元素的宽度、高度设为0。然后设置边框样式。

width: 0;
height: 0;
border-top: 40px solid transparent;
border-left: 40px solid transparent;
border-right: 40px solid transparent;
border-bottom: 40px solid #ff0000;

12 一个满屏品字布局如何设计?

第一种真正的品字:

  1. 三块高宽是确定的;
  2. 上面那块用margin: 0 auto;居中;
  3. 下面两块用float或者inline-block不换行;
  4. 用margin调整位置使他们居中。

第二种全屏的品字布局:
上面的div设置成100%,下面的div分别宽50%,然后使用float或者inline使其不换行。

13 常见的兼容性问题?

  1. 不同浏览器的标签默认的margin和padding不一样。

    *{margin:0;padding:0;}

  2. IE6双边距bug:块属性标签float后,又有横行的margin情况下,在IE6显示margin比设置的大。hack:display:inline;将其转化为行内属性。
  3. 渐进识别的方式,从总体中逐渐排除局部。首先,巧妙的使用“9”这一标记,将IE浏览器从所有情况中分离出来。接着,再次使用“+”将IE8和IE7、IE6分离开来,这样IE8已经独立识别。

    {
    background-color:#f1ee18;/*所有识别*/
    .background-color:#00deff\9; /*IE6、7、8识别*/
    +background-color:#a200ff;/*IE6、7识别*/
    _background-color:#1e0bd1;/*IE6识别*/
    }
    
  4. 设置较小高度标签(一般小于10px),在IE6,IE7中高度超出自己设置高度。hack:给超出高度的标签设置overflow:hidden;或者设置行高line-height 小于你设置的高度。
  5. IE下,可以使用获取常规属性的方法来获取自定义属性,也可以使用getAttribute()获取自定义属性;Firefox下,只能使用getAttribute()获取自定义属性。解决方法:统一通过getAttribute()获取自定义属性。
  6. Chrome 中文界面下默认会将小于 12px 的文本强制按照 12px 显示,可通过加入 CSS 属性 -webkit-text-size-adjust: none; 解决。
  7. 超链接访问过后hover样式就不出现了,被点击访问过的超链接样式不再具有hover和active了。解决方法是改变CSS属性的排列顺序:L-V-H-A ( love hate ): a:link {} a:visited {} a:hover {} a:active {}

14 为什么要初始化CSS样式

因为浏览器的兼容问题,不同浏览器对有些标签的默认值是不同的,如果没对CSS初始化往往会出现浏览器之间的页面显示差异。

15 absolute的containing block计算方式跟正常流有什么不同?

无论属于哪种,都要先找到其祖先元素中最近的 position 值不为 static 的元素,然后再判断:

  1. 若此元素为 inline 元素,则 containing block 为能够包含这个元素生成的第一个和最后一个 inline box 的 padding box (除 margin, border 外的区域) 的最小矩形;
  2. 否则,则由这个祖先元素的 padding box 构成。

如果都找不到,则为 initial containing block。

补充:

  1. static(默认的)/relative:简单说就是它的父元素的内容框(即去掉padding的部分)
  2. absolute: 向上找最近的定位为absolute/relative的元素
  3. fixed: 它的containing block一律为根元素(html/body)

16 CSS里的visibility属性有个collapse属性值?在不同浏览器下以后什么区别?

当一个元素的visibility属性被设置成collapse值后,对于一般的元素,它的表现跟hidden是一样的。

  1. chrome中,使用collapse值和使用hidden没有区别。
  2. firefox,opera和IE,使用collapse值和使用display:none没有什么区别。

17 display:none与visibility:hidden的区别?

display:none 不显示对应的元素,在文档布局中不再分配空间(回流+重绘)
visibility:hidden 隐藏对应元素,在文档布局中仍保留原来的空间(重绘)

18 position跟display、overflow、float这些特性相互叠加后会怎么样?

display属性规定元素应该生成的框的类型;position属性规定元素的定位类型;float属性是一种布局方式,定义元素在哪个方向浮动。
类似于优先级机制:position:absolute/fixed优先级最高,有他们在时,float不起作用,display值需要调整。float 或者absolute定位的元素,只能是块元素或表格。

19 对BFC规范(块级格式化上下文:block formatting context)的理解?

BFC规定了内部的Block Box如何布局。
定位方案:

  1. 内部的Box会在垂直方向上一个接一个放置。
  2. Box垂直方向的距离由margin决定,属于同一个BFC的两个相邻Box的margin会发生重叠。
  3. 每个元素的margin box 的左边,与包含块border box的左边相接触。
  4. BFC的区域不会与float box重叠。
  5. BFC是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。
  6. 计算BFC的高度时,浮动元素也会参与计算。

满足下列条件之一就可触发BFC

  1. 根元素,即html
  2. float的值不为none(默认)
  3. overflow的值不为visible(默认)
  4. display的值为inline-block、table-cell、table-caption
  5. position的值为absolute或fixed

20 为什么会出现浮动和什么时候需要清除浮动?清除浮动的方式?

浮动元素碰到包含它的边框或者浮动元素的边框停留。由于浮动元素不在文档流中,所以文档流的块框表现得就像浮动框不存在一样。浮动元素会漂浮在文档流的块框上。
浮动带来的问题:

  1. 父元素的高度无法被撑开,影响与父元素同级的元素
  2. 与浮动元素同级的非浮动元素(内联元素)会跟随其后
  3. 若非第一个元素浮动,则该元素之前的元素也需要浮动,否则会影响页面显示的结构。

清除浮动的方式:

  1. 父级div定义height
  2. 最后一个浮动元素后加空div标签 并添加样式clear:both。
  3. 包含浮动元素的父标签添加样式overflow为hidden或auto。
  4. 父级div定义zoom

21 上下margin重合的问题

在重合元素外包裹一层容器,并触发该容器生成一个BFC。
例子:

<div class="aside"></div>
<div class="text">
    <div class="main"></div>
</div>
<!--下面是css代码-->
 .aside {
            margin-bottom: 100px;  
            width: 100px;
            height: 150px;
            background: #f66;
        }
        .main {
            margin-top: 100px;
            height: 200px;
            background: #fcc;
        }
         .text{
            /*盒子main的外面包一个div,通过改变此div的属性使两个盒子分属于两个不同的BFC,以此来阻止margin重叠*/
            overflow: hidden;  //此时已经触发了BFC属性。
        }

22设置元素浮动后,该元素的display值是多少?

自动变成display:block

23 移动端的布局用过媒体查询吗?

通过媒体查询可以为不同大小和尺寸的媒体定义不同的css,适应相应的设备的显示。

  1. <head>里边

    <link rel="stylesheet" type="text/css" href="xxx.css" media="only screen and (max-device-width:480px)">

  2. CSS : @media only screen and (max-device-width:480px) {/css样式/}

24 使用 CSS 预处理器吗?
Less sass

25 CSS优化、提高性能的方法有哪些?

  1. 避免过度约束
  2. 避免后代选择符
  3. 避免链式选择符
  4. 使用紧凑的语法
  5. 避免不必要的命名空间
  6. 避免不必要的重复
  7. 最好使用表示语义的名字。一个好的类名应该是描述他是什么而不是像什么
  8. 避免!important,可以选择其他选择器
  9. 尽可能的精简规则,你可以合并不同类里的重复规则

26 浏览器是怎样解析CSS选择器的?

CSS选择器的解析是从右向左解析的。若从左向右的匹配,发现不符合规则,需要进行回溯,会损失很多性能。若从右向左匹配,先找到所有的最右节点,对于每一个节点,向上寻找其父节点直到找到根元素或满足条件的匹配规则,则结束这个分支的遍历。两种匹配规则的性能差别很大,是因为从右向左的匹配在第一步就筛选掉了大量的不符合条件的最右节点(叶子节点),而从左向右的匹配规则的性能都浪费在了失败的查找上面。
而在 CSS 解析完毕后,需要将解析的结果与 DOM Tree 的内容一起进行分析建立一棵 Render Tree,最终用来进行绘图。在建立 Render Tree 时(WebKit 中的「Attachment」过程),浏览器就要为每个 DOM Tree 中的元素根据 CSS 的解析结果(Style Rules)来确定生成怎样的 Render Tree。

27 在网页中的应该使用奇数还是偶数的字体?为什么呢?

使用偶数字体。偶数字号相对更容易和 web 设计的其他部分构成比例关系。Windows 自带的点阵宋体(中易宋体)从 Vista 开始只提供 12、14、16 px 这三个大小的点阵,而 13、15、17 px时用的是小一号的点。(即每个字占的空间大了 1 px,但点阵没变),于是略显稀疏。

28 margin和padding分别适合什么场景使用?

何时使用margin:

  1. 需要在border外侧添加空白
  2. 空白处不需要背景色
  3. 上下相连的两个盒子之间的空白,需要相互抵消时。

何时使用padding:

  1. 需要在border内侧添加空白
  2. 空白处需要背景颜色
  3. 上下相连的两个盒子的空白,希望为两者之和。

兼容性的问题:在IE5 IE6中,为float的盒子指定margin时,左侧的margin可能会变成两倍的宽度。通过改变padding或者指定盒子的display:inline解决。

29 元素竖向的百分比设定是相对于容器的高度吗?

当按百分比设定一个元素的宽度时,它是相对于父容器的宽度计算的,但是,对于一些表示竖向距离的属性,例如 padding-top , padding-bottom , margin-top , margin-bottom 等,当按百分比设定它们时,依据的也是父容器的宽度,而不是高度。

30 全屏滚动的原理是什么?用到了CSS的哪些属性?

  1. 原理:有点类似于轮播,整体的元素一直排列下去,假设有5个需要展示的全屏页面,那么高度是500%,只是展示100%,剩下的可以通过transform进行y轴定位,也可以通过margin-top实现
  2. overflow:hidden;transition:all 1000ms ease;

31 什么是响应式设计?响应式设计的基本原理是什么?如何兼容低版本的IE?

响应式网站设计(Responsive Web design)是一个网站能够兼容多个终端,而不是为每一个终端做一个特定的版本。
基本原理是通过媒体查询检测不同的设备屏幕尺寸做处理。
页面头部必须有meta声明的viewport。

<meta name=’viewport’ content=”width=device-width, initial-scale=1. maximum-scale=1,user-scalable=no”>

32 视差滚动效果?

视差滚动(Parallax Scrolling)通过在网页向下滚动的时候,控制背景的移动速度比前景的移动速度慢来创建出令人惊叹的3D效果。

  1. CSS3实现
    优点:开发时间短、性能和开发效率比较好,缺点是不能兼容到低版本的浏览器
  2. jQuery实现
    通过控制不同层滚动速度,计算每一层的时间,控制滚动效果。
    优点:能兼容到各个版本的,效果可控性好
    缺点:开发起来对制作者要求高
  3. 插件实现方式
    例如:parallax-scrolling,兼容性十分好

33 ::before 和 :after中双冒号和单冒号有什么区别?解释一下这2个伪元素的作用

  1. 单冒号(:)用于CSS3伪类,双冒号(::)用于CSS3伪元素。
  2. ::before就是以一个子元素的存在,定义在元素主体内容之前的一个伪元素。并不存在于dom之中,只存在在页面之中。

:before 和 :after 这两个伪元素,是在CSS2.1里新出现的。起初,伪元素的前缀使用的是单冒号语法,但随着Web的进化,在CSS3的规范里,伪元素的语法被修改成使用双冒号,成为::before ::after

34 你对line-height是如何理解的?

行高是指一行文字的高度,具体说是两行文字间基线的距离。CSS中起高度作用的是height和line-height,没有定义height属性,最终其表现作用一定是line-height。
单行文本垂直居中:把line-height值设置为height一样大小的值可以实现单行文字的垂直居中,其实也可以把height删除。
多行文本垂直居中:需要设置display属性为inline-block。

35 怎么让Chrome支持小于12px 的文字?

p{font-size:10px;-webkit-transform:scale(0.8);} //0.8是缩放比例

36 让页面里的字体变清晰,变细用CSS怎么做?

-webkit-font-smoothing在window系统下没有起作用,但是在IOS设备上起作用-webkit-font-smoothing:antialiased是最佳的,灰度平滑。

37 position:fixed;在android下无效怎么处理?

<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no"/>

38 如果需要手动写动画,你认为最小时间间隔是多久,为什么?
多数显示器默认频率是60Hz,即1秒刷新60次,所以理论上最小间隔为1/60*1000ms = 16.7ms。

39 li与li之间有看不见的空白间隔是什么原因引起的?有什么解决办法?

行框的排列会受到中间空白(回车空格)等的影响,因为空格也属于字符,这些空白也会被应用样式,占据空间,所以会有间隔,把字符大小设为0,就没有空格了。
解决方法:

  1. 可以将<li>代码全部写在一排
  2. 浮动li中float:left
  3. 在ul中用font-size:0(谷歌不支持);可以使用letter-space:-3px

40 display:inline-block 什么时候会显示间隙?

  1. 有空格时候会有间隙 解决:移除空格
  2. margin正值的时候 解决:margin使用负值
  3. 使用font-size时候 解决:font-size:0、letter-spacing、word-spacing

41 有一个高度自适应的div,里面有两个div,一个高度100px,希望另一个填满剩下的高度

外层div使用position:relative;高度要求自适应的div使用position: absolute; top: 100px; bottom: 0; left: 0

42 png、jpg、gif 这些图片格式解释一下,分别什么时候用。有没有了解过webp?

  1. png是便携式网络图片(Portable Network Graphics)是一种无损数据压缩位图文件格式.优点是:压缩比高,色彩好。 大多数地方都可以用。
  2. jpg是一种针对相片使用的一种失真压缩方法,是一种破坏性的压缩,在色调及颜色平滑变化做的不错。在www上,被用来储存和传输照片的格式。
  3. gif是一种位图文件格式,以8位色重现真色彩的图像。可以实现动画效果.
  4. webp格式是谷歌在2010年推出的图片格式,压缩率只有jpg的2/3,大小比png小了45%。缺点是压缩的时间更久了,兼容性不好,目前谷歌和opera支持。

43 style标签写在body后与body前有什么区别?

页面加载自上而下 当然是先加载样式。
写在body标签后由于浏览器以逐行方式对HTML文档进行解析,当解析到写在尾部的样式表(外联或写在style标签)会导致浏览器停止之前的渲染,等待加载且解析样式表完成之后重新渲染,在windows的IE下可能会出现FOUC现象(即样式失效导致的页面闪烁问题)

44 CSS属性overflow属性定义溢出元素内容区的内容会如何处理?

参数是scroll时候,必会出现滚动条。
参数是auto时候,子元素内容大于父元素时出现滚动条。
参数是visible时候,溢出的内容出现在父元素之外。
参数是hidden时候,溢出隐藏。

45 阐述一下CSS Sprites

将一个页面涉及到的所有图片都包含到一张大图中去,然后利用CSS的 background-image,background- repeat,background-position 的组合进行背景定位。利用CSS Sprites能很好地减少网页的http请求,从而大大的提高页面的性能;CSS Sprites能减少图片的字节。

持续更新中...

查看原文

niaogege 赞了文章 · 2月4日

50道CSS基础面试题(附答案)

1 介绍一下标准的CSS的盒子模型?与低版本IE的盒子模型有什么不同的?

标准盒子模型:宽度=内容的宽度(content)+ border + padding + margin
低版本IE盒子模型:宽度=内容宽度(content+border+padding)+ margin

2 box-sizing属性?

用来控制元素的盒子模型的解析模式,默认为content-box
context-box:W3C的标准盒子模型,设置元素的 height/width 属性指的是content部分的高/宽
border-box:IE传统盒子模型。设置元素的height/width属性指的是border + padding + content部分的高/宽

3 CSS选择器有哪些?哪些属性可以继承?

CSS选择符:id选择器(#myid)、类选择器(.myclassname)、标签选择器(div, h1, p)、相邻选择器(h1 + p)、子选择器(ul > li)、后代选择器(li a)、通配符选择器(*)、属性选择器(a[rel="external"])、伪类选择器(a:hover, li:nth-child)

可继承的属性:font-size, font-family, color

不可继承的样式:border, padding, margin, width, height

优先级(就近原则):!important > [ id > class > tag ]
!important 比内联优先级高

4 CSS优先级算法如何计算?

元素选择符: 1
class选择符: 10
id选择符:100
元素标签:1000

  1. !important声明的样式优先级最高,如果冲突再进行计算。
  2. 如果优先级相同,则选择最后出现的样式。
  3. 继承得到的样式的优先级最低。

5 CSS3新增伪类有那些?

p:first-of-type 选择属于其父元素的首个元素
p:last-of-type 选择属于其父元素的最后元素
p:only-of-type 选择属于其父元素唯一的元素
p:only-child 选择属于其父元素的唯一子元素
p:nth-child(2) 选择属于其父元素的第二个子元素
:enabled :disabled 表单控件的禁用状态。
:checked 单选框或复选框被选中。

6 如何居中div?如何居中一个浮动元素?如何让绝对定位的div居中?

div:

border: 1px solid red;
margin: 0 auto; 
height: 50px;
width: 80px;

浮动元素的上下左右居中:

border: 1px solid red;
float: left;
position: absolute;
width: 200px;
height: 100px;
left: 50%;
top: 50%;
margin: -50px 0 0 -100px; 

绝对定位的左右居中:

border: 1px solid black;
position: absolute;
width: 200px;
height: 100px;
margin: 0 auto;
left: 0;
right: 0; 

还有更加优雅的居中方式就是用flexbox,我以后会做整理。

7 display有哪些值?说明他们的作用?

inline(默认)--内联
none--隐藏
block--块显示
table--表格显示
list-item--项目列表
inline-block

8 position的值?

static(默认):按照正常文档流进行排列;
relative(相对定位):不脱离文档流,参考自身静态位置通过 top, bottom, left, right 定位;
absolute(绝对定位):参考距其最近一个不为static的父级元素通过top, bottom, left, right 定位;
fixed(固定定位):所固定的参照对像是可视窗口。

9 CSS3有哪些新特性?

  1. RGBA和透明度
  2. background-image background-origin(content-box/padding-box/border-box) background-size background-repeat
  3. word-wrap(对长的不可分割单词换行)word-wrap:break-word
  4. 文字阴影:text-shadow: 5px 5px 5px #FF0000;(水平阴影,垂直阴影,模糊距离,阴影颜色)
  5. font-face属性:定义自己的字体
  6. 圆角(边框半径):border-radius 属性用于创建圆角
  7. 边框图片:border-image: url(border.png) 30 30 round
  8. 盒阴影:box-shadow: 10px 10px 5px #888888
  9. 媒体查询:定义两套css,当浏览器的尺寸变化时会采用不同的属性

10 请解释一下CSS3的flexbox(弹性盒布局模型),以及适用场景?

该布局模型的目的是提供一种更加高效的方式来对容器中的条目进行布局、对齐和分配空间。在传统的布局方式中,block 布局是把块在垂直方向从上到下依次排列的;而 inline 布局则是在水平方向来排列。弹性盒布局并没有这样内在的方向限制,可以由开发人员自由操作。
试用场景:弹性布局适合于移动前端开发,在Android和ios上也完美支持。

11 用纯CSS创建一个三角形的原理是什么?

首先,需要把元素的宽度、高度设为0。然后设置边框样式。

width: 0;
height: 0;
border-top: 40px solid transparent;
border-left: 40px solid transparent;
border-right: 40px solid transparent;
border-bottom: 40px solid #ff0000;

12 一个满屏品字布局如何设计?

第一种真正的品字:

  1. 三块高宽是确定的;
  2. 上面那块用margin: 0 auto;居中;
  3. 下面两块用float或者inline-block不换行;
  4. 用margin调整位置使他们居中。

第二种全屏的品字布局:
上面的div设置成100%,下面的div分别宽50%,然后使用float或者inline使其不换行。

13 常见的兼容性问题?

  1. 不同浏览器的标签默认的margin和padding不一样。

    *{margin:0;padding:0;}

  2. IE6双边距bug:块属性标签float后,又有横行的margin情况下,在IE6显示margin比设置的大。hack:display:inline;将其转化为行内属性。
  3. 渐进识别的方式,从总体中逐渐排除局部。首先,巧妙的使用“9”这一标记,将IE浏览器从所有情况中分离出来。接着,再次使用“+”将IE8和IE7、IE6分离开来,这样IE8已经独立识别。

    {
    background-color:#f1ee18;/*所有识别*/
    .background-color:#00deff\9; /*IE6、7、8识别*/
    +background-color:#a200ff;/*IE6、7识别*/
    _background-color:#1e0bd1;/*IE6识别*/
    }
    
  4. 设置较小高度标签(一般小于10px),在IE6,IE7中高度超出自己设置高度。hack:给超出高度的标签设置overflow:hidden;或者设置行高line-height 小于你设置的高度。
  5. IE下,可以使用获取常规属性的方法来获取自定义属性,也可以使用getAttribute()获取自定义属性;Firefox下,只能使用getAttribute()获取自定义属性。解决方法:统一通过getAttribute()获取自定义属性。
  6. Chrome 中文界面下默认会将小于 12px 的文本强制按照 12px 显示,可通过加入 CSS 属性 -webkit-text-size-adjust: none; 解决。
  7. 超链接访问过后hover样式就不出现了,被点击访问过的超链接样式不再具有hover和active了。解决方法是改变CSS属性的排列顺序:L-V-H-A ( love hate ): a:link {} a:visited {} a:hover {} a:active {}

14 为什么要初始化CSS样式

因为浏览器的兼容问题,不同浏览器对有些标签的默认值是不同的,如果没对CSS初始化往往会出现浏览器之间的页面显示差异。

15 absolute的containing block计算方式跟正常流有什么不同?

无论属于哪种,都要先找到其祖先元素中最近的 position 值不为 static 的元素,然后再判断:

  1. 若此元素为 inline 元素,则 containing block 为能够包含这个元素生成的第一个和最后一个 inline box 的 padding box (除 margin, border 外的区域) 的最小矩形;
  2. 否则,则由这个祖先元素的 padding box 构成。

如果都找不到,则为 initial containing block。

补充:

  1. static(默认的)/relative:简单说就是它的父元素的内容框(即去掉padding的部分)
  2. absolute: 向上找最近的定位为absolute/relative的元素
  3. fixed: 它的containing block一律为根元素(html/body)

16 CSS里的visibility属性有个collapse属性值?在不同浏览器下以后什么区别?

当一个元素的visibility属性被设置成collapse值后,对于一般的元素,它的表现跟hidden是一样的。

  1. chrome中,使用collapse值和使用hidden没有区别。
  2. firefox,opera和IE,使用collapse值和使用display:none没有什么区别。

17 display:none与visibility:hidden的区别?

display:none 不显示对应的元素,在文档布局中不再分配空间(回流+重绘)
visibility:hidden 隐藏对应元素,在文档布局中仍保留原来的空间(重绘)

18 position跟display、overflow、float这些特性相互叠加后会怎么样?

display属性规定元素应该生成的框的类型;position属性规定元素的定位类型;float属性是一种布局方式,定义元素在哪个方向浮动。
类似于优先级机制:position:absolute/fixed优先级最高,有他们在时,float不起作用,display值需要调整。float 或者absolute定位的元素,只能是块元素或表格。

19 对BFC规范(块级格式化上下文:block formatting context)的理解?

BFC规定了内部的Block Box如何布局。
定位方案:

  1. 内部的Box会在垂直方向上一个接一个放置。
  2. Box垂直方向的距离由margin决定,属于同一个BFC的两个相邻Box的margin会发生重叠。
  3. 每个元素的margin box 的左边,与包含块border box的左边相接触。
  4. BFC的区域不会与float box重叠。
  5. BFC是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。
  6. 计算BFC的高度时,浮动元素也会参与计算。

满足下列条件之一就可触发BFC

  1. 根元素,即html
  2. float的值不为none(默认)
  3. overflow的值不为visible(默认)
  4. display的值为inline-block、table-cell、table-caption
  5. position的值为absolute或fixed

20 为什么会出现浮动和什么时候需要清除浮动?清除浮动的方式?

浮动元素碰到包含它的边框或者浮动元素的边框停留。由于浮动元素不在文档流中,所以文档流的块框表现得就像浮动框不存在一样。浮动元素会漂浮在文档流的块框上。
浮动带来的问题:

  1. 父元素的高度无法被撑开,影响与父元素同级的元素
  2. 与浮动元素同级的非浮动元素(内联元素)会跟随其后
  3. 若非第一个元素浮动,则该元素之前的元素也需要浮动,否则会影响页面显示的结构。

清除浮动的方式:

  1. 父级div定义height
  2. 最后一个浮动元素后加空div标签 并添加样式clear:both。
  3. 包含浮动元素的父标签添加样式overflow为hidden或auto。
  4. 父级div定义zoom

21 上下margin重合的问题

在重合元素外包裹一层容器,并触发该容器生成一个BFC。
例子:

<div class="aside"></div>
<div class="text">
    <div class="main"></div>
</div>
<!--下面是css代码-->
 .aside {
            margin-bottom: 100px;  
            width: 100px;
            height: 150px;
            background: #f66;
        }
        .main {
            margin-top: 100px;
            height: 200px;
            background: #fcc;
        }
         .text{
            /*盒子main的外面包一个div,通过改变此div的属性使两个盒子分属于两个不同的BFC,以此来阻止margin重叠*/
            overflow: hidden;  //此时已经触发了BFC属性。
        }

22设置元素浮动后,该元素的display值是多少?

自动变成display:block

23 移动端的布局用过媒体查询吗?

通过媒体查询可以为不同大小和尺寸的媒体定义不同的css,适应相应的设备的显示。

  1. <head>里边

    <link rel="stylesheet" type="text/css" href="xxx.css" media="only screen and (max-device-width:480px)">

  2. CSS : @media only screen and (max-device-width:480px) {/css样式/}

24 使用 CSS 预处理器吗?
Less sass

25 CSS优化、提高性能的方法有哪些?

  1. 避免过度约束
  2. 避免后代选择符
  3. 避免链式选择符
  4. 使用紧凑的语法
  5. 避免不必要的命名空间
  6. 避免不必要的重复
  7. 最好使用表示语义的名字。一个好的类名应该是描述他是什么而不是像什么
  8. 避免!important,可以选择其他选择器
  9. 尽可能的精简规则,你可以合并不同类里的重复规则

26 浏览器是怎样解析CSS选择器的?

CSS选择器的解析是从右向左解析的。若从左向右的匹配,发现不符合规则,需要进行回溯,会损失很多性能。若从右向左匹配,先找到所有的最右节点,对于每一个节点,向上寻找其父节点直到找到根元素或满足条件的匹配规则,则结束这个分支的遍历。两种匹配规则的性能差别很大,是因为从右向左的匹配在第一步就筛选掉了大量的不符合条件的最右节点(叶子节点),而从左向右的匹配规则的性能都浪费在了失败的查找上面。
而在 CSS 解析完毕后,需要将解析的结果与 DOM Tree 的内容一起进行分析建立一棵 Render Tree,最终用来进行绘图。在建立 Render Tree 时(WebKit 中的「Attachment」过程),浏览器就要为每个 DOM Tree 中的元素根据 CSS 的解析结果(Style Rules)来确定生成怎样的 Render Tree。

27 在网页中的应该使用奇数还是偶数的字体?为什么呢?

使用偶数字体。偶数字号相对更容易和 web 设计的其他部分构成比例关系。Windows 自带的点阵宋体(中易宋体)从 Vista 开始只提供 12、14、16 px 这三个大小的点阵,而 13、15、17 px时用的是小一号的点。(即每个字占的空间大了 1 px,但点阵没变),于是略显稀疏。

28 margin和padding分别适合什么场景使用?

何时使用margin:

  1. 需要在border外侧添加空白
  2. 空白处不需要背景色
  3. 上下相连的两个盒子之间的空白,需要相互抵消时。

何时使用padding:

  1. 需要在border内侧添加空白
  2. 空白处需要背景颜色
  3. 上下相连的两个盒子的空白,希望为两者之和。

兼容性的问题:在IE5 IE6中,为float的盒子指定margin时,左侧的margin可能会变成两倍的宽度。通过改变padding或者指定盒子的display:inline解决。

29 元素竖向的百分比设定是相对于容器的高度吗?

当按百分比设定一个元素的宽度时,它是相对于父容器的宽度计算的,但是,对于一些表示竖向距离的属性,例如 padding-top , padding-bottom , margin-top , margin-bottom 等,当按百分比设定它们时,依据的也是父容器的宽度,而不是高度。

30 全屏滚动的原理是什么?用到了CSS的哪些属性?

  1. 原理:有点类似于轮播,整体的元素一直排列下去,假设有5个需要展示的全屏页面,那么高度是500%,只是展示100%,剩下的可以通过transform进行y轴定位,也可以通过margin-top实现
  2. overflow:hidden;transition:all 1000ms ease;

31 什么是响应式设计?响应式设计的基本原理是什么?如何兼容低版本的IE?

响应式网站设计(Responsive Web design)是一个网站能够兼容多个终端,而不是为每一个终端做一个特定的版本。
基本原理是通过媒体查询检测不同的设备屏幕尺寸做处理。
页面头部必须有meta声明的viewport。

<meta name=’viewport’ content=”width=device-width, initial-scale=1. maximum-scale=1,user-scalable=no”>

32 视差滚动效果?

视差滚动(Parallax Scrolling)通过在网页向下滚动的时候,控制背景的移动速度比前景的移动速度慢来创建出令人惊叹的3D效果。

  1. CSS3实现
    优点:开发时间短、性能和开发效率比较好,缺点是不能兼容到低版本的浏览器
  2. jQuery实现
    通过控制不同层滚动速度,计算每一层的时间,控制滚动效果。
    优点:能兼容到各个版本的,效果可控性好
    缺点:开发起来对制作者要求高
  3. 插件实现方式
    例如:parallax-scrolling,兼容性十分好

33 ::before 和 :after中双冒号和单冒号有什么区别?解释一下这2个伪元素的作用

  1. 单冒号(:)用于CSS3伪类,双冒号(::)用于CSS3伪元素。
  2. ::before就是以一个子元素的存在,定义在元素主体内容之前的一个伪元素。并不存在于dom之中,只存在在页面之中。

:before 和 :after 这两个伪元素,是在CSS2.1里新出现的。起初,伪元素的前缀使用的是单冒号语法,但随着Web的进化,在CSS3的规范里,伪元素的语法被修改成使用双冒号,成为::before ::after

34 你对line-height是如何理解的?

行高是指一行文字的高度,具体说是两行文字间基线的距离。CSS中起高度作用的是height和line-height,没有定义height属性,最终其表现作用一定是line-height。
单行文本垂直居中:把line-height值设置为height一样大小的值可以实现单行文字的垂直居中,其实也可以把height删除。
多行文本垂直居中:需要设置display属性为inline-block。

35 怎么让Chrome支持小于12px 的文字?

p{font-size:10px;-webkit-transform:scale(0.8);} //0.8是缩放比例

36 让页面里的字体变清晰,变细用CSS怎么做?

-webkit-font-smoothing在window系统下没有起作用,但是在IOS设备上起作用-webkit-font-smoothing:antialiased是最佳的,灰度平滑。

37 position:fixed;在android下无效怎么处理?

<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no"/>

38 如果需要手动写动画,你认为最小时间间隔是多久,为什么?
多数显示器默认频率是60Hz,即1秒刷新60次,所以理论上最小间隔为1/60*1000ms = 16.7ms。

39 li与li之间有看不见的空白间隔是什么原因引起的?有什么解决办法?

行框的排列会受到中间空白(回车空格)等的影响,因为空格也属于字符,这些空白也会被应用样式,占据空间,所以会有间隔,把字符大小设为0,就没有空格了。
解决方法:

  1. 可以将<li>代码全部写在一排
  2. 浮动li中float:left
  3. 在ul中用font-size:0(谷歌不支持);可以使用letter-space:-3px

40 display:inline-block 什么时候会显示间隙?

  1. 有空格时候会有间隙 解决:移除空格
  2. margin正值的时候 解决:margin使用负值
  3. 使用font-size时候 解决:font-size:0、letter-spacing、word-spacing

41 有一个高度自适应的div,里面有两个div,一个高度100px,希望另一个填满剩下的高度

外层div使用position:relative;高度要求自适应的div使用position: absolute; top: 100px; bottom: 0; left: 0

42 png、jpg、gif 这些图片格式解释一下,分别什么时候用。有没有了解过webp?

  1. png是便携式网络图片(Portable Network Graphics)是一种无损数据压缩位图文件格式.优点是:压缩比高,色彩好。 大多数地方都可以用。
  2. jpg是一种针对相片使用的一种失真压缩方法,是一种破坏性的压缩,在色调及颜色平滑变化做的不错。在www上,被用来储存和传输照片的格式。
  3. gif是一种位图文件格式,以8位色重现真色彩的图像。可以实现动画效果.
  4. webp格式是谷歌在2010年推出的图片格式,压缩率只有jpg的2/3,大小比png小了45%。缺点是压缩的时间更久了,兼容性不好,目前谷歌和opera支持。

43 style标签写在body后与body前有什么区别?

页面加载自上而下 当然是先加载样式。
写在body标签后由于浏览器以逐行方式对HTML文档进行解析,当解析到写在尾部的样式表(外联或写在style标签)会导致浏览器停止之前的渲染,等待加载且解析样式表完成之后重新渲染,在windows的IE下可能会出现FOUC现象(即样式失效导致的页面闪烁问题)

44 CSS属性overflow属性定义溢出元素内容区的内容会如何处理?

参数是scroll时候,必会出现滚动条。
参数是auto时候,子元素内容大于父元素时出现滚动条。
参数是visible时候,溢出的内容出现在父元素之外。
参数是hidden时候,溢出隐藏。

45 阐述一下CSS Sprites

将一个页面涉及到的所有图片都包含到一张大图中去,然后利用CSS的 background-image,background- repeat,background-position 的组合进行背景定位。利用CSS Sprites能很好地减少网页的http请求,从而大大的提高页面的性能;CSS Sprites能减少图片的字节。

持续更新中...

查看原文

赞 304 收藏 750 评论 55

niaogege 收藏了文章 · 1月16日

关于协程和 ES6 中的 Generator

关于协程和 ES6 中的 Generator

什么是协程?

进程和线程

众所周知,进程线程都是一个时间段的描述,是CPU工作时间段的描述,不过是颗粒大小不同,进程是 CPU 资源分配的最小单位,线程是 CPU 调度的最小单位。

其实协程(微线程,纤程,Coroutine)的概念很早就提出来了,可以认为是比线程更小的执行单元,但直到最近几年才在某些语言中得到广泛应用。

那么什么是协程呢?

子程序,或者称为函数,在所有语言中都是层级调用的,比如 A 调用 B,B 在执行过程中又调用 C,C 执行完毕返回,B 执行完毕返回,最后是 A 执行完毕,显然子程序调用是通过栈实现的,一个线程就是执行一个子程序,子程序调用总是一个入口,一次返回,调用顺序是明确的;而协程的调用和子程序不同,协程看上去也是子程序,但执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。

我们用一个简单的例子来说明,比如现有程序 A 和 B:

def A():
    print '1'
    print '2'
    print '3'

def B():
    print 'x'
    print 'y'
    print 'z'

假设由协程执行,在执行 A 的过程中,可以随时中断,去执行 B,B 也可能在执行过程中中断再去执行 A,结果可能是:

1
2
x
y
3
z

但是在 A 中是没有调用 B 的,所以协程的调用比函数调用理解起来要难一些。看起来 A、B 的执行有点像多线程,但协程的特点在于是一个线程执行,和多线程比协程最大的优势就是协程极高的执行效率,因为子程序切换不是线程切换,而是由程序自身控制,因此没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显;第二大优势就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态即可,所以执行效率比多线程高很多。

Wiki 中的定义: Coroutine

协程是一种程序组件,是由子例程(过程、函数、例程、方法、子程序)的概念泛化而来的,子例程只有一个入口点且只返回一次,而协程允许多个入口点,可以在指定位置挂起和恢复执行。
  • 协程的本地数据在后续调用中始终保持
  • 协程在控制离开时暂停执行,当控制再次进入时只能从离开的位置继续执行

解释协程时最常见的就是生产消费者模式:

var q := new queue

coroutine produce
    loop
        while q is not full
            create some new items
            add the items to q
        yield to consume

coroutine consume
    loop
        while q is not empty
            remove some items from q
            use the items
        yield to produce

这个例子中容易让人产生疑惑的一点就是 yield 的使用,它与我们通常所见的 yield 指令不同,因为我们常见的 yield 指令大都是基于生成器(Generator)这一概念的。

var q := new queue

generator produce
    loop
        while q is not full
            create some new items
            add the items to q
        yield consume
        
generator consume
    loop
        while q is not empty
            remove some items from q
            use the items
        yield produce
        
subroutine dispatcher
    var d := new dictionary(generator → iterator)
    d[produce] := start produce
    d[consume] := start consume
    var current := produce
    loop
        current := next d[current]

这是基于生成器实现的协程,我们看这里的 produce 与 consume 过程完全符合协程的概念,不难发现根据定义生成器本身就是协程。

“子程序就是协程的一种特例。” —— Donald Knuth

什么是 Generator?

在本文我们使用 ES6 中的 Generators 特性来介绍生成器,它是 ES6 提供的一种异步编程解决方案,语法上首先可以把它理解成是一个状态机,封装多个内部状态,执行 Generator 函数会返回一个遍历器对象,也就是说 Generator 函数除状态机外,还是一个遍历器对象生成函数,返回的遍历器对象可以依次遍历 Generator 函数内部的每一个状态,先看一个简单的例子:

function* quips(name) {
  yield "你好 " + name + "!";
  yield "希望你能喜欢这篇介绍ES6的译文";
  if (name.startsWith("X")) {
    yield "你的名字 " + name + "  首字母是X,这很酷!";
  }
  yield "我们下次再见!";
}

这段代码看起来很像一个函数,我们称之为生成器函数,它与普通函数有很多共同点,但是二者有如下区别:

  • 普通函数使用 function 声明,而生成器函数使用 function* 声明
  • 在生成器函数内部,有一种类似 return 的语法即关键字 yield,二者的区别是普通函数只可以 return 一次,而生成器函数可以 yield 多次,在生成器函数的执行过程中,遇到 yield 表达式立即暂停,并且后续可恢复执行状态

Generator 函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号,不同的是调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象

当调用 quips() 生成器函数时发生什么?

> var iter = quips("jorendorff");
  [object Generator]
> iter.next()
  { value: "你好 jorendorff!", done: false }
> iter.next()
  { value: "希望你能喜欢这篇介绍ES6的译文", done: false }
> iter.next()
  { value: "我们下次再见!", done: false }
> iter.next()
  { value: undefined, done: true }

每当生成器执行 yield 语句时,生成器的堆栈结构(本地变量、参数、临时值、生成器内部当前的执行位置 etc.)被移出堆栈,然而生成器对象保留对这个堆栈结构的引用(备份),所以稍后调用 .next() 可以重新激活堆栈结构并且继续执行。当生成器运行时,它和调用者处于同一线程中,拥有确定的连续执行顺序,永不并发。

遍历器对象的 next 方法的运行逻辑如下:

  1. 遇到 yield 表达式,就暂停执行后面的操作,并将紧跟在 yield 后面的那个表达式的值,作为返回的对象的 value 属性值
  2. 下一次调用 next 方法时,再继续往下执行,直到遇到下一个 yield 表达式
  3. 如果没有再遇到新的 yield 表达式,就一直运行到函数结束,直到 return 语句为止,并将 return 语句后面的表达式的值,作为返回的对象的 value 属性值
  4. 如果该函数没有 return 语句,则返回的对象的 value 属性值为 undefined

生成器是迭代器!

迭代器是 ES6 中独立的内建类,同时也是语言的一个扩展点,通过实现 [Symbol.iterator]() 和 .next() 两个方法就可以创建自定义迭代器。

// 应该弹出三次 "ding"
for (var value of range(0, 3)) {
  alert("Ding! at floor #" + value);
}

我们可以使用生成器实现上面循环中的 range 方法:

function* range(start, stop) {
  for (var i = start; i < stop; i++)
    yield i;
}

生成器是迭代器,所有的生成器都有内建 .next() 和 [Symbol.iterator]() 方法的实现,我们只需要编写循环部分的行为即可。

for...of 循环可以自动遍历 Generator 函数时生成的 Iterator 对象,且此时不再需要调用 next 方法。

function* foo() {
  yield 1;
  yield 2;
  yield 3;
  yield 4;
  yield 5;
  return 6;
}

for (let v of foo()) {
  console.log(v);
}

// 1 2 3 4 5

上面代码使用 for...of 循环,依次显示 5 个 yield 表达式的值。这里需要注意,一旦 next 方法的返回对象的 done 属性为 true,for...of 循环就会中止,且不包含该返回对象,所以上面代码的 return 语句返回的6,不包括在 for...of 循环之中。

下面是一个利用 Generator 函数和 for...of 循环,实现斐波那契数列的例子:

function* fibonacci() {
  let [prev, curr] = [0, 1];
  for (;;) {
    [prev, curr] = [curr, prev + curr];
    yield curr;
  }
}

for (let n of fibonacci()) {
  if (n > 1000) break;
  console.log(n);
}
除了 for...of 循环以外,扩展运算符(...)、解构赋值和 Array.from 方法内部调用的,都是遍历器接口,这意味着它们都可以将 Generator 函数返回的 Iterator 对象作为参数。

使用 Generator 实现生产消费者模式

function producer(c) {
    c.next();
    let n = 0;
    while (n < 5) {
        n++;
        console.log(`[PRODUCER] Producing ${n}`);
        const { value: r } = c.next(n);
        console.log(`[PRODUCER] Consumer return: ${r}`);
    }
    c.return();
}

function* consumer() {
    let r = '';
    while (true) {
        const n = yield r;
        if (!n) return;
        console.log(`[CONSUMER] Consuming ${n}`);
        r = '200 OK';
    }
}

const c = consumer();
producer(c);
[PRODUCER] Producing 1
[CONSUMER] Consuming 1
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 2
[CONSUMER] Consuming 2
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 3
[CONSUMER] Consuming 3
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 4
[CONSUMER] Consuming 4
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 5
[CONSUMER] Consuming 5
[PRODUCER] Consumer return: 200 OK
[Finished in 0.1s]

异步流程控制

ES6 诞生以前,异步编程的方法大概有下面四种:

  1. 回调函数
  2. 事件监听
  3. 发布/订阅
  4. Promise 对象

想必大家都经历过同样的问题,在异步流程控制中会使用大量的回调函数,甚至出现多个回调函数嵌套导致的情况,代码不是纵向发展而是横向发展,很快就会乱成一团无法管理,因为多个异步操作形成强耦合,只要有一个操作需要修改,它的上层回调函数和下层回调函数,可能都要跟着修改,这种情况就是我们常说的"回调函数地狱"。

Promise 对象就是为了解决这个问题而提出的,它不是新的语法功能,而是一种新的写法,允许将回调函数的嵌套,改成链式调用。然而,Promise 的最大问题就是代码冗余,原来的任务被 Promise 包装一下,不管什么操作一眼看去都是一堆 then,使得原来的语义变得很不清楚。

那么,有没有更好的写法呢?

哈哈这里有些明知故问,答案当然就是 Generator!Generator 函数是协程在 ES6 的实现,整个 Generator 函数就是一个封装的异步任务,或者说是异步任务的容器,Generator 函数可以暂停执行和恢复执行,这是它能封装异步任务的根本原因,除此之外,它还有两个特性使它可以作为异步编程的完整解决方案:函数体内外的数据交换和错误处理机制。

function* gen(x){
  var y = yield x + 2;
  return y;
}

var g = gen(1);
g.next() // { value: 3, done: false }
g.next(2) // { value: 2, done: true }

上面代码中,第一个 next 方法的 value 属性,返回表达式 x + 2 的值3,第二个 next 方法带有参数2,这个参数可以传入 Generator 函数,作为上个阶段异步任务的返回结果,被函数体内的变量 y 接收,因此这一步的 value 属性返回的就是2(也就是变量 y 的值)。

function* gen(x){
  try {
    var y = yield x + 2;
  } catch (e){
    console.log(e);
  }
  return y;
}

var g = gen(1);
g.next();
g.throw('出错了');
// 出错了

上面代码的最后一行,Generator 函数体外,使用指针对象的 throw 方法抛出的错误,可以被函数体内的 try...catch 代码块捕获,这意味着出错的代码与处理错误的代码实现了时间和空间上的分离,这对于异步编程无疑是很重要的。

Generator 函数的自动流程管理

Thunk 函数

函数的"传值调用"和“传名调用”一直以来都各有优劣(比如传值调用比较简单,但是对参数求值的时候,实际上还没用到这个参数,有可能造成性能损失),本文不多赘述,在这里需要提到的是:编译器的“传名调用”实现,往往是将参数放到一个临时函数之中,再将这个临时函数传入函数体,这个临时函数就叫做 Thunk 函数。

function f(m) {
  return m * 2;
}

f(x + 5);

// 等同于

var thunk = function () {
  return x + 5;
};

function f(thunk) {
  return thunk() * 2;
}

任何函数,只要参数有回调函数,就能写成 Thunk 函数的形式。下面是一个简单的 JavaScript 语言 Thunk 函数转换器:

// ES5 版本
var Thunk = function(fn){
  return function (){
    var args = Array.prototype.slice.call(arguments);
    return function (callback){
      args.push(callback);
      return fn.apply(this, args);
    }
  };
};

// ES6 版本
const Thunk = function(fn) {
  return function (...args) {
    return function (callback) {
      return fn.call(this, ...args, callback);
    }
  };
};

你可能会问, Thunk 函数有什么用?回答是以前确实没什么用,但是 ES6 有了 Generator 函数,Thunk 函数现在可以用于 Generator 函数的自动流程管理

首先 Generator 函数本身是可以自动执行的:

function* gen() {
  // ...
}

var g = gen();
var res = g.next();

while(!res.done){
  console.log(res.value);
  res = g.next();
}

但是,这并不适合异步操作,如果必须保证前一步执行完,才能执行后一步,上面的自动执行就不可行,这时 Thunk 函数就能派上用处,以读取文件为例,下面的 Generator 函数封装了两个异步操作:

var fs = require('fs');
var thunkify = require('thunkify');
var readFileThunk = thunkify(fs.readFile);

var gen = function* (){
  var r1 = yield readFileThunk('/etc/fstab');
  console.log(r1.toString());
  var r2 = yield readFileThunk('/etc/shells');
  console.log(r2.toString());
};

上面代码中,yield 命令用于将程序的执行权移出 Generator 函数,那么就需要一种方法,将执行权再交还给 Generator 函数,这种方法就是 Thunk 函数,因为它可以在回调函数里,将执行权交还给 Generator 函数,为了便于理解,我们先看如何手动执行上面这个 Generator 函数:

var g = gen();

var r1 = g.next();
r1.value(function (err, data) {
  if (err) throw err;
  var r2 = g.next(data);
  r2.value(function (err, data) {
    if (err) throw err;
    g.next(data);
  });
});

仔细查看上面的代码,可以发现 Generator 函数的执行过程,其实是将同一个回调函数,反复传入 next 方法的 value 属性,这使得我们可以用递归来自动完成这个过程,下面就是一个基于 Thunk 函数的 Generator 执行器:

function run(fn) {
  var gen = fn();

  function next(err, data) {
    var result = gen.next(data);
    if (result.done) return;
    result.value(next);
  }

  next();
}

function* g() {
  // ...
}

run(g);

Thunk 函数并不是 Generator 函数自动执行的唯一方案,因为自动执行的关键是,必须有一种机制自动控制 Generator 函数的流程,接收和交还程序的执行权,回调函数可以做到这一点,Promise 对象也可以做到这一点。

查看原文

niaogege 赞了文章 · 1月16日

关于协程和 ES6 中的 Generator

关于协程和 ES6 中的 Generator

什么是协程?

进程和线程

众所周知,进程线程都是一个时间段的描述,是CPU工作时间段的描述,不过是颗粒大小不同,进程是 CPU 资源分配的最小单位,线程是 CPU 调度的最小单位。

其实协程(微线程,纤程,Coroutine)的概念很早就提出来了,可以认为是比线程更小的执行单元,但直到最近几年才在某些语言中得到广泛应用。

那么什么是协程呢?

子程序,或者称为函数,在所有语言中都是层级调用的,比如 A 调用 B,B 在执行过程中又调用 C,C 执行完毕返回,B 执行完毕返回,最后是 A 执行完毕,显然子程序调用是通过栈实现的,一个线程就是执行一个子程序,子程序调用总是一个入口,一次返回,调用顺序是明确的;而协程的调用和子程序不同,协程看上去也是子程序,但执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。

我们用一个简单的例子来说明,比如现有程序 A 和 B:

def A():
    print '1'
    print '2'
    print '3'

def B():
    print 'x'
    print 'y'
    print 'z'

假设由协程执行,在执行 A 的过程中,可以随时中断,去执行 B,B 也可能在执行过程中中断再去执行 A,结果可能是:

1
2
x
y
3
z

但是在 A 中是没有调用 B 的,所以协程的调用比函数调用理解起来要难一些。看起来 A、B 的执行有点像多线程,但协程的特点在于是一个线程执行,和多线程比协程最大的优势就是协程极高的执行效率,因为子程序切换不是线程切换,而是由程序自身控制,因此没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显;第二大优势就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态即可,所以执行效率比多线程高很多。

Wiki 中的定义: Coroutine

协程是一种程序组件,是由子例程(过程、函数、例程、方法、子程序)的概念泛化而来的,子例程只有一个入口点且只返回一次,而协程允许多个入口点,可以在指定位置挂起和恢复执行。
  • 协程的本地数据在后续调用中始终保持
  • 协程在控制离开时暂停执行,当控制再次进入时只能从离开的位置继续执行

解释协程时最常见的就是生产消费者模式:

var q := new queue

coroutine produce
    loop
        while q is not full
            create some new items
            add the items to q
        yield to consume

coroutine consume
    loop
        while q is not empty
            remove some items from q
            use the items
        yield to produce

这个例子中容易让人产生疑惑的一点就是 yield 的使用,它与我们通常所见的 yield 指令不同,因为我们常见的 yield 指令大都是基于生成器(Generator)这一概念的。

var q := new queue

generator produce
    loop
        while q is not full
            create some new items
            add the items to q
        yield consume
        
generator consume
    loop
        while q is not empty
            remove some items from q
            use the items
        yield produce
        
subroutine dispatcher
    var d := new dictionary(generator → iterator)
    d[produce] := start produce
    d[consume] := start consume
    var current := produce
    loop
        current := next d[current]

这是基于生成器实现的协程,我们看这里的 produce 与 consume 过程完全符合协程的概念,不难发现根据定义生成器本身就是协程。

“子程序就是协程的一种特例。” —— Donald Knuth

什么是 Generator?

在本文我们使用 ES6 中的 Generators 特性来介绍生成器,它是 ES6 提供的一种异步编程解决方案,语法上首先可以把它理解成是一个状态机,封装多个内部状态,执行 Generator 函数会返回一个遍历器对象,也就是说 Generator 函数除状态机外,还是一个遍历器对象生成函数,返回的遍历器对象可以依次遍历 Generator 函数内部的每一个状态,先看一个简单的例子:

function* quips(name) {
  yield "你好 " + name + "!";
  yield "希望你能喜欢这篇介绍ES6的译文";
  if (name.startsWith("X")) {
    yield "你的名字 " + name + "  首字母是X,这很酷!";
  }
  yield "我们下次再见!";
}

这段代码看起来很像一个函数,我们称之为生成器函数,它与普通函数有很多共同点,但是二者有如下区别:

  • 普通函数使用 function 声明,而生成器函数使用 function* 声明
  • 在生成器函数内部,有一种类似 return 的语法即关键字 yield,二者的区别是普通函数只可以 return 一次,而生成器函数可以 yield 多次,在生成器函数的执行过程中,遇到 yield 表达式立即暂停,并且后续可恢复执行状态

Generator 函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号,不同的是调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象

当调用 quips() 生成器函数时发生什么?

> var iter = quips("jorendorff");
  [object Generator]
> iter.next()
  { value: "你好 jorendorff!", done: false }
> iter.next()
  { value: "希望你能喜欢这篇介绍ES6的译文", done: false }
> iter.next()
  { value: "我们下次再见!", done: false }
> iter.next()
  { value: undefined, done: true }

每当生成器执行 yield 语句时,生成器的堆栈结构(本地变量、参数、临时值、生成器内部当前的执行位置 etc.)被移出堆栈,然而生成器对象保留对这个堆栈结构的引用(备份),所以稍后调用 .next() 可以重新激活堆栈结构并且继续执行。当生成器运行时,它和调用者处于同一线程中,拥有确定的连续执行顺序,永不并发。

遍历器对象的 next 方法的运行逻辑如下:

  1. 遇到 yield 表达式,就暂停执行后面的操作,并将紧跟在 yield 后面的那个表达式的值,作为返回的对象的 value 属性值
  2. 下一次调用 next 方法时,再继续往下执行,直到遇到下一个 yield 表达式
  3. 如果没有再遇到新的 yield 表达式,就一直运行到函数结束,直到 return 语句为止,并将 return 语句后面的表达式的值,作为返回的对象的 value 属性值
  4. 如果该函数没有 return 语句,则返回的对象的 value 属性值为 undefined

生成器是迭代器!

迭代器是 ES6 中独立的内建类,同时也是语言的一个扩展点,通过实现 [Symbol.iterator]() 和 .next() 两个方法就可以创建自定义迭代器。

// 应该弹出三次 "ding"
for (var value of range(0, 3)) {
  alert("Ding! at floor #" + value);
}

我们可以使用生成器实现上面循环中的 range 方法:

function* range(start, stop) {
  for (var i = start; i < stop; i++)
    yield i;
}

生成器是迭代器,所有的生成器都有内建 .next() 和 [Symbol.iterator]() 方法的实现,我们只需要编写循环部分的行为即可。

for...of 循环可以自动遍历 Generator 函数时生成的 Iterator 对象,且此时不再需要调用 next 方法。

function* foo() {
  yield 1;
  yield 2;
  yield 3;
  yield 4;
  yield 5;
  return 6;
}

for (let v of foo()) {
  console.log(v);
}

// 1 2 3 4 5

上面代码使用 for...of 循环,依次显示 5 个 yield 表达式的值。这里需要注意,一旦 next 方法的返回对象的 done 属性为 true,for...of 循环就会中止,且不包含该返回对象,所以上面代码的 return 语句返回的6,不包括在 for...of 循环之中。

下面是一个利用 Generator 函数和 for...of 循环,实现斐波那契数列的例子:

function* fibonacci() {
  let [prev, curr] = [0, 1];
  for (;;) {
    [prev, curr] = [curr, prev + curr];
    yield curr;
  }
}

for (let n of fibonacci()) {
  if (n > 1000) break;
  console.log(n);
}
除了 for...of 循环以外,扩展运算符(...)、解构赋值和 Array.from 方法内部调用的,都是遍历器接口,这意味着它们都可以将 Generator 函数返回的 Iterator 对象作为参数。

使用 Generator 实现生产消费者模式

function producer(c) {
    c.next();
    let n = 0;
    while (n < 5) {
        n++;
        console.log(`[PRODUCER] Producing ${n}`);
        const { value: r } = c.next(n);
        console.log(`[PRODUCER] Consumer return: ${r}`);
    }
    c.return();
}

function* consumer() {
    let r = '';
    while (true) {
        const n = yield r;
        if (!n) return;
        console.log(`[CONSUMER] Consuming ${n}`);
        r = '200 OK';
    }
}

const c = consumer();
producer(c);
[PRODUCER] Producing 1
[CONSUMER] Consuming 1
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 2
[CONSUMER] Consuming 2
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 3
[CONSUMER] Consuming 3
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 4
[CONSUMER] Consuming 4
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 5
[CONSUMER] Consuming 5
[PRODUCER] Consumer return: 200 OK
[Finished in 0.1s]

异步流程控制

ES6 诞生以前,异步编程的方法大概有下面四种:

  1. 回调函数
  2. 事件监听
  3. 发布/订阅
  4. Promise 对象

想必大家都经历过同样的问题,在异步流程控制中会使用大量的回调函数,甚至出现多个回调函数嵌套导致的情况,代码不是纵向发展而是横向发展,很快就会乱成一团无法管理,因为多个异步操作形成强耦合,只要有一个操作需要修改,它的上层回调函数和下层回调函数,可能都要跟着修改,这种情况就是我们常说的"回调函数地狱"。

Promise 对象就是为了解决这个问题而提出的,它不是新的语法功能,而是一种新的写法,允许将回调函数的嵌套,改成链式调用。然而,Promise 的最大问题就是代码冗余,原来的任务被 Promise 包装一下,不管什么操作一眼看去都是一堆 then,使得原来的语义变得很不清楚。

那么,有没有更好的写法呢?

哈哈这里有些明知故问,答案当然就是 Generator!Generator 函数是协程在 ES6 的实现,整个 Generator 函数就是一个封装的异步任务,或者说是异步任务的容器,Generator 函数可以暂停执行和恢复执行,这是它能封装异步任务的根本原因,除此之外,它还有两个特性使它可以作为异步编程的完整解决方案:函数体内外的数据交换和错误处理机制。

function* gen(x){
  var y = yield x + 2;
  return y;
}

var g = gen(1);
g.next() // { value: 3, done: false }
g.next(2) // { value: 2, done: true }

上面代码中,第一个 next 方法的 value 属性,返回表达式 x + 2 的值3,第二个 next 方法带有参数2,这个参数可以传入 Generator 函数,作为上个阶段异步任务的返回结果,被函数体内的变量 y 接收,因此这一步的 value 属性返回的就是2(也就是变量 y 的值)。

function* gen(x){
  try {
    var y = yield x + 2;
  } catch (e){
    console.log(e);
  }
  return y;
}

var g = gen(1);
g.next();
g.throw('出错了');
// 出错了

上面代码的最后一行,Generator 函数体外,使用指针对象的 throw 方法抛出的错误,可以被函数体内的 try...catch 代码块捕获,这意味着出错的代码与处理错误的代码实现了时间和空间上的分离,这对于异步编程无疑是很重要的。

Generator 函数的自动流程管理

Thunk 函数

函数的"传值调用"和“传名调用”一直以来都各有优劣(比如传值调用比较简单,但是对参数求值的时候,实际上还没用到这个参数,有可能造成性能损失),本文不多赘述,在这里需要提到的是:编译器的“传名调用”实现,往往是将参数放到一个临时函数之中,再将这个临时函数传入函数体,这个临时函数就叫做 Thunk 函数。

function f(m) {
  return m * 2;
}

f(x + 5);

// 等同于

var thunk = function () {
  return x + 5;
};

function f(thunk) {
  return thunk() * 2;
}

任何函数,只要参数有回调函数,就能写成 Thunk 函数的形式。下面是一个简单的 JavaScript 语言 Thunk 函数转换器:

// ES5 版本
var Thunk = function(fn){
  return function (){
    var args = Array.prototype.slice.call(arguments);
    return function (callback){
      args.push(callback);
      return fn.apply(this, args);
    }
  };
};

// ES6 版本
const Thunk = function(fn) {
  return function (...args) {
    return function (callback) {
      return fn.call(this, ...args, callback);
    }
  };
};

你可能会问, Thunk 函数有什么用?回答是以前确实没什么用,但是 ES6 有了 Generator 函数,Thunk 函数现在可以用于 Generator 函数的自动流程管理

首先 Generator 函数本身是可以自动执行的:

function* gen() {
  // ...
}

var g = gen();
var res = g.next();

while(!res.done){
  console.log(res.value);
  res = g.next();
}

但是,这并不适合异步操作,如果必须保证前一步执行完,才能执行后一步,上面的自动执行就不可行,这时 Thunk 函数就能派上用处,以读取文件为例,下面的 Generator 函数封装了两个异步操作:

var fs = require('fs');
var thunkify = require('thunkify');
var readFileThunk = thunkify(fs.readFile);

var gen = function* (){
  var r1 = yield readFileThunk('/etc/fstab');
  console.log(r1.toString());
  var r2 = yield readFileThunk('/etc/shells');
  console.log(r2.toString());
};

上面代码中,yield 命令用于将程序的执行权移出 Generator 函数,那么就需要一种方法,将执行权再交还给 Generator 函数,这种方法就是 Thunk 函数,因为它可以在回调函数里,将执行权交还给 Generator 函数,为了便于理解,我们先看如何手动执行上面这个 Generator 函数:

var g = gen();

var r1 = g.next();
r1.value(function (err, data) {
  if (err) throw err;
  var r2 = g.next(data);
  r2.value(function (err, data) {
    if (err) throw err;
    g.next(data);
  });
});

仔细查看上面的代码,可以发现 Generator 函数的执行过程,其实是将同一个回调函数,反复传入 next 方法的 value 属性,这使得我们可以用递归来自动完成这个过程,下面就是一个基于 Thunk 函数的 Generator 执行器:

function run(fn) {
  var gen = fn();

  function next(err, data) {
    var result = gen.next(data);
    if (result.done) return;
    result.value(next);
  }

  next();
}

function* g() {
  // ...
}

run(g);

Thunk 函数并不是 Generator 函数自动执行的唯一方案,因为自动执行的关键是,必须有一种机制自动控制 Generator 函数的流程,接收和交还程序的执行权,回调函数可以做到这一点,Promise 对象也可以做到这一点。

查看原文

赞 3 收藏 2 评论 0

niaogege 赞了文章 · 1月15日

Promise 中的三兄弟 .all(), .race(), .allSettled()

阿里云最近在做活动,低至2折,有兴趣可以看看:
https://promotion.aliyun.com/...

为了保证的可读性,本文采用意译而非直译。

从ES6 开始,我们大都使用的是 Promise.all()Promise.race()Promise.allSettled() 提案已经到第4阶段,因此将会成为ECMAScript 2020的一部分。

1.概述

Promise.all<T>(promises: Iterable<Promise<T>>): Promise<Array<T>>

  • Promise.all(iterable) 方法返回一个 Promise 实例,此实例在 iterable 参数内所有的 promise 都“完成(resolved)”或参数中不包含 promise 时回调完成(resolve);如果参数中 promise 有一个失败(rejected),此实例回调失败(reject),失败原因的是第一个失败 promise 的结果

Promise.race<T>(promises: Iterable<Promise<T>>): Promise<T>

  • Promise.race(iterable) 方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。

Promise.allSettled<T>(promises: Iterable<Promise<T>>): Promise<Array<SettlementObject<T>>>

  • Promise.allSettled()方法返回一个promise,该promise在所有给定的promise已被解析或被拒绝后解析,并且每个对象都描述每个promise的结果。

回顾: Promise 状态

给定一个返回Promise的异步操作,以下这些是Promise的可能状态:

  • pending: 初始状态,既不是成功,也不是失败状态。
  • fulfilled: 意味着操作成功完成。
  • rejected: 意味着操作失败。
  • Settled: Promise要么被完成,要么被拒绝。Promise一旦达成,它的状态就不再改变。

clipboard.png

3.什么是组合

又称部分-整体模式,将对象整合成树形结构以表示“部分整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性,它基于两种函数:

  • 基元函数(简短:基元)创建原子块。
  • 组合函数(简称:组合)将原子和/或复合件组合在一起以形成复合件。

对于 JS 的 Promises 来说

  • 基元函数包括:Promise.resolve()Promise.reject()
  • 组合函数:Promise.all(), Promise.race(), Promise.allSettled()

4. Promise.all()

Promise.all()的类型签名:

  • Promise.all<T>(promises: Iterable<Promise<T>>): Promise<Array<T>>

返回情况:

完成(Fulfillment):
如果传入的可迭代对象为空,Promise.all 会同步地返回一个已完成(resolved)状态的promise
如果所有传入的 promise 都变为完成状态,或者传入的可迭代对象内没有 promisePromise.all 返回的 promise 异步地变为完成。
在任何情况下,Promise.all 返回的 promise 的完成状态的结果都是一个数组,它包含所有的传入迭代参数对象的值(也包括非 promise 值)。

失败/拒绝(Rejection):
如果传入的 promise 中有一个失败(rejected),Promise.all 异步地将失败的那个结果给失败状态的回调函数,而不管其它 promise 是否完成。

来个例子:

const promises = [
  Promise.resolve('a'),
  Promise.resolve('b'),
  Promise.resolve('c'),
];
Promise.all(promises)
  .then((arr) => assert.deepEqual(
    arr, ['a', 'b', 'c']
  ));

如果其中的一个 promise 被拒绝,那么又是什么情况:

const promises = [
  Promise.resolve('a'),
  Promise.resolve('b'),
  Promise.reject('ERROR'),
];
Promise.all(promises)
  .catch((err) => assert.equal(
    err, 'ERROR'
  ));

下图说明Promise.all()是如何工作的

这需要有数字电路的知识才能看得懂哦

4.1 异步 .map() 与 Promise.all()

数组转换方法,如.map().filter()等,用于同步计算。例如

function timesTwoSync(x) {
  return 2 * x;
}
const arr = [1, 2, 3];
const result = arr.map(timesTwoSync);
assert.deepEqual(result, [2, 4, 6]);

如果.map()的回调是基于Promise的函数会发生什么? 使用这种方式 .map()返回的的结果是一个Promises数组。

Promises数组不是普通代码可以使用的数据,但我们可以通过Promise.all()来解决这个问题:它将Promises数组转换为Promise,并使用一组普通值数组来实现。

function timesTwoAsync(x) {
  return new Promise(resolve => resolve(x * 2));
}
const arr = [1, 2, 3];
const promiseArr = arr.map(timesTwoAsync);
Promise.all(promiseArr)
  .then(result => {
    assert.deepEqual(result, [2, 4, 6]);
  });

更实际工作上关于 .map()示例

接下来,咱们使用.map()Promise.all()Web下载文件。 首先,咱们需要以下帮助函数:

function downloadText(url) {
  return fetch(url)
    .then((response) => { // (A)
      if (!response.ok) { // (B)
        throw new Error(response.statusText);
      }
      return response.text(); // (C)
    });
}

downloadText()使用基于Promise的fetch API 以字符串流的方式下载文件:

  • 首先,它异步检索响应(第A行)。
  • response.ok(B行)检查是否存在“找不到文件”等错误。
  • 如果没有错误,使用.text()(第C行)以字符串的形式取回文件的内容。

在下面的示例中,咱们 下载了两个文件

const urls = [
  'http://example.com/first.txt',
  'http://example.com/second.txt',
];

const promises = urls.map(
  url => downloadText(url));

Promise.all(promises)
  .then(
    (arr) => assert.deepEqual(
      arr, ['First!', 'Second!']
    ));

Promise.all()的一个简版实现

function all(iterable) {
  return new Promise((resolve, reject) => {
    let index = 0;
    for (const promise of iterable) {
      // Capture the current value of `index`
      const currentIndex = index;
      promise.then(
        (value) => {
          if (anErrorOccurred) return;
          result[currentIndex] = value;
          elementCount++;
          if (elementCount === result.length) {
            resolve(result);
          }
        },
        (err) => {
          if (anErrorOccurred) return;
          anErrorOccurred = true;
          reject(err);
        });
      index++;
    }
    if (index === 0) {
      resolve([]);
      return;
    }
    let elementCount = 0;
    let anErrorOccurred = false;
    const result = new Array(index);
  });
}

5. Promise.race()

Promise.race()方法的定义:

Promise.race<T>(promises: Iterable<Promise<T>>): Promise<T>

Promise.race(iterable) 方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。来几个例子,瞧瞧:

const promises = [
  new Promise((resolve, reject) =>
    setTimeout(() => resolve('result'), 100)), // (A)
  new Promise((resolve, reject) =>
    setTimeout(() => reject('ERROR'), 200)), // (B)
];
Promise.race(promises)
  .then((result) => assert.equal( // (C)
    result, 'result'));

在第 A 行,Promise 是完成状态 ,所以 第 C 行会执行(尽管第 B 行被拒绝)。

如果 Promise 被拒绝首先执行,在来看看情况是嘛样的:

const promises = [
  new Promise((resolve, reject) =>
    setTimeout(() => resolve('result'), 200)),
  new Promise((resolve, reject) =>
    setTimeout(() => reject('ERROR'), 100)),
];
Promise.race(promises)
  .then(
    (result) => assert.fail(),
    (err) => assert.equal(
      err, 'ERROR'));

注意,由于 Promse 先被拒绝,所以 Promise.race() 返回的是一个被拒绝的 Promise

这意味着Promise.race([])的结果永远不会完成。

下图演示了Promise.race()的工作原理:

这需要有数字电路的知识才能看得懂哦

Promise.race() 在 Promise 超时下的情况

在本节中,我们将使用Promise.race()来处理超时的 Promise。 以下辅助函数:

function resolveAfter(ms, value=undefined) {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(value), ms);
  });
}

resolveAfter() 主要做的是在指定的时间内,返回一个状态为 resolvePromise,值为为传入的 value

调用上面方法:

function timeout(timeoutInMs, promise) {
  return Promise.race([
    promise,
    resolveAfter(timeoutInMs,
      Promise.reject(new Error('Operation timed out'))),
  ]);
}

timeout() 返回一个Promise,该 Promise 的状态取决于传入 promise 状态 。

其中 timeout 函数中的 resolveAfter(timeoutInMs, Promise.reject(new Error('Operation timed out')) ,通过 resolveAfter 定义可知,该结果返回的是一个被拒绝状态的 Promise

再来看看timeout(timeoutInMs, promise)的运行情况。如果传入promise在指定的时间之前状态为完成时,timeout 返回结果就是一个完成状态的 Promise,可以通过.then的第一个回调参数处理返回的结果。

timeout(200, resolveAfter(100, 'Result!'))
  .then(result => assert.equal(result, 'Result!'));

相反,如果是在指定的时间之后完成,刚 timeout 返回结果就是一个拒绝状态的 Promise,从而触发catch方法指定的回调函数。

timeout(100, resolveAfter(2000, 'Result!'))
  .catch(err => assert.deepEqual(err, new Error('Operation timed out')));

重要的是要了解“Promise 超时”的真正含义:

  1. 如果传入入Promise 较到的得到解决,其结果就会给返回的 Promise
  2. 如果没有足够快得到解决,输出的 Promise 的状态为拒绝。

也就是说,超时只会阻止传入的Promise,影响输出 Promise(因为Promise只能解决一次), 但它并没有阻止传入Promise的异步操作。

5.2 Promise.race() 的一个简版实现

以下是 Promise.race()的一个简化实现(它不执行安全检查)

function race(iterable) {
  return new Promise((resolve, reject) => {
    for (const promise of iterable) {
      promise.then(
        (value) => {
          if (settlementOccurred) return;
          settlementOccurred = true;
          resolve(value);
        },
        (err) => {
          if (settlementOccurred) return;
          settlementOccurred = true;
          reject(err);
        });
    }
    let settlementOccurred = false;
  });
}

6.Promise.allSettled()

“Promise.allSettled”这一特性是由Jason WilliamsRobert PamelyMathias Bynens提出。

promise.allsettle()方法的定义:

  • Promise.allSettled<T>(promises: Iterable<Promise<T>>)
    : Promise<Array<SettlementObject<T>>>

它返回一个ArrayPromise,其元素具有以下类型特征:

type SettlementObject<T> = FulfillmentObject<T> | RejectionObject;

interface FulfillmentObject<T> {
  status: 'fulfilled';
  value: T;
}

interface RejectionObject {
  status: 'rejected';
  reason: unknown;
}

Promise.allSettled()方法返回一个promise,该promise在所有给定的promise已被解析或被拒绝后解析,并且每个对象都描述每个promise的结果。

举例说明, 比如各位用户在页面上面同时填了3个独立的表单, 这三个表单分三个接口提交到后端, 三个接口独立, 没有顺序依赖, 这个时候我们需要等到请求全部完成后给与用户提示表单提交的情况

在多个promise同时进行时咱们很快会想到使用Promise.all来进行包装, 但是由于Promise.all的短路特性, 三个提交中若前面任意一个提交失败, 则后面的表单也不会进行提交了, 这就与咱们需求不符合.

Promise.allSettledPromise.all类似, 其参数接受一个Promise的数组, 返回一个新的Promise, 唯一的不同在于, 其不会进行短路, 也就是说当Promise全部处理完成后我们可以拿到每个Promise的状态, 而不管其是否处理成功.

下图说明promise.allsettle()是如何工作的

这需要有数字电路的知识才能看得懂哦

6.1 Promise.allSettled() 例子

这是Promise.allSettled() 使用方式快速演示示例

Promise.allSettled([
  Promise.resolve('a'),
  Promise.reject('b'),
])
.then(arr => assert.deepEqual(arr, [
  { status: 'fulfilled', value:  'a' },
  { status: 'rejected',  reason: 'b' },
]));

6.2 Promise.allSettled() 较复杂点的例子

这个示例类似于.map()Promise.all()示例(我们从其中借用了downloadText()函数):我们下载多个文本文件,这些文件的url存储在一个数组中。但是,这一次,咱们不希望在出现错误时停止,而是希望继续执行。Promise.allSettled()允许咱们这样做:

const urls = [
  'http://example.com/exists.txt',
  'http://example.com/missing.txt',
];

const result = Promise.allSettled(
  urls.map(u => downloadText(u)));
result.then(
  arr => assert.deepEqual(
    arr,
    [
      {
        status: 'fulfilled',
        value: 'Hello!',
      },
      {
        status: 'rejected',
        reason: new Error('Not Found'),
      },
    ]
));

6.3 Promise.allSettled() 的简化实现

这是promise.allsettle()的简化实现(不执行安全检查)

function allSettled(iterable) {
  return new Promise((resolve, reject) => {
    function addElementToResult(i, elem) {
      result[i] = elem;
      elementCount++;
      if (elementCount === result.length) {
        resolve(result);
      }
    }

    let index = 0;
    for (const promise of iterable) {
      // Capture the current value of `index`
      const currentIndex = index;
      promise.then(
        (value) => addElementToResult(
          currentIndex, {
            status: 'fulfilled',
            value
          }),
        (reason) => addElementToResult(
          currentIndex, {
            status: 'rejected',
            reason
          }));
      index++;
    }
    if (index === 0) {
      resolve([]);
      return;
    }
    let elementCount = 0;
    const result = new Array(index);
  });
}

7. 短路特性

Promise.all()romise.race() 都具有 短路特性

  • Promise.all(): 如果参数中 promise 有一个失败(rejected),此实例回调失败(reject)

Promise.race():如果参数中某个promise解决或拒绝,返回的 promise就会解决或拒绝。

8.并发性和 Promise.all()

8.1 顺序执行与并发执行

考虑下面的代码:

asyncFunc1()
  .then(result1 => {
    assert.equal(result1, 'one');
    return asyncFunc2();
  })
  .then(result2 => {
    assert.equal(result2, 'two');
  });

使用.then()顺序执行基于Promise的函数:只有在 asyncFunc1()的结果被解决后才会执行asyncFunc2()

Promise.all() 是并发执行的

Promise.all([asyncFunc1(), asyncFunc2()])
  .then(arr => {
    assert.deepEqual(arr, ['one', 'two']);
  });

9.2 并发技巧:关注操作何时开始

确定并发异步代码的技巧:关注异步操作何时启动,而不是如何处理它们的Promises

例如,下面的每个函数都同时执行asyncFunc1()asyncFunc2(),因为它们几乎同时启动。

function concurrentAll() {
  return Promise.all([asyncFunc1(), asyncFunc2()]);
}

function concurrentThen() {
  const p1 = asyncFunc1();
  const p2 = asyncFunc2();
  return p1.then(r1 => p2.then(r2 => [r1, r2]));
}

另一方面,以下两个函数依次执行asyncFunc1()asyncFunc2(): asyncFunc2()仅在asyncFunc1()的解决之后才调用。

function sequentialThen() {
  return asyncFunc1()
    .then(r1 => asyncFunc2()
      .then(r2 => [r1, r2]));
}

function sequentialAll() {
  const p1 = asyncFunc1();
  const p2 = p1.then(() => asyncFunc2());
  return Promise.all([p1, p2]);
}

9.3 Promise.all() 与 Fork-Join 分治编程

Promise.all() 与并发模式“fork join”松散相关。重温一下咱们前面的一个例子:

Promise.all([
    // (A) fork
    downloadText('http://example.com/first.txt'),
    downloadText('http://example.com/second.txt'),
  ])
  // (B) join
  .then(
    (arr) => assert.deepEqual(
      arr, ['First!', 'Second!']
    ));
  • Fork:在A行中,分割两个异步任务并同时执行它们。
  • Join:在B行中,对每个小任务得到的结果进行汇总。

代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

交流

干货系列文章汇总如下,觉得不错点个Star,欢迎 加群 互相学习。

https://github.com/qq44924588...

我是小智,公众号「大迁世界」作者,对前端技术保持学习爱好者。我会经常分享自己所学所看的干货,在进阶的路上,共勉!

关注公众号,后台回复福利,即可看到福利,你懂的。

clipboard.png

查看原文

赞 48 收藏 30 评论 3

认证与成就

  • 获得 29 次点赞
  • 获得 16 枚徽章 获得 0 枚金徽章, 获得 2 枚银徽章, 获得 14 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2017-03-02
个人主页被 1.4k 人浏览