# 其他
# 前端性能优化
服务端:
- 使用 CDN,CDN 可以通过 DNS 负载均衡技术将用户的请求转移到就近的 cache 服务器上,提高请求返回速度
- 利用静态资源缓存,给返回头加上 Cache-Control 或者 Etag 头
网络:
- 通过雪碧图、合并请求等方法减少HTTP请求数
- 减少文件大小:压缩 CSS、JS 和图片,在服务器(Nginx)中开启 Gzip:也就是先在服务端进行压缩,再在客户端进行解压
客户端:
- 使用外联 CSS 和 JS,CSS 放头,JS 放在文档尾部,防止页面加载阻塞以减少对并发下载的影响,尽早给用户展示出页面
- 懒加载(图片懒加载,上滑加载更多)
- 对DOM查询进行缓存,频繁DOM操作,合并到一起插入DOM结构
- 节流throttle 防抖 debounce
- JS: 使用事件委托、减少重绘回流,如设置 class 更新样式
# 一个网站有大量图片,怎么优化
- 图片懒加载,在页面上的未可视区域可以添加一个滚动条事件,判断图片位置与浏览器顶端的距离与页面的距离,如果前者小于后者,优先加载。
- 如果为幻灯片、相册等,可以使用图片预加载技术,将当前展示图片的前一张和后一张优先下载。
- 如果图片为css图片,可以使用CSSsprite,SVGsprite,Iconfont、Base64等技术。
- 如果图片过大,可以使用特殊编码的图片,加载时会先加载一张压缩的特别厉害的缩略图,以提高用户体验。
- 如果图片展示区域小于图片的真实大小,则因在服务器端根据业务需要先行进行图片压缩,图片压缩后大小与展示一致。
# 白屏时间与首屏时间
白屏时间指的是浏览器开始显示内容的时间。因此我们只需要知道是浏览器开始显示内容的时间点,即页面白屏结束时间点即可获取到页面的白屏时间。
白屏时间 = firstPaint - pageStartTime
首屏时间 = 地址栏输入网址后回车 - 浏览器第一屏渲染完成
# 什么是进程 线程
区别:
- 进程是资源分配的最小单位,线程是CPU调度的最小单位
- 一个进程可以包含若干个线程
- 进程拥有自己的资源空间,每启动一个进程,系统就会为它分配地址空间;而线程与CPU资源分配无关,多个线程共享同一进程内的资源,使用相同的地址空间
- 线程间通信方便,同一进程下的线程共享全局变量、静态变量等数据,切换快,开销小
- 一个线程挂了整个进程挂掉,进程之间不会相互影响
知乎的答案,很舒服
看了一遍排在前面的答案,类似”**进程是资源分配的最小单位,线程是CPU调度的最小单位“**这样的回答感觉太抽象,都不太容易让人理解。
做个简单的比喻:进程=火车,线程=车厢
- 线程在进程下行进(单纯的车厢无法运行)
- 一个进程可以包含多个线程(一辆火车可以有多个车厢)
- 不同进程间数据很难共享(一辆火车上的乘客很难换到另外一辆火车,比如站点换乘)
- 同一进程下不同线程间数据很易共享(A车厢换到B车厢很容易)
- 进程要比线程消耗更多的计算机资源(采用多列火车相比多个车厢更耗资源)
- 进程间不会相互影响,一个线程挂掉将导致整个进程挂掉(一列火车不会影响到另外一列火车,但是如果一列火车上中间的一节车厢着火了,将影响到所有车厢)
- 进程可以拓展到多机,进程最多适合多核(不同火车可以开在多个轨道上,同一火车的车厢不能在行进的不同的轨道上)
- 进程使用的内存地址可以上锁,即一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。(比如火车上的洗手间)-"互斥锁"
- 进程使用的内存地址可以限定使用量(比如火车上的餐厅,最多只允许多少人进入,如果满了需要在门口等,等有人出来了才能进去)-“信号量”
# 纯函数与非纯函数
纯函数
1、不改变源数组(没有副作用);2、返回一个数组
concat map filter slice
非纯函数
push pop shift unshift
foreach some every splice(剪接) reduce
# 函数式编程
相当于把原本一个大函数拆解成一个个独立的小函数,然后通过链式调用,把独立的小函数串联起来,已达到输出的结果与原本的大函数一致,有点类似于管道,或者说流水线,上一个工序处理结束,传给下一个工序,最后输出成品。
函数式编程,其函数必须满足一个基本条件:函数必须是没有副作用的,不能直接或间接修改除自身外的变量,仅仅是一种数据转换的行为。片草丛中过,既不沾花也不惹草。
函数式编程有两个最基本的运算:合成和柯里化。
https://juejin.im/post/6844903831906729997
# 阻止事件冒泡和默认行为
event.stopPropagation()
event.preventDefault()
# 什么是JSON⭐
- JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。
- 它是基于JavaScript的一个子集。
- 数据格式简单, 易于读写, 占用带宽小。
{"age":"12", "name":"back"}
json转化为字符串 JSON.stringify()
字符串转化为json JSON.parse()
# 理解TCP长连接(Keepalive)
长连接的环境下,进行一次数据交互后,很长一段时间内无数据交互时,客户端可能意外断电、死机、崩溃、重启,还是中间路由网络无故断开,这些TCP连接并未来得及正常释放,那么,连接的另一方并不知道对端的情况,它会一直维护这个连接,长时间的积累会导致非常多的半打开连接,造成端系统资源的消耗和浪费,且有可能导致在一个无效的数据链路层面发送业务数据,结果就是发送失败。所以服务器端要做到快速感知失败,减少无效链接操作,这就有了TCP的Keepalive(保活探测)机制。
TCP Keepalive作用
- 探测连接的对端是否存活
在应用交互的过程中,可能存在以下几种情况:
(1)客户端或服务器意外断电,死机,崩溃,重启。
(2)中间网络已经中断,而客户端与服务器并不知道。
利用保活探测功能,可以探知这种对端的意外情况,从而保证在意外发生时,可以释放半打开的TCP连接。
- 防止中间设备因超时删除连接相关的连接表
中间设备如防火墙等,会为经过它的数据报文建立相关的连接信息表,并为其设置一个超时时间的定时器,如果超出预定时间,某连接无任何报文交互的话,
中间设备会将该连接信息从表中删除,在删除后,再有应用报文过来时,中间设备将丢弃该报文,从而导致应用出现异常。
TCP Keepalive HTTP Keep-Alive 的关系
在HTTP/1.0中,默认使用的是短连接。也就是说,浏览器和服务器每进行一次HTTP操作,就建立一次连接,但任务结束就中断连接。如果客户端浏览器访问的某个HTML或其他类型的 Web页中包含有其他的Web资源,如JavaScript文件、图像文件、CSS文件等;当浏览器每遇到这样一个Web资源,就会建立一个HTTP会话。
但从 HTTP/1.1起,**默认使用长连接,用以保持连接特性。**使用长连接的HTTP协议,会在响应头加上Connection、Keep-Alive字段
HTTP协议的Keep-Alive意图在于TCP连接复用,同一个连接上串行方式传递请求-响应数据;
TCP的Keepalive机制意图在于探测连接的对端是否存活(探测保活)。
HTTP1.1 keep-alive重点:连接复用
HTTP1.1规定了默认保持长连接,数据传输完成了保持TCP连接不断开(不发RST包、不四次握手),等待在同域名下继续用这个通道传输数据;相反的就是短连接。
在timeout空闲时间内,连接不会关闭,相同重复的request将复用原先的connection,减少握手的次数,大幅提高效率。
并非keep-alive的timeout设置时间越长,就越能提升性能。长久不关闭会造成过多的僵尸连接和泄露连接出现。
# 什么是柯里化 (curry)?
柯里化,即 currying,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
以下为简单实现:
// 普通的add函数
function add(x, y) {
return x + y
}
// Currying后
function curryingAdd(x) {
return function (y) {
return x + y
}
}
add(1, 2) // 3
curryingAdd(1)(2) // 3
# 柯里化的一道经典面试题
// 实现一个add方法,使计算结果能够满足如下预期:
add(1)(2)(3) = 6;
add(1, 2, 3)(4) = 10;
add(1)(2)(3)(4)(5) = 15;
function add() {
// 第一次执行时,定义一个数组专门用来存储所有的参数
var _args = Array.prototype.slice.call(arguments);
// 在内部声明一个函数,利用闭包的特性保存_args并收集所有的参数值
var _adder = function() {
_args.push(...arguments);
return _adder;
};
// 利用toString隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
_adder.toString = function () {
return _args.reduce(function (a, b) {
return a + b;
});
}
return _adder;
}
add(1)(2)(3) // 6
add(1, 2, 3)(4) // 10
add(1)(2)(3)(4)(5) // 15
add(2, 6)(1) // 9
# 什么是单例模式
**单例模式:**保证一个类仅有一个实例,并提供一个访问它的全局访问点。
经典的实现方式是,创建一个类,这个类包含一个方法,这个方法在没有对象存在的情况下,将会创建一个新的实例对象。如果对象存在,这个方法只是返回这个对象的引用。
实现代码:
var SingleTon = function(name){ //创建一个对象
this.name = name;
this.instance = null; //通过这个变量来标志是否创建过对象
};
SingleTon.prototype.getName = function(){
alert(this.name);
};
SingleTon.getInstance = function(name){
if(!this.instance){
this.instance = new SingleTon(name); //在没有对象存在的情况下,将会创建一个新的实例对象
}
return this.instance;
};
var a = SingleTon.getInstance( 'instance1' );
var b = SinleTon.getInstance( 'instance2' );
alert( a === b); //返回true
CommonJs用在服务器端,AMD和CMD用在浏览器环境 AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。 CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。 AMD:提前执行(异步加载:依赖先执行)+延迟执行 CMD:延迟执行(运行到需加载,根据顺序执行)
# 消息推送的几种实现方式
1、轮询
客户端定时向服务器发送Ajax请求,服务器接到请求后马上返回响应信息,并关闭连接。
优点:后端程序编写比较容易 缺点:请求中大半是无用的,浪费带宽和服务器资源 实例:适用于小型应用
2.长轮询:
客户端向服务器发送Ajax请求,服务器接到请求后Hold住连接,直到有新消息才返回响应信息,并关闭连接;客户端处理完响应信息后再向服务器发送新的请求。
优点:在无消息的情况下不会频繁的请求,耗费的资源少 缺点:服务器Hold住连接会消耗资源,返回数据顺序无法保证,难于管理和维护 实例:扫码登录,微信网页端获取消息等。
长连接:
客户端和服务端建立长链接,基于http1.1 ,keepalive ,websocket,comet,iframe等,基于socket的需要维持心跳
优点:通信及时,通信模式采用双工,类似于打电话 缺点:服务端和客户端需要同时改造,当链接过多时,消耗服务端资源比较大。
使用场景:实时性要求很高,银行系统,股票系统等
# 面向对象的三大特性
封装、继承、多态
# 一、封装:
封装:将内容封装到某个地方,之后调用的时候直接调用被封装在某处的内容
# 二、继承:
继承,面向对象中的继承和现实生活中的继承相同,即:子可以继承父的内容。
# 三:多态
多态的具体表现为方法重载和方法重写:
方法重载:重载是指不同的函数使用相同的函数名,但是函数的参数个数或类型不同。调用的时候根据函数的参数来区别不同的函数
方法重写:重写(也叫覆盖)是指在派生类中重新对基类中的虚函数(注意是虚函数)重新实现。即函数名和参数都一样,只是函数的实现体不一样
# 栈和队列的区别:
栈的插入和删除操作都是在一端进行的,而队列的操作却是在两端进行的。
栈是先进后出,队列是先进先出。
栈只允许在表尾一端进行插入和删除,队列只允许在表尾一端进行插入,在表头一端进行删除。
# 栈和堆的区别:
栈区:由编辑器自动分配释放,存放函数的参数值,局部变量的值等(基本类型值)。
堆区:由程序员分配释放,若程序员不释放,程序结束时可能有OS回收(引用类型值)。
栈(数据结构):一种先进后出的数据结构。
堆(数据结构):堆可以被看成是一棵树,如:堆排序。
# 数组和链表的区别
数组静态分配内存,链表动态分配内存;
数组在内存中连续,链表不连续;
数组元素在栈区,链表元素在堆区;
数组利用下标定位,时间复杂度为O(1),链表定位元素时间复杂度O(n);
数组插入或删除元素的时间复杂度O(n),链表的时间复杂度O(1)。
# JS常见的几种设计模式
# 单例模式
使用构造函数实例化的时候,不管实例化多少回,都实例化出同一个对象
- 一个构造函数一生只能 new 出一个对象
- 当我们使用构造函数,每一次 new 出来的对象 属性/功能/方法 完全一样 的时候,我们把他设计成单例模式
# 组合模式
举一个简单的例子,就像家里每个电器都有单独的开关,而组合模式就是设置一个总开关
,这个开关可以控制家中所有电器的开关,这就是组合模式
。
# 观察者模式
观察者
观察着被观察者
只要被观察者数据发生变化,观察者就要做一些事情- 举个生动的例子,学生就是被观察者,老师就是观察者,只要学生上课状态不好,老师就会请家长。
# 发布/订阅模式
分成三个状态
- 订阅
- 取消订阅
- 发布
要实现
订阅/取消订阅
功能需要一个消息盒子{ }
- 订阅就是往消息盒子里添加内容
- 取消订阅就是删除消息盒子里的内容
- 发布就是执行消息盒子里的内容
# For in
数组中也有for……in,相较于对象中的用法是有区别的:
数组中
var arr = ['曹操','曹植','曹丕']
for(i in arr){
console.log(i) //0 1 2
console.log(arr[i]) //曹操 曹植 曹丕
}
对象中
var obj = new Object();
obj = {
father:'曹操',
son:'曹植'
}
for(i in obj){
console.log(i) ; //代表key值 father son
console.log(obj[i]) ; //代表vulue值 曹操 曹植
}
# Object.key() 和 for in 区别
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.hobbies = ['eat'];
let p = new Person;
// 不遍历原型上的属性
console.log(Object.keys(p));// [ 'name', 'age' ]
// 可遍历原型链上的可枚举属性
for (let item in p) {
console.log(item);// name age hobbies
}
# 内存泄露
定义:应用程序不再需要占用内存的时候,由于某些原因,内存没有被操作系统或可用内存池回收。
哪些操作会造成内存泄露
1)意外的全局变量引起的内存泄露
function leak(){
leak="xxx";//leak成为一个全局变量,不会被回收
}
2)闭包引起的内存泄露
function bindEvent(){
var obj=document.createElement("XXX");
obj.οnclick=function(){
//Even if it's a empty function
}
}
3)没有清理的DOM元素引用
var elements={
button: document.getElementById("button"),
image: document.getElementById("image"),
text: document.getElementById("text")
};
function doStuff(){
image.src="http://some.url/image";
button.click():
console.log(text.innerHTML)
}
function removeButton(){
document.body.removeChild(document.getElementById('button'))
}
4)被遗忘的定时器或者回调
var someResouce=getData();
setInterval(function(){
var node=document.getElementById('Node');
if(node){
node.innerHTML=JSON.stringify(someResouce)
}
},1000)
如何监控:
开发工具 performance 那块,看内存监控图表,如果发生内存泄露了
# 各种图片格式区别
JPEG 有损压缩
PNG PNG能够提供长度比GIF小30%的无损压缩图像文件
SVG 一种开放标准的矢量图形语言,可任意放大图形显示,边缘异常清晰,文字在SVG图像中保留可编辑和可搜寻的状态,没有字体的限制,生成的文件很小,下载很快,十分适合用于设计高分辨率的Web图形页面。
# 溢出省略
单行省略
// 单行显示省略号
p {
overflow: hidden;
text-overflow:ellipsis;
white-space: nowrap;
}
多行省略
// 多行显示省略号,数字3为超出3行显示,
p {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
}
# 进程之间常见的通信方式
管道pipe:管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
命名管道FIFO:有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
消息队列:消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
共享存储:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。
信号量:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
信号:信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
套接字:套接口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同设备及其间的进程通信。
# canvas和svg的区别
canvas
- 可以看做画布,绘制的是标量图,多用于图像密集型游戏
- 不支持事件处理器,Canvas 绘制的图像 都在Canvas这个画布里面,是Canvas的一部分,不能用js获取已经绘制好的图形元素。
svg
- 绘制的图形是矢量图,可以无限放大不失真(比如地图),适合静态图片展示,高保真文档查看和打印的应用场景
- 支持事件处理器
# 多条数据前端如何渲染
从数据上处理:分页分表,比如前端可以把数据分页展示,后端也分段吐数据
从渲染上解决:
异步渲染,比如进入页面先不渲染,然后加载好页面再渲染。
局部渲染:只渲染目前可见区域的数据,再渲染次屏数据。
还有性能瓶颈,可以考虑web worker 做压缩和解码,也可以考虑离屏canvas做预渲染。
减少网络耗时:压缩数据,gzip等
分批加载,懒加载,监听用户的滑动分批显示数据
通过worker来做子线程实现
通俗点讲就是:因为js是单线程运行的,在遇到一些需要处理大量数据的js时,可能会阻塞页面的加载,造成页面的假死。这时我们可以使用worker来开辟一个独立于主线程的子线程来进行哪些大量运算。这样就不会造成页面卡死。也说明 worker可以用来解决大量运算是造成页面卡死的问题。
# jQuery设计思想原理
jQuery实质上是一个构造函数,该构造函数接受一个参数,jQuery通过这个参数利用原生API找到节点,之后返回一个方法对象,该方法对象上的方法对节点进行操作(方法使用了闭包)。
# git的常见操作
git clone
git add .
git commit -m ''
git push
git pull
git revert HEAD 撤销提交
git checkout feature 切换到分支
git reset head 文件名称
git branch 查看本地分支
git merge 分支之间的合并
# git merge 与 git rebase 的区别
1)这两个命令都能达到两分支合并的效果;2)git rebase 最终的效果比git merge 的要漂亮。
git merge 是通过暴力地将两分支的最新commit 揉合到一个新的commit 上达到合并效果的。而git rebase 则是通过一种续接的方式:将分支b1拆下,续到master上来。就相当于本来master 与b1都在同一起点,现在改为master的起点为b1。
富途的博客写的挺好:
https://juejin.im/post/6844903603694469134
# 渐进增强+优雅降级
渐进增强:
一开始就针对低版本浏览器进行构建页面,完成基本的功能,然后再针对高级浏览器进行效果、交互、追加功能达到更好的体验。
优雅降级:
一开始就构建站点的完整功能,然后针对浏览器测试和修复。比如一开始使用 CSS3 的特性构建了一个应用,然后逐步针对各大浏览器进行 hack 使其可以在低版本浏览器上正常浏览。
# 如何保持登录状态
cookie+session实现登录状态保持
1、服务端登录的时候,给分配一个session用于存储数据,同时将sessionID返回给浏览器
2、浏览器通过cookie把sessionID存储起来,下次访问时携带上
3、服务端就可以通过sessionID来确定用户是否登录
缺点:
浪费大量内存
手机端很多不支持cookie或者禁用cookie
token技术
当小明登录系统的时候,根据小明的用户名生成一个token(令牌),把令牌交给小明,服务端不做任何存储。下次小明登录时,小明带上token进行访问,服务端对token进行校验,当然这个令牌必须是通过一定的算法进行严格加密的,只有服务器才能解析校验
# promise的错误捕获
ES6 中 Promise 对象的实例提供了 catch() 方法,表示异步捕获异常。
Promise.prototype.catch 方法是用于指定发生错误时的回调函数,上面代码中,fun() 方法返回一个 Promise 对象,如果该对象状态由 pending 变为 resolved,则会调用then方法指定的回调函数;如果异步操作抛出错误,状态就会变为 rejected,就会调用 catch 方法指定的回调函数,处理这个错误。另外,then 方法指定的回调函数,如果运行中抛出错误,也会被catch方法捕获。
https://www.jianshu.com/p/b7808e6299f2
超时捕获
使用promoise.race([p1, p2])设置promise超时
https://www.jianshu.com/p/3327682f0eca
# 动态类型和静态类型的区别
动态类型:在运行时代码可以根据某些条件改变自身结构,运行期间才去做数据类型检查的语言,说的是数据类型
静态类型:数据类型是在编译期间(或运行之前)确定的,编写代码的时候要明确确定变量的数据类型。
# 解决300ms延迟问题(点击穿透)
禁用缩放
<meta name="viewport" content="user-scalable=no">
<meta name="viewport" content="initial-scale=1,maximum-scale=1">
更改默认的视口宽度
<meta name="viewport" content="width=device-width">
FastClick.js
FastClick 是 FT Labs 专门为解决移动端浏览器 300 毫秒点击延迟问题所开发的一个轻量级的库。FastClick的实现原理是在检测到touchend事件的时候,会通过DOM自定义事件立即出发模拟一个click事件,并把浏览器在300ms之后的click事件阻止掉。
CSS touch-action
html {
touch-action: none
}
这是个CSS属性,如果值为 none 的话, 就表示禁用掉该元素上的浏览器代理的任何默认行为(缩放,移动, 拖拽),无300ms延迟。你也可以在html标签上面设置该属性,虽然该方法实现了禁用缩放。但是其他的触摸类型交互事件都被禁用了。导致页面无法滚动。
指针事件的polyfill
# 响应式方案
- 媒体查询,移动端优先
- 百分比布局
- rem布局
- 视口单位 vh vw
- 图片响应式
← 算法母题