言
金九银十跳槽季,offer快到碗里来,前端面试考点众多,而网上各个知识点的博客文章又太多,看的眼花缭乱...所以我整理了一下常见知识点的精华文章,每个知识点的文章控制在3篇以内,尽量覆盖该知识点的下容易被面试到的所有内容,文章都是之前自己读过的,确定是精华干货。文章会一直更新,也欢迎大家推荐精华文章,大家共同学习进步呀!
display:flex; 在父元素设置,子元素受弹性盒影响,默认排成一行,如果超出一行,按比例压缩 flex:1; 子元素设置,设置子元素如何分配父元素的空间,flex:1,子元素宽度占满整个父元素align-items:center 定义子元素在父容器中的对齐方式,center 垂直居中justify-content:center 设置子元素在父元素中居中,前提是子元素没有把父元素占满,让子元素水平居中。
transtion transition-property 规定设置过渡效果的 CSS 属性的名称。
transition-duration 规定完成过渡效果需要多少秒或毫秒。
transition-timing-function 规定速度效果的速度曲线。
transition-delay 定义过渡效果何时开始。
animation属性可以像Flash制作动画一样,通过控制关键帧来控制动画的每一步,实现更为复杂的动画效果。
ainimation实现动画效果主要由两部分组成:
通过类似Flash动画中的帧来声明一个动画;
在animation属性中调用关键帧声明的动画。
translate 3D建模效果
图片中的 alt属性是在图片不能正常显示时出现的文本提示。alt有利于SEO优化
图片中的 title属性是在鼠标在移动到元素上的文本提示。
复制代码 <style>
div {
width: 0;
height: 0;
border-top: 40px solid transparent;
border-left: 40px solid transparent;
border-right: 40px solid transparent;
border-bottom: 40px solid #ff0000;
}
</style>
</head>
<body>
<div></div>
</body>
标准盒子模型:宽度=内容的宽度(content)+ border + padding
低版本IE盒子模型:宽度=内容宽度(content+border+padding)
已知宽度,block元素 ,添加添加margin:0 auto属性。
已知宽度,绝对定位的居中 ,上下左右都为0,margin:auto
复制代码div {
position: relative / fixed; /* 相对定位或绝对定位均可 */
width:500px;
height:300px;
top: 50%;
left: 50%;
margin-top:-150px;
margin-left:-250px;
外边距为自身宽高的一半 */
background-color: pink; /* 方便看效果 */
}
.container {
display: flex;
align-items: center; /* 垂直居中 */
justify-content: center; /* 水平居中 */
}
.container div {
width: 100px; /* 可省 */
height: 100px; /* 可省 */
background-color: pink; /* 方便看效果 */
}
clear清除浮动(添加空div法)在浮动元素下方添加空div,并给该元素写css样式 {clear:both;height:0;overflow:hidden;}
给浮动元素父级设置高度
父级同时浮动(需要给父级同级元素添加浮动)
父级设置成inline-block,其margin: 0 auto居中方式失效
给父级添加overflow:hidden 清除浮动方法
万能清除法 after伪类 清浮动(现在主流方法,推荐使用)
复制代码float_div:after{
content:".";
clear:both;
display:block;
height:0;
overflow:hidden;
visibility:hidden;
}
.float_div{
zoom:1
}
圣杯布局/双飞翼布局
复制代码 <style>
* {
margin: 0;
padding: 0;
}
.middle,
.left,
.right {
position: relative;
float: left;
min-height: 130px;
}
.container {
padding: 0 220px 0 200px;
overflow: hidden;
}
.left {
margin-left: -100%;
left: -200px;
width: 200px;
background: red;
}
.right {
margin-left: -220px;
right: -220px;
width: 220px;
background: green;
}
.middle {
width: 100%;
background: blue;
word-break: break-all;
}
</style>
</head>
<body>
<div class='container'>
<div class='middle'></div>
<div class='left'></div>
<div class='right'></div>
</div>
</body>
display:none 隐藏对应的元素,在文档布局中不再给它分配空间,它各边的元素会合拢,就当他从来不存在。
visibility:hidden 隐藏对应的元素,但是在文档布局中仍保留原来的空间。
link属于HTML标签,而@import是CSS提供的页面被加载的时,link会同时被加载,而@import引用的CSS会等到页面被加载完再加载
import只在IE5以上才能识别,而link是HTML标签,无兼容问题
link方式的样式的权重 高于@import的权重.
共同点: 改变行内元素的呈现方式,display被置为block 让元素脱离普通流,不占据空间 默认会覆盖到非定位元素上
不同点: absolute的”根元素“是可以设置的 fixed的”根元素“固定为浏览器窗口。当你滚动网页,fixed元素与浏览器窗口之间的距离是不变的。
Animation和transition大部分属性是相同的,他们都是随时间改变元素的属性值,他们的主要区别是transition需要触发一个事件才能改变属性, 而animation不需要触发任何事件的情况下才会随时间改变属性值,并且transition为2帧,从from .... to,而animation可以一帧一帧的。
transition 规定动画的名字 规定完成过渡效果需要多少秒或毫秒 规定速度效果 定义过渡效果何时开始 animation 指定要绑定到选择器的关键帧的名称
复制代码不同级别:总结排序:!important > 行内样式>ID选择器 > 类选择器 > 标签 > 通配符 > 继承 > 浏览器默认属性
1.属性后面加!import 会覆盖页面内任何位置定义的元素样式
2.作为style属性写在元素内的样式
3.id选择器
4.类选择器
5.标签选择器
6.通配符选择器(*)
7.浏览器自定义或继承
**同一级别:后写的会覆盖先写的**
css选择器的解析原则:选择器定位DOM元素是从右往左的方向,这样可以尽早的过滤掉一些不必要的样式规则和元素
复制代码 多个图片集成在一个图片中的图
使用雪碧图可以减少网络请求的次数,加快允许的速度
通过background-position,去定位图片在屏幕的哪个位置
相同点: 都常用来判断一个变量是否为空,或者是什么类型的。
不同点: typeof 返回值是一个字符串,用来说明变量的数据类型 instanceof 用于判断一个变量是否属于某个对象的实例.
复制代码visibility:hidden、display:none、z-index=-1、opacity:0
1.opacity:0,该元素隐藏起来了,但不会改变页面布局,并且,如果该元素已经绑定了一些事件,如click事件也能触发
2.visibility:hidden,该元素隐藏起来了,但不会改变页面布局,但是不会触发该元素已经绑定的事件
3.display:node, 把元素隐藏起来,并且会改变页面布局,可以理解成在页面中把该元素删掉
浅克隆: 只是拷贝了基本类型的数据,而引用类型数据,复制后也是会发生引用,我们把这种拷贝叫做“(浅复制)浅拷贝”,换句话说,浅复制仅仅是指向被复制的内存地址,如果原地址中对象被改变了,那么浅复制出来的对象也会相应改变。
深克隆: 创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。 JSON.parse、JSON.stringify()
let定义块级作用域变量 没有变量的提升,必须先声明后使用 let声明的变量,不能与前面的let,var,conset声明的变量重名
const 定义只读变量 const声明变量的同时必须赋值,const声明的变量必须初始化,一旦初始化完毕就不允许修改 const声明变量也是一个块级作用域变量 const声明的变量没有“变量的提升”,必须先声明后使用 const声明的变量不能与前面的let, var , const声明的变量重 const定义的对象\数组中的属性值可以修改,基础数据类型不可以
ES6可以给形参函数设置默认值
在数组之前加上三个点(...)展开运算符
数组的解构赋值、对象的解构赋值
箭头函数的特点 箭头函数相当于匿名函数,是不能作为构造函数的,不能被new 箭头函数没有arguments实参集合,取而代之用...剩余运算符解决 箭头函数没有自己的this。他的this是继承当前上下文中的this 箭头函数没有函数原型 箭头函数不能当做Generator函数,不能使用yield关键字 不能使用call、apply、bind改变箭头函数中this指向 Set数据结构,数组去重
=赋值
==返回一个布尔值;相等返回true,不相等返回false; 允许不同数据类型之间的比较; 如果是不同类型的数据进行,会默认进行数据类型之间的转换; 如果是对象数据类型的比较,比较的是空间地址
===只要数据类型不一样,就返回false;
复制代码1、js工厂模式
2、js构造函数模式
3、js原型模式
4、构造函数+原型的js混合模式
5、构造函数+原型的动态原型模式
6、观察者模式
7、发布订阅模式
call() 和apply()的第一个参数相同,就是指定的对象。这个对象就是该函数的执行上下文。
call()和apply()的区别就在于,两者之间的参数。
call()在第一个参数之后的 后续所有参数就是传入该函数的值。
apply() 只有两个参数,第一个是对象,第二个是数组,这个数组就是该函数的参数。 bind() 方法和前两者不同在于: bind() 方法会返回执行上下文被改变的函数而不会立即执行,而前两者是 直接执行该函数。他的参数和call()相同。
原型链继承 核心: 将父类的实例作为子类的原型
构造继承 核心:使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类
实例继承 核心:为父类实例添加新特性,作为子类实例返回
拷贝继承
组合继承 核心:通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现 函数复用
寄生组合继承 核心:通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实 例方法/属性,避免的组合继承的缺点
个人感觉,简单来说闭包就是在函数里面声明函数,本质上说就是在函数内部和函数外部搭建起一座桥梁,使得子函数可以访问父函数中所有的局部变量,但是反之不可以,这只是闭包的作用之一,另一个作用,则是保护变量不受外界污染,使其一直存在内存中,在工作中我们还是少使用闭包的好,因为闭包太消耗内存,不到万不得已的时候尽量不使用。
把所有的对象共用的属性全部放在堆内存的一个对象(共用属性组成的对象),然后让每一个对象的 __proto__存储这个「共用属性组成的对象」的地址。而这个共用属性就是原型,原型出现的目的就是为了减少不必要的内存消耗。而原型链就是对象通过__proto__向当前实例所属类的原型上查找属性或方法的机制,如果找到Object的原型上还是没有找到想要的属性或者是方法则查找结束,最终会返回undefined
将html代码按照深度优先遍历来生成DOM树。 css文件下载完后也会进行渲染,生成相应的CSSOM。 当所有的css文件下载完且所有的CSSOM构建结束后,就会和DOM一起生成Render Tree。 接下来,浏览器就会进入Layout环节,将所有的节点位置计算出来。 最后,通过Painting环节将所有的节点内容呈现到屏幕上。
复制代码1、浏览器的地址栏输入URL并按下回车。
2、浏览器查找当前URL是否存在缓存,并比较缓存是否过期。3、DNS解析URL对应的IP。
4、根据IP建立TCP连接(三次握手)。
5、HTTP发起请求。
6、服务器处理请求,浏览器接收HTTP响应。
7、渲染页面,构建DOM树。
8、关闭TCP连接(四次挥手)
相同点 都是保存在浏览器端,且同源的。
不同点
同源策略(协议+端口号+域名要相同)
1、jsonp跨域(只能解决get) 原理:动态创建一个script标签。利用script标签的src属性不受同源策略限制,因为所有的src属性和href属性都不受同源策略的限制,可以请求第三方服务器资源内容
步骤: 1).去创建一个script标签 2).script的src属性设置接口地址 3).接口参数,必须要带一个自定义函数名,要不然后台无法返回数据 4).通过定义函数名去接受返回的数据
2、document.domain 基础域名相同 子域名不同
3、window.name 利用在一个浏览器窗口内,载入所有的域名都是共享一个window.name
4、服务器设置对CORS的支持 原理:服务器设置Access-Control-Allow-Origin HTTP响应头之后,浏览器将会允许跨域请求
5、利用h5新特性window.postMessage()
1.创建ajax实例
2.执行open 确定要访问的链接 以及同步异步
3.监听请求状态
4.发送请求
ES6的set对象 先将原数组排序,在与相邻的进行比较,如果不同则存入新数组
复制代码function unique(arr){
var arr2=arr.sort();
var res=[arr2[0]];
for(var i=1;i<arr2.length;i++){
if(arr2[i] !==res[res.length-1]){
res.push(arr2[i]);
}
}
return res;
}
利用下标查询
function unique(arr){
var newArr=[arr[0]];
for(var i=1;i<arr.length;i++){
if(newArr.indexOf(arr[i])==-1){
newArr.push(arr[i]);
}
}
return newArr;
}
2开头
3开头
以4开头的都是客户端的问题;
以5开头都是服务端的问题
同步:在同一时间内做一件事情
异步:在同一时间内做多个事情 JS是单线程的,每次只能做一件事情,JS运行在浏览器中,浏览器是多线程的,可以在同一时间执行多个任务。
定时器、ajax、事件绑定、回调函数、async await、promise
三次握手
四次挥手
建立连接的时候, 服务器在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。 而关闭连接时,服务器收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,而自己也未必全部数据都发送给对方了,所以己方可以立即关闭,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会分开发送,从而导致多了一次。
如果元素类型发生变化,直接替换 如果是文本,则比较文本里面的内容,是否有差异,如果是元素就需要比较当前元素的属性是否相等,会先比较key, 在比较类型 为什么 react中循环 建议不要使用索引 ,如果纯为了展示 那可以使用索引
全局作用域
私有作用域
块级作用域
上级作用域
他是ES6中新增加的一个类(new Promise),目的是为了管理JS中的异步编程的,所以把他称为“Promise设计模式” new Promise 经历三个状态:padding(准备状态:初始化成功、开始执行异步的任务)、fullfilled(成功状态)、rejected(失败状态)==Promise本身是同步编程的,他可以管理异步操作的(重点),new Promise的时候,会把传递的函数立即执行 Promise函数天生有两个参数,resolve(当异步操作执行成功,执行resolve方法),rejected(当异步操作失败,执行reject方法) then()方法中有两个函数,第一个传递的函数是resolve,第二个传递的函数是reject ajax中false代表同步,true代表异步,如果使用异步,不等ajax彻底完成
相同点
不同点
注意:forEach对于空数组是不会调用回调函数的。
async/await函数是异步代码的新方式
async/await是基于promise实现的
async/await使异步代码更像同步代码
await 只能在async函数中使用,不能再普通函数中使用,要成对出现
默认返回一个promise实例,不能被改变
await下面的代码是异步,后面的代码是同步的
全局作用域下的this指向window 如果给元素的事件行为绑定函数,那么函数中的this指向当前被绑定的那个元素 函数中的this,要看函数执行前有没有 . , 有 . 的话,点前面是谁,this就指向谁,如果没有点,指向window 自执行函数中的this永远指向window 定时器中函数的this指向window 构造函数中的this指向当前的实例 call、apply、bind可以改变函数的this指向 箭头函数中没有this,如果输出this,就会输出箭头函数定义时所在的作用域中的this
所有的函数数据类型都天生自带一个prototype属性,该属性的属性值是一个对象 prototype的属性值中天生自带一个constructor属性,其constructor属性值指向当前原型所属的类 所有的对象数据类型,都天生自带一个_proto_属性,该属性的属性值指向当前实例所属类的原型
复制代码promise、generator、async/await
promise: 1.是一个对象,用来传递异步操作的信息。代表着某个未来才会知道结果的时间,并未这个事件提供统一的api,供进异步处理
2.有了这个对象,就可以让异步操作以同步的操作的流程来表达出来,避免层层嵌套的回调地狱
3.promise代表一个异步状态,有三个状态pending(进行中),Resolve(以完成),Reject(失败)
4.一旦状态改变,就不会在变。任何时候都可以得到结果。从进行中变为以完成或者失败
promise.all() 里面状态都改变,那就会输出,得到一个数组
promise.race() 里面只有一个状态变为rejected或者fulfilled即输出
promis.finally()不管指定不管Promise对象最后状态如何,都会执行的操作(本质上还是then方法的特例)
复制代码事件流描述的是从页面中接受事件的顺序,事件 捕获阶段 处于目标阶段 事件冒泡阶段 addeventListener 最后这个布尔值参数如果是true,表示在捕获阶段调用事件处理程序;如果是false,表示在冒泡阶段调用事件处理程序。
1、事件捕获阶段:实际目标div在捕获阶段不会接受事件,也就是在捕获阶段,事件从document到<html>再到<body>就停止了。
2、处于目标阶段:事件在div发生并处理,但是事件处理会被看成是冒泡阶段的一部分。
3、冒泡阶段:事件又传播回文档
阻止冒泡事件event.stopPropagation()
function stopBubble(e) {
if (e && e.stopPropagation) { // 如果提供了事件对象event 这说明不是IE浏览器
e.stopPropagation()
} else {
window.event.cancelBubble=true //IE方式阻止冒泡
}
}
阻止默认行为event.preventDefault()
function stopDefault(e) {
if (e && e.preventDefault) {
e.preventDefault()
} else {
// IE浏览器阻止函数器默认动作的行为
window.event.returnValue=false
}
}
在DOM标准事件模型中,是先捕获后冒泡。但是如果要实现先冒泡后捕获的效果, 对于同一个事件,监听捕获和冒泡,分别对应相应的处理函数,监听到捕获事件,先暂缓执行,直到冒泡事件被捕获后再执行捕获事件。
复制代码千万不要使用typeof来判断对象和数组,因为这种类型都会返回object。
typeOf()是判断基本类型的Boolean,Number,symbol, undefined, String。 对于引用类型:除function,都返回object null返回object。
installOf() 用来判断A是否是B的实例,installof检查的是原型。
toString() 是Object的原型方法,对于 Object 对象,直接调用 toString() 就能返回 [Object Object] 。而对于其他对象,则需要通过 call / apply 来调用才能返回正确的类型信息。
hasOwnProperty()方法返回一个布尔值,指示对象自身属性中是否具有指定的属性,该方法会忽略掉那些从原型链上继承到的属性。
isProperty()方法测试一个对象是否存在另一个对象的原型链上。
复制代码因为js是单线程的。浏览器遇到etTimeout 和 setInterval会先执行完当前的代码块,在此之前会把定时器推入浏览器的
待执行时间队列里面,等到浏览器执行完当前代码之后会看下事件队列里有没有任务,有的话才执行定时器里的代码
复制代码 1.slice(start,end):方法可以从已有数组中返回选定的元素,返回一个新数组,
包含从start到end(不包含该元素)的数组方法
注意:该方法不会更新原数组,而是返回一个子数组
2.splice():该方法想或者从数组中添加或删除项目,返回被删除的项目。(该方法会改变原数组)
splice(index, howmany,item1,...itemx)
·index参数:必须,整数规定添加或删除的位置,使用负数,从数组尾部规定位置
·howmany参数:必须,要删除的数量,
·item1..itemx:可选,向数组添加新项目
3.map():会返回一个全新的数组。使用于改变数据值的时候。会分配内存存储空间数组并返回,forEach()不会返回数据
4.forEach(): 不会返回任何有价值的东西,并且不打算改变数据,单纯的只是想用数据做一些事情,他允许callback更改原始数组的元素
5.reduce(): 方法接收一个函数作为累加器,数组中的每一个值(从左到右)开始缩减,最终计算一个值,不会改变原数组的值
6.filter(): 方法创建一个新数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。它里面通过function去做处理
vue是一个渐进式的JS框架。他易用,灵活,高效; 可以把一个页面分隔成多个组件;当其他页面有类似功能时,直接让封装的组件进行复用; 他是构建用户界面的声明式框架,只关心图层;不关心具体是如何实现的
Vue的双向数据绑定是由数据劫持结合发布者订阅者实现的。 数据劫持是通过Object.defineProperty()来劫持对象数据的setter和getter操作。 在数据变动时作你想做的事
原理 通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化->视图更新 在初始化vue实例时,遍历data这个对象,给每一个键值对利用Object.definedProperty对data的键值对新增get和set方法,利用了事件监听DOM的机制,让视图去改变数据
react整体是函数式的思想,把组件设计成纯组件,状态和逻辑通过参数传入,所以在react中,是单向数据流;
vue的思想是响应式的,也就是基于是数据可变的,通过对每一个属性建立Watcher来监听,当属性变化的时候,响应式的更新对应的虚拟dom。
复制代码页面通过mapAction异步提交事件到action。action通过commit把对应参数同步提交到mutation。
mutation会修改state中对于的值。 最后通过getter把对应值跑出去,在页面的计算属性中
通过mapGetter来动态获取state中的值
state中保存着共有数据,数据是响应式的 getter可以对state进行计算操作,主要用来过滤一些数据,可以在多组件之间复用 mutations定义的方法动态修改state中的数据,通过commit提交方法,方法必须是同步的 actions将mutations里面处理数据的方法变成异步的,就是异步操作数据,通store.dispatch来分发actions,把异步的方法写在actions中,通过commit提交mutations,进行修改数据。 modules:模块化vuex
hash ——即地址栏URL中的#符号(此hsah 不是密码学里的散列运算) hash 虽然出现URL中,但不会被包含在HTTP请求中,对后端完全没有影响,因此改变hash不会重新加载页面。 history ——利用了HTML5 History Interface 中新增的pushState() 和replaceState() 方法
这两个方法应用于浏览器的历史记录站,在当前已有的back、forward、go 的基础之上,它们提供了对历史记录进行修改的功能。只是当它们执行修改是,虽然改变了当前的URL,但你浏览器不会立即向后端发送请求。
当 Vue.js 用v-for正在更新已渲染过的元素列表时,它默认用“就地复用”策略。 如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序,而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素。
key的作用主要是为了高效的更新虚拟DOM。
$route是“路由信息对象”,包括path,params,hash,query,fullPath,matched,name等路由信息参数。 $router是“路由实例”对象包括了路由的跳转方法,钩子函数等。
导航守卫 router.beforeEach 全局前置守卫
复制代码// main.js 入口文件
import router from './router'; // 引入路由
router.beforeEach((to, from, next)=> {
next();
});
router.beforeResolve((to, from, next)=> {
next();
});
router.afterEach((to, from)=> {
console.log('afterEach 全局后置钩子');
});
路由独享的守卫 你可以在路由配置上直接定义 beforeEnter 守卫
复制代码const router=new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next)=> {
// ...
}
}
]
})
组件内的守卫 你可以在路由组件内直接定义以下路由导航守卫
复制代码const Foo={
template: `...`,
beforeRouteEnter (to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
},
beforeRouteUpdate (to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave (to, from, next) {
// 导航离开该组件的对应路由时调用,我们用它来禁止用户离开
// 可以访问组件实例 `this`
// 比如还未保存草稿,或者在用户离开前,
将setInterval销毁,防止离开之后,定时器还在调用。
}
}
请求后台资源的模块。
复制代码$ npm install axios -S装好
然后发送的是跨域,需在配置文件中config/index.js进行设置。后台如果是Tp5则定义一个资源路由。 js中使用import进来,然后.get或.post。返回在.then函数中如果成功,失败则是在.catch函数中
1.不要在模板里面写过多表达式
2.循环调用子组件时添加key
3.频繁切换的使用v-show,不频繁切换的使用v-if
4.尽量少用float,可以用flex
5.按需加载,可以用require或者import()按需加载需要的组件
6.路由懒加载
extend 是构造一个组件的语法器。 然后这个组件你可以作用到Vue.component这个全局注册方法里 还可以在任意vue模板里使用组件。 也可以作用到vue实例或者某个组件中的components属性中并在内部使用apple组件。 Vue.component 你可以创建 ,也可以取组件。
png24位的图片在iE6浏览器上出现背景 解决方案是做成PNG8.也可以引用一段脚本处理.
浏览器默认的margin和padding不同。 解决方案是加一个全局的*{margin:0;padding:0;}来统一。
IE6双边距bug:块属性标签float后,又有横行的margin情况下,在ie6显示margin比设置的大。
浮动ie产生的双倍距离(IE6双边距问题:在IE6下,如果对元素设置了浮动,同时又设置了margin-left或margin-right,margin值会加倍。) #box{ float:left; width:10px; margin:0 0 0 100px;}
复制代码=> 相同点:
1.数据驱动页面,提供响应式的试图组件
2.都有virtual DOM,组件化的开发,通过props参数进行父子之间组件传递数据,都实现了webComponents规范
3.数据流动单向,都支持服务器的渲染SSR
4.都有支持native的方法,react有React native, vue有wexx=> 不同点:
1.数据绑定:Vue实现了双向的数据绑定,react数据流动是单向的
2.数据渲染:大规模的数据渲染,react更快
3.使用场景:React配合Redux架构适合大规模多人协作复杂项目,Vue适合小快的项目
4.开发风格:react推荐做法jsx + inline style把html和css都写在js了
vue是采用webpack + vue-loader单文件组件格式,html, js, css同一个文件
复制代码Redux数据流里,reduces其实是根据之前的状态(previous state)和现有的action(current action)
更新state(这个state可以理解为上下累加器的结果)
每次redux reducer被执行时,state和action被传入,这个state根据action进行累加或者是'自身消减'(reduce),
进而返回最新的state,这也就是典型reduce函数的用法:state -> action -> state
复制代码refs就想一个逃生窗,允许我们之间访问dom元素或者组件实例,可以向组件添加一个ref属性的值是一个回调函数,
它将接受地城dom元素或组件的已挂在实例,作为第一个参数
复制代码帮组我们跟踪哪些项目已更改、添加、从列表中删除,key是独一无二的,可以让我们高效的去定位元素,并且操作它
复制代码三个状态:Mounting(已插入真实的DOM)
Updating(正在被重新渲染)
Unmounting(已移除真实的DOM)
componentDIdMount 在第一次渲染后调用,只在客服端。之后组件已经生成对应的DOM结构,
componentDidUpdate 在组件完成更新后立即调用,在出初始化是不会调用
复制代码父组件通过props 给子组件传递数据,子组件则是通过调用父组件传给它的函数给父组件传递数据。
复制代码虚拟DOM相当于在js和真实dom中间加了一个缓存,利用dom diff算法避免了没有必要的doom操作,从而提高性能
具体实现步骤:
·用JavaScript对象结构表示DOM树的结构;然后用这个树构建一个真正的DOM树,插到文档中
·当状态变更的时候,重新构造一棵树的对象树,然后用新的树和旧的树进行对比,记录两棵树差异
·把2所记录的差异应用到步骤1所构建的真正的DOM树上,试图就更新了。
复制代码1.把树形结构按照层级分解,只比较同级元素
2.给列表结构的每个单元添加key属性,方便比较。在实际代码中,会对新旧两棵树进行一个深度优先的遍历,这样每个节点都会有一个标记
3.在深度优先遍历的时候,每遍历到一个节点就把该节点和新的树进行对比。如果有差异的话就记录到一个对象里面
Vritual DOM 算法主要实现上面步骤的三个函数:element, diff, patch。然后就可以实际的进行使用
react只会匹配相同的class的component(这里的class指的是组件的名字)
合并操作,条用component的setState方法的时候,React将其标记为dirty.到每一个时间循环借宿,React检查所有标记dirty的component重新绘制
4.选择性子树渲染。可以重写shouldComponentUpdate提高diff的性能
复制代码flux的最大特点,就是数据的‘单向流动’
1.用户访问View
2.View发出用户的Action
3.Dispatcher收到Action,要求state进行相应的更新
4.store更新后,发出一个‘change’事件后,更新页面
复制代码shouldComponentUpdate 这个方法用来判断是否需要调用render方法重新描绘dom.因为dom的描绘非常消耗性能,
如果我们在shouldComponentUpdate方法中能够写出更优化的dom diff算法,可以极大的提高性能
复制代码根据组件的职责通常把组件分为UI组件和容器组件
UI组件负责UI的呈现,容器组件负责管理数据和逻辑
两者通过React-redux提供connect方法联系起来
复制代码setState通过一个队列机制实现state更新,当执行setState时,会将需要更新的state很后放入状态队列
而不会立即更新this.state,队列机制可以高效地批量更新state。如果不通过setState而直接修改this.state的值
那么该state将不会被放入状态队列中。当下次调用setState并对状态队列进行合并时,就会忽略之前修改的state,造成不可预知的错误
同时,也利用了队列机制实现了setState的异步更新,避免了频繁的重复更新state
同步更新state:
setState 函数并不会阻塞等待状态更新完毕,因此 setNetworkActivityIndicatorVisible 有可能先于数据渲染完毕就执行。
第二个参数是一个回调函数,在setState的异步操作结束并且组件已经重新渲染的时候执行
也就是说,我们可以通过这个回调来拿到更新的state的值,实现代码的同步
例子:componentDidMount() {
fetch('https://test.com')
.then((res)=> res.json())
.then(
(data)=> {
this.setState({ data:data });
StatusBar.setNetworkActivityIndicatorVisible(false);
}
复制代码1.异步加载模块
2.提取第三库
3.代码压缩
4.去除不必要的插件
复制代码一、减少代码体积 1.使用CommonsChunksPlugin 提取多个chunk之间的通用模块,减少总体代码体积
2.把部分依赖转移到CDN上,避免每次编译过程都由Webpack处理
3.对一些组件库采用按需加载,避免无用的代码
二、减少目录检索范围
·在使用loader的时候,通过制定exclude和include选项,减少loader遍历的目录范围,从而加快webpack编译速度
三、减少检索路经:resolve.alias可以配置webpack模块解析的别名,对于比较深的解析路经,可以对其配置alias
复制代码 1、首屏加载和按需加载,懒加载
2、资源预加载
3、图片压缩处理,使用base64内嵌图片
4、合理缓存dom对象
5、使用touchstart代替click(click 300毫秒的延迟)
6、利用transform:translateZ(0),开启硬件GUP加速
7、不滥用web字体,不滥用float(布局计算消耗性能),减少font-size声明
8、使用viewport固定屏幕渲染,加速页面渲染内容
9、尽量使用事件代理,避免直接事件绑定
复制代码1.减少入口文件体积
2.静态资源本地缓存
3.开启Gzip压缩
4.使用SSR,nuxt.js
复制代码由来:
300毫米延迟解决的是双击缩放。双击缩放,手指在屏幕快速点击两次。safari浏览器就会将网页缩放值原始比例。由于用户可以双击缩放或者是滚动的操作,
当用户点击屏幕一次之后,浏览器并不会判断用户确实要打开至这个链接,还是想要进行双击操作
因此,safair浏览器就会等待300ms,用来判断用户是否在次点击了屏幕
解决方案:1.禁用缩放,设置meta标签 user-scalable=no
2.fastclick.js
原理:FastClick的实现原理是在检查到touchend事件的时候,会通过dom自定义事件立即
发出click事件,并把浏览器在300ms之后真正的click事件阻止掉
fastclick.js还可以解决穿透问题
在不改变外部行为的前提下,简化结构、添加可读性
复制代码 2XX(成功处理了请求状态)
200 服务器已经成功处理请求,并提供了请求的网页
201 用户新建或修改数据成功
202 一个请求已经进入后台
204 用户删除成功
3XX(每次请求使用的重定向不要超过5次)
304 网页上次请求没有更新,节省带宽和开销
4XX(表示请求可能出错,妨碍了服务器的处理)
400 服务器不理解请求的语法
401 用户没有权限(用户名,密码输入错误)
403 用户得到授权(401相反),但是访问被禁止
404 服务器找不到请求的网页,
5XX(表示服务器在处理请求的时候发生内部错误)
500 服务器遇到错误,无法完成请求
503 服务器目前无法使用(超载或停机维护)
复制代码1.服务器首先产生Etag,服务器可在稍后使用它来判断页面是否被修改。本质上,客户端通过该记号传回服务器要求服务器验证(客户端)缓存)
2.304是 HTTP的状态码,服务器用来标识这个文件没有被修改,不返回内容,浏览器接受到这个状态码会去去找浏览器缓存的文件
3.流程:客户端请求一个页面A。服务器返回页面A,并在A上加一个Tage客服端渲染该页面,并把Tage也存储在缓存中。客户端再次请求页面A
并将上次请求的资源和ETage一起传递给服务器。服务器检查Tage.并且判断出该页面自上次客户端请求之后未被修改。直接返回304
last-modified: 客服端请求资源,同时有一个last-modified的属性标记此文件在服务器最后修改的时间
客服端第二次请求此url时,根据http协议。浏览器会向服务器发送一个If-Modified-Since报头,
询问该事件之后文件是否被修改,没修改返回304
有了Last-Modified,为什么还要用ETag?
1、因为如果在一秒钟之内对一个文件进行两次更改,Last-Modified就会不正确(Last—Modified不能识别秒单位的修改)
2、某些服务器不能精确的得到文件的最后修改时间
3、一些文件也行会周期新的更改,但是他的内容并不改变(仅仅改变修改的事件),这个时候我们并不希望客户端认为文件被修改,而重新Get
ETag,为什么还要用Last-Modified?
1、两者互补,ETag的判断的缺陷,比如一些图片等静态文件的修改
2、如果每次扫描内容都生成ETag比较,显然要比直接比较修改时间慢的多。
ETag是被请求变量的实体值(文件的索引节,大小和最后修改的时间的Hash值)
1、ETag的值服务器端对文件的索引节,大小和最后的修改的事件进行Hash后得到的。
复制代码1.get数据是存放在url之后,以?分割url和传输数据,参数之间以&相连; post方法是把提交的数据放在http包的Body中
2.get提交的数据大小有限制,(因为浏览器对url的长度有限制),post的方法提交的数据没有限制
3.get需要request.queryString来获取变量的值,而post方式通过request.from来获取变量的值
4.get的方法提交数据,会带来安全问题,比如登录一个页面,通过get的方式提交数据,用户名和密码就会出现在url上
复制代码1.超文本的传输协议,是用于从万维网服务器超文本传输到本地资源的传输协议
2.基于TCP/IP通信协议来传递数据(HTML,图片资源)
3.基于运用层的面向对象的协议,由于其简洁、快速的方法、适用于分布式超媒体信息系统
4.http请求信息request:
请求行(request line)、请求头部(header),空行和请求数据四部分构成
请求行,用来说明请求类型,要访问的资源以及所使用的HTTP版本.
请求头部,用来说明服务器要使用的附加信息
空行,请求头部后面的空行是必须的
请求数据也叫主体,可以添加任意的其他数据。
5.http相应信息Response
状态行、消息报头、空行和响应正文
状态行,由HTTP协议版本号, 状态码, 状态消息 三部分组成
消息报头,用来说明客户端要使用的一些附加信息
空行,消息报头后面的空行是必须的
响应正文,服务器返回给客户端的文本信息。
复制代码https:是以安全为目标的HTTP通道,简单讲是HTTP的安全版本,通过SSL加密
http:超文本传输协议。是一个客服端和服务器端请求和应答的标准(tcp),使浏览器更加高效,使网络传输减少
复制代码长连接:HTTP1.0需要使用keep-alive参数来告知服务器建立一个长连接,而HTP1.1默认支持长连接
节约宽带:HTTP1.1支持只发送一个header信息(不带任何body信息)
host域(设置虚拟站点,也就是说,web server上的多个虚拟站点可以共享同一个ip端口):HTTP1.0没有host域
1.http2采用的二进制文本传输数据,而非http1文本格式,二进制在协议的解析和扩展更好
2.数据压缩:对信息头采用了HPACK进行压缩传输,节省了信息头带来的网络流量
3.多路复用:一个连接可以并发处理多个请求
4.服务器推送:我们对支持HTTP2.0的web server请求数据的时候,服务器会顺便把一些客户端需要的资源一起推送到客户端,免得客户端再次创建连接发送请求到服务器端获取。这种方式非常合适加载静态资源
复制代码1.web缓存就是存在于客户端与服务器之间的一个副本、当你第一个发出请求后,缓存根据请求保存输出内容的副本
2.缓存的好处
(1)减少不必要的请求
(2)降低服务器的压力,减少服务器的消耗
(3)降低网络延迟,加快页面打开速度(直接读取浏览器的数据)
复制代码1.sql注入原理:是将sql代码伪装到输入参数中,传递到服务器解析并执行的一种攻击手法。也就是说,
在一些对server端发起的请求参数中植入一些sql代码,server端在执行sql操作时,会拼接对应参数,
同时也将一些sql注入攻击的“sql”拼接起来,导致会执行一些预期之外的操作。
防范:1.对用户输入进行校验
2.不适用动态拼接sql
2.XSS(跨站脚本攻击):往web页面插入恶意的html标签或者js代码。
举例子:在论坛放置一个看是安全的链接,窃取cookie中的用户信息
防范:1.尽量采用post而不使用get提交表单
2.避免cookie中泄漏用户的隐式
3.CSRF(跨站请求伪装):通过伪装来自受信任用户的请求
举例子:黄轶老师的webapp音乐请求数据就是利用CSRF跨站请求伪装来获取QQ音乐的数据
防范:在客服端页面增加伪随机数,通过验证码
XSS和CSRF的区别:
1.XSS是获取信息,不需要提前知道其他用户页面的代码和数据包
2.CSRF代替用户完成指定的动作,需要知道其他页面的代码和数据包
复制代码1.尽可能的避开互联网有可能影响数据传输速度和稳定性的瓶颈和环节。使内容传输的更快更稳定。
2.关键技术:内容存储和分发技术中
3.基本原理:广泛采用各种缓存服务器,将这些缓存服务器分布到用户访问相对的地区或者网络中。当用户访问网络时利用全局负载技术
将用户的访问指向距离最近的缓存服务器,由缓存服务器直接相应用户的请求(全局负载技术)
复制代码客服端发c起请求连接服务器端s确认,服务器端也发起连接确认客服端确认。
第一次握手:客服端发送一个请求连接,服务器端只能确认自己可以接受客服端发送的报文段
第二次握手: 服务端向客服端发送一个链接,确认客服端收到自己发送的报文段
第三次握手: 服务器端确认客服端收到了自己发送的报文段
复制代码1.查询NDS(域名解析),获取域名对应的IP地址 查询浏览器缓存
2.浏览器与服务器建立tcp链接(三次握手)
3.浏览器向服务器发送http请求(请求和传输数据)
4.服务器接受到这个请求后,根据路经参数,经过后端的一些处理生成html代码返回给浏览器
5.浏览器拿到完整的html页面代码开始解析和渲染,如果遇到外部的css或者js,图片一样的步骤
6.浏览器根据拿到的资源对页面进行渲染,把一个完整的页面呈现出来
复制代码流程:解析html以及构建dom树 -> 构建render树 -> 布局render树 -> 绘制render树
概念:1.构建DOM树: 渲染引擎解析HTML文档,首先将标签转换成DOM树中的DOM node(包括js生成的标签)生成内容树
2.构建渲染树: 解析对应的css样式文件信息(包括js生成的样式和外部的css)
3.布局渲染树:从根节点递归调用,计算每一个元素的大小,位置等。给出每个节点所在的屏幕的精准位置
4.绘制渲染树:遍历渲染树,使用UI后端层来绘制每一个节点
重绘:当盒子的位置、大小以及其他属性,例如颜色、字体大小等到确定下来之后,浏览器便把这些颜色都按照各自的特性绘制一遍,将内容呈现在页面上
触发重绘的条件:改变元素外观属性。如:color,background-color等
重绘是指一个元素外观的改变所触发的浏览器行为,浏览器会根据元素的新属性重新绘制,使元素呈现新的外观
注意:table及其内部元素需要多次计算才能确定好其在渲染树中节点的属性值,比同等元素要多发时间,要尽量避免使用table布局
重排(重构/回流/reflow): 当渲染书中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建,这就是回流。
每个页面都需要一次回流,就是页面第一次渲染的时候
重排一定会影响重绘,但是重绘不一定会影响重排
复制代码1.浏览器预先加载css后,可以不必等待HTML加载完毕就可以渲染页面了
2.其实HTML渲染并不会等到完全加载完在渲染页面,而是一边解析DOM一边渲染。
3.js写在尾部,主要是因为js主要扮演事件处理的功能,一方面很多操作是在页面渲染后才执行的。另一方面可以节省加载时间,使页面能够更加的加载,提高用户的良好体验
但是随着JS技术的发展,JS也开始承担页面渲染的工作。比如我们的UI其实可以分被对待,把渲染页面的js放在前面,时间处理的js放在后面
复制代码1.indexBD: 是h5的本地存储库,把一些数据存储到浏览器中,没网络,浏览器可以从这里读取数据,离线运用。5m
2.Cookie: 通过浏览器记录信息确认用户身份,最大4kb,这也就限制了传输的数据,请求的性能会受到影响
3.Session: 服务器端使用的一种记录客户状态的机制(session_id存在set_cookie发送到客服端,保存为cookie)
4.localStroage: h5的本地存储,数据永久保存在客服端
1、cookie,sessionStorage,localStorage是存放在客户端,session对象数据是存放在服务器上 实际上浏览器和服务器之间仅需传递session id即可,服务器根据session-id找到对应的用户session对象 session存储数据更安全一些,一般存放用户信息,浏览器只适合存储一般的数据 2、cookie数据始终在同源的http请求中携带,在浏览器和服务器来回传递,里面存放着session-id sessionStorage,localStorage仅在本地保存 3、大小限制区别,cookie数据不超过4kb,localStorage在谷歌浏览中2.6MB 4、数据有效期不同,cookie在设置的(服务器设置)有效期内有效,不管窗口和浏览器关闭 sessionStorage仅在当前浏览器窗口关闭前有效,关闭即销毁(临时存储) localStorage始终有效
SessionStorage和localStorage区别: 1.sessionStorage用于本地存储一个会话(session)中的数据,这些数据只有在用一个会话的页面中才能被访问(也就是说在第一次通信过程中) 并且在会话结束后数据也随之销毁,不是一个持久的本地存储,会话级别的储存 2.localStorage用于持久化的本地存储,除非主动删除数据,否则不会过期
复制代码1、token就是令牌,比如你授权(登录)一个程序时,他就是个依据,判断你是否已经授权该软件(最好的身份认证,安全性好,且是唯一的)
用户身份的验证方式
2、cookie是写在客户端一个txt文件,里面包括登录信息之类的,这样你下次在登录某个网站,就会自动调用cookie自动登录用户名
服务器生成,发送到浏览器、浏览器保存,下次请求再次发送给服务器(存放着登录信息)
3、session是一类用来客户端和服务器之间保存状态的解决方案,会话完成被销毁(代表的就是服务器和客户端的一次会话过程)
cookie中存放着sessionID,请求会发送这个id。sesion因为request对象而产生。
复制代码 1、用户通过用户名和密码发送请求
2、服务器端验证
3、服务器端返回一个带签名的token,给客户端
4、客户端储存token,并且每次用于发送请求
5、服务器验证token并且返回数据
每一次请求都需要token
复制代码 1、cookie数据存放在客户的浏览器上,session数据放在服务器上。
2、cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗考虑到安全应当使用session。
3、session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能考虑到减轻服务器性能方面,应当使用COOKIE。
4、单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。
复制代码 1、session认证只是把简单的User的信息存储Session里面,sessionID不可预测,一种认证手段。只存在服务端,不能共享到其他的网站和第三方App
2、token是oAuth Token,提供的是认证和授权,认证针对用户,授权是针对App,目的就是让某APP有权访问某用户的的信息。Token是唯一的,
token不能转移到其他的App,也不能转到其他用户上。(适用于App)
3、session的状态是存在服务器端的,客户端只存在session id, Token状态是存储在客户端的
复制代码 1、数量和长度的限制。每个特定的域名下最多生成20个cookie(chorme和safari没有限制)
2、安全性问题。
一、观察者模式:juejin.cn/post/684490… juejin.cn/post/684490… 在软件开发设计中是一个对象(subject),维护一系列依赖他的对象(observer),当任何状态发生改变自动通知他们。强依赖关系 简单理解:数据发生改变时,对应的处理函数就会自动执行。一个Subjet,用来维护Observers,为某些event来通知(notify)观察者
二、发布-订阅者 有一个信息中介,过滤 耦合性低 它定义了一种一对多的关系,可以使多个观察者对象对一个主题对象进行监听,当这个主题对象发生改变时,依赖的所有对象都会被通知到。
复制代码1.冒泡排序:重复走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把它们交换过来。
实现过程:1.比较相邻的元素。如果第一个比第二个大,就交换他们两个
2.对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数
3.针对所有的元素重复以上的步骤,除了最后一个
4.重复步骤1-3,直到排序完成。
2.选择排序:首先在未排序序列中找到最小值,放在排序序列的起始位置,然后,在从剩下未排序元素中继续寻找最小值,然后放在与排序序列的末尾
实现过程:
3.插入排序:构建有序序列,对于未排序数据,在已排序序列中冲后向前扫描,找到相应位置并插入
实现过程:1.从第一个元素开始,该元素可以认为已经被排序
2.取出下一个元素,在已排序的元素序列中冲后向前扫描
3.如果该元素(以排序)大于新元素,将元素向后移一位
4.在取出一个元素,比较之前的,直到找到自己合适的位置
4.桶排序:将数据分布到有限数量的桶里,每个桶在分别排序
1.快速排序:快速排序使用分治法把一个串(list)分为两个子串(sub-lists).具体算法实现
实现过程:1.从数组中挑出一个元素,成为一个基准
2.重新排列数组,所有元素比基准小的摆在基准前面,所有元素比基准大的摆在基准后面(相同的可以摆在一边)
这个分区退出之后,该基准就处于数列的中间位置。成为分区操作。
3.递归的把小于基准值的子数列和大于基准值元素的子数列排序
算法实现: function quickSort (arr) {
if (arr.length <=1) {return arr}
var destIndex=Math.floor(arr.length/2)
var left=[], right=[];
var dest=arr.splice(destIndex,1)[0];
for (var i=0;i<arr.length;i++){
if (arr[i]<dest) {
left.push(arr[i])
} else {
right.push(arr[i]) }
return quickSort(left).concat([dest],quickSort(right)
2.堆排序:利用对这种数据结构所涉及的一种排序算法,堆积是一个近乎完全二叉树的结构,并同时满足堆积的性质:即子节点的键值或索引总是小于(或大于)它的父节点。
实现过程:1.
复制代码1.双重循环
2.indexOf
3.数组排序去重 最快你Olong
复制代码判断回文字符串:(递归的思想)
1.字符串分隔,倒转,聚合[...obj].reverse().join('')
2.字符串头部和尾部,逐次向中间检测
实现:function isPalindrome(line) {
line +='';
for (var i=0,j=line.length-1;i<j;i++,j--) {
if (line.chartAt(i) !==line.chartAt(j) {
return false
}
3.递归
复制代码 二分查找可以解决已排序数组的查找问题,即只要数组中包含T(要查找的值),那么通过不断的缩小包含T的数据范围,就可以最终要找到的数
(1) 一开始,数据范围覆盖整个数组。
(2) 将数组的中间项与T进行比较,如果T比数组的中间项小,则到数组的前半部分继续查找,反之,则到数组的后半部分继续查找。
(3) 就这样,每次查找都可以排除一半元素,相当于范围缩小一半。这样反复比较,反复缩小范围,最终会在数组中找到T
代码实现:function binarySearch (data, dest, start, end){
var end=end || data.length-1;
var start=start || 0;
var m=Math.floor((start+end)/2);
if (dest<data[m]){
return binarySearch(data, dest, 0, m-1)
} else {
return binarySearch(data, dest, m+1, end)
}}
return false
复制代码一句话概括:1.bind()返回一个新函数,并不会立即执行。
2.bind的第一个参数将作为他运行时的this,之后的一系列参数将会在传递的实参前传入作为他的参数
3.bind返回函数作为构造函数,就是可以new的,bind时指定的this值就会消失,但传入的参数依然生效
复制代码Function.prototype.bind=function (obj, arg) {
var arg=Array.prototype.slice.call(arguments, 1);
var context=this;
var bound=function (newArg) {
arg=arg.concat(Array.prototype.slice.call(newArg);
return context.apply(obj, arg)
}
var F=function () {} // 在new一个bind会生成新函数,必须的条件就是要继承原函数的原型,因此用到寄生继承来完成我们的过程
F.prototype=context.prototype;
bound.prototype=new F();
return bound;
}
复制代码ajax的原理:相当于在用户和服务器之间加一个中间层(ajax引擎),使用户操作与服务器响应异步化。
优点:在不刷新整个页面的前提下与服务器通信维护数据。不会导致页面的重载
可以把前端服务器的任务转嫁到客服端来处理,减轻服务器负担,节省宽带
劣势:不支持back。对搜索引擎的支持比较弱;不容易调试
怎么解决呢?通过location.hash值来解决Ajax过程中导致的浏览器前进后退按键失效,
解决以前被人常遇到的重复加载的问题。主要比较前后的hash值,看其是否相等,在判断是否触发ajax
复制代码function getData(url) {
var xhr=new XMLHttpRequest(); // 创建一个对象,创建一个异步调用的对象
xhr.open('get', url, true) // 设置一个http请求,设置请求的方式,url以及验证身份
xhr.send() //发送一个http请求
xhr.onreadystatechange=function () { //设置一个http请求状态的函数
if (xhr.readyState==4 && xhr.status==200) {
console.log(xhr.responseText) // 获取异步调用返回的数据
}
}
}
Promise(getData(url)).resolve(data=> data)
AJAX状态码:0 - (未初始化)还没有调用send()方法
1 - (载入)已调用send方法,正在发送请求
2 - (载入完成呢)send()方法执行完成
3 - (交互)正在解析相应内容
4 - (完成)响应内容解析完成,可以在客户端调用了
```
#### 三、函数节流(throttle)
```
function throttle (func, wait) {
var timeout;
var previous=0;
return function () {
context=this;
args=arguments;
if (!timeout) {
timeout=setTimeout(()=> {
timeout=null;
func.apply(context,args)
}, wait);
}
}
}
}
```
#### 四、函数防抖(dobounce)
```
function debounce (func, wait) {
var timeout;
return function() {
var context=this;
var args=arguments;
clearTimeout(timeout);
timeout=setTimeout(()=> {
func.apply(context,args)
}, wait);
}
}
```
#### 五、实现一个函数clone,可以对JavaScript中的5种主要的数据类型(包括Number、String、Object、Array、Boolean)进行值复制
```
Object.prototype.clone=function() {
var newObject=this.constructor===Array ? [] : {} //对象的深拷贝 获取对应的构造函数 [] 或者 {}
for (let e in this) { //遍历对象的属性 in this[e]
newObject[e]=typeof this[e]==='object' ? this[e].clone() : this[e] //对象中的属性如果还是对象 那就继续递归 否则就返回基本的数据类型
}
return newObject
}
```
#### 六、实现一个简单的Promise https://juejin.cn/post/6844903625769091079
```
class Promise {
constructor (executor) { // executor里面有两个参数,一个叫resolve(成功),一个叫reject(失败)。
this.status='pending',
this.value=undefined;
this.reason=undefined;
// 成功存放的数组
this.onResolvedCallbacks=[];
// 失败存放法数组
this.onRejectedCallbacks=[];
let resolve=(value)=> {
if (this.status=='pending') {
this.status='resolve';
this.value=value;
this.onResolvedCallbacks.forEach(fn=> fn())
}
}
let reject=(reason)=> {
if (this.status=='pending') {
this.status='reject';
this.reason=reason;
this.onRejectedCallbacks.forEach(fn=> fn())
}
}
try{
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
then (onFullFilled,onRejected) {
if (this.status=='resolved') {
onFullFilled(this.value)
}
if (this.status=='rejectd') {
onRejected(this.reason);
}
if (this.status=='pending') {
this.onResolvedCallbacks.push(()=>{
onFullFilled(this.value);
})
this.onRejectedCallbacks.push(()=> {
onRejected(this.reason);
})
}
}
}
const p=new Promise((resolve, reject)=> {
setTimeout(()=> {
resolve('hello world')
}, 1000);
})
p.then((data)=>{
console.log(data)
},(err)=>{
console.log(err);
})
```
#### 七、发布订阅者模式(观察者模式)
```
var event={}; // 发布者
event.clientList=[] //发布者的缓存列表
event.listen=function (fn) { // 增加订阅者函数
this.clientList.push(fn)
}
event.trigger=function () { // 发布信息
for (var i=0;i<this.clientList.length;i++) {
var fn=this.clientList[i];
fn.apply(this, arguments);
}
}
event.listen (function(time) {
console.log('正式上班时间为:' +time)
})
event.trigger ('2018/7')
```
#### 八、手动写一个node服务器
```
const http=require('http');
const fs=require('fs');
const server=http.createServer((req,res)=> {
if (reu.url=='/') {
const indexFile=fs.createReadStream('./index.html')
req.writeHead(200,{'context-Type':'text/html;charset=utf8})
indexFile.pipe(res)
}
server.listen(8080)
```
如果本文对你有帮助,就留个关注点个赞支持下吧,你的「关注」和「赞」是我创作的动力。
原文链接:https://juejin.cn/post/6844903976693940231
日,我的一位同事向我寻求建议,她打算为自己构建一个博客。于是,我对静态网站生成器和博客引擎进行了一番研究,发现 Hugo 是一个很不错的选择。但是,我的同事还有一些特殊要求,比如,她想要一个自定义的博客网址和 CSS 主题。尽管这些 Hugo 都可以实现,但我并不打算花时间来学习它。我想自己创建一个简单的静态网站生成器,以便我的同事在她已经准备好的 HTML 中编写博客文章。
这个静态网站生成器的代码大约 100 行,非常简洁。它提供了详细代码和示例博客 。众所周知,GitLab 提供静态页面的免费托管服务,还带有 CI/CD 功能,它允许你在部署之前编译页面。
以下教程将带你使用 Node.js 设置自己的静态网站生成器,Node.js 的版本需要 “>=8.11.x”。
npm init npm i --save-exact bluebird chokidar fs-extra mustache mkdir src mkdir public
首先,设置项目:
开始之前,我们需要弄清楚一个问题:为什么需要静态网站生成器?因为某些情况并不需要静态网站生成器。假如你的博客访问量很小,你只需简单地手工创建 HTML 页面并发布它们即可。实际上,在服务器编程兴起之前,在很长时间内这就是大多数 Web 的发布方式。但是,一旦页面和内容增加,对这些页面中的通用部分(例如页面底部)进行更改将会变得非常重复和乏味。因此,我们开始寻找一种更加理想的方法,尝试使用某种简单的模板引擎来分离常见内容,然后在特定的地方插入所需的内容。
开始研究模板引擎之前,先设置我们的网站。我们需要在项目根目录下创建 2 个文件夹 :
我们的目标是将 src 目录的内容复制到 public 目录中。在项目根目录下创建 index.js 文件,其内容如下:
const Promise=require("bluebird"); const fse=require("fs-extra"); Promise.resolve().then(async ()=> { await main(); }); const main=async()=> { await generateSite(); }; const generateSite=async()=> { await copyAssets(); }; const copyAssets=async()=> { await fse.emptyDir("public"); await fse.copy("src", "public"); };
执行命令 node index.js,即可启动该脚本。
祝贺你!此刻,你已荣升为一名后端开发人员。
接下来,我们将添加文件监视器,src 文件夹中的内容一旦发生更改就将重新生成网站。该博客总共包含 500-1000 个文件,我们可以在任何变化发生时重新生成整个网站:
const chokidar=require("chokidar"); const main=async()=> { await generateSite(); watchFiles(); }; const watchFiles=()=> { const watcher=chokidar.watch( [ "src" ], { ignored: /(^|[/\])../, // chokidar will watch folders recursively ignoreInitial: false, persistent: true } ); watcher.on("change", async path=> { console.log("changed " + path + ", recompiling"); await generateSite(); }); // catch ctrl+c event and exit normally process.on("SIGINT", function() { watcher.close(); }); };
上面的代码清楚地说明了为什么初始版本有一个名为 generateSite 的函数。现在执行命令 node index.js 启动我们的静态网站生成器,如果在 src 目录中编辑任何文件,public 都会发生变化。此时,我们还将添加一个环境变量来区分开发和生产模式。在开发模式中,我们将关注更改情况并重新生成网站,而在生产模式中,我们只需重新生成:
const env=process.env.NODE_ENV || "dev"; const main=async ()=> { console.log("Running app in " + env); await generateSite(); if (env==="dev") { watchFiles(); } };
我们可以执行命令 export NODE_ENV=prod || set NODE_ENV=prod && node index.js 来运行以上代码。请注意,观察源目录的更改和重新编译并不是每次都必须的,你可以跳过此步骤,只需在每次进行更改时运行脚本即可。
至此,差不多完成了!现在来说说模板。我们将使用 Mustache.js 模板,它非常简单易用,并且我们的需求并不复杂。创建一个文件夹 src/partials,用来存放网站的公共部分。然后稍微修改我们的网站结构,保证所有页面都存放在 src/pages 目录中。接下来加载页面并使用 Mustache 渲染:
const fs=require("fs"); const generateSite=async ()=> { await copyAssets(); await buildContent(); }; const buildContent=async ()=> { const pages=await compilePages(); await writePages(pages); }; const compilePages=async ()=> { const partials=await loadPartials(); const result={}; const pagesDir=path.join("src", "pages"); const fileNames=await fs.readdirAsync(pagesDir); for (const fileName of fileNames) { const name=path.parse(fileName).name; const fileContent=await fs.readFileAsync(path.join(pagesDir, fileName)); result[name]=Mustache.render(fileContent.toString(), {}, partials); } return result; }; const loadPartials=async ()=> { const result={}; const partialsDir=path.join("src", "partials"); const fileNames=await fs.readdirAsync(partialsDir); for (const fileName of fileNames) { const name=path.parse(fileName).name; const content=await fs.readFileAsync(path.join(partialsDir, fileName)); result[name]=content.toString(); } return result; }; const writePages=async pages=> { for (const page of Object.keys(pages)) { await fs.writeFileAsync(path.join("public", page + ".html"), pages[page]); } };
想要了解最终版本,请查看 Software Dawg 项目(https://gitlab.com/wheresvic/software-dawg)。它与本教程有一些细微差别:
此外,你还可以安装 browser-sync 软件包,然后通过命令 npm run live-reload 运行它,如此一来,只要有任何更改发生浏览器就会自动刷新。请注意,由于任何更改都将重新生成整个网站,因此并不适用于 Windows。
GitLab 提供静态网站免费托管,只需一个 .gitlab-ci.yml 配置文件即可。真正令人难以置信之处在于,你可以自定义构建过程,这意味着在该例中,我们可以在部署之前生成网站!有关此功能的详细信息,请参见https://about.gitlab.com/features/pages/。
本教程到此结束,我的同事对此非常满意,该方案非常灵活,它允许她根据自己的喜好进行自定义,也希望对你有所助益!
原文:https://smalldata.tech/blog/2018/08/16/building-a-simple-static-site-generator-using-node-js
作者简介:Victor Parmar,是一位全栈工程师,热爱旅行,热爱 DIY。
译者:安翔,责编:屠敏
者 | L的存在
来源 | 我是程序员小贱(ID:Lanj1995Q)
说到后端开发,难免会遇到各种所谓高大上的「关键词 」,对于我们应届生小白,难免会觉得比较陌生,因为在学校确实比较少遇见这些所谓高大上的东西,那么今天就带着学习的态度和大家分享这些看似可以装逼可以飞的带逼格的关键词吧。
大纲
在学校里的项目中,一个 Web 系统可能咋们一个人就搞定,因为几乎不考虑并发量,性能咋样,所谓「过得去 」足矣,但是为了面试考虑,我们又不得不找点类似秒杀系统作为我们简历的支撑项目(即使已经烂大街)。那么先问你第一个问题,为什么就采用了分布式的方案落地这个项目?
当一个人或者几十个使用你的系统,哎呀我去,请求秒回,效果倍棒,于是乎简历砰砰写上却多么牛X,当面试官就会问你你这项目做了啥,测试过没,并发量如何,性能如何?你就…..
当访问系统的用户越来越多,可是我们的系统资源有限,所以需要更多的 CPU 和内存去处理用户的计算请求,当然也就要求更大的网络带宽去处理数据的传输,也需要更多的磁盘空间存储数据。
资源不够,消耗过度,服务器崩溃,系统也就不干活了,那么在这样的情况怎么处理?
垂直伸缩
纵向生长。通过提升单台服务器的计算处理能力来抵抗更大的请求访问量。比如使用更快频率的CPU,更快的网卡,塞更多的磁盘等。
其实这样的处理方式在电信,银行等企业比较常见,让摩托车变为小汽车,更强大的计算机,处理能力也就越强,但是对于运维而言也就越来越复杂。那真的就这样花钱买设备就完事了?
当然不,单台服务器的计算处理能力是有限的,而且也会严重受到计算机硬件水平的制约。
水平伸缩
一台机器处理不过来,我就用多台廉价的机器合并同时处理,人多力量大嘛,通过多台服务器构成分布式集群从而提升系统的整体处理能力。这里说到了分布式,那我们看看分布式的成长过程
记住一句话:系统的技术架构是需求所驱动
最初的单体系统,只需要部分用户访问:
单体结构
做系统的原因当然是有需求,有价值,可赚钱。随着使用系统的用户越来越多,这时候关注的人越来越多,单台服务器扛不住了,关注的人觉得响应真慢,没啥意思,就开始吐槽,但是这一吐槽,导致用户更多,毕竟大家都爱吃瓜。
这样下去不得不进行系统的升级,将数据库和应用分离。
数据库应用分离
这样子,咋们将数据库和应用程序分离后,部署在不同的服务器中,从1台服务器变为多台服务器,处理响应更快,内容也够干,访问的用户呈指数增长,这多台服务器都有点扛不住了,怎么办?
加一个缓存吧,我们不每次从数据库中读取数据,而将应用程序需要的数据暂存在缓冲中。缓存呢,又分为本地缓存和分布式的缓存。分布式缓存,顾名思义,使用多台服务器构成集群,存储更多的数据并提供缓存服务,从而提升缓存的能力。
加了缓存哪些好处?
应用程序不再直接访问数据库,提升访问效率。因为缓存内容在内存中,不用每次连接存放磁盘中的数据库。
系统越来越火,于是考虑将应用服务器也作为集群。
集群
干啥啥不行,缓存第一名。不吹牛,缓存应用在计算机的各个角落。缓存可说是软件技术中的的杀手锏,无论是程序代码使用buffer,还是网络架构中使用缓存,虚拟机也会使用大量的缓存。
其实最初在CPU中也就开始使用缓存。缓存分为两种,一种是通读缓存,一种是旁路缓存
通读缓存
假设当前应用程序获取数据,如果数据存在于通读缓存中就直接返回。如果不存在于通读缓存,那么就访问数据源,同时将数据存放于缓存中。
下次访问就直接从缓存直接获取。比较常见的为CDN和反向代理。
通读缓存
CDN
CDN称为内容分发网络。想象我们京东购物的时候,假设我们在成都,如果买的东西在成都仓库有就直接给我们寄送过来,可能半天就到了,用户体验也非常好,就不用从北京再寄过来。
同样的道理,用户就可以近距离获得自己需要的数据,既提高了响应速度,又节约了网络带宽和服务器资源。
旁路缓存
应用程序需要自己从数据源读取数据,然后将这个数据写入到旁路缓存中。这样,下次应用程序需要数据的时候,就可以通过旁路缓存直接获得数据了。
旁路缓存
缓存的好处
因为大部分缓存的数据存储在内存中,相比于硬盘或者从网络中获取效率更高,响应时间更快,性能更好;
通过 CDN 等通读缓存可以降低服务器的负载能力;
因为缓存通常会记录计算结果。如果缓存命中直接返回,否则需要进行大量的运算。所以使用缓存也减少了CPU 的计算小号,加快处理速度。
缓存缺点
我们缓存的数据来自源数据,如果源数据被修改了,俺么缓存数据很肯能也是被修改过的,成为脏数据,所以怎么办?
过期失效
在每次写入缓存数据的时候标记失效时间,读取数据的时候检查数据是否失效,如果失效了就重新从数据源获取数据。
失效通知
应用程序在更新数据源的时候,通知清除缓存中的数据。
是不是数据使用缓存都有意义呢?
非也,通常放入缓存中的数据都是带有热点的数据,比如当日热卖商品,或者热门吃瓜新闻,这样将数据存放在缓存中,会被多次读取,从而缓存的命中率也会比较高。
在前面中,通过缓存实际上很多时候是解决了读的问题,加快了读取数据的能力。因为缓存通常很难保证数据的持久性和一致性,所以我们通常不会将数据直接写入缓存中,而是写入 RDBMAS 等数据中,那如何提升系统的写操作性能呢?
此时假设两个系统分别为A,B,其中A系统依赖B系统,两者通信采用远程调用的方式,此时如果B系统出故障,很可能引起A系统出故障。
从而不得不单独进行升级,怎么办?
使用消息队列的异步架构,也成为事件驱动模型。
异步相对于同步而言,同步通常是当应用程序调用服务的时候,不得不阻塞等待服务期完成,此时CPU空闲比较浪费,直到返回服务结果后才会继续执行。
同步
举个例子,小蓝今天想在系统中加一个发邮件的功能,通过SMTP和远程服务器通信,但是远程服务器有很多邮件需要等待发送呢,当前邮件就可能等待比较长时间才能发送成功,发送成功后反馈与应用程序。
这个过程中,远程服务器发送邮件的时候,应用程序就阻塞,准确的说是执行应用程序的线程阻塞。
这样阻塞带来什么问题“?
不能释放占用的系统资源,导致系统资源不足,影响系统性能
无法快速给用户响应结果
但是在实际情况中,我们发送邮件,并不需要得到发送结果。比如用户注册,发送账号激活邮件,无论邮件是否发送成功都会收到"返回邮件已经发送,请查收邮件确认激活",怎样才能让应用程序不阻塞?
消息队列的异步模型
此时就比较清晰了,调用者将消息发送给消息队列直接返回,应用程序收到返回以后继续执行,快读响应用户释放资源。
有专门的消费队列程序从中消息队列取出数据并进行消费。如果远程服务出现故障,只会传递给消费者程序而不会影响到应用程序。
消息队列
消息队列模型中通常有三个角色,分别为生产者,消息队列和消费者。生产者产生数据封装为消息发送给消息队列,专门的消费程序从消息队列中取出数据,消费数据。
在我看来,消息队列主要是缓冲消息,等待消费者消费。其中消费的方式分为两种:
点对点
对生产者多消费者的情况。一个消息被一个消费者消费
多生产消费
上述的发邮件例子就是典型的点对点模式。互不干扰,其中某个服务出现问题不会印象到全局。
订阅模式
开发人员在消息队列中设置主题,生产者往相应的主题发送数据,消费者从对应的主题中消费数据,每个消费者按照自己业务逻辑分别进行计算
订阅模式
这个比较好理解,比如在用户注册的时候,我们将注册信息放入主题用户中,消费者订阅了这个主题,可能有构造短信消息的消费者,也有推广产品的消费者,都可以根据自己业务逻辑进行数据处理。
用户注册案例
使用异步模型的优点
快速响应
不在需要等待。生产者将数据发送消息队列后,可继续往下执行,不虚等待耗时的消费处理
削峰填谷(需要修改)
互联网产品会在不同的场景其并发请求量不同。互联网应用的访问压力随时都在变化,系统的访问高峰和低谷的并发压力可能也有非常大的差距。
如果按照压力最大的情况部署服务器集群,那么服务器在绝大部分时间内都处于闲置状态。
但利用消息队列,我们可以将需要处理的消息放入消息队列,而消费者可以控制消费速度,因此能够降低系统访问高峰时压力,而在访问低谷的时候还可以继续消费消息队列中未处理的消息,保持系统的资源利用率。
降低耦合
如果调用是同步,如果调用是同步的,那么意味着调用者和被调用者必然存在依赖,一方面是代码上的依赖,应用程序需要依赖发送邮件相关的代码,如果需要修改发送邮件的代码,就必须修改应用程序,而且如果要增加新的功能
那么目前主要的消息队列有哪些,其有缺点是什么?(好好记下这个高频题目啦)
一台机器扛不住了,需要多台机器帮忙,既然使用多台机器,就希望不要把压力都给一台机器,所以需要一种或者多种策略分散高并发的计算压力,从而引入负载均衡,那么到底是如何分发到不同的服务器的呢?
最初实现负载均衡采取的方案很直接,直接上硬件,当然也就比较贵,互联网的普及,和各位科学家的无私奉献,各个企业开始部署自己的方案,从而出现负载均衡服务器。
HTTP重定向负载均衡
也属于比较直接,当HTTP请求叨叨负载均衡服务器后,使用一套负载均衡算法计算到后端服务器的地址,然后将新的地址给用户浏览器,浏览器收到重定向响应后发送请求到新的应用服务器从而实现负载均衡,如下图所示:
HTTP重定向负载均衡
优点:
简单,如果是java开发工程师,只需要servlet中几句代码即可
缺点:
加大请求的工作量。第一次请求给负载均衡服务器,第二次请求给应用服务器
因为要先计算到应用服务器的 IP 地址,所以 IP 地址可能暴露在公网,既然暴露在了公网还有什么安全可言
DNS负载均衡
了解计算机网络的你应该很清楚如何获取 IP 地址,其中比较常见的就是 DNS 解析获取 IP 地址。
用户通过浏览器发起HTTP请求的时候,DNS 通过对域名进行即系得到 IP 地址,用户委托协议栈的 IP 地址简历 HTTP 连接访问真正的服务器。这样不同的用户进行域名解析将会获取不同的IP地址从而实现负载均衡。
DNS负载均衡
乍一看,和HTTP重定向的方案不是很相似吗而且还有 DNS 解析这一步骤,也会解析出 IP 地址,不一样的暴露?
每次都需要解析吗,当然不,通常本机就会有缓存,在实际的工程项目中通常是怎么样的呢?
通过 DNS 解析获取负载均衡集群某台服务器的地址;
负载均衡服务器再一次获取某台应用服务器,这样子就不会将应用服务器的 IP 地址暴露在官网了。
反向代理负载均衡
这里典型的就是Nginx提供的反向代理和负载均衡功能。用户的请求直接叨叨反向代理服务器,服务器先看本地是缓存过,有直接返回,没有则发送给后台的应用服务器处理。
反向代理负载均衡
IP负载均衡
上面一种方案是基于应用层的,IP很明显是从网络层进行负载均衡。TCP./IP协议栈是需要上下层结合的方式达到目标,当请求到达网络层的黑猴。
负载均衡服务器对数据包中的IP地址进行转换,从而发送给应用服务器。
IP负载均衡
注意,这种方案通常属于内核级别,如果数据比较小还好,但是大部分情况是图片等资源文件,这样负载均衡服务器会出现响应或者请求过大所带来的瓶颈。
数据链路负载均衡
它可以解决因为数据量他打而导致负载均衡服务器带宽不足这个问题。怎么实现的呢。它不修改数据包的IP地址,而是更改mac地址。
应用服务器和负载均衡服务器使用相同的虚拟IP。
数据链路负载均衡
以上介绍了几种负载均衡的方式,但是很重要的负载均衡算法却没有设计,其中包含了轮询,随机,最少连接,下面分别对此进行介绍。
公司存在的价值在于流量,流量需要数据,可想而知数据的存储,数据的高可用可说是公司的灵魂。那么改善数据的存储都有哪些手段或方法呢?
数据主从复制
主从复制比较好理解,需要使用两个数据库存储一样的数据。其原理为当应用程序A发送更新命令到主服务器的时候,数据库会将这条命令同步记录到Binlog中。
然后其它线程会从Binlog中读取并通过远程通讯的方式复制到另外服务器。服务器收到这更新日志后加入到自己Relay Log中,然后SQL执行线程从Relay Log中读取次日志并在本地数据库执行一遍,从而实现主从数据库同样的数据。
主从复制
主从复制可以方便进行读写分离,可以使用一主多从的方式保证高可用,如果从数据库A挂了,可以将读操作迁移到从数据库完成高可用。
但是如果主数据库挂了咋搞,那就Mysql的主主复制。可是不管上面说的那种方式都不是提升它的存储能力,这就需要进行数据库的分片了。
数据库分片
将一张表分成若干片,其中每一片都包含一部分行记录,然后将每一片存储在不同的服务器中,这样就实现一张表存放在多台服务器中,哪都有哪些分片存储的方案?
最开始使用"硬编码"的方式,此方式从字面上可以理解为直接在代码中指定。假定表为用户表,通过ID的奇偶存放在不同的服务器上,如下图:
这种方式的缺点很明显,当需要增加服务器的时候,就需要改动代码,这就不友好了。比较常见的数据库分片算法是通过余数Hash算法,根据主键ID和服务器的数量取模,根据余数确定服务器。
我们使用谷歌浏览器的时候,输入搜索关键字,就会出现搜索到多少条结果,用时多少,它是如何做到在如此短的时间完成这么大数据量的搜索。
先来想第一个问题,全世界这么多网页在哪里?
互联网的存在让你和我隔着屏幕都知道你多帅。当然,每个网页中都会存在很多其他网页的超链接,这样构成了庞大的网络。
对于搜索引擎而言,目标为解析这些网页获取超链接,下载链接内容(过滤),具体一些说。
将URL存放于池子中,从池子中取出URL模拟请求,下载对应的HTML,并存放于服务器上,解析HTML内容时如果有超链接URL,检测是否已经爬取过,如果没有暂存队列,后面再依次爬取。架构图如下:
爬虫常规方法
将获取的所有网页进行编号并得到网页集合。然后通过分词技术获得每一个单词,组成矩阵如下所示:
分词
就这样按照单词-文档的方式组织起来就叫做倒排索引了。
倒排索引
Google通过建立<单词,地址>这样的文档,主要搜索到单词就能定位到文档的地址列表,然后根据列表中文档的编号就展现文档信息从而实现快速的检索。
如果搜索出来结果很多,Google是如何能更精准的将我们需要的信息呈现给我们呢。它很明显有个排序功能,那是如何排序的?
Google使用了一种叫做"PageRank"的算法,通过计算每个网页的权重,并按照权重排序,权重高的自然就在显示在前面,那问题来了,为啥权重高的,排在前面的通常就是用户所需要的呢?这就得了解下pagerank算法了。
在pagerank中,如果网页A包含网页B说明A认可B,即投一票。如下图ABCD四个网页所示,箭头代表超链接的方向,比如A->B代表A网页包含B的超链接
pagerank
怎么计算的?
ABCD 初始值都为1,然后根据关系计算权重。比如此时B包含了AD两个网页,那么权重1被分为两个1/2分别给A和D,此时A包含BCD,那么此时A页面新的权重为1/2 + 1/3 + 1=11/6。
pagerank 值越受推荐,代表用户越想看到。基于每个网页的 pagerank 值对倒排索引中的文档列表排序,靠前者则是用户想看到的文档。
这种是因为超链接,引入权值的方式排序。还有其他诸如对于商品售卖次数排序或者电影点赞或评价分数排序。
还有通过关键字查找,希望找到和搜索词相关,这个时候可能就会采用词频TF进行排序,词频代表所查词和文档的相关程度。
词频TF
比如我们搜索"Java后端"出现的结果以"后端"的相关技术。
在大部分的应用中都会涉及到搜索引擎技术,技术庞大且复杂,希望各位老铁根据自身情况搜索相关所需学习,校招面试中不出现盲点即可。
技术的引进一定是想解决某个痛点。我不希望在一个系统中,一小点改动就影响到全局,希望各个功能模块拆分清晰,不管是测试还是运维都能节省更多的时间。
那么单体的架构出现了哪些问题?
代码分支管理困难
各个部门分别完成各自的任务,但是最后需要 merge 在一起成为整个系统,merge过程经历的人都知道,问题是真XX多。所以不再是996
新增功能麻烦
随着项目的效益越来越好,用户的需求也更多,招聘的人可能更多,对于新手来说上来是一脸懵逼的,老员工忙的要死,新员工成为了摸鱼专家
耗尽连接
用户的增多,每个应用都得和数据库连接,给数据库的连接造成太大的压力甚至耗尽连接
微服务
“微"-------微微一笑很倾城,微笑,微小。顾名思义,讲一个大的系统,拆分为一个个小的服务,分别对各个小服务进行管理。这样说感觉太不专业了,专业点
大应用拆分为小模块
小模块不属于集群中
通过远程调用的方式依赖各个独立的模块完成业务的处理
这些小的模块就叫做------微服务,整体也就是所谓的微服务架构。
既然拆分成了小服务,这么多小服务怎么协调成为一个问题,甚至都不知道怎么掉这个服务,所以在微服务的整体架构中出现了注册中心,谁需要调用使用提供的接口即可。如下图所示:
注册中心
从上图我们能知道主要是三个概念:
服务提供者
微服务的具体提供者,其他微服务通过接口调用即可
服务消费者
对应于服务提供者,按照提供者接口编程即可。这么轻松的嘛,当然很多细节。举个例子,注明的dubbo服务框架,服务接口通过Dubbo框架代理机制访问Dubbo客户端,客户端通过服务接口声明去注册中心查看有哪些服务器,并将这服务器列表给客户端。
客户端然后根据负载均衡策略选择其中一个服务器,通过远程调用的方式发送服务调用。那么使用微服务需要注重哪几点?
选择中的注意事项
不要拿工具硬上需求,结合业务也许会更佳!
高可用,意味着一台机器挂了没事,其他机器可以照常工作,用户体验一样倍棒,用户压根就不知道,卧槽,你居然升级了系统,我居然一点感受都没有。那么高可用总有个标准吧,是百分之80就行还是90?
一个系统突然不能访问的原因很多:
硬件故障
数据库宕机
磁盘孙欢
bug
光缆断了
可用性指标
通过多少个9来衡量,比如大宝系统可用性为4个9,意味着是99.99%,说明它的服务保证运行时间只有0.01不可用。
高可用涉及到技术成本和设备成本,不是说高可用值越高越好,而是根据具体工具具体场景而定,这里分享一些高可用策略。
冗余备份
任何一个服务都有备份,就反复我们都会对我们笔记本电脑相关文件进行备份一样,以防万一。即使一台服务器挂了,可以很快的切换以致于让用户不会觉得"这系统怎么这么渣"。
负载均衡
使用多台服务器分担一台服务器的压力,满足高并发的请求。怎么实现的呢,应用程序会有一个心跳检测/健康检查机制,如果发现不妥切换即可。上面提到的数据库主主复制也是高可用的方案之一,技术思想果然是相通的
负载均衡
限流降级
我们的目标不是没有蛀牙,而是希望整个系统不要挂掉。限流是对部分请求进行丢弃处理,保证大部分的用户可以正常的请求完成任务。
降级:
可以屏蔽部分当前看来不是很有用的任务。比如电商系统做秒杀活动的过程中,确认收货功能给予的压力挺大,暂时看来并不是核心任务,而且系统到期也会自动确认收货,所以暂时关闭,将系统的资源留给准备下单,放购物车的太太们
异地多活:
有时候我在想要是地震,火灾等自然灾害发生的时候,很多系统的数据怎么办啊。想多了撒,大些的系统多会在各个地方部署数据中心,采用异地多活的多机房策略。用户可以访问任何数据中心,那问题来了,用户的请求是如何到达不同的机房去的?
域名解析
公司中的数据财产,其重要程度不言而喻。系统的健壮性和安全性是保证系统持久运行的基础。不要因为数据泄露才去关注安全问题。
也许说到安全问题,首先想到的是“用户名密码”泄漏,数据库被脱裤导致数据泄露,hack直接拿到用户的敏感信息,所以我们通常有哪些手段或方法来尽全力的抵抗hack嘞,办法总比问题多嘛。
数据加解密
通过对用户密码,身份证等敏感数据加密是常用方法。加密方法通常分为:单向散列加密,对称加密和非对称加密。
所谓单向散列加密,主要体现在单向二字,意味着对明文加密后是不可以解密,你即使是给明文加密的加密者,也无法通过密文知道其明文是什么,即加密单向,不支持解密。说的这么绝对?这么无敌?
那只是理论上而已,常用的MD5算法就是大象散列加密,我们完全可以通过彩虹表等方式进行破解。怎么破解?
说个简单的道理,我们设置密码的时候,通常会使用生日?手机号?什么love?什么520?,这些组合是有限的,我们就可以建立一个比如生日和密文的映射表,然后通过XX彩虹表就可得到密码明文。
单向散列加密
所以,通常的情况,使用单向散列加密的时候需要加一点盐,这样一来,hack拿到密文,不知道盐,无法建立彩虹表,就更难还原明文。
应用场景:通常应用在用户密码加密。其加密和校验过程如下:
应用场景
我们通过上图来回顾一下一个网站的注册登录模块中的用户部分,用户注册需要输入用户名和密码,我们一般不会将裸露的将密码直接存放在数据库,不然被脱裤直接算裸奔。
所以,用户输入密码,应用服务器获得密码后,调用单向散列加密算法,将加密的密文存放于数据库,用户下一次登录的时候,用户依然会输入密码。
只是到了Web服务器后,Web服务器会对输入的密码再进行一次单向散列加密,然后和数据库中取出来的密文进行对比,如果相同,则用户的验证成功,通常这样的方式可以保证用户密码的安全性,当然如果加一点盐,会增加破解的难度。
对称加密
对称加密是通过一个加密算法和密钥,对一段明文进行加密后得到密文,然后使用相同的密钥和对应的解密算法进行解密得到明文。
对称加密
举个例子,我们不会将银行卡卡号,有效期等直接存储在数据库,而是会通过先加密,然后存储于数据库。
使用的时候必须对密文进行解密还原出明文。这个时候使用对称加密算法,存储的时候加密算法进行加密,使用的时候解密算法解密。
非对称加密
非对称加密是说使用一个加密算法和一个加密秘钥进行加密得到密文,但是在解密出明文的时候,其加解密密钥和加密密钥不同,通常加密密钥叫做公钥,解密密钥叫做私钥。
非对称加密
其实我们常用的 HTTPS 即是非对称加密的应用场景。用户在客户端进行通讯的时候,对数据使用的加密密钥和加密算法进行加密得到密文,到了服务端以后,使用解密密钥和算法进行解密得到明文。
但是非对称消耗的资源比较多,所以HTTPS不是每次请求响应都采用非对称加密,而是先利用非对称加密,在客户单和服务器之间交换一个对称加密的密钥,然后每次的请求响应再使用对称加密。
综上,使用费对称加密保证对称加密迷药的安全,使用对称加密密钥保证请求响应数据的安全。
HTTP攻击与防护
HTTP明文协议,咋们通过嗅探工具就可以清晰查看会话内容,这也是hack攻击门槛最低的方式。很常见的也就是SQL注入和XSS攻击。
SQL注入是攻击者在提交请求参数的时候,包含了恶意的SQL脚本:
SQL注入
server处理计算后向数据库提交的SQL如下:
Select id from users where username='Mike';
如果是恶意攻击,提交的HTTP请求如下:
http://www.a.com?username=Mike';drop table users;--
此时最终生成的SQL是:
Select id from users where username='Mike';drop table users;--';
查询完就直接给删除表了。怎么防护?
比较常用的解决方案是使用PrepareStaement预编译,先将SQL交给数据库生成执行计划,后面hack不管提交什么字符串也只能交给这个执行计划执行,不会生成新的SQL,也就不会被攻击啦。
XSS攻击
跨站点脚本攻击,攻击者通过构造恶意的浏览器脚本文件,使其在其他用户的浏览器运行进而进行攻击。
假设小A将含有恶意脚本的请求给360服务器,服务器将恶意的脚本存储在本地的数据库,当其他正常用户通过这个服务器浏览信息的时候,服务器就会读取数据库中含有恶意脚本的数据并呈现给用户,在用户正常使用浏览器的时候达到攻击的目的。
防御:
比较常见的是在入口处对危险的请求比如drop table等进行拦截,设置一个Web应用防火墙将危险隔离。
安全防御
其实在上面提到的分布式中就有涉及大数据相关知识。无外乎是数据量越来越大,我们如何尽可能使用较低的成本存储更多的数据,给公司企业带来更好的利润。上面说过分布式缓存,负载均衡等技术,其共同特点是如何抵抗高并发的压力,而这里的大数据技术主要谈论的是如何满足大规模的计算。
通过对数据的分析,进而发掘海量数据中的价值,这里的数据包含数据库数据,日志信息,用户行为数据等等。那么这么多不同类型的数据,怎么去存储呢?
分布式文件存储 HDFS 架构
如何将数以万计的服务器组成统一的文件存储系统?其中使用Namenode服务器作为控制块,负责元数据的管理(记录文件名,访问权限,数据存储地址等),而真正的文件存储在DataNode中。
Mapreduce
大量的数据存储下来的目的是通过相应的算法进行数据分析,获得通过深度学习/机器学习进行预测,从而获取有效的价值,这么大的文件,我们不可能将HDFS当做普通的文件,从文件中读取数据然后计算,这样子不知道算到何时何地。
大数据处理经典的处理框架即MapReduce,分为Map和Reduce两个阶段,其中一个Map过程是将每个服务器上启动Map进程,计算后输出一个
下面以WordCount统计所有数据中相同的词频数据为例,详细看看Map和Reduce的过程。
wordcoun计算过程
在这个例子中,通过对value中的1组成的列表,reduce对这些1进行求和操作从而得到每个单词的词频。代码实现如下:
public class WordCount {
// Mapper四个参数:第一个Object表示输入key的类型;第二个Text表示输入value的类型;第三个Text表示表示输出键的类型;第四个IntWritable表示输出值的类型。map这里的输出是指输出到reduce
public static class doMapper extends Mapper<Object, Text, Text, IntWritable> {
public static final IntWritable one=new IntWritable(1);//这里的IntWritable相当于Int类型
public static Text word=new Text;//Text相当于String类型
// map参数<keyIn key,valueIn value,Context context>,将处理后的数据写入context并传给reduce
protected void map(Object key, Text value, Context context) throws IOException, InterruptedException {
//StringTokenizer是Java工具包中的一个类,用于将字符串进行拆分
StringTokenizer tokenizer=new StringTokenizer(value.toString, " ");
//返回当前位置到下一个分隔符之间的字符串
word.set(tokenizer.nextToken);
//将word存到容器中,记一个数
context.write(word, one);
}
}
//参数同Map一样,依次表示是输入键类型,输入值类型,输出键类型,输出值类型。这里的输入是来源于map,所以类型要与map的输出类型对应 。
public static class doReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
private IntWritable result=new IntWritable;
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context)
throws IOException, InterruptedException {
int sum=0;
//for循环遍历,将得到的values值累加
for (IntWritable value : values) {
sum +=value.get;
}
result.set(sum);
context.write(key, result);//将结果保存到context中,最终输出形式为"key" + "result"
}
}
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
System.out.println("start");
Job job=Job.getInstance;
job.setJobName("wordCount");
Path in=new Path("hdfs://***:9000/user/hadoop/input/buyer_favorite1.txt");//设置这个作业输入数据的路径(***部分为自己liunx系统的localhost或者ip地址)
Path out=new Path("hdfs://***:9000/user/hadoop/output/wordCount"); //设置这个作业输出结果的路径
FileInputFormat.addInputPath(job, in);
FileOutputFormat.setOutputPath(job, out);
job.setJarByClass(WordCount.class);// 设置运行/处理该作业的类
job.setMapperClass(doMapper.class);//设置实现了Map步的类
job.setReducerClass(doReducer.class);//设置实现了Reduce步的类
job.setOutputKeyClass(Text.class);//设置输出结果key的类型
job.setOutputValueClass(IntWritable.class);//设置输出结果value的类型
////执行作业
System.exit(job.waitForCompletion(true) ? 0 : 1);
System.out.println("end");
}
}
那么这个map和reduce进程是怎么在分布式的集群中启动的呢?
map/reduce启动过程
上图比较清晰地阐述的整个过程,再描述一波。MR中主要是两种进程角色,分别为 JobTracke r和 TaskTracker 两种。
JobTracker在集群中只有一个,而 TaskTracker 存在多个,当 JobClient 启动后,往 JobTracker 提交作业,JobTracker查看文件路径决定在哪些服务器启动 Map 进程。
然后发送命令给 TaskTracker,告诉它要准备执行任务了,TaskTracker收到任务后就会启动 TaskRunner 下载任务对应的程序。
map计算完成,TaskTracker对map输出结果 shuffer 操作然后加载 reduce 函数进行后续计算,这就是各个模块协同工作的简单过程。
上述过程还是比较麻烦,我们能不能直接写SQL,然后引擎帮助我们生成mapreduce代码,就反复我们在web开发的时候,不直接写SQL语句,直接交给引擎那么方便,有的,它就是HIVE。
举个例子:
SQL
拆分
那么使用MR的计算过程完成这条SQL的处理:
MR TO SQL
Spark是基于内存计算的大数据并行计算框架。基于此说说上面hadoop中组件的缺点:
磁盘IO开销大。每次执行都需要从磁盘读取并且计算完成后还需要将将中间结果存放于磁盘
表达能力有限。大多数计算都需要转换为Map和Reduce两个操作,难以描述复杂的数据处理
spark优点:
编程模型不限于map和reduce,具有更加灵活的编程模型
spark提供内存计算,带来更高的迭代运算效率且封装了良好的机器学习算法
采用了基于图DAG的任务调度机制
Flink
Flink是大数据处理的新规,发展速度之快,这两年也相继出现中文资料。作为流式数据流执行引擎,针对数据流的分布式计算提供数据分布,数据通信以及容错机制等功能。同时Flink也提供了机器学习库,图计算库等。
附一张去年参加会议回答问题中奖的马克杯,嘻嘻。
去年参会
关于大数据相关知识点可作为扩充点,在面试的过程中经常会有大数问题,除了从算法的角度来阐述,也可以从这些框架中吸取一些经验。
对于之前从事c/c++开发的我,很多时候是Linux的开发。在学校又没怎么接触系统性的项目,更不知道后端技术的博大进深,可能文中涉及的也就一部分,不过希望还在学校的小伙伴可以知道有这些东西,然后通过强大的搜索引擎,给自己个比较明确的方向,也许会少走点弯路,这周的文章就到这了,goodbye!
点分享
*请认真填写需求信息,我们会在24小时内与您取得联系。