1. JSBridge 通信?
主要是给 JavaScript
提供调用 Native 功能
的接口,让混合开发中的前端部分可以方便地使用 Native
的功能(例如:地址位置
、摄像头
)。是 Native
和 非 Native
之间的桥梁,它的核心是构建 Native
和 非 Native
间 消息通信的通道 ,而且这个通信的通道是双向的。
JSBridge
的通信原理JavaScript
调用Native
的方式: 主要有两种:注入API
和拦截URL SCHEME
注入 API 方式的主要原理是,通过
WebView
提供的接口,向JavaScript
的Context(window)
中注入对象或者方法,让JavaScript
调用时,直接执行相应的Native 代码
逻辑,达到JavaScript
调用Native
的目的拦截 URL SCHEME 的主要流程是:
Web 端
通过某种方式(例如iframe.src
)发送URL Scheme
请求,之后Native
拦截到请求并根据URL SCHEME
(包括所带的参数)进行相关操作在时间过程中,这种方式有一定的缺陷:
- 使用
iframe.src
发送URL SCHEME
会有url 长度
的隐患。 - 创建请求,需要一定的耗时,比注入 API 的方式调用同样的功能,耗时会较长
- 使用
因此:
JavaScript
调用Native
推荐使用: 注入 API 的方式Native
调用JavaScript
的方式相比于
JavaScript
调用Native
,Native
调用 JavaScript较为简单,直接执行拼接好的
JavaScript 代码即可。从外部调用
JavaScript中的方法,因此
JavaScript的方法必须在全局的
window` 上。
扩展:RN
与 webView
的通信
webview
中点击事件代码通过
postMessage()
方法,传递两个参数
。一个是type 值
,是我们做判断时候用的。判断是哪种类型的消息。另一个是我们想传递的其他参数
。这里记得参数传递前做一次:JSON.stringify()
方法,解析json 字符串
RN
中获取传过来的参数Webview
组件的onMessage
方法中接受:JSON.parse(event.nativeEvent.data)
2. 小程序 与 Vue 有什么区别?
生命周期:相比之下,小程序的钩子函数要简单得多。vue 的钩子函数在跳转新页面时,钩子函数都会触发,但是小程序的钩子函数,页面不同的跳转方式,触发的钩子并不一样
onLoad:
页面加载
: 一个页面只会调用一次,可以在onLoad
中获取打开当前页面所调用的query
参数onShow:
页面显示
: 每次打开页面都会调用一次onReady:
页面初次渲染完成
: 一个页面只会调用一次,代表页面已经准备妥当,可以和视图层进行交互。对界面的设置如wx.setNavigationBarTitle
请在onReady
之后设置onHide:
页面隐藏
: 当navigateTo
或底部tab
切换时调用onUnload:
页面卸载
: 当redirectTo
或navigateBack
的时候调用
数据请求
在页面加载请求数据时,两者钩子的使用有些类似,
vue
一般会在created
或者mounted
中请求数据。而在小程序,会在onLoad
或者onShow
中请求数据数据绑定
Vue:
vue
动态绑定一个变量的值为元素的某个属性的时候,会在变量前面加上冒号:
<img :src="imgSrc" />
小程序: 绑定某个变量的值为元素属性时,会用
两个大括号
括起来,如果不加括号,为被认为是字符串
<image src="{{imgSrc}}"></image>
列表渲染
- Vue:
<li v-for="item in items"></li>
- 小程序:
<text wx:for=""></text>
- Vue:
显示与隐藏元素
- Vue: 使用
v-if
和v-show
控制元素的显示和隐藏 - 小程序: 使用
wx-if
和hidden
控制元素的显示和隐藏
- Vue: 使用
事件处理
Vue: 使用
v-on:event
绑定事件,或者使用@event
绑定事件<button v-on:click="counter += 1">Add 1</button> <button @click="counter += 1">Add 1</button>
小程序: 全用
bindtap(bind+event)
,或者catchtap(catch+event)
绑定事件<button bindtap="noWork">明天不上班</button> <button catchtap="noWork">明天不上班</button>
数据双向绑定
- Vue: 表单元素上加
v-model
- 小程序: 通过监听输入框事件
bindinput
,调用setData({})
方法进行模拟
- Vue: 表单元素上加
取值
- Vue: 通过
this.xxx
取值 - 小程序: 通过
this.data.xxx
取值
- Vue: 通过
绑定事件传参
Vue: 绑定事件传参挺简单,只需要在触发事件的方法中,把需要传递的数据作为形参传入就可以了
<button @click="say('明天不上班')"></button>
小程序: 不能直接在绑定事件的方法中传入参数,需要将参数作为属性值,绑定到元素上的
data-属性
上,然后在方法中,通过e.currentTarget.dataset.*
的方式获取<view class="tr" bindtap="toApprove" data-id="{{item.id}}"></view> toApprove(e) { let id = e.currentTarget.dataset.id; }
小程序实现原理解析
小程序目录结构
- 一个完整的小程序主要由以下几部分组成:
- 一个入口文件:
app.js
- 一个全局样式:
app.wxss
- 一个全局配置:
app.json
- 一个入口文件:
页面:
pages
下,每个页面再按文件夹划分,每个页面 4 个文件- 视图:
wxml
,wxss
- 逻辑:
js
,json
(页面配置, 不是必须)
- 一个完整的小程序主要由以下几部分组成:
小程序架构
微信小程序的框架包含两部分:
View视图层
、App Service逻辑层
.View 层
用来渲染页面结构,AppService 层
用来逻辑处理
、数据请求
、接口调用
,它们在两个进程(两个Webview
)里运行视图层
和逻辑层
通过系统层的 JSBridge 进行通信,逻辑层
把数据变化通知到视图层
,触发视图层页面更新,视图层把触发的事件通知到逻辑层
进行业务处理扩展:小程序不允许打开超过 5 个层级的页面?
小程序的视图和逻辑处理是用多个
webview
实现的,逻辑处理的JS 代码
全部加载到一个Webview
里面,称之为AppService
,整个小程序只有一个,并且整个生命周期常驻内存。而所有的视图(wxml
和wxss
)都是单独的Webview
来承载,称之为AppView
。所以一个小程序打开至少就会有 2 个 webview 进程,正式因为每个视图都是一个独立的webview
进程,考虑到性能消耗原理上,
微信 App
里包含javascript 运行引擎
、WXML/WXSS 处理引擎
,最终会把界面翻译成系统原生的界面,并展示出来。这样做的目的是为了提供和原生 App 性能相当的用户体验。- 在
ios
中,小程序的javascript
运行在javascriptCore
中 - 在
Android
中,小程序的javascript
是通过X5 内核
来解析的
- 在
小程序的优劣势
- 优势:
- 无需下载,通过搜索和扫一扫就可以打开
- 良好的用户体验:打开速度快
- 开发成本要比 App 要低
- 安卓上可以添加到桌面,与原生 App 差不多
- 为用户提供良好的安全保障。小程序的发布,微信拥有一套严格的审查流程, 不能通过审查的小程序是无法发布到线上的
- 劣势:
- 限制较多: 页面大小不能超过
1M
。不能打开超过5 个层级
的页面 - 样式单一: 小程序的部分组件已经是成型的了,样式不可以修改。例如:幻灯片、导航
- 推广面窄: 不能分享朋友圈,只能通过分享给朋友,附近小程序推广。其中附近小程序也受到微信的限制
- 依托于微信: 无法开发后台管理功能
- 限制较多: 页面大小不能超过
提高微信小程序的应用速度的手段有哪些?
小程序首次启动前,微信会在小程序启动前为小程序准备好通用的运行环境
,如运行中的线程和一些基础库的初始化。然后才开始进入启动状态,展示一个固定的启动界面,界面内包含小程序的图标、名称和加载提示图标。此时,微信会在背后完成几项工作:
下载
小程序代码包加载
小程序代码包初始化
小程序首页
注意:下载到的小程序代码包不是小程序的源代码
,而是编译、压缩、打包
之后的代码包
围绕上图小程序的启动流程, 提高速度我们可以从加载
、渲染
两个纬度进行切入:
- 小程序启动加载性能:
- 控制代码包的大小
- 分包加载
- 首屏体验(预请求,利用缓存,避免白屏)
- 小程序渲染性能
- 不要过于频繁调用
setData
,应考虑将多次setData
合并成一次setData
调用 - 与界面渲染无关的数据最好不要设置在
data
中 - 自定义组件(各个组件也将具有各自独立的逻辑空间。分别拥有自己的独立的数据、
setData
调用)
- 不要过于频繁调用
3. 团队的开发流程?
4. 如何防止首页白屏?
首屏时间(First Contentful Paint
),指的是浏览器从响应用户输入网址地址,到首屏内容渲染完成的时间,此时整个网页不一定要全部渲染完成,但需要展示当前视窗需要的内容
- 通过
DOMContentLoad
或者performance
来计算出首屏时间?
// 方案一:
document.addEventListener('DOMContentLoaded', (event) => {
console.log('first contentful painting');
});
// 方案二:
performance.getEntriesByName("first-contentful-paint")[0].startTime
// performance.getEntriesByName("first-contentful-paint")[0]
// 会返回一个 PerformancePaintTiming的实例,结构如下:
{
name: "first-contentful-paint",
entryType: "paint",
startTime: 507.80000002123415,
duration: 0,
};
先说下 Spa 单页面
的加载过程: 首先就是 html
,也就是 FP 阶段
; 然后是静态资源 css
,js
,之后解析 js
,生成 HTML
,也就是 FCP 阶段
,css
, js
资源加载下来了,首次的内容绘制,有一个大结构了; 到最后,就是 FMP
,ajax 请求数据之后,首次有效绘制,就是页面加载差不多了,但是可能图片还没加载出来
总结: 从 FP
到 FMP
这个过程全是 白屏,可能你的 header
如果有啥大背景色啊,这个背景色或许会出来,ajax
之后,才会真正去解析我们的数据,把数据放入我们的 html
标签中
加载慢的原因
在页面渲染的过程,导致加载速度慢的因素可能如下:
- 网络延时问题
- 资源文件体积是否过大
- 资源是否重复发送请求去加载了
- 加载脚本的时候,渲染内容堵塞了
解决方案:
减少入口文件体积
常用的手段是
路由懒加载
,把不同路由对应的组件分割成不同的代码块,待路由被请求的时候会单独打包路由,使得入口文件变小,加载速度大大增加在
vue-router
配置路由的时候,采用动态加载路由的形式:routes:[ path: 'Test', name: 'Test', component: () => import('./components/Test.vue') ]
静态资源本地缓存
后端返回资源问题:
- 采用
HTTP 缓存
,设置Cache-Control
,Last-Modified
,Etag
等响应头 - 采用
Service Worker
离线缓存
前端合理利用
localStorage
- 采用
UI 框架按需加载
图片资源的压缩
对大图片进行压缩、对
icon 图
可以使用在线字体图标或者雪碧图,将众多小图标合并到同一张图上,用以减轻http
请求压力。组件重复打包
如果一个依赖库被多个文件引入会多次打包。解决方案:在
webpack
的config
文件中,修改CommonsChunkPlugin
的配置。minChunks
为 3 表示会把使用 3 次及以上的包抽离出来,放进公共依赖文件,避免了重复加载组件开启 GZip 压缩
plugins: [ new CompressionPlugin({ test: /\.js$|\.html$|\.css/, // 匹配文件名 threshold: 10240, // 对超过10k的数据进行压缩 deleteOriginalAssets: false, // 是否删除原文件 }), ]
使用 SSR
SSR(Server side )
,也就是服务端渲染
,组件或页面通过服务器生成html 字符串
,再发送到浏览器vue -> Nuxt.js, react -> Next.js
减少首屏渲染时间的方法有很多,总的来讲可以分成两大部分 :
资源加载优化
和页面渲染优化
解决办法:
预渲染
SSR
路由懒加载
使用 Gzip 压缩
webpack entry
多页应用骨架屏(原理)
loading
在首页
index.html
里加一个loading css
效果,当页面加载完成消失
5. 怎么理解前端工程化、模块化、组件化?
- 工程化:是一种思想而不是某种技术,而模块化和组件化是为工程化思想下相对较具体的开发方式。可以简单的认为模块化和组件化是工程化的表现形式
- 模块化开发:一个模块就是一个实现特定功能的文件,有了模块我们就可以更方便的使用别人的代码,要用什么功能就加载什么模块
- 模块化开发的好处:
- 提高代码复用率和维护性
- 避免变量污染、命名冲突
- 模块化开发的好处:
- 组件化开发:就是将一个页面拆分成很多的小组件
6. 微前端中的渲染、JS 沙箱、样式隔离、数据通信?
7. 如何实现扫码登录功能?
- 访问 PC 端二维码生成页面,PC 端请求服务端获取
二维码ID
- 服务端生成相应的
二维码ID
,设置二维码的过期时间,状态等。 - PC 获取
二维码ID
,生成相应的二维码。 - 手机端扫描二维码,获取
二维码ID
。 - 手机端将
手机端token
和二维码ID
发送给服务端,确认登录。 - 服务端校验
手机端token
,根据手机端token
和二维码ID
生成 PC 端token
- PC 端通过
轮询方式
请求服务端,通过二维码 ID 获取二维码状态,如果已成功,返回 PC token,登录成功。
扩展:轮询方式有哪些,以及它们各自的优缺点?
轮询
基本思路
: 就是浏览器每隔一段时间向浏览器发送http 请求
,服务器端在收到请求后,不论是否有数据更新,都直接进行响应。这种方式实现的即时通信,本质上还是浏览器发送请求,服务器接受请求的一个过程,通过让客户端不断的进行请求,使得客户端能够模拟实时地收到服务器端的数据的变化。优点: 是比较简单,易于理解,实现起来也没有什么技术难点。
缺点: 由于需要不断的建立 http 连接,严重浪费了服务器端和客户端的资源. 人数越多,服务器端压力越大,这是很不合理的。
长轮询
基本思路
: 服务器收到客户端发来的请求后,服务器端不会直接进行响应,而是先将这个请求挂起,然后判断服务器端数据是否有更新。如果有更新,则进行响应,如果一直没有数据,则到达一定的时间限制(服务器端设置)才返回。客户端JavaScript 响应
处理函数会在处理完服务器返回的信息后,再次发出请求,重新建立连接。优点: 减少了很多不必要的 http 请求次数,相比之下节约了资源
缺点: 接挂起也会导致资源的浪费
轮询与长轮询都是基于 HTTP 的
长连接(SSE)
SSE
是HTML5
新增的功能,全称为Server-Sent Events
。它可以允许服务推送数据到客户端。SSE
在本质上就与之前的长轮询、短轮询不同,虽然都是基于http 协议
的,但是轮询需要客户端先发送请求。而SSE
最大的特点就是不需要客户端发送请求,可以实现只要服务器端数据有更新,就可以马上发送到客户端。优点: 它不需要建立或保持大量的客户端发往服务器端的请求,节约了很多资源,提升应用性能
WebSocket
WebSocket
是Html5
定义的一个新协议,与传统的http 协议
不同,该协议可以实现服务器与客户端之间全双工通信
8. 如何防止重复发送请求?
let count = 1
let promiseFunction = () =>
new Promise((rs) =>
window.setTimeout(() => {
rs(count++)
})
)
let firstFn = firstPromise(promiseFunction)
firstFn().then(console.log) // 1
firstFn().then(console.log) // 1
firstFn().then(console.log) // 1
function firstPromise(promiseFunction) {
let p = null
return function(...args) {
// 请求的实例,已存在意味着正在请求中,直接返回实例,不触发新的请求
return p
? p
: // 否则发送请求,且在finally时将p置空,那么下一次请求可以重新发起
(p = promiseFunction.apply(this, args).finally(() => (p = null)))
}
}
扩展:前端做后台管控系统,在某些接口请求时间过长的场景下,需要防止用户反复发起请求,实现方式也有好几种:
- 在按钮点击发起请求后,弹个蒙层,显示个 loading,等请求数据返回了将蒙层隐藏掉
- 在按钮点击发起请求后,将按钮禁用掉,同样等数据返回了将按钮禁用解除
以上两种方案优点仅仅是简单,但是每个需要处理的页面都要单独写一串重复的代码,哪怕利用 mixin 也要多不少冗余代码。利用指令的方式仅仅需要在合适的地方加上个一条 v-xxxx,其他都在指令的逻辑内统一处理。
let forbidClick = null
export default {
bind(e) {
const el = e
let timer = null
forbidClick = () => {
el.disabled = true
el.classList.add('is-disabled')
timer = setInterval(() => {
if (window.currentRes.done) {
clearInterval(timer)
el.disabled = false
el.classList.remove('is-disabled')
}
}, 500)
}
el.addEventListener('click', forbidClick)
},
unbind() {
document.removeEventListener('click', forbidClick)
},
}
// 再考虑请求,记录当前请求是否完成。请求拦截器中,接口请求时长超过3s,则视为完成,不管请求结果成功或失败
这样就实现了只要在按钮上加上了 v-clickForbidden。按钮点击后就会被禁用,仅当某个请求返回数据或者 3s 后将按钮的禁用解除
9. 实现一个页面操作不会整页刷新的网站
实现一个页面操作不会整页刷新的网站,并且能在浏览器的前进,后退时正确响应。给出你的技术实现方案?
方式一 用 cookie 或者 localStorage 来记录应用的状态即可,刷新页面时读取一下这个状态,然后发送相应 ajax 请求来改变页面即可(注意:ajax 可以无刷新改变页面内容,但无法改变页面 URL;ajax 的使用对搜索引擎很不友好)
方式二 HTML5 里引用了新的 API,就是 history.pushState 和 history.replaceState,就是通过这个接口做到无刷新改变页面 URL 的
通过 pushState 和 replaceState 接口操作浏览器历史,并且改变当前页面的 URL。pushState 是将指定的 URL 添加到浏览器历史里,replaceState 是将指定的 URL 替换当前的 URL。(注意:通过 onpopstate 事件来响应浏览器的前进和后退的操作)
方式三 内容发生改变后,改变 URL 的 hash。(注意:hash 的方式不能很好的处理浏览器的前进、后退等问题,通过 onhashchange 事件监听 hash 的改变)
总结:结合 ajax 和 pushState 完美的进行无刷新浏览了
扩展:
window.history.back() -- 相当于用户在浏览器的工具栏上点击返回按钮
window.history.forward() -- 相当于用户在浏览器的工具栏上点击前进按钮
go()方法,指定一个相对于当前页面位置的数值,从当前会话的历史记录中加载页面(当前位置页面索引:0,上一页:-1,下一页:1) window.history.go(-2)--后退 2 页,相当于调用两次 back();
window.history.go(1)--前进 1 页,相当于调用 forward();
通过 window.history.length,得到历史记录栈中一共有多少页
pushState(stateObject,title,URL)。state: 对象,存放一些数据表示当前状态。当浏览器执行前进后退操作时触发 onpopstate 事件
window.onpopstate = function(event) {
console.log(event.state) // 当前历史记录条目的状态
}
window.addEventListener('popstate', function(event) {
// 做一些操作
})
10、如何优化单页面首屏加载白屏体验或加重速度慢问题 ?
- 将公用的 JS 库通过 script 标签外部引入,减小 app.bundle 的大小,让浏览器并行下载资源文件,提高下载速度
- 在配置 路由时,页面和组件使用懒加载的方式引入,进一步缩小 app.bundle 的体积,在调用某个组件时再加载对应的 js 文件
- root 中插入 loading 或者 骨架屏 prerender-spa-plugin,提升用户体验(
saas项目中添加 loading 来防止白屏
) - 如果在 webview 中的页面,可以进行页面预加载
- 独立打包异步组件公共 Bundle,以提高复用性&缓存命中率
- 静态文件本地缓存,有两种方式分别为 HTTP 缓存,设置 Cache-Control,Last-Modified,Etag 等响应头和 Service Worker 离线缓存
- 配合 PWA 使用
- SSR
- 使用 Tree Shaking 减少业务代码体积
- 懒加载和预加载的区别?
- 预加载是指在页面加载完成之前,提前将所需资源下载,之后使用的时候从缓存中调用;懒加载是延迟加载,按照一定的条件或者需求等到满足条件的时候再加载对应的资源
- 预加载增加了服务器压力,换来的是用户体验的提升,典型例子是在一个图片较多的网页中,如果使用了预加载就可以避免网页加载出来是时,图片的位置一片空白(图片可能还没加载出来),造成不好的用户体验;懒加载的作用减少不要的请求,缓解了服务器压力
11. 如何设计一个 Vue 组件?
12. 如何开发一个开源组件库?
13. 如何统一对错误进行捕获的?
14. vue 的异步错误如何捕获?
15. 如如何用鉴权控制登陆时间设计保持七天登录状态?
16. 为什么 B 站的弹幕可以不挡人物?
利用 CSS 中新增的-webkit-mask-image 属性