1. 什么是重绘和回流?
要了解什么是重绘和回流,首先要了解浏览器是如何渲染UI的
那么浏览器是如何渲染UI的呢?
① 浏览器获取HTML文件,然后对文件进行解析,形成 DOM Tree
。
② 与此同时,进行CSS解析,生成 Style Rules
。
③ 接着将 DOM Tree
与 Style Rules
合成为 Render Tree
。
④ 接着进入布局(Layout)阶段,也就是为每个节点分配一个应出现在屏幕上的确切坐标。
⑤ 随后调用GPU进行绘制(Paint),遍历 Render Tree
的节点,并将元素呈现出来。
现在知道了浏览器是如何渲染UI的,再来说说什么是重绘与回流👇。
重绘
: 当渲染树中的一些元素需要更新属性,而这些属性只是影响元素的外观、风格,而不会影响布局,就会触发重绘。(比如 color, border-radius)
回流
:回流又之为重排,当Render Tree
中的一部分(或者全部)因元素的规模,尺寸,布局等改变,而需要重新构建页面,就会触发回流。(比如height, display,padding)
注意:重绘不一定回流,回流一定重绘,回流所需的成本比重绘高的多。
那么如何避免回流?
css
- 避免使用 table 布局。
- 尽可能在 DOM 树的最末端改变 class。
- 避免设置多层内联样式。
- 将动画效果应用到 position 属性为 absolute 或 fixed 的元素上。
- 避免使用CSS表达式(例如:calc())。
JavaScript
- 避免频繁操作样式,最好一次性重写 style 属性,或者将样式列表定义为 class 并一次性更改 class 属性。
- 避免频繁操作 DOM,创建一个 documentFragment,在它上面应用所有DOM操作,最后再把它添加到文档中。
- 也可以先为元素设置 display: none,操作结束后再把它显示出来。因为在 display 属性为 none 的元素上进行的DOM操作不会引发回流和重绘。
- 避免频繁读取会引发回流/重绘的属性,如果确实需要多次使用,就用一个变量缓存起来。
- 对具有复杂动画的元素使用绝对定位,使它脱离文档流,否则会引起父元素及后续元素频繁回流。
详细资料可以参考:浏览器的回流与重绘 (Reflow & Repaint)
2. 伪类与伪元素的区别?
css引入伪类
和伪元素
概念是为了格式化文档树以外的信息。也就是说,伪类和伪元素是用来修饰不在文档树中的部分,比如,一句
话中的第一个字母,或者是列表中的第一个元素。
「伪类」:
伪类用于当已有的元素处于某个状态时,为其添加对应的样式,这个状态是根据用户行为而动态变化的。比如说,当用户悬停在指定的
元素时,我们可以通过 :hover
来描述这个元素的状态。
「伪元素」:
伪元素用于创建一些不在文档树中的元素,并为其添加样式。它们允许我们为元素的某些部分设置样式。比如说,我们可以通过 ::before
来在一个元素前增加一些文本,并为这些文本添加样式。虽然用户可以看到这些文本,但是这些文本实际上不在文档树中。
有时你会发现伪元素使用了两个冒号(::)而不是一个冒号(:)。这是 CSS3 的一部分,并尝试区分伪类和伪元素。大多数浏览器都支持这两个值。按照规则应该使用(::)而不是(:),从而区分伪类和伪元素。但是,由于在旧版本的W3C规范并未对此进行特别区分,因此目前绝大多数的浏览器都支持使用这两种方式表示伪元素。
3. 写一个方法对数组进行排序
利用 sort 排序
首先来了解一下 sort 这个方法。
MDN中是这样定义的
sort()
方法用原地算法
对数组的元素进行排序,并返回数组。默认排序顺序是在将元素转换为字符串
,然后比较它们的 UTF-16
代码单元值序列时构建的
由于它取决于具体实现,因此无法保证排序的时间和空间复杂性。
let months = ['March', 'Jan', 'Feb', 'Dec','daming','alin'];
months.sort();
console.log(months); // ["Dec", "Feb", "Jan", "March", "alin", "daming"]
let array1 = [1, 30, 4, 21, 100000];
array1.sort();
console.log(array1); // [1, 100000, 21, 30, 4]
语法:
arr.sort([compareFunction])
参数:
compareFunction
可选
用来指定按某种顺序进行排列的函数。如果省略,元素按照转换为的字符串的各个字符的Unicode位点进行排序。
firstEl
第一个用于比较的元素。
secondEl
第二个用于比较的元素。
返回值:
排序后的数组。请注意,数组已原地排序,并且不进行复制。
注意:如果没有指明 compareFunction ,那么元素会按照转换为的字符串的诸个字符的Unicode位点进行排序。例如 "Banana" 会被排列到 "cherry" 之前。当数字按由小到大排序时,9 出现在 80 之前,但因为(没有指明 compareFunction),比较的数字会先被转换为字符串,所以在Unicode顺序上 "80" 要比 "9" 要靠前。
如果指明了 compareFunction
,那么数组会按照调用该函数的返回值排序。即 a 和 b 是两个将要被比较的元素:
- 如果
compareFunction(a, b)
小于 0 ,那么 a 会被排列到 b 之前; - 如果
compareFunction(a, b
) 等于 0 , a 和 b 的相对位置不变。备注: ECMAScript 标准并不保证这一行为,而且也不是所有浏览器都会遵守(例如 Mozilla 在 2003 年之前的版本); - 如果
compareFunction(a, b)
大于 0 , b 会被排列到 a 之前。 compareFunction(a, b)
必须总是对相同的输入返回相同的比较结果,否则排序的结果将是不确定的。
举个例子🌰:
var numbers = [4, 2, 5, 1, 3];
numbers.sort(function(a, b) {
return a - b;
});
console.log(numbers);
// 也可以写成:
var numbers = [4, 2, 5, 1, 3];
numbers.sort((a, b) => a - b);
console.log(numbers);
// [1, 2, 3, 4, 5]
当排序非 ASCII 字符的字符串(如包含类似 e, é, è, a, ä 等字符的字符串)。一些非英语语言的字符串需要使用 String.localeCompare
。这个函数可以将函数排序到正确的顺序。
var items = ['réservé', 'premier', 'cliché', 'communiqué', 'café', 'adieu'];
items.sort(function (a, b) {
return a.localeCompare(b);
});
// items is ['adieu', 'café', 'cliché', 'communiqué', 'premier', 'réservé']
还有一些经典算法可以参考: 几种经典排序算法的JS实现方法
4. 数组去重
1. 利用 es6 里的 Set 去重
Set
对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用。
Set
对象是值的集合,你可以按照插入的顺序迭代它的元素。 Set 中的元素只会出现一次,即 Set 中的元素是唯一的。
NaN
和 undefined
都可以被存储在 Set
中, NaN 之间被视为相同的值(NaN被认为是相同的,尽管 NaN !== NaN)。
var arr = [12,12,'true','true',true,true,5,5,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
var newArr = [...new Set(arr)];
不考虑兼容性,这种去重的方法代码最少。这种方法还无法去掉“{}”空对象,后面的高阶方法会添加去掉重复“{}”的方法。
2. 利用 for 嵌套 for,然后 splice 去重(ES5中最常用)
function unique(arr){
// 双层循环,外层循环元素,内层循环时比较值。
for(var i=0; i<arr.length; i++){
for(var j=i+1; j<arr.length; j++){
if(arr[i]==arr[j]){ //第一个等同于第二个,splice方法删除第二个
arr.splice(j,1);
j--;
}
}
}
return arr;
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
// NaN和{}没有去重,两个null直接消失了
3. 利用 indexOf 去重
新建一个空的结果数组,for
循环原数组,判断结果数组是否存在当前元素,如果有相同的值则跳过,不相同则 push
进数组。
function unique(arr) {
if (!Array.isArray(arr)) {
console.log('type error!')
return
}
var array = [];
for (var i = 0; i < arr.length; i++) {
if (array .indexOf(arr[i]) === -1) {
array .push(arr[i])
}
}
return array;
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
// NaN、{}没有去重
4. 利用 sort()
利用 sort()
排序方法,然后根据排序后的结果进行遍历及相邻元素比对。
function unique(arr) {
if (!Array.isArray(arr)) {
console.log('type error!')
return;
}
arr = arr.sort()
var arrry= [arr[0]];
for (var i = 1; i < arr.length; i++) {
if (arr[i] !== arr[i-1]) {
arrry.push(arr[i]);
}
}
return arrry;
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
// NaN、{}没有去重
5. 利用 includes
function unique(arr) {
if (!Array.isArray(arr)) {
console.log('type error!')
return
}
var array =[];
for(var i = 0; i < arr.length; i++) {
if( !array.includes( arr[i]) ) {//includes 检测数组是否有某个值
array.push(arr[i]);
}
}
return array
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
// {}没有去重
6. 利用递归去重
function unique(arr) {
var array= arr;
var len = array.length;
array.sort(function(a,b){ //排序后更加方便去重
return a - b;
})
function loop(index){
if(index >= 1){
if(array[index] === array[index-1]){
array.splice(index,1);
}
loop(index - 1); //递归loop,然后数组去重
}
}
loop(len-1);
return array;
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
参考文章:JavaScript数组去重(12种方法,史上最全)
5. position 的值 relative 和 absolute 定位原点是?
① relative
定位的元素,是相对于元素本身的正常位置来进行定位的。
② absolute
定位的元素,是相对于它的第一个 position 值不为 static 的祖先元素的 paddingbox 来进行定位的。这句话
我们可以这样来理解,我们首先需要找到绝对定位元素的一个 position 的值不为 static 的祖先元素,然后相对于这个祖先元素的 paddingbox 来定位,也就是说在计算定位距离的时候,padding 的值也要算进去。
6. null 和 undefined 的区别?
首先 Undefined
和 Null
都是基本数据类型,这两个基本数据类型分别都只有一个值,就是 undefined 和 null。
undefined
代表的含义是未定义
,null
代表的含义是空对象
。一般变量声明了但还没有定义的时候会返回 undefined,null
主要用于赋值给一些可能会返回对象的变量,作为初始化。
undefined
在 js 中不是一个保留字
,这意味着我们可以使用 undefined 来作为一个变量名,这样的做法是非常危险的,它
会影响我们对 undefined 值的判断。但是我们可以通过一些方法获得安全的 undefined 值,比如说 void 0。
当我们对两种类型使用 typeof
进行判断的时候,Null
类型化会返回 “object”,这是一个历史遗留的问题。当我们使用双等号
对两种类型的值进行比较时会返回 true,使用三个等号
时会返回 false。
详细资料可以参考:《JavaScript 深入理解之 undefined 与 null》
7. 前端常用性能优化
前端性能优化主要是为了提高页面的加载速度,优化用户的访问体验。可以从这些方面来进行优化。
1. 页面的内容方面
① 通过文件合并、css 雪碧图、使用 base64 等方式来减少 HTTP 请求数,避免过多的请求造成等待的情况。
② 通过 DNS 缓存等机制来减少 DNS 的查询次数。
③ 通过设置缓存策略,对常用不变的资源进行缓存。
④ 使用延迟加载的方式,来减少页面首屏加载时需要请求的资源。延迟加载的资源当用户需要访问时,再去请求加载。
⑤ 通过用户行为,对某些资源使用预加载的方式,来提高用户需要访问资源时的响应速度。
2. 服务器方面
① 使用 CDN 服务,来提高用户对于资源请求时的响应速度。
② 服务器端启用 Gzip、Deflate 等方式对于传输的资源进行压缩,减小文件的体积。
③ 尽可能减小 cookie 的大小,并且通过将静态资源分配到其他域名下,来避免对静态资源请求时携带不必要的 cookie
3. CSS 和 JavaScript 方面
① 把样式表放在页面的 head 标签中,减少页面的首次渲染的时间。
② 避免使用 @import 标签。
③ 尽量把 js 脚本放在页面底部或者使用 defer 或 async 属性,避免脚本的加载和执行阻塞页面的渲染。
④ 通过对 JavaScript 和 CSS 的文件进行压缩,来减小文件的体积。
更多可参考:前端性能优化之雅虎35条军规