admin管理员组文章数量:1547523
一、网络相关
(一)Get和Post请求的区别
简单来说:GET产生一个TCP数据包,POST产生两个TCP数据包
严格的说:对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);而对于POST请求。浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)
(二)状态码
- 1**:信息,服务器收到请求,需要请求者继续执行操作
- 2**:成功,操作被成功接收并处理
- 3**:重定向,需要进一步的操作以完成请求
- 4**:客户端错误,请求包含语法错误或者无法完成请求
- 5**:服务器错误,服务器在处理请求的过程中发生了错误
常见状态码:
- 200:请求成功
- 301:永久重定向
- 302、307:临时重定向,请求的文档被临时移动到别处
- 304:缓存(协商缓存)
- 401:请求要求用户的身份认证
- 403:禁止,服务器理解客户端请求,但是拒绝处理此请求,通常是权限设置所致(一般设置ip过滤)
- 404:请求的资源(网页等)不存在
- 500:内部服务器错误
- 502:充当网关或代理的服务器从远端服务器接收到了一个无效的请求;Nginx没配好,或者服务没开启
- 504:请求超时
(三)缓存机制
1. 强缓存
不会发起请求,状态码为200,Size显示from disk cache或from memory cache;
强缓存可以通过设置两种 HTTP Header 实现:Expires 和 Cache-Control。Cache-Control比Expires优先级高
- Expires
http1.0产物;
缓存过期时间,用来指定资源到期的时间,是服务器端的具体的时间点;受限于本地时间; - Cache-Control
http1.1产物;
参数:
- public:所有内容都将被缓存(客户端和代理服务器都可缓存)
- private:所有内容只有客户端可以缓存,默认取值
- no-cache:不使用强缓存
- no-store:不适用缓存(包括强缓存和协商缓存)
- max-age:max-age=xxx (xxx is numeric)表示缓存内容将在xxx秒后失效
- s-maxage(单位为s):同max-age作用一样,只在代理服务器中生效(比如CDN缓存),s-maxage的优先级高于max-age。如果存在s-maxage,则会覆盖掉max-age和Expires header。
- max-stale:能容忍的最大过期时间
- min-fresh:能够容忍的最小新鲜度
2. 协商缓存
会发出请求,协商缓存生效,返回304和Not Modified;协商缓存失效,返回200和请求结果;
协商缓存可以通过设置两种 HTTP Header 实现:Last-Modified 和 ETag 。Etag要优于Last-Modified
- Last-Modified和If-Modified-Since
第一次获取资源时,在response header中添加 Last-Modified的header,值是这个资源在服务器上的最后修改时间,浏览器接收后缓存文件和header;下一次请求的request header中浏览器会将Last-Modified的值添加给If-Modified-Since,服务器通过Last-Modified和If-Modified-Since的值做比较,如果相同则返回304,如果不同则返回200和最新资源;
弊端:本地打开缓存文件,Last-Modified会被修改,导致缓存失效;是秒级别的,如果修改时间小于秒级别则不能被感知 - ETag和If-None-Match
Etag是服务器响应请求时,返回当前资源文件的一个唯一标识hash(由服务器生成),只要资源有变化,Etag就会重新生成。览器在下一次加载资源向服务器发送请求时,会将上一次返回的Etag值放到request header里的If-None-Match里,服务器比较两个hash值来判断是否返回新资源
(四)HTTP和HTTPS
1. HTTP协议
HTTP协议是一种使用明文数据传输的网络协议。
特点:
- 无状态,可以通过Cookie/Session技术来保持状态
- 无连接,HTTP/1.0使用短连接;HTTP/1.1使用长连接,持久连接(HTTP keep-alive)方法,只要任意一端没有明确提出断开连接,则保持TCP连接状态,在请求首部字段中的Connection: keep-alive即为表明使用了持久连接
- 基于请求和响应
- 简单快速、灵活
- 通信使用明文、请求和响应不会对通信方进行确认、无法保护数据的完整性
http2.0
- http1.x采用文本方式传输,而http2.0采用二进制传输
- 多路复用:即在一个TCP连接中存在多个流,即可以同时发送多个请求,对端可以通过帧中的表示知道该帧属于哪个请求(帧是最小的数据单位,每个帧会标识出该帧属于哪个流,流是多个帧组成的数据流);解决了1.x并行时出现的头队阻塞问题
- header压缩:使用了HPACK(HTTP2头部压缩算法)压缩格式对传输的header进行编码,减少了header的大小
- 服务器Push:服务端可以在客户端某个请求后,主动推送其他资源
- 更安全
新的底层协议QUIC
2. HTTPS协议
基于HTTP协议,通过SSL或TLS提供加密处理数据、验证对方身份以及数据完整性保护
特点:
- 内容加密:采用混合加密技术,中间者无法直接查看明文内容
- 验证身份:通过证书认证客户端访问的是自己的服务器
- 保护数据完整性:防止传输的内容被中间人冒充或者篡改
对称加密(共享密匙加密)
客户端和服务器公用一个密匙用来对消息加解密,这种方式称为对称加密。
对称加密的优点:对称加密解决了http中消息保密性的问题
对称加密的缺点:对称加密虽然保证了消息保密性,但是因为客户端和服务器共享一个密匙,这样就使得密匙特别容易泄露。
非对称加密(公有密匙加密)
采用非对称加密时,客户端和服务端均拥有一个公有密匙和一个私有密匙。公有密匙可以对外暴露,而私有密匙只有自己可见。这样客户端在发送消息前,先用服务器的公匙对消息进行加密,服务器收到后再用自己的私匙进行解密。
非对称加密的优点:
- 非对称加密采用公有密匙和私有密匙的方式,解决了http中消息保密性问题,而且使得私有密匙泄露的风险降低;
- 因为公匙加密的消息只有对应的私匙才能解开,所以较大程度上保证了消息的来源性以及消息的准确性和完整性。
非对称加密的缺点: - 非对称加密时需要使用到接收方的公匙对消息进行加密,但是公匙不是保密的,任何人都可以拿到,中间人也可以。那么中间人可以做两件事,第一件是中间人可以在客户端与服务器交换公匙的时候,将客户端的公匙替换成自己的。这样服务器拿到的公匙将不是客户端的,而是服务器的。服务器也无法判断公匙来源的正确性。第二件是中间人可以不替换公匙,但是他可以截获客户端发来的消息,然后篡改,然后用服务器的公匙加密再发往服务器,服务器将收到错误的消息;
- 非对称加密的性能相对对称加密来说会慢上几倍甚至几百倍,比较消耗系统资源。正是因为如此,https将两种加密结合了起来。
ssl混合加密
- 客户端申请https通信
- 服务器响应并向客户端传递证书
- 客户端验证证书,获取公钥,生成对称加密密钥,用公钥加密后传给服务端
- 服务端接受消息,用私钥解密,拿出对称密钥,并通知客户端。ssl通道建立完成,https通道也建立完成
- 共享密钥交换成功,https通信建立后,客户端和服务端利用共享密钥加密通信
(五)TCP/IP
1. 层级模型
七层模型
应用层、表示层、会话层、传输层、网络层、数据链路层、物理层
TCP/IP概念层模型
应用层(应用层、表示层、会话层)、传输层、网络层、链路层(数据链路层、物理层)
2. 传输层协议:TCP和UDP
- TCP 是面向连接的、可靠的流协议。流就是指不间断的数据结构,
- UDP 是不具有可靠性的数据报协议。
- TCP 和 UDP 的优缺点无法简单地、绝对地去做比较:TCP 用于在传输层有必要实现可靠传输的情况;而在一方面,UDP 主要用于那些对高速传输和实时性有较高要求的通信或广播通信。TCP 和 UDP 应该根据应用的目的按需使用。
3. 三次握手、四次挥手
三次握手
- 第一次握手:客户端将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给服务器端,客户端进入SYN_SENT状态,等待服务器端确认。
- 第二次握手:服务器端收到数据包后由标志位SYN=1知道客户端请求建立连接,服务器端将标志位SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给客户端以确认连接请求,服务器端进入SYN_RCVD状态。
- 第三次握手:客户端收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给服务器端,服务器端检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,客户端和服务器端进入ESTABLISHED状态,完成三次握手,随后客户端与服务器端之间可以开始传输数据了。
四次挥手
- 第一次挥手:客户端发送一个FIN=M,用来关闭客户端到服务器端的数据传送,客户端进入FIN_WAIT_1状态。意思是说"我客户端没有数据要发给你了",但是如果你服务器端还有数据没有发送完成,则不必急着关闭连接,可以继续发送数据。
- 第二次挥手:服务器端收到FIN后,先发送ack=M+1,告诉客户端,你的请求我收到了,但是我还没准备好,请继续你等我的消息。这个时候客户端就进入FIN_WAIT_2 状态,继续等待服务器端的FIN报文。
- 第三次挥手:当服务器端确定数据已发送完成,则向客户端发送FIN=N报文,告诉客户端,好了,我这边数据发完了,准备好关闭连接了。服务器端进入LAST_ACK状态。
- 第四次挥手:客户端收到FIN=N报文后,就知道可以关闭连接了,但是他还是不相信网络,怕服务器端不知道要关闭,所以发送ack=N+1后进入TIME_WAIT状态,如果Server端没有收到ACK则可以重传。服务器端收到ACK后,就知道可以断开连接了。客户端等待了2MSL后依然没有收到回复,则证明服务器端已正常关闭,那好,我客户端也可以关闭连接了。最终完成了四次握手。
(六)同源策略、跨域
1. 同源策略
- 同源的概念:协议、域名、端口均相同则称为同源
- 同源策略:同源策略是浏览器的一个安全功能,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源。
2. 跨域
JSONP
利用script标签(get请求)可跨域的特点,在跨域脚本中可以直接回调当前脚本的函数
CORS
服务器设置HTTP响应头中Access-Control-Allow-Origin值,解除跨域限制
- 简单请求
对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个Origin字段。
如果满足以下条件就属于简单请求
1)请求方法是post、get、head之一
2)HTTP的头信息不超出以下几种字段:
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type:只限于三个值
application/x-www-form-urlencoded、multipart/form-data、text/plain
响应头参数 - Access-Control-Allow-Origin:必选,同意跨域的源
- Access-Control-Allow-Credentials:非必选,它的值是一个布尔值,表示是否允许发送Cookie;客户端也必须打开withCredentials
- Access-Control-Expose-Headers:非必选,客户端如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定
- 非简单请求(预检请求)
非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json
预检"请求用的请求方法是OPTIONS,且只发送header
请求头参数:
- origin
- Access-Control-Request-Method:该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法,上例是PUT
- Access-Control-Request-Headers:该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段
响应头参数: - Access-Control-Allow-Methods:该字段必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法
- Access-Control-Allow-Headers
- Access-Control-Allow-Credentials
- Access-Control-Max-Age:该字段可选,用来指定本次预检请求的有效期,单位为秒
(七)网络安全
1. XSS
跨站脚本攻击(Cross Site Scripting)黑客通过js代码劫持客户端跟服务器之间的会话。
XSS其实就是Html的注入问题,攻击者的输入没有经过严格的控制进入了数据库,最终显示给来访的用户,导致可以在来访用户的浏览器里以浏览用户的身份执行Html代码,数据流程如下:攻击者的Html输入—>web程序—>进入数据库—>web程序—>用户浏览器。
类型:
- 反射型XSS
用户点击带有特定参数的链接引起的攻击,是非持久的 - 持久性XSS
攻击者将攻击代码存入了数据库中。 - DOM based XSS
基于文档对象模型(Document Objeet Model,DOM)的一种漏洞,攻击代码从url中通过dom操作被写入html中
危害:
- 盗用 cookie ,获取敏感信息
- 利用植入 Flash ,通过 crossdomain 权限设置进一步获取更高权限;或者利用Java等得到类似的操作
- 利用 iframe、frame、XMLHttpRequest或上述Flash等方式,以(被攻击)用户的身份执行一些管理动作,或执行一些一般的如发微博、加好友、发私信等操作
- 利用可被攻击的域受到其他域信任的特点,以受信任来源的身份请求一些平时不允许的操作,如进行不当的投票活动
- 在访问量极大的一些页面上的XSS可以攻击一些小型网站,实现DDoS攻击的效果
防御:
对输入(和URL参数)进行过滤,对输出进行编码。
2. CSRF
CSRF跨站请求伪造, 顾名思义就是伪造请求,冒充用户在站内的正常操作。利用攻击网站利用源网站的cookie发起危险请求。
防御
- 修改信息使用post不用get
- 加验证码
- 使用token
- 使用referer
二、JS基础
(一)执行期上下文
- 创建全局GO对象和AO对象(执行期上下文),先GO后AO AO对象是独一无二的,每次执行函数都会创建
- 找形参和变量声明,将变量和形参名作为AO对象的属性名,其值为undefined
- 将实参与形参相统一,实参值放入形参
- 找函数声明,函数名作为AO对象属性名,将值变为函数体
例子:
function fn(a) {
console.log(a); //function a()
var a = 123;
cnosole.log(a); //123
function a(){}
console.log(a); //123
var b = function(){};
console.log(b); //function b()
function d(){}
}
fn(1)
(二)原型 prototype
定义:原型是function对象的一个属性,它定义了构造函数制造出来的对象的公共祖先,通过该构造函数产生的对象,可以继承该原型的属性和方法。原型也是对象。
- 将固定值放入原型,提取公有属性
Car.prototype.carName = "BMW";
function Car(color){
this.color = color;
}
var car = new Car("red");
-
修改与原型同名的属性,会在自身添加该属性
-
通过delete删除自身属性,无法删除原型属性
-
constructor
返回构造函数,继承至prototype
,可以手动修改
Car.prototype = {
constructor: Person
}
__proto__
属性用于连接自身和原型,默认指向prototype
Obj.prototyp.name = "123";
function Obj(){}
var obj = new Obj();
Obj.prototype.name = "321";
console.log(obj.name) //"321"
var obj = new Obj();
Obj.prototype = {
name: "321"
}
obj.name; //"123"
Obj.prototype = {name: "321"};
var obj = new Obj();
obj.name //321
- 但
new Obj()
时,在内部生成this{__proto__: Obj.prototype}
,obj就获得了this,修改Obj.prototype
属性是直接修改指向的那一段内存所存的东西 - 而
Obj.prototype={}
,是将prototype
指向的内存变了,但__proto
指向的内存没有改变 new
之后只有__proto__
,而没有prototype
(三)this指向
- 函数预编译过程中this指向window
- 全局作用域里this指向window
- call、apply可以改变this的指向
- 谁调用了这个方法,方法里面的this就指向谁
- 箭头函数内部的this对象就是定义时所在的对象
arguments.callee
指向函数自身引用function.caller
指向函数运行环境
(四)call、apply、bind
用来改变this指向
apply和call的区别是call方法接收的若干个参数列表,而apply接收的是一个包含多个参数的数组
bind绑定后会创建一个新函数,不会立即执行
fun.call(this, arg1, arg2, arg3)
fun.apply(this, [arg1, arg2, agr3])
fun.bind(this, arg1, arg2)()
手写实现:
call()
Function.prototype.myCall = function(context) {
// 判断是否是undefined和null
if (typeof context === 'undefined' || context === null) {
context = window
}
// 首先要获取调用call的函数,用this可以获取
context.fn = this
let args = [...arguments].slice(1)
let result = context.fn(...args)
delete context.fn
return result
}
apply()
Function.prototype.myApply = function(context) {
if (typeof context === 'undefined' || context === null) {
context = window
}
context.fn = this
let args = arguments[1]
let result
if (args) {
result = context.fn(...args)
} else {
result = context.fn()
}
delete context.fn
return result
}
bind()
Function.prototype.myBind = function(context) {
if (typeof this !== 'function') {
throw new TypeError('Error')
}
let _this = this
let args = [...arguments].slice(1)
return function F() { // 判断是否被当做构造函数使用
if (this instanceof F) {
return _this.apply(this, args.concat([...arguments]))
}
return _this.apply(context, args.concat([...arguments]))
}
}
(五)闭包
- 定义:当内部函数被保存到外部,就产生了闭包。闭包有可能造成内存泄露。
function a(){
function b(){
var b = 123;
a = 222;
console.log(a);
}
var a = 123;
return b;
}
var demo = a();
demo(); //222
- 作用:
- 闭包可以实现公有变量
- 闭包可以做缓存(私有化变量)
- 触发闭包
function test(){
var arr = [];
for(var i = 0; i < 10; i++){
arr[i] = function(){
console.log(i);
}
}
return arr;
}
var myArr = test();
for(var i = 0; i < 10; i++){
myArr[i]();
}
//10 10 10 10 10 10 10 10 10
- 消除闭包:用立即执行函数创建独立的AO
function test(){
var arr = [];
for (var i = 0; i < 10; i++){
(function(j){
arr[j] = function(){
console.log(j);
}
}(i));
}
return arr;
}
(六)作用域链
- [[scope]]:数作用域,隐式属性,不可使用。其中存了执行期上下文的集合
- 作用域链: [[scope]]中所存储的执行期上下文对象的集合,这个集合呈链式链接,这种链式链接称为作用域链
function a(){
function b(){
var b = 234;
}
var a = 123;
b();
}
var glob = 100;
a();
执行流程:
1. a定义:a.[[scope]] => {0: GO{}}
2. a执行:a.[[scope]] => {0: aAO{}, 1: GO{}}
3. b定义:b.[[scope]] => {0: aAO{}, 1: GO{}}
4. b执行:b.[[scope]] => {0: bAO{}, 1:aAO{}, 2:GO{}}
(七)继承
1. 分类:
按照是否使用Object.create分为:
不使用Object.create:原型链继承、构造函数继承、组合继承(由前两种组合)
使用Object.create:原生式继承、寄生式继承、寄生组合继承(由前两种改造、组合继承优化得到)
2. 继承方式:
2.1 原型链继承
核心:将父类的实例作为子类的原型
SubType.prototype = new SuperType()
SubType.prototype.constructor = SubType // 修正子类构造函数指向它本身
优点:父类可以复用
缺点:
- 父类的引用属性会被所有子类实例共享
- 子类构建实例时不能向父类传递参数
2.2 构造函数继承
核心:将父类构造函数的内容复制给了子类的构造函数。这是所有继承中唯一一个不涉及到prototype的继承。
SuperType.call(SubType);
优点:
- 父类的引用属性不会被共享
- 子类构建实例时可以向父类传递参数
缺点:父类的方法不能复用,子类实例的方法每次都是单独创建的
2.3 组合继承
核心:原型式继承和构造函数继承的组合,兼具了二者的优点
function SuperType() {
this.name = 'parent';
this.arr = [1, 2, 3];
}
SuperType.prototype.say = function() {
console.log('this is parent')
}
function SubType() {
SuperType.call(this) // 第二次调用SuperType
}
SubType.prototype = new SuperType() // 第一次调用SuperTyp
优点:
- 父类的方法可以被复用
- 父类的引用属性不会被共享
- 子类构建实例时可以向父类传递参数
缺点:
调用了两次父类的构造函数,第一次给子类的原型添加了父类的name, arr属性,第二次又给子类的构造函数添加了父类的name, arr属性,从而覆盖了子类原型中的同名参数。这种被覆盖的情况造成了性能上的浪费。
2.4 原型式继承
核心:原型式继承的object方法本质上是对参数对象的一个浅复制,可以直接使用es5的Object.create()
function object(o){
function F(){}
F.prototype = o;
return new F();
}
优点:父类方法可以复用
缺点:
- 父类的引用属性会被所有子类实例共享
- 子类构建实例时不能向父类传递参数
2.5 寄生式继承
核心:使用原型式继承获得一个目标对象的浅复制,然后增强这个浅复制的能力
function createAnother(original){
var clone=object(original); //通过调用函数创建一个新对象
clone.sayHi = function(){ //以某种方式来增强这个对象
alert("hi");
};
return clone; //返回这个对象
}
2.6 寄生组合继承
function clonePrototype(Super,Suber){
var prototype = Object.create(Super.prototype);
prototype.constructor = Suber;
Suber.prototype = prototype;
}
(八) Promise
1. 实现promise.all()
Promise.prototype.all = function(promises) {
let results = [];
let promiseCount = 0;
let promisesLength = promises.length;
return new Promise(function(resolve, reject) {
for (let val of promises) {
Promise.resolve(val).then(function(res) {
promiseCount++;
// results.push(res);
results[i] = res;
// 当所有函数都正确执行了,resolve输出所有返回结果。
if (promiseCount === promisesLength) {
return resolve(results);
}
}, function(err) {
return reject(err);
});
}
});
};
2. 中断Promise
- 当新对象保持“pending”状态时,原Promise链将会中止执行
Promise.resolve().then(() => {
console.log('ok1')
return new Promise(()=>{}) // 返回“pending”状态的Promise对象
}).then(() => {
// 后续的函数不会被调用
console.log('ok2')
}).catch(err => {
console.log('err->', err)
})
- Promise.race竞速方法
let p1 = new Promise((resolve, reject) => {
resolve('ok1')
})
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {resolve('ok2')}, 10)
})
Promise.race([p2, p1]).then((result) => {
console.log(result) //ok1
}).catch((error) => {
console.log(error)
})
- 当Promise链中抛出一个错误时,错误信息沿着链路向后传递,直至被捕获
Promise.resolve().then(() => {
console.log('ok1')
throw 'throw error1'
}).then(() => {
console.log('ok2')
}, err => {
// 捕获错误
console.log('err->', err)
}).then(() => {
// 该函数将被调用
console.log('ok3')
throw 'throw error3'
}).then(() => {
// 错误捕获前的函数不会被调用
console.log('ok4')
}).catch(err => {
console.log('err->', err)
})
三、事件循环机制
同步事件->微任务->宏任务
宏任务 macrotask
- setTimeout
- setInterval
- setImmediate
- I/O
- UI rendering
微任务 microtask
- process.nextTick
- promise
- Object.observe
- MutationObserver
遇到await会先执行右边内容,然后跳出函数执行后续的同步任务,再返回函数执行await后续内容;await后面的内容会以微任务的形式执行。
四、Vue相关
(一)虚拟dom和diff算法
为什么使用虚拟dom?
因为真实dom的实现内容太多了,如果直接操作真实dom对象,对性能是巨大的浪费。虚拟dom就是一个简化后的dom对象,只存储了重要的参数。virtual dom很多时候都不是最优的操作,但它具有普适性,在效率、可维护性之间达平衡。虚拟dom类似于计算机中的缓存。包括几个步骤:
- 用 JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文档当中
- 状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异
- 把2所记录的差异应用到步骤1所构建的真正的DOM树上,视图就更新了
两个树的完全的 diff 算法时间复杂度是O(n^3),vue的diff算法采用同层比较的方式将diff算法的时间复杂度优化为O(n)。深度优先遍历,不断的遍历比较子节点,使用patch补丁记录差异。
(二)$nextTick原理
本质是microtask;
旧版本使用MutationObserver,由于IOS9.3的WebvView的MO有bug;
后来改成了window.postMessage,但该任务的回调是放入macrotask队列中的,那么会在当前的task和所有microtask执行完毕之后才在以后的某一次task执行过程中处理flushBatcherQueue,这个时候已经错过了多此触发重绘、渲染UI的时机;
最后,改成了promise,降级使用MO。尽可能microtask优先,可以更快的将变化呈现给用户
同步任务 -> microtask -> macrotask -> ui renderr
(三)响应式/双向绑定原理
vue的双向绑定指的是数据变化更新视图,视图变化更新数据。视图变化更新数据一般就采用事件监听的方式就可以了。数据变化更新视图就需要涉及效应式原理。
vue2.x的响应式原理的基础是Objec.defineProperty属性。vue主要通过Observer、Watcher、Dep这三个主要的类来实现。首先vue通过observer类把data函数返回的数据对象的所有属性使用defineProperty转化为getter/setter,变成可观察的。然后每一个data都会声明一个Dep,dep就会收集需要监听数据变化的watcher,每一个watcher对应一个实际使用的数据(如render、computed、watch),同时watch也会收集对应dep,他们是一个多对多的关系;当数据发生变化时,就会触发set,dep就会通知所有订阅了该数据的watcher,最后由watche回调cb进行视图的更新。
而在vue3.x中用Proxy和Reflect替代了Object.defineProperty
(四)computed和watch的区别
computed:是计算属性,依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值
watch:没有缓存性,更多的是「观察」的作用,类似于某些数据的监听回调 ,每当监听的数据变化时都会执行回调进行后续操作;当我们需要深度监听对象中的属性时,可以打开deep:true选项,这样便会对对象中的每一项进行监听;handler\immediate。watch最初绑定的时候式不会执行的,需要将immediate设置为true才能立即执行handler方法。
运用场景:
- 当我们需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算;
- 当我们需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用watch选项允许我们执行异步操作 ( 访问一个 API ),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。
(五)vue-router
1. 路由模式
- hash模式:默认路由模式,再域名后加#
- history模式:基于HTML5的history对象,当用户刷新或直接输入地址时会向服务器发送一个请求,所以history模式需要服务端同学进行支持,将路由都重定向到根路由
2. 使用
import VueRouter from 'vue-router';
Vue.use(VueRouter);
// 配置路由
const routes = [
{
path: '',
redirect: {name:xxx} // 重定向
component: () => import('....vue') // 懒加载
}, {
path: '/user',
component: userPage,
}];
// 创建Router实例
const router = new VueRouter({
routes,
mode: 'history', // 设置路由模式
});
new Vue({
router,
render: h => h(App)
}).$mount('#app');
动态路由配置:
const routes = [
{
path: '',
component: indexPage,
}, {
path: '/user/:id',
component: userPage,
}];
$router.params.id
嵌套路由配置
routes: [
{ path: '/user/:id', component: User,
children: [
{
// 当 /user/:id/profile 匹配成功,
// UserProfile 会被渲染在 User 的 <router-view> 中
path: 'profile',
component: UserProfile
}
]
}
]
(六)生命周期顺序
父 beforeCreate-> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 Created -> 子 mounted -> 父 mounted
父 beforeUpdate -> 子 beforeUpdate -> 子 Updated -> 父 Updated
父 beforeDestroy -> 子 beforeDestroy -> 子destroyed -> 父 destroyed
五、Webpack相关
(一) Loader和Plugin的区别以及常用的Loader和PluginLoader
因为webpack只认识js所以,需要使用loader将其他类型的资源进行转义;Plugin翻译过来就是插件,用来扩展webpack的功能。
Loader在modules.rules中配置,作为模块的解析规则,类型为数组。每一项都是一个Object,内部包含了test(类型文件)、loader、option等属性
Plugin在plugins中配置,类型为数组,每一项是一个Plugin的实例,参数通过构造函数传入
常见的loader:
- vue-loader
- css-loader
- style-loader
- less-loader
- babel-loader
- file-loader
- url-loader
常见的plugin - html-webpack-plugin
- clean-webpack-plugin
- VueLoaderPlugin
- mini-css-extract-plugin
- uglifyjs-webpack-plugin
- hard-source-webpack-plugin:dll
- happypack: HappyPack 的核心原理就是把loader文件转换的这部分任务分解到多个进程去并行处理,从而减少了总的构建时间。
- webpack-bundle-analyzer:打包分析
(二)Webpack构建流程
Webpack 的运行流程是一个串行的过程,从启动到结束会依次执行以下流程:
- 初始化参数:从配置文件和shell语句中读取与合并参数,得出最终的参数
- 开始编译:用上一步得到的参数初始化compiler对象,加载所有配置的插件,执行对象的run方法开始执行编译
- 确定入口:根据配置中entry找出所有入口文件
- 编译模块:从入口文件出发,调用所有配置的loader对模块进行转译,在找出该模块依赖的模块,在递归本步骤知道所有入口依赖的文件都经过了本步骤的处理
- 完成模块编译:在经过第4步使用loader转译完所有模块后,得到每个模块被翻译后的最终内容以及他们之间的依赖关系
- 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的chunk,再把每个chunk转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会
- 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统
简单的说: - 初始化:启动构建,读取与合并配置参数,加载plugin,实例化compiler
- 编译:从entry出发,针对每个module串行调用对应的loader去翻文件的内容,再找到该module依赖的module,递归的进行编译处理
- 输出:将编译后的module组合成chunk,将chunk转换成文件,输出到文件系统中
(三)Webpack 的热更新原理
HMR, 这个机制可以做到不用刷新浏览器而将新变更的模块替换掉旧的模块;使用webpack-dev-server在devServer中配置hot为true
HMR的核心就是客户端从服务器拉去更新后的文件,准确说是chunk diff(chunk需要更新的部分),实际上WDS与浏览器之间维护一个websocket,当本地资源发生变化时,WDS向浏览器推送更新,并带上构建时的hash,让客户端与上一次资源进行对比。客户端对比出差异后会向WDS发起Ajax请求来获取更改内容(文件列表、hash),这样客户端就可以再借助这些信息继续向WDS发起jsonp请求获取该chunk的增量更新。后面具体的(拿到增量更新之后如何处理?哪些状态该保留?哪些又需要更新?)就由HotModulePlugin来完成,提供了相关 API 以供开发者针对自身场景进行处理, vue-loader 就是借助这些 API 实现 HMR
(四)文件指纹
文件指纹是打包后输出的文件名的后缀
- Hash:和整个项目的构建相关,只要项目文件有修改,整个项目构建的hash值就会更改
- Chunkhash:和Webpack打包的chunk有关,不同的entry会生出不同的chunkhash
- Contenthash:根据文件内容来定义hash,文件内容不变,则contenthash不变
(五)webpack优化
- 多进程/多实例构建:HappyPack(不维护了)、thread-loader
- 压缩代码
- 通过 mini-css-extract-plugin 提取 Chunk 中的 CSS 代码到单独文件,通过 css-loader 的 minimize 选项开启 cssnano 压缩 CSS。
- 多进程并行压缩:
- webpack-paralle-uglify-plugin
- uglifyjs-webpack-plugin 开启 parallel 参数
- terser-webpack-plugin 开启 parallel 参数
- 图片压缩:设置limit小图片转base64,大图片用image-webpack-loader压缩
- 拆包:
- 使用 html-webpack-externals-plugin,将基础包通过 CDN 引入,不打入 bundle 中
- 使用 SplitChunksPlugin 进行(公共脚本、基础包、页面公共文件)分离(Webpack4内置) ,替代了 CommonsChunkPlugin 插件
- dll:HardSourceWebpackPlugin
- Tree shaking
六、优化相关
(一)节流和防抖
1. 函数节流
-
定义:指定时间间隔内只会执行一次任务
-
实现:
function throttle(fn, interval = 300) {
let canRun = true;
return function () {
if (!canRun) return;
canRun = false;
setTimeout(() => {
fn.apply(this, arguments);
canRun = true;
}, interval);
};
}
2. 函数防抖
- 定义:任务频繁触发的情况下,只有任务触发的间隔超过指定间隔的时候,任务才会执行
- 实现:
function debounce(fn, interval = 300) {
let timeout = null;
return function () {
clearTimeout(timeout);
timeout = setTimeout(() => {
fn.apply(this, arguments);
}, interval);
};
}
七、各种规范
(一)AMD、CMD、commenJS、UMD
都是js模块化的一些解决方案规范。
amd是由requireJS推动一个异步加载模块的规范,通过define定义模块,require去引用模块
cmd是由seaJS推动的模块化规范,推崇一个模块一个文件,通过define定义模块,seajs.use来加载模块
amd推崇依赖前置,在定义模块的时候就要声明其依赖的模块
cmd推崇就近依赖,只有在用到某个模块的时候再去require
commenJS是一种同步加载模块的规范,被nodejs推广开,使用module.exports和require去使用
umd是AMD和CommonJS的糅合,umd先判断是否支持Node.js的模块(exports)是否存在,存在则使用Node.js模块模式。在判断是否支持AMD(define是否存在),存在则使用AMD方式加载模块
模块的循环加载
CommonJS模块是加载时执行,即脚本代码在require时就全部执行。一旦出现某个模块被“循环加载”,就只输出已经执行的部分,没有执行的部分不会输出;再次加载直接从缓存中获取
S6模块对导出变量,方法,对象是动态引用,遇到模块加载命令import时不会去执行模块,只是生成一个指向被加载模块的引用,需要开发者保证真正取值时能够取到值,只要引用是存在的,代码就能执行
(二)Restful
(三)BFC
BFC(Block formatting context)直译为"块级格式化上下文"。它是一个独立的渲染区域,只有Block-level box参与, 它规定了内部的Block-level Box如何布局,并且与这个区域外部毫不相干。
触发条件:
- 父级块框自带隐藏BFC属性
- 浮动元素
- 绝对定位元素(包括absolute和fixed)
- 框类型display为:inline-block|table-cell|table-caption
- overflow属性为hidden|auto|scroll
八、面向对象
(一)什么是面向对象?
面向对象是一种编程思想,JS本身就是基于面向对象构建出来的(例如:JS中有很多的内置类,像Promise是ES6中新增的一个内置类,我们可以基于new Promise来创建一个实例,来管理异步编程),我们平时用的Vue也是基于面向对象构建出来的,它们都是类,平时开发的时候都是创建它们的实例来操作。当然我自己在真实项目中也封装过一些组件插件,也是基于面向对象开发的,这样可以创造不同的实例,来管理私有的属性和共有的方法,很方便
JS中的面向对象,和其他编程语言还是略有不同的,JS中类和实例是基于原型和原型链机制来处理的;而且JS中关于类的重载、重写、继承也和其他语言不太一样。JS中重载不像其他语言是多个方法方法名相同、参数不同来实现重载,而一般是通过同一个函数中实参的个数来实现不同的功能,因为在JS中多个同名方法在同一个作用域下只会保留最后定义的。
九、浏览器相关
(一)页面构建流程
- 处理html标记并构建dom树
- 处理css标记并狗叫cssom树
- 将dom和cssom合并成一个render树
- 根据render树来布局,以计算每个节点的几何信息
- 将各个节点绘到屏幕上
(二)回流和重绘
回流:当Render Tree中部分或全部元素的尺寸、结构、或某些属性发生改变时,浏览器重新渲染部分或全部文档的过程称为回流
重绘:当页面中元素样式的改变并不影响它在文档流中的位置时(例如:color、background-color、visibility等),浏览器会将新样式赋予给元素并重新绘制它,这个过程称为重绘
回流比重绘的代价要更高
十、CSS相关
(一)盒模型
- 标准盒模型:宽高只包含content,不包含border和padding
- IE(怪异)盒模型:宽高包含border和padding,即content+padding+border
若在页面中声明了DOCTYPE类型,所有的浏览器都会把盒模型解释为W3C盒模型。若不声明DOCTYPE类型,IE浏览器会将盒子模型解释为IE盒子模型,FireFox等会将其解释为W3C盒子模型;
使用box-sizing: border-box可以将标准和模型转换为怪异盒模型
(二)居中
- 宽高固定元素
position: absolute;
top: 50%;
left: 50%
margin: 宽高的值一半 - 宽高不固定元素
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%); - 万能flex
display: flex;
justify-content: center;
align-items: center;
(三)清除浮动
- 在浮动元素后使用一个空元素如 ,并在CSS中赋予.clear{clear:both;}属性即可清理浮动
- 给浮动元素的容器添加overflow:hidden;或overflow:auto;可以清除浮动
- 给浮动的元素的容器添加浮动:,但是这样会使其整体浮动,影响布局,不推荐使用
- 使用CSS的:after伪元素,使用clear:both
(四)flex
1. 容器属性
- flex-direction主轴方向,项目的排列方向
- flex-wrap如何换行
- flex-flow = ||
- justify-content水平对齐方式
- align-items垂直对齐方式
- align-content定义了多根轴线的对齐方式,如果项目只有一根轴线,该属性不起作用
2. 项目属性
- order定义项目的排列顺序。数值越小,排列越靠前,默认为0。
- flex-grow属性定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大
- flex-shrink属性定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小
- flex-basis属性定义了在分配多余空间之前,项目占据的主轴空间
- flex: flex-grow, flex-shrink 和 flex-basis的简写,默认值为0 1 auto
- align-self属性允许单个项目有与其他项目不一样的对齐方式,可覆盖align-items属性
3.flex-shrink的计算
先计算总权重TW = 100px * 1(flex-shrink) + 200px *2(flex-shrink) + 300px *3(flex-shrink) = 1400px 也就是每个div的宽度乘以flex-shrink系数的总和
每个div收缩的空间为:div的宽度 * flex-shrink系数/ 总权重TW * 需要收缩的总宽度(在我们的例子中是600px - 500px = 100px)
本文标签:
版权声明:本文标题:前端面试整理 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://m.elefans.com/xitong/1727193427a1101673.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论