胖虎

胖虎 查看完整档案

深圳编辑  |  填写毕业院校  |  填写所在公司/组织 visamz.com 编辑
编辑

<?='Hello World'?>

个人动态

胖虎 关注了标签 · 2019-01-09

golang

Go语言是谷歌2009发布的第二款开源编程语言。Go语言专门针对多处理器系统应用程序的编程进行了优化,使用Go编译的程序可以媲美C或C++代码的速度,而且更加安全、支持并行进程。
Go语言是谷歌推出的一种全新的编程语言,可以在不损失应用程序性能的情况下降低代码的复杂性。谷歌首席软件工程师罗布派克(Rob Pike)说:我们之所以开发Go,是因为过去10多年间软件开发的难度令人沮丧。Go是谷歌2009发布的第二款编程语言。

七牛云存储CEO许式伟出版《Go语言编程
go语言翻译项目 http://code.google.com/p/gola...
《go编程导读》 http://code.google.com/p/ac-m...
golang的官方文档 http://golang.org/doc/docs.html
golang windows上安装 http://code.google.com/p/gomi...

关注 25995

胖虎 关注了收藏夹 · 2018-12-26

带你入门 gRPC

关注 222

胖虎 关注了标签 · 2018-12-23

golang

Go语言是谷歌2009发布的第二款开源编程语言。Go语言专门针对多处理器系统应用程序的编程进行了优化,使用Go编译的程序可以媲美C或C++代码的速度,而且更加安全、支持并行进程。
Go语言是谷歌推出的一种全新的编程语言,可以在不损失应用程序性能的情况下降低代码的复杂性。谷歌首席软件工程师罗布派克(Rob Pike)说:我们之所以开发Go,是因为过去10多年间软件开发的难度令人沮丧。Go是谷歌2009发布的第二款编程语言。

七牛云存储CEO许式伟出版《Go语言编程
go语言翻译项目 http://code.google.com/p/gola...
《go编程导读》 http://code.google.com/p/ac-m...
golang的官方文档 http://golang.org/doc/docs.html
golang windows上安装 http://code.google.com/p/gomi...

关注 25995

胖虎 收藏了文章 · 2018-10-07

从浏览器多进程到JS单线程,JS运行机制最全面的一次梳理

前言

见解有限,如有描述不当之处,请帮忙及时指出,如有错误,会及时修正。

----------超长文+多图预警,需要花费不少时间。----------

如果看完本文后,还对进程线程傻傻分不清,不清楚浏览器多进程、浏览器内核多线程、JS单线程、JS运行机制的区别。那么请回复我,一定是我写的还不够清晰,我来改。。。

----------正文开始----------

最近发现有不少介绍JS单线程运行机制的文章,但是发现很多都仅仅是介绍某一部分的知识,而且各个地方的说法还不统一,容易造成困惑。
因此准备梳理这块知识点,结合已有的认知,基于网上的大量参考资料,
从浏览器多进程到JS单线程,将JS引擎的运行机制系统的梳理一遍。

展现形式:由于是属于系统梳理型,就没有由浅入深了,而是从头到尾的梳理知识体系,
重点是将关键节点的知识点串联起来,而不是仅仅剖析某一部分知识。

内容是:从浏览器进程,再到浏览器内核运行,再到JS引擎单线程,再到JS事件循环机制,从头到尾系统的梳理一遍,摆脱碎片化,形成一个知识体系

目标是:看完这篇文章后,对浏览器多进程,JS单线程,JS事件循环机制这些都能有一定理解,
有一个知识体系骨架,而不是似懂非懂的感觉。

另外,本文适合有一定经验的前端人员,新手请规避,避免受到过多的概念冲击。可以先存起来,有了一定理解后再看,也可以分成多批次观看,避免过度疲劳。

大纲

  • 区分进程和线程
  • 浏览器是多进程的

    • 浏览器都包含哪些进程?
    • 浏览器多进程的优势
    • 重点是浏览器内核(渲染进程)
    • Browser进程和浏览器内核(Renderer进程)的通信过程
  • 梳理浏览器内核中线程之间的关系

    • GUI渲染线程与JS引擎线程互斥
    • JS阻塞页面加载
    • WebWorker,JS的多线程?
    • WebWorker与SharedWorker
  • 简单梳理下浏览器渲染流程

    • load事件与DOMContentLoaded事件的先后
    • css加载是否会阻塞dom树渲染?
    • 普通图层和复合图层
  • 从Event Loop谈JS的运行机制

    • 事件循环机制进一步补充
    • 单独说说定时器
    • setTimeout而不是setInterval
  • 事件循环进阶:macrotask与microtask
  • 写在最后的话

区分进程和线程

线程和进程区分不清,是很多新手都会犯的错误,没有关系。这很正常。先看看下面这个形象的比喻:

- 进程是一个工厂,工厂有它的独立资源

- 工厂之间相互独立

- 线程是工厂中的工人,多个工人协作完成任务

- 工厂内有一个或多个工人

- 工人之间共享空间

再完善完善概念:

- 工厂的资源 -> 系统分配的内存(独立的一块内存)

- 工厂之间的相互独立 -> 进程之间相互独立

- 多个工人协作完成任务 -> 多个线程在进程中协作完成任务

- 工厂内有一个或多个工人 -> 一个进程由一个或多个线程组成

- 工人之间共享空间 -> 同一进程下的各个线程之间共享程序的内存空间(包括代码段、数据集、堆等)

然后再巩固下:

如果是windows电脑中,可以打开任务管理器,可以看到有一个后台进程列表。对,那里就是查看进程的地方,而且可以看到每个进程的内存资源信息以及cpu占有率。

所以,应该更容易理解了:进程是cpu资源分配的最小单位(系统会给它分配内存)

最后,再用较为官方的术语描述一遍:

  • 进程是cpu资源分配的最小单位(是能拥有资源和独立运行的最小单位)
  • 线程是cpu调度的最小单位(线程是建立在进程的基础上的一次程序运行单位,一个进程中可以有多个线程)

tips

  • 不同进程之间也可以通信,不过代价较大
  • 现在,一般通用的叫法:单线程与多线程,都是指在一个进程内的单和多。(所以核心还是得属于一个进程才行)

浏览器是多进程的

理解了进程与线程了区别后,接下来对浏览器进行一定程度上的认识:(先看下简化理解)

  • 浏览器是多进程的
  • 浏览器之所以能够运行,是因为系统给它的进程分配了资源(cpu、内存)
  • 简单点理解,每打开一个Tab页,就相当于创建了一个独立的浏览器进程。

关于以上几点的验证,请再第一张图

图中打开了Chrome浏览器的多个标签页,然后可以在Chrome的任务管理器中看到有多个进程(分别是每一个Tab页面有一个独立的进程,以及一个主进程)。
感兴趣的可以自行尝试下,如果再多打开一个Tab页,进程正常会+1以上

注意:在这里浏览器应该也有自己的优化机制,有时候打开多个tab页后,可以在Chrome任务管理器中看到,有些进程被合并了
(所以每一个Tab标签对应一个进程并不一定是绝对的)

浏览器都包含哪些进程?

知道了浏览器是多进程后,再来看看它到底包含哪些进程:(为了简化理解,仅列举主要进程)

  1. Browser进程:浏览器的主进程(负责协调、主控),只有一个。作用有

    • 负责浏览器界面显示,与用户交互。如前进,后退等
    • 负责各个页面的管理,创建和销毁其他进程
    • 将Renderer进程得到的内存中的Bitmap,绘制到用户界面上
    • 网络资源的管理,下载等
  2. 第三方插件进程:每种类型的插件对应一个进程,仅当使用该插件时才创建
  3. GPU进程:最多一个,用于3D绘制等
  4. 浏览器渲染进程(浏览器内核)(Renderer进程,内部是多线程的):默认每个Tab页面一个进程,互不影响。主要作用为

    • 页面渲染,脚本执行,事件处理等

强化记忆:在浏览器中打开一个网页相当于新起了一个进程(进程内有自己的多线程)

当然,浏览器有时会将多个进程合并(譬如打开多个空白标签页后,会发现多个空白标签页被合并成了一个进程),如图

另外,可以通过Chrome的更多工具 -> 任务管理器自行验证

浏览器多进程的优势

相比于单进程浏览器,多进程有如下优点:

  • 避免单个page crash影响整个浏览器
  • 避免第三方插件crash影响整个浏览器
  • 多进程充分利用多核优势
  • 方便使用沙盒模型隔离插件等进程,提高浏览器稳定性

简单点理解:如果浏览器是单进程,那么某个Tab页崩溃了,就影响了整个浏览器,体验有多差;同理如果是单进程,插件崩溃了也会影响整个浏览器;而且多进程还有其它的诸多优势。。。

当然,内存等资源消耗也会更大,有点空间换时间的意思。

重点是浏览器内核(渲染进程)

重点来了,我们可以看到,上面提到了这么多的进程,那么,对于普通的前端操作来说,最终要的是什么呢?答案是渲染进程

可以这样理解,页面的渲染,JS的执行,事件的循环,都在这个进程内进行。接下来重点分析这个进程

请牢记,浏览器的渲染进程是多线程的(这点如果不理解,请回头看进程和线程的区分

终于到了线程这个概念了?,好亲切。那么接下来看看它都包含了哪些线程(列举一些主要常驻线程):

  1. GUI渲染线程

    • 负责渲染浏览器界面,解析HTML,CSS,构建DOM树和RenderObject树,布局和绘制等。
    • 当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行
    • 注意,GUI渲染线程与JS引擎线程是互斥的,当JS引擎执行时GUI线程会被挂起(相当于被冻结了),GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行。
  2. JS引擎线程

    • 也称为JS内核,负责处理Javascript脚本程序。(例如V8引擎)
    • JS引擎线程负责解析Javascript脚本,运行代码。
    • JS引擎一直等待着任务队列中任务的到来,然后加以处理,一个Tab页(renderer进程)中无论什么时候都只有一个JS线程在运行JS程序
    • 同样注意,GUI渲染线程与JS引擎线程是互斥的,所以如果JS执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞。
  3. 事件触发线程

    • 归属于浏览器而不是JS引擎,用来控制事件循环(可以理解,JS引擎自己都忙不过来,需要浏览器另开线程协助)
    • 当JS引擎执行代码块如setTimeOut时(也可来自浏览器内核的其他线程,如鼠标点击、AJAX异步请求等),会将对应任务添加到事件线程中
    • 当对应的事件符合触发条件被触发时,该线程会把事件添加到待处理队列的队尾,等待JS引擎的处理
    • 注意,由于JS的单线程关系,所以这些待处理队列中的事件都得排队等待JS引擎处理(当JS引擎空闲时才会去执行)

  4. 定时触发器线程

    • 传说中的setIntervalsetTimeout所在线程
    • 浏览器定时计数器并不是由JavaScript引擎计数的,(因为JavaScript引擎是单线程的, 如果处于阻塞线程状态就会影响记计时的准确)
    • 因此通过单独线程来计时并触发定时(计时完毕后,添加到事件队列中,等待JS引擎空闲后执行)
    • 注意,W3C在HTML标准中规定,规定要求setTimeout中低于4ms的时间间隔算为4ms。
  5. 异步http请求线程

    • 在XMLHttpRequest在连接后是通过浏览器新开一个线程请求
    • 将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将这个回调再放入事件队列中。再由JavaScript引擎执行。

看到这里,如果觉得累了,可以先休息下,这些概念需要被消化,毕竟后续将提到的事件循环机制就是基于事件触发线程的,所以如果仅仅是看某个碎片化知识,
可能会有一种似懂非懂的感觉。要完成的梳理一遍才能快速沉淀,不易遗忘。放张图巩固下吧:

再说一点,为什么JS引擎是单线程的?额,这个问题其实应该没有标准答案,譬如,可能仅仅是因为由于多线程的复杂性,譬如多线程操作一般要加锁,因此最初设计时选择了单线程。。。

Browser进程和浏览器内核(Renderer进程)的通信过程

看到这里,首先,应该对浏览器内的进程和线程都有一定理解了,那么接下来,再谈谈浏览器的Browser进程(控制进程)是如何和内核通信的,
这点也理解后,就可以将这部分的知识串联起来,从头到尾有一个完整的概念。

如果自己打开任务管理器,然后打开一个浏览器,就可以看到:任务管理器中出现了两个进程(一个是主控进程,一个则是打开Tab页的渲染进程)
然后在这前提下,看下整个的过程:(简化了很多)

  • Browser进程收到用户请求,首先需要获取页面内容(譬如通过网络下载资源),随后将该任务通过RendererHost接口传递给Render进程
  • Renderer进程的Renderer接口收到消息,简单解释后,交给渲染线程,然后开始渲染

    • 渲染线程接收请求,加载网页并渲染网页,这其中可能需要Browser进程获取资源和需要GPU进程来帮助渲染
    • 当然可能会有JS线程操作DOM(这样可能会造成回流并重绘)
    • 最后Render进程将结果传递给Browser进程
  • Browser进程接收到结果并将结果绘制出来

这里绘一张简单的图:(很简化)

看完这一整套流程,应该对浏览器的运作有了一定理解了,这样有了知识架构的基础后,后续就方便往上填充内容。

这块再往深处讲的话就涉及到浏览器内核源码解析了,不属于本文范围。

如果这一块要深挖,建议去读一些浏览器内核源码解析文章,或者可以先看看参考下来源中的第一篇文章,写的不错

梳理浏览器内核中线程之间的关系

到了这里,已经对浏览器的运行有了一个整体的概念,接下来,先简单梳理一些概念

GUI渲染线程与JS引擎线程互斥

由于JavaScript是可操纵DOM的,如果在修改这些元素属性同时渲染界面(即JS线程和UI线程同时运行),那么渲染线程前后获得的元素数据就可能不一致了。

因此为了防止渲染出现不可预期的结果,浏览器设置GUI渲染线程与JS引擎为互斥的关系,当JS引擎执行时GUI线程会被挂起,
GUI更新则会被保存在一个队列中等到JS引擎线程空闲时立即被执行。

JS阻塞页面加载

从上述的互斥关系,可以推导出,JS如果执行时间过长就会阻塞页面。

譬如,假设JS引擎正在进行巨量的计算,此时就算GUI有更新,也会被保存到队列中,等待JS引擎空闲后执行。
然后,由于巨量计算,所以JS引擎很可能很久很久后才能空闲,自然会感觉到巨卡无比。

所以,要尽量避免JS执行时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞的感觉。

WebWorker,JS的多线程?

前文中有提到JS引擎是单线程的,而且JS执行时间过长会阻塞页面,那么JS就真的对cpu密集型计算无能为力么?

所以,后来HTML5中支持了Web Worker

MDN的官方解释是:

Web Worker为Web内容在后台线程中运行脚本提供了一种简单的方法。线程可以执行任务而不干扰用户界面

一个worker是使用一个构造函数创建的一个对象(e.g. Worker()) 运行一个命名的JavaScript文件 

这个文件包含将在工作线程中运行的代码; workers 运行在另一个全局上下文中,不同于当前的window

因此,使用 window快捷方式获取当前全局的范围 (而不是self) 在一个 Worker 内将返回错误

这样理解下:

  • 创建Worker时,JS引擎向浏览器申请开一个子线程(子线程是浏览器开的,完全受主线程控制,而且不能操作DOM)
  • JS引擎线程与worker线程间通过特定的方式通信(postMessage API,需要通过序列化对象来与线程交互特定的数据)

所以,如果有非常耗时的工作,请单独开一个Worker线程,这样里面不管如何翻天覆地都不会影响JS引擎主线程,
只待计算出结果后,将结果通信给主线程即可,perfect!

而且注意下,JS引擎是单线程的,这一点的本质仍然未改变,Worker可以理解是浏览器给JS引擎开的外挂,专门用来解决那些大量计算问题。

其它,关于Worker的详解就不是本文的范畴了,因此不再赘述。

WebWorker与SharedWorker

既然都到了这里,就再提一下SharedWorker(避免后续将这两个概念搞混)

  • WebWorker只属于某个页面,不会和其他页面的Render进程(浏览器内核进程)共享

    • 所以Chrome在Render进程中(每一个Tab页就是一个render进程)创建一个新的线程来运行Worker中的JavaScript程序。
  • SharedWorker是浏览器所有页面共享的,不能采用与Worker同样的方式实现,因为它不隶属于某个Render进程,可以为多个Render进程共享使用

    • 所以Chrome浏览器为SharedWorker单独创建一个进程来运行JavaScript程序,在浏览器中每个相同的JavaScript只存在一个SharedWorker进程,不管它被创建多少次。

看到这里,应该就很容易明白了,本质上就是进程和线程的区别。SharedWorker由独立的进程管理,WebWorker只是属于render进程下的一个线程

简单梳理下浏览器渲染流程

本来是直接计划开始谈JS运行机制的,但想了想,既然上述都一直在谈浏览器,直接跳到JS可能再突兀,因此,中间再补充下浏览器的渲染流程(简单版本)

为了简化理解,前期工作直接省略成:(要展开的或完全可以写另一篇超长文)

- 浏览器输入url,浏览器主进程接管,开一个下载线程,
然后进行 http请求(略去DNS查询,IP寻址等等操作),然后等待响应,获取内容,
随后将内容通过RendererHost接口转交给Renderer进程

- 浏览器渲染流程开始

浏览器器内核拿到内容后,渲染大概可以划分成以下几个步骤:

  1. 解析html建立dom树
  2. 解析css构建render树(将CSS代码解析成树形的数据结构,然后结合DOM合并成render树)
  3. 布局render树(Layout/reflow),负责各元素尺寸、位置的计算
  4. 绘制render树(paint),绘制页面像素信息
  5. 浏览器会将各层的信息发送给GPU,GPU会将各层合成(composite),显示在屏幕上。

所有详细步骤都已经略去,渲染完毕后就是load事件了,之后就是自己的JS逻辑处理了

既然略去了一些详细的步骤,那么就提一些可能需要注意的细节把。

这里重绘参考来源中的一张图:(参考来源第一篇)

load事件与DOMContentLoaded事件的先后

上面提到,渲染完毕后会触发load事件,那么你能分清楚load事件与DOMContentLoaded事件的先后么?

很简单,知道它们的定义就可以了:

  • 当 DOMContentLoaded 事件触发时,仅当DOM加载完成,不包括样式表,图片。

(譬如如果有async加载的脚本就不一定完成)

  • 当 onload 事件触发时,页面上所有的DOM,样式表,脚本,图片都已经加载完成了。

(渲染完毕了)

所以,顺序是:DOMContentLoaded -> load

css加载是否会阻塞dom树渲染?

这里说的是头部引入css的情况

首先,我们都知道:css是由单独的下载线程异步下载的。

然后再说下几个现象:

  • css加载不会阻塞DOM树解析(异步加载时DOM照常构建)
  • 但会阻塞render树渲染(渲染时需等css加载完毕,因为render树需要css信息)

这可能也是浏览器的一种优化机制。

因为你加载css的时候,可能会修改下面DOM节点的样式,
如果css加载不阻塞render树渲染的话,那么当css加载完之后,
render树可能又得重新重绘或者回流了,这就造成了一些没有必要的损耗。
所以干脆就先把DOM树的结构先解析完,把可以做的工作做完,然后等你css加载完之后,
在根据最终的样式来渲染render树,这种做法性能方面确实会比较好一点。

普通图层和复合图层

渲染步骤中就提到了composite概念。

可以简单的这样理解,浏览器渲染的图层一般包含两大类:普通图层以及复合图层

首先,普通文档流内可以理解为一个复合图层(这里称为默认复合层,里面不管添加多少元素,其实都是在同一个复合图层中)

其次,absolute布局(fixed也一样),虽然可以脱离普通文档流,但它仍然属于默认复合层

然后,可以通过硬件加速的方式,声明一个新的复合图层,它会单独分配资源
(当然也会脱离普通文档流,这样一来,不管这个复合图层中怎么变化,也不会影响默认复合层里的回流重绘)

可以简单理解下:GPU中,各个复合图层是单独绘制的,所以互不影响,这也是为什么某些场景硬件加速效果一级棒

可以Chrome源码调试 -> More Tools -> Rendering -> Layer borders中看到,黄色的就是复合图层信息

如下图。可以验证上述的说法

如何变成复合图层(硬件加速)

将该元素变成一个复合图层,就是传说中的硬件加速技术

  • 最常用的方式:translate3dtranslateZ
  • opacity属性/过渡动画(需要动画执行的过程中才会创建合成层,动画没有开始或结束后元素还会回到之前的状态)
  • will-chang属性(这个比较偏僻),一般配合opacity与translate使用(而且经测试,除了上述可以引发硬件加速的属性外,其它属性并不会变成复合层),

作用是提前告诉浏览器要变化,这样浏览器会开始做一些优化工作(这个最好用完后就释放)

  • <video><iframe><canvas><webgl>等元素
  • 其它,譬如以前的flash插件

absolute和硬件加速的区别

可以看到,absolute虽然可以脱离普通文档流,但是无法脱离默认复合层。
所以,就算absolute中信息改变时不会改变普通文档流中render树,
但是,浏览器最终绘制时,是整个复合层绘制的,所以absolute中信息的改变,仍然会影响整个复合层的绘制。
(浏览器会重绘它,如果复合层中内容多,absolute带来的绘制信息变化过大,资源消耗是非常严重的)

而硬件加速直接就是在另一个复合层了(另起炉灶),所以它的信息改变不会影响默认复合层
(当然了,内部肯定会影响属于自己的复合层),仅仅是引发最后的合成(输出视图)

复合图层的作用?

一般一个元素开启硬件加速后会变成复合图层,可以独立于普通文档流中,改动后可以避免整个页面重绘,提升性能

但是尽量不要大量使用复合图层,否则由于资源消耗过度,页面反而会变的更卡

硬件加速时请使用index

使用硬件加速时,尽可能的使用index,防止浏览器默认给后续的元素创建复合层渲染

具体的原理时这样的:
**webkit CSS3中,如果这个元素添加了硬件加速,并且index层级比较低,
那么在这个元素的后面其它元素(层级比这个元素高的,或者相同的,并且releative或absolute属性相同的),
会默认变为复合层渲染,如果处理不当会极大的影响性能**

简单点理解,其实可以认为是一个隐式合成的概念:如果a是一个复合图层,而且b在a上面,那么b也会被隐式转为一个复合图层,这点需要特别注意

另外,这个问题可以在这个地址看到重现(原作者分析的挺到位的,直接上链接):

http://web.jobbole.com/83575/

从Event Loop谈JS的运行机制

到此时,已经是属于浏览器页面初次渲染完毕后的事情,JS引擎的一些运行机制分析。

注意,这里不谈可执行上下文VOscop chain等概念(这些完全可以整理成另一篇文章了),这里主要是结合Event Loop来谈JS代码是如何执行的。

读这部分的前提是已经知道了JS引擎是单线程,而且这里会用到上文中的几个概念:(如果不是很理解,可以回头温习)

  • JS引擎线程
  • 事件触发线程
  • 定时触发器线程

然后再理解一个概念:

  • JS分为同步任务和异步任务
  • 同步任务都在主线程上执行,形成一个执行栈
  • 主线程之外,事件触发线程管理着一个任务队列,只要异步任务有了运行结果,就在任务队列之中放置一个事件。
  • 一旦执行栈中的所有同步任务执行完毕(此时JS引擎空闲),系统就会读取任务队列,将可运行的异步任务添加到可执行栈中,开始执行。

看图:

看到这里,应该就可以理解了:为什么有时候setTimeout推入的事件不能准时执行?因为可能在它推入到事件列表时,主线程还不空闲,正在执行其它代码,
所以自然有误差。

事件循环机制进一步补充

这里就直接引用一张图片来协助理解:(参考自Philip Roberts的演讲《Help, I'm stuck in an event-loop》)

上图大致描述就是:

  • 主线程运行时会产生执行栈,

栈中的代码调用某些api时,它们会在事件队列中添加各种事件(当满足触发条件后,如ajax请求完毕)

  • 而栈中的代码执行完毕,就会读取事件队列中的事件,去执行那些回调
  • 如此循环
  • 注意,总是要等待栈中的代码执行完毕后才会去读取事件队列中的事件

单独说说定时器

上述事件循环机制的核心是:JS引擎线程和事件触发线程

但事件上,里面还有一些隐藏细节,譬如调用setTimeout后,是如何等待特定时间后才添加到事件队列中的?

是JS引擎检测的么?当然不是了。它是由定时器线程控制(因为JS引擎自己都忙不过来,根本无暇分身)

为什么要单独的定时器线程?因为JavaScript引擎是单线程的, 如果处于阻塞线程状态就会影响记计时的准确,因此很有必要单独开一个线程用来计时。

什么时候会用到定时器线程?当使用setTimeoutsetInterval,它需要定时器线程计时,计时完成后就会将特定的事件推入事件队列中。

譬如:

setTimeout(function(){
    console.log('hello!');
}, 1000);

这段代码的作用是当1000毫秒计时完毕后(由定时器线程计时),将回调函数推入事件队列中,等待主线程执行

setTimeout(function(){
    console.log('hello!');
}, 0);

console.log('begin');

这段代码的效果是最快的时间内将回调函数推入事件队列中,等待主线程执行

注意:

  • 执行结果是:先beginhello!
  • 虽然代码的本意是0毫秒后就推入事件队列,但是W3C在HTML标准中规定,规定要求setTimeout中低于4ms的时间间隔算为4ms。

(不过也有一说是不同浏览器有不同的最小时间设定)

  • 就算不等待4ms,就算假设0毫秒就推入事件队列,也会先执行begin(因为只有可执行栈内空了后才会主动读取事件队列)

setTimeout而不是setInterval

用setTimeout模拟定期计时和直接用setInterval是有区别的。

因为每次setTimeout计时到后就会去执行,然后执行一段时间后才会继续setTimeout,中间就多了误差
(误差多少与代码执行时间有关)

而setInterval则是每次都精确的隔一段时间推入一个事件
(但是,事件的实际执行时间不一定就准确,还有可能是这个事件还没执行完毕,下一个事件就来了)

而且setInterval有一些比较致命的问题就是:

  • 累计效应(上面提到的),如果setInterval代码在(setInterval)再次添加到队列之前还没有完成执行,

就会导致定时器代码连续运行好几次,而之间没有间隔。
就算正常间隔执行,多个setInterval的代码执行时间可能会比预期小(因为代码执行需要一定时间)

  • 譬如像iOS的webview,或者Safari等浏览器中都有一个特点,在滚动的时候是不执行JS的,如果使用了setInterval,会发现在滚动结束后会执行多次由于滚动不执行JS积攒回调,如果回调执行时间过长,就会非常容器造成卡顿问题和一些不可知的错误(这一块后续有补充,setInterval自带的优化,不会重复添加回调)
  • 而且把浏览器最小化显示等操作时,setInterval并不是不执行程序,

它会把setInterval的回调函数放在队列中,等浏览器窗口再次打开时,一瞬间全部执行时

所以,鉴于这么多但问题,目前一般认为的最佳方案是:用setTimeout模拟setInterval,或者特殊场合直接用requestAnimationFrame

补充:JS高程中有提到,JS引擎会对setInterval进行优化,如果当前事件队列中有setInterval的回调,不会重复添加。不过,仍然是有很多问题。。。

事件循环进阶:macrotask与microtask

这段参考了参考来源中的第2篇文章(英文版的),(加了下自己的理解重新描述了下),
强烈推荐有英文基础的同学直接观看原文,作者描述的很清晰,示例也很不错,如下:

https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/

上文中将JS事件循环机制梳理了一遍,在ES5的情况是够用了,但是在ES6盛行的现在,仍然会遇到一些问题,譬如下面这题:

console.log('script start');

setTimeout(function() {
    console.log('setTimeout');
}, 0);

Promise.resolve().then(function() {
    console.log('promise1');
}).then(function() {
    console.log('promise2');
});

console.log('script end');

嗯哼,它的正确执行顺序是这样子的:

script start
script end
promise1
promise2
setTimeout

为什么呢?因为Promise里有了一个一个新的概念:microtask

或者,进一步,JS中分为两种任务类型:macrotaskmicrotask,在ECMAScript中,microtask称为jobs,macrotask可称为task

它们的定义?区别?简单点可以按如下理解:

  • macrotask(又称之为宏任务),可以理解是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)

    • 每一个task会从头到尾将这个任务执行完毕,不会执行其它
    • 浏览器为了能够使得JS内部task与DOM任务能够有序的执行,会在一个task执行结束后,在下一个 task 执行开始前,对页面进行重新渲染
(`task->渲染->task->...`)
  • microtask(又称为微任务),可以理解是在当前 task 执行结束后立即执行的任务

    • 也就是说,在当前task任务后,下一个task之前,在渲染之前
    • 所以它的响应速度相比setTimeout(setTimeout是task)会更快,因为无需等渲染
    • 也就是说,在某一个macrotask执行完后,就会将在它执行期间产生的所有microtask都执行完毕(在渲染前)

分别很么样的场景会形成macrotask和microtask呢?

  • macrotask:主代码块,setTimeout,setInterval等(可以看到,事件队列中的每一个事件都是一个macrotask)
  • microtask:Promise,process.nextTick等

__补充:在node环境下,process.nextTick的优先级高于Promise__,也就是可以简单理解为:在宏任务结束后会先执行微任务队列中的nextTickQueue部分,然后才会执行微任务中的Promise部分。

参考:https://segmentfault.com/q/1010000011914016

再根据线程来理解下:

  • macrotask中的事件都是放在一个事件队列中的,而这个队列由事件触发线程维护
  • microtask中的所有微任务都是添加到微任务队列(Job Queues)中,等待当前macrotask执行完毕后执行,而这个队列由JS引擎线程维护

(这点由自己理解+推测得出,因为它是在主线程下无缝执行的)

所以,总结下运行机制:

  • 执行一个宏任务(栈中没有就从事件队列中获取)
  • 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
  • 宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)
  • 当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染
  • 渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)

如图:

另外,请注意下Promisepolyfill与官方版本的区别:

  • 官方版本中,是标准的microtask形式
  • polyfill,一般都是通过setTimeout模拟的,所以是macrotask形式
  • 请特别注意这两点区别

注意,有一些浏览器执行结果不一样(因为它们可能把microtask当成macrotask来执行了),
但是为了简单,这里不描述一些不标准的浏览器下的场景(但记住,有些浏览器可能并不标准)

20180126补充:使用MutationObserver实现microtask

MutationObserver可以用来实现microtask
(它属于microtask,优先级小于Promise,
一般是Promise不支持时才会这样做)

它是HTML5中的新特性,作用是:监听一个DOM变动,
当DOM对象树发生任何变动时,Mutation Observer会得到通知

像以前的Vue源码中就是利用它来模拟nextTick的,
具体原理是,创建一个TextNode并监听内容变化,
然后要nextTick的时候去改一下这个节点的文本内容,
如下:(Vue的源码,未修改)

var counter = 1
var observer = new MutationObserver(nextTickHandler)
var textNode = document.createTextNode(String(counter))

observer.observe(textNode, {
    characterData: true
})
timerFunc = () => {
    counter = (counter + 1) % 2
    textNode.data = String(counter)
}

对应Vue源码链接

不过,现在的Vue(2.5+)的nextTick实现移除了MutationObserver的方式(据说是兼容性原因),
取而代之的是使用MessageChannel
(当然,默认情况仍然是Promise,不支持才兼容的)。

MessageChannel属于宏任务,优先级是:MessageChannel->setTimeout
所以Vue(2.5+)内部的nextTick与2.4及之前的实现是不一样的,需要注意下。

这里不展开,可以看下https://juejin.im/post/5a1af88f5188254a701ec230

写在最后的话

看到这里,不知道对JS的运行机制是不是更加理解了,从头到尾梳理,而不是就某一个碎片化知识应该是会更清晰的吧?

同时,也应该注意到了JS根本就没有想象的那么简单,前端的知识也是无穷无尽,层出不穷的概念、N多易忘的知识点、各式各样的框架、
底层原理方面也是可以无限的往下深挖,然后你就会发现,你知道的太少了。。。

另外,本文也打算先告一段落,其它的,如JS词法解析,可执行上下文以及VO等概念就不继续在本文中写了,后续可以考虑另开新的文章。

最后,喜欢的话,就请给个赞吧!

附录

博客

初次发布2018.01.21于我个人博客上面

http://www.dailichun.com/2018/01/21/js_singlethread_eventloop.html

招聘软广

阿里巴巴钉钉商业化团队大量hc,高薪股权。机会好,技术成长空间足,业务也有很大的发挥空间!

还在犹豫什么,来吧!!!

社招(P6~P7)

职责和挑战

  1. 负责钉钉工作台。工作台是帮助企业实现数字化管理和协同的门户,是拥有亿级用户量的产品。如何保障安全、稳定、性能和体验是对我们的一大挑战。
  2. 负责开放能力建设。针对纷繁的业务场景,提供合理的开放方案,既要做到深入用户场景理解并支撑业务发展,满足企业千人千面、千行千面的诉求,又要在技术上保障用户的安全、稳定和体验。需要既要有技术抽象能力、平台架构能力,又要有业务的理解和分析能力。
  3. 开放平台基础建设。保障链路的安全和稳定。同时对如何保障用户体验有持续精进的热情和追求。

职位要求

  1. 精通HTML5、CSS3、JS(ES5/ES6)等前端开发技术
  2. 掌握主流的JS库和开发框架,并深入理解其设计原理,例如React,Vue等
  3. 熟悉模块化、前端编译和构建工具,例如webpack、babel等
  4. (加分项)了解服务端或native移动应用开发,例如nodejs、Java等
  5. 对技术有强追求,有良好的沟通能力和团队协同能力,有优秀的分析问题和解决问题的能力。

前端实习

面向2021毕业的同学

  1. 本科及以上学历,计算机相关专业
  2. 熟练掌握HTML5/CSS3/Javascript等web前端技术
  3. 熟悉至少一种常用框架,例如React、vue等
  4. 关注新事物、新技术,有较强的学习能力,有强烈求知欲和进取心
  5. 有半年以上实际项目经验,大厂加分

image.png

image.png

内推邮箱

lichun.dlc@alibaba-inc.com

简历发我邮箱,必有回应,符合要求直接走内推!!!

一对一服务,有问必答!

也可加我微信了解更多:a546684355

参考资料

查看原文

胖虎 收藏了文章 · 2018-03-11

面试的信心来源于过硬的基础

在过去的一年很多人不满于公司没有福利、人际关系不好相处、没有发展前途的境遇等等,想着在开年来换一份工作来重新开始自己,那么 你 准备好了吗?

下面是本人整理的一份面试材料,本想自己用的,但是新年第一天 公司突然给了我个惊喜,涨工资了!!!

1、 viewport

    <meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no" />
    // width    设置viewport宽度,为一个正整数,或字符串‘device-width’
    // device-width  设备宽度
    // height   设置viewport高度,一般设置了宽度,会自动解析出高度,可以不用设置
    // initial-scale    默认缩放比例(初始缩放比例),为一个数字,可以带小数
    // minimum-scale    允许用户最小缩放比例,为一个数字,可以带小数
    // maximum-scale    允许用户最大缩放比例,为一个数字,可以带小数
    // user-scalable    是否允许手动缩放

延伸 提问

怎样处理 移动端 1px 被 渲染成 2px 问题


    1 局部处理
        meta标签中的 viewport属性 ,initial-scale 设置为 1 
        rem 按照设计稿标准走,外加利用transfrome 的scale(0.5) 缩小一倍即可;
    2 全局处理
        meta标签中的 viewport属性 ,initial-scale 设置为 0.5
        rem 按照设计稿标准走即可

2、跨域的几种方式

首先了解下浏览器的同源策略
同源策略/SOP(Same origin policy)是一种约定,由Netscape公司1995年引入浏览器,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSRF等攻击。所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。

那么怎样解决跨域问题的呢?

    1 通过jsonp跨域
        1.)原生实现:
         <script>
            var script = document.createElement('script');
            script.type = 'text/javascript';
        
            // 传参并指定回调执行函数为onBack
            script.src = 'http://www.....:8080/login?user=admin&callback=onBack';
            document.head.appendChild(script);
        
            // 回调执行函数
            function onBack(res) {
                alert(JSON.stringify(res));
            }
         </script>
    2、 document.domain + iframe跨域  
        此方案仅限主域相同,子域不同的跨域应用场景。
        1.)父窗口:(http://www.domain.com/a.html)

            <iframe id="iframe" data-original="http://child.domain.com/b.html"></iframe>
            <script>
                document.domain = 'domain.com';
                var user = 'admin';
            </script>
            2.)子窗口:(http://child.domain.com/b.html)
            
            <script>
                document.domain = 'domain.com';
                // 获取父窗口中变量
                alert('get js data from parent ---> ' + window.parent.user);
            </script>

        弊端:请看下面渲染加载优化

    3、 nginx代理跨域
    4、 nodejs中间件代理跨域
    5、 后端在头部信息里面设置安全域名
    
    更多跨域的具体内容请看  https://segmentfault.com/a/1190000011145364
    

3、 渲染优化

    1.禁止使用iframe(阻塞父文档onload事件);
        *iframe会阻塞主页面的Onload事件;
        *搜索引擎的检索程序无法解读这种页面,不利于SEO;
        *iframe和主页面共享连接池,而浏览器对相同域的连接有限制,所以会影响页面的并行加载。

        使用iframe之前需要考虑这两个缺点。如果需要使用iframe,最好是通过javascript
        动态给iframe添加src属性值,这样可以绕开以上两个问题。

    2.禁止使用gif图片实现loading效果(降低CPU消耗,提升渲染性能);
    3、使用CSS3代码代替JS动画(尽可能避免重绘重排以及回流);
    4、对于一些小图标,可以使用base64位编码,以减少网络请求。但不建议大图使用,比较耗费CPU;
            小图标优势在于:
                1.减少HTTP请求;
                2.避免文件跨域;
                3.修改及时生效;

    5、页面头部的<style></style> 会阻塞页面;(因为 Renderer进程中 JS线程和渲染线程是互斥的);
    6、页面头部<script</script> 会阻塞页面;(因为 Renderer进程中 JS线程和渲染线程是互斥的);
    7、页面中空的 href 和 src 会阻塞页面其他资源的加载 (阻塞下载进程);
    
    8、网页Gzip,CDN托管,data缓存 ,图片服务器;
    9、前端模板 JS+数据,减少由于HTML标签导致的带宽浪费,前端用变量保存AJAX请求结果,每次操作本地变量,不用请求,减少请求次数
    10、用innerHTML代替DOM操作,减少DOM操作次数,优化javascript性能。
    11、当需要设置的样式很多时设置className而不是直接操作style。
    12、少用全局变量、缓存DOM节点查找的结果。减少IO读取操作。
    13、避免使用CSS Expression(css表达式)又称Dynamic properties(动态属性)。
    14、图片预加载,将样式表放在顶部,将脚本放在底部  加上时间戳。

    15、 避免在页面的主体布局中使用table,table要等其中的内容完全下载之后才会显示出来,显示比div+css布局慢。
        对普通的网站有一个统一的思路,就是尽量向前端优化、减少数据库操作、减少磁盘IO。
            向前端优化指的是,在不影响功能和体验的情况下,能在浏览器执行的不要在服务端执行,
            能在缓存服务器上直接返回的不要到应用服务器,程序能直接取得的结果不要到外部取得,
            本机内能取得的数据不要到远程取,内存能取到的不要到磁盘取,缓存中有的不要去数据库查询。
            减少数据库操作指减少更新次数、缓存结果减少查询次数、将数据库执行的操作尽可能的让你的程序完成(例如join查询),
            减少磁盘IO指尽量不使用文件系统作为缓存、减少读写文件次数等。程序优化永远要优化慢的部分,换语言是无法“优化”的。
            

4、事件的各个阶段

1:捕获阶段 ---> 2:目标阶段 ---> 3:冒泡阶段
document   ---> target目标 ----> document

由此,addEventListener的第三个参数设置为true和false的区别已经非常清晰了:

true表示该元素在事件的“捕获阶段”(由外往内传递时)响应事件;

false表示该元素在事件的“冒泡阶段”(由内向外传递时)响应事件。

5、let var const

let 允许你声明一个作用域被限制在块级中的变量、语句或者表达式
    let绑定不受变量提升的约束,这意味着let声明不会被提升到当前
    该变量处于从块开始到初始化处理的“暂存死区”。

var 声明变量的作用域限制在其声明位置的上下文中,而非声明变量总是全局的
    由于变量声明(以及其他声明)总是在任意代码执行之前处理的,所以在代码中的任意位置声明变量总是等效于在代码开头声明
    
const 声明创建一个值的只读引用 (即指针)
    这里就要介绍下 JS 常用类型 
    String、Number、Boolean、Array、Object、Null、Undefined
    其中基本类型 有 Undefined、Null、Boolean、Number、String,保存在栈中;
    复合类型 有 Array、Object ,保存在堆中;
    
    基本数据当值发生改变时,那么其对应的指针也将发生改变,故造成 const申明基本数据类型时,
    再将其值改变时,将会造成报错, 例如 const a = 3 ; a = 5 时 将会报错;
    但是如果是复合类型时,如果只改变复合类型的其中某个Value项时, 将还是正常使用;

6、箭头函数

    语法比函数表达式更短,并且不绑定自己的this,arguments,super或 new.target。这些函数表达式最适合用于非方法函数,并且它们不能用作构造函数。
    

7、快速的让一个数组乱序

    var arr = [1,2,3,4,5,6,7,8,9,10];
    arr.sort(function(){
        return Math.random() - 0.5;
    })
    console.log(arr);

此处解释:(语言组织能力不足,请勿吐槽)

首先: 当return 的值

    小于 0 ,那么 a 会被排列到 b 之前;
    等于 0 , a 和 b 的相对位置不变;
    大于 0 , b 会被排列到 a 之前;

这里你会 发现起始 的时候数组是正序排列,每当进行一次排列的时候, 都会先随机一个随机数 
(注意这里的每一次排列 指 每一个红框指一次排列, 共9次排列 , 一次排列中可能存在多次比较);

当一次排列的 随机数大于0.5 时 将会进行第二次比较, 当第二次随机数 仍然大于0.5 时 ,
    将会再 进行一次比较, 直到 随机数大于0.5 或者排列到第一位;

当一次排列的 随机数 小于0.5时 当前比较的两项 索引将不会改变 ,继续下一次 的排列;

8、字体font-family

    @ 宋体      SimSun
    @ 黑体      SimHei
    @ 微信雅黑   Microsoft Yahei
    @ 微软正黑体 Microsoft JhengHei
    @ 新宋体    NSimSun
    @ 新细明体  MingLiU
    @ 细明体    MingLiU
    @ 标楷体    DFKai-SB
    @ 仿宋     FangSong
    @ 楷体     KaiTi
    @ 仿宋_GB2312  FangSong_GB2312
    @ 楷体_GB2312  KaiTi_GB2312  
    @
    @ 说明:中文字体多数使用宋体、雅黑,英文用Helvetica
    
    body { font-family: Microsoft Yahei,SimSun,Helvetica; } 

9、可能用到的meta标签

    
    <!-- 设置缩放 -->
    <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no, minimal-ui" />
    <!-- 可隐藏地址栏,仅针对IOS的Safari(注:IOS7.0版本以后,safari上已看不到效果) -->
    <meta name="apple-mobile-web-app-capable" content="yes" />
    <!-- 仅针对IOS的Safari顶端状态条的样式(可选default/black/black-translucent ) -->
    <meta name="apple-mobile-web-app-status-bar-style" content="black" />
    <!-- IOS中禁用将数字识别为电话号码/忽略Android平台中对邮箱地址的识别 -->
    <meta name="format-detection"content="telephone=no, email=no" />

    其他meta标签
    <!-- 启用360浏览器的极速模式(webkit) -->
    <meta name="renderer" content="webkit">
    <!-- 避免IE使用兼容模式 -->
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <!-- 针对手持设备优化,主要是针对一些老的不识别viewport的浏览器,比如黑莓 -->
    <meta name="HandheldFriendly" content="true">
    <!-- 微软的老式浏览器 -->
    <meta name="MobileOptimized" content="320">
    <!-- uc强制竖屏 -->
    <meta name="screen-orientation" content="portrait">
    <!-- QQ强制竖屏 -->
    <meta name="x5-orientation" content="portrait">
    <!-- UC强制全屏 -->
    <meta name="full-screen" content="yes">
    <!-- QQ强制全屏 -->
    <meta name="x5-fullscreen" content="true">
    <!-- UC应用模式 -->
    <meta name="browsermode" content="application">
    <!-- QQ应用模式 -->
    <meta name="x5-page-mode" content="app">
    <!-- windows phone 点击无高光 -->
    <meta name="msapplication-tap-highlight" content="no">

10、消除transition闪屏

    .css {
        -webkit-transform-style: preserve-3d;
        -webkit-backface-visibility: hidden;
        -webkit-perspective: 1000;
    }
    过渡动画(在没有启动硬件加速的情况下)会出现抖动的现象, 以上的 解决方案只是改变 视角 来启动硬件加速的一种方式;
    启动硬件加速的 另外一种方式: 
        .css {
            -webkit-transform: translate3d(0,0,0);
            -moz-transform: translate3d(0,0,0);
            -ms-transform: translate3d(0,0,0);
            transform: translate3d(0,0,0);
        }
    
    启动硬件加速
    最常用的方式:translate3d、translateZ、transform

    opacity属性/过渡动画(需要动画执行的过程中才会创建合成层,动画没有开始或结束后元素还会回到之前的状态)

    will-chang属性(这个比较偏僻),一般配合opacity与translate使用(而且经测试,除了上述可以引发硬件加速的属性外,
    其它属性并不会变成复合层),

    弊端: 硬件加速会导致 CPU性能占用量过大,电池电量消耗加大 ;因此 尽量避免泛滥使用硬件加速。

11、android 4.x bug

    1.三星 Galaxy S4中自带浏览器不支持border-radius缩写
    2.同时设置border-radius和背景色的时候,背景色会溢出到圆角以外部分
    3.部分手机(如三星),a链接支持鼠标:visited事件,也就是说链接访问后文字变为紫色
    4.android无法同时播放多音频audio
    5.oppo 的border-radius 会失效

12、JS 判断设备来源

    function deviceType(){
        var ua = navigator.userAgent;
        var agent = ["Android", "iPhone", "SymbianOS", "Windows Phone", "iPad", "iPod"];    
        for(var i=0; i<len,len = agent.length; i++){
            if(ua.indexOf(agent[i])>0){         
                break;
            }
        }
    }
    deviceType();
    window.addEventListener('resize', function(){
        deviceType();
    })


    微信的 有些不太一样
    function isWeixin(){
        var ua = navigator.userAgent.toLowerCase();
        if(ua.match(/MicroMessenger/i)=='micromessenger'){
            return true;
        }else{
            return false;
        }
    }

13、audio元素和video元素在ios和andriod中无法自动播放

    
    原因: 因为各大浏览器都为了节省流量,做出了优化,在用户没有行为动作时(交互)不予许自动播放;

    /音频,写法一
    <audio data-original="music/bg.mp3" autoplay loop controls>你的浏览器还不支持哦</audio>
    
    //音频,写法二
    <audio controls="controls"> 
        <source data-original="music/bg.ogg" type="audio/ogg"></source>
        <source data-original="music/bg.mp3" type="audio/mpeg"></source>
        优先播放音乐bg.ogg,不支持在播放bg.mp3
    </audio>
    
    //JS绑定自动播放(操作window时,播放音乐)
    $(window).one('touchstart', function(){
        music.play();
    })
    
    //微信下兼容处理
    document.addEventListener("WeixinJSBridgeReady", function () {
        music.play();
    }, false);
    
    //小结
    //1.audio元素的autoplay属性在IOS及Android上无法使用,在PC端正常;
    //2.audio元素没有设置controls时,在IOS及Android会占据空间大小,而在PC端Chrome是不会占据任何空间;
    //3.注意不要遗漏微信的兼容处理需要引用微信JS;

14、css实现单行文本溢出显示 ...

直接上效果:相对于多行文本溢出做处理, 单行要简单多,且更容易理解。

实现方法

overflow: hidden;
text-overflow:ellipsis;
white-space: nowrap;
当然还需要加宽度width属来兼容部分浏览。

15、实现多行文本溢出显示...

效果:

实现方法:

display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;

适用范围:

因使用了WebKit的CSS扩展属性,该方法适用于WebKit浏览器及移动端;

注:

1、-webkit-line-clamp用来限制在一个块元素显示的文本的行数。 为了实现该效果,它需要组合其他的WebKit属性。常见结合属性:
2、display: -webkit-box; 必须结合的属性 ,将对象作为弹性伸缩盒子模型显示 。
3、-webkit-box-orient 必须结合的属性 ,设置或检索伸缩盒对象的子元素的排列方式 。

如果你觉着这样还不够美观, 那么就接着往下看:

效果:

这样 是不是你想要的呢?

实现方法:

div {
    position: relative;
    line-height: 20px;
    max-height: 40px;
    overflow: hidden;
}

div:after {
    content: "..."; position: absolute; bottom: 0; right: 0; padding-left: 40px;
    background: -webkit-linear-gradient(left, transparent, #fff 55%);
    background: -o-linear-gradient(right, transparent, #fff 55%);
    background: -moz-linear-gradient(right, transparent, #fff 55%);
    background: linear-gradient(to right, transparent, #fff 55%);
}

不要只顾着吃,要注意胃口,此方法有个弊端 那就是 【未超出行的情况下也会出现省略号】 ,这样会不会很挫!!! 没办法,只能结合JS 进行优化该方法了。

注:


1、将height设置为line-height的整数倍,防止超出的文字露出。
2、给p::after添加渐变背景可避免文字只显示一半。
3、由于ie6-7不显示content内容,所以要添加标签兼容ie6-7(如:<span>…<span/>);兼容ie8需要将::after替换成:after。

16、让图文不可复制

这点应该大家 都很熟悉了, 某些时候【你懂的】为了快捷搜索答案,可是打死也不让你复制

-webkit-user-select: none; 
-ms-user-select: none;
-moz-user-select: none;
-khtml-user-select: none;
user-select: none;

那有些网页为了尊重原创,复制的文本 都会被加上一段来源说明,是如何做到的呢?问的好! 等的就是你这个问题 -_- 。

大致思路:


1、答案区域监听copy事件,并阻止这个事件的默认行为。
2、获取选中的内容(window.getSelection())加上版权信息,然后设置到剪切板(clipboarddata.setData())。

17、盒子垂直水平居中

这个问题好像面试必问的吔!反正我是必问的,哈哈!!! 其实无关多少种实现思路,只要你能实现就可以!

提供4种方法

1、定位 盒子宽高已知, position: absolute; left: 50%; top: 50%; margin-left:-自身一半宽度; margin-top: -自身一半高度;

2、table-cell布局 父级 display: table-cell; vertical-align: middle;  子级 margin: 0 auto;

3、定位 + transform ; 适用于 子盒子 宽高不定时; (这里是本人常用方法)
    
    position: relative / absolute;
    /*top和left偏移各为50%*/
       top: 50%;
       left: 50%;
    /*translate(-50%,-50%) 偏移自身的宽和高的-50%*/
    transform: translate(-50%, -50%); 注意这里启动了3D硬件加速哦 会增加耗电量的 (至于何是3D加速 请看浏览器进程与线程篇)

4、flex 布局
    父级: 
        /*flex 布局*/
        display: flex;
        /*实现垂直居中*/
        align-items: center;
        /*实现水平居中*/
        justify-content: center;

再加一种水平方向上居中 :margin-left : 50% ; transform: translateX(-50%);

18、改变placeholder的字体颜色大小

其实这个方法也就在PC端可以,真机上屁用都没有,当时我就哭了。 但 还是贴出来吧

input::-webkit-input-placeholder { 
    /* WebKit browsers */ 
    font-size:14px;
    color: #333;
} 
input::-moz-placeholder { 
    /* Mozilla Firefox 19+ */ 
    font-size:14px;
    color: #333;
} 
input:-ms-input-placeholder { 
    /* Internet Explorer 10+ */ 
    font-size:14px;
    color: #333;
}

19、最快捷的数组求最大值

    var arr = [ 1,5,1,7,5,9];
    Math.max(...arr)  // 9 

20、更短的数组去重写法

    [...new Set([2,"12",2,12,1,2,1,6,12,13,6])]
    
    // [2, "12", 12, 1, 6, 13]

21、 vue 父子组件嵌套时,组件内部的各个生命周期钩子触发先后顺序

首先 我们可以把 子组件当做function函数来看待,当父组件 import 子组件的时候, 就当是声明了 并加载了这个函数,
在调用的时候才会去执行这个函数(子组件)。那么父子组件中的各个声明周期钩子触发的先后顺序是怎样的呢?
如下图:

下图带222 的 是为子组件,所以一次顺序是为 先创建父组件,然后才穿件子组件,当子组件创建完成并且实体dom挂载完成后父组件才挂载完成

注:资源来源于自己长期收集整理而来,如有和其他网站和论坛相同部分,在此抱歉!

查看原文

胖虎 关注了用户 · 2017-10-17

miya @tiffany_58a90b3daf00f

前端工程师

关注 7

胖虎 关注了问题 · 2017-04-12

关于网易云音乐爬虫的api接口?

抓包能力有限,分析了一下网易云音乐的一些api接口,但是关于它很多post请求都是加了密,没有弄太明白。之前在知乎看到过一个豆瓣工程师写的教程,但是被投诉删掉了,请问有网友fork了的吗?因为我觉得他写的代码都比较pythonic,符合pep8规范。知乎另外几名大V比如@路人甲虽然经常分享数据,但是很少分享源代码,github也没有公布,公开过的代码我觉得他很多基础的pep8细节都忽略了,而且他的爬虫的效率非常低,比如爬网易10k+评论,在专栏说爬了6个小时之类的很辛苦之类的话,但是看他的代码别提异步了,甚至连多线程/多进程都没有使用,觉得有点水。前面那位大神还是真的有很多干货。

不一定要是python,任意语言都行,各位网友能否分享一些分析网易云音乐api接口的设计博客教程/大型项目

关注 15 回答 7

胖虎 赞了文章 · 2015-09-23

JS模块化编程总结_待续

一、背景

随着的互联网技术的不断发展,浏览器逐渐成了集大成的CS客户端,页面功能在向系统级、软件级靠拢的趋势下,开发团队也需要一些软件工程的方法来开发WEB项目,如测试驱动、面向对象,而模块化编程更是成为一种急需应用的技术。

二、原因

为方便文件管理、增加复用,我们需要细化JS文件,每个文件负责单一职能,称之为模块,明确每个文件的职能,当交互功能较复杂时,引用的文件也越加庞大,此时我们就需要模块化管理。

如我们编写一个模块组件Dialog.js,其依赖event.js / bootstrap.js / component.js,而event.js又依赖 base.js / core.js / el.js,bootstrap.js依赖 boot.js / under.js / almond.js,同理可能依赖更多的js,传统方法我们用到Dialog.js时实际上需要引用9个js,如下

<html>
<body>
...
 <script data-original="/public/base.js"></script>
 <script data-original="/public/core.js"></script>
 <script data-original="/public/el.js"></script>
 <script data-original="/public/boot.js"></script>
 <script data-original="/public/under.js"></script>
 <script data-original="/public/almond.js"></script>
 <script data-original="/module/event.js"></script>
 <script data-original="/module/bootstrap.js"></script>
 <script data-original="/module/component.js"></script>
 <script data-original="/widget/dialog.js"></script>
</body>
</html>

我们将其细分,Dialog所依赖的每个js都负责了某一独立功能,分开便于其它组件复用,也利于学习理解。

但将这9个js都到html里显示引用,工作量却非常可观,且不能弄错路径及加载顺序,若后续Dialog.js又需要多引用一个js,所有的项目都要手动增加,且这样依次同步加载下来会导致会占用浏览器线程,可能导致假死。

三、作用

模块化的作用就是解决上面提到的两点问题。

1、实现js文件的异步加载,避免浏览器假死。
2、管理模块之间的依赖,便于模块的维护

四、实现

模块化,就是把一个相对独立的功能,单独成一个文件,可输入指定依赖、输出指定的函数,供外界调用,其它都是内部的。这样即可方便不同的项目重复使用,也不会对其造成额外的影响。而既然要给不同的项目应用,特别像git上的模块是面向全球范围的,必然要形成可遵循的规范,开发者和使用者都有据可寻。

目前通用javascript规范分为服务端(CommonJS),客户端(AMD / CMD)。

CommonJS是针对javascript的服务端,一切模块都是通过require来同步加载调用, Nodejs采用的就是这个规范。

var module1 = require( 'module1'),
      module2 = require( 'module2' );

function module3(){
     ...
 }
exports.module3 = module3;

注:通过require加载module1、2,操作后通过exports导致module3。

这里我们主要讲客户端模块

客户端与服务端的不同在于异步加载,之所以异步是因为CommonJS规则应用在服务端时,文件都是本地硬盘读取,不受限于网络,等待硬盘读取时间很短。而浏览器要考虑网速,若长时间等待会导致浏览器假死。

引用形式如下图

amd pic

五、AMD

是"Asynchronous Module Definition"的缩写,意思就是"异步模块定义",

以require.js为代表,官网http://requirejs.org/ ,其形式主要是

=============main.js===================
 <script data-original="js/require.js" data-main="js/Dialog"></script>

==============Dialog.js=======================
 define( ['event', 'bootstrap', 'component' ], function( event, bootstrap, component ){
      event.doSomthing();
      bootstrap.doSomthing();
       component.doSomthing();
      var Dialog = function(){
          ...
      }
      return {
          Dialog: Dialog
     }
 });

注:require接收两个参数,第一个参数是数组,含需加载的模块,第二个参数是回调,在模块加载成功后执行,且回调中的参数对应模块的加载顺序,这样主程序只需要加载一个JS文件,Dialog.js即可,其自会去加载event.js等文件,event.js再用同样的方式去加载base.js等,使用者无需考虑Dialog加载了多少引用,也无法担心其使用后会增减依赖。

六、CMD

以sea.js为代表,官网 http://seajs.org/docs/,理念为需要时再执行,即依赖就近

 =============main.js===================
 <script data-original="js/sea.js"></script>    
<script>seajs.use("../static/hello/src/main")</script>

==============Dialog.js=======================
define( function( require, exports, module ){
    var event = require( 'event' );
    event.doSomthing();
    var bootstrap = require( 'bootstrap' );
    bootstrap.doSomthing();
    var component = require( 'component' );
    component.doSomthing();
    var Dialog = function(){
         ...
    }                    
     exports.Dialog = Dialog;
})

注:模块引用本身就在回调里,并且在使用前引用即可,所以sea.js强调的是懒加载,这也就是AMD和CMD之间唯一的区别。实际上,require.js也支持CMD的写法,不过依赖前置是其官方推荐。我最初因为对国产和玉伯的支持敬佩,使用了seajs,近来为了弄清楚AMD,项目全部用了requirejs,虽然网上及seajs官方对两种逻列了一些分别,但使用下来,感觉并无优劣。

七、UMD

因服务端与客户端对模块化加载方式的不同,就会人想做一个兼容的模块UMD(Universal Module Definition),来解决跨平台的问题。但对于我们开发者来讲实则没有什么意义,也不深入了。

八、require.js 使用

require.config({
      baseUrl: 'module/js',     //配置path下的统一目的,以便path中不会分别配置
      path: {                              //配置module路径,以便引用时不用写全路径
          module1: 'module1',
          module2: 'module2',
          module3: 'http://www.site.com/module3'     //若引用的是第三方,可以全部全路径
     },
      shim: {     //对不符合AMD规则的模块引用
          'underscore': {
               exports: '_'          //配置其原对外接口
          },
          'backbone': {
               deps: ['underscore', 'jquery'],     //配置其依赖项
               exports: 'Backbone'
         }
     }
 });

define( ['module1', 'module2' ], function( module1, module2 ){
      module1.doSomthing();
      module2.doSomthing();
      function module3(){
          ...
     }
      return {
          module3: module3
     }
 })

九、require.js 时间戳

require.js没有找到可配置时间戳,可修改require.js源文件中的load方法

 load: function (id, url) {
    url += '?' + timestamp;     
    req.load(context, id, url);     
},

新增 url += '?' + timestamp; 这一处语句,即为url添加timestamp,timestamp可以另外通过配置文件配置。我使用nodejs开发,在服务端的config文件配置 timestamp: '2015091112',将其插入到客户端 ejs文件内

 <script>
     var timestamp = '<%= timestamp %>';
 </script>

这样即可在 require.js 文件内调用。

十、require.js 合并

官网提供了r.js用于该模块化的打包 https://github.com/jrburke/r.js
r.js是将通过require.js引用的模块化文件都合并到一个文件里,以减少文件加载数量。
项目参考 https://github.com/xiaolulu/mynodejs.git| d7a0f54705a10ac329dd2f62a7d569da2 |
首先下载r.js,可以到静态文件根目录,新建build.js用于配置r.js,常见的配置如下


build.js

({ 
    appDir: './', //项目根目录,给后面的baseUrl做参考;非必要项;默认为与r.js平级目录
    dir: './min', //压缩后的文件的输出目录 
    baseUrl: './', //模块相对目录,与后面的paths配置使用
    modules: [ 
            { name:'login'},
            { name: 'register' },
            { name: 'center' }
    ], 
    fileExclusionRegExp: /^(r|build)\.js|.*\.scss$/, //过滤文件不压缩
    optimizeCss: 'standard', //css的标准方式
    optimize: 'uglify', //设置是否压缩;uglify(压缩格式)|none(只合并不压缩)
    removeCombined: true, //设置移除压缩后的文件
    paths: { //设置模块别名,方便引用,压缩后require.js引用的模块路径就在这里定义的,不是require.config里的
            md5: 'core/js/md5',
            validate: 'widget/validate/validate',
            all: 'public/js/all',
            login: 'module/issue/login/login',
            register: 'module/issue/register/register',
            center: 'module/user/center/center'
    }
})

shell执行 node r.js -o build.js

十一、almond

合并后所有原通过模块化加载的文件都已经在一个文件里了,已经不需要require.js的大部分功能,但因合并后代码里还是有require / define的函数定义,这里命令解析还是要有的,所以就有了almond.js(约15KB),其保留了require.js(约80KB)的子集,但不支持模块化加载,即对已经合并后的模块解析可以使用,减少代码量。

不过,其理念是好的,但实现方案还待改进,其执行后就会almond.js与前面合并后过的业务JS合并成一个文件,直接用 src来引用,意味着每个页面都需要的 almond.js将无法缓存,所以当业务JS超过五个时,这个方案将没有意义,有兴趣可以自行到git上学习,这里不在讲述 https://github.com/jrburke/almond

查看原文

赞 5 收藏 78 评论 10

胖虎 回答了问题 · 2015-09-16

lavarel 报错白板怎么破

多数情况是cache目录不可写的原因

关注 3 回答 3

认证与成就

  • 获得 33 次点赞
  • 获得 21 枚徽章 获得 3 枚金徽章, 获得 8 枚银徽章, 获得 10 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2011-09-22
个人主页被 527 人浏览