可参考:juejin.im/post/5ac43e… 可参考:github.com/ltadpoles/w…
一、HTML&CSS
1.文本溢出显示省略号
- 单行文本
<p style="width: 300px;overflow: hidden;white-space: nowrap;text-overflow: ellipsis;">
复制代码
width:加宽度属性来兼容部分浏览器
white-space:设置如何处理元素中的空白。详细见:developer.mozilla.org/zh-CN/docs/…
text-overflow:ellipsis:单行文本的溢出显示省略号
- 多行文本
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
复制代码
因使用了WebKit的CSS扩展属性,该方法适用于WebKit浏览器及移动端;
注:-webkit-line-clamp用来限制在一个块元素显示的文本的行数。 为了实现该效果,它需要组合其他的WebKit属性。
常见结合属性:
- display: -webkit-box:必须结合的属性,将对象作为弹性伸缩盒子模型显示 。
- -webkit-box-orient:必须结合的属性,设置或检索伸缩盒对象的子元素的排列方式 。
PS:字符串中包含'\r\n' 需添加white-space: pre-line;
渲染才能换行展示
2.按钮渐变+阴影
background-image: linear-gradient(-90deg, #ff62c5 0%, #ff5687 100%);
box-shadow: 0 1px 4px 0 rgba(255, 100, 145, 0.7);
复制代码
linear-gradient(45deg, blue, red);/* 渐变轴为45度,从蓝色渐变到红色 */
linear-gradient(to left top, blue, red);/* 从右下到左上、从蓝色渐变到红色 */
linear-gradient(0deg, blue, green 40%, red);/* 从下到上,从蓝色开始渐变、到高度40%位置是绿色渐变开始、最后以红色结束 */
box-shadow: 2px 2px 2px 1px rgba(0, 0, 0, 0.2);/* x偏移量 | y偏移量 | 阴影模糊半径 | 阴影扩散半径 | 阴影颜色 */
3.transition过渡和animation动画
transition过渡
- (1)transition
当鼠标放置于图片上,图片会迅速变大。图片变大是瞬间实现的。
img{
height:15px;
width:15px;
}
img:hover{
height: 450px;
width: 450px;
}
复制代码
transition的作用在于,指定状态变化所需要的时间。
img{
transition: 1s;
}
复制代码
还可以指定transition适用的属性,比如只适用于height。只有height的变化需要1秒实现,其他变化(主要是width)依然瞬间实现。
img{
transition: 1s height;
}
复制代码
height和width的变是同时进行,跟不指定它们没有差别
img{
transition: 1s height, 1s width;
}
复制代码
- (2)transition-delay 延迟变化
让height先发生变化,等结束以后,再让width发生变化。
img{
transition: 1s height, 1s 1s width;
}
复制代码
- (3)transition-timing-function transition的状态变化速度(又称timing function),默认不是匀速的,而是逐渐放慢,叫做ease。
img{
transition: 1s ease;
}
复制代码
除了ease以外,其他模式还包括
(1)linear:匀速
(2)ease-in:加速
(3)ease-out:减速
(4)cubic-bezier函数:自定义速度模式,cubic-bezier可以使用工具网站来定制。
img{
transition: 1s height cubic-bezier(.83,.97,.05,1.44);
}
复制代码
transition的各项属性 transition的完整写法如下。
img{
transition: 1s 1s height ease;
}
复制代码
这其实是一个简写形式,可以单独定义成各个属性。
img{
transition-property: height;
transition-duration: 1s;
transition-delay: 1s;
transition-timing-function: ease;
}
复制代码
animation动画
CSS Animation需要指定动画一个周期持续的时间,以及动画效果的名称。
- (1)animation
当鼠标悬停在div元素上时,会产生名为rainbow的动画效果,持续时间为1秒。为此,我们还需要用keyframes关键字,定义rainbow效果。rainbow效果一共有三个状态,分别为起始(0%)、中点(50%)和结束(100%)。
div:hover {
animation: 1s rainbow;
}
@keyframes rainbow {
0% { background: #c00; }
50% { background: orange; }
100% { background: yellowgreen; }
}
复制代码
默认情况下,动画只播放一次。加入infinite关键字,可以让动画无限次播放。
div:hover {
animation: 1s rainbow infinite;
}
复制代码
也可以指定动画具体播放的次数,比如3次。
div:hover {
animation: 1s rainbow 3;
}
复制代码
- (2)animation-fill-mode:动画结束以后,会立即从结束状态跳回到起始状态。如果想让动画保持在结束状态,需要使用animation-fill-mode属性。
div:hover {
animation: 1s rainbow forwards; /* forwards表示让动画停留在结束状态 */
}
复制代码
animation-fill-mode还可以使用下列值。
(1)none:默认值,回到动画没开始时的状态。
(2)backwards:让动画回到第一帧的状态。
(3)both: 根据animation-direction轮流应用forwards和backwards规则。
- (3)animation-direction:动画循环播放时,每次都是从结束状态跳回到起始状态,再开始播放。animation-direction属性,可以改变这种行为。
/* 有这样一个动画 */
@keyframes rainbow {
0% { background-color: yellow; }
100% { background: blue; }
}
/* 默认情况animation-direction等于normal */
div:hover {
animation: 1s rainbow 3 normal;
}
复制代码
此外,还可以等于取alternate、reverse、alternate-reverse等值。它们的含义见下图(假定动画连续播放三次)。
animation的各项属性,同transition一样,animation也是一个简写形式。
div:hover {
animation: 1s 1s rainbow linear 3 forwards normal;
}
复制代码
这是一个简写形式,可以分解成各个单独的属性。
div:hover {
animation-name: rainbow;
animation-duration: 1s;
animation-timing-function: linear;
animation-delay: 1s;
animation-fill-mode:forwards;
animation-direction: normal;
animation-iteration-count: 3;
}
复制代码
- keyframes的写法:keyframes关键字用来定义动画的各个状态,它的写法相当自由。
@keyframes rainbow {
0% { background: #c00 }
50% { background: orange }
100% { background: yellowgreen }
}
/* 0%可以用from代表,100%可以用to代表,因此上面的代码等同于下面的形式。 */
@keyframes rainbow {
from { background: #c00 }
50% { background: orange }
to { background: yellowgreen }
}
复制代码
如果省略某个状态,浏览器会自动推算中间状态,所以下面都是合法的写法。
@keyframes rainbow {
50% { background: orange }
to { background: yellowgreen }
}
@keyframes rainbow {
to { background: yellowgreen }
}
/* 甚至,可以把多个状态写在一行。*/
@keyframes pound {
from,to { transform: none; }
50% { transform: scale(1.2); }
}
/* 另外一点需要注意的是,浏览器从一个状态向另一个状态过渡,是平滑过渡。steps函数可以实现分步过渡。*/
div:hover {
animation: 1s rainbow infinite steps(10);
}
复制代码
- (4)animation-play-state:有时,动画播放过程中,会突然停止。这时,默认行为是跳回到动画的开始状态。
鼠标没有悬停时,动画状态是暂停;一旦悬停,动画状态改为继续播放。
div {
animation: spin 1s linear infinite;
animation-play-state: paused;
}
div:hover {
animation-play-state: running;
}
复制代码
注意:浏览器前缀,目前,IE 10和Firefox(>= 16)支持没有前缀的animation,而chrome不支持,所以必须使用webkit前缀。
4.css伪类和伪元素(单冒号:和双冒号::的使用)
在CSS2之前规范不明确的时候,伪元素和伪类都使用单冒号(:)来表示。
比如 :before :after :hover
而CSS3规范中的要求使用双冒号(::)表示伪元素,以此来区分伪元素和伪类。
上面的例子用CSS3的规范就应该写成 ::before ::after :hover
为了兼容过去的写法,CSS3之前的伪元素仍然可以使用单冒号(:)来表示,浏览器是可以解析的。
比如 :before 和 ::before 都可以被浏览器解析。
但是CSS3之后出现的伪元素必须用双冒号表示,不再支持单冒号的形式。
伪元素包括:
伪类包括:
5.浏览器的回流与重绘 (Reflow & Repaint)
(1) Reflow
也称作Layout,中文叫回流,一般意味着元素的内容、结构、位置或尺寸发生了变化,需要重新计算样式和渲染树,这个过程称为Reflow。
(2) Repaint
中文重绘,意味着元素发生的改变只是影响了元素的一些外观之类的时候(例如,背景色,边框颜色,文字颜色等),此时只需要应用新样式绘制这个元素就OK了,这个过程称为Repaint。
Reflow的成本比Repaint的成本高得多的多。DOM树里的每个结点都会有reflow方法,一个结点的reflow很有可能导致子结点,甚至父点以及同级结点的reflow。
(3) 下面这些动作有很大可能会是成本比较高的:
- 1、增加、删除、修改DOM结点时,会导致Reflow或Repaint。
- 2、移动DOM的位置,或是搞个动画的时候。
- 3、内容发生变化。
- 4、修改CSS样式的时候。
- 5、Resize窗口的时候(移动端没有这个问题),或是滚动的时候。
- 6、修改网页的默认字体时。
(4) 基本上来说,reflow有如下的几个原因:
1、Initial,网页初始化的时候。 2、Incremental,一些js在操作DOM树时。 3、Resize,其些元件的尺寸变了。 4、StyleChange,如果CSS的属性发生变化了。
6.css盒子模型,box-sizing属性的理解
css的盒模型由content(内容)、padding(内边距)、border(边框)、margin(外边距)组成。但盒子的大小由content+padding+border这几部分决定
box-sizing是一个CSS3属性,与盒子模型有着密切联系。即决定元素的宽高如何计算,box-sizing有三个属性:
box-sizing: content-box|border-box|inherit:
标准盒模型(content-box):盒模型的宽高只是内容(content)的宽高
怪异盒模型(border-box):盒模型的宽高是内容(content)+填充(padding)+边框(border)的总宽高
inherit 指定box-sizing属性的值,应该从父元素继承
7.清除浮动,什么时候需要清除浮动,清除浮动都有哪些方法
浮动的元素是脱离文档标准流的,如果我们不清楚浮动,那么就会造成父元素高度塌陷,影响页面布局。
清除浮动的方式:
-
为父元素设置高度
-
为父元素添加overflow:hidden
overflow:hidden可以触发BFC机制。BFC:块级格式化上下文,创建了 BFC的元素就是一个独立的盒子,它规定了内部如何布局,并且与这个独立盒子里的布局不受外部影响,当然它也不会影响到外面的元素,计算BFC的高度时,浮动元素也参与计算
-
伪元素
.fix::after { content:""; display:block; clear:both; } 复制代码
使用伪元素的好处:不增加冗余的DOM节点,符合语义化
8.多标签之间的通信
9.HTML5离线存储原理
10. CSS中link 和@import的区别
link属于XHTML标签,@import完全是CSS提供的一种方式,只能加载CSS
加载顺序的差别,当一个页面被加载的时候,link引用的CSS会同时被加载,而@import引用的CSS 会等到页面全部被下载完再被加载
兼容性的差别。由于@import是CSS2.1提出的所以老的浏览器不支持,而link标签无此问题
当使用javascript控制dom去改变样式的时候,只能使用link标签,因为@import不是dom可以控制的
11. offsetWidth/offsetHeight,clientWidth/clientHeight与scrollWidth/scrollHeight的区别
offsetWidth/offsetHeight
返回值包含content + padding + border,效果与e.getBoundingClientRect()相同
clientWidth/clientHeight
返回值只包含content + padding,如果有滚动条,也不包含滚动条
scrollWidth/scrollHeight
返回值包含content + padding + 溢出内容的尺寸
二、javascript
1.JavaScript 有哪些数据类型
6种原始数据类型:
- Boolean: 布尔表示一个逻辑实体,可以有两个值:true 和 false
- Number: 用于表示数字类型
- String: 用于表示文本数据
- Null: Null 类型只有一个值: null,特指对象的值未设置
- Undefined: 一个没有被赋值的变量会有个默认值 undefined
- Symbol: 符号(Symbols)是ECMAScript第6版新定义的。符号类型是唯一的并且是不可修改的
引用类型:Object
详见:developer.mozilla.org/zh-CN/docs/…
2.判断JS数据类型
-
typeof操作符:返回一个字符串,表示未经计算的操作数的类型。除了null都可以显示正确的类型。
typeof 操作符对于简单数据类型,返回其本身的数据类型,函数对象返回 function ,其他对象均返回 Object
typeof 1 //'number' typeof '1' //'string' typeof undefined //'undefined' typeof true //'boolean' typeof Symbol() //'symbol' typeof b //b没有生命,但是还会显示undefined typeof [] //'object' typeof {} //'object' typeof console.log //'function' typeof null //'object' 复制代码
null 返回 Object,出现这种情况的原因是:在js最初版本中,使用的是32位系统,为了性能考虑使用低位存储了变量的类型信息,000开头代表的是对象,然而null表示为全0,所以将它错误的判断成了object。虽然现在的内部类型判断代码已经改变了,但是这个bug还是存在的。
-
instanceof: 用来判断A 是否是 B的实例,表达式为 A instanceof B,返回一个Boolean类型的值 instanceof 检测的是原型,只能用来判断两个对象是否属于实例关系, 而不能判断一个对象实例具体属于哪种类型
let a = []; a instanceof Array // true a instanceof Object // true 复制代码
变量a 的 __ proto__ 直接指向Array.prototype,间接指向 Object.prototype,所以按照 instanceof 的判断规则,a 就是Object的实例.针对数组的这个问题,ES5 提供了 Array.isArray() 方法 。该方法用以确认某个对象本身是否为 Array 类型
constructor: 当一个函数被定义时,JS引擎会为其添加prototype原型,然后再在 prototype上添加一个 constructor 属性,并让其指向该函数的引用
null和undefined是无效的对象,因此是不会有constructor存在的,这两种类型的数据需要通过其他方式来判断
函数的constructor是不稳定的,这个主要体现在自定义对象上,当开发者重写prototype后,原有的constructor引用会丢失,constructor会默认为 Object
function F() {}; var f = new F; f.constructor == F // true F.prototype = {a: 1} var f = new F f.constructor == F // false 复制代码
在构造函数 F.prototype 没有被重写之前,构造函数 F 就是新创建的对象 f 的数据类型。当 F.prototype 被重写之后,原有的 constructor 引用丢失, 默认为 Object
因此,为了规范开发,在重写对象原型时一般都需要重新给 constructor 赋值,以保证对象实例的类型不被篡改
-
toString: Object 的原型方法,调用该方法,默认返回当前对象的 [[Class]] 。这是一个内部属性,其格式为 [object Xxx] ,其中 Xxx 就是对象的类型。可以获得变量的正确类型。
Object.prototype.toString.call('') ; // [object String] Object.prototype.toString.call(11) ; // [object Number] Object.prototype.toString.call(true) ; // [object Boolean] Object.prototype.toString.call(Symbol()); //[object Symbol] Object.prototype.toString.call(undefined) ; // [object Undefined] Object.prototype.toString.call(null) ; // [object Null] Object.prototype.toString.call(new Function()) ; // [object Function] Object.prototype.toString.call([]) ; // [object Array] 复制代码
3.isPrototypeOf、instanceof、hasOwnProperty函数介绍
可参考: www.jianshu.com/p/44ba37660…
4.js中的强制类型转换
5.js创建对象的方式
1. 对象字面量
var obj = {}
复制代码
2. Object 构造函数
var obj = new Object()
复制代码
3. 工厂模式
function Person(name, age) {
var o = new Object()
o.name = name;
o.age = age;
o.say = function() {
console.log(name)
}
return o
}
复制代码
缺点: 每次通过Person创建对象的时候,所有的say方法都是一样的,但是却存储了多次,浪费资源
4. 构造函数模式
function Person(name, age) {
this.name = name
this.age = age
this.say = function() {
console.log(name)
}
}
var person = new Person('hello', 18)
复制代码
构造函数模式,隐式地在最后return this
所以在缺少new的情况下,会将属性和方法添加给全局对象,浏览器端就会添加给window对象,可以根据return this 的特性调用call或者apply指定this
5. 原型模式
function Person() {}
Person.prototype.name = 'hanmeimei';
Person.prototype.say = function() {
alert(this.name);
}
Person.prototype.friends = ['lilei'];
var person = new Person();
复制代码
实现了方法与属性的共享,可以动态添加对象的属性和方法。但是没有办法创建实例自己的属性和方法,也没有办法传递参数
6. 构造函数和原型组合
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.say = function() {
console.log(this.name)
}
var person = new Person('hello')
复制代码
6.null和undefined的区别
参考:www.ruanyifeng.com/blog/2014/0…
7.数组对象有哪些常用方法
修改器方法:详细可参考:juejin.im/post/5c3ede… & juejin.im/post/5c3f01…
- pop(): 删除数组的最后一个元素,并返回这个元素
- push():在数组的末尾增加一个或多个元素,并返回数组的新长度
- reverse(): 颠倒数组中元素的排列顺序
- shift(): 删除数组的第一个元素,并返回这个元素
- unshift(): 在数组的开头增加一个或多个元素,并返回数组的新长度
- sort(): 对数组元素进行排序,并返回当前数组
- splice(): 在任意的位置给数组添加或删除任意个元素
访问方法:
- concat(): 返回一个由当前数组和其它若干个数组或者若干个非数组值组合而成的新数组
- join(): 连接所有数组元素组成一个字符串
- slice(): 抽取当前数组中的一段元素组合成一个新数组
- indeOf(): 返回数组中第一个与指定值相等的元素的索引,如果找不到这样的元素,则返回 -1
- lastIndexOf(): 返回数组中最后一个(从右边数第一个)与指定值相等的元素的索引,如果找不到这样的元素,则返回 -1
迭代方法:详细可参考另一篇总结:juejin.im/post/5c2c94…
- forEach(): 为数组中的每个元素执行一次回调函数,最终返回 undefined
- every(): 如果数组中的每个元素都满足测试函数,则返回 true,否则返回 false
- some(): 如果数组中至少有一个元素满足测试函数,则返回 true,否则返回 false
- filter(): 将所有在过滤函数中返回 true 的数组元素放进一个新数组中并返回
- map(): 返回一个由回调函数的返回值组成的新数组
8.js事件模型 事件委托
1.js事件的三个阶段分别为:捕获、目标、冒泡
- 1.捕获:事件由页面元素接收,逐级向下,到具体的元素
- 2.目标:具体的元素本身
- 3.冒泡:跟捕获相反,具体元素本身,逐级向上,到页面元素
事件捕获:当使用事件捕获时,父级元素先触发,子元素后触发
事件冒泡:当使用事件冒泡时,子级元素先触发,父元素后触发
W3C:任何事件发生时,先从顶层开始进行事件捕获,直到事件触发到达事件源,再从事件源向上进行事件捕获
标准浏览器:addEventListener("click","doSomething","true")方法,若第三参数为true则采用事件捕获,若为false,则采用事件冒泡
IE浏览器只支持事件冒泡,不支持事件捕获,所以它不支持addEventListener("click","doSomething","true")方法,所以ie浏览器使用ele.attachEvent("onclick",doSomething)
【事件传播的阻止方法】
在W3C中,使用stopPropagation()方法
在IE下使用cancelBubble = true方法
【阻止默认行为】
在W3c中,使用preventDefault()方法
在IE下return false
2.事件委托/代理:通俗来说就是将元素的事件委托给它的父级或者更外级元素处理
原理:利用事件冒泡机制实现的
优点:
- 只需要将同类元素的事件委托给父级或者更外级的元素,不需要给所有元素都绑定事件,减少内存空间占用,提升性能 无需为子节点注销事件。
- 动态新增的元素无需重新绑定事件
9.js中声明提升、作用域(链)、this
关键字和箭头函数
参考另一篇总结:juejin.im/post/5ca324…
10.闭包
简单来说,闭包就是能够读取其他函数内部变量的函数
function Person() {
var name = 'hello'
function say () {
console.log(name)
}
return say()
}
Person() // hello
复制代码
由于 JavaScript 特殊的作用域,函数外部无法直接读取内部的变量,内部可以直接读取外部的变量,从而就产生了闭包的概念
用途:
- 以读取函数内部的变量
- 让这些变量的值始终保持在内存中
注意点:
- 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露
- 解决方法是,在退出函数之前,将不使用的局部变量全部删除
循环定时器例题:juejin.im/post/58f1fa…
11.call, apply, bind 区别? 怎么实现 call,apply 方法
相似之处:
-
都是用来改变函数的 this 对象的指向的。
-
第一个参数都是 this 要指向的对象。
-
都可以利用后续参数传参。
区别:
-
call 接受函数传参方式为:fn.call(this, 1, 2, 3)
-
apply 接受函数传参方式为:fn.apply(this,[1, 2, 3])
-
bind 将函数绑定至某个对象,当在f()上调用bind()方法并传入一个对象o作为参数,这个方法会返回一个新的函数。以函数调用的方式调用新的函数将会把原始的函数f()当做新对象o的方法来调用。传入新函数的任何实参都将传入原始函数f()。
let a = {
value: 1
}
function getValue(name, age) {
console.log(name);
console.log(age);
console.log(this.value);
}
getValue().call(a, 'zyx','24');
getValue().apply(a, ['zyx','24']);
复制代码
不传入第一个参数,那么默认为window
改变了this指向,让新的对象可以执行该函数。那么可以认为是为改新的对象添加一个函数,执行完以后再删除
1. 手动实现 call 方法:
Function.prototype.myCall = function(context = window, ...rest) {//context就是传入的第一个参数a
context.fn = this; // this就是当前调用call的函数——即getValue;给a添加一个函数:a.fn = getValue
let result = context.fn(...rest); //getValue(...rest)
//将this指向销毁
delete context.fn;
return result;
};
复制代码
2. 手动实现apply
Function.prototype.myCall = function(context = window, params = []) {
context.fn = this; //此处this是指调用myCall的function
let result
if (params.length) {
result = context.fn(...params)
}else {
result = context.fn()
}
//将this指向销毁
delete context.fn;
return result;
};
复制代码
3. 手动实现 bind 方法:
var f = function(y, z) {
return this.x + y + z;
}
var a = {x:1}
var g = f.bind(a, 2);
g(3); // => 6 this.x绑定到1,y绑定到2,z绑定到3
复制代码
Function.prototype.myBind = function(context) {//context是a,
if(typeof this !== 'function') { // this就是f
throw new TypeError('Error');
}
var _this = this;
var args = [...arguments].slice(1); //arguments对象是所有(非箭头)函数中都可用的局部变量。此时arguments是 (a,2), [a,2].slice(1) => 得到对象后面的参数args=[2]
return function F() { //因为返回了一个函数,我们可以new F() 所以要判断
if (this instanceof F) { F就是g,如果f是g的实例 g也存在a
return new _this(...args, ...arguments); //args=[2];arguments是F即g的参数[3];=>new f(2,3)
}
return _this.apply(context, args.concat(...arguments));//如果f不是g的实例,f.apply(a,[2].concat(3))=>f.apply(a,[2,3])
}
}
复制代码
12.原型 原型链 它们分别有什么特点
原型
每个构造函数(constructor)都有一个原型对象(prototype),原型对象都包含一个指向构造函数的指针,而实例(instance)都包含一个指向原型对象的内部指针
- 每一个构造函数都拥有一个prototype属性,这个属性指向一个对象,也就是原型对象
- 原型对象默认拥有一个constructor属性,指向指向它的那个构造函数
- 每个对象都拥有一个隐藏的属性[[prototype]],指向它的原型对象
原型链
JavaScript中所有的对象都是由它的原型对象继承而来。而原型对象自身也是一个对象,它也有自己的原型对象,这样层层上溯,就形成了一个类似链表的结构,这就是原型链。
所有原型链的终点都是Object函数的prototype属性。Objec.prototype指向的原型对象同样拥有原型,不过它的原型是null,而null则没有原型
13.JavaScript 如何实现继承
每个函数都有prototype属性,除了Function.prototype.bind(),该属性指向原型。
每个对象都有__proto__属性,实例的__proto__指向构造函数的 prototype。
js 引擎会沿着__proto__-> ptototype 的顺序一直往上方查找,找到 Object.prototype 时,Object.prototype.__proto__又会指向 Object.ptototype,为了避免循环引用,原型链没有终点,js 把 Object.prototype.__proto__设置为 null,这样原型链就有了终点。其实原型链查找到 Object.ptototype 这里就停止了查找,如果没有找到,就会报错或者返回 undefined。
1.原型链继承
最简单的继承实现方式,但是也有其缺点
- 来自原型对象的所有属性被所有实例共享
- 创建子类实例时,无法向父类构造函数传参
- 要想为子类新增属性和方法,必须要在new语句之后执行,不能放到构造器中
function Animal() {}
Animal.prototype.name = 'cat'
Animal.prototype.age = 1
Animal.prototype.say = function() {console.log('hello')}
var cat = new Animal()
cat.name // cat
cat.age // 1
cat.say() // hello
复制代码
2. 构造继承
使用call或apply方法,将父对象的构造函数绑定在子对象上.
function Animal() {
this.species = "动物"
}
function Cat(name, age) {
Animal.call(this)
this.name = name
this.age = age
}
var cat = new Cat('豆豆', 2)
cat.name // 豆豆
cat.age // 2
cat.species // 动物
复制代码
ES5实现继承:组合继承,寄生组合继承
3.组合继承: 利用 call 继承父类上的属性,用子类的原型等于父类实例去继承父类的方法
缺点:调用两次父类,造成性能浪费
function Parent(name) {
this.name = name;
}
Parent.prototype.say = function() {
console.log(this.name);
};
function Child(name) {
Parent.call(this, name)
}
Child.prototype = new Parent;//如果没有这一行,Child.prototype.constructor是指向Child的;加了这一行以后,Child.prototype.constructor指向Parent。这显然会导致继承链的紊乱(c明明是用构造函数Child生成的),因此我们必须手动纠正,将Child.prototype对象的constructor值改为Child
Child.prototype.constructor = Child
let c = new Child("YaoChangTuiQueDuan");
c.say()
复制代码
4.寄生组合集成
利用call继承父类上的属性,用一个干净的函数的原型=父类原型,再用子类的原型=这个干净函数的原型
function Parent(name) {
this.name = name;
}
Parent.prototype.say = function() {
console.log(this.name);
};
function ExtendMiddle() {}
function Child(name) {
Parent.call(this, name)
}
ExtendMiddle.prototype = Parent.prototype;
Child.prototype = new ExtendMiddle
let c = new Child("YaoChangTuiQueDuan");
c.say()
复制代码
5.ES6 Class 可以通过extends关键字实现继承
用 extends 实现继承,必须添加 super 关键字定义子类的 constructor,这里的super() 就相当于 Animal.prototype.constructor.call(this)
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 调用父类的constructor(x, y)
this.color = color;
}
toString() {
return this.color + ' ' + super.toString(); // 调用父类的toString()
}
}
复制代码
上面代码中,constructor方法和toString方法之中,都出现了super关键字,它在这里表示父类的构造函数,用来新建父类的this对象。
为什么一定要使用super()?
子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super方法,子类就得不到this对象。
14.new 操作符具体干了什么
1.创建一个空对象
2.this变量引用该变量
3.继承函数的原型,属性和方法被加入到this引用的对象中
4.新创建的对象由this引用,并且最后隐式的返回this
对于实例对象来说,都是通过new产生的,无论是function Foo()还是let a = {b:1}
对于创建一个对象来说,更推荐使用字面量的方式创建对象(无论性能上还是可读性上)。如果使用 new Object()的方式创建对象需要通过作用域链一层层找到Object,但是使用字面量就没这个问题。
new的运算符优先级
function Foo() {
return this;
}
Foo.getName = function() {
console.log('1');
}
Foo.prototype.getName = function() {
console.log('2');
}
复制代码
15.深浅拷贝
let a = {
age: 1
}
let b = a;
a.age = 2;
console.log(b.age) //2
复制代码
如果给一个变量赋值,两者是同一个引用,其中一方改变,另一方也会相应改变。 当对象里面的值是简单数据类型,即不是对象类型的时候,浅拷贝就能解决该问题。
浅拷贝
- Object.assign
let a = { age: 1 } let b = Object.assign({}, a); a.age = 2; console.log(b.age)// 1 复制代码
- ...扩展运算符
let a = { age: 1 } b = {...a}; a.age = 2; console.log(b.age);//1 复制代码
- Array.prototype.slice(0) 可以浅拷贝一个数组
深拷贝
当存在这种情况,我们要使用深拷贝
let a = {
age: 1,
job: {
first: 'FE'
}
}
let b = {...a};
a.job.first = 'native';
console.log(b.job.first)//native
复制代码
- JSON.parse(JSON.stringify(object))
let a = {
age: 1,
job: {
first: 'FE'
}
}
let b = JSON.parse(JSON.stringify(a))
a.job.first = 'native';
console.log(b.job.first)//FE
复制代码
但是该方法也存在一定的局限性:
- 会忽略undefined
- 不会序列化函数
- 不能解决循环引用的对象
但是在通常情况下,复杂数据都是可以序列化的,所以这个方法可以解决大部分问题,并且该函数是内置函数中处理深拷贝最快的。
如果存在上述三种情况可以使用loadsh的深拷贝函数
如果需要拷贝的对象不包含函数,但存在undefined和循环引用的对象,可以使用MessageChannel
function structrualClone(obj) {
return new Promise(resolve => {
cost {port1, port2} = new MessageChannel();
port2.onmessage = ev => resolve(ev.data);
post1.postMessage(obj);
})
}
var obj = {
a: 1,
b: {
c: b
}
}
//注意该方法是异步的
//可以处理undefined和循环对象
const clone = await structrualClone(obj);
复制代码
16.Promise 对象 async 函数以及 awit 命令
关于Promise
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。
Promise 对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
特点:
- 对象的状态不受外界影响
- 一旦状态改变,就不会再变,任何时候都可以得到这个结果
- Promise 新建后就会立即执行
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
})
复制代码
Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数
promise.then(function(value) {
// success
}, function(error) {
// failure
})
复制代码
then 方法返回的是一个新的Promise实例
Promise.prototype.catch 用于指定发生错误时的回调函数,具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获
getJSON('/post/1.json').then(function(post) {
return getJSON(post.commentURL);
}).then(function(comments) {
// some code
}).catch(function(error) {
// 处理前面三个Promise产生的错误
});
复制代码
catch 方法返回的还是一个 Promise 对象,因此后面还可以接着调用 then 方法
关于async 函数以及 awit 命令
async 函数是什么?一句话,它就是 Generator 函数的语法糖
Generator 函数是一个普通函数,但是有两个特征。
- 1.function关键字与函数名之间有一个星号
- 2.函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)。
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
复制代码
调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,一个遍历器对象(Iterator Object)。
下一步,必须调用遍历器对象的next方法,使得指针移向下一个状态。也就是说,每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。换言之,Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行。
hw.next()
// { value: 'hello', done: false }
hw.next()
// { value: 'world', done: false }
hw.next()
// { value: 'ending', done: true }
hw.next()
// { value: undefined, done: true }
复制代码
async 特点:
-
async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数。当函数执行的时候,一旦遇到 await 就会先返回,等到异步操作完成,再接着执行函数体内后面的语句
-
async 函数内部 return 语句返回的值,会成为 then 方法回调函数的参数
-
async 函数返回的 Promise 对象,必须等到内部所有 await 命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到 return 语句或者抛出错误
-
async 函数内部抛出错误,会导致返回的 Promise 对象变为 reject 状态。抛出的错误对象会被 catch 方法回调函数接收到
function timeout(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
async function asyncPrint(value, ms) {
await timeout(ms);
console.log(value);
}
asyncPrint('hello world', 50);
复制代码
await 命令: await 命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值
async function f() {
// 等同于
// return 123;
return await 123;
}
f().then(v => console.log(v))
// 123
复制代码
await 命令后面是一个thenable对象(即定义then方法的对象),那么await会将其等同于 Promise 对象.也就是说就算一个对象不是Promise对象,但是只要它有then这个方法, await 也会将它等同于Promise对象
使用注意点:
- await 命令后面的 Promise 对象,运行结果可能是 rejected,所以最好把 await 命令放在 try...catch 代码块中
- 多个 await 命令后面的异步操作,如果不存在继发关系,最好让它们同时触发
- await 命令只能用在 async 函数之中,如果用在普通函数,就会报错
17.JS引擎执行机制 微任务 宏任务
首选明确两点:
-
JavaScript 是单线程语言
-
JavaScript 的 Event Loop 是 JS 的执行机制, 也就是事件循环
执行时,将任务分为两类:
-
macro-task(宏任务):包括整体代码script,setTimeout,setInterval
-
micro-task(微任务):Promise,process.nextTick
- 执行一个宏任务,过程中如果遇到微任务,就将其放到微任务的【事件队列】里(先进先出原则)
- 当前宏任务执行完成后,会查看微任务的【事件队列】,并将里面全部的微任务依次执行完(先进先出原则)
setTimeout(function(){console.log(1);},0);
new Promise(function(resolve){
console.log(2);
for(var i = 0; i < 10000; i++){
i == 99 && resolve();
}
}).then(function(){
console.log(3)
});
console.log(4);
// 2 4 3 1
复制代码
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(function(){
console.log('setTimeout');
}, 0);
async1();
new Promise(function(resolve){
console.log('promise1');
resolve();
}).then(function(){
console.log('promise2')
})
console.log('script end');
复制代码
输出:
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
18.export 与 export default有什么区别
export 与 export default 均可用于导出常量、函数、文件、模块等
-
在一个文件或模块中,export、import 可以有多个,export default 仅有一个
-
通过 export 方式导出,在导入时要加 { },export default 则不需要
-
使用 export default命令,为模块指定默认输出,这样就不需要知道所要加载模块的变量名; export 加载的时候需要知道加载模块的变量名
-
export default 命令的本质是将后面的值,赋给 default 变量,所以可以直接将一个值写在 export default 之后
19.什么是内存泄漏,哪些操作会造成内存泄漏
内存泄漏:是指一块被分配的内存既不能使用,又不能回收,直到浏览器进程结束
可能造成内存泄漏的操作:
- 意外的全局变量
- 闭包
- 循环引用
- 被遗忘的定时器或者回调函数
- setTimeout 的第一个参数使用字符串而非函数的话,会引发内存泄漏
20.同步和异步的区别,怎么异步加载 JavaScript
1.同步模式
同步模式,又称阻塞模式。javascript 在默认情况下是会阻塞加载的。当前面的 javascript 请求没有处理和执行完时,会阻止浏览器的后续处理
2.异步模式
异步加载又叫非阻塞,浏览器在下载执行 js 同时,还会继续进行后续页面的处理
如何实现异步加载 JavaScript
- 1.动态添加 script 标签
defer属性和async都是属于 script 标签上面的属性,两者都能实现 JavaScript 的异步加载,不同之处在于:
- 2.defer:defer 会等到 html 加载完毕之后再执行,只支持IE
- 3.async:async 在异步加载完成的时候就马上开始执行了
21.AJAX、$.ajax、axios、fetch、superagent
参考另一篇总结:juejin.im/post/5caed2…
22.js排序算法详解
各排序算法实现:mp.weixin.qq.com/s/gR0kCPRgQ…
三、浏览器和网络
1.从浏览器地址栏输入url到显示页面的步骤
1、浏览器的地址栏输入URL并按下回车。
2、浏览器查找当前URL是否存在缓存,并比较缓存是否过期。
3、DNS解析URL对应的IP。
4、根据IP建立TCP连接(三次握手)。
5、HTTP发起请求。
6、服务器处理请求,浏览器接收HTTP响应。
7、渲染页面,构建DOM树。
8、关闭TCP连接(四次挥手)
(1) URL
我们常见的RUL是这样的:www.baidu.com,这个域名由三部分组成:协议名、域名、端口号,这里端口是默认所以隐藏。除此之外URL还会包含一些路径、查询和其他片段,例如:www.tuicool.com/search?kw=�… 我们最常见的的协议是HTTP协议,除此之外还有加密的HTTPS协议、FTP协议、FILe协议等等。URL的中间部分为域名或者是IP,之后就是端口号了。通常端口号不常见是因为大部分的都是使用默认端口,如HTTP默认端口80,HTTPS默认端口443。
(2) 缓存
HTTP缓存有多种规则,根据是否需要重新向服务器发起请求来分类,分为强制缓存,对比缓存。
-
强制缓存判断HTTP首部字段:cache-control,Expires。
-
Expires: 是一个绝对时间,即服务器时间。浏览器检查当前时间,如果还没到失效时间就直接使用缓存文件。但是该方法存在一个问题:服务器时间与客户端时间可能不一致。因此该字段已经很少使用。
-
cache-control: cache-control中的max-age保存一个相对时间。例如Cache-Control: max-age = 484200,表示浏览器收到文件后,缓存在484200s内均有效。
-
如果同时存在cache-control和Expires,浏览器总是优先使用cache-control。
-
-
对比缓存通过HTTP的last-modified,Etag字段进行判断。
last-modified是第一次请求资源时,服务器返回的字段,表示最后一次更新的时间。下一次浏览器请求资源时就发送if-modified-since字段。服务器用本地Last-modified时间与if-modified-since时间比较,如果不一致则认为缓存已过期并返回新资源给浏览器;如果时间一致则发送304状态码,让浏览器继续使用缓存。
Etag:资源的实体标识(哈希字符串),当资源内容更新时,Etag会改变。服务器会判断Etag是否发生变化,如果变化则返回新资源,否则返回304。
(3) DNS域名解析
我们知道在地址栏输入的域名并不是最后资源所在的真实位置,域名只是与IP地址的一个映射。网络服务器的IP地址那么多,我们不可能去记一串串的数字,因此域名就产生了,域名解析的过程实际是将域名还原为IP地址的过程。
- 首先浏览器先检查本地hosts文件是否有这个网址映射关系,如果有就调用这个IP地址映射,完成域名解析。
- 如果没找到则会查找本地DNS解析器缓存,如果查找到则返回。
- 如果还是没有找到则会查找本地DNS服务器,如果查找到则返回。
- 最后
- 迭代查询,按根域服务器 ->顶级域,.cn->第二层域,hb.cn ->子域,www.hb.cn的顺序找到IP地址。
- 或递归查询,按上一级DNS服务器->上上级->....逐级向上查询找到IP地址。
- 迭代查询,按根域服务器 ->顶级域,.cn->第二层域,hb.cn ->子域,www.hb.cn的顺序找到IP地址。
(4) TCP连接
在通过第一步的DNS域名解析后,获取到了服务器的IP地址,在获取到IP地址后,便会开始建立一次连接,这是由TCP协议完成的,主要通过三次握手进行连接。
-
第一次握手(SYN=1, seq=x):
客户端发送一个 TCP 的 SYN 标志位置1的包,指明客户端打算连接的服务器的端口,以及初始序号 X,保存在包头的序列号(Sequence Number)字段里。
发送完毕后,客户端进入 SYN_SEND 状态。
-
第二次握手(SYN=1, ACK=1, seq=y, ACKnum=x+1):
服务器发回确认包(ACK)应答。即 SYN 标志位和 ACK 标志位均为1。服务器端选择自己 ISN 序列号,放到 Seq 域里,同时将确认序号(Acknowledgement Number)设置为客户的 ISN 加1,即X+1。
发送完毕后,服务器端进入 SYN_RCVD 状态。
-
第三次握手(ACK=1,ACKnum=y+1)
客户端再次发送确认包(ACK),SYN 标志位为0,ACK 标志位为1,并且把服务器发来 ACK 的序号字段+1,放在确定字段中发送给对方,并且在数据段放写ISN的+1。
发送完毕后,客户端进入 ESTABLISHED 状态,当服务器端接收到这个包时,也进入 ESTABLISHED 状态,TCP 握手结束。
(5) 浏览器向服务器发送HTTP请求
完整的HTTP请求包含请求起始行、请求头部、请求主体三部分。
(6) 浏览器接收响应
服务器在收到浏览器发送的HTTP请求之后,会将收到的HTTP报文封装成HTTP的Request对象,并通过不同的Web服务器进行处理,处理完的结果以HTTP的Response对象返回,主要包括状态码,响应头,响应报文三个部分。
- 状态码主要包括以下部分
- 1xx:指示信息–表示请求已接收,继续处理。
- 2xx:成功–表示请求已被成功接收、理解、接受。
- 3xx:重定向–要完成请求必须进行更进一步的操作。
- 4xx:客户端错误–请求有语法错误或请求无法实现。
- 5xx:服务器端错误–服务器未能实现合法的请求。
- 响应头主要由Cache-Control、 Connection、Date、Pragma等组成。
- 响应体为服务器返回给浏览器的信息,主要由HTML,css,js,图片文件组成。
(7) 页面渲染
如果说响应的内容是HTML文档的话,就需要浏览器进行解析渲染呈现给用户。
整个过程涉及两个方面:解析和渲染。
在渲染页面之前,需要构建DOM树和CSSOM树。
在浏览器还没接收到完整的 HTML文件时,它就开始渲染页面了,在遇到外部链入的脚本标签或样式标签或图片时,会再次发送 HTTP 请求重复上述的步骤。在收到CSS文件后会对已经渲染的页面重新渲染,加入它们应有的样式,图片文件加载完立刻显示在相应位置。在这一过程中可能会触发页面的重绘或重排。这里就涉及了两个重要概念:Reflow和Repaint
(8) 关闭TCP连接或继续保持连接
通过四次挥手关闭连接(FIN ACK, ACK, FIN ACK, ACK)。
-
第一次挥手(FIN=1,seq=x)
假设客户端想要关闭连接,客户端发送一个 FIN 标志位置为1的包,表示自己已经没有数据可以发送了,但是仍然可以接受数据。
发送完毕后,客户端进入 FIN_WAIT_1 状态。
-
第二次挥手(ACK=1,ACKnum=x+1)
服务器端确认客户端的 FIN 包,发送一个确认包,表明自己接受到了客户端关闭连接的请求,但还没有准备好关闭连接。
发送完毕后,服务器端进入 CLOSE_WAIT 状态,客户端接收到这个确认包之后,进入 FIN_WAIT_2 状态,等待服务器端关闭连接。
-
第三次挥手(FIN=1,seq=y)
服务器端准备好关闭连接时,向客户端发送结束连接请求,FIN 置为1。
发送完毕后,服务器端进入 LAST_ACK 状态,等待来自客户端的最后一个ACK。
-
第四次挥手(ACK=1,ACKnum=y+1)
客户端接收到来自服务器端的关闭请求,发送一个确认包,并进入 TIME_WAIT状态,等待可能出现的要求重传的 ACK 包。
服务器端接收到这个确认包之后,关闭连接,进入 CLOSED 状态。
客户端等待了某个固定时间(两个最大段生命周期,2MSL,2 Maximum Segment Lifetime)之后,没有收到服务器端的 ACK ,认为服务器端已经正常关闭连接,于是自己也关闭连接,进入 CLOSED 状态。
2.HTTP状态码及其含义
1XX:信息状态码
100 Continue 继续,一般在发送post请求时,已发送了http header之后服务端将返回此信息,表示确认,之后发送具体参数信息
2XX:成功状态码
200 OK 正常返回信息
201 Created 请求成功并且服务器创建了新的资源
202 Accepted 服务器已接受请求,但尚未处理
3XX:重定向
301 Moved Permanently 请求的网页已永久移动到新位置。
302 Found 临时性重定向。
303 See Other 临时性重定向,且总是使用 GET 请求新的 URI。
304 Not Modified 自从上次请求后,请求的网页未修改过。
4XX:客户端错误
400 Bad Request 服务器无法理解请求的格式,客户端不应当尝试再次使用相同的内容发起请求。
401 Unauthorized 请求未授权。
403 Forbidden 禁止访问。
404 Not Found 找不到如何与 URI 相匹配的资源。
408 (请求超时) 服务器等候请求时发生超时
5XX: 服务器错误
500 Internal Server Error 最常见的服务器端错误。
501 Internal Server Error 服务器遇到一个错误,使其无法对请求提供服务
502 (错误网关) 服务器作为网关或代理,从上游服务器收到无效响应
503 Service Unavailable 服务器端暂时无法处理请求(可能是过载或维护)。
3.跨域问题的产生,怎么解决它
由于浏览器的 同源策略,在出现 域名、端口、协议有一种不一致时,就会出现跨域,属于浏览器的一种安全限制。
解决跨域问题有很多种方式,常用的就是以下几种:
(1).jsonp 跨域
动态创建script,再请求一个带参网址实现跨域通信.缺点就是只能实现 get 一种请求
(2).document.domain + iframe跨域
两个页面都通过js强制设置document.domain为基础主域,就实现了同域.但是仅限主域相同,子域不同的跨域应用场景
(3).跨域资源共享(CORS)
只服务端设置Access-Control-Allow-Origin即可,前端无须设置,若要带cookie请求:前后端都需要设置
详细参考:www.ruanyifeng.com/blog/2016/0…
(4).nginx反向代理接口跨域
同源策略是浏览器的安全策略,不是HTTP协议的一部分。服务器端调用HTTP接口只是使用HTTP协议,不会执行JS脚本,不需要同源策略,也就不存在跨越问题
5.WebSocket协议跨域
4.前端性能优化
参考 blog.youkuaiyun.com/qfkfw/artic…
5.常见web安全及防护原理
(1).sql注入原理
就是通过把SQL命令插入到Web表单递交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令
总的来说有以下几点
永远不要信任用户的输入,要对用户的输入进行校验,可以通过正则表达式,或限制长度,对单引号和双"-"进行转换等 永远不要使用动态拼装SQL,可以使用参数化的SQL或者直接使用存储过程进行数据查询存取 永远不要使用管理员权限的数据库连接,为每个应用使用单独的权限有限的数据库连接 不要把机密信息明文存放,请加密或者hash掉密码和敏感的信息
(2).XSS原理及防范(Cross Site Scripting,跨站脚本攻击)
-
什么是XSS
XSS 全称“跨站脚本”,是注入攻击的一种。其特点是不对服务器端造成任何伤害,而是通过一些正常的站内交互途径,例如发布评论,提交含有 JavaScript 的内容文本。这时服务器端如果没有过滤或转义掉这些脚本,作为内容发布到了页面上,其他用户访问这个页面的时候就会运行这些脚本。
运行预期之外的脚本带来的后果有很多中,可能只是简单的恶作剧——一个关不掉的窗口:
while (true) { alert("你关不掉我~"); } 复制代码
-
如何防御 XSS 攻击?
理论上,所有可输入的地方没有对输入数据进行处理的话,都会存在XSS漏洞,漏洞的危害取决于攻击代码的威力,攻击代码也不局限于 script。防御 XSS 攻击最简单直接的方法,就是过滤用户的输入。
如果不需要用户输入 HTML,可以直接对用户的输入进行 HTML escape 。下面一小段脚本:
<script>window.location.href=”http://www.baidu.com”;</script> 复制代码
经过 escape 之后就成了:
<script>window.location.href="http://www.baidu.com"</script> 复制代码
它现在会像普通文本一样显示出来,变得无毒无害,不能执行了。
当我们需要用户输入 HTML 的时候,需要对用户输入的内容做更加小心细致的处理。仅仅粗暴地去掉 script 标签是没有用的,任何一个合法 HTML 标签都可以添加 onclick 一类的事件属性来执行 JavaScript。更好的方法可能是,将用户的输入使用 HTML 解析库进行解析,获取其中的数据。然后根据用户原有的标签属性,重新构建 HTML 元素树。构建的过程中,所有的标签、属性都只从白名单中拿取。
(3) CSRF原理及防范(Cross-site request forgery,跨站请求伪造)
-
什么是CSRF?
CSRF(XSRF) 顾名思义,是伪造请求,冒充用户在站内的正常操作。
例如,一论坛网站的发贴是通过 GET 请求访问,点击发贴之后 JS 把发贴内容拼接成目标 URL 并访问:
http://example.com/bbs/create_post.php?title=标题&content=内容 复制代码
那么,我们只需要在论坛中发一帖,包含一链接:
http://example.com/bbs/create_post.php?title=我是脑残&content=哈哈 复制代码
只要有用户点击了这个链接,那么他们的帐户就会在不知情的情况下发布了这一帖子。可能这只是个恶作剧,但是既然发贴的请求可以伪造,那么删帖、转帐、改密码、发邮件全都可以伪造。
-
如何防范 CSRF 攻击?
-
关键操作只接受 POST 请求
-
验证码
CSRF 攻击的过程,往往是在用户不知情的情况下构造网络请求。所以如果使用验证码,那么每次操作都需要用户进行互动,从而简单有效的防御了CSRF攻击。
但是如果你在一个网站作出任何举动都要输入验证码会严重影响用户体验,所以验证码一般只出现在特殊操作里面,或者在注册时候使用。
-
检测 Referer
常见的互联网页面与页面之间是存在联系的,比如你在 www.baidu.com 应该是找不到通往www.google.com 的链接的,再比如你在论坛留言,那么不管你留言后重定向到哪里去了,之前的那个网址一定会包含留言的输入框,这个之前的网址就会保留在新页面头文件的 Referer 中
通过检查 Referer 的值,我们就可以判断这个请求是合法的还是非法的,但是问题出在服务器不是任何时候都能接受到 Referer 的值,所以 Referer Check 一般用于监控 CSRF 攻击的发生,而不用来抵御攻击。
-
Token
目前主流的做法是使用 Token 抵御 CSRF 攻击。下面通过分析 CSRF 攻击来理解为什么 Token 能够有效
CSRF 攻击要成功的条件在于攻击者能够预测所有的参数从而构造出合法的请求。所以根据不可预测性原则,我们可以对参数进行加密从而防止 CSRF 攻击。
另一个更通用的做法是保持原有参数不变,另外添加一个参数 Token,其值是随机的。这样攻击者因为不知道 Token 而无法构造出合法的请求进行攻击。
【Token 使用原则】
- Token 要足够随机————只有这样才算不可预测
- Token 是一次性的,即每次请求成功后要更新Token————这样可以增加攻击难度,增加预测难度
- Token 要注意保密性————敏感操作使用 post,防止 Token 出现在 URL 中
注意:过滤用户输入的内容不能阻挡 csrf,我们需要做的是过滤请求的来源。
-
(4) XSS与CSRF有什么区别吗?
XSS(跨站脚本攻击——Cascading Style Sheets,为不和层叠样式表 缩写混淆,故将跨站脚本攻击缩写为XSS )是获取信息,不需要提前知道其他用户页面的代码和数据包。
CSRF(跨站请求伪造——Cross-site request forgery)是代替用户完成指定的动作,需要知道其他用户页面的代码和数据包。要完成一次CSRF攻击,受害者必须依次完成两个步骤 登录受信任网站A,并在本地生成Cookie 在不登出A的情况下,访问危险网站B
6. http与https
(1) http有什么特点
是互联网上应用最为广泛的一种网络协议,是一个客户端和服务器端请求和应答的标准(TCP),用于从WWW服务器传输超文本到本地浏览器的传输协议,它可以使浏览器更加高效,使网络传输减少.HTTP 协议构建于 TCP/IP 协议之上,是一个应用层协议,默认端口号是 80
-
简单快速:客户向服务器请求服务时,只需传送请求方法和路径
-
灵活:HTTP允许传输任意类型的数据对象。正在传输的类型由 Content-Type 加以标记
-
无状态:HTTP协议是无状态协议( Cookie 的出现)
-
无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接 (深入-持久连接、管线化)
-
如果客户端浏览器支持 Keep-Alive ,那么就在HTTP请求头中添加一个字段 Connection: Keep-Alive,当服务器收到附带有 Connection:Keep-Alive的请求时,它也会在响应头中添加一个同样的字段来使用 Keep-Alive 。这样一来,客户端和服务器之间的HTTP连接就会被保持,不会断开(超过 Keep-Alive 规定的时间,意外断电等情况除外),当客户端发送另外一个请求时,就使用这条已经建立的连接。
-
在 HTTP 1.1 版本中,默认情况下所有连接都被保持,如果加入 "Connection: close" 才关闭。目前大部分浏览器都使用 HTTP 1.1 协议,也就是说默认都会发起 Keep-Alive 的连接请求了,所以是否能完成一个完整的 Keep-Alive 连接就看服务器设置情况。
-
HTTP Keep-Alive 简单说就是保持当前的TCP连接,避免了重新建立连接。
-
HTTP 长连接不可能一直保持,例如 Keep-Alive: timeout=5, max=100,表示这个TCP通道可以保持5秒,max=100,表示这个长连接最多接收100次请求就断开。
-
HTTP 是一个无状态协议,这意味着每个请求都是独立的,Keep-Alive没能改变这个结果。另外,Keep-Alive也不能保证客户端和服务器之间的连接一定是活跃的,在HTTP1.1版本中也如此。唯一能保证的就是当连接被关闭时你能得到一个通知,所以不应该让程序依赖于 Keep-Alive 的保持连接特性,否则会有意想不到的后果。
-
使用长连接之后,客户端、服务端怎么知道本次传输结束呢?两部分:1. 判断传输数据是否达到了Content-Length 指示的大小;2. 动态生成的文件没有 Content-Length ,它是分块传输(chunked),这时候就要根据 chunked 编码来判断,chunked 编码的数据在最后有一个空 chunked 块,表明本次传输数据结束。
-
(2) https有什么特点
是以安全为目标的HTTP通道,简单讲是 HTTP 的安全版,即 HTTP 下加入 SSL 层(Secure Sockets Layer),HTTPS 的安全基础是 SSL ,因此加密的详细内容就需要 SSL
HTTPS加密、加密、及验证过程,如下图所示:
(3) http和https协议有什么区别
-
https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用
-
http 是超文本传输协议,信息是明文传输,https 则是具有安全性的 ssl 加密传输协议
-
http 和 https 使用的是完全不同的连接方式,用的端口也不一样,前者是 80 ,后者是 443
-
http 的连接很简单,是无状态的;HTTPS 协议是由 SSL+HTTP 协议构建的可进行加密传输、身份认证的网络协议,比 http 协议安全
-
HTTP使用TCP三次握手建立连接,客户端和服务器需要交换3个包(可参考 HTTP服务的七层架构技术解析及运用 user-gold-cdn.xitu.io/2019/4/17/1…)
(4) https的工作原理
这个没什么好说的,就是用户在浏览器里输入一个https网址,然后连接到server的443端口。
2、服务端的配置
采用HTTPS协议的服务器必须要有一套数字证书,可以自己制作,也可以向组织申请,区别就是自己颁发的证书需要客户端验证通过,才可以继续访问,而使用受信任的公司申请的证书则不会弹出提示页面(startssl就是个不错的选择,有1年的免费服务)。
这套证书其实就是一对公钥和私钥,如果对公钥和私钥不太理解,可以想象成一把钥匙和一个锁头,只是全世界只有你一个人有这把钥匙,你可以把锁头给别人,别人可以用这个锁把重要的东西锁起来,然后发给你,因为只有你一个人有这把钥匙,所以只有你才能看到被这把锁锁起来的东西。
3、传送证书
这个证书其实就是公钥,只是包含了很多信息,如证书的颁发机构,过期时间等等。
4、客户端解析证书
这部分工作是有客户端的TLS来完成的,首先会验证公钥是否有效,比如颁发机构,过期时间等等,如果发现异常,则会弹出一个警告框,提示证书存在问题。
如果证书没有问题,那么就生成一个随机值,然后用证书对该随机值进行加密,就好像上面说的,把随机值用锁头锁起来,这样除非有钥匙,不然看不到被锁住的内容。
5、传送加密信息
这部分传送的是用证书加密后的随机值,目的就是让服务端得到这个随机值,以后客户端和服务端的通信就可以通过这个随机值来进行加密解密了。
6、服务段解密信息
服务端用私钥解密后,得到了客户端传过来的随机值(私钥),然后把内容通过该值进行对称加密,所谓对称加密就是,将信息和私钥通过某种算法混合在一起,这样除非知道私钥,不然无法获取内容,而正好客户端和服务端都知道这个私钥,所以只要加密算法够彪悍,私钥够复杂,数据就够安全。
7、传输加密后的信息
这部分信息是服务段用私钥加密后的信息,可以在客户端被还原。
8、客户端解密信息
客户端用之前生成的私钥解密服务段传过来的信息,于是获取了解密后的内容,整个过程第三方即使监听到了数据,也束手无策。
7. 网络分层模型——TCP/IP模型和OSI模型
(1) TCP/IP模型
TCP/IP模型分为四层:
- 应用层(Application)
- 传输层(Host-to-Host Transport)
- 互联网层(Internet)
- 网络接口层(Network Interface)
在TCP/IP模型中并不包含物理层。另外,两个重要的协议ARP(Address Resolution Protocol,地址解析协议)和RARP(Reverse Address Resolution Protocol,反向地址转换协议),在OSI模型中一般被认为是在位于第二层数据链路层和第三层网络层之间,而在TCP/IP模型中则位于网络接口层。
(2)OSI模型
详细描述参考: juejin.im/post/5a98e1…
8. IE浏览器兼容问题
参考另一篇总结:juejin.im/post/5cb822…