关于前端:社招前端必会面试题

44次阅读

共计 19664 个字符,预计需要花费 50 分钟才能阅读完成。

OSI 七层模型

ISO为了更好的使网络应用更为遍及,推出了 OSI 参考模型。

(1)应用层

OSI参考模型中最靠近用户的一层,是为计算机用户提供利用接口,也为用户间接提供各种网络服务。咱们常见应用层的网络服务协定有:HTTPHTTPSFTPPOP3SMTP等。

  • 在客户端与服务器中常常会有数据的申请,这个时候就是会用到 http(hyper text transfer protocol)(超文本传输协定) 或者https. 在后端设计数据接口时,咱们经常应用到这个协定。
  • FTP是文件传输协定,在开发过程中,集体并没有波及到,然而我想,在一些资源网站,比方 百度网盘` 迅雷 ` 应该是基于此协定的。
  • SMTPsimple mail transfer protocol(简略邮件传输协定)。在一个我的项目中,在用户邮箱验证码登录的性能时,应用到了这个协定。

(2)表示层

表示层提供各种用于应用层数据的编码和转换性能, 确保一个零碎的应用层发送的数据能被另一个零碎的应用层辨认。如果必要,该层可提供一种规范示意模式,用于将计算机外部的多种数据格式转换成通信中采纳的规范示意模式。数据压缩和加密也是表示层可提供的转换性能之一。

在我的项目开发中,为了不便数据传输,能够应用 base64 对数据进行编解码。如果按性能来划分,base64应该是工作在表示层。

(3)会话层

会话层就是负责建设、治理和终止表示层实体之间的通信会话。该层的通信由不同设施中的应用程序之间的服务申请和响应组成。

(4)传输层

传输层建设了主机端到端的链接,传输层的作用是为下层协定提供端到端的牢靠和通明的数据传输服务,包含解决差错控制和流量管制等问题。该层向高层屏蔽了上层数据通信的细节,使高层用户看到的只是在两个传输实体间的一条主机到主机的、可由用户管制和设定的、牢靠的数据通路。咱们通常说的,TCP UDP就是在这一层。端口号既是这里的“端”。

(5)网络层

本层通过 IP 寻址来建设两个节点之间的连贯,为源端的运输层送来的分组,抉择适合的路由和替换节点,正确无误地依照地址传送给目标端的运输层。就是通常说的 IP 层。这一层就是咱们常常说的 IP 协定层。IP协定是 Internet 的根底。咱们能够这样了解,网络层规定了数据包的传输路线,而传输层则规定了数据包的传输方式。

(6)数据链路层

将比特组合成字节, 再将字节组合成帧, 应用链路层地址 (以太网应用 MAC 地址)来拜访介质, 并进行过错检测。
网络层与数据链路层的比照,通过下面的形容,咱们或者能够这样了解,网络层是布局了数据包的传输路线,而数据链路层就是传输路线。不过,在数据链路层上还减少了差错控制的性能。

(7)物理层

理论最终信号的传输是通过物理层实现的。通过物理介质传输比特流。规定了电平、速度和电缆针脚。罕用设施有(各种物理设施)集线器、中继器、调制解调器、网线、双绞线、同轴电缆。这些都是物理层的传输介质。

OSI 七层模型通信特点:对等通信 对等通信,为了使数据分组从源传送到目的地,源端 OSI 模型的每一层都必须与目标端的对等层进行通信,这种通信形式称为对等层通信。在每一层通信过程中,应用本层本人协定进行通信。

—- 问题知识点分割线 —-

position 的属性有哪些,区别是什么

position 有以下属性值:

属性值 概述
absolute 生成相对定位的元素,绝对于 static 定位以外的一个父元素进行定位。元素的地位通过 left、top、right、bottom 属性进行规定。
relative 生成绝对定位的元素,绝对于其原来的地位进行定位。元素的地位通过 left、top、right、bottom 属性进行规定。
fixed 生成相对定位的元素,指定元素绝对于屏幕视⼝(viewport)的地位来指定元素地位。元素的地位在屏幕滚动时不会扭转,⽐如回到顶部的按钮⼀般都是⽤此定位⽅式。
static 默认值,没有定位,元素呈现在失常的文档流中,会疏忽 top, bottom, left, right 或者 z-index 申明,块级元素从上往下纵向排布,⾏级元素从左向右排列。
inherit 规定从父元素继承 position 属性的值

后面三者的定位形式如下:

  • relative: 元素的定位永远是绝对于元素本身地位的,和其余元素没关系,也不会影响其余元素。
  • fixed: 元素的定位是绝对于 window(或者 iframe)边界的,和其余元素没有关系。然而它具备破坏性,会导致其余元素地位的变动。
  • absolute: 元素的定位绝对于前两者要简单许多。如果为 absolute 设置了 top、left,浏览器会依据什么去确定它的纵向和横向的偏移量呢?答案是浏览器会递归查找该元素的所有父元素,如果找到一个设置了 position:relative/absolute/fixed 的元素,就以该元素为基准定位,如果没找到,就以浏览器边界定位。如下两个图所示:

—- 问题知识点分割线 —-

网络劫持有哪几种,如何防备?

⽹络劫持分为两种:

(1)DNS 劫持: (输⼊京东被强制跳转到淘宝这就属于 dns 劫持)

  • DNS 强制解析: 通过批改运营商的本地 DNS 记录,来疏导⽤户流量到缓存服务器
  • 302 跳转的⽅式: 通过监控⽹络出⼝的流量,分析判断哪些内容是能够进⾏劫持解决的, 再对劫持的内存发动 302 跳转的回复,疏导⽤户获取内容

(2)HTTP 劫持: (拜访⾕歌然而⼀直有贪玩蓝⽉的⼴告), 因为 http 明⽂传输, 运营商会批改你的 http 响应内容(即加⼴告)

DNS 劫持因为涉嫌守法,曾经被监管起来,当初很少会有 DNS 劫持,⽽ http 劫持仍然⾮常盛⾏,最无效的方法就是全站 HTTPS,将 HTTP 加密,这使得运营商⽆法获取明⽂,就⽆法劫持你的响应内容。

—- 问题知识点分割线 —-

把握页面的加载过程

网页加载流程

  • 当咱们关上网址的时候,浏览器会从服务器中获取到 HTML 内容
  • 浏览器获取到 HTML 内容后,就开始从上到下解析 HTML 的元素
  • <head>元素内容会先被解析,此时 浏览器还没开始渲染页面

    • 咱们看到 <head> 元素里有用于形容页面元数据的 <meta> 元素,还有一些 <link> 元素波及内部资源(如 图片、CSS 款式 等),此时浏览器会去获取这些内部资源。除此之外,咱们还能看到 <head> 元素中还蕴含着不少的 <script> 元素,这些 <script> 元素通过 src 属性指向内部资源
  • 当浏览器解析到这里时(步骤 3),会暂停解析并下载 JavaScript 脚本
  • 当 JavaScript 脚本下载实现后,浏览器的控制权转交给 JavaScript 引擎。当脚本执行实现后,控制权会交回给渲染引擎,渲染引擎持续往下解析 HTML 页面
  • 此时 <body> 元素内容开始被解析,浏览器开始渲染页面
  • 在这个过程中,咱们看到 <head> 中搁置的 <script> 元素会阻塞页面的渲染过程:把 JavaScript 放在 <head> 里,意味着必须把所有 JavaScript 代码都 下载、解析和解释实现后,能力开始渲染页面
  • 如果内部脚本加载工夫很长(比方始终无奈实现下载),就会造成网页长时间失去响应,浏览器就会出现“假死”状态,用户体验会变得很蹩脚
  • 因而,对于对性能要求较高、须要疾速将内容出现给用户的网页,经常会将 JavaScript 脚本放在 <body> 的最初面。这样 能够防止资源阻塞,页面得以迅速展现 。咱们还能够应用defer/async/preload 等属性来标记 <script> 标签,来管制 JavaScript 的加载程序

提早加载的形式有哪些

js 的加载、解析和执行会阻塞页面的渲染过程,因而咱们心愿 js 脚本可能尽可能的提早加载,进步页面的渲染速度。

几种形式是:

  • 将 js 脚本放在文档的底部,来使 js 脚本尽可能的在最初来加载执行
  • 给 js 脚本增加 defer 属性,这个属性会让脚本的加载与文档的解析同步解析,而后在文档解析实现后再执行这个脚本文件,这样的话就能使页面的渲染不被阻塞。多个设置了 defer 属性的脚本按标准来说最初是程序执行的,然而在一些浏览器中可能不是这样
  • 给 js 脚本增加 async属性,这个属性会使脚本异步加载,不会阻塞页面的解析过程,然而当脚本加载实现后立刻执行 js 脚本,这个时候如果文档没有解析实现的话同样会阻塞。多个 async 属性的脚本的执行程序是不可预测的,个别不会依照代码的程序顺次执行
  • 动态创建 DOM 标签的形式,咱们能够对文档的加载事件进行监听,当文档加载实现后再动静的创立 script 标签来引入 js 脚本

怎么判断页面是否加载实现

  • Load 事件触发代表页面中的 DOMCSSJS,图片曾经全副加载结束。
  • DOMContentLoaded 事件触发代表初始的 HTML 被齐全加载和解析,不须要期待 CSSJS,图片加载

—- 问题知识点分割线 —-

数组去重

第一种:通过 ES6 新个性 Set()
例如:var arr = [1, 2, 3, 1, 2]; var newArr= [...new Set(arr)]
第二种:封装函数利用 {} 和【】function uniqueEasy(arr) {if(!arr instanceof Array) {throw Error('以后传入的不是数组')
    }
    let list = []
    let obj = {}
    arr.forEach(item => {if(!obj[item]) {list.push(item)
            obj[item] = true
        }
    })
    return list
}

当然还有其余的办法,但自己我的项目中个别应用以上两种根本满足

—- 问题知识点分割线 —-

层叠上下文

元素晋升为一个比拟非凡的图层,在三维空间中 (z 轴) 高出一般元素一等。

触发条件

  • 根层叠上下文(html)
  • position
  • css3属性

    • flex
    • transform
    • opacity
    • filter
    • will-change
    • webkit-overflow-scrolling

层叠等级:层叠上下文在 z 轴上的排序

  • 在同一层叠上下文中,层叠等级才有意义
  • z-index的优先级最高

—- 问题知识点分割线 —-

模块化

js 中当初比拟成熟的有四种模块加载计划:

  • 第一种是 CommonJS 计划,它通过 require 来引入模块,通过 module.exports 定义模块的输入接口。这种模块加载计划是服务器端的解决方案,它是以同步的形式来引入模块的,因为在服务端文件都存储在本地磁盘,所以读取十分快,所以以同步的形式加载没有问题。但如果是在浏览器端,因为模块的加载是应用网络申请,因而应用异步加载的形式更加适合。
  • 第二种是 AMD 计划,这种计划采纳异步加载的形式来加载模块,模块的加载不影响前面语句的执行,所有依赖这个模块的语句都定义在一个回调函数里,等到加载实现后再执行回调函数。require.js 实现了 AMD 标准
  • 第三种是 CMD 计划,这种计划和 AMD 计划都是为了解决异步模块加载的问题,sea.js 实现了 CMD 标准。它和 require.js 的区别在于模块定义时对依赖的解决不同和对依赖模块的执行机会的解决不同。
  • 第四种计划是 ES6 提出的计划,应用 import 和 export 的模式来导入导出模块

在有 Babel 的状况下,咱们能够间接应用 ES6的模块化

// file a.js
export function a() {}
export function b() {}
// file b.js
export default function() {}

import {a, b} from './a.js'
import XXX from './b.js'

CommonJS

CommonJsNode 独有的标准,浏览器中应用就须要用到 Browserify解析了。

// a.js
module.exports = {a: 1}
// or
exports.a = 1

// b.js
var module = require('./a.js')
module.a // -> log 1

在上述代码中,module.exportsexports 很容易混同,让咱们来看看大抵外部实现

var module = require('./a.js')
module.a
// 这里其实就是包装了一层立刻执行函数,这样就不会净化全局变量了,// 重要的是 module 这里,module 是 Node 独有的一个变量
module.exports = {a: 1}
// 根本实现
var module = {exports: {} // exports 就是个空对象
}
// 这个是为什么 exports 和 module.exports 用法类似的起因
var exports = module.exports
var load = function (module) {
    // 导出的货色
    var a = 1
    module.exports = a
    return module.exports
};

再来说说 module.exportsexports,用法其实是类似的,然而不能对 exports 间接赋值,不会有任何成果。

对于 CommonJSES6 中的模块化的两者区别是:

  • 前者反对动静导入,也就是 require(${path}/xx.js),后者目前不反对,然而已有提案, 前者是同步导入,因为用于服务端,文件都在本地,同步导入即便卡住主线程影响也不大。
  • 而后者是异步导入,因为用于浏览器,须要下载文件,如果也采纳同步导入会对渲染有很大影响
  • 前者在导出时都是值拷贝,就算导出的值变了,导入的值也不会扭转,所以如果想更新值,必须从新导入一次。
  • 然而后者采纳实时绑定的形式,导入导出的值都指向同一个内存地址,所以导入值会追随导出值变动
  • 后者会编译成 require/exports 来执行的

AMD

AMD 是由 RequireJS 提出的

AMD 和 CMD 标准的区别?

  • 第一个方面是在模块定义时对依赖的解决不同。AMD 推崇依赖前置,在定义模块的时候就要申明其依赖的模块。而 CMD 推崇就近依赖,只有在用到某个模块的时候再去 require。
  • 第二个方面是对依赖模块的执行机会解决不同。首先 AMD 和 CMD 对于模块的加载形式都是异步加载,不过它们的区别在于模块的执行机会,AMD 在依赖模块加载实现后就间接执行依赖模块,依赖模块的执行程序和咱们书写的程序不肯定统一。而 CMD 在依赖模块加载实现后并不执行,只是下载而已,等到所有的依赖模块都加载好后,进入回调函数逻辑,遇到 require 语句的时候才执行对应的模块,这样模块的执行程序就和咱们书写的程序保持一致了。
// CMD
define(function(require, exports, module) {var a = require("./a");
  a.doSomething();
  // 此处略去 100 行
  var b = require("./b"); // 依赖能够就近书写
  b.doSomething();
  // ...
});

// AMD 默认举荐
define(["./a", "./b"], function(a, b) {
  // 依赖必须一开始就写好
  a.doSomething();
  // 此处略去 100 行
  b.doSomething();
  // ...
})
  • AMDrequirejs 在推广过程中对模块定义的规范化产出,提前执行,推崇依赖前置
  • CMDseajs 在推广过程中对模块定义的规范化产出,提早执行,推崇依赖就近
  • CommonJs:模块输入的是一个值的 copy,运行时加载,加载的是一个对象(module.exports 属性),该对象只有在脚本运行完才会生成
  • ES6 Module:模块输入的是一个值的援用,编译时输入接口,ES6模块不是对象,它对外接口只是一种动态定义,在代码动态解析阶段就会生成。

谈谈对模块化开发的了解

  • 我对模块的了解是,一个模块是实现一个特定性能的一组办法。在最开始的时候,js 只实现一些简略的性能,所以并没有模块的概念,但随着程序越来越简单,代码的模块化开发变得越来越重要。
  • 因为函数具备独立作用域的特点,最原始的写法是应用函数来作为模块,几个函数作为一个模块,然而这种形式容易造成全局变量的净化,并且模块间没有分割。
  • 前面提出了对象写法,通过将函数作为一个对象的办法来实现,这样解决了间接应用函数作为模块的一些毛病,然而这种方法会裸露所有的所有的模块成员,内部代码能够批改外部属性的值。
  • 当初最罕用的是立刻执行函数的写法,通过利用闭包来实现模块公有作用域的建设,同时不会对全局作用域造成净化。

—- 问题知识点分割线 —-

常见的位运算符有哪些?其计算规定是什么?

古代计算机中数据都是以二进制的模式存储的,即 0、1 两种状态,计算机对二进制数据进行的运算加减乘除等都是叫位运算,行将符号位独特参加运算的运算。

常见的位运算有以下几种:

运算符 形容 运算规定
& 两个位都为 1 时,后果才为 1
` ` 两个位都为 0 时,后果才为 0
^ 异或 两个位雷同为 0,相异为 1
~ 取反 0 变 1,1 变 0
<< 左移 各二进制位全副左移若干位,高位抛弃,低位补 0
>> 右移 各二进制位全副右移若干位,负数左补 0,正数左补 1,左边抛弃

1. 按位与运算符(&)

定义: 加入运算的两个数据 按二进制位 进行“与”运算。运算规定:

0 & 0 = 0  
0 & 1 = 0  
1 & 0 = 0  
1 & 1 = 1

总结:两位同时为 1,后果才为 1,否则后果为 0。
例如:3&5 即:

0000 0011 
   0000 0101 
 = 0000 0001

因而 3&5 的值为 1。
留神:正数按补码模式加入按位与运算。

用处:

(1)判断奇偶

只有依据最未位是 0 还是 1 来决定,为 0 就是偶数,为 1 就是奇数。因而能够用 if ((i & 1) == 0) 代替 if (i % 2 == 0) 来判断 a 是不是偶数。

(2)清零

如果想将一个单元清零,即便其全副二进制位为 0,只有与一个各位都为零的数值相与,后果为零。

2. 按位或运算符(|)

定义: 加入运算的两个对象按二进制位进行“或”运算。

运算规定:

0 | 0 = 0
0 | 1 = 1  
1 | 0 = 1  
1 | 1 = 1

总结:加入运算的两个对象只有有一个为 1,其值为 1。
例如:3| 5 即:

0000 0011
  0000 0101 
= 0000 0111

因而,3| 5 的值为 7。
留神:正数按补码模式加入按位或运算。

3. 异或运算符(^)

定义: 加入运算的两个数据按二进制位进行“异或”运算。

运算规定:

0 ^ 0 = 0  
0 ^ 1 = 1  
1 ^ 0 = 1  
1 ^ 1 = 0

总结:加入运算的两个对象,如果两个相应位雷同为 0,相异为 1。
例如:3| 5 即:

0000 0011
  0000 0101 
= 0000 0110

因而,3^5 的值为 6。
异或运算的性质:

  • 交换律:(a^b)^c == a^(b^c)
  • 结合律:(a + b)^c == a^b + b^c
  • 对于任何数 x,都有 x^x=0,x^0=x
  • 自反性: a^b^b=a^0=a;

4. 取反运算符 (~)

定义: 加入运算的一个数据按二进制进行“取反”运算。

运算规定:

~ 1 = 0~ 0 = 1

总结:对一个二进制数按位取反,行将 0 变 1,1 变 0。
例如:~6 即:

0000 0110= 1111 1001

在计算机中,负数用原码示意,正数应用补码存储,首先看最高位,最高位 1 示意正数,0 示意负数。此计算机二进制码为正数,最高位为符号位。
当发现按位取反为正数时,就 间接取其补码,变为十进制:

0000 0110   = 1111 1001 反码:1000 0110 补码:1000 0111

因而,~6 的值为 -7。

5. 左移运算符(<<)

定义: 将一个运算对象的各二进制位全副左移若干位,右边的二进制位抛弃,左边补 0。
设 a=1010 1110,a = a<< 2 将 a 的二进制位左移 2 位、右补 0,即得 a =1011 1000。
若左移时舍弃的高位不蕴含 1,则每左移一位,相当于该数乘以 2。

6. 右移运算符(>>)

定义: 将一个数的各二进制位全副右移若干位,负数左补 0,正数左补 1,左边抛弃。
例如:a=a>>2 将 a 的二进制位右移 2 位,左补 0 或者 左补 1 得看被移数是正还是负。
操作数每右移一位,相当于该数除以 2。

7. 原码、补码、反码

下面提到了补码、反码等常识,这里就补充一下。
计算机中的 有符号数 有三种示意办法,即原码、反码和补码。三种示意办法均有符号位和数值位两局部,符号位都是用 0 示意“正”,用 1 示意“负”,而数值位,三种示意办法各不相同。

(1)原码

原码就是一个数的二进制数。例如:10 的原码为 0000 1010

(2)反码

  • 负数的反码与原码雷同,如:10 反码为 0000 1010
  • 正数的反码为除符号位,按位取反,即 0 变 1,1 变 0。

例如:-10

原码:1000 1010
反码:1111 0101

(3)补码

  • 负数的补码与原码雷同,如:10 补码为 0000 1010
  • 正数的补码是原码除符号位外的所有位取反即 0 变 1,1 变 0,而后加 1,也就是反码加 1。

例如:-10

原码:1000 1010
反码:1111 0101
补码:1111 0110

—- 问题知识点分割线 —-

渲染机制

1. 浏览器如何渲染网页

概述:浏览器渲染一共有五步

  1. 解决 HTML 并构建 DOM 树。
  2. 解决 CSS构建 CSSOM 树。
  3. DOMCSSOM 合并成一个渲染树。
  4. 依据渲染树来布局,计算每个节点的地位。
  5. 调用 GPU 绘制,合成图层,显示在屏幕上

第四步和第五步是最耗时的局部,这两步合起来,就是咱们通常所说的渲染

具体如下图过程如下图所示

渲染

  • 网页生成的时候,至多会渲染一次
  • 在用户拜访的过程中,还会一直从新渲染

从新渲染须要反复之前的第四步 (从新生成布局)+ 第五步(从新绘制) 或者只有第五个步(从新绘制)

  • 在构建 CSSOM 树时,会阻塞渲染,直至 CSSOM树构建实现。并且构建 CSSOM 树是一个非常耗费性能的过程,所以应该尽量保障层级扁平,缩小适度层叠,越是具体的 CSS 选择器,执行速度越慢
  • HTML 解析到 script 标签时,会暂停构建 DOM,实现后才会从暂停的中央从新开始。也就是说,如果你想首屏渲染的越快,就越不应该在首屏就加载 JS 文件。并且 CSS 也会影响 JS 的执行,只有当解析完样式表才会执行 JS,所以也能够认为这种状况下,CSS 也会暂停构建 DOM

2. 浏览器渲染五个阶段

2.1 第一步:解析 HTML 标签,构建 DOM 树

在这个阶段,引擎开始解析 html,解析进去的后果会成为一棵domdom的目标至多有 2

  • 作为下个阶段渲染树状图的输出
  • 成为网页和脚本的交互界面。(最罕用的就是 getElementById 等等)

当解析器达到 script 标签的时候,产生上面四件事件

  1. html解析器进行解析,
  2. 如果是内部脚本,就从内部网络获取脚本代码
  3. 将控制权交给 js 引擎,执行 js 代码
  4. 复原 html 解析器的控制权

由此能够失去第一个论断 1

  • 因为 <script> 标签是阻塞解析的,将脚本放在网页尾部会减速代码渲染。
  • deferasync 属性也能有助于加载内部脚本。
  • defer使得脚本会在 dom 残缺构建之后执行;
  • async标签使得脚本只有在齐全 available 才执行,并且是以非阻塞的形式进行的

2.2 第二步:解析 CSS 标签,构建 CSSOM 树

  • 咱们曾经看到 html 解析器碰到脚本后会做的事件,接下来咱们看下 html 解析器碰到样式表会产生的状况
  • js会阻塞解析,因为它会批改文档 (document)。css 不会批改文档的构造,如果这样的话,仿佛看起来 css 款式不会阻塞浏览器 html 解析。然而事实上 css样式表是阻塞的。阻塞是指当 cssom 树建设好之后才会进行下一步的解析渲染

通过以下伎俩能够加重 cssom 带来的影响

  • script 脚本放在页面底部
  • 尽可能快的加载 css 样式表
  • 将样式表依照 media typemedia query辨别,这样有助于咱们将 css 资源标记成非阻塞渲染的资源。
  • 非阻塞的资源还是会被浏览器下载,只是优先级较低

2.3 第三步:把 DOM 和 CSSOM 组合成渲染树(render tree)

2.4 第四步:在渲染树的根底上进行布局,计算每个节点的几何构造

布局 (layout):定位坐标和大小,是否换行,各种position, overflow, z-index 属性

2.5 调用 GPU 绘制,合成图层,显示在屏幕上

将渲染树的各个节点绘制到屏幕上,这一步被称为绘制painting

3. 渲染优化相干

3.1 Load 和 DOMContentLoaded 区别

  • Load 事件触发代表页面中的 DOMCSSJS,图片曾经全副加载结束。
  • DOMContentLoaded 事件触发代表初始的 HTML 被齐全加载和解析,不须要期待 CSSJS,图片加载

3.2 图层

一般来说,能够把一般文档流看成一个图层。特定的属性能够生成一个新的图层。不同的图层渲染互不影响,所以对于某些频繁须要渲染的倡议独自生成一个新图层,进步性能。但也不能生成过多的图层,会引起副作用。

通过以下几个罕用属性能够生成新图层

  • 3D 变换:translate3dtranslateZ
  • will-change
  • videoiframe 标签
  • 通过动画实现的 opacity 动画转换
  • position: fixed

3.3 重绘(Repaint)和回流(Reflow)

重绘和回流是渲染步骤中的一大节,然而这两个步骤对于性能影响很大

  • 重绘是当节点须要更改外观而不会影响布局的,比方扭转 color 就叫称为重绘
  • 回流是布局或者几何属性须要扭转就称为回流。

回流必定会产生重绘,重绘不肯定会引发回流。回流所需的老本比重绘高的多,扭转深层次的节点很可能导致父节点的一系列回流

以下几个动作可能会导致性能问题

  • 扭转 window 大小
  • 扭转字体
  • 增加或删除款式
  • 文字扭转
  • 定位或者浮动
  • 盒模型

很多人不晓得的是,重绘和回流其实和 Event loop 无关

  • Event loop 执行完Microtasks 后,会判断 document 是否须要更新。因为浏览器是 60Hz 的刷新率,每 16ms 才会更新一次。
  • 而后判断是否有 resize 或者 scroll,有的话会去触发事件,所以 resizescroll 事件也是至多 16ms才会触发一次,并且自带节流性能。
  • 判断是否触发了 media query
  • 更新动画并且发送事件
  • 判断是否有全屏操作事件
  • 执行 requestAnimationFrame 回调
  • 执行 IntersectionObserver 回调,该办法用于判断元素是否可见,能够用于懒加载上,然而兼容性不好
  • 更新界面
  • 以上就是一帧中可能会做的事件。如果在一帧中有闲暇工夫,就会去执行 requestIdleCallback 回调

常见的引起重绘的属性

  • color
  • border-style
  • visibility
  • background
  • text-decoration
  • background-image
  • background-position
  • background-repeat
  • outline-color
  • outline
  • outline-style
  • border-radius
  • outline-width
  • box-shadow
  • background-size

3.4 常见引起回流属性和办法

任何会扭转元素几何信息 (元素的地位和尺寸大小) 的操作,都会触发重排,上面列一些栗子

  • 增加或者删除可见的 DOM 元素;
  • 元素尺寸扭转——边距、填充、边框、宽度和高度
  • 内容变动,比方用户在 input 框中输出文字
  • 浏览器窗口尺寸扭转——resize事件产生时
  • 计算 offsetWidthoffsetHeight 属性
  • 设置 style 属性的值

回流影响的范畴

因为浏览器渲染界面是基于散失布局模型的,所以触发重排时会对四周 DOM 重新排列,影响的范畴有两种

  • 全局范畴:从根节点 html 开始对整个渲染树进行从新布局。
  • 部分范畴:对渲染树的某局部或某一个渲染对象进行从新布局

全局范畴回流

<body>
  <div class="hello">
    <h4>hello</h4>
    <p><strong>Name:</strong>BDing</p>
    <h5>male</h5>
    <ol>
      <li>coding</li>
      <li>loving</li>
    </ol>
  </div>
</body>

p 节点上产生 reflow 时,hellobody 也会从新渲染,甚至 h5ol都会收到影响

部分范畴回流

用部分布局来解释这种景象:把一个 dom 的宽高之类的几何信息定死,而后在 dom 外部触发重排,就只会从新渲染该 dom 外部的元素,而不会影响到外界

3.5 缩小重绘和回流

应用 translate 代替 top

<div class="test"></div>
<style>
    .test {
        position: absolute;
        top: 10px;
        width: 100px;
        height: 100px;
        background: red;
    }
</style>
<script>
    setTimeout(() => {
        // 引起回流
        document.querySelector('.test').style.top = '100px'
    }, 1000)
</script>
  • 应用 visibility 替换 display: none,因为前者只会引起重绘,后者会引发回流(扭转了布局)
  • DOM 离线后批改,比方:先把 DOMdisplay:none (有一次 Reflow),而后你批改 100 次,而后再把它显示进去
  • 不要把 DOM 结点的属性值放在一个循环里当成循环里的变量
for(let i = 0; i < 1000; i++) {
    // 获取 offsetTop 会导致回流,因为须要去获取正确的值
    console.log(document.querySelector('.test').style.offsetTop)
}
  • 不要应用 table 布局,可能很小的一个小改变会造成整个 table 的从新布局
  • 动画实现的速度的抉择,动画速度越快,回流次数越多,也能够抉择应用 requestAnimationFrame
  • CSS选择符从右往左匹配查找,防止 DOM深度过深
  • 将频繁运行的动画变为图层,图层可能阻止该节点回流影响别的元素。比方对于 video标签,浏览器会主动将该节点变为图层。

—- 问题知识点分割线 —-

代码输入后果

function foo() {console.log( this.a);
}

function doFoo() {foo();
}

var obj = {
  a: 1,
  doFoo: doFoo
};

var a = 2; 
obj.doFoo()

输入后果:2

在 Javascript 中,this 指向函数执行时的以后对象。在执行 foo 的时候,执行环境就是 doFoo 函数,执行环境为全局。所以,foo 中的 this 是指向 window 的,所以会打印出 2。

—- 问题知识点分割线 —-

深拷贝浅拷贝

浅拷贝:浅拷贝通过 ES6 新个性 Object.assign()或者通过扩大运算法... 来达到浅拷贝的目标,浅拷贝批改
正本,不会影响原数据,但毛病是浅拷贝只能拷贝第一层的数据,且都是值类型数据,如果有援用型数据,批改
正本会影响原数据。深拷贝:通过利用 JSON.parse(JSON.stringify())来实现深拷贝的目标,但利用 JSON 拷贝也是有毛病的,当要拷贝的数据中含有 undefined/function/symbol 类型是无奈进行拷贝的,当然咱们想我的项目开发中须要
深拷贝的数据个别不会含有以上三种类型,如有须要能够本人在封装一个函数来实现。

—- 问题知识点分割线 —-

前端贮存的⽅式有哪些?

  • cookies:在 HTML5 规范前本地贮存的次要⽅式,长处是兼容性好,申请头⾃带 cookie ⽅便,毛病是⼤⼩只有 4k,⾃动申请头加⼊ cookie 节约流量,每个 domain 限度 20 个 cookie,使⽤起来麻烦,须要⾃⾏封装;
  • localStorage:HTML5 加⼊的以键值对 (Key-Value) 为规范的⽅式,长处是操作⽅便,永久性贮存(除⾮⼿动删除),⼤⼩为 5M,兼容 IE8+;
  • sessionStorage:与 localStorage 根本相似,区别是 sessionStorage 当⻚⾯敞开后会被清理,⽽且与 cookie、localStorage 不同,他不能在所有同源窗⼝中共享,是会话级别的贮存⽅式;
  • Web SQL:2010 年被 W3C 废除的本地数据库数据存储⽅案,然而支流浏览器(⽕狐除外)都曾经有了相干的实现,web sql 相似于 SQLite,是真正意义上的关系型数据库,⽤ sql 进⾏操作,当咱们⽤ JavaScript 时要进⾏转换,较为繁琐;
  • IndexedDB:是被正式纳⼊ HTML5 规范的数据库贮存⽅案,它是 NoSQL 数据库,⽤键值对进⾏贮存,能够进⾏疾速读取操作,⾮常适宜 web 场景,同时⽤ JavaScript 进⾏操作会⾮常便。

—- 问题知识点分割线 —-

寄生组合继承

题目形容: 实现一个你认为不错的 js 继承形式

实现代码如下:

function Parent(name) {
  this.name = name;
  this.say = () => {console.log(111);
  };
}
Parent.prototype.play = () => {console.log(222);
};
function Children(name) {Parent.call(this);
  this.name = name;
}
Children.prototype = Object.create(Parent.prototype);
Children.prototype.constructor = Children;
// let child = new Children("111");
// // console.log(child.name);
// // child.say();
// // child.play();

—- 问题知识点分割线 —-

DNS 同时应用 TCP 和 UDP 协定?

DNS 占用 53 号端口,同时应用 TCP 和 UDP 协定。(1)在区域传输的时候应用 TCP 协定

  • 辅域名服务器会定时(个别 3 小时)向主域名服务器进行查问以便理解数据是否有变动。如有变动,会执行一次区域传送,进行数据同步。区域传送应用 TCP 而不是 UDP,因为数据同步传送的数据量比一个申请应答的数据量要多得多。
  • TCP 是一种牢靠连贯,保障了数据的准确性。

(2)在域名解析的时候应用 UDP 协定

  • 客户端向 DNS 服务器查问域名,个别返回的内容都不超过 512 字节,用 UDP 传输即可。不必通过三次握手,这样 DNS 服务器负载更低,响应更快。实践上说,客户端也能够指定向 DNS 服务器查问时用 TCP,但事实上,很多 DNS 服务器进行配置的时候,仅反对 UDP 查问包。

—- 问题知识点分割线 —-

AJAX

const getJSON = function(url) {return new Promise((resolve, reject) => {const xhr = XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP');
        xhr.open('GET', url, false);
        xhr.setRequestHeader('Accept', 'application/json');
        xhr.onreadystatechange = function() {if (xhr.readyState !== 4) return;
            if (xhr.status === 200 || xhr.status === 304) {resolve(xhr.responseText);
            } else {reject(new Error(xhr.responseText));
            }
        }
        xhr.send();})
}

实现数组原型办法

forEach

Array.prototype.forEach2 = function(callback, thisArg) {if (this == null) {throw new TypeError('this is null or not defined')
    }
    if (typeof callback !== "function") {throw new TypeError(callback + 'is not a function')
    }
    const O = Object(this)  // this 就是以后的数组
    const len = O.length >>> 0  // 前面有解释
    let k = 0
    while (k < len) {if (k in O) {callback.call(thisArg, O[k], k, O);
        }
        k++;
    }
}

O.length >>> 0 是什么操作?就是无符号右移 0 位,那有什么意义嘛?就是为了保障转换后的值为正整数。其实底层做了 2 层转换,第一是非 number 转成 number 类型,第二是将 number 转成 Uint32 类型

map

基于 forEach 的实现可能很容易写出 map 的实现:

- Array.prototype.forEach2 = function(callback, thisArg) {+ Array.prototype.map2 = function(callback, thisArg) {if (this == null) {throw new TypeError('this is null or not defined')
    }
    if (typeof callback !== "function") {throw new TypeError(callback + 'is not a function')
    }
    const O = Object(this)
    const len = O.length >>> 0
-   let k = 0
+   let k = 0, res = []
    while (k < len) {if (k in O) {-           callback.call(thisArg, O[k], k, O);
+           res[k] = callback.call(thisArg, O[k], k, O);
        }
        k++;
    }
+   return res
}

filter

同样,基于 forEach 的实现可能很容易写出 filter 的实现:

- Array.prototype.forEach2 = function(callback, thisArg) {+ Array.prototype.filter2 = function(callback, thisArg) {if (this == null) {throw new TypeError('this is null or not defined')
    }
    if (typeof callback !== "function") {throw new TypeError(callback + 'is not a function')
    }
    const O = Object(this)
    const len = O.length >>> 0
-   let k = 0
+   let k = 0, res = []
    while (k < len) {if (k in O) {-           callback.call(thisArg, O[k], k, O);
+           if (callback.call(thisArg, O[k], k, O)) {+               res.push(O[k])                
+           }
        }
        k++;
    }
+   return res
}

some

同样,基于 forEach 的实现可能很容易写出 some 的实现:

- Array.prototype.forEach2 = function(callback, thisArg) {+ Array.prototype.some2 = function(callback, thisArg) {if (this == null) {throw new TypeError('this is null or not defined')
    }
    if (typeof callback !== "function") {throw new TypeError(callback + 'is not a function')
    }
    const O = Object(this)
    const len = O.length >>> 0
    let k = 0
    while (k < len) {if (k in O) {-           callback.call(thisArg, O[k], k, O);
+           if (callback.call(thisArg, O[k], k, O)) {
+               return true
+           }
        }
        k++;
    }
+   return false
}

reduce

Array.prototype.reduce2 = function(callback, initialValue) {if (this == null) {throw new TypeError('this is null or not defined')
    }
    if (typeof callback !== "function") {throw new TypeError(callback + 'is not a function')
    }
    const O = Object(this)
    const len = O.length >>> 0
    let k = 0, acc

    if (arguments.length > 1) {acc = initialValue} else {
        // 没传入初始值的时候,取数组中第一个非 empty 的值为初始值
        while (k < len && !(k in O)) {k++}
        if (k > len) {throw new TypeError( 'Reduce of empty array with no initial value');
        }
        acc = O[k++]
    }
    while (k < len) {if (k in O) {acc = callback(acc, O[k], k, O)
        }
        k++
    }
    return acc
}

—- 问题知识点分割线 —-

Compositon api

Composition API也叫组合式 API,是 Vue3.x 的新个性。

通过创立 Vue 组件,咱们能够将接口的可重复部分及其性能提取到可重用的代码段中。仅此一项就能够使咱们的应用程序在可维护性和灵活性方面走得更远。然而,咱们的教训曾经证实,光靠这一点可能是不够的,尤其是当你的应用程序变得十分大的时候——想想几百个组件。在解决如此大的应用程序时,共享和重用代码变得尤为重要

  • Vue2.0 中,随着性能的减少,组件变得越来越简单,越来越难保护,而难以保护的根本原因是 Vue 的 API 设计迫使开发者应用 watch,computed,methods 选项组织代码,而不是理论的业务逻辑。
  • 另外 Vue2.0 短少一种较为简洁的低成本的机制来实现逻辑复用,尽管能够 minxis 实现逻辑复用,然而当 mixin 变多的时候,会使得难以找到对应的 data、computed 或者 method 来源于哪个mixin,使得类型推断难以进行。
  • 所以 Composition API 的呈现,次要是也是为了解决 Option API 带来的问题,第一个是代码组织问题,Compostion API能够让开发者依据业务逻辑组织本人的代码,让代码具备更好的可读性和可扩展性,也就是说当下一个开发者接触这一段不是他本人写的代码时,他能够更好的利用代码的组织反推出理论的业务逻辑,或者依据业务逻辑更好的了解代码。
  • 第二个是实现代码的逻辑提取与复用,当然 mixin 也能够实现逻辑提取与复用,然而像后面所说的,多个 mixin 作用在同一个组件时,很难看出 property 是来源于哪个 mixin,起源不分明,另外,多个mixinproperty存在变量命名抵触的危险。而 Composition API 刚好解决了这两个问题。

艰深的讲:

没有 Composition API 之前 vue 相干业务的代码须要配置到 option 的特定的区域,中小型我的项目是没有问题的,然而在大型项目中会导致前期的维护性比较复杂,同时代码可复用性不高。Vue3.x 中的 composition-api 就是为了解决这个问题而生的

compositon api 提供了以下几个函数:

  • setup
  • ref
  • reactive
  • watchEffect
  • watch
  • computed
  • toRefs
  • 生命周期的hooks

都说 Composition API 与 React Hook 很像,说说区别

从 React Hook 的实现角度看,React Hook 是依据 useState 调用的程序来确定下一次重渲染时的 state 是来源于哪个 useState,所以呈现了以下限度

  • 不能在循环、条件、嵌套函数中调用 Hook
  • 必须确保总是在你的 React 函数的顶层调用 Hook
  • useEffect、useMemo等函数必须手动确定依赖关系

而 Composition API 是基于 Vue 的响应式零碎实现的,与 React Hook 的相比

  • 申明在 setup 函数内,一次组件实例化只调用一次setup,而 React Hook 每次重渲染都须要调用 Hook,使得 React 的 GC 比 Vue 更有压力,性能也绝对于 Vue 来说也较慢
  • Compositon API的调用不须要顾虑调用程序,也能够在循环、条件、嵌套函数中应用
  • 响应式零碎主动实现了依赖收集,进而组件的局部的性能优化由 Vue 外部本人实现,而 React Hook 须要手动传入依赖,而且必须必须保障依赖的程序,让 useEffectuseMemo 等函数正确的捕捉依赖变量,否则会因为依赖不正确使得组件性能降落。

尽管 Compositon API 看起来比 React Hook 好用,然而其设计思维也是借鉴 React Hook 的。

—- 问题知识点分割线 —-

HTTP 之 URL

  • URI 是用来惟一标记服务器上资源的一个字符串,通常也称为 URL;
  • URI 通常由 schemehost:portpathquery 四个局部组成,有的能够省略;
  • scheme 叫“计划名”或者“协定名”,示意资源应该应用哪种协定来拜访;
  • host:port”示意资源所在的主机名和端口号;
  • path 标记资源所在的地位;
  • query 示意对资源附加的额定要求;
  • URI 里对“@&/”等特殊字符和汉字必须要做编码,否则服务器收到 HTTP报文后会无奈正确处理

—- 问题知识点分割线 —-

absolute 与 fixed 共同点与不同点

共同点:

  • 扭转行内元素的出现形式,将 display 置为 inline-block
  • 使元素脱离一般文档流,不再占据文档物理空间
  • 笼罩非定位文档元素

不同点:

  • abuselute 与 fixed 的根元素不同,abuselute 的根元素能够设置,fixed 根元素是浏览器。
  • 在有滚动条的页面中,absolute 会跟着父元素进行挪动,fixed 固定在页面的具体位置。

—- 问题知识点分割线 —-

代码输入后果

Promise.resolve().then(() => {return new Error('error!!!')
}).then(res => {console.log("then:", res)
}).catch(err => {console.log("catch:", err)
})

输入后果如下:

"then:" "Error: error!!!"

返回任意一个非 promise 的值都会被包裹成 promise 对象,因而这里的 return new Error('error!!!') 也被包裹成了return Promise.resolve(new Error('error!!!')),因而它会被 then 捕捉而不是 catch。

—- 问题知识点分割线 —-

AJAX

实现:利用 XMLHttpRequest

// get
const getJSON = (url) => {return new Promise((resolve, reject) => {let xhr = new XMLHttpRequest();
        // open 办法用于指定 HTTP 申请的参数: method, url, async(是否异步,默认 true)xhr.open("GET", url, false);
        xhr.setRequestHeader('Content-Type', 'application/json');
        // onreadystatechange 属性指向一个监听函数。// readystatechange 事件产生时(实例的 readyState 属性变动),就会执行这个属性。xhr.onreadystatechange = function(){
            // 4 示意服务器返回的数据曾经齐全接管,或者本次接管曾经失败
            if(xhr.readyState !== 4) return;
            // 申请胜利,基本上只有 2xx 和 304 的状态码,示意服务器返回是失常状态
            if(xhr.status === 200 || xhr.status === 304) {
                // responseText 属性返回从服务器接管到的字符串
                resolve(xhr.responseText);
            }
            // 申请失败
            else {reject(new Error(xhr.responseText));
            }
        }
        xhr.send();});
}

// post
const postJSON = (url, data) => {return new Promise((resolve, reject) => {let xhr = new XMLHttpRequest();
        xhr.open("POST", url);
        xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
        xhr.onreadystatechange = function(){if(xhr.readyState !== 4) return;
            if(xhr.status === 200 || xhr.status === 304) {resolve(xhr.responseText);
            }
            else {reject(new Error(xhr.responseText));
            }
        }
        xhr.send(data);
    });
}

正文完
 0