frontend interview

笔记年羹

前端基础

  • JS 原型链机制的理解
  • 作用域和this
    • 代码在一个环境中执行时,会创建变量对象的一个作用域链
    • 每个函数都有自己的作用域(函数作用域, let引入块作用域), 作用域可以层层嵌套, 子作用域变量覆盖父级作用域
    • 声明提前: js函数里所有变量声明(var)都被提前至函数体顶部.
    • es6: temporal dead zone, let代码段(if/for/switch)变量声明前引用会报ReferenceError
    • JS专题之严格模式
    • this四种绑定
      • 默认绑定全局window
      • 隐式绑定,最近的一个调用该函数的上下文对象(context object)
        • 在函数上下文中,也就是在任何函数体内部,this 指代调用函数的那个对象。严格模式下,this禁止指向window
      • 显式绑定(call/apply/bind), bind在定义函数时候就绑定this,call和apply在调用函数时候才绑定this
      • new绑定
        • 一个全新的对象会凭空创建(就是被构建)
        • 这个新构建的对象会被接入原形链([[Prototype]]-linked)
        • 这个新构建的对象被设置为函数调用的this绑定
        • 除非函数返回一个它自己的其他 对象,这个被new调用的函数将 自动返回这个新构建的对象
  • 闭包: 只有函数内部的子函数才能读取函数的局部变量. 闭包内的变量因为引用计数, 不会在函数调用完后清除.
    • 闭包做对象缓存, 模块化代码. 闭包会使得函数中的变量都被保存在内存中.滥用导致内测泄露.
    • 调用 f.bind(someObject) 会创建一个与 f 具有相同函数体和作用域的函数,但是在这个新函数中,this 将永久地被绑定到了 bind 的第一个参数,无论这个函数是如何被
  • 对象:

    • 基础类型string,number,boolean,null,undefined,object, build-in对象类型String,Number,Boolean,Object,Function,Array,Date,RegExp,Error
    • defineProperties:
      • [[Configurable]]:表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为数据属性。默认值为true。
      • [[Enumerable]]:表示能否通过for…in…循环遍历到该属性,默认值为true。
      • 访问器属性 [[Get]]:在读取属性时调用的函数。默认值为undefined。
      • 访问器属性 [[Set]]:在写入属性时调用的函数。默认值为undefined。
      • 数据属性 [[Writable]]:表示能否修改属性的值,默认为true。
      • 数据属性 [[Value]]:该属性的数值。
    • ...,assign都是浅拷贝
  • 设计模式:了解基本的前端设计模式,单例、适配器、工厂、观察者、迭代器、发布/订阅。

  • 跨域的方式、同源策
    略、为什么有同源策略、如何做安全防范:新=的- H5的跨域方式(cors、postmessage)。

  • 安全,对攻击方式、安全的防范上的了解 。
  • http、TCP 协议的知识,如:什么是无状态,http 状态码的分类。
    • CDN的域名不要和主站的域名一样,这样会防止访问CDN时还携带主站cookie的问题。
  • 知晓 CSS 布局原理,什么是BFC,如何实现垂直居中,绝对定位相对位置。
  • 如何做自适应布局,怎么计算 REM
  • websocket, WebRTC, EventSource 的区别

框架、组件化

  • 架构分层
  • 模块解耦:理解接口、事件通讯的两种方式。
  • 组件化趋势: shadow dom,react和vue 。
  • Virtual DOM 的优势以及缺陷
    • 减少dom的增删开支, 增加js计算消耗.
    • 未命中sameNode, 还是一样的dom增删消耗, 同级修改, 添加key.
    • dom结构越庞大, 优化的效果越明显.
    • snabbdom的怕patch算法和vue的一致.
  • 实践中如何解耦 UI 状态和领域状态
  • 目录结构如何规划

前端构建方案

  • 工程化的理解以及解决的问题如 gulp。
  • 如何拆分 SPA 中的大型代码
  • 有没有写过 webpack loader/ plugin, 以及这个 loader 是为了解决什么问题
  • 做过什么打包优化, 分离全家桶, 脚本构建npm/yarn
  • ts项目搭建
  • 性能优化

  • webview的优化:对静态资源缓存到native的原理和流程 ,- webview缓存、版本号管理、线下调试。

  • 如何加快首屏加载速度,Server Render 的实践。
  • 网页渲染性能优化,layout, paint, compose 三步骤的理解。
  • css 动画、SVG、canvas 的运用
  • 针对前端框架的性能优化,如 showComponentUpdate 的使用
  • 如何带领团队优化:制定量化指标,寻找性能瓶颈,集中优化。
  • 浏览器资源加载, 解析, 渲染.
  • 事件循环, 异步promise理解
  • 案例:

    1. Vue 应用性能优化指南

      质量保障

  • eslint、tslint 如何跟开发流程集成

  • 单元测试覆盖率
  • 如何面对需求变更带来的测试用例失效
  • 前端灰度方案
  • 如何排查内存泄漏
  • Jest/jasmine puppeteer/nightmare

    其他

  • 期望:偏技术或偏管理

  • 觉得一个前端专家应具备的技能和素质:综合、系统能力,需要理解- 系统和框架的原理,对前端前沿技术有所关注。
  • 前沿技术的了解
  • 对前端未来走向的判断
  • 对领域设计的理解

遇到过的问题

  • v-for在checker上引起的不更新

基础


  • 6, 11, 12, 23
Number Content
1 BAT及各大互联网公司2014前端笔试面试题–Html,Css篇
2 BAT及各大互联网公司2014前端笔试面试题–JavaScript篇
3 javascript面试题
4 javascript puzzlers
6 前端开发面试题
7 如何面试前端工程师:GitHub 很重要
8 44 个 JavaScript 变态题解析
9 如何通过饿了么 Node.js 面试
10 Front-end-Developer-Interview-Questions
11 轻课堂JavaScript面试题
12 我遇到的前端面试题2017
13 2016腾讯前端JavaScript笔试题
15 前端面试题的整理
16 魅族前端面试题
17 前端面试经典题目汇总
18 阿里、网易、滴滴共十次前端面试碰到的问题
19 原来又到了校招季,一个前端新人的回忆
20 Node.js面试题之2017
21 Node.js 常见面试题
22 2017年前端面试题整理汇总100题
23 前端面试题和答案
24 123-Essential-JavaScript-Interview-Question
25 interview-questions-in-javascript
26 js-interview-review
27 hiring-without-whiteboards
28 方正的前端面试知识点汇总
29 React 常用面试题目与分析
30 2016各大互联网公司前端面试题汇总
31 2017年第一波JavaScript前端面试题
32 前端面试之js相关问题(一)
33 前端面试之js相关问题(二)
34 无他,唯手熟尔-前端实习面试题篇
35 前端基础面试题(JS部分)
36 Vue面试题合集
37 JavaScript,Python,go实现十大经典排序算法
38 破解前端面试(80% 应聘者不及格系列):从 闭包说起
39 前端面试–四月二十家前端面试题分享
40 ECMAScript 6 六级考试
41 前端开发面试问题及答案整理
42 前端面试问题
43 前端经典面试题集合

笔记

链接: http , js, css, 框架 , 性能优化

http/https/websocket

http

特点: 无状态,被动型.
长轮询:等服务端响应,没等到就响应空数据,浏览器再发请求.
短轮询:服务端直接响应,没数据,客户端等会儿在发请求. 就是服务端还是客户端等一会儿的区别.
短轮询是每次http请求前都要建立连接,而长轮询是相邻几次请求前都要建立连接
http1.1支持长连接,由connection: keep-alive标识.长连接相邻几次请求只建立一次连接

https

http和TCP中间夹层SSL, 加证书验证身份.
但是公开密钥加密与共享密钥加密相比,其处理速度要慢. 非对称传共享秘钥, 后面就用共享秘钥加密内容.

https通信步骤:

  1. 客户端通过发送 Client Hello 报文开始 SSL 通信。报文中包含客户端支持的 SSL 的指定版本、加密组件(Cipher Suite)列表(所使用的加密算法及密钥长度等)

  2. 服务器可进行 SSL 通信时,会以 Server Hello 报文作为应答。和客户端一样,在报文中包含 SSL 版本以及加密组件。服务器的加密组件内容是从接收 到的客户端加密组件内筛选出来的。

  3. 之后服务器发送 Certificate 报文。报文中包含公开密钥证书。

  4. 最后服务器发送 Server Hello Done 报文通知客户端,最初阶段的 SSL 握手协商部分结束。
  5. SSL 第一次握手结束之后,客户端以 Client Key Exchange 报文作为回应。报文中包含通信加密中使用的一种被称为 Pre-master secret 的随机密码串。该 报文已用步骤#3 中的公开密钥进行加密。
  6. 接着客户端继续发送 Change Cipher Spec 报文。该报文会提示服务器,在此报文之后的通信会采用 Pre-master secret 密钥加密。
  7. 客户端发送 Finished 报文。该报文包含连接至今全部报文的整体校验值。这次握手协商是否能够成功,要以服务器是否能够正确解密该报文作为判定标准。
  8. 服务器同样发送 Change Cipher Spec 报文。
    服务器同样发送 Finished 报文。
  9. 服务器和客户端的 Finished 报文交换完毕之后,SSL 连接就算建立完成。当然,通信会受到 SSL 的保护。从此处开始进行应用层协议的通信,即发 送 HTTP 请求。
    应用层协议通信,即发送 HTTP 响应。
    最后由客户端断开连接。断开连接时,发送 close_notify 报文。

js

es6

  1. arrow function
    this 是lexical binding非 dynamic, 通常就是绑定外层function的this. 也就不要做self=this这种hack.
    适合代码少的function, 适合给内层function需要外层this/argument/super的function.
  2. spread + rest 看起来更简洁
  3. 函数默认参数, 不用再xx||default了
  4. 参数析构, 看起来更简洁
  5. 对象字面量增强 书写代码更简洁
  6. 模板字符串 书写代码更简洁,不要豆豆加加
  7. Let + Const blockscoped
  8. for..of 迭代器遍历
  9. 异步实现generator/Promises 被es7 await+async取代
  10. 模块化export/import, 统一了模块化的标准
  11. Map + Set + WeakMap + WeakSet 丰富了集合数据结构
  12. meta-data symbols, proxy/reflect
  13. Math + Number + String + Object APIs isNaN, array.from, array.entries/keys/values等iterator. object.assign, string.includes/repeat

    • 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      Number.EPSILON
      Number.isInteger(Infinity) // false
      Number.isNaN("NaN") // false
      Math.acosh(3) // 1.762747174039086
      Math.hypot(3, 4) // 5
      Math.imul(Math.pow(2, 32) - 1, Math.pow(2, 32) - 2) // 2
      "abcde".includes("cd") // true
      "abc".repeat(3) // "abcabcabc"
      Array.from(document.querySelectorAll("*")) // Returns a real Array
      Array.of(1, 2, 3) // Similar to new Array(...), but without special one-arg behavior
      [0, 0, 0].fill(7, 1) // [0,7,7]
      [1,2,3].findIndex(x => x == 2) // 1
      ["a", "b", "c"].entries() // iterator [0, "a"], [1,"b"], [2,"c"]
      ["a", "b", "c"].keys() // iterator 0, 1, 2
      ["a", "b", "c"].values() // iterator "a", "b", "c"
      Object.assign(Point, { origin: new Point(0,0) })
  14. class语法糖

    • 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      class SkinnedMesh extends THREE.Mesh {
      constructor(geometry, materials) {
      super(geometry, materials);
      this.idMatrix = SkinnedMesh.defaultMatrix();
      this.bones = [];
      this.boneMatrices = [];
      //...
      }
      update(camera) {
      //...
      super.update();
      }
      static defaultMatrix() {
      return new THREE.Matrix4();
      }
      }

js

JS继承的实现方式

  1. 原型链继承Cat.prototype = new Animal();
    • 缺点1. 来自原型对象的引用属性是所有实例共享的
    • 缺点2. 创建子类实例时,无法向父类构造函数传参
  2. 构造继承 原型链没连上
  3. 实例继承 实例是父类实例
  4. 拷贝继承 效率低
  5. 组合继承
  6. 寄生组合继承 解决调用了两次父类构造函数

单元测试sinon/jasmine

模拟输入数据比对输出结果, spy函数比对传参和返回结果, 时钟测异步, 模拟request,response,xmlhttp

css

css 常规布局/栅格 /左右两栏定宽,中间自适应的实现方

一个高度100px,另一个填满剩下的高度。

  1. 外层box-sizing: border-box; 同时设置padding: 100px 0 0;
    内层100像素高的元素向上移动100像素,或使用absolute定位防止占据空间;
    另一个元素直接height: 100%;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    html,
    body {
    height: 100%;
    padding: 0;
    margin: 0;
    }
    .outer {
    height: 100%;
    padding: 100px 0 0;
    box-sizing: border-box;
    position: relative;
    }
    .A {
    height: 100px;
    background: #BBE8F2;
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    }
    /* 或者 */
    .A {
    height: 100px;
    margin: -100px 0 0;
    background: #BBE8F2;
    }
    .B {
    height: 100%;
    background: #D9C666;
    }
  2. absolute positioning
    外层position: relative;
    百分百自适应元素直接position: absolute; top: 100px; bottom: 0; left: 0

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    .outer {
    height: 100%;
    position: relative;
    }
    .A {
    height: 100px;
    background: #BBE8F2;
    }
    .B {
    background: #D9C666;
    width: 100%;
    position: absolute;
    top: 100px;
    left: 0;
    bottom: 0;
    }
  3. flex

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    .outer {
    width: 200px;
    height: 300px;
    background: red;
    display: flex;
    flex-direction: column;
    }
    .A {
    height: 100px;
    background: green;
    }
    .B {
    background: blue;
    flex: 1
    }

品字布局

固定宽高布局

1
2
3
<div class="d1"></div>
<div class="d2"></div>
<div class="d3"></div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
.d1,
.d2,
.d3 {
height: 100px;
width: 100px;
background-color: #FF0000;
border: solid 1px #000000;
}
.d1 {
margin: 0 auto;
}
/* 这是左 */
.d3 {
float: left;
margin-left: -200px;
}
/* 这是右 */
.d2 {
float: left;
margin-left: 50%;
}

满屏布局

1
2
3
.div1{ margin:0 auto}
.div2{ background:green; float:left; width:50%;}
.div3{ background:blue; float:left; width:50%;}

框架 TODO

vue 轻量/相关技术栈齐全/有饿了么和阿里大公司使用和支持

  1. 父组件和子组件、子组件和子组件如何传递数据 props down emit up
    兄弟组件要么用一个Vue对象做通信bus, 要么vuex
  2. dom更新机制
    • vue初始化时通过Object.defineProperty给data设置setter/getter函数,用来实现reactivity以及依赖收集. get会往闭包内的dep添加watcher, set会调dep.notify遍历watcher
    • reativity通过nexttick触发diff算法进行局部DOM更新
  3. reactivity
  • 其实「依赖收集」的过程就是把 Watcher 实例存放到对应的 Dep 对象中去。get 方法可以让当前的 Watcher 对象(Dep.target)存放到它的 subs 中(addSub)方法,在数据变化时,set 会调用 Dep 对象的 notify 方法通知它内部所有的 Watcher 对象进行视图更新。
  • vuex. install方法里给Vue混入初始化方法. 初始化获取store到data上. commit方法遍历执行mutation, dispatch方法用promise执行action.
  1. compile
  • parse 用正则遍历template形成ast, 遍历获取标签,属性,文本, 指令,层级关系等
  • optimize 标记静态节点(没有v-if/v-for属性的文本节点type===3),在patch比对的时候可以直接跳过来优化
  • generate 将 AST 转化成 render funtion 字符串
  1. diff
  • 调patch方法比vnode
    • 只有当 key、 tag、 isComment就是sameVnode, 不是就替换realDom
    • 调用patchVnode ,通过静态节点, 两个都有子虚节点比对更新childVnode
  • 优化: 尽量不要跨层级的修改dom, 设置key可以最大化的重复利用节点
  1. nexttick
  • 调用nexttick回调会被放在队列里,然后异步批量执行
  • Internally Vue tries native Promise.then and MessageChannel for the asynchronous queuing and falls back to setTimeout(fn, 0).
  • Vue异步更新dom, notify后 watch的run会通过nextTick下个周期执行, watch只会添加进队列一次
    1
    2
    3
    4
    5
    this.message = 'updated' // setter触发update,将watcher的run加入nextTick回调
    console.log(this.$el.textContent) // => 'not updated'
    this.$nextTick(function () {
    console.log(this.$el.textContent) // => 'updated'
    })

路由

  • 脚本原理监听hash变化/state, 匹配路径, 执行对应的render回调

    angular

    指令间通信方式

  • service,单例可以重复注入
  • $broadcast及$emit利用scope hierarchy, 不推荐因为广播遍历整颗树
  • 同一元素指令利用attrs的$observe,$set通信
  • 层级指令通过require controller来通信

    双向绑定

  • interpolate,生成addTextInterpolateDirective, watch the expression and update the dom
  • ng-input收到input事件后去调ngModuleController的$setViewValue, 更新$modelValue,$viewValue

    controller as name

  • 把controller通过别名引用到scope上

    scope

  • $apply(外部函数执行后触发脏值检查)调用$digest, digest里面有脏数据就循环调$watch的listener, 超过10次抛异常结束
  • 共享scope原型链继承, 独立scope创建childScope = Object.create(this);. isolate scope有自己watchers,listeners等
    • Destroy a scope: 移除watchers, 从$parent的$$children移除自己
    • $digest将递归执行children的listeners
  • 事件系统基于scope层级,向上是emit向下是broadcast
    • 模拟propagationStopped, 结束$emit事件
  • $evalasync
    • 往$$asyncQueue加任务, 执行规则: 当前是digest phase就在$$digestOnce后的下次循环执行,没在digest phase就自己去调用$digest
    • 代码如下:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      Scope.prototype.$evalAsync = function(expr) {
      var self = this;
      if (!self.$$phase && !self.$$asyncQueue.length) { // If there’s something in the queue, we already have a timeout set and it will eventually drain the queue.
      setTimeout(function() {
      if (self.$$asyncQueue.length) {
      self.$root.$digest();
      }
      }, 0);
      }
      this.$$asyncQueue.push({ scope: this, expression: expr});
      };
      // digest snippet
      do {
      while (this.$$asyncQueue.length) {
      try {
      var asyncTask = this.$$asyncQueue.shift();
      asyncTask.scope.$eval(asyncTask.expression);
      } catch (error) {
      // console.log(error);
      }
      }
      dirty = this.$$digestOnce();
      if ((dirty || this.$$asyncQueue.length) && !(ttl--)) {
      this.$clearPhase();
      throw ttl + ' digest iterations reached';
      }
      } while (dirty || this.$$asyncQueue.length);

expression|filter

  • 表达式解析过程: lexer词法解析 -> tokens -> astBuilder生成ast -> astCompiler -> new Function生成函数表达式
  • expect预读token
  • astCompile的时候,操作符优先级通过()实现的
  • $watch与parse结合的时候为了提升效率,引入$$watchDelegate. 将特殊情况的表达式特殊处理(constant watch执行一次后就把watcher移除).

DI

  • providerCache里面是一些有$get属性的对象, instanceCache是providerCache的$get执行后获得的对象.
  • config是执行providerInjector的$injector.invoke,而且是在configblock里, 执行configblock的时候其他provider都已经实例化,可对这些provider进行配置. 所有module的runBlocks汇集后再执行
  • providerCache.$provide那几个方法都是往providerCache里加provider实例
  • Factory是一个可注入的function,它和service的区别就是:factory是普通function,而service是一个构造器(constructor),这样Angular在调用service时会用new关键字,而调用factory时只是调用普通的function,所以factory可以返回任何东西,而service可以不返回(可查阅new关键字的作用)
  • Providers allow you to configure the factory or service in the module’s config block before it is instantiated. 只有provider可以返回除了$get之外的方法
  • decorator修改providerInstance后再返回

Utils

promise
  • defer里面有promise, resolve, reject
  • $q的异步机制是调用$evalAsync, $$q的是setTimeout, 来调用processQueue??
  • promise内置变量$$state, 基于state转换,且只能resolve/reject一次

  • 机制

    • new Deffered会在内部创建一个promise, promise内部有个$$state(包含value, status, pending)
    • defer.resolve defer.reject会修改$$state.status并且去执行promise里$$state.pending数组, 执行回调结果会传参给下个defer的resolve/reject

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      Deferred.prototype.reject = function(reason) {
      if (this.promise.$$state.status) {
      return;
      }
      this.promise.$$state.value = reason;
      this.promise.$$state.status = 2;
      scheduleProcessQueue(this.promise.$$state);
      }
      // scheduleProcessQueue'异步'执行processQueue
      function processQueue(state) {
      var pending = state.pending;
      state.pending = undefined;
      _.forEach(pending, function(handlers) {
      var deferred = handlers[0];
      var fn = handlers[state.status];
      try {
      if (_.isFunction(fn)) {
      deferred.resolve(fn(state.value));
      } else if (state.status === 1) {
      deferred.resolve(state.value);
      } else {
      deferred.reject(state.value);
      }
      } catch (e) {
      deferred.reject(e);
      }
      });
      }
    • Promise.then会重复new Deferred(); return defer.promise这个过程,并把传进来的onFulfilled和新的defer保存到当前promise的$$state.pending数组里

      1
      2
      3
      4
      5
      6
      7
      8
      9
      Promise.prototype.then = function(onFulfilled, onRejected, onProgress) {
      var result = new Deferred();
      this.$$state.pending = this.$$state.pending || [];
      this.$$state.pending.push([result, onFulfilled, onRejected, onProgress]);
      if (this.$$state.status > 0) {
      scheduleProcessQueue(this.$$state);
      }
      return result.promise;
      };
  • 其他: reject后不能被resolve, 会一直调用到最底层的defer.

    http
  • http_backend回调传参

    1
    2
    3
    4
    5
    6
    callback(
    xhr.status,
    response,
    xhr.getAllResponseHeaders(),
    statusText
    )
  • $http的intercepter通过promise实现request和response的分层处理.

    • 拦截器有四个属性request, requestError, responce, responceError(对应四个阶段)
    • 这边在request里加请求头字段, response/responseError里统一处理异常信息

directive

  • controllers只是指令系统的一部分

    双向绑定

  • =属性绑定, parentValue和上次不一样覆盖子属性, 一样子覆盖父属性
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    case '=':
    if (definition.optional && !attrs[attrName]) {
    break;
    }
    parentGetParseFun = $parse(attrs[attrName]);
    var lastValue = destination[scopeName] = parentGetParseFun(
    scope); // right now scope is empty
    var parentValueWatch = function() {
    var parentValue = parentGetParseFun(scope);
    if (lastValue !== parentValue) {
    destination[scopeName] = parentValue;
    } else {
    parentValue = destination[scopeName];
    parentGetParseFun.assign(scope, parentValue);
    }
    lastValue = parentValue;
    return lastValue;
    };
    if (definition.collection) {
    unwatch = scope.$watchCollection(attrs[attrName],
    parentValueWatch);
    } else {
    unwatch = scope.$watch(parentValueWatch);
    }
    newScope.$on('$destroy', unwatch);
    break;

gulp原理

  • 基本概念与原理
  • Vinyl-fs,它主要的工作是接受 glob 模式的参数,然后读取匹配的文件。然后利用 Vinyl 制作一个 Transform Stream,称为 Vinyl Stream 对象,并返回。
  • 在 Gulp 中的 API gulp.src、gulp.watch、gulp.dest 都返回一个 Vinyl Stream 实例对象。Vinyl Stream 实例之间可以通过管道( vinyl1.pipe(vinyl2) )的形式来互相传输数据。

  • 从 Gulp 的 源码 中也能看出,这三个 API 都是由 vinyl-fs 提供全部的实现。

  • 再一点是,从这两个模块的实现来看,Gulp 是把文件内容以 Buffer 的形式读到内存中,然后再进行处理的

  • Orchestartor,为 gulp.task 提供了全部实现,这可以从 源码 中看出。
    它为 Gulp 提供了任务相关的功能,包括任务注册、任务执行以及相对应的任务进度、错误监控等功能。

    常用插件

  • gulp-load-plugins:自动加载 package.json 中的 gulp 插件
  • gulp-rename: 重命名
  • gulp-uglify:文件压缩
  • gulp-concat:文件合并
  • gulp-less:编译 less
  • gulp-sass:编译 sass
  • gulp-clean-css:压缩 CSS 文件
  • gulp-htmlmin:压缩 HTML 文件
  • gulp-babel: 使用 babel 编译 JS 文件
  • gulp-jshint:jshint 检查
  • gulp-imagemin:压缩jpg、png、gif等图片
  • gulp-livereload:当代码变化时,它可以帮我们自动刷新页面

    webpack的原理/常用插件

    webpack插件原理

  1. webpack插件就是一个function, function的prototype上定义了apply方法.
    webpack调用该方法注入compiler对象. complier(包含所有配置信息)通过Tapable的plugin方法注册编译事件回调. 事件回调执行时传入compilation对象
  2. compilation继承compiler, 在开发环境中compiler编译配置文件并返回compilation,compilation是每个资源发生变化后重新加载模块编译到bundle里
  3. compiler和compilation的事件钩子
    • compilation生成compilation对象, emit在将内存中assets写到硬盘前, make分析依赖,build模块
    • optimize优化编译, optimize-chunks获取模块依赖,loader等
      1
      2
      3
      4
      5
      6
      Plugin.prototype.apply = function (compiler) {
      compiler.plugin('emit', function (compilation, callback) {
      console.log(compilation);
      callback();
      });
      };

webapck模块化机制

  • webpack把模块代码包装在函数内部. 通过实现exports和require,然后自动加载入口模块,控制缓存模块, 做到依赖复用(angular依赖注入类似实现)
  • webpack传入的第一个参数module是当前缓存的模块,包含当前模块的信息和exports;第二个参数exports是module.exports的引用,这也符合commonjs的规范;第三个webpack_require 则是require的实现

    1
    2
    3
    function (module, exports, __webpack_require__) {
    /* 模块的代码 */
    }
  • webpack_require的实现和commonjs的require实现一致

    1. IIFE首先定义了installedModules ,这个变量被用来缓存已加载的模块。
    2. 定义了`webpack_require 这个函数,函数参数为模块的id。这个函数用来实现模块的require。
    3. __webpack_require__ 函数首先会检查是否缓存了已加载的模块,如果有则直接返回缓存模块的exports。
    4. 如果没有缓存,也就是第一次加载,则首先初始化模块,并将模块进行缓存。
    5. 然后调用模块函数,也就是前面webpack对我们的模块的包装函数,将module、module.exports和__webpack_require__作为参数传入。注意这里做了一个动态绑定,将模块函数的调用对象绑定为module.exports,这是为了保证在模块中的this指向当前模块。modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
    6. 调用完成后,模块标记为已加载。
    7. 返回模块exports的内容。
    8. 利用前面定义的__webpack_require__ 函数,require第0个模块,也就是入口模块。

webpack异步加载/code split 参考

  • installedChunks指的是文件chunk, installedModules才是文件里面的模块function
  • webpack通过__webpack_require__.e函数实现了动态加载,script loaded后再通过webpackJsonp函数去调__webpack_require__加载chunk里面module。
  • __webpack_require__.e代码解析

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    __webpack_require__.e = function requireEnsure(chunkId) {
    // 1、查找chunk是否loaded/loading
    var installedChunkData = installedChunks[chunkId];
    if(installedChunkData === 0) {
    return new Promise(function(resolve) { resolve(); });
    }
    if(installedChunkData) {
    return installedChunkData[2];
    }
    // 2、未load的chunk用resolve,reject,promise暂时来表示chunk loading状态
    var promise = new Promise(function(resolve, reject) {
    installedChunkData = installedChunks[chunkId] = [resolve, reject];
    });
    installedChunkData[2] = promise;
    // 3、script标签加载chunk(chunk内容格式是webpackJsonp([chunckid],{moduleId: module的function}))
    var head = document.getElementsByTagName('head')[0];
    var script = document.createElement('script');
    script.type = 'text/javascript';
    script.charset = 'utf-8';
    script.async = true;
    script.timeout = 120000;
    script.src = __webpack_require__.p + "" + ({"0":"foo"}[chunkId]||chunkId) + ".bundle.js";
    // 4、异常处理
    head.appendChild(script);
    // 5、返回promise
    return promise;
    };
  • webpackJsonp代码解析

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules, executeModules) {
    var moduleId, chunkId, i = 0, resolves = [], result;
    // 1、包含chunk的ID, 获取chunk对应的resolve函数, 标记chunk loaded
    for(;i < chunkIds.length; i++) {
    chunkId = chunkIds[i];
    if(installedChunks[chunkId]) {
    resolves.push(installedChunks[chunkId][0]);
    }
    installedChunks[chunkId] = 0;
    }
    // 2、把chunk的module function设置到modules
    for(moduleId in moreModules) {
    if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
    modules[moduleId] = moreModules[moduleId];
    }
    }
    if(parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules, executeModules);
    // 3、resolve 后`__webpack_require__`执行modulefunction获取exports
    while(resolves.length) {
    resolves.shift()();
    }
    };
  • commonchunk里的异步加载

    1
    2
    3
    4
    // 加载chunk0, 加载后__webpack_require__去调chunk0里的module1
    __webpack_require__.e/* import() */(0).then(__webpack_require__.bind(null, 1)).then(foo => {
    console.log(foo.foo());
    })

常用plugin

  • compression-webpack-plugin 生成gz压缩文件
  • extract-text-webpack-plugin 抽取css到文件
  • html-webpack-plugin 往入口HTML塞标签
  • HotModuleReplacementPlugin 调试时热更新
  • DefinePlugin 环境变量定义

性能优化

  1. 代码逻辑:优秀的代码逻辑结构可以有效减少渲染页面使用的内存和速度(比如虚拟DOM),此方面不在本文讨论范围内。

  2. SSR服务器渲染,也就是所谓的“直出”。将首屏所有内容在服务器端渲染成html静态代码后,直接输出给浏览器,可以有效加快用户访问站点时首屏的加载时间。不过此方面也不在本文讨论范围内。

  3. 提升静态文件的加载速度,这是本文会讨论的点,而这方面大致又可分为下面几点:

    • 加快静态文件下载速度
    • 减少静态文件的文件大小
    • 减少静态文件请求数量,从而减少发起请求的次数(对于移动端页面来说,请求的开销比网速的开销要大)

优化细则

  1. 代码压缩
  2. 文件合并
    1. 合并js脚本文件
    2. 合并css样式文件
    3. 合并css引用的图片,使用sprite雪碧图。
  3. gzip压缩
    • 使用插件如:compression-webpack-plugin
  4. cdn/缓存
    • 如果没有CDN服务,我们可以添加Expires头,减少DNS查找,配置ETag,使AjaX可缓存。

http缓存优化

强制缓存与对比缓存

  • 强制缓存有就取,没有就向服务器拿
  • 对比缓存有就发缓存标记问服务器是否过期. 没过期返回304, 过期就返回新文件

    缓存涉及头参数:

  • Cache-Control:no-cache //协商缓存/对比缓存
  • Last-Modified(响应)/If-Modified-Since(再次请求带上)
  • Etag (响应)/ If-None-Match(再次请求带上)

    其他

  1. 减少http请求次数:CSS Sprites, JS、CSS源码压缩、图片大小控制合适;网页Gzip,CDN托管,data缓存 ,图片服务器。

  2. 前端模板 JS+数据,减少由于HTML标签导致的带宽浪费,前端用变量保存AJAX请求结果,每次操作本地变量,不用请求,减少请求次数

  3. 用innerHTML代替DOM操作,减少DOM操作次数,优化javascript性能。

  4. 当需要设置的样式很多时设置className而不是直接操作style。

  5. 少用全局变量、缓存DOM节点查找的结果。减少IO读取操作。

  6. 避免使用CSS Expression(css表达式)又称Dynamic properties(动态属性)。

  7. 图片预加载,将样式表放在顶部,将脚本放在底部 加上时间戳。

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

前端安全

XSS,CSRF(CSRF是利用了系统对页面浏览器的信任,XSS则利用了系统对用户的信任。)

  • XSS:跨站脚本攻击(cross site script)
    • 它允许用户将恶意代码植入到提供给其他用户使用的页面中,可以简单的理解为一种javascript代码注入。
  • XSS的防御措施:
    • 对用户输入的内容过滤转义
    • 避免使用eval、new Function等执行字符串的方法,除非确定字符串和用户输入无关
    • 使用cookie的httpOnly属性,加上了这个属性的cookie字段,js是无法进行读写的
  • CSRF:跨站请求伪造(Cross-site request forgery)
    其实就是网站中的一些提交行为,被黑客利用,黑客帮你发起未经你允许的请求
  • CSRF防御措施:
    • 检测http referer是否是同域名
    • 关键请求使用验证码或者token机制(每一个表单生成一个随机数秘钥token)
    • 避免登录的session长时间存储在客户端中
    • 其他的一些攻击方法还有HTTP劫持、界面操作劫持

CSP 参考

  • web前端对于xss安全漏洞一定不陌生。我们知道Javascript语句甚至是css表达式都可能导致xss攻击,
  • 开发者明确告诉客户端,哪些外部资源可以加载和执行

笔试题

1.任意异步任务同步执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function run(){
function pro(fn) {
return new Promise(res => {
fn(res)
})
}
let arr = [].slice.apply(arguments)
arr.forEach(async fn => {await pro(fn)})
}
run(next=>{
console.log(1);
setTimeout(next,1000)
},next=>{
console.log(2);
next()
},next=>{
console.log(3);
next()
})

2.数组去重

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 空间换时间
function unique2(array) {
var n = {},
r = [],
len = array.length,
val;
for (var i = 0; i < len; i++) {
val = array[i];
if (!n[val]) {
n[val] = true;
r.push(val);
}
}
return r;
}
1
2
3
4
5
6
7
8
9
10
// 用indexof,IE8不支持
function deduplicate(src){
var des = [];
src.forEach(function(v,i){
if(src.indexOf(v) === i) des.push(v)
})
return des;
}
// es6
const unique = a => Array.from(new Set(a))

3.深拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function clone(obj){
var buf;
if(obj instanceof Array){
buf =[];
var i = obj.length;
while(i--){
buf[i] = clone(obj[i])
}
return buf;
}else if(obj instanceof Object){
buf={}
for(var k in obj){
buf[k] = clone(obj[k]);
}
return buf;
}else{
return obj;
}
}

4.快速排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 时间复杂度平均/最好都是O(nlogn),最差O(n^2)可通过random避免, 空间复杂度O(log)复杂类型排序不稳定
function quickSort(arr, start, end) {
if (start >= end) return;
var pIndex = partition(arr, start, end);
quickSort(arr, start, pIndex - 1);
quickSort(arr, pIndex + 1, end);
}
function partition(arr, start, end) {
var pivot = arr[end];
var pIndex = start;
for (var i = start; i <= end - 1; i++) {
if (arr[i] <= pivot) {
swap(arr, i, pIndex);
pIndex++;
}
}
swap(arr, pIndex, end);
return pIndex;
}
function swap(arr, i, j) {
var temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
var a = [3,5,6,7,8,4,0,9,2,1];
quickSort(a,0,9)
console.log(a);

事件委托

  • 事件委托就是利用事件冒泡, 委托它们父级代为执行事件
  • 适合用事件委托的事件:click,mousedown,mouseup,keydown,keyup,keypress。

闭包应用场景原理

  • 闭包是javascript语言的一大特点,主要应用闭包场合主要是为了:设计私有的方法和变量
  1. 希望一个变量长期驻扎在内存中
  2. 避免全局变量的污染
  3. 私有成员的存在

前端路由的原理

  • 什么是路由?简单的说,路由是根据不同的 url 地址展示不同的内容或页面
  • 使用场景?前端路由更多用在单页应用上, 也就是SPA, 因为单页应用, 基本上都是前后端分离的, 后端自然也就不会给前端提供路由。
  • 前端的路由和后端的路由在实现技术上不一样,但是原理都是一样的。在 HTML5 的 history API 出现之前,前端的路由都是通过 hash 来实现的,hash 能兼容低版本的浏览器。
  • 两种实现前端路由的方式
    • HTML5 History两个新增的API:history.pushState 和 history.replaceState,两个 API 都会操作浏览器的历史记录,而不会引起页面的刷新。
    • Hash就是url 中看到 # ,我们需要一个根据监听哈希变化触发的事件( hashchange) 事件。我们用 window.location 处理哈希的改变时不会重新渲染页面,而是当作新页面加到历史记录中,这样我们跳转页面就可以在 hashchange 事件中注册 ajax 从而改变页面内容。
  • 优点
    从性能和用户体验的层面来比较的话,后端路由每次访问一个新页面的时候都要向服务器发送请求,然后服务器再响应请求,这个过程肯定会有延迟。而前端路由在访问一个新页面的时候仅仅是变换了一下路径而已,没有了网络延迟,对于用户体验来说会有相当大的提升。
    更多内容请看这里
  • 缺点
    使用浏览器的前进,后退键的时候会重新发送请求,没有合理地利用缓存。

浏览器内核渲染的原理?

  1. HTML被解析成DOM Tree,CSS被解析成CSS Rule Tree
  2. 把DOM Tree和CSS Rule Tree经过整合生成Render Tree(布局阶段)
  3. 元素按照算出来的规则,把元素放到它该出现的位置,通过显卡画到屏幕上

nd q&a

  • timeline profile用来调优过吗? 没有,用performance看动画掉帧的原因(idle总比高,script,render,paint用时少), 用memory看内存泄漏(对比内存快照, 看什么对象分配内容没被及时释放)
  • 写spa和多页面区别是什么,注意点

    • spa: i.交互体验好页面开始响应, 无整页刷新 ii.前后端分离 iii.seo ajax无法爬虫 iv. 要求高版本浏览器(摒弃IE) v.首页加载慢
    • mpa: i.seo 友好 ii.方案成熟 iii.前后端代码混合 iv.页面整体刷新 v.兼容老版本浏览器
  • 做过什么性能调优,页面优化

  • js设计模式有哪些
    • 工厂模式, 复杂工厂模式子类重写父类方法
    • 单例模式, 利用闭包缓存示例, 可选传入构造函数做参数
    • 代理模式, 给图片加载前做loading图的代理, 给计算做缓存的代理, 给事件回调做聚合的代理
    • 发布订阅模式pubsub, 事件监听/触发事件
    • 职责链模式中多了一点节点对象,可能在某一次请求过程中,大部分节点没有起到实质性作用,他们的作用只是让请求传递下去,从性能方面考虑,避免过长的职责链提高性能。
    • 不同场景的业务代码通过代码解耦, 形成通用写法就是设计模式
  • HTTP2了解不
    • HTTP 2.0 的所有帧都采用二进制编码,所有首部数据都会被压缩。
    • 所有通信都在一个 TCP 连接上完成。
    • HTTP 2.0 把HTTP协议通信的基本单位缩小为一个一个的帧,这些帧对应着逻辑流中的消息。相应地,很多流可以并行地在同一个TCP 连接上交换消息
    • HTTP/2 的多路复用(Multiplexing) 则允许同时通过单一的 HTTP/2 连接发起多重的请求-响应消息
    • 在binary framing中, HTTP/2 会将所有传输的信息分割为更小的消息和帧(frame),并对它们采用二进制格式的编码 ,其中 HTTP1.x 的首部信息会被封装到 HEADER frame,而相应的 Request Body 则封装到 DATA frame 里面。
    • 用hpack算法压缩头部
    • 服务端推送。同源可共享资源, 推送内容可缓存。
  • promise内部怎么实现
  • ES6哪些新特性,比ES5好在哪里
  • 怎么跨域的,cors是做什么的

    • jsonp: 由于同源策略的限制,XmlHttpRequest只允许请求当前源(域名、协议、端口)的资源,为了实现跨域请求,可以通过script标签实现跨域请求,然后在服务端输出JSON数据并执行回调函数,从而解决了跨域的数据请求。 优点是兼容性好,简单易用,支持浏览器与服务器双向通信。缺点是只支持GET请求。
    • 服务器端对于CORS的支持,主要就是通过设置Access-Control-Allow-Origin来进行的。如果浏览器检测到相应的设置,就可以允许Ajax进行跨域的访问。
    • window.name/postMessage
  • Webpack怎么规划按需加载

    • 在路由里面注册component的时候用es6的import语法去引入组件/ 或者AMD的require.ensure
      • const home = r => require.ensure([], () => r(require('../page/home/home')), 'home')
      • const Home = () => import(/* webpackChunkName: "Home" */ "@page/home/home")
  • 测试框架用过什么,写单元测试吗
    • 单元测试jasmine/jtest, 模拟网络请求sinon
    • 模拟输入数据比对输出结果, spy函数比对传参和返回结果, 时钟测异步, 模拟request,response,xmlhttp

summary

解决过问题:
1,decorator重复点击, 代理模式在ng-clicklink之前帮一个事件监听stopImmediatePropagation,定时截流
2,gulp多目标 通过项目参数, 在打包的时候引入项目的配置信息, 样式等.
修改gulp任务时src只有一个,dist确需要多个.而且任务要按顺序来,sprite生成less后才能执行less任务编译成CSS.
查资料和测试后用merge stream的方式来实现, gulp-rename做路径修改
3.三个密码用promise all
4.less和sprite相互依赖/return steam callback ,runsequence
5.调盾,人脸指纹,密码接口
6.1px边框用,伪元素做边框, transform scaleY(0.5)

常见问题

  • 设计模式举例, es6语法(看深浅), 工作流程/协作, webpack/gulp插件,一些思想如 面向对象/spa/dom

参考

基础

类型检测

  1. instanceof 在原型链上查找prototype是否存在
  • The instanceof operator tests the presence of constructor.prototype in object’s prototype chain.
  • 可以通过Object.setPrototypeOf, __proto__修改instanceof的测试结果
  1. typeof 检测基础类型
  • es6前不会报错, es6后会因为TDZ报referenceError
  1. Object.prototype.toString()
  • returns a string representing the object.
  • 自定义对象可以通过覆盖Object.prototype.toString修改返回结果
  • 通过call/apply次此方法可以判断对象类型

模块化

  1. 只需为 script 元素添加 type=module 属性,浏览器就会把该元素对应的内联脚本或外部脚本当成 ECMAScript 模块进行处理。
  2. 循环引用处理
  • commonjs是同步模块加载方式,加载后缓存
  • ES6中对模块引用只是保存一个模块的引用而已,因此,即使是循环引用也不会出错。
  • requirejs中出现循环引用时,可将引用的模块用局部require进行包裹以避免错误出现:
  1. es6模块导出的区别
  • export default不需要记住变量名或函数名, 为了快速使用, 核心常用模块default导出,
  • export default就是输出一个叫做default的变量或方法,然后系统允许你为它取任意名字