前端知识点总结 (三)

267 阅读13分钟

1. 什么是重绘和回流?

要了解什么是重绘和回流,首先要了解浏览器是如何渲染UI的

那么浏览器是如何渲染UI的呢?

① 浏览器获取HTML文件,然后对文件进行解析,形成 DOM Tree

② 与此同时,进行CSS解析,生成 Style Rules

③ 接着将 DOM TreeStyle 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(ab) {
  return a - b;
});
console.log(numbers);

// 也可以写成:
var numbers = [4, 2, 5, 1, 3]; 
numbers.sort((ab) => 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 (ab) {
  return a.localeCompare(b);
});

// items is ['adieu''café''cliché''communiqué''premier''réservé']

还有一些经典算法可以参考: 几种经典排序算法的JS实现方法

4. 数组去重

1. 利用 es6 里的 Set 去重

Set 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用。

Set 对象是值的集合,你可以按照插入的顺序迭代它的元素。 Set 中的元素只会出现一次,即 Set 中的元素是唯一的。

NaNundefined 都可以被存储在 Set 中, NaN 之间被视为相同的值(NaN被认为是相同的,尽管 NaN !== NaN)。

var arr = [12,12,'true','true',true,true,5,5,false,falseundefined,undefinednull,nullNaNNaN,'NaN'00'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'00'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,falseundefined,undefinednull,nullNaNNaN,'NaN'00'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'00'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,falseundefined,undefinednull,nullNaNNaN,'NaN'00'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'00'a''a',{},{}];
console.log(unique(arr))

参考文章:JavaScript数组去重(12种方法,史上最全)

5. position 的值 relative 和 absolute 定位原点是?

relative 定位的元素,是相对于元素本身的正常位置来进行定位的。

absolute 定位的元素,是相对于它的第一个 position 值不为 static 的祖先元素的 paddingbox 来进行定位的。这句话 我们可以这样来理解,我们首先需要找到绝对定位元素的一个 position 的值不为 static 的祖先元素,然后相对于这个祖先元素的 paddingbox 来定位,也就是说在计算定位距离的时候,padding 的值也要算进去。

6. null 和 undefined 的区别?

首先 UndefinedNull 都是基本数据类型,这两个基本数据类型分别都只有一个值,就是 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条军规