web前端常规知识点

200 阅读8分钟

深拷贝与浅拷贝

数据类型

  • 基本数据类型number,string,boolean,null,undefined,symbol
  • 引用数据类型:object({}对象,[]数组),function

假如修改了A,B中的数据跟着变化,那就是浅拷贝
如果没有变化,那就是深拷贝,说明B另外开辟了一块新的内存存储。

基本数据类型一般存储在栈中;
引用数据类型属性名存在栈内存中,属性值存放在堆内存中,但是栈内存会提供一个引用的地址指向堆内存中的值

深拷贝方法

  • 使用递归方式,递归所有层级的属性
  • 使用JSON.stringify()和JSON.parse()
  • 借用JQ的$.extend()方法

盒子模型

  • content-box(w3c盒子模型):不包含内边距和边框
  • border-box(ie盒子模型):包含内边距和边框

原型和原型链

Person是一个构造函数,我们使用new构造一个实例对象person

prototype

每个函数都有一个prototype属性
每一个js对象在创建的时候都会指向它所在的原型,并从原型上继承属性

proto

每一个js对象都有proto属性,该属性指向该对象的原型

constructor

每个原型都有一个constructor属性指向关联的构造函数(即实例原型指向构造函数)

实例与原型

当从实例的对象中找不到属性的时候,就会往实例的原型中去找

person.__proto__ == Person.prototype //true

原型的原型/原型链

js默认不会复制对象的属性,相反,js只是在两个对象之间创建一个关联,这样一个对象就可以继承访问另一个对象的属性和函数。
原型链核心是依赖对象的_proto_的指向,当自身不存在的属性时候,就一层层的趴出创建对象的构造函数,直到object时就没有_proto_的指向了。
因为_proto_的实质找的是prototype,所以只要找到链条上面的构造函数的prototype,其中因为object.prototype是没有_proto_属性的,等于null。
优点
把相同或类似的方法写在原型上,方便实例化对象复用

面向对象三大特点

  • 继承
  • 封装
  • 多态

ES6继承

通过class,extends,super实现

//创建一个父类
class Parent {
   constructor(name) {
        this.name=name ||'jack'
        this.colors=['red','green','blue']
   }
  getName() {
    return this.name;
}
}
//创建一个子类
 class Child extends Parent {
      constructor(name) {
          super(name)  //super就是父类Parent
          //super(父类的参数),意思就是在子类中获得父类的this指针指向
      }
      getValue() {
      }
}

BFC块级格式化上下文(block formatting context)

BFC是一个独立的布局环境,可看作是一个隔离了的独立容器,容器里面的元素不会在布局上影响到外面的元素。并且在一个BFC中,块盒与行盒都会垂直的沿着其父元素的边框排列下去。

  • 在同一个BFC中,上下外边距会发生折叠利用BFC避免外边距折叠
  • BFC可以包含浮动的元素(清除浮动),触发了新的BFC区域,和其他的BFC隔离开来,给自对自己包裹的对象进行定位,也能包含浮动元素解决容器不会被撑开的问题
  • BFC可以阻止元素被浮动元素覆盖(可实现两栏自适应布局,左栏固定宽度,右栏自适应) 避免文字环绕

触发条件

  • 浮动元素(float)
  • 绝对定位元素(position为absolute、fixed)
  • display为 inline-block、table-cells、flex
  • overflow除了visible以外的值(hidden、auto、scroll)

前端性能优化

页面加载及渲染过程优化

HTTP缓存优化

不同刷新的请求执行过程

  • 直接写入URL回车,浏览器发新缓存中有这个文件不需要继续请求直接去缓存拿
  • F5:浏览器会发送一个请求带上if-modify-since去服务器询问是否过期
  • ctrl+F5:浏览器先把缓存中的这个文件删了,再去服务器请求完整的资源文件下来,客户端完成强制更新的操作
    优点
  • 减少冗余的数据传递,节省流量带宽
  • 减少服务器的负担,大大提高网站性能
  • 加快客户端加载网页的速度
CRP关键渲染路径优化

关键渲染路径就是浏览器将html、js、css转换为屏幕上呈现的像素内容所经历的一系列步骤,即浏览器渲染流程。
为尽快完成首次渲染,需要最大限度的减少以下三种可变因素:

  • 可能阻止网页首次渲染的资源
  • 获取所有关键资源的总时间或往返次数
  • 所有关键资源的文件大小总和
优化DOM结构
  • 删除不必要的代码和注释(包括空格),尽量做到最小化文件
  • 使用webpack压缩文件代码
  • 利用http缓存
优化CSSOM

在渲染的过程中,DOM和CSSOM是并行构建的,CSS的加载不会阻塞DOM元素的解析
但是最终的渲染树(Render tree)是依赖于DOM tree和CSSOM tree的,所以必须等待到CSSOM构建完成,才能开始渲染,所以CSS加载会阻塞DOM的渲染 所以我们可以从CSSOM的缩小、压缩或者缓存方面入手

  • 减少CSS元素的数量
优化JavaScript

当浏览器遇到script标签的时候

减少重绘与回流

CSS

  • 避免使用table布局
  • 尽可能的在DOM树的末端改变CSS,尽可能减少回流的影响
  • 避免设置多层内联样式,样式应该合并在一个外部类
  • 动画效果应用到position属性为absolute或fixed的元素上,不影响其他元素的布局,只会重绘而不是一个完整的回流,这样消耗更低
  • 牺牲平滑度换取速度
  • 避免使用css表达式 Javascript
  • 避免频繁操作样式,最好一次性重写style属性或者将样式列表定义为class并一次性更改class属性
  • 避免频繁操作DOM元素,可以创建一个documentFragment代码块(可以理解为一个暂时的DOM节点存储器)在它上面应用所有的DOM操作,最后再把它添加到文档中
  • 可以先将元素设置为display:none属性,操作结束后再把它显示出来,在none属性的元素上进行DOM操作不会引发回流和重绘 图片懒加载
    可以让一些不可视的图片不去加载,避免一次性加载过多的图片造成阻塞,提高网站的加载速度
    事件委托
    利用JS事件冒泡机制把原本需要绑定在子元素的响应事件委托给父元素进行监听,让父元素担当事件监听的职务
  • 大量减少DOM元素的事件注册,从而减少内存占用
  • 新增元素情况下,自动动态绑定事件
其他方面优化

1.对css、js源码进行源码压缩
2.少用全局变量,避免全局搜索
3.多使用原生方法
4.第三方资源按需加载
5.少用内联样式,尽量使用className
6.合理控制图片大小

渲染完成以后的页面交互优化

防抖
节流

vue性能优化

从输入url到获得页面经历的所有事情

1、浏览器输入url的时候,浏览器开始智能匹配,从书签、浏览历史记录、缓存等地方给出智能提示

2、如果输入的url是域名而不是ip地址,则开始进行DNS解析(域名解析,域名到IP地址的转换过程)。
首先从本机器的hosts主机文件查找IP地址;
浏览器使用解析程序,主机请求本地DNS服务器(递归查询);
本地DNS服务器向其他根服务器进行请求(迭代查询);

3、得到IP地址以后确认端口(默认是80),然后会向目标服务器发起Http请求,通过TCP连接来发送的(https请求则需要先建立SSL连接,再是TCP连接)
4、浏览器生成http请求报文,发起TCP连接,开始TCP三次握手

TCP三次握手

A(客户端) B(服务器)就像两个人打电话一样
1、A主动向B发出请求,然后A开始等待B的响应,此时A不知道B能不能接收到;
2、B接收到A发出的请求以后,可以确认它能接收到A,但是它还要确定A能不能接收到它发出的请求,所以B要再次发出请求,等待A的响应;
3、A接收到B的请求以后,此时可以确认B可以接收到A的请求,A也能接受到B的请求,A已经可以随时发出请求和响应了,但是B还在等待中,并不知道A到底有没有响应,所以A还需要发出请求回复B,B接收到响应之后就可以随时保持通信状态了;

5、TCP连接建立以后,开始进行数据传输,http请求报文段会从传输层传到网络层,在网络层被封装成IP数据包,网络层规定了通过怎样的路径(所谓的传输路线)到达目标服务器,并把数据包传送给对方。
6、服务器返回一个http响应报文,沿着原来的路径返回浏览器,浏览器接收http响应,释放TCP连接(TCP四次挥手),客户端和服务端都可以主动关闭请求。

TCP四次挥手

1、A主动关闭请求,向B发出申请,等待B的响应回复;
2、B收到A的消息以后,进入等待状态(当服务器B收到断开连接的请求时,服务器可能仍然有数据未发送完毕,所以服务器先发送确认信号,等所有数据发送完毕后再同意断开),A收到B的回复以后,继续等待B的回复;
3、B的数据传送完以后,发送请求等待A的回复确认是否真的可以下线;
4、A收到请求后,便响应请求,此时B收到后就直接下线,A过一会时间(2MSL)以后也下线;

对于A来说他要发送请求给B并等待B确认,对于B来说也要发送请求给A并等待A确认,两者都经过这两个过程才能完全释放TCP连接,而非单方面的释放。

建立连接只需要建立,没有数据的影响,而释放连接还要考虑数据是否传输完,所以建立连接的时候B确认收到A的建立请求与B发送建立请求这一步可以合成一步成为TCP建立连接的第二次握手,而释放连接时却必须分开。

7、浏览器之后会检查HTTP的响应状态,通过响应码来判断:

  • 1xx:表示通知信息,比如请求收到了或正在处理;100表示信息状态码,为继续,表示确认

  • 2xx:表示成功,操作被成功接收并处理; 如200
    201:请求成功并且服务器创建了新的资源 202:服务器已接受请求,但尚未处理

  • 3xx: 表示重定向,一般完成请求还必须采取进一步的行动;
    301:永久重定向
    302:临时重定向(一次性)
    303:临时重定向(get)
    304:访问资源未改变,即浏览器使用自身缓存

  • 4xx: 表示客户端的差错;
    401:请求未授权
    403:禁止访问,客户端没有权限
    404:请求地址有错误,服务端找不到
    406:无法使用请求的内容特性来响应请求的网页,即后台的返回结果前台无法解析

  • 5xx: 表示服务器的差错;
    500:服务器内部错误
    502:服务器忙,暂时无法处理请求,可能是过载或维护

8、浏览器根据http报头信息解析并处理返回的响应,如果响应可缓存,则存入缓存;

9、浏览器自上而下,从左往右开始加载html文档,最顶部会遇到声明,来声明浏览器使用什么规范来解析该文档;

10、浏览器一边加载一边解析,生成DOM树,加载过程中如果遇到外部CSS文件,浏览器便会发出请求来获取CSS文件,获取CSS后便会生成CSS Rule树,DOM树和CSS Rule树合并生成Render树(渲染树),页面便可以边加载边渲染了;
渲染(Render)=布局(layout)+绘制(paint)
布局:指给出每个DOM节点在浏览器中的准确位置
绘制:遍历Render树将布局好的节点DOM节点绘制在屏幕上 11、浏览器如果遇到script标签,会立即执行(暂不考虑defer及async属性),此时页面会立刻阻塞,不仅要等待文档中JS文件下载完毕,还要等待JS解析完成才会恢复HTML文档的加载解析。
这是为了防止出现JS修改DOM树,需要重新构建DOM树的情况,浏览器希望通过阻塞其他内容的下载来避免出现更多不必要的Reflow或Repaint。
如果script标签放在head中,则body标签无法被加载,这将导致在该js代码完全执行完之前,页面都是一片长时间的空白,推荐将所有script标签尽可能的放到body标签的底部以减少对整个页面下载的影响。
defer:浏览器立即下载,等DOM树生成完以后延迟执行(异步)
async:浏览器立即下载完成后立即执行(同步)

12、浏览器继续加载渲染,如果遇到图片资源,浏览器会另外发出请求来获取图片资源(异步请求),继续渲染后面的HTML文档。

13、等待服务器返回资源、图片,如果之前没有为这个图片设定宽高,由于图片占据了一定的面积,影响了后面段落的排布,浏览器便会进行重排。

14、当浏览器遇到html标签以后,此时页面加载渲染过程完成,立即触发DOMContentLoaded事件,这个事件是在形成完整的DOM树之后就会触发,而不会理会图像、资源、JS文件、CSS文件是否下载完毕。
当页面资源完成加载以后,则会触发load事件。

15、页面上进行交互的时候可能会导致页面的重绘或回流
Repaint(重绘):如果只是改变某个元素的背景颜色、文字颜色不影响元素周围或者内部布局的属性,将只会引起浏览器的重绘,重绘某一部分。
Reflow(回流):如果某个部分发生了变化而影响了布局,那么浏览器就需要倒回去重新渲染,每次回流必然会导致重绘。

es6新特性

let、const和块级作用域

symbol

map()、set()、reduce()

map():接收一个函数,将原数组中的所有元素用这个函数处理后放入新的数组返回。本质是与Object类似的结构。不同在于Object强制规定key值只能是字符串。而Map对象的key可以是任意对象:
reduce() :方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。
set(): 本质与数组类似,不同于Set中只能保存不同的元素,如果元素相同会被忽略。

解构赋值

ES6中允许安装一定模式从数组对象中提取值,然后对变量进行赋值,这样被称之为解构
数组解构
对象解构

箭头函数

对象的函数属性简写

默认参数

ES6中可以直接把默认值放在函数申明里面

模板字符串

可以使用新的语法$ {NAME},并把它放在反引号里

新的字符串API

includes()
startsWith()
endsWith()

promise

所谓Promise,简单来说就是容器,里面保存着某个未来才会结束的事件(通常为异步操作)的结果。从语法上说,Promise是一个对象,它可以获取异步操作的消息。Promise提供统一的API,各种异步操作都可以用这样的方式进行处理。

可以通过Promise的构造函数来创建Promise对象,并在内部封装一个异步执行的结果。

const promise = new Promise(function (resolve, reject) {
    // 执行异步操作
    if (/*异步操作成功*/){
        // 调用resolve,代表Promise将返回成功的结果
        resolve(value)
    }else {
        // 调用reject,代表Promise将返回错误的结果
        reject(error)
    }
})

执行完成 .then()
执行失败 .catch()

运算符扩展

扩展运算符(spread)是三个点(…),将一个数组转为用逗号分隔的参数序列。

class

通过class关键字定义类

DOM事件流

事件流描述的是从页面中接受事件的顺序

事件冒泡

即事件开始的时候,由最具体的元素接收(即事件发生所在的节点),然后逐级传播到较为不具体的节点。
阻止事件冒泡:canceBubble=true Jquery的e.stopPropagation会阻止冒泡

事件捕获

与事件冒泡相反,它认为在某个事件发生的时候,父元素应该更早的接收到事件,具体元素则是最后接收到事件。
默认addEventListener的第三个参数控制事件触发顺序,默认false即事件冒泡
true,事件捕获
false,事件冒泡

DOM事件流三个阶段

1、事件捕获阶段

当事件发生的时候,首先发生的是事件捕获,为父元素截断事件提供了机会

2、处于目标阶段

事件到了具体元素的时候,在具体元素上面发生,并且被看成冒泡阶段的一部分

3、事件冒泡阶段

事件冒泡阶段可以被阻止,防止事件冒泡带来不必要的错误和困扰。事件到达具体元素以后停止了冒泡,但不影响父元素的事件捕获。

JS为什么是单线程的? 为什么需要异步? 单线程又是如何实现异步的呢?

单线程

顾名思义即同一时间只能做一件事。js作为浏览器脚本语言,跟它的使用场景有关系,主要用途是与用户互动和操作DOM,决定了它只能单线程运行,否则会带类很复杂的同步问题,比如如果同时两个线程更改了同一个DOM节点,浏览器无法准确识别的问题。
H5中提出的web worker标准,允许js脚本创建多个线程,但是子线程仍然完全受主线程所控制,并且不能操作dom元素,所有新标准并没有改变js单线程的本质。

异步执行

js是自上而下执行的脚本语言,如果上一行代码解析时间很长,那么下面的代码将会阻塞,意味着卡死了导致很差的用户体验,所以js存在异步执行。

任务队列

所有的任务分成两种
同步任务:指在主线程上排队执行的任务
异步任务:不进入主线程,而进入任务队列的任务,只有任务队列通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行

异步执行的运行机制

1、所有同步任务都在主线程上执行,形成一个执行栈;
2、主线程之外还存在一个任务队列,只要异步任务有了运行结果,就在任务队列中放置一个事件(表示相关的异步任务可以进入执行栈了);
3、一旦执行栈中的所有同步任务都执行完毕,系统就会读取任务队列中的事件,里面所对应的异步任务就会结束等待状态进入执行栈开始执行;
4、主线程不断的重复步骤三
5、只要主线程空了,js就会读取任务队列,这个过程会不断重复

事件和回调函数

任务队列中的事件包含IO设备的事件、AJAX的网络请求等用户产生的事件。
回调函数就是那些被主线程挂起来的代码,异步任务必须指定回调函数,当主线程开始执行异步任务的时候,就是执行对应的回调函数。
任务队列是一个先进先出的数据结构,排在前面的事件优先被主线程所读取,只要执行栈清空,排在任务队列上的第一位事件就会自动进去主线程。
如果存在定时器,则主线程首先检查执行时间,只有到达了规定的时间,才能返回主线程。

js事件循环Event Loop

主线程从任务队列中读取事件这个过程的运行机制又称为事件循环。
遇到同步任务直接执行,遇到异步任务进行归类
异步任务包括:
宏任务(macrotask)
微任务(microtask)
1、同步和异步任务分别进入不同的执行场所,同步的进入主线程,异步的进入Event Table并注册回调函数;
2、当指定的事情完成以后,Event Table会将这个回调函数移入Event Queue 3、主线程内的任务执行完毕为空,就会去Event Queue读取对应的函数,进入主线程中执行
4、上述过程会不断的重复,也就是常说的事件循环Event Loop

宏任务与微任务之间的关系

new Promise在实例化的过程中所执行的代码都是同步进行的,而then中注册的回调才是异步执行的
在同步代码执行完成后才回去检查是否有异步任务完成,并执行对应的回调,而微任务又会在宏任务之前执行。 setTimeout就是作为宏任务来存在的,而Promise.then则是具有代表性的微任务

总结:有微则微,无微则宏
js是一门单线程语言,不管是什么新框架新语法糖实现的所谓的异步,其实都是用同步的方法去模拟的
Event Loop是js的执行机制,js在不同的环境下,执行方式是不同的比如node、浏览器等。而运行大多指的是js解析引擎,是统一的。

Promise原理以及手写代码

js函数防抖和节流

函数防抖

在第一次触发事件时,不立即执行函数,而是给出一个期限值比如200ms。
如果在200ms内没有再次触发滚动事件,那么就执行函数。
如果在200ms内再次触发滚动事件,那么当前的计时取消,重新开始计时。 效果:如果短时间内大量触发同一事件,只会执行一次函数。

function debounce(fn,delay){
    let timer = null //借助闭包
    return function() {
        if(timer){
                        clearTimeout(timer) //进入该分支语句,说明当前正在一个计时过程中,并且又触发了相同事件。所以要取消当前的计时,重新开始计时 
        }
        timer = setTimeout(fn,delay) // 进入该分支说明当前并没有在计时,那么就开始一个计时
    }
}

函数节流

让函数在某个时间间隔之后给出反馈,即如果短时间内大量触发同一事件,那么在函数执行一次以后,该函数在指定的时间期限内不再工作,直到过了这段时间以后才重新生效
我们可以设计一种类似控制阀门一样定期开放的函数,也就是让函数执行一次以后,在某个时间段内暂时失效,过了这段时间后再重新激活。

function throttle(fn,delay){
    let valid = true
    return function() {
       if(!valid){
           //休息时间 暂不接客
           return false 
       }
       // 工作时间,执行函数并且在间隔期内把状态位设为无效
        valid = false
        setTimeout(() => {
            fn()
            valid = true;
        }, delay)
    }
}

应用场景

1、搜索框的input事件、实时搜索反馈
2、页面的resize事件或视窗的监控事件,常用于做页面适配的时候