学习笔记
# 前言
面试准备中学习笔记
# js:
# 原型链:
function Person() {
}
var person = new Person();
console.log(person.__proto__ == Person.prototype) // true
console.log(Person.prototype.constructor == Person) // true
// 顺便学习一个ES5的方法,可以获得对象的原型
console.log(Object.getPrototypeOf(person) === Person.prototype) // true
console.log(Object.prototype.__proto__ === null) // true
2
3
4
5
6
7
8
9
10
11
var a = f.prototype, b = Object.getPrototypeOf(f);
a是构造函数f的原型 : {constructor: ƒ} b是实例f的原型对象 : ƒ () { [native code] }
什么是原型链?
当对象查找一个属性的时候,如果没有在自身找到,那么就会查找自身的原型,如果原型还没有找到,那么会继续查找原型的原型,直到找到 Object.prototype 的原型时,此时原型为 null,查找停止。 这种通过 通过原型链接的逐级向上的查找链被称为原型链
什么是原型继承?
一个对象可以使用另外一个对象的属性或者方法,就称之为继承。具体是通过将这个对象的原型设置为另外一个对象,这样根据原型链的规则,如果查找一个对象属性且在自身不存在时,就会查找另外一个对象,相当于一个对象可以使用另外一个对象的属性和方法了。
# 继承:
寄生组合式继承的高效率体现它只调用了一次 Parent 构造函数,并且因此避免了在 Parent.prototype 上面创建不必要的、多余的属性。与此同时,原型链还能保持不变;因此,还能够正常使用 instanceof 和 isPrototypeOf。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。
# 作用域链:
VO:变量对象,AO:活动对象,
# 闭包:
- 从理论角度:所有的函数。因为它们都在创建的时候就将上层上下文的数据保存起来了。哪怕是简单的全局变量也是如此,因为函数中访问全局变量就相当于是在访问自由变量,这个时候使用最外层的作用域。
- 从实践角度:以下函数才算是闭包:
- 即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回)
- 在代码中引用了自由变量
# 变量提升:
在进入执行上下文时,首先会处理函数声明,其次会处理变量声明,如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性。
# this指向:
追根溯源的从 ECMASciript 规范讲解 this 的指向
Reference 的构成,由三个组成部分,分别是:
- base value
- referenced name
- strict reference
base value 就是属性所在的对象或者就是 EnvironmentRecord,它的值只可能是 undefined, an Object, a Boolean, a String, a Number, or an environment record 其中的一种。
referenced name 就是属性的名称。
var foo = 1;
// 对应的Reference是:
var fooReference = {
base: EnvironmentRecord,
name: 'foo',
strict: false
};
GetValue(fooReference) // 1;
2
3
4
5
6
7
8
9
10
获取 Reference 组成部分的方法,比如 GetBase 和 IsPropertyReference。
GetBase()返回 reference 的 base value。IsPropertyReference()如果 base value 是一个对象,就返回true。
GetValue 返回对象属性真正的值。
如何判断this指向:
1.计算 MemberExpression 的结果赋值给 ref。MemberExpression 其实就是()左边的部分。
2.判断 ref 是不是一个 Reference 类型
2.1如果 ref 是 Reference,并且 IsPropertyReference(ref) 是 true, 那么 this 的值为 GetBase(ref)
2.2如果 ref 是 Reference,并且 base value 值是 Environment Record, 那么this的值为 ImplicitThisValue(ref),该函数始终返回undefined
2.3如果 ref 不是 Reference,那么 this 的值为 undefined
this指向undefined,如果有调用在严格模式下报错,非严格模式下为window
MemberExpression :
- PrimaryExpression // 原始表达式 可以参见《JavaScript权威指南第四章》
- FunctionExpression // 函数定义表达式
- MemberExpression [ Expression ] // 属性访问表达式
- MemberExpression . IdentifierName // 属性访问表达式
- new MemberExpression Arguments // 对象创建表达式
有赋值操作符、逻辑与算法、逗号操作符都会调用GetValue(),返回的对象都不会是reference
this一般有几种调用场景 var obj = {a: 1, b: function(){console.log(this);}} 1、作为对象调用时,指向该对象 obj.b(); // 指向obj 2、作为函数调用, var b = obj.b; b(); // 指向全局window 3、作为构造函数调用 var b = new Fun(); // this指向当前实例对象 4、作为call与apply调用 obj.b.apply(object, []); // this指向当前的object
# 立即执行函数
// 在IIFE里,`i`值被锁定在了`lockedInIndex`里。
// 在循环结束执行时,尽管`i`值的数值是所有元素的总和,但每一次函数表达式被调用时,
// IIFE 里的 `lockedInIndex` 值都是`i`传给它的值,所以当链接被点击时,正确的值被弹出。
var elems = document.getElementsByTagName('a');
for(var i = 0;i < elems.length;i++) {
(function(lockedInIndex){
elems[i].addEventListener('click',function(e){
e.preventDefault();
alert('I am link #' + lockedInIndex);
},false)
})(i);
}
2
3
4
5
6
7
8
9
10
11
12
13
# instanceof 原理
用 typeof
来判断number
, string
, object
, boolean
, function
, undefined
, symbol
这七种类型
typeof
的原理:
js 在底层存储变量的时候,会在变量的机器码的低位1-3位存储其类型信息👉
000:对象
010:浮点数
100:字符串
110:布尔
1:整数
所有机器码均为0:null
−2^30 整数:undefined
Object.prototype.toString,我们可以利用这个方法来对一个变量的类型来进行比较准确的判断
Object.prototype.toString.call(1) // "[object Number]"
Object.prototype.toString.call('hi') // "[object String]"
Object.prototype.toString.call({a:'hi'}) // "[object Object]"
Object.prototype.toString.call([1,'a']) // "[object Array]"
Object.prototype.toString.call(true) // "[object Boolean]"
Object.prototype.toString.call(() => {}) // "[object Function]"
Object.prototype.toString.call(null) // "[object Null]"
Object.prototype.toString.call(undefined) // "[object Undefined]"
Object.prototype.toString.call(Symbol(1)) // "[object Symbol]"
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
instanceof就是查找原型链。
简单来说,我们使用 typeof
来判断基本数据类型是 ok 的,不过需要注意当用 typeof
来判断 null
类型时的问题,如果想要判断一个对象的具体类型可以考虑用 instanceof
,但是 instanceof
也可能判断不准确,比如一个数组,他可以被 instanceof
判断为 Object。所以我们要想比较准确的判断对象实例的类型时,可以采取 Object.prototype.toString.call
方法。
# apply、call、bind
调用改变函数this指向
# 柯里化
在数学和计算机科学中,柯里化是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术
# v8垃圾回收机制
# 如何避免内存泄漏
# 1、尽可能少地创建全局变量
如果你遇到需要必须使用全局变量的场景,那么请保证一定要在全局变量使用完毕后将其设置为null
从而触发回收机制。
# 2、手动清除定时器
# 3、少用闭包
# 4、清除DOM引用
# 5、 弱引用
WeakMap和
WeakSet
# 双精度浮点数
0.1+0.2 不等于0.3
function DeviationValue(num1, num2) {
return Math.abs(num1 - num2) < Number.EPSILON
}
2
3
因为JavaScript使用IEEE浮点类型双精度。转换为二进制在计算但是小数无限延伸,二进制截取53位导致精度丢失。 这就是0.1+0.2不为0.3的原因 Number.EPSILON的精度是2^-52,所以只要丢失精度小于Number.EPSILON基本可以确认相等。
# new运算符
new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象类型之一
function objectFactory() {
var obj = new Object(),
Constructor = [].shift.call(arguments);
obj.__proto__ = Constructor.prototype;
var ret = Constructor.apply(obj, arguments);
return typeof ret === 'object' ? ret : obj;
};
2
3
4
5
6
7
8
9
10
11
12
13
# Event Loop事件循环机制
# 1.执行栈与事件队列
js引擎遇到一个异步事件后并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务。当一个异步事件返回结果后,js会将这个事件加入与当前执行栈不同的另一个队列,我们称之为事件队列。被放入事件队列不会立刻执行其回调,而是等待当前执行栈中的所有任务都执行完毕, 主线程处于闲置状态时,主线程会去查找事件队列是否有任务。如果有,那么主线程会从中取出排在第一位的事件,并把这个事件对应的回调放入执行栈中,然后执行其中的同步代码...,如此反复,这样就形成了一个无限的循环。这就是这个过程被称为“事件循环(Event Loop)”的原因。
# 2.macro task与micro task
在一个事件循环中,异步事件返回结果后会被放到一个任务队列中。然而,根据这个异步事件的类型,这个事件实际上会被对应的宏任务队列或者微任务队列中去。并且在当前执行栈为空的时候,主线程会 查看微任务队列是否有事件存在。如果不存在,那么再去宏任务队列中取出一个事件并把对应的回到加入当前执行栈;如果存在,则会依次执行队列中事件对应的回调,直到微任务队列为空,然后去宏任务队列中取出最前面的一个事件,把对应的回调加入当前执行栈...如此反复,进入循环。
我们只需记住当当前执行栈执行完毕时会立刻先处理所有微任务队列中的事件,然后再去宏任务队列中取出一个事件。同一次事件循环中,微任务永远在宏任务之前执行。
macro-task大概包括:
- script(整体代码)
- setTimeout
- setInterval
- setImmediate
- I/O
- UI render
micro-task大概包括:
- process.nextTick
- Promise
- Async/Await(实际就是promise)
- MutationObserver(html5新特性)
如果有async/await函数,那么await函数后会先跳出函数,继续执行本次事件循环,本次结束后回来将await后的代码加入微任务队列
# node环境下的事件循环机制
node中的事件循环的顺序:
外部输入数据-->轮询阶段(poll)-->检查阶段(check)-->关闭事件回调阶段(close callback)-->定时器检测阶段(timer)-->I/O事件回调阶段(I/O callbacks)-->闲置阶段(idle, prepare)-->轮询阶段...
┌───────────────────────┐
┌─>│ timers │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ I/O callbacks │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ idle, prepare │
│ └──────────┬────────────┘ ┌───────────────┐
│ ┌──────────┴────────────┐ │ incoming: │
│ │ poll │<──connections─── │
│ └──────────┬────────────┘ │ data, etc. │
│ ┌──────────┴────────────┐ └───────────────┘
│ │ check │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
└──┤ close callbacks │
└───────────────────────┘
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- timers: 这个阶段执行定时器队列中的回调如
setTimeout()
和setInterval()
。 - I/O callbacks: 这个阶段执行几乎所有的回调。但是不包括close事件,定时器和
setImmediate()
的回调。 - idle, prepare: 这个阶段仅在内部使用,可以不必理会。
- poll: 等待新的I/O事件,node在一些特殊情况下会阻塞在这里。
- check:
setImmediate()
的回调会在这个阶段执行。 - close callbacks: 例如
socket.on('close', ...)
这种close事件的回调。
因为在I/O事件的回调中,setImmediate方法的回调永远在timer的回调前执行。
node11 版本一旦执行一个阶段里的一个宏任务(setTimeout,setInterval和setImmediate)就立刻执行对应的微任务队列。
# Promise原理
主要考手写promise
# 高阶函数
函数作为参数或者函数作为返回值
# Css:
# 盒子模型:
# 外边距重叠
只有普通文档流中块框的垂直外边距才会发生外边距合并,行内框、浮动框或绝对定位之间的外边距不会合并。
# BFC**(Block Formatting Context) ** 块级格式化上下文
如何创建BFC
- overflow不为visible;
- float的值不为none;
- position的值不为static或relative;
- display属性为inline-blocks,table,table-cell,table-caption,flex,inline-flex;
# CSS 模块化的实现方式
# BEM 命名规范
BEM 的意思就是块(block)、元素(element)、修饰符(modifier)。是由 Yandex 团队提出的一种前端命名方法论。这种巧妙的命名方法让你的 css 类对其他开发者来说更加透明而且更有意义。
BEM 的命名规范如下:
/* 块即是通常所说的 Web 应用开发中的组件或模块。每个块在逻辑上和功能上都是相互独立的。 */
.block {
}
/* 元素是块中的组成部分。元素不能离开块来使用。BEM 不推荐在元素中嵌套其他元素。 */
.block__element {
}
/* 修饰符用来定义块或元素的外观和行为。同样的块在应用不同的修饰符之后,会有不同的外观 */
.block--modifier {
}
复制代码
2
3
4
5
6
7
8
9
10
11
12
通过 bem 的命名方式,可以让我们的 css 代码层次结构清晰,通过严格的命名也可以解决命名冲突的问题,但也不能完全避免,毕竟只是一个命名约束,不按规范写照样能运行。
# html和浏览器
# 跨标签页通信
# BroadCast Channel
下面的方式就可以创建一个标识为AlienZHOU
的频道:
const bc = new BroadcastChannel('AlienZHOU');
复制代码
2
各个页面可以通过onmessage
来监听被广播的消息:
bc.onmessage = function (e) {
const data = e.data;
const text = '[receive] ' + data.msg + ' —— tab ' + data.from;
console.log('[BroadcastChannel] receive message:', text);
};
复制代码
2
3
4
5
6
要发送消息时只需要调用实例上的postMessage
方法即可:
bc.postMessage(mydata);
# Service Worker
# LocalStorage
当 LocalStorage 变化时,会触发storage
事件。利用这个特性,我们可以在发送消息时,把消息写入到某个 LocalStorage 中;然后在各个页面内,通过监听storage
事件即可收到通知。
window.addEventListener('storage', function (e) {
if (e.key === 'ctc-msg') {
const data = JSON.parse(e.newValue);
const text = '[receive] ' + data.msg + ' —— tab ' + data.from;
console.log('[Storage I] receive message:', text);
}
});
复制代码
2
3
4
5
6
7
8
在各个页面添加如上的代码,即可监听到 LocalStorage 的变化。当某个页面需要发送消息时,只需要使用我们熟悉的setItem
方法即可
# Shared Worker
# IndexedDB
# window.open + window.opener
# history和hash两种路由
# hash模式
使用window.location.hash属性及窗口的onhashchange事件,可以实现监听浏览器地址hash值变化,执行相应的js切换网页。下面具体介绍几个使用过程中必须理解的要点:
hash指的是地址中#号以及后面的字符,也称为散列值。hash也称作锚点,本身是用来做页面跳转定位的。如http://localhost/index.html#abc,这里的#abc就是hash; 散列值是不会随请求发送到服务器端的,所以改变hash,不会重新加载页面; 监听 window 的 hashchange 事件,当散列值改变时,可以通过 location.hash 来获取和设置hash值; location.hash值的变化会直接反应到浏览器地址栏;
# history模式
window.history 属性指向 History 对象,它表示当前窗口的浏览历史。当发生改变时,只会改变页面的路径,不会刷新页面。 History 对象保存了当前窗口访问过的所有页面网址。通过 history.length 可以得出当前窗口一共访问过几个网址。 由于安全原因,浏览器不允许脚本读取这些地址,但是允许在地址之间导航。 浏览器工具栏的“前进”和“后退”按钮,其实就是对 History 对象进行操作。
popstate 事件 每当 history 对象出现变化时,就会触发 popstate 事件。
注意:
仅仅调用pushState()方法或replaceState()方法 ,并不会触发该事件; 只有用户点击浏览器倒退按钮和前进按钮,或者使用 JavaScript 调用History.back()、History.forward()、History.go()方法时才会触发。 另外,该事件只针对同一个文档,如果浏览历史的切换,导致加载不同的文档,该事件也不会触发。 页面第一次加载的时候,浏览器不会触发popstate事件。 使用的时候,可以为popstate事件指定回调函数,回调函数的参数是一个 event 事件对象,它的 state 属性指向当前的 state 对象。
window.addEventListener('popstate', function(e) {
//e.state 相当于 history.state
console.log('state: ' + JSON.stringify(e.state));
console.log(history.state);
});
2
3
4
5
通过history.pushState 实现页面 tab 切换的功能。
# DOM树
HTML 解析器(HTMLParser)的模块,它的职责就是负责将 HTML 字节流转换为 DOM 结构。
第一个阶段,通过分词器将字节流转换为 Token。
后续的第二个和第三个阶段是同步进行的,需要将 Token 解析为 DOM 节点,并将 DOM 节点添加到 DOM 树中。
HTML 解析器维护了一个Token 栈结构,该 Token 栈主要用来计算节点之间的父子关系,在第一个阶段中生成的 Token 会被按照顺序压到这个栈中。具体的处理规则如下所示:
- 如果压入到栈中的是StartTag Token,HTML 解析器会为该 Token 创建一个 DOM 节点,然后将该节点加入到 DOM 树中,它的父节点就是栈中相邻的那个元素生成的节点。
- 如果分词器解析出来是文本 Token,那么会生成一个文本节点,然后将该节点加入到 DOM 树中,文本 Token 是不需要压入到栈中,它的父节点就是当前栈顶 Token 所对应的 DOM 节点。
- 如果分词器解析出来的是EndTag 标签,比如是 EndTag div,HTML 解析器会查看 Token 栈顶的元素是否是 StarTag div,如果是,就将 StartTag div 从栈中弹出,表示该 div 元素解析完成。
引入 JavaScript 线程会阻塞 DOM,不过也有一些相关的策略来规避,比如使用 CDN 来加速 JavaScript 文件的加载,压缩 JavaScript 文件的体积。另外,如果 JavaScript 文件中没有操作 DOM 相关代码,就可以将该 JavaScript 脚本设置为异步加载,通过 async 或 defer 来标记代码。
Chrome 浏览器做了很多优化,其中一个主要的优化是预解析操作。当渲染引擎收到字节流之后,会开启一个预解析线程,用来分析 HTML 文件中包含的 JavaScript、CSS 等相关文件,解析到相关文件之后,预解析线程会提前下载这些文件。
需要先解析 JavaScript 语句之上所有的 CSS 样式。所以如果代码里引用了外部的 CSS 文件,那么在执行 JavaScript 之前,还需要等待外部的 CSS 文件下载完成,并解析生成 CSSOM 对象之后,才能执行 JavaScript 脚本。
而 JavaScript 引擎在解析 JavaScript 之前,是不知道 JavaScript 是否操纵了 CSSOM 的,所以渲染引擎在遇到 JavaScript 脚本时,不管该脚本是否操纵了 CSSOM,都会执行 CSS 文件下载,解析操作,再执行 JavaScript 脚本。
# 事件模型
浏览器的事件模型,就是通过监听函数(listener)对事件做出反应。事件发生后,浏览器监听到了这个事件,就会执行对应的监听函数。这是事件驱动编程模式(event-driven)的主要编程方式。
JavaScript 有三种方法,可以为事件绑定监听函数。
# HTML 的 on- 属性
# 元素节点的事件属性
# EventTarget.addEventListener()
addEventListener
方法第三个参数的不同,会导致绑定两个监听函数
# 事件代理
由于事件会在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件。这种方法叫做事件的代理(delegation)。
event.stopPropagation();//阻止事件冒泡
event.stopImmediatePropagation();//阻止事件冒泡的同时,停止执行直接的js
2
# 缓存机制
HTTP请求(Request)报文,报文格式为:请求行 – HTTP头(通用信息头,请求头,实体头) – 请求报文主体(只有POST才有报文主体)。
HTTP响应(Response)报文,报文格式为:状态行 – HTTP头(通用信息头,响应头,实体头) – 响应报文主体。
浏览器与服务器通信的方式为应答模式,即是:浏览器发起HTTP请求 – 服务器响应该请求。那么浏览器第一次向服务器发起该请求后拿到请求结果,会根据响应报文中HTTP头的缓存标识,决定是否缓存结果,是则将请求结果和缓存标识存入浏览器缓存中,简单的过程如下图:
由上图我们可以知道:
- 浏览器每次发起请求,都会先在浏览器缓存中查找该请求的结果以及缓存标识
- 浏览器每次拿到返回的请求结果都会将该结果和缓存标识存入浏览器缓存中
以上两点结论就是浏览器缓存机制的关键,他确保了每个请求的缓存存入与读取,只要我们再理解浏览器缓存的使用规则,那么所有的问题就迎刃而解了,本文也将围绕着这点进行详细分析。为了方便大家理解,这里我们根据是否需要向服务器重新发起HTTP请求将缓存过程分为两个部分,分别是强制缓存和协商缓存。
# 强制缓存
# Expires
# Cache-Control
在HTTP/1.1中,Cache-Control是最重要的规则,主要用于控制网页缓存,主要取值为:
- public:所有内容都将被缓存(客户端和代理服务器都可缓存)
- private:所有内容只有客户端可以缓存,Cache-Control的默认取值
- no-cache:客户端缓存内容,但是是否使用缓存则需要经过协商缓存来验证决定
- no-store:所有内容都不会被缓存,即不使用强制缓存,也不使用协商缓存
- max-age=xxx (xxx is numeric):缓存内容将在xxx秒后失效
Cache-Control优先。
from memory cache代表使用内存中的缓存,from disk cache则代表使用的是硬盘中的缓存,浏览器读取缓存的顺序为memory –> disk。
虽然我已经直接把结论说出来了,但是相信有不少人对此不能理解,那么接下来我们一起详细分析一下缓存读取问题,这里仍让以我的博客为例进行分析:
访问https://heyingye.github.io/ –> 200 –> 关闭博客的标签页 –> 重新打开https://heyingye.github.io/ –> 200(from disk cache) –> 刷新 –> 200(from memory cache)
有强制缓存优先走缓存,没有缓存请求服务器,缓存失效走协商缓存
# 协商缓存
协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程
# 协商缓存生效,返回304
# 协商缓存失效,返回200和请求结果结果
同样,协商缓存的标识也是在响应报文的HTTP头中和请求结果一起返回给浏览器的,控制协商缓存的字段分别有:Last-Modified / If-Modified-Since和Etag / If-None-Match,其中Etag / If-None-Match的优先级比Last-Modified / If-Modified-Since高。
强制缓存优先于协商缓存进行,若强制缓存(Expires和Cache-Control)生效则直接使用缓存,若不生效则进行协商缓存(Last-Modified / If-Modified-Since和Etag / If-None-Match),协商缓存由服务器决定是否使用缓存,若协商缓存失效,那么代表该请求的缓存失效,重新获取请求结果,再存入浏览器缓存中;生效则返回304,继续使用缓存,主要过程如下:
# 浏览器架构
# 浏览器工作原理
# 内存泄露
# JavaScript 内存泄漏的一些场景
# 意外的全局变量
# 被遗忘的计时器(在beforeDestroy生命周期内清除)
# 被遗忘的事件监听器(在beforeDestroy生命周期removeEventListener)
# 被遗忘的 ES6 Set 成员(使用完后delete或使用WeakSet)
# 被遗忘的 ES6 Map 键名(使用完后delete键名或使用WeakMap)
# 被遗忘的订阅发布事件监听器
# 被遗忘的闭包
# 脱离 DOM 的引用
# 如何发现内存泄漏?
# 性能
# 前端性能优化
- 超过50ms的响应,一定要提供反馈,比如倒计时,进度百分比等。
- 可以采用 lazy load (opens new window),code-splitting (opens new window) 等 其他优化 (opens new window) 手段,让第一次加载的资源更少
# 分析RAIL用的工具
RAIL是一个旅程,为了提升用户在网站的交互体验而不断探索。你需要去理解用户如何感知你的站点,这样才能设置最佳的性能目标
- 聚焦用户
- 100ms内响应用户的输入
- 10ms内产生1帧,在滚动或者动画执行时
- 最大化主线程的空闲时间
- 5s内让网页变得可交互
# 重排(reflow)和重绘(Repaints)
在页面的生命周期中,网页生成的时候,至少会渲染一次。在用户访问的过程中,还会不断触发重排(reflow)和重绘(repaint),不管页面发生了重绘还是重排,都会影响性能,最可怕的是重排,会使我们付出高额的性能代价,所以我们应尽量避免。
- 重绘:某些元素的外观被改变,例如:元素的填充颜色
- 重排:重新生成布局,重新排列元素。
重绘不一定导致重排,但重排一定会导致重绘。
# 下面情况会发生重排:
- 页面初始渲染,这是开销最大的一次重排
- 添加/删除可见的DOM元素
- 改变元素位置
- 改变元素尺寸,比如边距、填充、边框、宽度和高度等
- 改变元素内容,比如文字数量,图片大小等
- 改变元素字体大小
- 改变浏览器窗口尺寸,比如resize事件发生时
- 激活CSS伪类(例如:
:hover
) - 设置 style 属性的值,因为通过设置style属性改变结点样式的话,每一次设置都会触发一次reflow
- 查询某些属性或调用某些计算方法:offsetWidth、offsetHeight等,除此之外,当我们调用
getComputedStyle
方法,或者IE里的currentStyle
时,也会触发重排,原理是一样的,都为求一个“即时性”和“准确性”。
# 重排优化建议:
# 减少重排范围
# 减少重排次数
# 样式集中改变
# 分离读写操作
# 将 DOM 离线
# 使用 absolute 或 fixed 脱离文档流
# 优化动画
# 小结:
- 渲染的三个阶段 Layout,Paint,Composite Layers。 Layout:重排,又叫回流。 Paint:重绘,重排重绘这些步骤都是在 CPU 中发生的。 Compostite Layers:CPU 把生成的 BitMap(位图)传输到 GPU,渲染到屏幕。
- CSS3 就是在 GPU 发生的:Transform Opacity。在 GPU 发生的属性比较高效。所以 CSS3 性能比较高。
# 优化白屏
# 1. DNS解析优化
# 2. TCP网络链路优化
# 3. 服务端处理优化
# 4. 浏览器下载、解析、渲染页面优化
根据浏览器对页面的下载、解析、渲染过程,可以考虑一下的优化处理:
- 尽可能的精简HTML的代码和结构
- 尽可能的优化CSS文件和结构
- 一定要合理的放置JS代码,尽量不要使用内联的JS代码
# 大量图片加载
1、优先加载首屏图片
、在 HTTP/1.0 和 HTTP/1.1 协议下,由于 Chrome 只支持同域同时发送 6 个并发请求,可以进行域名切分,来提升并发的请求数量,或者使用 HTTP/2 协议。
3、动态裁剪图片
4、优化图片大小
# 描述下浏览器从输入网址到页面展现的整个过程
输入网址URL 浏览器获取url,通过DNS解析获得网址的对应IP地址。首先先去各个缓存当中看看有没有DNS缓存,如果有则直接显示,不需要重新发送HTTP请求,如果没有进行下一步。 通过DNS解析获得网址的对应IP地址 浏览器与服务器 通过TCP三次握手协商来建立一个 TCP连接。(1.浏览器问 “服务器,在吗?” 2.服务器回答“在的,怎么了?” 3.浏览器说“我想请你帮个忙”) 浏览器服务器 发送一个 HTTP 请求报文 服务器处理请求并返回一个 HTTP 响应报文 浏览器收到响应,进行客户端渲染,生成Dom树、Css样式树、执行Js交互 构建渲染树,计算并布局。 浏览器绘制页面。
# 动画性能
# 精简DOM,合理布局
# 使用transform代替left、top减少使用引起页面重排的属性
# 开启硬件加速
# 尽量避免浏览器创建不必要的图形层
# 尽量减少js动画,如需要,使用对性能友好的requestAnimationFrame
# 使用chrome performance工具调试动画性能
# 工程化
# 模块化机制
- CommonJS规范主要用于服务端编程,加载模块是同步的,这并不适合在浏览器环境,因为同步意味着阻塞加载,浏览器资源是异步加载的,因此有了AMD CMD解决方案。
- AMD规范在浏览器环境中异步加载模块,而且可以并行加载多个模块。不过,AMD规范开发成本高,代码的阅读和书写比较困难,模块定义方式的语义不顺畅。
- CMD规范与AMD规范很相似,都用于浏览器编程,依赖就近,延迟执行,可以很容易在Node.js中运行。不过,依赖SPM 打包,模块的加载逻辑偏重
- ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。
# tree-shaking
# Uglify原理
# babel原理
# webpack工作流程
# webpack插件机制
# Webpack loader机制
# 前端微服务
Qiankun
https://qiankun.umijs.org/zh
# 框架
# react
# vue
# 操作系统
# 网络
# tcp
3次握手跟4次挥手 3次握手就跟早期打电话时的情况一样:1、听得到吗?2、听得到,你呢?3、我也听到了。然后才开始真正对话 4次挥手是:1、老师,下课了。2、好,我知道了,我说完这点。3、好了,说完了,下课吧。4、谢谢老师,老师再见
# 七层网络结构
# http
# HTTP 0.9 / 1.0
# HTTP/1.1
keepalive可长连接
# HTTP/2
- HTTP/2是一个二进制协议,增加了数据传输的效率。
- HTTP/2是可以在一个TCP链接中并发请求多个HTTP请求,移除了HTTP/1.1中的串行请求。
# HTTP/3
把tcp改成udp
# https
- 数据加密 传输内容进行混淆
- 身份验证 通信双方验证对方的身份真实性
- 数据完整性保护 检测传输的内容是否被篡改或伪造
# 加密方式
HTTPS采用共享密钥加密和公开密钥加密混合的加密方式,在交换密钥对环节使用公开密钥加密方式(防止被监听泄漏密钥)加密共享的密钥,在随后的通信过程中使用共享密钥的方式使用共享的密钥进行加解密。
# 认证方式
- 操作系统和浏览器内置
- 证书颁发机构
- 手动指定证书
# HTTPS数据交互过程
SSL 和 TLS
# http2.0
# http3.0
使用UDP取代TCP
- UDP本身是无连接的、没有建链和拆链成本
# websocket
Webscoket是Web浏览器和服务器之间的一种全双工通信协议,其中WebSocket协议由IETF定为标准,WebSocket API由W3C定为标准。一旦Web客户端与服务器建立起连接,之后的全部数据通信都通过这个连接进行。通信过程中,可互相发送JSON、XML、HTML或图片等任意格式的数据。 WS(WebSocket)与HTTP协议相比,
相同点主要有:
都是基于TCP的应用层协议; 都使用Request/Response模型进行连接的建立; 在连接的建立过程中对错误的处理方式相同,在这个阶段WS可能返回和HTTP相同的返回码; 都可以在网络中传输数据。 不同之处在于:
WS使用HTTP来建立连接,但是定义了一系列新的header域,这些域在HTTP中并不会使用; WS的连接不能通过中间人来转发,它必须是一个直接连接; WS连接建立之后,通信双方都可以在任何时刻向另一方发送数据; WS连接建立之后,数据的传输使用帧来传递,不再需要Request消息; WS的数据帧有序。
# Node
# 模块机制
# CommonJS
规范
通过require()载入模块,module.export xxx 导出模块
# import
和require
import
是ES6的模块规范,require
是commonjs的模块规范,import是静态加载模块,require是动态加载
import性能更好,按需引入,支持tree-shaking
# cluster是从来管理多线程的
# stream 流机制
# pipe原理
# 设计架构
# 设计模式
# 重构
设计模式是理想主义 为理想的软件设计提供目标,但是达到这个目标通常很困难,很有可能存在过度设计
重构是实用主义 满足当前需求设计同时,在版本迭代中不断改进当前的设计,尽量靠近设计模式的理想国度
相辅相成的关系,设计模式为重构提供目标,在不断的重构中达到理想状态
# MVVM
在MVVM架构下,View 和 Model 之间其实并没有直接的联系,而是通过ViewModel进行交互,Model 和 ViewModel 之间的交互是双向的, 因此View 数据的变化会同步到Model中,而Model 数据的变化也会立即反应到View 上。
ViewModel 通过双向数据绑定把 View 层和 Model 层连接了起来,ViewModel里面包含DOM Listeners和Data Bindings,DOM Listeners和Data Bindings是实现双向绑定的关键。DOM Listeners监听页面所有View层DOM元素的变化,当发生变化,Model层的数据随之变化;Data Bindings监听Model层的数据,当数据发生变化,View层的DOM元素随之变化。
# 网络安全
# XSS
攻击
XSS
攻击是指浏览器中执行恶意脚本, 然后拿到用户的信息进行操作。主要分为存储型
、反射型
和文档型
。防范的措施包括:
- 对输入内容过滤或者转码,尤其是类似于
<script>
、<img>
、<a>
标签 - 利用CSP
- 利用Cookie的HttpOnly属性
除了以上策略之外,我们还可以通过添加验证码防止脚本冒充用户提交危险操作。而对于一些不受信任的输入,还可以限制其输入长度,这样可以增大 XSS 攻击的难度。
CSP,即浏览器中的内容安全策略,它的核心思想大概就是服务器决定浏览器加载哪些资源,具体来说有几个功能👇
- 限制加载其他域下的资源文件,这样即使黑客插入了一个 JavaScript 文件,这个 JavaScript 文件也是无法被加载的;
- 禁止向第三方域提交数据,这样用户数据也不会外泄;
- 提供上报机制,能帮助我们及时发现 XSS 攻击。
- 禁止执行内联脚本和未授权的脚本
# CSRF攻击
跟XSS对比的话,CSRF攻击并不需要将恶意代码注入HTML中,而是跳转新的页面,利用**「服务器的验证漏洞」和「用户之前的登录状态」**来模拟用户进行操作
CSRF(Cross-site request forgery), 即跨站请求伪造,本质是冲着浏览器分不清发起请求是不是真正的用户本人,所以防范的关键在于在请求中放入黑客所不能伪造的信息。从而防止黑客伪造一个完整的请求欺骗服务器。
「防范措施」:验证码机制,验证来源站点,利用Cookie的SameSite属性,CSRF Token
# 遇到的问题
# 1、element ui 的el-table表格
现象:在el-table最后一列操作栏设置el-popover时,出现了多个el-popover
原因:element ui 的el-table表格的表头和其他定位元素,比如设置了fixed属性的列,他其实是多创建了一个table,隐藏了部分其他不需要的元素。通过看源码得知,从控制台验证。
解决:不设置fix:right,感觉是element的一个坑
# 2、swiper的兼容性问题。
现象:用户在低版本安卓、ios系统打开app,网页页面空白
原因:1、第三方库swiper提供的是源码,es6语法的源码没有经过编译。
2、第三方库swiper的依赖库dom7也用了es6语法并且没有编译。
3、babel编译代码时会默认忽略node_modules里的依赖。
解决:1、确定了js文件是加载了但是vue代码没执行,获取到用户的浏览器版本是chrome51,专门去找了对应版本的浏览器,在windows虚拟机安装后复现出问题,确定是es6语法报错,并且开启测试服的sourcemap,找到对应语法所在的依赖就是swiper。
在项目配置里显示指定babel编译第三方依赖swiper。
2、之后尝试了一下ie浏览器发现可以查出语法报错问题,是dom7这个依赖的es6语法。
在项目配置里显示指定babel编译依赖dom7。
transpileDependencies: [ "swiper", "dom7" ]