CSS相关
Flexbox
主要css属性:
- 对于容器:
.container {
display: flex;
flex-direction: row | row-reverse | column | column-reverse;
flex-wrap: wrap | nowrap | wrap-reverse; // 换行
justify-content: flex-start | flex-end | center | space-between | space-around | space-evenly; // 主轴上的对齐方式
align-items: flex-start | flex-end | center | stretch | baseline; // 交叉轴上的对齐方式
align-content: flex-start | flex-end | center | space-between | space-around | stretch; // 交叉轴上如果有多行元素时候的排序
}
- 对于item:
.item {
order: <integer>;
flex-grow: <integer>;
align-self: auto | flex-start | flex-end | baseline | center | stretch;
}
HTTP
GET和POST的区别
- 报文格式不同,get把参数放在Url里,post把参数放在body里;
- get请求由于把所有参数都暴露在url里,所以相对post安全性差一些;
- url有长度限制,所以会限制get传参(这条其实会根据浏览器的实现不同而不同);
- get可以被缓存可以被收藏为书签,post不能;
- get会被保存在浏览器历史里,post不会;
- 回退或者前进的时候get是无害的,post会重新提交;
IP和TCP
IP: 互联网的数据是通过数据包传递的,如果要将主机A的数据传递到主机B,就需要知道主机B的IP地址,才能正确寻址;额外的,数据包上还会添加上A的IP地址,这样B才知道怎么回复A;但是,B拿到数据包之后,并不知道要交给哪个程序进行处理,就需要UDP(用户数据包协议)和TCP协议的帮助;
TCP(传输控制协议):上层把数据包传递给传输层 --> 传输层给数据包加上UDP头交给网络层 --> 网络层把IP头附加在数据包上,交给底层 --> 数据包被传输给了B的网络层,这时候主机B把IP头拆开,把数据包交给传输层 --> 传输层把UDP头拆开,根据端口号交给相应的应用程序进行处理;
TCP的三次握手:主要是为了确认通信能力,也就是双方都有发信和收信的能力,如果不确认的话任何数据传送都是不稳定的~所以需要三次握手:
客户端A向服务端B发送报文(B已确认了A的发信能力和B的收信能力) --> 服务端收到报文后,向客户端发送报文(A已确认了双方的收发能力) --> A返回ACK并建立连接(B确认了A的收信能力和B的发信能力)。TCP连接建立后,浏览器就可以利用HTTP/HTTPS协议向服务器发送请求了。
TCP的四次挥手:由于TCP协议有半关闭的状态(就是只可以接收信息不可以发生信息),关闭可以由客户端或者服务端提出:
两者在established状态,客户端A向服务端B提出关闭请求 --> B收到之后,给A发送收到信息的响应,同时通知应用程序关闭相关资源 --> B准备好了之后,给A发送关闭通知消息 --> A回复B,断开连接。
缓存
DNS缓存
主要就是在浏览器本地把对应的 IP 和域名关联起来,这样在进行DNS解析的时候就很快。
MemoryCache
存在内存里的缓存。不会请求服务器。从优先级上来说,它是浏览器最先尝试去命中的一种缓存。从效率上来说,它是响应速度最快的一种缓存。主要用来缓存图片、字体、一般脚本等;和渲染进程“生死相依”,tab关掉就没有了;
base64解码的图片都会被扔进memoryCache; 体积不大的css/js也有可能进入memory cache;
http缓存
- 强缓存:不会请求服务器。expires和cache-control字段控制。expires依赖本地时间,所以并不是最好的策略。
cache-control: max-age=31536000
比较合适。cache-control比expires优先度更高; - 协商缓存:依赖于服务端与浏览器之间的通信。协商缓存机制下,浏览器需要向服务器去询问缓存的相关信息,进而判断是重新发起请求、下载完整的响应,还是从本地获取缓存的资源。如果服务端提示缓存资源未改动(Not Modified),资源会被重定向到浏览器缓存,
这种情况下网络请求对应的状态码是 304。
协商缓存的实现,从Last-Modified
到Etag
。Last-Modified 是一个时间戳,第一次请求的时候,服务器会返回Last-Modified
,随后我们每次请求时,会带上一个叫 If-Modified-Since
的时间戳字段, 服务器会进行比较;但是存在last-modified
不准确的现象(比如我们编辑了文件但是内容没有发生改变,服务器只认修改的时间,因此会去修改last-modified
的值,这样的话就么有正确使用协商缓存~),因此有了Etag
,Etag
是由服务器为每个资源生成的唯一的标识字符串。
Service worker cache
Service Worker 是一种独立于主线程之外的 Javascript 线程。它脱离于浏览器窗体,因此无法直接访问 DOM。这样独立的个性使得 Service Worker 的“个人行为”无法干扰页面的性能,这个“幕后工作者”可以帮我们实现离线缓存、消息推送和网络代理等功能。我们借助 Service worker 实现的离线缓存就称为 Service Worker Cache。
Service worker
Service Worker
GoogleChrome关于service worker的sample.js
service worker的由来和作用:
service worker是基于web worker发展而来的(web worker主要是因为js是单线程的,用ww可以开辟一个新的线程,是脱离在主线程之外的,我们可以将复杂耗费时间的事情交给web worker来做)。sw是在ww的基础上增加了离线缓存的能力。
sw是由事件驱动的,具有生命周期,可以拦截处理页面的所有网络请求(fetch),可以访问cache和indexDB,支持推送,并且可以让开发者自己控制管理缓存的内容以及版本,为离线弱网环境下的 web 的运行提供了可能,让 web 在体验上更加贴近 native。换句话说他可以把你应用里的所有静态动态资源根据不同策略缓存起来,在你下次打开时不再需要去服务器请求,这样一来就减少了网络耗时,使得web应用可以秒开,并且在离线环境下也变得可用。做到这一切你只需要增加一个sw文件,不会对原有的代码产生任何侵入。
// 页面注册service worker
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/sw.js');
});
}
service worker的生命周期:
最常绑定的事件:
- install 事件中, 抓取资源进行缓存
- activate 事件中, 遍历缓存, 清除过期的资源
- fetch 事件中, 拦截请求, 查询缓存或者网络, 返回请求的资源
ES6
let和const
class
函数(箭头函数、参数解构)
Promise
async / await
Map和Set
Vue
为什么vue的列表渲染要加key?
computed和watch的区别:
computed:
1.是计算值,
2.应用:就是简化tempalte里面{{}}计算和处理props或$emit的传值
3.具有缓存性,页面重新渲染值不变化,计算属性会立即返回之前的计算结果,而不必再次执行函数
watch:
1.是观察的动作,
2.应用:监听props,$emit或本组件的值执行异步操作
3.无缓存性,页面重新渲染时值不变化也会执行
v-if和v-show的区别:
v-if
是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。v-if
也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
相比之下,v-show
就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。
一般来说,v-if
有更高的切换开销,而v-show
有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用v-show
较好;如果在运行时条件很少改变,则使用v-if
较好。
vue数组和object的替换的注意事项
由于 JavaScript 的限制,Vue不能检测以下数组的变动:
- 当你利用索引直接设置一个数组项时,例如:
vm.items[indexOfItem] = newValue
- 当你修改数组的长度时,例如:
vm.items.length = newLength
可以使用:
Vue.set(vm.items, indexOfItem, newValue)
OR:
vm.items.splice(indexOfItem, 1, newValue)
还是由于 JavaScript 的限制,Vue 不能检测对象属性的添加或删除, 可以用Vue.set(object, propertyName, value)
方法向嵌套对象添加响应式属性。
React
Promise
跨域
九种跨域方式实现原理(完整版
js遵循同源策略,即同协议,同域名,同端口号,否则都算跨域。
下图帮助理解:
可以进行跨域的主要有
- iframe
- JSONP
- CORS
- window.postMessage()
- node中间件代理(二次跨域)
- webSocket协议(ws://)
- nginx反向代理
JSONP
利用<script>
标签没有跨域限制的漏洞,网络可以从其他来源动态产生的json。JSONP一定要对方的服务器支持才可以。
优点和缺点
-
优点:
- 简单兼容性好,可以用于解决主流浏览器的跨域问题
-
缺点:
- 仅支持GET,不大安全易遭受XSS攻击
const jsonp = ({ url, params, callback }) => {
return new Promise((resolve, reject) => {
let script = document.createElement('script')
window[callback] = data => {
resolve(data)
document.body.removeChild(script)
}
params = {...params, callback}
let arr = Object.keys(params).map(key => `${key}=${params[key]}`)
script.src = `${url}?${arr.join('&')}`
document.body.appendChild(script)
})
}
jsonp({
url: 'http://localhost:3000/say',
params: {wd: '520'},
callback: 'show'
}).then(data => {
// data就是jsonp返回的值
console.log(data)
})
CORS (cross-origin resource sharing)
跨域资源共享(cross-origin resource sharing)
优点和缺点:
-
优点:
- 支持POST和http所有请求;
- 安全性相对JSONP较高;
-
缺点:
- 不兼容老版本,比如IE9及以下(IE8和IE9需要使用XDomainRequest);
- 需要服务端支持(服务端开启
Access-Control-Allow-Origin
),且CORS主要是后端;
withCredentials: true
iframe
<iframe src="demo.html" height="300" width="500" name="demo" scrolling="auto" sandbox="allow-same-origin"></iframe>
- window.self: 自己; window.parent: 父级窗口; window.top: 顶级窗口;
- 在前端领域,我们可以通过
window.top
来防止我们页面被嵌套。
if(window != window.top){
window.top.location.href = myURL;
}
- iframe通过iframe.postMessage进行跨域通信;通过window.addEventListener('message', fn)来监听通信事件;
postMessage
postMessage()方法允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档、多窗口、跨域消息传递。otherWindow.postMessage(message, targetOrigin, \[transfer\]);
nginx反向代理
使用nginx反向代理实现跨域,是最简单的跨域方式。只需要修改nginx的配置即可解决跨域问题,支持所有浏览器,支持session,不需要修改任何代码,并且不会影响服务器性能。
总结
- CORS支持所有类型的HTTP请求,是跨域HTTP请求的根本解决方案
- JSONP只支持GET请求,JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。
- 不管是Node中间件代理还是nginx反向代理,主要是通过同源策略对服务器不加限制。
- 日常工作中,用得比较多的跨域方案是cors和nginx反向代理
闭包
原型链和class
浏览器相关
输入url地址之后发生了什么?
首先我们需要通过 DNS(域名解析系统)将 URL 解析为对应的 IP 地址,然后与这个 IP 地址确定的那台服务器建立起 TCP 网络连接,随后我们向服务端抛出我们的 HTTP 请求,服务端处理完我们的请求之后,把目标数据放在 HTTP 响应里返回给客户端,拿到响应数据的浏览器就可以开始走一个渲染的流程。渲染完毕,页面便呈现给了用户,并时刻等待响应用户的操作(如下图所示)。
我们将这个过程切分为如下的过程片段:
- DNS 解析
- TCP 连接
- HTTP 请求抛出
- 服务端处理请求,HTTP 响应返回
- 浏览器拿到响应数据,解析响应内容,把解析的结果展示给用户
EventLoop
- js是单线程的,会出现阻塞问题,因此有了异步队列的出现
- 主进程同步执行任务,异步操作将添加到异步队列中
- 等候主进程执行完毕后再执行异步队列中的操作
什么是宏任务和微任务?
异步队列有宏任务和微任务之分。
-
宏任务
- setTimeout
- setImmediate
- setIntarval
- requestAnimationFrame
- I/O
- UI rendering
-
微任务
- process.nextTick
- Promise.then
- Object.observe
- MutationObserver
为什么VUE的nextTick要优先使用promise?
JS 的 event loop 执行时会区分 task 和 microtask,引擎在每个 task 执行完毕,从队列中取下一个 task 来执行之前,会先执行完所有 microtask 队列中的 microtask。
setTimeout 回调会被分配到一个新的 task 中执行,而 Promise 的 resolver、MutationObserver 的回调都会被安排到一个新的 microtask 中执行,会比 setTimeout 产生的 task 先执行。
要创建一个新的 microtask,优先使用 Promise,如果浏览器不支持,再尝试 MutationObserver。
实在不行,只能用 setTimeout 创建 task 了。
为啥要用 microtask?
根据 HTML Standard,在每个 task 运行完以后,UI 都会重渲染,那么在 microtask 中就完成数据更新,当前 task 结束就可以得到最新的 UI 了。
反之如果新建一个 task 来做数据更新,那么渲染就会进行两次。
Event Loop 过程解析
基于对 micro 和 macro 的认知,我们来走一遍完整的事件循环过程。
一个完整的 Event Loop 过程,可以概括为以下阶段:
- 初始状态:调用栈空。micro 队列空,macro 队列里有且只有一个 script 脚本(整体代码)。
- 全局上下文(script 标签)被推入调用栈,同步代码执行。在执行的过程中,通过对一些接口的调用,可以产生新的 macro-task 与 micro-task,它们会分别被推入各自的任务队列里。同步代码执行完了,script 脚本会被移出 macro 队列,这个过程本质上是队列的 macro-task 的执行和出队的过程。
- 上一步我们出队的是一个 macro-task,这一步我们处理的是 micro-task。但需要注意的是:当 macro-task 出队时,任务是一个一个执行的;而 micro-task 出队时,任务是一队一队执行的(如下图所示)。因此,我们处理 micro 队列这一步,会逐个执行队列中的任务并把它出队,直到队列被清空。
- 执行渲染操作,更新界面(敲黑板划重点)。
- 检查是否存在 Web worker 任务,如果有,则对其进行处理 。
(上述过程循环往复,直到两个队列都清空)
我们总结一下,每一次循环都是一个这样的过程:
浏览器渲染过程
- 解析HTML,构建DOM树;
- 解析CSS,生成CSS规则树;
- 合成HTML和CSS,生成render树;
- 布局render树,计算每一个节点的位置和尺寸;
- 绘制render树(paint),绘制图像信息;
- 浏览器会把各层信息发给GPU,由GPU显示在屏幕上;
重新布局叫回流,就是改变布局,每次的回流都会触发重绘(repaint), 又要去消耗gpu; 但并不是每次repaint都会触发reflow,比如改个背景色啥的,就不需要重新布局;
onLoad: 在页面所有文件加载完成后执行;DomContentLoaded: DOM加载后执行,不必等待样式脚本和图片下载;
- 使用 document.createElement 创建的 script 默认是异步的.
优化UI渲染和JS执行效率
- 动画实现使用
requestAnimationFrame
; - 长耗时的JS代码放到Web Workers中执行(web worker不能操作DOM);
- 拆分操作DOM元素的任务,分别在多个frame完成;
- 使用Chrome DevTools的Timeline来分析JavaScript的性能
GZIP
accept-encoding:gzip
首先要承认 Gzip 是高效的,压缩后通常能帮我们减少响应 70% 左右的大小。
但它并非万能。Gzip 并不保证针对每一个文件的压缩都会使其变小。
Gzip 压缩背后的原理,是在一个文本文件中找出一些重复出现的字符串、临时替换它们,从而使整个文件变小。根据这个原理,文件中代码的重复率越高,那么压缩的效率就越高,使用 Gzip 的收益也就越大。反之亦然。
图片加载
JPEG/JPG :
- 特点:有损压缩、体积小、加载快、不支持透明;
- 业务场景:经常作为大的背景图、轮播图或 Banner 图出现;
- 缺点:处理矢量图形和Logo等线条感较强、颜色对比强烈的图像时,人为压缩导致的图片模糊会相当明显,同时不支持透明度处理;
PNG-8 与 PNG-24 :
- 特点:无损压缩、质量高、体积大、支持透明;
SVG
- 特点: 文本文件、体积小、不失真、兼容性好;
- SVG 的局限性主要有两个方面,一方面是它的渲染成本比较高,这点对性能来说是很不利的;另一方面,SVG 存在着其它图片格式所没有的学习成本(它是可编程的)
base64
- 并不是图片格式,而是一种编码方式;
- 特点是文本文件、依赖编码、小图标解决方案;
- 图片进行base64编码得到一个字符串,放在
src
里,浏览器也能理解,减少http请求; - 缺点是经过base64编码,图片大小会膨胀到原来的三分之四,因此其实是减少http请求数和图片size的权衡;
webP
- WebP 像 JPEG 一样对细节丰富的图片信手拈来,像 PNG 一样支持透明,像 GIF 一样可以显示动态图片——它集多种图片文件格式的优点于一身;
- 缺点是浏览器兼容性较差,只在chrome比较好;
本地存储
cookie
Cookie 的本职工作并非本地存储,而是“维持状态”。cookie是一个存储在浏览器里的一个小小的文本文件,它附着在 HTTP 请求上,在浏览器和服务器之间“飞来飞去”。它可以携带用户信息,当服务器检查 Cookie 的时候,便可以获取到客户端的状态。
主要的劣势是太小,只有4k。
web Storage
- 生命周期:Local Storage 是持久化的本地存储,存储在其中的数据是永远不会过期的,使其消失的唯一办法是手动删除;而 Session Storage 是临时性的本地存储,它是会话级别的存储,当会话结束(页面被关闭)时,存储内容也随之被释放。
- 作用域:Local Storage、Session Storage 和 Cookie 都遵循同源策略。但 Session Storage 特别的一点在于,即便是相同域名下的两个页面,只要它们不在同一个浏览器窗口中打开,那么它们的 Session Storage 内容便无法共享。
indexedDB
IndexedDB 是一个运行在浏览器上的非关系型数据库。既然是数据库了,那就不是 5M、10M 这样小打小闹级别了。理论上来说,IndexedDB 是没有存储上限的(一般来说不会小于 250M)。它不仅可以存储字符串,还可以存储二进制数据。
CDN
CDN (Content Delivery Network,即内容分发网络)指的是一组分布在各个地区的服务器。这些服务器存储着数据的副本,因此服务器可以根据哪些服务器与用户距离最近,来满足数据的请求。 CDN 提供快速服务,较少受高流量影响。
CDN 的核心点有两个,一个是缓存,一个是回源。
这两个概念都非常好理解。对标到上面描述的过程,“缓存”就是说我们把资源 copy 一份到 CDN 服务器上这个过程,“回源”就是说 CDN 发现自己没有这个资源(一般是缓存的数据过期了),转头向根服务器(或者它的上层服务器)去要这个资源的过程。
CDN 往往被用来存放静态资源。所谓“静态资源”,就是像 JS、CSS、图片等不需要业务服务器进行计算即得的资源。
另外,CDN的域名往往和业务服务器的不一样,因为在同一域名下的请求总是会不分青红皂白地携带cookie,但是静态资源往往是不需要带cookie的,因此部署在CDN服务器上可以避免不必要的cookie出现,提高性能。
SSR
SSR解决了什么问题
- 效益方面:搜索引擎只会查找现成的内容,并不会去跑JS代码,因此,如果需要做SEO的页面就要考虑使用SSR;
- 性能方面:服务器拿过来的html直接是渲染好的,因此可提高首屏加载速度。
SSR的实现
import express from 'express'
import React from 'react'
import { renderToString } from 'react-dom/server'
import VDom from './VDom'
// 创建一个express应用
const app = express()
// renderToString 是把虚拟DOM转化为真实DOM的关键方法
const RDom = renderToString(<VDom />)
// 编写HTML模板,插入转化后的真实DOM内容
const Page = `
<html>
<head\>
<title\>test</title\>
</head\>
<body>
<span\>服务端渲染出了真实DOM: </span\>
${RDom}
<body\>
<html\>`
// 配置HTML内容对应的路由
app.get('/index', function(req, res) { res.send(Page) })
// 配置端口号
const server = app.listen(8000)
所以主要是两步:
- 主要是
renderToString()
方法; - 把转化结果“塞”进模板里;
SSR的应用场景
在实践中,一般会建议大家先忘记服务端渲染这个事情——服务器稀少而宝贵,但首屏渲染体验和 SEO 的优化方案却很多——我们最好先把能用的低成本“大招”都用完。除非网页对性能要求太高了,以至于所有的招式都用完了,性能表现还是不尽人意,这时候我们就可以考虑向老板多申请几台服务器,把服务端渲染搞起来了~
webSocket
web Socket 协议
WebSocket 协议实现了浏览器与服务器全双工通信,能更好的节省服务器资源和带宽并达到实时通讯的目的。它与HTTP一样通过已建立的TCP连接来传输数据,但是它和HTTP最大不同是:
- WebSocket是一种双向通信协议。在建立连接后,WebSocket服务器端和客户端都能主动向对方发送或接收数据,就像Socket一样;
- WebSocket需要像TCP一样,先建立连接,连接成功后才能相互通信。
WebSocket 连接必须由浏览器发起,请求协议是一个标准的HTTP请求(也就是说,WebSocket的建立是依赖HTTP的)。请求报文格式如下:GET ws://localhost:3000/ws/chat HTTP/1.1
Host: localhost
Upgrade: websocket
Connection: Upgrade
Origin: http://localhost:3000
Sec-WebSocket-Key: client-random-string // 用于标识这个连接
Sec-WebSocket-Version: 13 //指定了websocket的协议版本
web Socket API
创建web socket, 指定open、message等事件的回调即可(和web worker和service worker还挺像);
WebSocket 构造函数const ws = new WebSocket('ws://localhost:8080/ws')
webSocket.readyState
CONNECTING: 值为0
,表示正在连接。
OPEN: 值为1
,表示连接成功,可以通信了。
CLOSING: 值为2
,表示连接正在关闭。
CLOSED: 值为3
,表示连接已经关闭,或者打开连接失败。
webSocket.onopen : 指定连接成功之后的回调函数:
ws.onopen = function() {
console.log('hello websocket')
}
webSocket.onclose: 指定连接关闭之后的回调函数
webSocket.onmessage: 指定收到服务器数据后的回调函数
webSocket.onmessage = function (event) {
// 数据可能是文本或者二进制数据
if (typeof event.data === 'string') {
//...
} else if (event.data.instanceof ArrayBuffer) {
//...
}
console.log(event.data)
}
webSocket.send: : 实例对象的send()方法用于向服务器发送数据webSocket.send(message)
SPA和PWA
SPA的优缺点
微信小程序和PWA的异同点:
来自微信小程序和PWA对比分析
两者和native app最大的区别都是“无需安装、用完即走”。
- 小程序:主要依托微信自有生态,好处是几乎不需要运营成本,且方便分享和传播;同时,微信小程序类似于hybrid app的开发方式,用H5完成主要的渲染工作,原生层面上有WebView预加载,原生模块覆盖(camera/map/storage)等。坏处是微信出于稳定考虑没有暴露DOM api, 且只能基于微信系统做个性化封装,开发有一定的局限性,小程序的生态也不是很好;
- PWA:主要利用了service worker做缓存策略以及推送通知。优化了用户的体验,最大的限制就是看浏览器是不是支持service worker。
MVC和MVVM
MVC:
我们所有的App都是界面和数据的交互,所以需要类来进行界面的绘制,于是出现了View,需要类来管理数据于是出现了Model。我们设计的View应该能显示任意的内容比如页面中显示的文字应该是任意的而不只是某个特定Model的内容,所以我们不应该在View的实现中去写和Model相关的任何代码,如果这样做了,那么View的可扩展性就相当低了。而Model只是负责处理数据的,它根本不知道数据到时候会拿去干啥,可能拿去作为算法噼里啪啦去了,可能拿去显示给用户了,它既然无法接收用户的交互,它就不应该去管和视图相关的任何信息,所以Model中不应该写任何View相关代码。然而我们的数据和界面应该同步,也就是一定要有个地方要把Model的数据赋值给View,而Model内部和View的内部都不可能去写这样的代码,所以只能新创造一个类出来了,取名为Controller。
这张图把MVC分为三个独立的区域,并且中间用了一些线来隔开。很有意思的设计,因为这些线似乎出现在了驾校科目一的内容中,你瞧C和V以及C和M之间的白线,一部分是虚线一部分是实线对吧,这就表明了引用关系:C可以直接引用V和M,而V和M不能直接引用C,至少你不能显式的在V和M的代码中去写和C相关的任何代码,而V和M之间则是双黄线,没错,它们俩谁也不能引用谁,你既不能在M里面写V,也不能在V里面写M。哦,上面的描述有点小小的问题,你不是“不能”这样写,而是“不应该”这样写,没人能阻止你在写代码的时候在一个M里面去写V,但是一旦你这样做了,那么你就违背了MVC的规范,你就不是在使用MVC了,所以这算是MVC的一个必要条件:使用MVC –> M里面没有V的代码。所以M里面没有V的代码就是使用MVC的必要条件。
MVVM:
MVVM的诞生:
就像我们之前分析MVC是如何合理分配工作的一样,我们需要数据所以有了M,我们需要界面所以有了V,而我们需要找一个地方把M赋值给V来显示,所以有了C,然而我们忽略了一个很重要的操作:数据解析。在MVC出生的年代,手机APP的数据往往都比较简单,没有现在那么复杂,所以那时的数据解析很可能一步就解决了,所以既然有这样一个问题要处理,而面向对象的思想就是用类和对象来解决问题,显然V和M早就被定义死了,它们都不应该处理“解析数据”的问题,理所应当的,“解析数据”这个问题就交给C来完成了。而现在的手机App功能越来越复杂,数据结构也越来越复杂,所以数据解析也就没那么简单了。如果我们继续按照MVC的设计思路,将数据解析的部分放到了Controller里面,那么Controller就将变得相当臃肿。还有相当重要的一点:Controller被设计出来并不是处理数据解析的。1、管理自己的生命周期;2、处理Controller之间的跳转;3、实现Controller容器。这里面根本没有“数据解析”这一项,所以显然,数据解析也不应该由Controller来完成。那么我们的MVC中,M、V、C都不应该处理数据解析,那么由谁来呢?这个问题实际上在面向对象的时候相当好回答:既然目前没有类能够处理这个问题,那么就创建一个新的类出来解决不就好了?所以我们聪明的开发者们就专门为数据解析创建出了一个新的类:ViewModel。这就是MVVM的诞生。
webpack
实现commonjs
整个打包的代码就是一个IIFE(立即执行函数), 浏览器本身不支持模块化,那么webpack就用函数作用域来hack模块化的效果。IIFE先定义了installedModules
,这个变量用来缓存已加载的模块。如果有缓存就直接使用缓存,如果没有缓存,也就是第一次加载,则首先初始化模块,并将模块进行缓存。
splitting
- bundle splitting : 创建更多更小的文件,并行加载,以获得更好的缓存效果,主要作用就是使浏览器并行下载,提高下载速度。并且运用浏览器缓存,只有代码被修改,文件名中的哈希值改变了才会去再次加载。
(比如拆分成main.js和vendor.js, 把vendor.js拆分成各个npm包,把main.js根据业务场景也拆分成更小的包)
-
code splitting :只加载用户最需要的部分,其余的代码都遵从懒加载的策略,主要的作用就是加快页面的加载速度,不加载不必要的代码。
- 最简单就是使用
import
或者require
; - react里可以使用
React.lazy(() => import('/aComponent')
) 或者bundle-loader
(其实bundle-loader也是用require.ensure()实现的);
- 最简单就是使用
加快打包效率
- 缓存
- 多核 (happyPack): 任务分解成多个子进程去并发执行,大大提升打包效率
- 抽离(利用CDN配置externals)
前后端鉴权
cookie-session验证
- 服务器在接受客户端首次访问时在服务器端创建seesion,然后保存seesion(我们可以将seesion保存在内存中,也可以保存在redis中,推荐使用后者),然后给这个session生成一个唯一的标识字符串,然后在响应头中种下这个唯一标识字符串;
- 浏览器中收到请求响应的时候会解析响应头,然后将sid保存在本地cookie中,浏览器在下次http请求的请求头中会带上该域名下的cookie信息;
- 服务器在接受客户端请求时会去解析请求头cookie中的sid,然后根据这个sid去找服务器端保存的该客户端的session,然后判断该请求是否合法。
Token验证
- 客户端使用用户名跟密码请求登录
- 服务端收到请求,去验证用户名与密码
- 验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端
- 客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里或者 Local Storage 里
- 客户端每次向服务端请求资源的时候需要带着服务端签发的 Token
- 服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端返回请求的数据
看着和cookie-session验证很像,只是这里的token代替了session id,但是两者的差别是很大的:
- sessionid 他只是一个唯一标识的字符串,但是token本身就是一种登陆成功凭证,他是在登陆成功后根据某种规则生成的一种信息凭证,他里面本身就保存着用户的登陆状态。服务器端只需要根据定义的规则校验这个token是否合法就行。
- session-cookie是需要cookie配合的,那么在http代理客户端的选择上就是只有浏览器了,因为只有浏览器才会去解析请求响应头里面的cookie,然后每次请求再默认带上该域名下的cookie。简单点来说cookie-session机制他限制了客户端的类型,而token验证机制丰富了客户端类型。
- 时效性。session-cookie的sessionid实在登陆的时候生成的而且在登出事时一直不变的,在一定程度上安全就会低,而token是可以在一段时间内动态改变的;
- 可扩展性。token验证本身是比较灵活的,一是token的解决方案有许多,常用的是JWT,二来我们可以基于token验证机制,专门做一个鉴权服务,用它向多个服务的请求进行统一鉴权。
OAuth验证(第三方鉴权)
微信公众号:
关系型与非关系型数据库的比较
1.成本:Nosql
数据库简单易部署,基本都是开源软件,不需要像使用Oracle
那样花费大量成本购买使用,相比关系型数据库价格便宜。
2.查询速度:Nosql
数据库将数据存储于缓存之中,而且不需要经过SQL
层的解析,关系型数据库将数据存储在硬盘中,自然查询速度远不及Nosql
数据库。
3.存储数据的格式:Nosql
的存储格式是key,value
形式、文档形式、图片形式等等,所以可以存储基础类型以及对象或者是集合等各种格式,而数据库则只支持基础类型。
4.扩展性:关系型数据库有类似join这样的多表查询机制的限制导致扩展很艰难。Nosql
基于键值对,数据之间没有耦合性,所以非常容易水平扩展。
5.持久存储:Nosql
不使用于持久存储,海量数据的持久存储,还是需要关系型数据库
6.数据一致性:非关系型数据库一般强调的是数据最终一致性,不像关系型数据库一样强调数据的强一致性,从非关系型数据库中读到的有可能还是处于一个中间态的数据, Nosql
不提供对事务的处理。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。