# 很久很久以前的面试面试复习二
# ES6
问题一:ES6模块化如何使用,开发环境如何打包?
- 基本语法为:import export(注意有无default)
- 可以用 webpack 或者 rollup 等打包工具打包
- 需要配置安装多个 babel ,以及 .babelrc 文件
问题二: Class 和普通构造函数的区别?
- class 在语法上更加贴合面向对象的写法
- class 实现继承更加易读、易理解
- 更易于写后 java 等后端语言的开发者快速入门使用
- 本质还是语法糖,使用 prototype
问题三:Promise 的基本使用和原理
new Promise 实例,而且要 return
new Promise 时要传入参数,函数有 resolve reject 两个参数
成功时执行 resolve() 失败时执行 reject()
then 监听结果
promise里面的then函数仅仅是注册了后续需要执行的代码,真正的执行是在resolve方法里面执行的
通过Promise.prototype.then和Promise.prototype.catch方法将观察者方法注册到被观察者Promise对象中,同时返回一个新的Promise对象,以便可以链式调用
被观察者管理内部pending、fulfilled和rejected的状态转变,同时通过构造函数中传递的resolve和reject方法以主动触发状态转变和通知观察者。
问题四: 总结一下 ES6 的其它常用功能
- let/const
- 多行字符串/模板变量
- 解构赋值
- 块级作用域
- 函数默认参数
- 箭头函数
- 拓展运算符
- Symbol类型
- Iterator 遍历器
- Generator 函数
- Async 函数
# 原型高级
问题一:说一说原型的实际应用
- 描述一下 jquery 如何使用原型
- 描述一下 zepto 如何使用原型
- instanceof 原理
- 再结合自己的项目经验,说一个自己的开发例子
zepto 源码:
(function (window) {
//空对象
var zepto = {}
//构造函数
function Z(dom, selector) {
var i, len = dom ? dom.length : 0
for (i = 0; i < len; i++) {
this[i] = dom[i]
}
this.length = len
this.selector = selector || ''
}
zepto.Z = function (dom, selector) {
//注意new,返回一个实例
return new Z(dom, selector)
}
//初始化函数,源码中更复杂
zepto.init = function (selector) {
var slice = Array.prototype.slice
var dom = slice.call(document.querySelectorAll(selector))
return zepto.Z(dom, selector)
}
//即zepto的$
var $ = function (selector) {
return zepto.init(selector)
}
window.$ = $
$.fn = {
css: function (key, value) {
alert('css')
},
html: function (value) {
return '这是一个模拟的 html 函数'
}
}
//定义原型
Z.prototype = $.fn
})(window)
//调用
var $p = $('p')
$p.css('font-size', '40px') //css()与html()是原型方法
alert($p.html())
var $div1 = $('#div1')
$div1.css('color', 'blue')
alert($div1.html())
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
jquery 源码:
(function (window) {
//注意,第一步就找到了构造函数
var jQuery = function (selector) {
return new jQuery.fn.init(selector)
}
//初始化实例方法
jQuery.fn = {
css: function (key, value) {
alert('css')
},
html: function (value) {
return 'html'
}
}
//定义构造函数
var init = jQuery.fn.init = function (selector) {
var slice = Array.prototype.slice
var dom = slice.call(document.querySelectorAll(selector))
var i, len = dom ? dom.length : 0
for (i = 0; i < len; i++) {
this[i] = dom[i]
}
this.length = len
this.selector = selector || ''
}
//定义原型
init.prototype = jQuery.fn
window.$ = jQuery
})(window)
//调用
var $p = $('p')
$p.css('font-size', '40px')
alert($p.html())
var $div1 = $('#div1')
$div1.css('color', 'blue')
alert($div1.html())
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
34
35
36
37
38
39
40
41
为什么要把原型方法放在 $.fn 或者 Jquery.fn?
因为要扩展插件(本质就是扩展原型方法):
$.fn.getNodeName = function(){
return this[0].nodeName;
}
2
3
好处:
- 只有 $ 会暴露在 window 全局变量,限制暴露对象,避免全局污染
- 将插件扩展统一到 $.fn.xxx 这个接口,方便使用且统一
问题二:原型如何体现它的扩展性
- 说一下 jquery 和 zepto 的插件机制
- 封装对象方法
- 封装全局函数
- 选择器插件
- 结合自己的开发经验,做过的基于原型的插件
- jQuery插件的机制很简单,就是利用jQuery提供的jQuery.fn.extend()和jQuery.extend()方法,扩展jQuery的功能。
- jQuery.fn.extend()多用于扩展上面提到的3种类型中的第一种,jQuery.extend()用于扩展后两种插件。这两个方法都接受一个参数,类型为Object。Object对象的“名/值对”分别代表“函数或方法名/函数主体”。
# 异步
问题一:什么是单线程,和异步有什么关系?
- 单线程:只有一个线程,只能做一件事情
- 原因:避免 DOM 渲染的冲突。
- 浏览器需要渲染 dom
- js 可以修改 dom 的结构
- js 执行的时候,浏览器 dom 渲染就会暂停
- 两段 js 也不能同时执行(都修改 dom 就冲突了)
- H5 中的 webworker 支持多线程,但是不能访问 dom
- 解决方案就是异步
- 实现方式就是 even-loop
问题二:什么是 even-loop
- 事件轮询,js 实现异步的具体解决方案
- 同步代码,直接执行
- 异步函数先放在异步队列中
- 待同步函数执行完毕,轮询执行异步队列的函数
问题三:是否用过 jquery 的 Deferred?
- jquery 1.5的变化
- 无法改变 js 异步和单线程的本质
- 只能从写法上杜绝 callback 这种形式
- 它是一种语法糖形式,但是解耦了
- 很好的体现了:开放封闭原则
- 使用 Jquery Deferred
- 总结,dtd 的 API 可以分成两类,用意不同
- 第一类:dtd.resolve dtd.reject
- 第二类:dtd.then dtd.done dtd.fail
- 这两类应该分开,否则后果很严重
- 初步引入 Promise 概念
- then() 方法返回一个 Promise 。它最多需要有两个参数:Promise 的成功和失败情况的回调函数,而它的行为与then中的回调函数的返回值有关:
- 如果then中的回调函数返回一个值,那么then返回的Promise将会成为接受状态,并且将返回的值作为接受状态的回调函数的参数值。
- 如果then中的回调函数抛出一个错误,那么then返回的Promise将会成为拒绝状态,并且将抛出的错误作为拒绝状态的回调函数的参数值。
- 如果then中的回调函数返回一个已经是接受状态的Promise,那么then返回的Promise也会成为接受状态,并且将那个Promise的接受状态的回调函数的参数值作为该被返回的Promise的接受状态回调函数的参数值。
- 如果then中的回调函数返回一个已经是拒绝状态的Promise,那么then返回的Promise也会成为拒绝状态,并且将那个Promise的拒绝状态的回调函数的参数值作为该被返回的Promise的拒绝状态回调函数的参数值。
- 如果then中的回调函数返回一个未定状态(pending)的Promise,那么then返回Promise的状态也是未定的,并且它的终态与那个Promise的终态相同;同时,它变为终态时调用的回调函数参数与那个Promise变为终态时的回调函数的参数是相同的。
问题四:Promise 的基本使用和原理
- 基本语法回顾
- 异常捕获
- 多个串联
- Promise.all 和 promise.race
- Promise 标准
- 三种状态:pending fulfilled reject
- 初始状态是 pending
- pending 变为 fulfilled,或者 pending 变为 reject
- 状态是不可逆的
- promise 实例必须实现 then 这个方法
- then() 必须可以接收两个函数作为参数
- then() 返回的必须是一个 promise 实例(对象)
//基本使用
function loadImg(src) {
var promise = new Promise(function (resolve, reject) {
var img = document.createElement('img')
//throw new Error("图片加载失败")//模仿语法报错
img.onload = function () {
resolve(img)
}
img.onerror = function () {
reject('图片加载失败')
}
img.src = src
})
return promise
}
var src = 'https://www.imooc.com/static/img/index/logo_new.png'
var result = loadImg(src)
result.then(function (img) {
console.log(1, img.width)
return img // 非promise对象会自动包装成一个 promise 对象,并且img会作为参数传递,类似于:resolve(img)
}, function () {
console.log('error 1')
}).then(function (img) {
console.log(2, img.height)
return img
},function(){
console.log('error 2')
})
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
// 统一捕获异常
var src = 'https://www.imooc.com/static/img/index/logo_new.png'
var result = loadImg(src)
result.then(function (img) {
console.log(1, img.width)
return img
}).then(function (img) {
console.log(2, img.height)
}).catch(function (ex) {
// 统一捕获异常
console.log(ex)
})
2
3
4
5
6
7
8
9
10
11
12
//多个串联,第一个加载完后加载第二个,以此类推
var src1 = 'https://www.imooc.com/static/img/index/logo_new.png'
var result1 = loadImg(src1)
var src2 = 'https://img1.mukewang.com/545862fe00017c2602200220-100-100.jpg'
var result2 = loadImg(src2)
result1.then(function (img1) {
console.log('第一个图片加载完成', img1.width)
return result2 // 重要!!!
}).then(function (img2) {
console.log('第二个图片加载完成', img2.width)
}).catch(function (ex) {
//统一catch
console.log(ex)
})
2
3
4
5
6
7
8
9
10
11
12
13
14
//promise.all 接收一个 promise 对象的数组
//待全部完成后,统一执行 success
var src1 = 'https://www.imooc.com/static/img/index/logo_new.png'
var result1 = loadImg(src1)
var src2 = 'https://img1.mukewang.com/545862fe00017c2602200220-100-100.jpg'
var result2 = loadImg(src2)
Promise.all([result1, result2]).then(function (datas) {
//接收到的 datas 是一个数组,依次包含了多个 promise 返回内容
console.log('all', datas[0])
console.log('all', datas[1])
})
//promise.race 接收一个包含多个 promise对象数组
//只要有一个完成,就执行 success
Promise.race([result1, result2]).then(function (data) {
//data 即最先执行完成的 promises 的返回值
console.log('race', data)
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
问题五:介绍一下 async/await(和 promise 的区别、联系)
- then 只是将 callback 拆分了
- async/await 是最直接的同步写法,再也没有回调函数
- 使用了 promise ,并没有和 promise 冲突
- 但是:改变不了 js 单线程,异步的本质
- 需要 babel-polyfill
问题六:总结一下当前 js 解决异步的方案
- 回调函数(setTimeout())
- 事件监听
- 发布订阅(jQuery.subscribe("done", f2);jQuery.publish("done");)
- promise
- generator
- async/await
# virtual dom
问题一: 什么是 vdom?
- virtual dom,虚拟 DOM
- 用 JS 模拟 DOM 结构
- DOM 变化对比,放在 JS 层来做(图灵完备语言)
- 目的是提高重绘性能
- DOM 操作是“昂贵”的,JS 运行效率高
- 尽量减少 DOM 操作,而不是 “推到重来”
- 项目越复杂,影响就越严重
- vdom 可以解决这个问题
- 核心api
- h('标签名',{属性},[子元素]),对应 vue 中的vm._c,有子元素
- h('标签名',{属性},[字符串]),对应 vue 中的vm._c,无子元素
- patch(containter,vnode),对应 vue 中的vm.patch,首次渲染
- patch(vnode,newVnode),对应 vue 中的vm.patch,diff 对比修改
问题二: 介绍一下 diff 算法?
- 知道什么是 diff 算法,是 linux 的基础命令
- vdom 中应用 diff 算法是为了找到需要更新的节点
- diff 实现,path(container,vnode);patch(vnode,newVnode)
- 核心逻辑,createElement 和 updataChildren
# MVVM 和 Vue
问题一:说一下使用 Jquery 和使用框架的区别
- 数据和视图分离,解耦(开放封闭原则,数据可修改,DOM操作渲染封装)
- 以数据驱动视图,只关心数据变化,DOM 操作被封装
问题二:说一下对 MVVM(Model View ViewModel) 的理解,如何实现 MVVM?
- Model 数据、模型
- View 视图、模板(视图和模型是分离的)
- ViewModel 连接 Model 和 View(View 通过 DOM Listeners 事件监听 Model 来控制 Model 的改变,Model 通过 Data Bindings 操控视图的更新)
问题三:响应式,vue 中如何实现响应式
- 关键是理解 Object.defineProperty
- 将 data 的属性代理到 vm 上
<div id="app">
<input type="text" id="testId">
<p id="show-text"></p>
</div>
<script>
//双向绑定
var obj = {}
Object.defineProperty(obj, 'theValue', {
get: function () {
return obj
},
set: function (newValue) {
console.log("自动执行set方法")
document.getElementById('testId').value = newValue
document.getElementById('show-text').innerHTML = newValue
}
})
document.addEventListener('keyup', function (e) {
obj.theValue = e.target.value
})
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//将 data 的属性代理到 vm 上
var vm = {}
var data = {
name: 'zhangsan',
age: 20
}
var key, value
for (key in data) {
(function (key) {
Object.defineProperty(vm, key, {
get: function () {
console.log('get', data[key]) // 监听
return data[key]
},
set: function (newVal) {
console.log('set', newVal) // 监听
data[key] = newVal
}
})
})(key)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
问题四:模板引擎是什么? vue 中如何解析模板? vue 的模板如何被渲染成 html?
- 跟 html 类似,但是有很大区别
- 模板本质是字符串,模板最终必须转换成 js 代码
- 有逻辑,如 v-if,v-for 等,逻辑必须用 js 才能实现(图灵完备)
- 最终还是要转换为 hmtl来显示,必须用 js 来转换
- 因此,模板最重要转换成一个 js 函数(通过 render 函数[vm._c,vm._s,vm._l...])
- 知道 render 函数长什么样子,render 函数返回 vnode
- updateComponent 函数 patch(),首次直接渲染,第二次开始 diff 对比旧的 vnode
问题五:vue 的整个实现流程是怎么样的?
- 第一步,解析模板成 render 函数
- with 用法
- 模板中用的所有信息都被 render 函数包含
- 模板中用到的 data 中的属性,都变成了 js 变量
- 模板中的 v-model v-for v-on 都变成了 js 逻辑
- render 函数返回 vnode
- 第二步,响应式开始监听(data,watch等)
- Object.defineProperty
- 将 data 的属性代理到 vm 上
- 第三步,首次渲染,显示页面,且绑定依赖
- 初次渲染,执行 updataComponent,执行 vm._render()
- 执行 render 函数,会访问到 vm.list 和 vm.title 等 data 上的数据
- 会被响应式的 get 方式监听到
- 为何要监听 get,直接监听 set 不行吗?
- data 中有许多属性,有些被用到,有些可能不被用到
- 被用到的会走 get,不被用到的不会走 get
- 未走 get 中的属性,set 的时候我们也无需关心
- 避免不必要的重复渲染
- 执行 updateComponent,会走 vnode 的 patch 方法
- patch 将 vnode 渲染成 DOM,初次渲染完成
- 第四部,data 属性变化,触发 rerender
- 修改属性,被响应式的 set 监听到
- set 中执行 updateComponent
- updateComponent 重新执行 vm._render()
- 生成的 vnode 和 preVnode,通过 patch 进行对比
- 渲染到 html 中
# 组件化和 React
问题一:说一下对组件化的理解
- 组件的封装:封装视图、数据、变化逻辑
- 组件的复用:props 传递、复用
问题二:JSX 本质是什么
- JSX 语法(标签、JS 表达式、判断、循环、事件绑定)
- JSX 本质是语法糖,需要被解析成 JS 才能运行
- JSX 是独立的标准,可被其他项目使用
问题三:JSX 和 vdom 的关系
- 为何需要 vdom: JSX 需要渲染成 html,数据驱动视图
- React.createElement 和 h,都生成 vnode
- 何时 patch: ReactDOM.render(...) 和 setState
- 自定义组件的解析:初始化实例,然后执行 render,例如:
//把 React.createElement(App,null); 转换成下面两句:
var app = new App(null); //这里传入的属性是 null,或者 {data:this.state.list},{addTitle:this.addTitle.bind(this)} 等数据或者函数
return app.render();
2
3
问题四: 说一下 setState 的过程
- setState 的异步:效果、原因(提高效率)
- 可能会一次执行多次 setState
- 你无法规定、限制用户如何使用 setState
- 没必要每次 setState 都重新渲染,考虑性能
- 即便是每次重新渲染,用户也看不到中间的效果(执行 JS 时,DOM 渲染是暂停的)
- vue 修改属性也是异步的:效果、原因(提高效率)
- setState 的过程:最终走到 patch(preVnode,newVnode)
- 每个组件实例,都有 renderComponent 方法
- 执行 renderComponent 会重新执行实例的 render
- render 函数返回 newVnode ,然后拿到 preVnode
- 执行 patch(preVnode,newVnode)
# React vs Vue
- 两者的本质区别
- vue 本质是 MVVM 框架,由 MVC 发展而来
- React 本质是前端组件化框架,由后端组件化发展而来
- 但并不妨碍他们两者都能实现相同的功能
- 看模板的和组件化的区别
- 模板
- vue 使用模板(最初由 angular 提出)
- React 使用 JSX
- 模板语法上,我更加倾向于 JSX
- 模板分离上,我更加倾向于 Vue
- 组件化
- React 本身就是组件化,没有组件化就不是 React
- vue 也支持组件化,不过是在 MVVM 上的扩展
- 对于组件化,我更加倾向于 React ,做的彻底而清晰
- 模板
- 两者共同点
- 都支持组件化
- 都是数据驱动视图
# Hybird
问题一:hybird 存在的价值,为何会用 hybrid?
- hybird 是客户端和前端的混合开发
- 没有访问手机内部信息功能的权限,所以可以快速迭代更新,无需审核
- 体验流畅(和 NA 的体验基本类似)
- 减少开发和沟通成本,双端公用一套代码
问题二: webview
- 是 app 中的一个组件(app 可以有 webview,也可以没有)
- 用于加载 h5 页面,即一个小型的浏览器内核
问题三: file:// 协议
- file 协议:本地文件快
- http(s) 协议:网络加载,慢
问题四:hybrid 使用场景:
- 不是所有的场景都适合 hybrid
- 使用 NA:体验要求极致,变化不频繁
- 使用 hybrid: 体验要求高,变化频繁
- 使用 h5:体验无要求,不经常使用
问题五: hybrid 具体实现:
- 前端做好静态页面(html,js,css),将文件交给客户端
- 客户端拿到静态页面,以文件形式存储在 app 中
- 客户端在一个 webview 中使用 file 协议加载静态文件
问题六: app 发布之后,静态文件如何实时更新?
- 分版本,有版本号,如 201803211015
- 将静态文件压缩成 zip 包,上传到服务器
- 客户端每次启动,都去服务端检查版本号
- 如果服务端本本号大于客户端版本号,就去下载最新的 zip 包
- 下载完之后解压包,然后将现有文件覆盖
问题七: hybrid 和 h5 的区别
- 优点
- 体验好,跟 NA 体验基本一致
- 可快速迭代,无需 app 审核(关键)
- 缺点
- 开发成本高,联调、测试、查 bug 都比较麻烦
- 运维成本高,更新流程复杂
- 适用场景
- hybrid:产品的稳定功能,体验要求高,迭代频繁(产品型)
- h5:单词的运营活动(如 xx 红包)或不常用功能(运营型)
问题八:前端和客户端通讯
- 静态页面如何获取内容,例如:新闻详情页,前端如何获取新闻内容?
- 不能用 ajax 获取,第一跨域,第二速度慢
- 客户端获取新内容(客户端可以提前获取),然后 js 与客户端通讯拿到内容,再渲染;客户端获取内容可以提前获取不像 js 渲染完才可以通过 ajax 请求获取
- 总结起来就是:js 访问客户端,传递参数和回调函数,客户端通过回调函数返回内容
- JS 和客户端通讯的基本形式
- 通过 schema 协议——前端和客户端通讯约定
- 调用能力,传递参数,监听回调
- schema 协议简介和使用
- schema 使用封装
- 内置上线
- 更快更安全