# 其他

# 前端性能优化

服务端:

  1. 使用 CDN,CDN 可以通过 DNS 负载均衡技术将用户的请求转移到就近的 cache 服务器上,提高请求返回速度
  2. 利用静态资源缓存,给返回头加上 Cache-Control 或者 Etag 头

网络:

  1. 通过雪碧图、合并请求等方法减少HTTP请求数
  2. 减少文件大小:压缩 CSS、JS 和图片,在服务器(Nginx)中开启 Gzip:也就是先在服务端进行压缩,再在客户端进行解压

客户端:

  1. 使用外联 CSS 和 JS,CSS 放头,JS 放在文档尾部,防止页面加载阻塞以减少对并发下载的影响,尽早给用户展示出页面
  2. 懒加载(图片懒加载,上滑加载更多)
  3. 对DOM查询进行缓存,频繁DOM操作,合并到一起插入DOM结构
  4. 节流throttle 防抖 debounce
  5. JS: 使用事件委托、减少重绘回流,如设置 class 更新样式

# 一个网站有大量图片,怎么优化

  1. 图片懒加载,在页面上的未可视区域可以添加一个滚动条事件,判断图片位置与浏览器顶端的距离与页面的距离,如果前者小于后者,优先加载。
  2. 如果为幻灯片、相册等,可以使用图片预加载技术,将当前展示图片的前一张和后一张优先下载。
  3. 如果图片为css图片,可以使用CSSsprite,SVGsprite,Iconfont、Base64等技术。
  4. 如果图片过大,可以使用特殊编码的图片,加载时会先加载一张压缩的特别厉害的缩略图,以提高用户体验。
  5. 如果图片展示区域小于图片的真实大小,则因在服务器端根据业务需要先行进行图片压缩,图片压缩后大小与展示一致。

# 白屏时间与首屏时间

白屏时间指的是浏览器开始显示内容的时间。因此我们只需要知道是浏览器开始显示内容的时间点,即页面白屏结束时间点即可获取到页面的白屏时间。

白屏时间 = firstPaint - pageStartTime

首屏时间 = 地址栏输入网址后回车 - 浏览器第一屏渲染完成

img

img

# 什么是进程 线程

区别:

  • 进程是资源分配的最小单位,线程是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. 探测连接的对端是否存活

在应用交互的过程中,可能存在以下几种情况:

(1)客户端或服务器意外断电,死机,崩溃,重启。

(2)中间网络已经中断,而客户端与服务器并不知道。

​ 利用保活探测功能,可以探知这种对端的意外情况,从而保证在意外发生时,可以释放半打开的TCP连接。

  1. 防止中间设备因超时删除连接相关的连接表

中间设备如防火墙等,会为经过它的数据报文建立相关的连接信息表,并为其设置一个超时时间的定时器,如果超出预定时间,某连接无任何报文交互的话,

中间设备会将该连接信息从表中删除,在删除后,再有应用报文过来时,中间设备将丢弃该报文,从而导致应用出现异常。

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包、不四次握手),等待在同域名下继续用这个通道传输数据;相反的就是短连接。

img

在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 出来的对象 属性/功能/方法 完全一样 的时候,我们把他设计成单例模式

# 组合模式

举一个简单的例子,就像家里每个电器都有单独的开关,而组合模式就是设置一个总开关,这个开关可以控制家中所有电器的开关,这就是组合模式

# 观察者模式

  • 观察者观察着被观察者只要被观察者数据发生变化,观察者就要做一些事情
  • 举个生动的例子,学生就是被观察者,老师就是观察者,只要学生上课状态不好,老师就会请家长。

# 发布/订阅模式

分成三个状态

  1. 订阅
  2. 取消订阅
  3. 发布

要实现 订阅/取消订阅 功能需要一个 消息盒子{ }

  • 订阅就是往消息盒子里添加内容
  • 取消订阅就是删除消息盒子里的内容
  • 发布就是执行消息盒子里的内容

# 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
  • 图片响应式