Mr. Panda
Tech For Fun

[Interview] 前端面试题集锦(7)

本文收集了一些常见的前端面试题,包括脚手架的配置过程、原型链原理、死锁的四个必要条件、还有什么方法能够实现Cookie的功能?、V-for key、require的机制、Vue中VDOM的原理?、vue不支持IE8原因、vue中 v-model原理等。

脚手架的配置过程

初始化项目及安装依赖库

  • 初始化项目
npm init
  • 安装依赖库

(1)运行依赖库

axios/vue/vue-router

(2)开发依赖库

样式相关:autoprefixer/css-loader/style-loader/sass-loader/node-sass/postcss-loader/extract-text-webpack-plugin

ES6语法编译:babel-core/babel-loader/babel-plugin-transform-runtime/babel-preset-es2015/babel-preset-latest/babel-preset-stage-2

ESLint语法检查:eslint/babel-eslint/eslint-config-standard/eslint-loader/eslint-plugin-html/eslint-plugin-promise/eslint-plugin-standard

图片加载引用优化:file-loader/image-webpack-loader/url-loader

Vue相关:vue-loader/vue-style-loader/vue-template-compiler

开发打包配置:webpack/webpack-dev-server

  • 配置开发调试和打包生产脚本
 ”scripts”: {
 “server”: “webpack-dev-server”,
 “build”: “set NODE_ENV=production&&webpack -p”
 } 

webpack配置及加载器配置

这是最关键的一步,webpack中配置了对项目各种资源和文件的处理,下面直接给出配置代码,关于webpack的配置和解析可以参考下面这两篇文章:

《Webpack 2.0 实用配置指南》

《Webpack教程及实用配置指南》

  • webpack配置:
  • babel编译配置(参考vue-cli生成项目)
 {
 “presets”: [
 [“latest”, {
 “es2015”: { “modules”: false }
 }],
 “stage-2”
 ],
 “plugins”: [“transform-runtime”],
 “comments”: false,
 “env”: {
 “test”: {
 “presets”: [“latest”, “stage-2”],
 “plugins”: [ “istanbul” ]
 }
 }
 } 
  • postcss配置:
 module.exports = {
 plugins: [
 require(‘autoprefixer’)({ /* …options */ })
 ]
 } 
  • eslint配置(参考vue-cli生成项目中的标准eslint配置模式):

项目入口搭建

  1. 首页入口
  2. 程序逻辑入口
  3. 根组件
  4. 测试子组件
  5. 全局样式文件

参考:手动搭建一个vue2.0项目工程框架;


原型链原理

ECMAScript中描述了原型链的概念,并将原型链作为实现继承的主要方法。其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。在JavaScript中,用 __proto__ 属性来表示一个对象的原型链。当查找一个对象的属性时,JavaScript 会向上遍历原型链,直到找到给定名称的属性为止

由于JavaScript规定,任何类都继承自Object类,所以"Person.prototype = new Object();//让Person类继承Object类"即使我们不写,我猜想JavaScript引擎也会自动帮我们加上这句话,或者是使用"Person.prototype = Object.prototype;"这种方式,让Person类去继承Object类。"Person.prototype = new Object();",其实这样就相当于Object对象是Person的一个原型,这样就相当于了把Object对象的属性和方法复制到了Person上了。


死锁的四个必要条件

什么是死锁:

死锁是指多个进程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进。

死锁的四个必要条件:

互斥条件:一个资源每次只能被一个进程使用,即在一段时间内某 资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。

请求与保持:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源 已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。

不可剥夺:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能 由获得该资源的进程自己来释放(只能是主动释放)。

循环等待: 若干进程间形成首尾相接循环等待资源的关系

这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。

死锁的避免与预防:

1. 死锁避免:

死锁避免的基本思想:系统对进程发出的每一个系统能够满足的资源申请进行动态检查,并根据检查结果决定是否分配资源,如果分配后系统可能发生死锁,则不予分配,否则予以分配,这是一种保证系统不进入死锁状态的动态策略。
如果操作系统能保证所有进程在有限时间内得到需要的全部资源,则系统处于安全状态否则系统是不安全的。

安全状态是指:如果系统存在 由所有的安全序列{P1,P2,…Pn},则系统处于安全状态。一个进程序列是安全的,如果对其中每一个进程Pi(i >=1 && i <= n)他以后尚需要的资源不超过系统当前剩余资源量与所有进程Pj(j < i)当前占有资源量之和,系统处于安全状态则不会发生死锁。

2. 死锁预防:

我们可以通过破坏死锁产生的4个必要条件来 预防死锁,由于资源互斥是资源使用的固有特性是无法改变的。

  1. 破坏“不可剥夺”条件:一个进程不能获得所需要的全部资源时便处于等待状态,等待期间他占有的资源将被隐式的释放重新加入到 系统的资源列表中,可以被其他的进程使用,而等待的进程只有重新获得自己原有的资源以及新申请的资源才可以重新启动,执行。
  2. 破坏”请求与保持条件“:第一种方法静态分配每个进程在开始执行时就申请他所需要的全部资源。第二种是动态分配即每个进程在申请所需要的资源时他本身不占用系统资源。
  3. 破坏“循环等待”条件:采用资源有序分配其基本思想是将系统中的所有资源顺序编号,将紧缺的,稀少的采用较大的编号,在申请资源时必须按照编号的顺序进行,一个进程只有获得较小编号的进程才能申请较大编号的进程。

参考:死锁的四个必要条件?如何避免与预防死锁?


还有什么方法能够实现Cookie的功能?

参考:限制cookie滥用,可以用localStorage替代大部分cookie功能


V-for key

vue和react的虚拟DOM的Diff算法大致相同,对操作前后的dom树同一层的节点进行对比,一层一层对比。

加了key(一定要具有唯一性) id的checkbox跟内容进行了一个关联,标识组件的唯一性,高效的更新虚拟DOM。

参考:VUE中演示v-for为什么要加key


require的机制

假设Y是路径,X是文件名或目录名,当 Nodejs 遇到 require(Y+X) 时,按照下面的顺序处理:

1、如果 X 是核心模块(例如:require("http"))

a.返回该模块

b.不再继续执行

2、如果Y是以“./”、“/”或“../”开头

a.把X当成文件,从指定路径开始,依次查找下面文件:X、X.js、X.json、X.node,只要其中一个存在,就返回该文件,不再继续执行

b.把X当成目录,从指定路径开始,依次查找下面文件:X/package.json(main字段)、X/index.js、X/index.json、X/index.node,只要其中一个存在,就返回该文件,不再继续执行

3.如果 X 不是核心模块,也没有以“./”、“/”或“../”开头,则Nodejs会从当前模块的父目录开始,尝试从它的 /node_module 目录里加载模块,如果还是没有找到,则移动到再上一层父目录,直到文件系统的根目录

4.抛出“not found”


Vue中VDOM的原理?

Vue生命周期图:

VDOM原理:

VDOM原理

参考:

Vue原理解析之Virtual Dom;

Vue实现virtual-dom的原理简析;


vue不支持IE8原因

官方文档这样解释:

当你把一个普通的 JavaScript 对象传给 Vue 实例的 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty() 把这些属性全部转为 getter/setter。Object.defineProperty() 是 ES5 中一个低级浏览器无法 shim 的特性,这也就是为什么 Vue 不支持 IE8 以及更低版本浏览器的原因。(Object.defineProperty()这个特性是无法使用低级浏览器中的方法来实现)

何为shim特性:

指把一个库引入另一个旧的浏览器,然后用旧的API,实现一些新的API功能。(shim可以将新的API引入到旧的环境中,而且仅靠就环境中已有的手段实现。)

Object.defineProperty()在IE8下只能对DOM对象使用, 如果对原生对象使用Object.defineProtry()会报错。

es5-shim可以让一些低级的浏览器支持最新的ecmascript5的一些特性。支持浏览器或node.js,已经测试的功能见:https://github.com/es-shims/es5-shim

什么是Object.defineProperty()?参考: 深入浅出 Object.defineProperty() - 简书

参考:Vue不兼容IE8原因以及Object.defineProperty详解


vue中 v-model原理

temp-or8

change事件和input事件的区别

input事件:

input事件在输入框输入的时候实时响应并触发。

change事件:

change事件在input失去焦点才会考虑触发,它的缺点是无法实时响应。与blur事件有着相似的功能,但与blur事件不同的是,change事件在输入框的值未改变时并不会触发,当输入框的值和上一次的值不同,并且输入框失去焦点,就会触发change事件。


Virtual DOM 真的比操作原生 DOM 快吗?

一、前言

网上都说操作真实dom怎么怎么慢,这儿有个例子:http://chrisharrington.github.io/demos/performance/,例子循环2000个随机数组,点击按钮重新生成随机数组渲染页面,也是自己用的js 操作dom 比用react 和angular 都要快。这是引用知乎上的一个问题。觉得写的很好。文章内容主要是来源于尤大大的回答。

二、原生dom操作VS通过框架封装操作

这是一个性能 vs. 可维护性的取舍。框架的意义在于为你掩盖底层的 DOM 操作,让你用更声明式的方式来描述你的目的,从而让你的代码更容易维护。没有任何框架可以比纯手动的优化 DOM 操作更快,因为框架的 DOM 操作层需要应对任何上层 API 可能产生的操作,它的实现必须是普适的。针对任何一个 benchmark,我都可以写出比任何框架更快的手动优化,但是那有什么意义呢?在构建一个实际应用的时候,你难道为每一个地方都去做手动优化吗?出于可维护性的考虑,这显然不可能。框架给你的保证是,你在不需要手动优化的情况下,我依然可以给你提供过得去的性能。

三、 对 React 的 Virtual DOM 的误解

React 从来没有说过 “React 比原生操作 DOM 快”。React 的基本思维模式是每次有变动就整个重新渲染整个应用。如果没有 Virtual DOM,简单来想就是直接重置 innerHTML。很多人都没有意识到,在一个大型列表所有数据都变了的情况下,重置 innerHTML 其实是一个还算合理的操作... 真正的问题是在 “全部重新渲染” 的思维模式下,即使只有一行数据变了,它也需要重置整个 innerHTML,这时候显然就有大量的浪费。

我们可以比较一下 innerHTML vs. Virtual DOM 的重绘性能消耗:

(1)innerHTML: render html string O(template size) + 重新创建所有 DOM 元素 O(DOM size)

(2)Virtual DOM: render Virtual DOM + diff O(template size) + 必要的 DOM 更新 O(DOM change)

Virtual DOM render + diff 显然比渲染 html 字符串要慢,但是!它依然是纯 js 层面的计算,比起后面的 DOM 操作来说,依然便宜了太多。可以看到,innerHTML 的总计算量不管是 js 计算还是 DOM 操作都是和整个界面的大小相关,但 Virtual DOM 的计算量里面,只有 js 计算和界面大小相关,DOM 操作是和数据的变动量相关的。前面说了,和 DOM 操作比起来,js 计算是极其便宜的。这才是为什么要有 Virtual DOM:它保证了 1)不管你的数据变化多少,每次重绘的性能都可以接受;2) 你依然可以用类似 innerHTML 的思路去写你的应用。

四、 MVVM vs. Virtual DOM

相比起 React,其他 MVVM 系框架比如 Angular, Knockout 以及 Vue、Avalon 采用的都是数据绑定:通过 Directive/Binding 对象,观察数据变化并保留对实际 DOM 元素的引用,当有数据变化时进行对应的操作。MVVM 的变化检查是数据层面的,而 React 的检查是 DOM 结构层面的。MVVM 的性能也根据变动检测的实现原理有所不同:Angular 的脏检查使得任何变动都有固定的
O(watcher count) 的代价;Knockout/Vue/Avalon 都采用了依赖收集,在 js 和 DOM 层面都是 O(change):

(1)脏检查:scope digest O(watcher count) + 必要 DOM 更新 O(DOM change)

(2)依赖收集:重新收集依赖 O(data change) + 必要 DOM 更新 O(DOM change)可以看到,Angular 最不效率的地方在于任何小变动都有的和 watcher 数量相关的性能代价。但是!当所有数据都变了的时候,Angular 其实并不吃亏。依赖收集在初始化和数据变化的时候都需要重新收集依赖,这个代价在小量更新的时候几乎可以忽略,但在数据量庞大的时候也会产生一定的消耗。

MVVM 渲染列表的时候,由于每一行都有自己的数据作用域,所以通常都是每一行有一个对应的 ViewModel 实例,或者是一个稍微轻量一些的利用原型继承的 "scope" 对象,但也有一定的代价。所以,MVVM 列表渲染的初始化几乎一定比 React 慢,因为创建 ViewModel / scope 实例比起 Virtual DOM 来说要昂贵很多。这里所有 MVVM 实现的一个共同问题就是在列表渲染的数据源变动时,尤其是当数据是全新的对象时,如何有效地复用已经创建的 ViewModel 实例和 DOM 元素。假如没有任何复用方面的优化,由于数据是 “全新” 的,MVVM 实际上需要销毁之前的所有实例,重新创建所有实例,最后再进行一次渲染!这就是为什么题目里链接的 angular/knockout 实现都相对比较慢。相比之下,React 的变动检查由于是 DOM 结构层面的,即使是全新的数据,只要最后渲染结果没变,那么就不需要做无用功。

Angular 和 Vue 都提供了列表重绘的优化机制,也就是 “提示” 框架如何有效地复用实例和 DOM 元素。比如数据库里的同一个对象,在两次前端 API 调用里面会成为不同的对象,但是它们依然有一样的 uid。这时候你就可以提示 track by uid 来让 Angular 知道,这两个对象其实是同一份数据。那么原来这份数据对应的实例和 DOM 元素都可以复用,只需要更新变动了的部分。或者,你也可以直接 track by index来进行“原地复用”:直接根据在数组里的位置进行复用。在题目给出的例子里,如果angular实现加上trackby<?XML:NAMESPACE PREFIX = "[default] http://www.w3.org/1998/Math/MathML" NS = "http://www.w3.org/1998/Math/MathML" />index来进行“原地复用”:直接根据在数组里的位置进行复用。在题目给出的例子里,如果angular实现加上trackbyindex 的话,后续重绘是不会比 React 慢多少的。甚至在 dbmonster 测试中,Angular 和 Vue 用了 track by $index 以后都比 React 快: dbmon (注意 Angular 默认版本无优化,优化过的在下面)

顺道说一句,React 渲染列表的时候也需要提供 key 这个特殊 prop,本质上和 track-by 是一回事。

五、性能比较也要看场合

在比较性能的时候,要分清楚初始渲染、小量数据更新、大量数据更新这些不同的场合。Virtual DOM、脏检查 MVVM、数据收集 MVVM 在不同场合各有不同的表现和不同的优化需求。Virtual DOM 为了提升小量数据更新时的性能,也需要针对性的优化,比如 shouldComponentUpdate 或是 immutable data。

(1)初始渲染:Virtual DOM > 脏检查 >= 依赖收集

(2)小量数据更新:依赖收集 >> Virtual DOM + 优化 > 脏检查(无法优化) > Virtual DOM 无优化

(3)大量数据更新:脏检查 + 优化 >= 依赖收集 + 优化 > Virtual DOM(无法/无需优化)>> MVVM 无优化

不要天真地以为 Virtual DOM 就是快,diff 不是免费的,batching 么 MVVM 也能做,而且最终 patch 的时候还不是要用原生 API。在我看来 Virtual DOM 真正的价值从来都不是性能,而是它

(1) 为函数式的 UI 编程方式打开了大门;(2) 可以渲染到 DOM 以外的 backend,比如 ReactNative。

注:react本身遵循的就是 UI = fn(state) 这样的一个公式,这里的fn 就是函数,通过state去触发fn(在这个过程是有很多复杂的计算操作,比如Virtual DOM对比),最后导致UI的更新,不知道我理解的对不对。

六、总结

以上这些比较,更多的是对于框架开发研究者提供一些参考。主流的框架 + 合理的优化,足以应对绝大部分应用的性能需求。如果是对性能有极致需求的特殊情况,其实应该牺牲一些可维护性采取手动优化:比如 Atom 编辑器在文件渲染的实现上放弃了 React 而采用了自己实现的 tile-based rendering;又比如在移动端需要 DOM-pooling 的虚拟滚动,不需要考虑顺序变化,可以绕过框架的内置实现自己搞一个。

七、参考

尤大大的回复:https://www.zhihu.com/question/31809713/answer/53544875


VUE渲染模板的原理?

:本质字符串。

与字符串的区别

  • 有逻辑(vue中有v-if、v-for)
  • 嵌入js变量({{变量}})
  • 需要转化为html,页面才会识别并正确解析。
  • 1、 模板解析成render函数---->返回JS模拟的虚拟DOM结构:模板是一段字符串,模板解析生成render函数,执行render函数返回为vnode,vnode表明了各个节点的层级关系、特性、样式、绑定的事件。
    2、 vnode---->html:通过 updateComponent函数调用vm._update()传入vnode,利用基于snabbdom的patch()方法改造的生成真实DOM节点并渲染页面。
    注:vm.__patch__:初次渲染时,调用vm.__patch__(containe, vnode),生成真实的DOM结构渲染到容器中。re-render时,调用vm.__patch__(vnode, newVnode)利用diff算法对比新旧vnode之间的差异,生成需要更新的真实DOM,渲染到容器的对应位置。   参考: Vue中模板渲染原理; 13.java三大框架SSM是哪三个,怎么用?   参考:SSM三大框架的运行流程、原理、核心技术详解!; 14.跨站脚本攻击(XSS)和跨站请求伪造(CSRF)   参考: xss 跨站脚本攻击跨站请求伪造攻击的基本原理与防范(转载)
jonsam ng

jonsam ng

文章作者

海阔凭鱼跃,天高任鸟飞。

[Interview] 前端面试题集锦(7)
本文收集了一些常见的前端面试题,包括脚手架的配置过程、原型链原理、死锁的四个必要条件、还有什么方法能够实现Cookie的功能?、V-for key、require的机制、Vue中VDOM的原理?、vue不支…
扫描二维码继续阅读
2019-10-25