公司:脉脉css
分类:Vuehtml
答案&解析前端
指计算机语言中添加的某种语法,这种语法对语言的功能并无影响,可是更方便程序员使用。一般来讲使用语法糖可以增长程序的可读性,从而减小程序代码出错的机会。糖在不改变其所在位置的语法结构的前提下,实现了运行时的等价。能够简单理解为,加糖后的代码编译后跟加糖前同样,代码更简洁流畅,代码更语义天然.vue
动态绑定了 input
的 value
指向了 messgae
变量,而且在触发 input
事件的时候去动态把 message
设置为目标值node
<input v-model="sth" />
// 等同于
<input v-bind:value="message" v-on:input="message=$event.target.value" > //$event 指代当前触发的事件对象; //$event.target 指代当前触发的事件对象的dom; //$event.target.value 就是当前dom的value值; //在@input方法中,value => sth; //在:value中,sth => value; 复制代码
在自定义组件中,v-model 默认会利用名为 value 的 prop 和名为 input 的事件react
本质是一个父子组件通讯的语法糖,经过prop和$.emit实现jquery
所以父组件v-model
语法糖本质上能够修改成 '<child :value="message" @input="function(e){message = e}"></child>'
webpack
在组件的实现中,咱们是能够经过 v-model属性 来配置子组件接收的prop名称,以及派发的事件名称。ios
例子git
// 父组件
<aa-input v-model="aa"></aa-input>
// 等价于
<aa-input v-bind:value="aa" v-on:input="aa=$event.target.value"></aa-input>
// 子组件:
<input v-bind:value="aa" v-on:input="onmessage"></aa-input>
props:{value:aa,}
methods:{
onmessage(e){
$emit('input',e.target.value)
}
}
复制代码
默认状况下,一个组件上的 v-model 会把 value 用做 prop 且把 input 用做 event
可是一些输入类型好比单选框和复选框按钮可能想使用 value prop 来达到不一样的目的。使用 model 选项能够回避这些状况产生的冲突。
js 监听input 输入框输入数据改变,用oninput ,数据改变之后就会马上出发这个事件。
经过input事件把数据$emit 出去,在父组件接受。
父组件设置v-model的值为input$emit过来的值。
公司:微医
分类:React
在代码中调用setState函数以后,React 会将传入的参数对象与组件当前的状态合并,而后触发所谓的调和过程(Reconciliation)。
通过调和过程,React 会以相对高效的方式根据新的状态构建 React 元素树而且着手从新渲染整个UI界面。
在 React 获得元素树以后,React 会自动计算出新的树与老树的节点差别,而后根据差别对界面进行最小化重渲染。
在差别计算算法中,React 可以相对精确地知道哪些位置发生了改变以及应该如何改变,这就保证了按需更新,而不是所有从新渲染。
假如全部setState是同步的,意味着每执行一次setState时(有可能一个同步代码中,屡次setState),都从新vnode diff + dom修改,这对性能来讲是极为很差的。若是是异步,则能够把一个同步代码中的多个setState合并成一次组件更新。
在setTimeout或者原生事件中,setState是同步的。
公司:爱范儿
分类:JavaScript
由于每一个请求处理时长不一致,可能会致使先发送的请求后响应,即请求响应顺序和请求发送顺序不一致,从而致使数据显示不正确。
便可以理解为连续触发多个请求,如何保证请求响应顺序和请求发送顺序一致。对于问题所在场景,用户只关心最后数据是否显示正确,便可以简化为:连续触发多个请求,如何保证最后响应的结果是最后发送的请求(不关注以前的请求是否发送或者响应成功)
相似场景:input输入框即时搜索,表格快速切换页码
防抖(过滤掉一些非必要的请求) + 取消上次未完成的请求(保证最后一次请求的响应顺序)
取消请求方法:
XMLHttpRequest
使用 abort
api
取消请求axios
使用 cancel token
取消请求伪代码(以 setTimeout 模拟请求,clearTimeout 取消请求)
/** * 函数防抖,必定时间内连续触发事件只执行一次 * @param {*} func 须要防抖的函数 * @param {*} delay 防抖延迟 * @param {*} immediate 是否当即执行,为true表示连续触发时当即执行,即执行第一次,为false表示连续触发后delay ms后执行一次 */
let debounce = function(func, delay = 100, immediate = false) {
let timeoutId, last, context, args, result
function later() {
const interval = Date.now() - last
if (interval < delay && interval >= 0) {
timeoutId = setTimeout(later, delay - interval)
} else {
timeoutId = null
if (!immediate) {
result = func.apply(context, args)
context = args = null
}
}
}
return function() {
context = this
args = arguments
last = Date.now()
if (immediate && !timeoutId) {
result = func.apply(context, args)
context = args = null // 解除引用
}
if (!timeoutId) {
timeoutId = setTimeout(later, delay)
}
return result
}
}
let flag = false // 标志位,表示当前是否正在请求数据
let xhr = null
let request = (i) => {
if (flag) {
clearTimeout(xhr)
console.log(`取消第${i - 1}次请求`)
}
flag = true
console.log(`开始第${i}次请求`)
xhr = setTimeout(() => {
console.log(`请求${i}响应成功`)
flag = false
}, Math.random() * 200)
}
let fetchData = debounce(request, 50) // 防抖
// 模拟连续触发的请求
let count = 1
let getData = () => {
setTimeout(() => {
fetchData(count)
count++
if (count < 11) {
getData()
}
}, Math.random() * 200)
}
getData()
/* 某次测试输出: 开始第2次请求 请求2响应成功 开始第3次请求 取消第3次请求 开始第4次请求 请求4响应成功 开始第5次请求 请求5响应成功 开始第8次请求 取消第8次请求 开始第9次请求 请求9响应成功 开始第10次请求 请求10响应成功 */
复制代码
因文章篇幅过长,影响阅读体验,接下来的7道题目答案将被折叠,点击查看解析,便可查看所有答案,选择你感兴趣的题查看答案吧~
公司:有赞、微医、58
分类:React、Vue
从本质上来讲,Virtual Dom是一个JavaScript对象,经过对象的方式来表示DOM结构。将页面的状态抽象为JS对象的形式,配合不一样的渲染工具,使跨平台渲染成为可能。经过事务处理机制,将屡次DOM修改的结果一次性的更新到页面上,从而有效的减小页面渲染的次数,减小修改DOM的重绘重排次数,提升渲染性能。
虚拟dom是对DOM的抽象,这个对象是更加轻量级的对DOM的描述。它设计的最初目的,就是更好的跨平台,好比Node.js就没有DOM,若是想实现SSR,那么一个方式就是借助虚拟dom, 由于虚拟dom自己是js对象。
在代码渲染到页面以前,vue或者react会把代码转换成一个对象(虚拟DOM)。以对象的形式来描述真实dom结构,最终渲染到页面。在每次数据发生变化前,虚拟dom都会缓存一份,变化之时,如今的虚拟dom会与缓存的虚拟dom进行比较。
在vue或者react内部封装了diff算法,经过这个算法来进行比较,渲染时修改改变的变化,原先没有发生改变的经过原先的数据进行渲染。
另外现代前端框架的一个基本要求就是无须手动操做DOM,一方面是由于手动操做DOM没法保证程序性能,多人协做的项目中若是review不严格,可能会有开发者写出性能较低的代码,另外一方面更重要的是省略手动DOM操做能够大大提升开发效率。
看一下页面渲染的一个流程:
下面对比一下修改DOM时真实DOM操做和Virtual DOM的过程,来看一下它们重排重绘的性能消耗:
Virtual DOM的更新DOM的准备工做耗费更多的时间,也就是JS层面,相比于更多的DOM操做它的消费是极其便宜的。尤雨溪在社区论坛中说道: 框架给你的保证是,你不须要手动优化的状况下,我依然能够给你提供过得去的性能。
Virtual DOM本质上是JavaScript的对象,它能够很方便的跨平台操做,好比服务端渲染、uniapp等。
分类:工程化
webpack是所谓的模块捆绑器,内部有循环引用来分析模块间之间的依赖,把文件解析成AST,经过一系类不一样loader的加工,最后所有打包到一个js文件里。
webpack4之前在打包速度上没有作过多的优化手段,编译慢的大部分时间是花费在不一样loader编译过程,webpack4之后,吸取借鉴了不少优秀工具的思路,
如支持0配置,多线程等功能,速度也大幅提高,但依然有一些优化手段。如合理的代码拆分,公共代码的提取,css资源的抽离
exclude/include
(肯定 loader 规则范围)resolve.modules
指明第三方模块的绝对路径 (减小没必要要的查找)resolve.extensions
尽量减小后缀尝试的可能性noParse
对彻底不须要解析的库进行忽略 (不去解析但仍会打包到 bundle 中,注意被忽略掉的文件里不该该包含 import、require、define 等模块化语句)注意:thread-loader 和 cache-loader 兩個要一块儿使用的話,請先放 cache-loader 接著是 thread-loader 最後才是 heavy-loader
来看下具体使用
module.exports = {
module: {
noParse: /jquery/,
rules:[]
}
}
复制代码
./local
则忽略掉import 'moment/locale/zh-cn'
module.exports = {
plugins: [
new Webpack.IgnorePlugin(/\.\/local/, /moment/),
]
}
复制代码
webpack.DllPlugin
、Webpack.DllReferencePlugin
thread-loader 会将您的 loader 放置在一个 worker 池里面运行,以达到多线程构建。
把这个 loader 放置在其余 loader 以前,放置在这个 loader 以后的 loader 就会在一个单独的 worker 池(worker pool)中运行。
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.js$/,
include: path.resolve("src"),
use: [
"thread-loader",
// 你的高开销的loader放置在此 (e.g babel-loader)
]
}
]
}
}
复制代码
每一个 worker 都是一个单独的有 600ms 限制的 node.js 进程。同时跨进程的数据交换也会被限制。请在高开销的loader中使用,不然效果不佳
不推荐使用 webpack-paralle-uglify-plugin,项目基本处于没人维护的阶段,issue 没人处理,pr没人合并。
Webpack 4.0之前:uglifyjs-webpack-plugin,parallel参数
module.exports = {
optimization: {
minimizer: [
new UglifyJsPlugin({
parallel: true,
}),
],
},};
复制代码
推荐使用 terser-webpack-plugin
module.exports = {
optimization: {
minimizer: [new TerserPlugin(
parallel: true // 多线程
)],
},
};
复制代码
分类:网络&安全
浏览器缓存策略:
浏览器每次发起请求时,先在本地缓存中查找结果以及缓存标识,根据缓存标识来判断是否使用本地缓存。若是缓存有效,则使用本地缓存;不然,则向服务器发起请求并携带缓存标识。根据是否需向服务器发起HTTP请求,将缓存过程划分为两个部分:强制缓存和协商缓存,强缓优先于协商缓存。
HTTP缓存都是从第二次请求开始的
服务器通知浏览器一个缓存时间,在缓存时间内,下次请求,直接用缓存,不在时间内,执行比较缓存策略。
强缓存命中则直接读取浏览器本地的资源,在network中显示的是from memory或者from disk
控制强制缓存的字段有:Cache-Control(http1.1)和Expires(http1.0)
1)强缓存-expires
该字段是服务器响应消息头字段,告诉浏览器在过时时间以前能够直接从浏览器缓存中存取数据。
Expires 是 HTTP 1.0 的字段,表示缓存到期时间,是一个绝对的时间 (当前时间+缓存时间)。在响应消息头中,设置这个字段以后,就能够告诉浏览器,在未过时以前不须要再次请求。
因为是绝对时间,用户可能会将客户端本地的时间进行修改,而致使浏览器判断缓存失效,从新请求该资源。此外,即便不考虑修改,时差或者偏差等因素也可能形成客户端与服务端的时间不一致,导致缓存失效。
优点特色:
劣势问题:
2)强缓存-cache-control
已知Expires的缺点以后,在HTTP/1.1中,增长了一个字段Cache-control,该字段表示资源缓存的最大有效时间,在该时间内,客户端不须要向服务器发送请求。
这二者的区别就是前者是绝对时间,然后者是相对时间。下面列举一些 Cache-control
字段经常使用的值:(完整的列表能够查看MDN)
max-age
:即最大有效时间。must-revalidate
:若是超过了 max-age
的时间,浏览器必须向服务器发送请求,验证资源是否还有效。no-cache
:不使用强缓存,须要与服务器验证缓存是否新鲜。no-store
: 真正意义上的“不要缓存”。全部内容都不走缓存,包括强制和对比。public
:全部的内容均可以被缓存 (包括客户端和代理服务器, 如 CDN)private
:全部的内容只有客户端才能够缓存,代理服务器不能缓存。默认值。Cache-control 的优先级高于 Expires,为了兼容 HTTP/1.0 和 HTTP/1.1,实际项目中两个字段均可以设置。
该字段能够在请求头或者响应头设置,可组合使用多种指令:
max-age=<seconds>
:缓存存储的最大周期,超过这个周期被认为过时。s-maxage=<seconds>
:设置共享缓存,好比can。会覆盖max-age和expires。max-stale[=<seconds>]
:客户端愿意接收一个已通过期的资源min-fresh=<seconds>
:客户端但愿在指定的时间内获取最新的响应stale-while-revalidate=<seconds>
:客户端愿意接收陈旧的响应,而且在后台一部检查新的响应。时间表明客户端愿意接收陈旧响应 的时间长度。stale-if-error=<seconds>
:如新的检测失败,客户端则愿意接收陈旧的响应,时间表明等待时间。优点特色:
劣势问题:
让客户端与服务器之间能实现缓存文件是否更新的验证、提高缓存的复用率,将缓存信息中的Etag和Last-Modified经过请求发送给服务器,由服务器校验,返回304状态码时,浏览器直接使用缓存。
1)协商缓存-协商缓存-Last-Modified/If-Modified-since
Last-Modified
字段告知客户端,资源最后一次被修改的时间,例如 Last-Modified: Mon, 10 Nov 2018 09:10:11 GMT
Last-Modified
的值写入到请求头的 If-Modified-Since
字段If-Modified-Since
的值与 Last-Modified
字段进行对比。若是相等,则表示未修改,响应 304;反之,则表示修改了,响应 200 状态码,并返回数据。优点特色:
劣势问题:
2)协商缓存-Etag/If-None-match
Etag
和 If-None-Match
Etag
存储的是文件的特殊标识(通常都是 hash 生成的),服务器存储着文件的 Etag
字段。以后的流程和 Last-Modified
一致,只是 Last-Modified
字段和它所表示的更新时间改变成了 Etag
字段和它所表示的文件 hash,把 If-Modified-Since
变成了 If-None-Match
。服务器一样进行比较,命中返回 304, 不命中返回新资源和 200。优点特色:
劣势问题:
强缓存:服务器通知浏览器一个缓存时间,在缓存时间内,下次请求,直接用缓存,不在时间内,执行其余缓存策略
Cache-Contrl: max-age=3000
公司:爱范儿
分类:JavaScript
1)懒加载:获取首屏数据,后边的数据进行滑动加载请求
2)利用骨架屏提高用户体验
3)PreloadJS预加载
使用PreloadJS库,PreloadJS提供了一种预加载内容的一致方式,以便在HTML应用程序中使用。预加载可使用HTML标签以及XHR来完成。默认状况下,PreloadJS会尝试使用XHR加载内容,由于它提供了对进度和完成事件的更好支持,可是因为跨域问题,使用基于标记的加载可能更好。
4)除了添加前端loading和超时404页面外,接口部分能够添加接口缓存和接口的预加载
上面的方法,能够根据业务需求选择组合使用。
因此咱们能够大体分为在
首先先上一张经典到不能再经典的图
其中cnd在dns阶段, dom渲染在processing onload阶段
上图从 promot for unload 到 onload 的过程这么多步骤, 在用户体验来讲, 一个页面从加载到展现超过 4 秒, 就会有一种很是直观的卡顿现象, 其中 load 对应的位置是 onLoad 事件结束后, 才开始构建 dom 树, 可是用户不必定是关心当前页面是不是完成了资源的下载; 每每是一个页面开始出现可见元素开始FCP 首次内容绘制或者是FC 首次绘制 此时用户视觉体验开始, 到TTI(可交互时间) , 可交互元素的出现, 意味着,用户交互体验开始, 这时候用户就能够愉快的浏览使用咱们的页面啦;
因此这个问题的主要痛点是须要缩短到达 TTI 和 FCP 的时间
可是这里已知进入咱们详情页面时, 接口数据返回速度是很慢的, FCP 和 FC , 以及加快到达 TTI , 就须要咱们页面预处理了
第一次 进入详情页面, 可使用骨架图进行模拟 FC 展现, 而且骨架图, 可以使用背景图且行内样式的方式对首次进入详情页面进行展现, 对于请求过慢的详情接口使用 worker 进程, 对详情的接口请求丢到另一个工做线程进行请求, 页面渲染其余已返回数据的元素; 当很慢的数据回来后, 须要对页面根据商品 id 签名为 key 进行 webp 或者是缩略图商品图的 cnd 路径 localStorage 的缓存, 商品 id 的签名由放在 cookie 并设置成 httpOnly
非第一次 进入详情页时, 前端可经过特定的接口请求回来对应的商品 id 签名的 cookieid, 读取 localStorage 的商品图片的缓存数据, 这样对于第一次骨架图的展现时间就能够缩短, 快速到达 TTI 与用户交互的时间, 再经过 worker 数据, 进行高清图片的切换
对于缓存图片地址的处理, 虽然说缓存图片是放在 localStorage 中, 不会用大小限制, 可是太多也是很差的, 这里使用 LRU 算法对图片以及其余 localStorage 进行清除处理, 对于超过 7 天的数据进行清理 localStorage 详情页的数据, 数据结构以下:
"读取后端的cookieID": {
"path": "对应cdn图片的地址",
"time": "缓存时间戳",
"size": "大小"
}
复制代码
这里有两种情景
移动端, 对于移动端, 通常不会出现大量图片, 通常一个商品详情页, 不会超过 100 张图片资源; 这时候, 选择懒加载方案; 根据 GitHub 现有的不少方案, 当前滑动到可被观察的元素后才加载当前可视区域的图片资源, 一样使用的是 Intersection Observer API ; 好比 vue 的一个库 vue-lazy , 这个库就是对 Intersection_Observer_API 进行封装, 对可视区域的 img 便签进行 data-src 和 src 属性替换
第二个状况, pc 端, 可能会出现大量的 img 标签, 可能多达 300~400 张, 这时候, 使用懒加载, 用户体验就不太好了; 好比说: 当用户在查看商品说明介绍时, 这些商品说明和介绍有可能只是一张张图片, 当用户很快速的滑动时, 页面还没懒加载完, 用户就有可能看不到想看的信息; 鉴于会出现这种状况, 这里给出一个方案就是, img 出现一张 load 一张; 实现以下:
// 这里针对非第一次进入详情页,
//当前localStorage已经有了当前详情页商品图片的缩略图
for(let i = 0; i < worker.img.length; i++) {
// nodeList是对应img标签,
// 注意, 这里对应的nodeList必定要使用内联style把位置大小设置好, 避免大量的重绘重排
const img = nodeList[i]
img.src = worker.img['path'];
img.onerror = () => {
// 将替换失败或者加载失败的图片降级到缩略图,
// 即缓存到localStorage的缩略图或者webp图
// 兼容客户端处理webp失败的状况
}
}
复制代码
触发重排的操做主要是几何因素:
尽可能减小上面这些产生重绘重排的操做
好比说:
这里产生很大的重绘重排主要发生在 worker 回来的数据替换页面中的图片 src 这一步
// 该节点为img标签的父节点
const imgParent = docucment.getElementById('imgParent');
// 克隆当前须要替换img标签的父元素下全部的标签
const newImgParent = imgParent.cloneNode(true);
const imgParentParent = docucment.getElementById('imgParentParent');
for(let i = 0; i < newImgParent.children.length; i++) {
// 批量获取完全部img标签后, 再进行重绘
newImgParent.children[i].src = worker.img[i].path;
}
// 经过img父节点的父节点, 来替换整个img父节点
// 包括对应的全部子节点, 只进行一次重绘操做
imgParentParent.replaceChild(newImgParent, imgParent);
复制代码
注意被阻塞的css资源
众所周知, css的加载会阻塞浏览器其余资源的加载, 直至CSSOM CSS OBJECT MODEL 构建完成, 而后再挂在DOM树上, 浏览器依次使用渲染树来布局和绘制网页。
不少人都下意识的知道, 将css文件一概放到head标签中是比较好的, 可是为何将css放在head标签是最后了呢?
咱们用淘宝作例子
好比这种没有css样式的页面称之为FOUC(内容样式短暂失效), 可是这种状况通常出如今ie系列以及前期的浏览器身上; 就是当cssom在domtree生成后, 依然还没完成加载出来, 先展现纯html代码的页面一会再出现正确的带css样式的页面;
减小不一样页面的css代码加载
对于电商页面, 有些在头部的css代码有些是首页展现的有些是特定状况才展现的, 好比当咱们须要减小一些css文件大小可是当前网站又须要多屏展现, 这时候, 不少人都会想到是媒体查询, 没错方向是对的, 可是怎样的媒体查询才对css文件保持足够的小呢, 可使用link标签媒体查询,看下边的的例子:
<link href="base.css" rel="stylesheet">
<link href="other.css" rel="stylesheet" media="(min-width: 750px)">
复制代码
第一个css资源表示全部页面都会加载, 第二个css资源, 宽度在750px才会加载, 默认media="all"
在一些需求写css媒体查询的网站, 不要在css代码里面写, 最好写两套css代码, 经过link媒体查询去动态加载, 这样就能很好的减轻网站加载css文件的压力
这种js代码, 是那些关于埋点, 本地日记, 以及动态修改css代码, 读取页面成型后的信息的一些js代码, 这种一概放在同域下的localStorage上面, 什么是同域下的localStorage
这里仍是以天猫为例
公司:CVTE
分类:JavaScript
单点登陆SSO(Single Sign On),是一个多系统共存的环境下,用户在一处登陆后,就不用在其余系统中登陆,也就是用户的一次登陆获得其余全部系统的信任
好比现有业务系统A、B、C以及SSO系统,第一次访问A系统时,发现没有登陆,引导用户到SSO系统登陆,根据用户的登陆信息,生成惟一的一个凭据token,返回给用户。后期用户访问B、C系统的时候,携带上对应的凭证到SSO系统去校验,校验经过后,就能够单点登陆;
单点登陆在大型网站中使用的很是频繁,例如,阿里旗下有淘宝、天猫、支付宝等网站,其背后的成百上千的子系统,用户操做一次或者交易可能涉及到不少子系统,每一个子系统都须要验证,因此提出,用户登陆一次就能够访问相互信任的应用系统
单点登陆有一个独立的认证中心,只有认证中心才能接受用户的用户名和密码等信息进行认证,其余系统不提供登陆入口,只接受认证中心的间接受权。间接受权经过令牌实现,当用户提供的用户名和密码经过认证中心认证后,认证中心会建立受权令牌,在接下来的跳转过程当中,受权令牌做为参数发送给各个子系统,子系统拿到令牌即获得了受权,而后建立局部会话。
单点登陆有同域和跨域两种场景
适用场景:都是企业本身的系统,全部系统都使用同一个一级域名经过不一样的二级域名来区分。
举个例子:公司有一个一级域名为 zlt.com ,咱们有三个系统分别是:门户系统(sso.zlt.com)、应用1(app1.zlt.com)和应用2(app2.zlt.com),须要实现系统之间的单点登陆,实现架构以下
核心原理:
单点登陆之间的系统域名不同,例如第三方系统。因为域名不同不能共享Cookie了,须要的一个独立的受权系统,即一个独立的认证中心(passport),子系统的登陆都可以经过passport,子系统自己将不参与登陆操做,当一个系统登陆成功后,passprot将会颁发一个令牌给子系统,子系统能够拿着令牌去获取各自的保护资源,为了减小频繁认证,各个子系统在被passport受权之后,会创建一个局部会话,在必定时间内无需再次向passport发起认证
基本原理
登陆流程
注销流程
公司:58
分类:Html
当对Dom元素进行一系列操做时,对Dom进行访问和修改Dom引发的重绘和重排都比较消耗性能,因此关于操做Dom,应该从如下几点出发:
首先无论在什么场景下。操做Dom通常首先会去访问Dom,尤为是像循环遍历这种时间复杂度可能会比较高的操做。那么能够在循环以前就将主节点,没必要循环的Dom节点先获取到,那么在循环里就能够直接引用,而没必要去从新查询。
let rootElem = document.querySelector('#app');
let childList = rootElem.child; // 假设全是dom节点
for(let i = 0;i<childList.len;j++){
/** * 根据条件对应操做 */
}
复制代码
利用document.createDocumentFragment()
方法建立文档碎片节点,建立的是一个虚拟的节点对象。向这个节点添加dom节点,修改dom节点并不会影响到真实的dom结构。
咱们能够利用这一点先将咱们须要修改的dom一并修改完,保存至文档碎片中,而后用文档碎片一次性的替换真是的dom节点。与虚拟dom相似,一样达到了不频繁修改dom而致使的重排跟重绘的过程。
let fragment = document.createDocumentFragment();
const operationDomHandle = (fragment) =>{
// 操做
}
operationDomHandle(fragment);
// 而后最后再替换
rootElem.replaceChild(fragment,oldDom);
复制代码
这样就只会触发一次回流,效率会获得很大的提高。若是须要对元素进行复杂的操做(删减、添加子节点),那么咱们应当先将元素从页面中移除,而后再对其进行操做,或者将其复制一个(cloneNode()),在内存中进行操做后再替换原来的节点。
var clone=old.cloneNode(true);
operationDomHandle(clone);
rootElem.replaceChild(clone,oldDom)
复制代码
批量读,一次性写。先对一个不在render tree上的节点进行操做,再把这个节点添加回render tree。这样只会触发一次DOM操做。 使用requestAnimationFrame()
,把任何致使重绘的操做放入requestAnimationFrame
js模拟DOM树并对DOM树操做的一种技术。virtual DOM是一个纯js对象(字符串对象),因此对他操做会高效。
利用virtual dom,将dom抽象为虚拟dom,在dom发生变化的时候先对虚拟dom进行操做,经过dom diff算法将虚拟dom和原虚拟dom的结构作对比,最终批量的去修改真实的dom结构,尽量的避免了频繁修改dom而致使的频繁的重排和重绘。
/* 根据传入参数n(数字)对一维数组(纯数字)按照距离n最近的顺序排序。 (距离即数字与n的差值的绝对值) */
var arr = [7, 28, -1, 0, 7, 33];
function sort(n) {
//your code
}
复制代码
公司:高思教育
分类:算法
var arr = [7, 28, -28, 0, 7, 33];
function sort(n) {
arr.sort((a, b) => {
return Math.abs(a - n) - Math.abs(b - n);
})
console.log(arr);
}
sort(28);
复制代码
var nearbySort = function (n, arr) {
var splitedArr = [];
for (var i = 0; i < arr.length; i++) {
splitedArr.push([arr[i]]);
}
while(splitedArr.length > 1) { // 两两一组,依次归并 ~ 递归就不用了~
var half = Math.ceil(splitedArr.length / 2);
for(var j = 0; j < half; j++) {
splitedArr[j] = mergeArr(n, splitedArr[j], splitedArr[j + half]);
}
console.log(half, splitedArr);
splitedArr.length = half;
}
return (splitedArr.length === 1) ? splitedArr[0] : [];
}
var getDistance = function (n, m){ // 获取n 与 m数值差别(绝对值)
return n > m ? (n - m) : (m - n);
}
var mergeArr = function(n, left, right){ // left right为已排序数组
if (!left) {
return right;
}
if (!right) {
return left;
}
var sortedArr = [], leftIndex = 0, rightIndex = 0, leftLen = left.length, rightLen = right.length;
while(leftIndex < leftLen || rightIndex < rightLen){
var leftNum = left[leftIndex], rightNum = right[rightIndex];
if (leftNum === undefined) {
sortedArr.push(rightNum);
rightIndex++;
} else if (rightNum === undefined) {
sortedArr.push(leftNum);
leftIndex++;
} else {
var leftDistance = getDistance(n, leftNum);
var rightDistanc = getDistance(n, rightNum);
if (leftDistance <= rightDistanc) {
sortedArr.push(leftNum);
leftIndex++;
} else {
sortedArr.push(rightNum);
rightIndex++;
}
}
}
return sortedArr;
}
复制代码
var getDistance = function (n, m){ // 获取n 与 m数值差别(绝对值)
return n > m ? (n - m) : (m - n);
}
var nearbySort = function (n, arr) {
var distanceObj = {}, len = arr.length, distance, maxDistance = 0, result = [];
for(var i = 0; i < len; i++){
distance = getDistance(n, arr[i]);
if(distance > maxDistance){
maxDistance = distance;
}
if (distanceObj[distance]) {
distanceObj[distance].push(arr[i]);
} else {
distanceObj[distance] = [arr[i]];
}
}
for(var j = 0; j <= maxDistance; j++) { // 两层for, 但内层循环数固定, 总循环固定O(n);
if (distanceObj[j]) {
var eachDistance = distanceObj[j];
var eachLength = eachDistance.length;
for(var m = 0; m < eachLength; m++) {
result.push(eachDistance[m])
}
}
}
return result;
}
复制代码