前端基础
- 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理解
案例:
- Vue 应用性能优化指南
质量保障
- Vue 应用性能优化指南
eslint、tslint 如何跟开发流程集成
- 单元测试覆盖率
- 如何面对需求变更带来的测试用例失效
- 前端灰度方案
- 如何排查内存泄漏
Jest/jasmine puppeteer/nightmare
其他
期望:偏技术或偏管理
- 觉得一个前端专家应具备的技能和素质:综合、系统能力,需要理解- 系统和框架的原理,对前端前沿技术有所关注。
- 前沿技术的了解
- 对前端未来走向的判断
- 对领域设计的理解
遇到过的问题
- v-for在checker上引起的不更新
基础
- 技术面试必备基础知识
- JavaScript开发者应懂的33个概念
- 前端面试之路一(HTML+CSS面试整理)
- 前端面试之路二(javaScript基础整理)
- 前端面试之路三(javaScript高级篇)
- 前端面试之路四(web性能优化篇)
- 前端面试之路五(网络HTTP篇)
- 前端面试之路六(Javascript设计模式篇)
- 6, 11, 12, 23
笔记
http/https/websocket
http
特点: 无状态,被动型.
长轮询:等服务端响应,没等到就响应空数据,浏览器再发请求.
短轮询:服务端直接响应,没数据,客户端等会儿在发请求. 就是服务端还是客户端等一会儿的区别.
短轮询是每次http请求前都要建立连接,而长轮询是相邻几次请求前都要建立连接
http1.1支持长连接,由connection: keep-alive标识.长连接相邻几次请求只建立一次连接
https
http和TCP中间夹层SSL, 加证书验证身份.
但是公开密钥加密与共享密钥加密相比,其处理速度要慢. 非对称传共享秘钥, 后面就用共享秘钥加密内容.
https通信步骤:
客户端通过发送 Client Hello 报文开始 SSL 通信。报文中包含客户端支持的 SSL 的指定版本、加密组件(Cipher Suite)列表(所使用的加密算法及密钥长度等)
服务器可进行 SSL 通信时,会以 Server Hello 报文作为应答。和客户端一样,在报文中包含 SSL 版本以及加密组件。服务器的加密组件内容是从接收 到的客户端加密组件内筛选出来的。
之后服务器发送 Certificate 报文。报文中包含公开密钥证书。
- 最后服务器发送 Server Hello Done 报文通知客户端,最初阶段的 SSL 握手协商部分结束。
- SSL 第一次握手结束之后,客户端以 Client Key Exchange 报文作为回应。报文中包含通信加密中使用的一种被称为 Pre-master secret 的随机密码串。该 报文已用步骤#3 中的公开密钥进行加密。
- 接着客户端继续发送 Change Cipher Spec 报文。该报文会提示服务器,在此报文之后的通信会采用 Pre-master secret 密钥加密。
- 客户端发送 Finished 报文。该报文包含连接至今全部报文的整体校验值。这次握手协商是否能够成功,要以服务器是否能够正确解密该报文作为判定标准。
- 服务器同样发送 Change Cipher Spec 报文。
服务器同样发送 Finished 报文。 - 服务器和客户端的 Finished 报文交换完毕之后,SSL 连接就算建立完成。当然,通信会受到 SSL 的保护。从此处开始进行应用层协议的通信,即发 送 HTTP 请求。
应用层协议通信,即发送 HTTP 响应。
最后由客户端断开连接。断开连接时,发送 close_notify 报文。
js
es6
- arrow function
this 是lexical binding非 dynamic, 通常就是绑定外层function的this. 也就不要做self=this这种hack.
适合代码少的function, 适合给内层function需要外层this/argument/super的function. - spread + rest 看起来更简洁
- 函数默认参数, 不用再xx||default了
- 参数析构, 看起来更简洁
- 对象字面量增强 书写代码更简洁
- 模板字符串 书写代码更简洁,不要豆豆加加
- Let + Const blockscoped
- for..of 迭代器遍历
- 异步实现generator/Promises 被es7 await+async取代
- 模块化export/import, 统一了模块化的标准
- Map + Set + WeakMap + WeakSet 丰富了集合数据结构
- meta-data symbols, proxy/reflect
Math + Number + String + Object APIs isNaN, array.from, array.entries/keys/values等iterator. object.assign, string.includes/repeat
- 1234567891011121314151617181920Number.EPSILONNumber.isInteger(Infinity) // falseNumber.isNaN("NaN") // falseMath.acosh(3) // 1.762747174039086Math.hypot(3, 4) // 5Math.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 ArrayArray.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) })
class语法糖
- 1234567891011121314151617class 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继承的实现方式
- 原型链继承Cat.prototype = new Animal();
- 缺点1. 来自原型对象的引用属性是所有实例共享的
- 缺点2. 创建子类实例时,无法向父类构造函数传参
- 构造继承 原型链没连上
- 实例继承 实例是父类实例
- 拷贝继承 效率低
- 组合继承
- 寄生组合继承 解决调用了两次父类构造函数
单元测试sinon/jasmine
模拟输入数据比对输出结果, spy函数比对传参和返回结果, 时钟测异步, 模拟request,response,xmlhttp
css
css 常规布局/栅格 /左右两栏定宽,中间自适应的实现方
一个高度100px,另一个填满剩下的高度。
外层box-sizing: border-box; 同时设置padding: 100px 0 0;
内层100像素高的元素向上移动100像素,或使用absolute定位防止占据空间;
另一个元素直接height: 100%;123456789101112131415161718192021222324252627282930313233html,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;}absolute positioning
外层position: relative;
百分百自适应元素直接position: absolute; top: 100px; bottom: 0; left: 012345678910111213141516171819.outer {height: 100%;position: relative;}.A {height: 100px;background: #BBE8F2;}.B {background: #D9C666;width: 100%;position: absolute;top: 100px;left: 0;bottom: 0;}flex
1234567891011121314151617.outer {width: 200px;height: 300px;background: red;display: flex;flex-direction: column;}.A {height: 100px;background: green;}.B {background: blue;flex: 1}
品字布局
固定宽高布局
|
|
|
|
满屏布局
|
|
框架 TODO
vue 轻量/相关技术栈齐全/有饿了么和阿里大公司使用和支持
- 父组件和子组件、子组件和子组件如何传递数据 props down emit up
兄弟组件要么用一个Vue对象做通信bus, 要么vuex - dom更新机制
- vue初始化时通过Object.defineProperty给data设置setter/getter函数,用来实现reactivity以及依赖收集. get会往闭包内的dep添加watcher, set会调dep.notify遍历watcher
- reativity通过nexttick触发diff算法进行局部DOM更新
- reactivity
- 其实「依赖收集」的过程就是把 Watcher 实例存放到对应的 Dep 对象中去。get 方法可以让当前的 Watcher 对象(Dep.target)存放到它的 subs 中(addSub)方法,在数据变化时,set 会调用 Dep 对象的 notify 方法通知它内部所有的 Watcher 对象进行视图更新。
- vuex. install方法里给Vue混入初始化方法. 初始化获取store到data上. commit方法遍历执行mutation, dispatch方法用promise执行action.
- compile
- parse 用正则遍历template形成ast, 遍历获取标签,属性,文本, 指令,层级关系等
- optimize 标记静态节点(没有v-if/v-for属性的文本节点type===3),在patch比对的时候可以直接跳过来优化
- generate 将 AST 转化成 render funtion 字符串
- diff
- 调patch方法比vnode
- 只有当 key、 tag、 isComment就是sameVnode, 不是就替换realDom
- 调用patchVnode ,通过静态节点, 两个都有子虚节点比对更新childVnode
- 优化: 尽量不要跨层级的修改dom, 设置key可以最大化的重复利用节点
- 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只会添加进队列一次12345this.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 - 代码如下: 12345678910111213141516171819202122232425262728Scope.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 snippetdo {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);
- 往$$asyncQueue加任务, 执行规则: 当前是digest phase就在
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
12345678910111213141516171819202122232425262728Deferred.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'异步'执行processQueuefunction 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
数组里123456789Promise.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回调传参
123456callback(xhr.status,response,xhr.getAllResponseHeaders(),statusText)$http的intercepter通过promise实现request和response的分层处理.
- 拦截器有四个属性request, requestError, responce, responceError(对应四个阶段)
- 这边在request里加请求头字段, response/responseError里统一处理异常信息
directive
- controllers只是指令系统的一部分
双向绑定
=
属性绑定, parentValue和上次不一样覆盖子属性, 一样子覆盖父属性123456789101112131415161718192021222324252627case '=':if (definition.optional && !attrs[attrName]) {break;}parentGetParseFun = $parse(attrs[attrName]);var lastValue = destination[scopeName] = parentGetParseFun(scope); // right now scope is emptyvar 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插件原理
- webpack插件就是一个function, function的prototype上定义了apply方法.
webpack调用该方法注入compiler对象. complier(包含所有配置信息)通过Tapable的plugin方法注册编译事件回调. 事件回调执行时传入compilation对象 - compilation继承compiler, 在开发环境中compiler编译配置文件并返回compilation,compilation是每个资源发生变化后重新加载模块编译到bundle里
- compiler和compilation的事件钩子
compilation
生成compilation对象,emit
在将内存中assets写到硬盘前,make
分析依赖,build模块optimize
优化编译,optimize-chunks
获取模块依赖,loader等123456Plugin.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的实现
123function (module, exports, __webpack_require__) {/* 模块的代码 */}webpack_require的实现和commonjs的require实现一致
- IIFE首先定义了installedModules ,这个变量被用来缓存已加载的模块。
- 定义了`webpack_require 这个函数,函数参数为模块的id。这个函数用来实现模块的require。
__webpack_require__
函数首先会检查是否缓存了已加载的模块,如果有则直接返回缓存模块的exports。- 如果没有缓存,也就是第一次加载,则首先初始化模块,并将模块进行缓存。
- 然后调用模块函数,也就是前面webpack对我们的模块的包装函数,将module、module.exports和
__webpack_require__
作为参数传入。注意这里做了一个动态绑定,将模块函数的调用对象绑定为module.exports,这是为了保证在模块中的this指向当前模块。modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
- 调用完成后,模块标记为已加载。
- 返回模块exports的内容。
- 利用前面定义的
__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
代码解析12345678910111213141516171819202122232425262728__webpack_require__.e = function requireEnsure(chunkId) {// 1、查找chunk是否loaded/loadingvar 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、返回promisereturn promise;};webpackJsonp代码解析
12345678910111213141516171819202122window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules, executeModules) {var moduleId, chunkId, i = 0, resolves = [], result;// 1、包含chunk的ID, 获取chunk对应的resolve函数, 标记chunk loadedfor(;i < chunkIds.length; i++) {chunkId = chunkIds[i];if(installedChunks[chunkId]) {resolves.push(installedChunks[chunkId][0]);}installedChunks[chunkId] = 0;}// 2、把chunk的module function设置到modulesfor(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获取exportswhile(resolves.length) {resolves.shift()();}};commonchunk里的异步加载
1234// 加载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 环境变量定义
性能优化
代码逻辑:优秀的代码逻辑结构可以有效减少渲染页面使用的内存和速度(比如虚拟DOM),此方面不在本文讨论范围内。
SSR服务器渲染,也就是所谓的“直出”。将首屏所有内容在服务器端渲染成html静态代码后,直接输出给浏览器,可以有效加快用户访问站点时首屏的加载时间。不过此方面也不在本文讨论范围内。
提升静态文件的加载速度,这是本文会讨论的点,而这方面大致又可分为下面几点:
- 加快静态文件下载速度
- 减少静态文件的文件大小
- 减少静态文件请求数量,从而减少发起请求的次数(对于移动端页面来说,请求的开销比网速的开销要大)
优化细则
- 代码压缩
- 文件合并
- 合并js脚本文件
- 合并css样式文件
- 合并css引用的图片,使用sprite雪碧图。
- gzip压缩
- 使用插件如:
compression-webpack-plugin
- 使用插件如:
- cdn/缓存
- 如果没有CDN服务,我们可以添加Expires头,减少DNS查找,配置ETag,使AjaX可缓存。
http缓存优化
强制缓存与对比缓存
- 强制缓存有就取,没有就向服务器拿
- 对比缓存有就发缓存标记问服务器是否过期. 没过期返回304, 过期就返回新文件
缓存涉及头参数:
- Cache-Control:no-cache //协商缓存/对比缓存
- Last-Modified(响应)/If-Modified-Since(再次请求带上)
- Etag (响应)/ If-None-Match(再次请求带上)
其他
减少http请求次数:CSS Sprites, JS、CSS源码压缩、图片大小控制合适;网页Gzip,CDN托管,data缓存 ,图片服务器。
前端模板 JS+数据,减少由于HTML标签导致的带宽浪费,前端用变量保存AJAX请求结果,每次操作本地变量,不用请求,减少请求次数
用innerHTML代替DOM操作,减少DOM操作次数,优化javascript性能。
当需要设置的样式很多时设置className而不是直接操作style。
少用全局变量、缓存DOM节点查找的结果。减少IO读取操作。
避免使用CSS Expression(css表达式)又称Dynamic properties(动态属性)。
图片预加载,将样式表放在顶部,将脚本放在底部 加上时间戳。
避免在页面的主体布局中使用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.任意异步任务同步执行
|
|
2.数组去重
|
|
|
|
3.深拷贝
|
|
4.快速排序
|
|
事件委托
- 事件委托就是利用事件冒泡, 委托它们父级代为执行事件
- 适合用事件委托的事件:click,mousedown,mouseup,keydown,keyup,keypress。
闭包应用场景原理
- 闭包是javascript语言的一大特点,主要应用闭包场合主要是为了:设计私有的方法和变量
- 希望一个变量长期驻扎在内存中
- 避免全局变量的污染
- 私有成员的存在
前端路由的原理
- 什么是路由?简单的说,路由是根据不同的 url 地址展示不同的内容或页面
- 使用场景?前端路由更多用在单页应用上, 也就是SPA, 因为单页应用, 基本上都是前后端分离的, 后端自然也就不会给前端提供路由。
- 前端的路由和后端的路由在实现技术上不一样,但是原理都是一样的。在 HTML5 的 history API 出现之前,前端的路由都是通过 hash 来实现的,hash 能兼容低版本的浏览器。
- 两种实现前端路由的方式
- HTML5 History两个新增的API:history.pushState 和 history.replaceState,两个 API 都会操作浏览器的历史记录,而不会引起页面的刷新。
- Hash就是url 中看到 # ,我们需要一个根据监听哈希变化触发的事件( hashchange) 事件。我们用 window.location 处理哈希的改变时不会重新渲染页面,而是当作新页面加到历史记录中,这样我们跳转页面就可以在 hashchange 事件中注册 ajax 从而改变页面内容。
- 优点
从性能和用户体验的层面来比较的话,后端路由每次访问一个新页面的时候都要向服务器发送请求,然后服务器再响应请求,这个过程肯定会有延迟。而前端路由在访问一个新页面的时候仅仅是变换了一下路径而已,没有了网络延迟,对于用户体验来说会有相当大的提升。
更多内容请看这里 - 缺点
使用浏览器的前进,后退键的时候会重新发送请求,没有合理地利用缓存。
浏览器内核渲染的原理?
- HTML被解析成DOM Tree,CSS被解析成CSS Rule Tree
- 把DOM Tree和CSS Rule Tree经过整合生成Render Tree(布局阶段)
- 元素按照算出来的规则,把元素放到它该出现的位置,通过显卡画到屏幕上
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")
- 在路由里面注册component的时候用es6的import语法去引入组件/ 或者AMD的require.ensure
- 测试框架用过什么,写单元测试吗
- 单元测试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
参考
基础
类型检测
- instanceof 在原型链上查找prototype是否存在
- The instanceof operator tests the presence of constructor.prototype in object’s prototype chain.
- 可以通过
Object.setPrototypeOf
,__proto__
修改instanceof的测试结果
- typeof 检测基础类型
- es6前不会报错, es6后会因为TDZ报referenceError
- Object.prototype.toString()
- returns a string representing the object.
- 自定义对象可以通过覆盖
Object.prototype.toString
修改返回结果 - 通过call/apply次此方法可以判断对象类型
模块化
- 只需为 script 元素添加 type=module 属性,浏览器就会把该元素对应的内联脚本或外部脚本当成 ECMAScript 模块进行处理。
- 循环引用处理
- commonjs是同步模块加载方式,加载后缓存
- ES6中对模块引用只是保存一个模块的引用而已,因此,即使是循环引用也不会出错。
- requirejs中出现循环引用时,可将引用的模块用局部require进行包裹以避免错误出现:
- es6模块导出的区别
- export default不需要记住变量名或函数名, 为了快速使用, 核心常用模块default导出,
- export default就是输出一个叫做default的变量或方法,然后系统允许你为它取任意名字