校招社招必备核心前端面试问题与详细解答

本文总结了前端老司机常常问题的一些问题并结合我的总结给出了比较详尽的答案。网易阿里腾讯校招社招必备知识点。javascript

原理讲解参考:前端增加-从新定义大前端css

在线课程:网易云课堂课程      思否课堂html

官方博客:前端学堂 fed123.com前端

1.关于性能优化说说js文件摆放顺序、减小请求、雪碧图等等原理, 说下window.performance.timing api是干什么的?

  • 浏览器是按照文档流解析html,为了更快构建DOM树和渲染树将页面呈现到屏幕上,建议是降js放在文档dom树结尾,body标签闭合前。
  • 浏览器并发HTTP请求有限制(6个左右),加载页面html后开始解析,解析到外链资源好比js css和图片,就会发http请求获取对应资源。减小请求就是减小这些资源请求, 能够 css资源合并,js资源合并,图片资源合并同时作lazyload,区分首屏非首屏接口,按需请求数据。
  • 雪碧图是一种图片资源的合并方法,将一些小图片合成一张图,经过background-position来定位到对应部分。
  • window.performance.timing 参考下前端页面性能指标数据计算方法, performance接口属于w3c标准hight resolution time中的一部分,经过navigation timeline api 、 performance timeline api,user timing api,resource timeline api 这四个接口作了加强实现。其中navigation timeline api中PerformanceTiming 接口数据放在 performance.timing这个对象上。主要记录了浏览器从跳转开始的各个时间点的时间,好比navigationStart是页面开始跳转时间,fetchStart是页面开始时间,domainLookupStart是DNS开始时间,domainLookupEnd是DNS结束时间, 查找到DNS后创建http连接,connectStart和connectEnd分别是连接开始和结束时间,而后是requestStart开始发起请求时间,responseStart开始响应时间,responseEnd响应结束时间。而后是苟安DOM树时间,分别是domLoading, domInteractive, domContentLoad和domComplete时间,分别对应document.readyState状态loading、interactive和complete。最后是页面onload,分别是loadEventStart和loadEventEnd时间节点。

能够经过这个接口统计前端的页面性能数据。vue

  • domainLookupStart - fetchStart = appCache时间,这段时间浏览器首先检查缓存
  • domainLookupEnd -domainLookupStart = DNS时间
  • connectEnd - connectStart = TCP时间
  • responseStart - requestStart = FTTB首字节时间,或者说是服务器响应等待时间
  • domContentLoad - navigationStart = 页面pageLoad时间
  •  loadEventEnd - navigationStart = 页面onLoad时间

2.请你描述下一个网页是如何渲染出来的,dom树和css树是如何合并的,浏览器的运行机制是什么,什么是否会形成渲染阻塞?

参考下:浏览器工做原理   浏览器渲染与阻塞原理html5

第一部分经过performance.time这个api咱们能够了解浏览器加载网页的流程,浏览器边加载html边构建DOM树,固然会有容错和修正机制。浏览器解析到行内css和内联css会立马加入到构建渲染树,解析到外链css就开始加载,加载完以后也会合并到渲染树的构建中,最后将渲染树和DOM作节点链路匹配,也叫layout阶段,计算每一个DOM元素最终在屏幕上显示的大小和位置。 遍历顺序为从左至右,从上到下,绘制在屏幕上,layout过程当中可能会触发页面回流和重绘,好比某个外链css加载完解析以后合并构建到渲染树中,其中某个css改变了DOM树种某个元素的定位(改为绝对定位)或者改变了长宽边距等位置信息,会触发从新layout,因此会回流reflow。重绘是好比css改变了以前的背景图片颜色,浏览器会从新绘制。java

会有渲染阻塞,浏览器刷新的频率大概是60次/秒, 也就是说刷新一次大概时间为16ms,若是浏览器对每一帧的渲染工做超过了这个时间, 页面的渲染就会出现卡顿的现象。浏览器内核中有3个最主要的线程:JS线程,UI渲染线程,事件处理线程。此外还有http网络线程,定时器任务线程,文件系统处理线程等等。node

  • JS线程负责JS代码解析编译执行,称为主线程。常说‘浏览器是单线程’指的是JS主线程只能有一个,主线程执行同步任务,会阻塞UI渲染线程。JS线程核心是js引擎 (IE9+: Chakra firefox:monkey chrome:v8)。webworker能够建立多个js线程,可是受主线程控制,主要用于cpu密集型计算。
  • UI渲染线程固然是负责构建渲染树,执行页面元素渲染。核心是渲染引擎(firefox:gecko、chrome/safari:webkit),因为JS能够操做DOM元素处理样式等,JS主线程是执行同步任务的,因此设计上JS引擎线程和GUI渲染线程是互斥的。 也就是说JS引擎处于运行状态时,GUI渲染线程将处于冻结状态。
  • 事件处理线程,因为浏览器是事件驱动的,事件处理线程用来控制事件回调处理,浏览器触发某个事件后会把事件回调函数放到任务队列中,能够看下下面会提到。
  • 其余线程统称工做线程,如处理 ajax 的线程,dom事件线程、定时器线程、读写文件的线程等,工做线程的任务完成以后, 会推入到一个任务队列(task queue)

总结一下,渲染阻塞有两个方面:react

  • js主线程执行时间长会致使渲染线程阻塞,影响渲染。咱们也称为longtask
  • 渲染线程自身阻塞,渲染时间达不到帧率60,会看起来卡顿,好比回流或者重绘等,或者css效率过低,动画处理不合适,致使渲染耗时

3.请简述下js引擎的工做原理,js是怎样处理事件的eventloop,宏任务源tasks和微任务源jobs分别有哪些?js是如何构造抽象语法树(AST)的?

js引擎只执行同步任务, 异步任务会有工做线程来执行,当须要进行异步操做(定时器、ajax请求、dom事件注册等), 主线程会发一个异步任务的请求, 相应的工做线程接受请求; 当工做线程完成工做以后, 通知主线程;主线程接收到通知以后, 会执行必定的操做(回调函数)。主线程和工做线程之间的通知机制叫作事件循环。webpack

  • 调用栈 (call stack): 主线程执行时生成的调用栈
  • 任务队列 (task queue): 工做线程完成任务后会把消息推到一个任务队列, 消息就是注册时的回调函数

当调用栈为空时, 主线程会从任务队列里取一条消息并放入当前的调用栈当中执行, 主线程会一直重复这个动做直到消息队列为空。 这个过程就叫作事件循环 (event-loop)。

关于宏任务和微任务,参考 事件流、事件模型、事件循环概念理解? 浏览器线程理解与microtask与macrotask

ES6新引入了Promise标准,同时浏览器实现上多了一个microtask微任务概念。在ECMAScript中,microtask称为jobs,macrotask可称为task

  • macrotask宏任务tasks,也就是上面说到的任务队列的任务。执行栈上的每一个任务都属于宏任务,主线程执行完执行栈的任务,从任务队列取新的任务。宏任务执行时不会中断,会一次性执行完,为了及时渲染数据,主线程执行完一个宏任务以后,会执行一次渲染。

task--》渲染 --》宏任务 --》渲染  .....

  • microtask微任务jobs,能够当作是插队须要及时处理的任务,会在当前主线程task任务执行后,渲染线程渲染以前,执行完当前积累全部的微任务。

task--》jobs --》渲染 --》宏任务 --》jobs --》渲染  .....

AST 参考:程序语言进阶之DSL与AST实战解析

将抽象语法树以前要先了解下NLP中文法的几率。任何一种语言,具体说就是DSL,都有本身的一套文法,用来表示这套语言的逻辑规范。不一样的文法写出来的语法表达式也不同。咱们根据语法表达式来解析语言,就能够造成一个AST抽象语法树。而后能够做进一步处理。我经常使用的是PEG解析表达式语法。能够很轻松的写出语法的每一条产生式规则,来构造生成AST。所谓AST能够理解成按照必定语法结构组成的词汇流,每一个词汇有特定的语法含义,好比说这是一个声明,这个一个操做符等等。

上面这个图是苹果最先作的KHTML渲染引擎中的KJS(javascript引擎),他是基于AST来实现的JavaScript语言解析的,先经过词法分析获得JSTokens流,而后通过语法分析获得抽象语法树,而后通过字节码生成器,转换成字节码。字节码通过JavaScript虚拟机JIT编译成机器码,而后执行。这是最初的设计架构,后来苹果公司基于此重构出了webkit渲染引擎,google基于webkit单独维护,称为blink渲染引擎,chrome的JS引擎改造为V8引擎。参考:简述Chromium, CEF, Webkit, JavaScriptCore, V8, Blink

举个例子经常使用的babel插件的原理就是基于babylon词法语法分析器生成抽象语法树,将代码文本转换成按照特定语法组合的token流集合,而后通过babtlon-traverse这个组件来负责处理遍历语法树,访问每一个token节点,经过对token的处理,能够生成咱们须要的AST语法树,而后再经过babylon-generator这个组件来作代码生成,根据AST生成代码。好比能够将 箭头函数 转换成 function函数。

浏览器中,经过开发者调试工具分析就能看到,下载完js脚本后,首先浏览器要先解析代码=》初始化上下文环境=》执行代码,整个是evaluate script的过程,解析代码的过程也是编译js的过程因此看最前面第一步就是compile script,将js代码编译成字节码(这一块涉及到浏览器js引擎的优化,v8引擎是编译成字节码,后面通过JIT解析执行(这个参考 你不知道的LLVM编译器 能够提高效率作动态优化), 这个相似于java、C#这些须要将源代码编译成中间语言,而后在虚拟机执行,javascript编译成字节码后面也是在虚拟机执行),而后就开始执行脚本。

4.你是否考虑全面你编写的整个函数,或者整个功能的容错性与扩展性?怎样构建一个组件是最合理最科学的,对于错误的处理是否有统一的方式方法?

扩展性主要是从功能上考虑,容错性是从数据上考虑。

  • 设计开发组件的时候首先要设计好数据模型,固然能够和后端共同约定一个标准,后面只要是这部分都用这个标准字段。后面能够对标准字段作扩展,开发时候要作容错和数据响应式开发。
  • 功能这部分其实能够从基础功能和扩展功能来看,基础功能能够在原有组件上作根据数据来展现。扩展功能能够经过组件结合的形式来处理。

我主要考虑的是组件复用,能够将一类组件归类,好比商品卡片,基本都是头图加标题行动点,价格,按钮。这就是最基础的一个组件。扩展性能够经过数据来作响应式的展现,好比新增一个描述,数据模型新增描述字段,有描述字段卡片上就展现描述,没有就不展现。像点击按钮的加购功能能够单独作成功能组件,统一处理,而不放在卡片上。由于这种加购每每附带的是商业逻辑,有不少业务逻辑要处理,独立出来反而更利于维护和拓展。

错误处理咱们这边是基于组件的方式来处理,开发一个错误处理的功能组件,提供thenable的能力,区分不一样的错误类型,提供统一埋点作监控和记录。

5.浏览器缓存的基本策略,何时该缓存何时不应缓存,以及对于控制缓存的字段的相关设置是否清楚?

参考下:HTTP协商缓存VS强缓存原理

前面介绍navigation api时候介绍了浏览器加载页面的各个关键时间节点。和缓存相关的主要有两部分

  • appcache,这部分是离线缓存,在fetchStart和domainLookupStart之间,这部分参考whatwg标准已经弃用,建议用serviceworker。这里也不作介绍。
  • HTTP缓存这部分是在requestStart开始,发起资源http请求开始,这部分涉及到强缓存和协商缓存。浏览器对于请求过得资源会缓存下来请求的响应数据,后面请求时会先从缓存查找匹配的请求的响应头,若是命中强缓存(判断cache-control和expires信息)那么直接从缓存获取响应数据,不会再发送http请求。若是没有命中浏览器会发送请求到服务器,同时会携带第一次请求的响应头的缓存相关header字段(last-modified/if-modified-since, Etag/if-none-match), 服务端根据这些请求头判断是否走缓存,若是走缓存,服务端会返回新的响应头,但不返回数据,浏览器会更新响应头,从缓存拿数据。若是不走缓存,服务端就会返回新的响应头和数据,而后浏览器更新缓存的数据。

-》强缓存,判断依据是expires(http 1.0协议规定)和cache-control(http 1.1协议规定)字段,expires是绝对时间,cache-control有可选值no-cache(不使用本地缓存,走协商缓存),no-store(禁止浏览器缓存数据,每次都是从新获取数据),public(能够被客户端和中间商CDN作缓存),private(只能客户端缓存,CDN不能缓存)

-》协商缓存,用到的响应头字段是last-modified/if-modified-since, Etag/if-none-match,这是两对哈,每队/前面一个是服务端返回的response header中的字段,/后面是请求头request携带的头部字段,第一次请求资源浏览器会返回last-modified(最后修改时间),后面再次请求请求头会带上if-modified-since,固然这个值和上次浏览器返回的last-modified是同样的,而后浏览器判断若是文件没有变化,那么返回304 Not Modified http code,响应请求头不会携带last-modified字段,浏览器从缓存取数据,也不用更新last-modified字段,若是有修改,那么响应头返回新的last-modified字段数据,返回响应内容。Etag/if-none-match这一对是一样的逻辑,不一样之处是用etag标识来判断文件是否修改,而不是用时间,由于服务器时间可能会变的,还会收到时区的影响。还有一点是每次请求都会返回etag字段,即便没有变化。

6.你是否能够利用面向对象的思惟去抽象你的功能,你会构建一个class(ES6)吗?你对于前端架构的理解?

我目前开发分状况用不一样的技术框架。

  • 若是单纯开发导购页面,好比一个商品列表页面,这种为了加载性能和操做体验,我是不考虑用框架的,也不用class,单纯用本身开发的原生ES框架本身控制页面模块生命周期,基于函数式编程写stateless组件。尽可能减小复杂度,简单化。
  • 若是是开发功能性组件,我是会用面型对象的模式来作开发。面向对象的核心是封装、继承、多态。封装就是将具体化为抽象,抽象成class,封装抽象出来的属性和方法。继承是由于抽象能够有层级,好比对异常处理,参数异常能够抽象成一类,状态异常能够抽象成一类,参数异常和状态异常有共通的地方,好比结构上都会返回异常的名称和描述,这就能够抽象一层公共父类,而后这两个异常继承自公共父类,这就是集成。多态也是随着继承而来的,好比参数异常和状态异常都继承了name这个属性,均可以实现对应的get方法,可是他们的实现结果可定是不同的,根据自身类的抽象来实现,调用的时候调用一样的方法也就有不一样的表现。好比参数异常和状态异常都继承了toString的方法,在调用各自的实例的toString方法时,输出的数据是不同的。另外设计的2大原则是:单一职责原则和开放封闭原则。单一职责只是抽象的类尽可能保持功能专注,开闭原则指设计的时候要考虑好扩展,对修改关闭,对扩展开放。
export class RuntimeException {

    constructor(message) {
        this._message = message;
    }

    get name() {
        return 'RuntimeException';
    }

    get message() {
        return this._message;
    }

    toString() {
        return this.name + ': ' + this.message;
    }

}

export class IllegalStateException extends RuntimeException {

    constructor(message) {
        super(message);
    }

    get name() {
        return 'IllegalStateException';
    }

}

export class InvalidArgumentException extends RuntimeException {

    constructor(message) {
        super(message);
    }

    get name() {
        return 'InvalidArgumentException';
    }

}

export class NotImplementedException extends RuntimeException {

    constructor(message) {
        super(message);
    }

    get name() {
        return 'NotImplementedException';
    }

}

对于前端领域来讲,目前前端框架作掉了不少事情,搭建好项目框架以后,开发的就行就是填功能。所编写的模块和组件的模式也比较固定,能够根据具体状况来实现。

7.你会用VUE,你会用React,你读得懂这两个架构的源码吗?你懂他俩的基本设计模式吗?让你去构建一个相似的框架你如何下手?

angular

特色: 数据双向绑定-》数据驱动开发的思想

html标签化的模板,模块化思想

数据绑定,控制器,依赖注入,

服务,指令,过滤器…

 

优势: 比较完善规范,文档、社区比较活跃

模块清晰,代码明了

 

缺点: 功能规范太固定,开发发挥空间小。

相对react和vue,不够轻量化

扩展性不够灵活

 

react

特色: 强大的组件化思想,任意封装、组合

首创JSX语法,virtual dom智能patch,灵活高效

轻量,易扩展,模块清晰,代码明了

社区生态完善,组件库、插件库丰富

 

缺点: 组件难以在复杂交互场景复用

侧重于作组件,作view展现层,对于业务逻辑等封装治理不如angular强大

JSX中html模板不够完备和健壮,好比一些属性变换写法,绑定事件大小写

 

vue

特色: 文档丰富,容易上手

模板较完备,声明式渲染,插值表达式与指令系统,

事件处理器,修饰符,计算属性 ,简单易用,功能强

社区生态完善,组件库、插件库丰富

 

缺点: 轻量框架使用是要结合生态插件组件使用,项目初始配置比较麻烦,

不过能够参考各类场景的标准模板配置,不少脚手架

 

声明式渲染与命令式渲染:  这个涉及到函数式编程中的一个声明式编程和命令式编程的概念。

好比命令式编程:

let a = []
for(let i=0; i< 10; i++){
  a.push(i*10)
}

声明式编程:

let a = []
arr.forEach(i=>{
  a.push(i*10)
})

声明式编程隐藏了函数处理细节,命令式编程则须要处理细节。

声明式编程的好处是简单化,易于理解,减小劳动量。好比vue中的指令绑定事件,绑定属性都是这样。@click,:title等等,用的时候很方便,这正是声明式编程最直观的好处。

 

8.你了解的ES6只是const、let、promise吗?你考虑过ES6提出的真正趋势吗?

怎么可能,我又不是你。ES6中最经常使用的像变量定义这部分用let、const能够避免一些坑,异步处理能够用promise,不过我到喜欢用async/await 更简洁好用。

  • 还有简写的箭头函数,代码看起来更清晰。
  • "..." 变量析构和组装, 函数默认值
  • ``模板字符串,便于字符拼接;标签模板 功能
  • Object对象的扩展, for in, Object.keys
  • Set和Map
  • class和module相关

发展趋势: 整体来讲前端开发更规范,更简单,语法更完备和成熟。支持的功能加强,开发效率提高,体验加强。

  • ES6的模块化相关支持,能够更好地支持模块化开发。
  • 原生支持class,面向对象的编程,概念更容易理解,易于软件开发和集成。
  • 异步操做规范化,异步编程更简单

9.你会用less,那么让你去写一个loader你能够吗?

参考下:程序语言进阶之DSL与AST实战解析

能够的,说一下原理,须要将 .less 文件最终解析成CSS,less是一种DSL,咱们能够现根据less预发,先将其解析成AST,而后解析成CSS便可。 我推荐用PEG.js这种解析表达式语法更简单些,只须要描述产生式规则便可。也能够本身根据LESS预发来写正则表达来匹配规则,而后转换成css。比较经常使用的是PostCSS,处理流程以下:参考官方文档

PostCSS的处理流程也是通过词法解析语法分析,将读取到的文件字符转化成词汇流tokens,根据语法分析,根据less的语法,解析成一个AST。

source string → tokens → AST

核心组件有:

10.webpack你也会用,你了解其中原理吗?你知道分析打包依赖的过程吗?你知道tree-shaking是如何干掉无用重复的代码的吗?

你们以前应该用过gulp,grunt这种代码打包工具,定义不一样的打包任务和打包流程。我用的比较多的rollup这个打包工具,配置起来比较简单些。

webpack也是用来作代码打包,能够作代码分析,拆分,混淆,压缩等等,基于他的插件扩展机制能够作不少事情。分析webpack的原理,能够先从webpack配置文件提及。参考:webpack编译代码原理介绍 用webpack4和一些插件提高代码编译速度

首先做为打包工具,要定义打包的输入entry和输出output;而后是定义webpack要用到的module,好比babel js loader, cssloader等等。执行编译具体的流程是:

加载webpack配置文件 --》 根据配置初始化编译器compiler --》找到入口,根据loader配置开始编译入口文件以及层层依赖 --》编译完成以后,能够获得全部编译过的文件和依赖关系结构 --》根据依赖关系将模块组装成一个个包含多个模块的chunk,而后根据配置写到输出文件。

webpack构建流程可分为如下三大阶段。

  1. 初始化:启动构建,读取与合并配置参数,加载plugin,实例化Compiler
  2. 编译:从Entry出发,针对每一个Module串行调用对应的Loader去翻译文件中的内容,再找到该Module依赖的Module,递归的进行编译处理
  3. 输出:将编译后的Module组合成Chunk,将Chunk转换成文件,输出到文件系统中

分析依赖是在编译过程当中完成的,从入口查找依赖,最后造成依赖关系。 为了提升效率,能够记录分析过的依赖,这样下次遇到一样的模块就不用再分析,直接引用编译过的依赖就能够了。

tree-shaking的名字原理同样,就是摇一摇大树,落下来的叶子都是冗余的部分。Tree-shaking 较早由 Rich_Harris 的 rollup 实现,后来,webpack2 也增长了tree-shaking 的功能。其实在更早,google closure compiler 也作过相似的事情。三个工具的效果和使用各不相同,使用方法能够经过官网文档去了解。

tree shaking的目的是去掉无用代码,减小代码体积。其实对于编译的编程语言对应的编译器基本都有判断哪些代码不会影响输出,从而在编译时移除这些代码的功能,称为DCE(dead code elimination)。tree shaking 是DCE的一种实现,传统的是消除没有引用不会执行的代码,tree shaking 主要是要消除没有用的代码。

Dead Code 通常具备如下几个特征

•代码不会被执行,不可到达

•代码执行的结果不会被用到

•代码只会影响死变量(只写不读)

在前端代码打包处理中,最终都会有个代码压缩混淆的环节,这个环节其实会完成DCE的工做,会将这些dead code移除。

可是uglify代码是只是单个单个文件处理,并不能分析出这个代码有没有被其余文件用到,固然也不会对这些为被调用的函数作处理,如上图uglify就不会去除没用到的get函数,因此就须要tree shaking。tree shaking是有限制的,只能消除函数和import/export的变量,不会处理import/export的class(由于javascript动态语言特性使得分析比较困难,可能致使之外的错误,side effect比较大), 对于纯函数处理效果较好。

11.你真的熟练使用css吗,那你知道position有几个属性吗

具体参考https://github.com/wintercn/b...

  • static:无特殊定位,对象遵循正常文档流。top,right,bottom,left等属性不会被应用。
  • relative:对象遵循正常文档流,但将依据top,right,bottom,left等属性在正常文档流中偏移位置,相对定位相对的是它本来在文档流中的位置而进行的偏移。而其层叠经过z-index属性定义。占据的文档空间不会随 top / right / left / bottom 等属性的偏移而发生变更,也就是说它后面的元素是依据( top / left / right / bottom 等属性生效以前)进行的定位,这点必定要理解。
  • absolute:对象脱离正常文档流,使用top,right,bottom,left等属性进行绝对定位。而其层叠经过z-index属性定义。使用absoulte或fixed定位的话,必须指定 left、right、 top、 bottom 属性中的至少一个,不然left/right/top/bottom属性会使用它们的默认值 auto ,这将致使对象听从正常的HTML布局规则,在前一个对象以后当即被呈递,简单讲就是都变成relative,会占用文档空间,这点很是重要,不少人使用absolute定位后发现没有脱离文档流就是这个缘由。
  • fixed:对象脱离正常文档流,使用top,right,bottom,left等属性以窗口为参考点进行定位,当出现滚动条时,对象不会随着滚动。而其层叠经过z-index属性定义。
  • sticky: The element is positioned according to the normal flow of the document, and then offset relative to its nearest scrolling ancestor and containing block (nearest block-level ancestor), including table-related elements, based on the values of toprightbottom, and left. The offset does not affect the position of any other elements.This value always creates a new stacking context. Note that a sticky element "sticks" to its nearest ancestor that has a "scrolling mechanism" (created when overflow is hiddenscrollauto, or overlay), even if that ancestor isn't the nearest actually scrolling ancestor. This effectively inhibits any "sticky" behavior (see the Github issue on W3C CSSWG).

absolute就只能根据祖先类元素(父类以上)进行定位,而这个祖先类还必须是以postion非static方式定位的, 举个例子,a元素使用absoulte定位,它会从父类开始找起,寻找以position非static方式定位的祖先类元素(注意,必定要是直系祖先才算哦~),直到<html>标签为止,这里还须要注意的是,relative和static方式在最外层时是以<body>标签为定位原点的,而absoulte方式在无父级是position非static定位时是以<html>做为原点定位。

参考: position属性

关于Layout and the containing block,看下官方介绍的contain block,另外相关的点是 BFC:如何建立块级格式化上下文(block formatting context),BFC有什么用

11.5 前端动画渲染机制了解吗?硬件加速原理?

参考:浏览器渲染流水线解析与网页动画性能优化

动画能够看作是一个连续的帧序列的组合。咱们把网页的动画分红两大类 —— 一类是合成器动画,一类是非合成器动画(UC 内部也将其称为内核动画或者 Blink Animation,虽然这不是 Chrome 官方的术语)。

  1. 合成器动画顾名思义,动画的每一帧都是由 Layer Compositor 生成并输出的,合成器自身驱动着整个动画的运行,在动画的过程当中,不须要新的 Main Frame 输入;
  2. 非合成器动画,每一帧都是由 Blink 生成,都须要产生一个新的 Main Frame;

合成器动画又能够分为两类:

  1. 合成器自己触发并运行的,好比最多见的网页惯性滚动,包括整个网页或者某个页内可滚动元素的滚动;
  2. Blink 触发而后交由合成器运行,好比说传统校招社招必备核心前端面试问题与详细解答的 CSS Translation 或者新的 Animation API,若是它们触发的动画经由 Blink 判断能够交由合成器运行;

Blink 触发的动画,若是是 Transform 和 Opacity 属性的动画基本上均可以由合成器运行,由于它们没有改变图层的内容。不过即便能够交由合成器运行,它们也须要产生一个新的 Main Frame 提交给合成器来触发这个动画,若是这个 Main Frame 包含了大量的图层变动,也会致使触发的瞬间卡顿,页端事先对图层结构进行优化能够避免这个问题。

非合成器动画也能够分为两类:

  1. 使用 CSS Translation 或者 Animation API 建立的动画,可是没法由合成器运行;
  2. 使用 Timer 或者 rAF 由 JS 驱动的动画,比较典型的就是 Canvas/WebGL 游戏,这种动画其实是由页端本身定义的,浏览器自己并无对应的动画的概念,也就是说浏览器自己是不知道这个动画何时开始,是否正在运行,何时结束,这些彻底是页端本身的内部逻辑;

合成器动画和非合成器动画在渲染流水线上有较大的差别,后者更复杂,流水线更长。上面四种动画的分类,按渲染流水线的复杂程度和理论性能排列(复杂程度由低到高,理论性能由高到低):

  1. 合成器自己触发并运行的动画;
  2. Blink 触发,合成器运行的动画;
  3. Blink 触发,没法由合成器运行的动画;
  4. 由 Timer/rAF 驱动的 JS 动画;

开启硬件加速的方法不少,好比transform: translate3d(0,0,0); 加了以后,在chrome开发者工具中的layer栏目下能够看到多了一层 composition layer,同时给出了理由描述是开启了3D transform,这个元素就放入了Composited Layer中托管,其动画效果都是在单独一个图形层上面处理,不会影响其它层。

什么状况下能使元素得到本身的层?虽然 Chrome 的启发式方法(heuristic)随着时间在不断发展进步,可是从目前来讲,知足如下任意状况便会建立层:

  • 3D 或透视变换(perspective transform) CSS 属性
  • 使用加速视频解码的 元素
  • 拥有 3D (WebGL) 上下文或加速的 2D 上下文的 元素
  • 混合插件(如 Flash)
  • 对本身的 opacity 作 CSS 动画或使用一个动画 webkit 变换的元素
  • 拥有加速 CSS 过滤器的元素
  • 元素有一个包含复合层的后代节点(换句话说,就是一个元素拥有一个子元素,该子元素在本身的层里)
  • 元素有一个 z-index 较低且包含一个复合层的兄弟元素(换句话说就是该元素在复合层上面渲染)

使用3D硬件加速提高动画性能时,最好给元素增长一个z-index属性,人为干扰复合层的排序,能够有效减小chrome建立没必要要的复合层,提高渲染性能,移动端优化效果尤其明显。

关于层的介绍:gpu-accelerated-compositing-in-chrome

理解CSS animations 和 transitions的性能问题与动画调试

12.你了解js的数据结构吗?基本数据类型有哪些?复杂数据类型有哪些?在内存是如何表现的?

参考MDN,最新的 ECMAScript 标准定义了 7 种数据类型:

除 Object 之外的全部类型都是不可变的(值自己没法被改变)。例如,与 C 语言不一样,JavaScript 中字符串是不可变的。JavaScript 中对字符串的操做必定返回了一个新字符串,原始字符串并无被改变。

标准的" 对象, 和函数【复杂数据类型】

日期:内建的 Date 对象

数组和类型数组:

数组是一种使用整数做为键(integer-key-ed)属性和长度(length)属性之间关联的常规对象。此外,数组对象还继承了 Array.prototype 的一些操做数组的便捷方法。例如, indexOf (搜索数组中的一个值) or push (向数组中添加一个元素),等等。 这使得数组是表示列表或集合的最优选择。

类型数组(Typed Arrays)是ECMAScript Edition 6中新定义的 JavaScript 内建对象,提供了一个基本的二进制数据缓冲区的类数组视图。

集合对象Map、WeakMap、Set、WeakSet:这些数据结构把对象的引用看成键,其在ECMAScript第6版中有介绍。当 Map 和 WeakMap 把一个值和对象关联起来的时候, Set 和 WeakSet 表示一组对象。 Map和WeakMaps之间的差异在于,在前者中,对象键是可枚举的。

结构化数据JSON:JSON (JavaScript Object Notation) 是一种轻量级的数据交换格式

参考:标准全局内置对象

两种类型:

1.   ECMAScript变量包含两种不一样类型的值:基本类型值、引用类型值;

2.   基本类型值:指的是保存在栈内存中的简单数据段;

3.   引用类型值:指的是那些保存在堆内存中的对象,意思是,变量中保存的实际上只是一个指针,这个指针执行内存中的另外一个位置,由该位置保存对象;

两种访问方式:

4.   基本类型值:按值访问,操做的是他们实际保存的值;

5.   引用类型值:按引用访问,当查询时,咱们须要先从栈中读取内存地址,而后再顺藤摸瓜地找到保存在堆内存中的值;

数据复制

  • 基本类型变量的复制:从一个变量向一个变量复制时,会在栈中建立一个新值,而后把值复制到为新变量分配的位置上;
  • 引用类型变量的复制:复制的是存储在栈中的指针,将指针复制到栈中未新变量分配的空间中,而这个指针副本和原指针执行存储在堆中的同一个对象;复制操做结束后,两个变量实际上将引用同一个对象;所以改变其中的一个,将影响另外一个;

三种变量类型检测

1.   Typeof操做符是检测基本类型的最佳工具;

2.   若是变量值是null或者对象,typeof 将返回“object”;结合null == null 来判断

3.   Instanceof用于检测引用类型,能够检测到具体的,它是什么类型的实例;

4.   若是变量是给定引用类型的实例,instanceof操做符会返回true;

5. Object.prototype.toString.call(xx) 来打印原型判断类型

13.你能够用js去实现一个单向、双向、循环链表吗?你能够实现查找、插入、删除操做吗?

能够在这里试一下:在线编程环境

链表:

插入链表节点:

删除链表节点:

双向链表:

循环链表:

下面给一个最简单的单项链表示例:

/**
** 先建立一个节点类,记录当前数据,和下个节点,若是是双向链表,就包含prev
** prev: 对上个节点的引用
** next: 对下个节点的应用
**/
class Node{
  constructor(data){
    this.data = data;
    this.next = null;
  }
}
/**
** 建立链表,head是链表中的一个起始节点,关于单项链表,双向链表和循环链表参考文章介绍
** find: 找到数据所在的节点,这里是示例,其实应该有个惟一标识
** insert: 在指定节点后面插入节点
**/
class LinkTable{
  constructor(data){
    this.head = null;
    this.end = null;
    if(data){
      this.head = new Node(data)
    }
  }
  find(data){
    let start = this.head;
    while(start.data != data){
      start = start.next;
    }
    return start;
  }
  insert(data,node){
    let nod = new Node(data);
    nod.next = node.next;
    item.next = nod;
  }
}

 

14.你了解基本常见算法吗?快速排序写一个?要是限制空间利用你该如何写?

快速排序:

(1)在数据集之中,选择一个元素做为"基准"(pivot)。

(2)全部小于"基准"的元素,都移到"基准"的左边;全部大于"基准"的元素,都移到"基准"的右边。

(3)对"基准"左边和右边的两个子集,不断重复第一步和第二步,直到全部子集只剩下一个元素为止。

选择排序:

(1)首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置

(2)再从剩余未排序元素中继续寻找最小(大)元素,而后放到已排序序列的末尾

(3)直到全部都排序

冒泡排序:

  1. 比较相邻的元素。若是第一个比第二个大,就交换他们两个。
  2. 对每一对相邻元素做一样的工做,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
  3. 针对全部的元素重复以上的步骤,除了最后一个。
  4. 持续每次对愈来愈少的元素重复上面的步骤,直到没有任何一对数字须要比较。

直接插入排序:

(1)将待排序数组取一个数值插入到已排序数组中合适的位置

(2)重复取数据,直到全部数据取完

 

15.你了解贪心算法、动态规划、分治算法、回溯算法等常见的算法吗?

分治算法

分治法的设计思想是:将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。

分治策略是:对于一个规模为n的问题,若该问题能够容易地解决(好比说规模n较小)则直接解决,不然将其分解为k个规模较小的子问题,这些子问题互相独立且与原问题形式相同,递归地解这些子问题,而后将各子问题的解合并获得原问题的解。这种算法设计策略叫作分治法。

分治法所能解决的问题通常具备如下几个特征:

1) 该问题的规模缩小到必定的程度就能够容易地解决

2) 该问题能够分解为若干个规模较小的相同问题,即该问题具备最优子结构性质。

3) 利用该问题分解出的子问题的解能够合并为该问题的解;

4) 该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子子问题。

贪心算法

 所谓贪心算法是指,在对问题求解时,老是作出在当前看来是最好的选择。也就是说,不从总体最优上加以考虑,他所作出的仅是在某种意义上的局部最优解。

     贪心算法没有固定的算法框架,算法设计的关键是贪心策略的选择。必须注意的是,贪心算法不是对全部问题都能获得总体最优解,选择的贪心策略必须具有无后效性,即某个状态之后的过程不会影响之前的状态,只与当前状态有关。

    因此对所采用的贪心策略必定要仔细分析其是否知足无后效性。

  • 无后效性:即某阶段状态一旦肯定,就不受这个状态之后决策的影响。也就是说,某状态之后的过程不会影响之前的状态,只与当前状态有关。

贪心算法的基本思路:

    1.创建数学模型来描述问题。

    2.把求解的问题分红若干个子问题。

    3.对每一子问题求解,获得子问题的局部最优解。

    4.把子问题的解局部最优解合成原来解问题的一个解。

动态规划算法

动态规划过程是:每次决策依赖于当前状态,又随即引发状态的转移。一个决策序列就是在变化的状态中产生出来的,因此,这种多阶段最优化决策解决问题的过程就称为动态规划。

    基本思想与分治法相似,也是将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。在求解任一子问题时,列出各类可能的局部解,经过决策保留那些有可能达到最优的局部解,丢弃其余局部解。依次解决各子问题,最后一个子问题就是初始问题的解。

    因为动态规划解决的问题多数有重叠子问题这个特色,为减小重复计算,对每个子问题只解一次,将其不一样阶段的不一样状态保存在一个二维数组中。

    与分治法最大的差异是:适合于用动态规划法求解的问题,经分解后获得的子问题每每不是互相独立的(即下一个子阶段的求解是创建在上一个子阶段的解的基础上,进行进一步的求解)。

能采用动态规划求解的问题的通常要具备3个性质:

    (1) 最优化原理:若是问题的最优解所包含的子问题的解也是最优的,就称该问题具备最优子结构,即知足最优化原理。

    (2) 无后效性:即某阶段状态一旦肯定,就不受这个状态之后决策的影响。也就是说,某状态之后的过程不会影响之前的状态,只与当前状态有关。

   (3)有重叠子问题:即子问题之间是不独立的,一个子问题在下一阶段决策中可能被屡次使用到。(该性质并非动态规划适用的必要条件,可是若是没有这条性质,动态规划算法同其余算法相比就不具有优点)

回溯法

在包含问题的全部解的解空间树中,按照深度优先搜索的策略,从根结点出发深度探索解空间树。当探索到某一结点时,要先判断该结点是否包含问题的解,若是包含,就从该结点出发继续探索下去,若是该结点不包含问题的解,则逐层向其祖先结点回溯。(其实回溯法就是对隐式图的深度优先搜索算法)

分支限界法

相似于回溯法,也是一种在问题的解空间树T上搜索问题解的算法。但在通常状况下,分支限界法与回溯法的求解目标不一样。回溯法的求解目标是找出T中知足约束条件的全部解,而分支限界法的求解目标则是找出知足约束条件的一个解,或是在知足约束条件的解中找出使某一目标函数值达到极大或极小的解,即在某种意义下的最优解

因为求解目标不一样,致使分支限界法与回溯法在解空间树T上的搜索方式也不相同。回溯法以深度优先的方式搜索解空间树T,而分支限界法则以广度优先或以最小耗费优先的方式搜索解空间树T

 

16.你是如何理解前端架构的?你了解持续集成吗?

架构,我理解主要作:系统分解、服务分层的工做。

 

持续集成 (Continuous integration,简称CI)。项目是一个迭代一个迭代快速开发,每一个迭代开发不一样的feature,全部的feature合在一块儿构成完整的功能。

持续集成的目的,就是让产品能够快速迭代,同时还能保持高质量。它的核心措施是,代码集成到主干以前,必须经过自动化测试。只要有一个测试用例失败,就不能集成。

Martin Fowler说过,"持续集成并不能消除Bug,而是让它们很是容易发现和改正。"

与持续集成相关的,还有两个概念,分别是持续交付和持续部署。

 

17.你了解基本的设计模式吗?举例单例模式、策略模式、代理模式、迭代模式、发布订阅模式。。。?

设计模式(Design pattern)表明了最佳的实践,一般被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程当中面临的通常问题的解决方案。

单例模式:

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于建立型模式,它提供了一种建立对象的最佳方式。

这种模式涉及到一个单一的类,该类负责建立本身的对象,同时确保只有单个对象被建立。这个类提供了一种访问其惟一的对象的方式,能够直接访问,不须要实例化该类的对象。

注意:

  • 一、单例类只能有一个实例。
  • 二、单例类必须本身建立本身的惟一实例。
  • 三、单例类必须给全部其余对象提供这一实例。
class Singleton{
  static getInstance(){
    if(!this.ins) this.ins = 1;
    return this.ins;
  }
}
console.log(Singleton.getInstance())
class Singleton{
  constructor(){
    this.ins = 1;
  }
  getInstance(){
    return this.ins;
  }
}
console.log(new Singleton().getInstance())

策略模式

在策略模式(Strategy Pattern)中,一个类的行为或其算法能够在运行时更改。这种类型的设计模式属于行为型模式。

在策略模式中,咱们建立表示各类策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。

很好理解,好比上面给的一个异常处理的代码,写个简单的示例。

export class RuntimeException {

    constructor(message) {
        this._message = message;
    }

    get name() {
        return 'RuntimeException';
    }

    get message() {
        return this._message;
    }

    toString() {
        return this.name + ': ' + this.message;
    }

}

export class IllegalStateException extends RuntimeException {

    constructor(message) {
        super(message);
    }

    get name() {
        return 'IllegalStateException';
    }

}

export class InvalidArgumentException extends RuntimeException {

    constructor(message) {
        super(message);
    }

    get name() {
        return 'InvalidArgumentException';
    }

}

export class NotImplementedException extends RuntimeException {

    constructor(message) {
        super(message);
    }

    get name() {
        return 'NotImplementedException';
    }

}

export function funcWrapper(args){
    try{
      if(!args) throw new InvalidArgumentException('args undefined')
      if(args == 1) throw new IllegalStateException('args illegal')
    }catch(e){
        console.log(e.toString())
    }
}

浏览器能够跑下结果看看:

这就是策略模式,不一样的状况,输出的结果是不同的。

代理模式

 

18.写一个事件监听函数呗?实现once、on、remove、emit功能

 

19.node.js的实现层是什么?

 

20.node的事件循环机制是怎样的?node的child_process模块有几个api,分别的做用是什么?

在node中,事件循环表现出的状态与浏览器中大体相同。不一样的是node中有一套本身的模型。node中事件循环的实现是依靠的libuv引擎。咱们知道node选择chrome v8引擎做为js解释器,v8引擎将js代码分析后去调用对应的node api,而这些api最后则由libuv引擎驱动,执行对应的任务,并把不一样的事件放在不一样的队列中等待主线程执行。 所以实际上node中的事件循环存在于libuv引擎中。下面是一个libuv引擎中的事件循环的模型:

注:模型中的每个方块表明事件循环的一个阶段

这个模型是node官网上的一篇文章中给出的,我下面的解释也都来源于这篇文章。我会在文末把文章地址贴出来,有兴趣的朋友能够亲自与看看原文。

咱们知道Linux中有个高效多路IO复用的poll/select模型,加强改进有个epoll模型。参考这里

上面这个node的poll模型中,咱们能够大体分析出node中的事件循环的顺序:

外部输入数据-->轮询阶段(poll)-->检查阶段(check)-->关闭事件回调阶段(close callback)-->定时器检测阶段(timer)-->I/O事件回调阶段(I/O callbacks)-->闲置阶段(idle, prepare)-->轮询阶段...

以上各阶段的名称是根据我我的理解的翻译,为了不错误和歧义,下面解释的时候会用英文来表示这些阶段。

这些阶段大体的功能以下:

  • timers: 这个阶段执行定时器队列中的回调如 setTimeout() 和 setInterval()
  • I/O callbacks: 这个阶段执行几乎全部的回调。可是不包括close事件,定时器和setImmediate()的回调。
  • idle, prepare: 这个阶段仅在内部使用,能够没必要理会。
  • poll: 等待新的I/O事件,node在一些特殊状况下会阻塞在这里。
  • check: setImmediate()的回调会在这个阶段执行。
  • close callbacks: 例如socket.on('close', ...)这种close事件的回调。

详情参考:浏览器与node环境的事件循环机制

22.http1.0与1.1协议的区别?node是如何实现http模块的?

 

25.nginx相关配置了解过吗?

 

27.小程序架构

 

 

28. vue v-model 语法糖?vue push式更新?vue computed和watch的区别?

 

29. redux dispatch一个action以后的更新过程

知道connect的时候出于性能优化的考虑作了一层浅比较。

相关文章
相关标签/搜索