自用的前端知识点整理笔记(长期更新)
开启面试造火箭模式📔👈点击获得更好的阅读体验
有错误的地方请指出,感激不尽
HTML
你是如何理解HTML语义化的?⭐
总结:用恰当的标签来标记内容。
比如段落就写 p 标签,标题就写 h1 标签,文章就写article标签,视频就写video标签,等等。
优点:
1.让人更容易读懂(增加代码可读性)
2.让搜索引擎更容易读懂(SEO(搜索引擎优化)
Doctype
Doctype声明于文档最前面,告诉浏览器以何种方式来解析文档,这里有两种模式,标准模式和兼容模式。
在标准模式下,浏览器的解析规则都是按照最新的标准进行解析的。而在兼容模式下,浏览器会以向后兼容的方式来模拟老式浏览器的行为,以保证一些老的网站的正确访问。
meta标签
<meta>
元素可提供有关页面的元信息(meta-information),比如页面的描述和关键词,利于搜索引擎检索。
页面描述
<meta name=”description” content=”不超过150个字符”/>
页面关键词者
<meta name=”keywords” content=””/>
网页作者
<meta name=”author” content=”name, email@gmail.com”/>
meta viewport 是做什么用的,怎么写?
viewport 是对移动网页的优化
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1">
width:控制 viewport 的大小,如 device-width 为设备的宽度
maximum-scale=1,minimum-scale=1,即不允许用户缩放
行内元素和块级元素的区别
补充问:为什么图片能设置宽高
·常见块级元素有:html、body、div、header、footer、nav、section、aside、article、p、hr、h1~h6、ul、ol、dl、form、table、tbody、thead、tfoot、tr
等。
·常见行内元素有:span、a、img、textarea、button、input、br、label、select、canvas、progress、cite、code、strong、em、audio、video
等。
而他们明显的区别是:
块级元素:会自动换行,在横向充满其父元素的内容区域,默认独占一行的,可修改宽高。
行内元素:不会自动换行,行内元素左右可以有其他元素,行内元素的宽高大多是auto*auto
。
注意:行内元素设置宽高无效(但是行内置换元素可以设置宽高,下面有详细解释)、设置上下margin无效,设置上下padding类似无效(不影响文档流排列)
另外一种分类方式:可替换元素和不可替换元素的分类
替换元素:替换元素根据其标签和属性来决定元素的具体显示内容,<button><img><input><textarea>
等。替换一般有内在尺寸如img默认的src属性引用的图片的宽高,表单元素如input也有默认的尺寸。img和input的宽高可以设定。
不可替换元素:即将内容直接表现给用户端。
注意:几乎大部分的替换元素都是行内元素,所以说如input、img、textarea是行内元素但是是可以设置宽高的说法。
你用过哪些 HTML 5 标签?
header 网站标志、主导航、搜索框、全站链接
nav 导航
main 页面主要内容
section 具有相似主题的一组内容
aside 侧栏,友链,广告
article 包含像报纸一样的内容,表示文档、页面 或一个独立的容器
footer 页脚
其他像pre预格式化文本,strong,em标记重点内容
特别的:video标签
属性有:autoplay/preload:自动播放/预加载,controls控制条,muted静音
<img>
的title
和alt
有什么区别
title
是[全局属性,用于为元素提供附加的 提示信息。通常当鼠标滑动到元素上的时候显示。alt
是<img>
的特有属性,是图片内容的等价描述,用于图片无法加载时显示。可提图片高可访问性,除了纯装饰图片外都必须设置有意义的值,搜索引擎会重点分析。
script标签的async 和 defer 的作用是什么?有什么区别?
-
脚本没有 defer 或 async,浏览器会立即加载并执行指定的脚本,也就是说不等待后续载入的文档元素,读到就加载并执
行。 -
defer 属性表示延迟执行引入的 JavaScript,即这段 JavaScript 加载时 HTML 并未停止解析,这两个过程是并行的。
当整个 document 解析完毕后再执行脚本文件,在 DOMContentLoaded 事件触发之前完成。多个脚本按顺序执行。 -
async 属性表示异步执行引入的 JavaScript,与 defer 的区别在于,如果已经加载好,就会开始执行,也就是说它的执
行仍然会阻塞文档的解析,只是它的加载过程不会阻塞。多个脚本的执行顺序无法保证。
如何进行响应式开发
- 移动端优先。由于移动端页面限制条件比较多,如视口面积小、网速慢、考虑 touch 事件等等因素,从移动端页面扩展到 PC 端页面要更容易一些
- 使用媒体查询根据不同的视口宽度调整样式 @media
- 使用流式布局来保证布局会随着视口宽度的改变进行调整
- 调整 meta viewport,避免浏览器使用虚拟 viewport
CSS
CSS3 有哪些新特性?
选择器 E:last-child E:nth-child(n)
圆角 border-radius:8px
阴影 box-shadow:: 水平方向的偏移量 垂直方向的偏移量 模糊程度 扩展程度 颜色 是否具有内阴影
背景 background-image:url('1.jpg),url('2.jpg')
渐变 background:linear-gradient(方向,开始颜色,结束颜色)
过渡动画 transition:property duration timing-function delay
动画 @keyframes动画帧 animation:name duration timing-function delay interation-count direction play-state
变换 rotate()旋转 translate()平移 scale( )缩放 skew()扭曲/倾斜
媒体查询 @media
两种盒模型box-sizing⭐
**content-box **
默认值,其中设置的width 和height是只包含了内容的宽高(content),但不包含内边距(padding)、边框(border)、外边距(margin)
border-box
其中设置的width和height是包含内容(content)、和padding、border但是不包含margin
如何垂直居中 ⭐
一般常见的几种居中的方法有:
对于宽高固定的元素
(1)我们可以利用margin:0auto来实现元素的水平居中。
(2)利用绝对定位,设置四个方向的值都为0,并将margin设置为auto,由于宽高固定,因此对应方向实现平分,可以实现水
平和垂直方向上的居中。
(3)利用绝对定位,先将元素的左上角通过top:50%和left:50%定位到页面的中心,然后再通过margin负值来调整元素
的中心点到页面的中心。
(4)利用绝对定位,先将元素的左上角通过top:50%和left:50%定位到页面的中心,然后再通过translate来调整元素
的中心点到页面的中心。
(5)使用flex布局,通过align-items:center和justify-content:center设置容器的垂直和水平方向上为居中对
齐,然后它的子元素也可以实现垂直和水平的居中。
对于宽高不定的元素,上面的后面两种方法,可以实现元素的垂直和水平的居中。
width: 300px;
position: absolute;
top: 50%;
left: 50%;
margin-left: -150px;
height: 100px;
margin-top: -50px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
display: flex;
justify-content: center;
align-items: center;
position: absolute;
width: 300px;
height: 200px;
margin: auto;
top: 0;
bottom: 0;
left: 0;
right: 0;
flex 怎么用,常用属性有哪些?
Flex属性 flex-grow
(放大比例),flex-shrink
(缩小比例),和 flex-basis
(占据空间) 属性
order
(排列顺序) flex-direction
主轴的方向 flex-flow
如果一排放不下如何换行
justify-content: flex-start | flex-end | center | space-around | space-between
align-items: stretch | flex-start | flex-end | center
flex-wrap: nowrap | wrap | wrap-reverse;
BFC 是什么?⭐
block format context ,块级格式化上下文
一块独立渲染区域,内部元素的渲染不会影响边界以外的元素
触发条件:
- 浮动元素(元素的 float 不是 none)
- 绝对定位元素(元素的 position 为 absolute 或 fixed)
- 行内块元素
- overflow 值不为 visible 的块元素
- 弹性元素(display为 flex 或 inline-flex元素的直接子元素)
作用:
- 可以包含浮动元素
- 不被浮动元素覆盖
- 阻止父子元素的 margin 折叠
CSS 选择器优先级
内联样式 > 内部样式 > 外部样式
选择器的优先权:
内联样式1000
ID选择器100
类选择器 10=伪类
元素选择器 1
总结:
- 越具体优先级越高
- 同样优先级写在后面的覆盖写在前面的
- !important优先级最高,但要少用
清除浮动⭐
.clearfix:after{content: '';display: block; /*或者 table*/clear: both;}.clearfix{zoom: 1; /* IE 兼容*/}
其他方法:父级div定义overflow:hidden,定义overflow:auto当内部超出时会有滚动条
position 的值 relative 和 absolute 定位原点是?
relative定位的元素,是相对于元素本身的正常位置来进行定位的。
absolute定位的元素,是相对于它的第一个position值为absolute或者relative的父元素的paddingbox来进行定位的。
补充:
fixed(老IE不支持)
生成绝对定位的元素,相对于浏览器窗口进行定位。
static
默认值。没有定位,元素出现在正常的流中
CSS隐藏元素的方式
-
使用 **display:none;**隐藏元素,渲染树不会包含该渲染对象,因此该元素不会在页面中占据位置,也不会响应绑定的监听事件。
-
使用 **visibility:hidden;**隐藏元素。元素在页面中仍占据空间,但是不会响应绑定的监听事件。
-
使用 **opacity:0;**将元素的透明度设置为 0,以此来实现元素的隐藏。元素在页面中仍然占据空间,并且能够响应元素绑定的监听事件。
-
通过使用绝对定位将元素移除可视区域内,以此来实现元素的隐藏。
-
通过z-index 负值,来使其他元素遮盖住该元素,以此来实现隐藏。
-
通过 **transform:scale(0,0)**来将元素缩放为 0,以此来实现元素的隐藏。这种方法下,元素仍在页面中占据位置,但是不会响应绑定的监听事件。
em
和 rem
的区别
1em,等于本元素的字体大小,所以在不同的元素里1em的绝对大小是不一样的。
而1rem,等于根元素的字体大小,在一个页面中,无论在哪个元素上1rem都是一样的。
em 适合于用在需要大小需要跟随字体变化的属性上,比如padding、margin、height、width等等,元素继承了不同的字体大小,这些属性最好也能跟着变化;
rem适用于字体,这样就可以通过改变根元素的字体大小来改变整个页面的字体大小。
css两栏布局的实现
以左边宽度固定为 200px 为例
(1)利用浮动,将左边元素宽度设置为 200px,并且设置向左浮动。将右边元素的 margin-left 设置为 200px,宽度设置为 auto(默认为 auto,撑满整个父元素)。
(2)第二种是利用 flex 布局,将左边元素的放大和缩小比例设置为 0,基础大小设置为 200px。将右边的元素的放大比例设置为 1,缩小比例设置为 1,基础大小设置为 auto。
(3)第三种是利用绝对定位布局的方式,将父级元素设置相对定位。左边元素设置为 absolute 定位,并且宽度设置为 200px。将右边元素的 margin-left 的值设置为 200px。
(4)第四种还是利用绝对定位的方式,将父级元素设置为相对定位。左边元素宽度设置为 200px,右边元素设置为绝对定位,左边定位为 200px,其余方向定位为 0。
css 三栏布局的实现
这里以左边宽度固定为100px,右边宽度固定为200px为例。
(1)利用绝对定位的方式,左右两栏设置为绝对定位,中间设置对应方向大小的margin的值。
(2)利用flex布局的方式,左右两栏的放大和缩小比例都设置为0,基础大小设置为固定的大小,中间一栏设置为auto。
(3)利用浮动的方式,左右两栏设置固定大小,并设置对应方向的浮动。中间一栏设置左右两个方向的margin值,注意这种方式,中间一栏必须放到最后。
(4)**圣杯布局,利用浮动和负边距来实现。**父级元素设置左右的padding,三列均设置向左浮动,中间一列放在最前面,宽度设置为父级元素的宽度,因此后面两列都被挤到了下一行,通过设置margin负值将其移动到上一行,再利用相对定位,定位到两边。
(5)双飞翼布局,双飞翼布局相对于圣杯布局来说,左右位置的保留是通过中间列的margin值来实现的,而不是通过父元素的padding来实现的。本质上来说,也是通过浮动和外边距负值来实现的。
圣杯布局和双飞翼布局 ⭐
圣杯布局
<div id="header"></div>
<div id="container"><div id="center" class="column"></div><div id="left" class="column"></div><div id="right" class="column"></div>
</div>
<div id="footer"></div>
body {min-width: 550px;
}#container {padding-left: 200px; padding-right: 150px;
}#container .column {float: left;
}#center {width: 100%;
}#left {width: 200px; margin-left: -100%;position: relative;right: 200px;
}#right {width: 150px; margin-right: -150px;
}#footer {clear: both;
}
双飞翼布局
<body><div id="header"></div><div id="container" class="column"><div id="center"></div></div><div id="left" class="column"></div><div id="right" class="column"></div><div id="footer"></div>
<body>
body {min-width: 500px;
}#container {width: 100%;
}.column {float: left;
}#center {margin-left: 200px;margin-right: 150px;
}#left {width: 200px; margin-left: -100%;
}#right {width: 150px; margin-left: -150px;
}#footer {clear: both;
}
CSS动画
-
animation属性
div{animation: myfirst 5s linear 2s infinite alternate; } @keyframes myfirst{0% {background: red; left:0px; top:0px;}25% {background: yellow; left:200px; top:0px;}50% {background: blue; left:200px; top:200px;}75% {background: green; left:0px; top:200px;}100% {background: red; left:0px; top:0px;} }
-
transtion属性
语法:transition: property duration timing-function delay; //宽度从100变到300 div{width:100px;height:100px;background:blue;transition:width 2s; }div:hover{width:300px; }
CSSSprites原理和优势
将一个页面涉及到的所有图片都包含到一张大图中去,然后利用CSS的background-image,background-repeat,background
-position的组合进行背景定位。
优点:
减少HTTP请求数,极大地提高页面加载速度
增加图片信息重复度,提高压缩比,减少图片大小
更换风格方便,只需在一张或几张图片上修改颜色或样式即可实现
缺点:
图片合并麻烦
维护麻烦,修改一个图片可能需要重新布局整个图片,样式
CSS实现三角形
.arrow{width:0;height:0;border-right:50px solid transparent;border-left:50px solid transparent;border-bottom:50px solid red;
}
::before 和:after 中双冒号和单冒号有什么区别?
在css3中使用单冒号来表示伪类,用双冒号来表示伪元素。但是为了兼容已有的伪元素的写法,在一些浏览器中也可以使用单冒号
来表示伪元素。
伪类一般匹配的是元素的一些特殊状态,如hover、link等,而伪元素一般匹配的特殊的位置,比如after、before等。
原生JS
ES6 语法知道哪些,分别怎么用
https://fangyinghang.com/es-6-tutorials/
let const 展开运算符 解构赋值 模块导入导出 class继承 promise symbol,set数据类型
var、let 及 const 区别(相当于变量提升题目)⭐
以下需要记牢(五点):
-
var声明的变量会挂载在window上,而let和const声明的变量不会
-
var声明变量存在变量提升,let和const不存在变量提升
-
let和const声明形成块作用域,而var不存在此作用域
-
同一作用域下let和const不能声明同名变量,而var可以
-
let、const存在暂存死区
JavaScript 中,函数及变量的声明都将被提升到函数的最顶部。
JavaScript 中,变量可以在使用后声明,也就是变量可以先使用再声明。
console.log(a) // undefined
var a = 1
- 函数提升优先于变量提升,函数提升会把整个函数挪到作用域顶部,变量提升只会把声明挪到作用域顶部
var
存在提升,我们能在声明之前使用。let
、const
因为暂时性死区的原因,不能在声明前使用var
在全局作用域下声明变量会导致变量挂载在window
上,其他两者不会let
和const
作用基本一致,但是后者声明的变量不能再次赋值
为什么0.1+0.2!==0.3
因为 0.1 和 0.2 被转成二进制后会无限循环,由于JS中位数的限制多余的位数会被截掉,就出现了精度损失。
与=的区别
“==” 就代表会先把两端的变量试图转换成相同类型,然后再比较;
“===” 就代表会直接去比较类型是否相同,如果类型相同则继续比较值是否相同。
==操作符的强制类型转换规则,看看就好
(1)字符串和数字之间的相等比较,将字符串转换为数字之后再进行比较。(2)其他类型和布尔类型之间的相等比较,先将布尔值转换为数字后,再应用其他规则进行比较。(3)null 和 undefined 之间的相等比较,结果为真。其他值和它们进行比较都返回假值。(4)对象和非对象之间的相等比较,对象先调用 ToPrimitive 抽象操作后,再进行比较。(5)如果一个操作值为 NaN ,则相等比较返回 false( NaN 本身也不等于 NaN )。(6)如果两个操作值都是对象,则比较它们是不是指向同一个对象。如果两个操作数都指向同一个对象,则相等操作符返回 true,否则,返回 false。
Map、WeakMap、Set、WeakSet
Set
-
成员不能重复,成员可以使任意类型的值
-
只有健值,没有健名,有点类似数组。
-
可以遍历,方法有add, delete,has
WeakSet
-
成员只能是对象
-
成员都是弱引用,垃圾回收机制不考虑WeakSet对该对象的引用
-
不能遍历,方法有add, delete,has
Map
-
本质上是健值对的集合,类似集合,可以接受任何类型的值作为键名
-
可以遍历,方法很多,可以干跟各种数据格式转换
WeakMap
-
只接受对象作为健名(null除外),不接受其他类型的值作为健名
-
健名所指向的对象,不计入垃圾回收机制
-
不能遍历,方法同get,set,has,delete
JS的数据类型及存放位置
-
Number
-
string
-
Boolean
-
Null
-
Undefined
-
Symbol(ES6中新引入的新类型—可以用来创建匿名的对象属性,代表创建后独一无二且不可变的数据类型,它的出现我认为主要是为了解决可能出现的全局变量冲突的问题。)
-
BigInt(它可以表示任意精度格式的整数)
原始类型存储的都是值,是没有函数可以调用的,
typeof null
会输出object
,是JS存在的一个bug
(一共有8种数据类型,其中7种是原始类型)
栈:原始数据类型(Undefined、Null、Boolean、Number、String)
堆:引用数据类型(对象、数组和函数)
判断数据类型
typeof(原始数据类型)
它返回表示数据类型的字符串
typeof(1) //number
typeof("1") //string
typeof(true) //boolean
typeof(undefined) //undefined
typeof(null) //object
**注释:**您也许会问,为什么 typeof 运算符对于 null 值会返回 “object”。这实际上是 JavaScript 最初实现中的一个错误,然后被 ECMAScript 沿用了。现在,null 被认为是对象的占位符,从而解释了这一矛盾,但从技术上来说,它仍然是原始值。
instanceof(引用类型)
原理 因为A instanceof B 可以判断A是不是B的实例,返回一个布尔值,由构造类型判断出数据类型
console.log(arr instanceof Array ); // true
console.log(date instanceof Date ); // true
console.log(fn instanceof Function ); // true
//注意: instanceof 后面一定要是对象类型,大小写不能写错,该方法试用一些条件选择或分支
使用Object.prototype.toString.call()
来判断值的类型
可以通用的来判断原始数据类型和引用数据类型
Object.prototype.toString.call({name:'Jack'}) // [object Object]
Object.prototype.toString.call(function(){}) // [object Function]
Object.prototype.toString.call(/name/) // [object RegExp]
而对于基本类型:
Object.prototype.toString.call('abc') // [object String]
Object.prototype.toString.call(12) // [object Number]
Object.prototype.toString.call(true) // [object Boolean]
基本类型值是没有构造函数的,为什么也能返回构造函数名呢?这是因为在toString
被调用时 JavaScript 将基本类型值转换成了包装类型。
而对于null
和undefined
:
Object.prototype.toString.call( null ); // "[object Null]"
Object.prototype.toString.call( undefined ); // "[object Undefined]"
虽然 JavaScript 中没有Null()
和Undefined
构造器,但是 JavaScript 也为我们处理这这两种情况。
什么是原型,什么是原型链⭐
原型
我们创建的每个函数都有一个 prototype(原型)
属性,这个属性是一个指针,指向一个原型对象。其实原型对象就只是个普通对象,里面存放着所有实例对象需要共享的属性和方法!
原型链
1.每一个构造函数都有一个prototype属性,称之为显式原型;
2.每一个引用类型都有一个__proto__属性,称之为隐式原型;
3.每一个引用类型的__proto__指向他的构造函数的prototype;
每一个构造函数也有自己的__proto__,因为函数本身就是一个引用类型,这个构造函数的__proto__又指向他自己构造函数的prototype,这样一级一级往上找就形成了原型链;
{:height=“50%” width=“50%”}
同步异步的区别
异步不会阻塞代码的执行,使用场景:网络请求&定时任务
同步会阻塞代码执行
同步异步
首先来解释同步和异步的概念,这两个概念与消息的通知机制有关。也就是同步与异步主要是从消息通知机制角度来说的。
所谓同步就是一个任务的完成需要依赖另外一个任务时,只有等待被依赖的任务完成后,依赖的任务才能算完成,这是一种可靠的任务序列。要么成功都成功,失败都失败,两个任务的状态可以保持一致。
所谓异步是不需要等待被依赖的任务完成,只是通知被依赖的任务要完成什么工作,依赖的任务也立即执行,只要自己完成了整个任务就算完成了。至于被依赖的任务最终是否真正完成,依赖它的任务无法确定,所以它是不可靠的任务序列。
阻塞非阻塞
阻塞和非阻塞这两个概念与程序(线程)等待消息通知(无所谓同步或者异步)时的状态有关。也就是说阻塞与非阻塞主要是程序(线程)等待消息通知时的状态角度来说的。
阻塞调用是指调用结果返回之前,当前线程会被挂起,一直处于等待消息通知,不能够执行其他业务。函数只有在得到结果之后才会返回。
异步编程方案(有待完善)
1.JS 异步编程进化史:callback -> promise -> generator(*|yield) -> async + await
2.async/await 函数的实现,就是将 Generator 函数和自动执行器,包装在一个函数里。
3.async/await可以说是异步终极解决方案了。
Promise原理
阮一峰ES6中的解释:
Promise 是异步编程的一种解决方案,解决了回调地狱的问题
所谓Promise
,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
特点
- 对象的状态不受外界影响。
Promise
对象代表一个异步操作,有三种状态:pending
(进行中)、fulfilled
(已成功)和rejected
(已失败)。 - 一旦状态改变,就不会再变
这段代码里的 this 是什么?⭐
this的值是在函数执行时确定的
- fn() (比如setInterval())
this => window/global - obj.fn()
this => obj - fn.call/apply/bind(xx)
this => xx - new Fn()
this => 新的对象 - fn = ()=> {}
this => 外面的 this
bind、call、apply的区别
call
和apply
改变了函数的this上下文后便执行该函数,而bind
则是返回改变了上下文后的一个函数。
他们俩之间的差别在于参数的区别,call
和apply
的第一个参数都是要改变上下文的对象,而call
从第二个参数开始以参数列表的形式展现,apply
则是把参数放在一个数组里面作为它的第二个参数。
fn.call(this,p1,p2,p3)
fn.apply(this,arguments)
闭包/立即执行函数是什么?⭐
闭包的定义:其实很简单:函数 A 内部有一个函数 B,函数 B 可以访问到函数 A 中的变量,那么函数 B 就是闭包。
//闭包隐藏数据,只提供API
function createCache(){const data={} //闭包中的数据,被隐藏,不被外接访问return{set:function(key,val){data[key]=val},get:function(key){return data[key] }}
}const c = createCache()
c.set('a',100)
console.log(c.get('a'))
闭包的作用
1.可以在函数外调用闭包函数,访问到函数内的变量,可以用这种方法来创建私有变量
2.保护变量不被内存回收机制回收
闭包的缺点
闭包长期占用内存,内存消耗很大,可能导致内存泄露
立即执行函数就是
- 声明一个匿名函数
- 马上调用这个匿名函数
只有一个作用:创建一个独立的作用域。
async/await 怎么用,如何捕获异常?
async
ES2017 标准引入了 async 函数,使得异步操作变得更加方便。
async 函数是什么?一句话,它就是 Generator 函数的语法糖。
async
函数就是将 Generator 函数的星号(*
)替换成async
,将yield
替换成await
,仅此而已。
await
正常情况下,await
命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。
await
命令只能用在async
函数之中,如果用在普通函数,就会报错。
await
命令后面的Promise
对象,运行结果可能是rejected
,所以最好把await
命令放在try...catch
代码块中。
如何用正则实现 trim()?
function trim(string){return string.replace(/^\s+|\s+$/g, '')
}
垃圾回收机制⭐(有待补充)
垃圾回收的意思可以理解为,将已经不需要的内存释放
一个主要的方法就是:找出用不到的对象,然后删除它
//假设有一个对象a,占用了100m内存
var a = {/*我占用了100M内存*/}
a = null // 浏览器就会垃圾回收掉那100m内存 什么时候回收不确定
总结来说就是:把所有指向这块内存的变量全部置为null
-
什么是垃圾(没有被引用的是垃圾,如果有几个对象相互引用形成环,那也是垃圾)
-
如何捡垃圾(遍历和计数,只是不同的算法而已)(从全局作用域开始,把所有遇到的变量都标记一下,如果这些变量引用了其他变量,那就再标记,直到早不到新的对象。标记完后将所有没有标记的对象清除掉)(另一种方法计数标记法)
如何捕获JS中的异常
try{//todo
}catch(ex){console.error(ex) //手动捕获
}finally{//todo
}
//自动捕获
window.onerror=function(message,source,lineNum,colNum,error){//第一,对于跨域的js,如cdn的,不会有详细的报错信息//第二,对于压缩的js,还要配合sourceMap反查到未压缩的代码的行列
}
requestAnimationFrame的优势是什么?
告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。不需要设置时间间隔,是由系统的时间间隔定义的。大多数浏览器的刷新频率是60Hz(每秒钟反复绘制60次),循环间隔是1000/60,约等于16.7ms。不需要调用者指定帧速率,浏览器会自行决定最佳的帧效率。只被执行一次,这样就不会引起丢帧现象,也不会导致动画出现卡顿的问题。
语法:
window.requestanimationframe(callback);
参数callback:下一次重绘之前更新动画帧所调用的函数(即上面所说的回调函数)。
范例
var start = null;
var element = document.getElementById('SomeElementYouWantToAnimate');
element.style.position = 'absolute';function step(timestamp) {if (!start) start = timestamp;var progress = timestamp - start;element.style.left = Math.min(progress / 10, 200) + 'px';if (progress < 2000) {window.requestAnimationFrame(step);}
}window.requestAnimationFrame(step);
说一下事件循环(Event Loop)?
js
是单线程的,所以一次只能执行一个任务,当一个任务需要很长时间时,主线程一直等待任务的执行完成,在执行下个任务是很浪费资源的。- JavaScript在处理异步操作时,利用的是事件循环机制。
所以,js
中任务就被分成两种,一种是同步任务,一种是异步任务。执行步骤如下图所示:
为了更精细的区分任务,js
中可以将异步任务划分为宏任务和微任务。
宏任务(macro-task): 同步 script (整体代码),setTimeout 回调函数, setInterval 回调函数, I/O, UI rendering;
微任务(micro-task): process.nextTick, Promise 回调函数,Object.observe,MutationObserver
其执行的顺序是这样的:
- 首先 JavaScript 引擎会执行一个宏任务,注意这个宏任务一般是指主干代码本身,也就是目前的同步代码
- 执行过程中如果遇到微任务,就把它添加到微任务任务队列中
- 宏任务执行完成后,立即执行当前微任务队列中的微任务,直到微任务队列被清空
- 微任务执行完成后,开始执行下一个宏任务
- 如此循环往复,直到宏任务和微任务被清空
Node.js也是单线程的Event Loop,但是它的运行机制不同于浏览器环境。
DOM
property 和 attribute
property:修改对象属性,不会体现到html结构中
attribute:修改html属性,会改变html结构
事件绑定的方式
嵌入dom
<button onclick="func()">按钮</button>
直接绑定
btn.onclick = function(){}
事件监听
btn.addEventListener('click',function(){},false) 一般是false只冒泡
btn.attachEvent('click',handle)
DOM事件流与事件委托(代理)
DOM事件流
当事件发生在某个DOM节点上时,事件在DOM结构中进行一级一级的传递,这便形成了“流”,事件流便描述了从页面中接收事件的顺序。
捕获与冒泡
在捕获阶段,事件由window
对象开始,一级一级地向下传递,直到传递到最具体的button
对象上。
事件冒泡阶段与捕获阶段恰好相反,冒泡阶段是从最具体的目标对象开始,一层一层地向上传递,直到window
对象。
事件委托
事件委托又叫事件代理,就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。
ul.addEventListener('click', function(e){if(e.target.tagName.toLowerCase() === 'li'){fn() // 执行某个函数}})
bug 在于,如果用户点击的是 li 里面的 span,就没法触发 fn,这显然不对。
高级版:点击 span 后,递归遍历 span 的祖先元素看其中有没有 ul 里面的 li。
很好记忆的例子:
简单说,事件委托就是把本来该自己接收的事件委托给自己的上级(父级,祖父级等等)的某个节点,让自己的“长辈们”帮忙盯着,一旦有事件触发,再由“长辈们”告诉自己:“喂,孙子,有人找你~~”。
恩,差不多就是这么个意思,可怜天下父母心。
JS操作DOM的方法
节点查找(笔试常考)
document.getElementById :根据ID查找元素,大小写敏感,如果有多个结果,只返回第一个;document.getElementsByClassName :根据类名查找元素,多个类名用空格分隔,返回一个 HTMLCollection 。注意兼容性为IE9+(含)。另外,不仅仅是document,其它元素也支持 getElementsByClassName 方法;document.getElementsByTagName :根据标签查找元素, * 表示查询所有标签,返回一个 HTMLCollection 。document.getElementsByName :根据元素的name属性查找,返回一个 NodeList 。document.querySelector :返回单个Node,IE8+(含),如果匹配到多个结果,只返回第一个。document.querySelectorAll :返回一个 NodeList ,IE8+(含)。document.forms :获取当前页面所有form,返回一个 HTMLCollection ;
DOM元素的基本操作
(1).创建:createElement('p');
(2).插入:parentNode.appendChild('p');
(3).删除:removeChild)('p');
(4).更新:replaceChild('p',old);
HTTP/浏览器
HTTP 状态码知道哪些?分别什么意思?
2XX 成功
- 200 OK
- 204 No Content
- 206 Partial Content
3XX 重定向
- 301 永久重定向
- 302 临时重定向
- 304 资源未修改
4XX 客户端错误
- 400 请求报文中有语法错误
- 401 请求未经授权
- 403 服务器拒绝访问
- 404 资源未找到
5XX 服务器错误
- 500 仅仅告诉你服务器出错了,出了啥错咱也不知道
- 502 错误网关,无效响应
- 503 服务器忙碌无法响应
HTTP请求方法
GET:获取资源
POST:传输实体文本
HEAD:获得报文首部
PUT:传输文件
DELETE:删除文件
OPTIONS:询问支持的方法
TRACE:追踪路径
CONNECT:要求用隧道协议连接代理
HTTPS与HTTP的一些区别
- HTTPS协议需要到CA申请证书,一般免费证书很少,需要交费。
- HTTPS相对于HTTP加入了ssl层,所有传输的内容都经过加密的,安全但是耗时多。
- HTTP和HTTPS使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
CA SSL 端口
HTTP2.0的特性
-
内容安全:http2.0是基于https的
-
二进制格式:http1.X的解析是基于文本的,http2.0将所有的传输信息分割为更小的消息和帧,并对他们采用二进制格式编码
-
多路复用——只需一个连接即可实现并行(请求管道化,防止队头阻塞,多个请求合并到一个TCP)
补充:HTTP2 主要解决的问题也是 TCP连接复用。但它比 keep-alive 更彻底,类似于通信工程里的时分复用,多个请求可以同时发送(不分先后),同时响应,解决了 队头阻塞(HOL blocking)的问题,极大提高效率。
keep-alive 的 HTTP pipelining 相当于单线程的,而 HTTP2 相当于并发。
TCP建立连接的三次握手过程⭐
[https://www.zhihu.com/search?type=content&q=%E4%B8%89%E6%AC%A1%E6%8F%A1%E6%89%8B%204%E6%AC%A1%E6%8C%A5%E6%89%8B](https://www.zhihu.com/search?type=content&q=三次握手 4次挥手)
三次握手:
- 首先服务器端处于LISTEN状态。
- 当客户端想要建立连接时,他将发送一个SYN包,序列号假如为x。客户端进入SYN_SENT状态。
- 当服务器端收到了这个SYN包,如果服务器同意建立连接,他将发送一个SYN,ACK包,序列号假如为y,确认号为x+1。服务器端进入SYN_RECD状态。
- 当客户端收到了服务器端的SYN,ACK包,它将再次确认,向服务器发送一个ACK包,序列号为x+1,确认号为y+1。此时客户端进入ESTABLISHED状态。
- 当服务器端收到了客户端的ACK包,也将进入ESTABLISHED状态。
断开链接四次挥手⭐
刚开始双方都处于 establised 状态,假如是客户端先发起关闭请求,则:
-
第一次挥手:客户端发送一个 FIN 报文,报文中会指定一个序列号x。此时客户端处于FIN_WAIT1(终止等待1)状态。
-
第二次挥手:服务端收到 FIN 之后,会发送 ACK 报文,且把x+ 1 作为 ACK 报文的序列号值,表明已经收到客户端的报文了,此时服务端处于 CLOSE_WAIT状态。
-
第三次挥手:如果服务端也想断开连接了,和客户端的第一次挥手一样,发给 FIN 报文,且指定一个序列号y。此时服务端处于 LAST_ACK 的状态。
-
第四次挥手:客户端收到 FIN 之后,一样发送一个 ACK 报文作为应答,且把服务端的序列号值 y+ 1 作为自己 ACK 报文的序列号值,此时客户端处于 TIME_WAIT 状态。需要过一阵子以确保服务端收到自己的 ACK 报文之后才会进入CLOSED 状态
-
服务端收到 ACK 报文之后,就处于关闭连接了,处于 CLOSED 状态。
SSL/TLS 握手过程(HTTPS通信过程)
阮一峰的例子:
客户端:爱丽丝 、 服务端:鲍勃
-
第一步,爱丽丝给出协议版本号、一个客户端生成的随机数(Client random),以及客户端支持的加密方法。
-
第二步,鲍勃确认双方使用的加密方法,并给出数字证书、以及一个服务器生成的随机数(Server random)。
-
第三步,爱丽丝确认数字证书有效,然后生成一个新的随机数(Premaster secret),并使用数字证书中的公钥,加密这个随机数,发给鲍勃。
-
第四步,鲍勃使用自己的私钥,获取爱丽丝发来的随机数(即Premaster secret)。
-
第五步,爱丽丝和鲍勃根据约定的加密方法,使用前面的三个随机数,生成"对话密钥"(session key),用来加密接下来的整个对话过程。
GET 和 POST 的区别
语义上的区别,get用于获取数据,post用于提交数据。
-
Post多用于副作用(对服务器的资源进行改变),Get多用于无副作用
-
Post 相对 Get 安全一点点,因为GET请求的数据会附在URL之后,以?分割URL和传输数据,参数之间以&相连,
POST把提交的数据则放置在是HTTP包的包体中。post易于防止CSRF。
-
GET的长度受限于url的长度,而url的长度限制是特定的浏览器和服务器设置的,理论上GET的长度可以无限长。
-
Post 支持更多的编码类型且不对数据类型限制,GET 只能进行 URL 编码,只能接收 ASCII 字符
-
私密性的信息请求使用post,查询信息和可以想要通过url分享的信息使用get。
副作用 参数位置 长度 编码类型
跨域及解决办法
浏览器遵循同源政策(scheme(协议)、host(主机)和port(端口)都相同则为同源)
当浏览器向目标 URI
发Ajax
请求时,只要当前 URL
和目标 URL
不同源,则产生跨域,被称为跨域请求。
目的 主要是用来防止 CSRF 攻击的。简单点说,CSRF 攻击是利用用户的登录态发起恶意请求。
解决
如果是像解决 ajax 无法提交跨域请求的问题,我们可以使用 jsonp、cors、websocket 协议、服务器代理来解决问题。
- jsonp ,允许 script 加载第三方资源 只支持get。JSONP 本质不是 Ajax 请求,是 script 标签请求。JSONP 请求本质上是利用了 “Ajax请求会受到同源策略限制,而 script 标签请求不会” 这一点来绕过同源策略。
- CORS 前后端协作设置请求头部,Access-Control-Allow-Origin 等头部信息
- 反向代理(nginx 服务内部配置 Access-Control-Allow-Origin )
- 使用 websocket 协议,这个协议没有同源限制。
如果只是想要实现主域名下的不同子域名的跨域操作,我们可以使用设置 document.domain 来解决。
-
document.domain + iframe跨域
-
location.hash
-
window.name + iframe跨域
-
postMessage 跨域
补充:加载图片css js 可无视同源策略,需server端同意
<img/>
----可用于统计打点
<link/> <script/>
----可使用CDN,CDN一般都是外域
<script/>
—可实现JSONP
CORS(跨域资源共享)
CORS 全称是跨域资源共享(Cross-Origin Resource Sharing),是一种 AJAX 跨域请求资源的方式。
CORS与JSONP的使用目的相同,但是比JSONP更强大。JSONP只支持GET请求,CORS支持所有类型的HTTP请求。JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。
CORS 需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,因此我们只需要在服务器端配置就行。浏览器将 CORS 请求分成两类:简单请求和非简单请求。
对于简单请求,浏览器直接发出 CORS 请求。具体来说,就是会在头信息之中,增加一个 Origin 字段。Origin 字段用来说明本次请求来自哪个源。服务器根据这个值,决定是否同意这次请求。对于如果 Origin 指定的源,不在许可范围内,服务器会返回一个正常的 HTTP 回应。浏览器发现,这个回应的头信息没有包含 Access-Control-Allow-Origin 字段,就知道出错了,从而抛出一个错误,ajax 不会收到响应信息。如果成功的话会包含一些以 Access-Control- 开头的字段。
非简单请求,浏览器会先发出一次预检请求,来判断该域名是否在服务器的白名单中,如果收到肯定回复后才会发起请求。
浏览器将CORS请求分成两类:简单请求和非简单请求
只要同时满足以下两大条件,就属于简单请求
- 请求方法是以下三种方法之一:
HEAD
GET
POST
- HTTP的头信息不超出以下几种字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
不同时满足上面两个条件,就属于非简单请求。
有几种方式可以实现存储功能,分别有什么优缺点?
特性 | cookie | localStorage | sessionStorage | indexDB |
---|---|---|---|---|
数据生命周期 | 一般由服务器生成,可以设置过期时间 | 除非被清理,否则一直存在 | 页面关闭就清理 | 除非被清理,否则一直存在 |
数据存储大小 | 4K | 5M | 5M | 无限 |
与服务端通信 | 每次都会携带在 header 中,对于请求性能影响 | 不参与 | 不参与 | 不参与 |
说一下cookie
通俗地说,就是当一个用户通过 HTTP 协议访问一个服务器的时候,这个服务器会将一些 Key/Value 键值对返回给客户端浏览器,并给这些数据加上一些限制条件,在条件符合时这个用户下次访问这个服务器的时候,数据又被完整地带回给服务器。
补充:
如果您在cookie中设置了HttpOnly属性,那么通过js脚本将无法读取到cookie信息,这样能有效的防止XSS攻击
Web storage和cookie的区别
1、Web Storage的概念和cookie相似,区别是它是为了更大容量存储设计的。Cookie的大小是受限的,并且每次你请求一个新的页面的时候Cookie都会被发送过去,这样无形中浪费了带宽,另外cookie还需要指定作用域,不可以跨域调用。
2、除此之外,Web Storage拥有setItem,getItem,removeItem,clear等方法,不像cookie需要前端开发者自己封装setCookie,getCookie。
3、但是cookie也是不可以或缺的:cookie的作用是与服务器进行交互,作为HTTP规范的一部分而存在 ,而Web Storage仅仅是为了在本地“存储”数据而生。
Cookie和Session的区别
1、cookie数据存放在客户的浏览器上,session数据放在服务器上。
2、cookie不是很安全,别人可以分析存放在本地的cookie并进行cookie欺骗,考虑到安全应当使用session。
3、session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,考虑到减轻服务器性能方面,应当使用cookie。
4、单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。
5、可以考虑将登陆信息等重要信息存放为session,其他信息如果需要保留,可以放在cookie中。
JWT与Session的比较?
JSON Web Token(简称 JWT)是目前最流行的跨域认证解决方案
使用JWT来传输数据,实际上传输的是一个字符串,这个字符串就是所谓的json web token字符串。所以广义上,JWT是一个标准的名称;狭义上,JWT指的就是用来传递的那个token字符串。
它由三部分组成:header(头部)、payload(载荷)、signature(签名),以.
进行分割。(这个字符串本来是只有一行的,此处分成3行,只是为了区分其结构)
header
用来声明类型(typ)和算法(alg)。payload
一般存放一些不敏感的信息,比如用户名、权限、角色等。signature
则是将header
和payload
对应的json结构进行base64url编码之后得到的两个串用英文句点号拼接起来,然后根据header
里面alg指定的签名算法生成出来的。
基于session和基于jwt的方式的主要区别就是用户的状态保存的位置,session是保存在服务端的,而jwt是保存在客户端的。
jwt优点 1、可扩展性好 2. 无状态
jwt缺点 1、 安全性 2. 性能 3、一次性
https://www.cnblogs.com/yuanrw/p/10089796.html
从输入url到渲染出页面的整个过程⭐
域名解析 –> 发起TCP的3次握手 –> 建立TCP连接后发起http请求 –> 服务器响应http请求,浏览器得到html代码 –> 浏览器解析html代码,并请求html代码中的资源(如js、css、图片等) –> 浏览器对页面进行渲染呈现给用户
加载过程
-
DNS
解析:域名 --> IP地址 -
浏览器根据IP地址向服务器发起
http
请求 -
服务器处理
http
请求,并返回给浏览器
渲染过程
- 浏览器解析接收到
HTML
文件并转换为DOM
树,确定节点的父子以及兄弟关系。(HTML-Token-DOM树) - 将
CSS
文件转换为CSSOM
树,确定css属性之间的级联关系。 - 生成
DOM
树和CSSOM
树以后,就需要将这两棵树组合为渲染树。 - 当浏览器生成渲染树以后,就会根据渲染树来进行布局(也可以叫做回流),然后调用
GPU
绘制,合成图层,显示在屏幕上。 - 遇到
<script>
则暂停渲染,优先加载并执行JS代码,完成再继续,直至把Render Tree
渲染完成
DNS域名解析的过程
解析流程就是分级查询
以[www.163.com为例:
- 客户端打开浏览器,输入一个域名。比如输入[www.163.com],这时,客户端会发出一个DNS请求到本地DNS服务器。本地DNS服务器一般都是你的网络接入服务器商提供,比如中国电信,中国移动。
- 查询[www.163.com]的DNS请求到达本地DNS服务器之后,本地DNS服务器会首先查询它的缓存记录,如果缓存中有此条记录,就可以直接返回结果。如果没有,本地DNS服务器还要向DNS根服务器进行查询。
- 根DNS服务器没有记录具体的域名和IP地址的对应关系,而是告诉本地DNS服务器,你可以到域服务器上去继续查询,并给出域服务器的地址。
- 本地DNS服务器继续向域服务器发出请求,在这个例子中,请求的对象是.com域服务器。.com域服务器收到请求之后,也不会直接返回域名和IP地址的对应关系,而是告诉本地DNS服务器,你的域名的解析服务器的地址。
- 最后,本地DNS服务器向域名的解析服务器发出请求,这时就能收到一个域名和IP地址对应关系,本地DNS服务器不仅要把IP地址返回给用户电脑,还要把这个对应关系保存在缓存中,以备下次别的用户查询时,可以直接返回结果,加快网络访问。
重绘(Repaint)和回流(Reflow)⭐
-
重绘是当节点需要更改外观而不会影响布局的,比如改变
color
就叫称为重绘 -
回流是布局或者几何属性需要改变就称为回流。
回流必定会发生重绘,重绘不一定会引发回流。回流所需的成本比重绘高的多,改变父节点里的子节点很可能会导致父节点的一系列回流
减少重绘和回流(能记多少是多少)
-
使用
transform
替代top
-
使用
visibility
替换display: none
,因为前者只会引起重绘,后者会引发回流(改变了布局 -
避免使用
table
布局,可能很小的一个小改动会造成整个table
的重新布局。 -
尽可能在
DOM
树的最末端改变class
,回流是不可避免的,但可以减少其影响。尽可能在DOM树的最末端改变class,可以限制了回流的范围,使其影响尽可能少的节点。 -
避免设置多层内联样式,CSS 选择符从右往左匹配查找,避免节点层级过多。
-
将动画效果应用到
position
属性为absolute
或fixed
的元素上,避免影响其他元素的布局 -
避免使用
CSS
表达式,可能会引发回流。 -
CSS3 硬件加速(GPU加速)
-
避免频繁操作样式,最好一次性重写
style
属性,或者将样式列表定义为class
并一次性更改class
属性。 -
避免频繁操作
DOM
,创建一个documentFragment
,在它上面应用所有DOM操作
,最后再把它添加到文档中。 -
避免频繁读取会引发回流/重绘的属性,如果确实需要多次使用,就用一个变量缓存起来。
浏览器缓存的读取规则⭐
它们的优先级是:(由上到下寻找,找到即返回;找不到则继续)
- Service Worker 运行在 JavaScript 主线程之外,虽然由于脱离了浏览器窗体无法直接访问 DOM,但是它可以完成离线缓存、消息推送、网络代理等功能。
- Memory Cache(内存中的缓存)
- Disk Cache(硬盘中的缓存)
- 网络请求
强缓存:
不会向服务器发送请求,直接从缓存中读取资源,状态码200。
强缓存可以通过设置两种 HTTP Header 实现:Expires
和 Cache-Control
。
Expires
设置一个日期,资源会过期,但受限于本地时间
Cache-Control:max-age=30
资源会在30秒后过期
协商缓存:
当强制缓存失效(超过规定时间)时,就需要使用协商缓存,由服务器决定缓存内容是否失效。
流程上说,浏览器先请求缓存数据库,返回一个缓存标识。之后浏览器拿这个标识和服务器通讯。如果缓存未失效,则返回 HTTP 状态码 304 表示继续使用,于是客户端继续使用缓存;如果失效,则返回新的数据和缓存规则,浏览器响应数据后,再把规则写入到缓存数据库。
协商缓存有 2 组字段(不是两个):
Last-Modified & If-Modified-Since
-
服务器通过
Last-Modified
字段告知客户端,资源最后一次被修改的时间 -
下一次请求相同资源时时,浏览器从自己的缓存中找出“不确定是否过期的”缓存。因此在请求头中将上次的
Last-Modified
的值写入到请求头的If-Modified-Since
字段 -
服务器会将
If-Modified-Since
的值与Last-Modified
字段进行对比。如果相等,则表示未修改,响应 304;反之,则表示修改了,响应 200 状态码,并返回数据。
但是他还是有一定缺陷的:
- 如果资源更新的速度是秒以下单位,那么该缓存是不能被使用的,因为它的时间单位最低是秒。
- 如果文件是通过服务器动态生成的,那么该方法的更新时间永远是生成的时间,尽管文件可能没有变化,所以起不到缓存的作用。
Etag & If-None-Match
为了解决上述问题,出现了一组新的字段 Etag
和 If-None-Match
Etag
存储的是文件的特殊标识(一般都是 hash 生成的),服务器存储着文件的 Etag
字段。之后的流程和 Last-Modified
一致,只是 Last-Modified
字段和它所表示的更新时间改变成了 Etag
字段和它所表示的文件 hash,把 If-Modified-Since
变成了 If-None-Match
。服务器同样进行比较,命中返回 304, 不命中返回新资源和 200。
Etag 的优先级高于 Last-Modified
TCP和UDP的区别(位于传输层)
- TCP 是面向连接的,UDP 是面向无连接的
- UDP程序结构较简单
- TCP 是面向字节流的,UDP 是基于数据报的
- TCP 保证数据正确性,UDP 可能丢包
- TCP 保证数据顺序,UDP 不保证
什么是面向连接,什么是面向无连接
在互通之前,面向连接的协议会先建立连接,如 TCP 有三次握手,而 UDP 不会
TCP 为什么是可靠连接
- 通过 TCP 连接传输的数据无差错,不丢失,不重复,且按顺序到达。
- TCP 报文头里面的序号能使 TCP 的数据按序到达
- 报文头里面的确认序号能保证不丢包,累计确认及超时重传机制
- TCP 拥有流量控制及拥塞控制的机制
总结:UDP面向无连接 不可靠性 高效
OSI七层模型
osi七层模型可以说是面试必考基础了 物理系有一个人叫数据链路在网络上传输了一个会话来表示应用
从上到下分别是:
应用层:特定应用的协议(电子邮件协议,远程登录协议,文件传输协议) HTTP HTTPS FTP DNS SMTP
表示层:数据格式转换
会话层:建立,解除会话
传输层:建立端口到端口的通信,TCP或UDP传输数据
网络层:建立主机到主机的通信,将数据传送到目标地址 IP
数据链路层:物理层面上的通信传输 ARP协议
物理层:负责0,1比特流与电压高低,光的闪灭之间的互换
TCP/IP模型四层架构从下到上分别是链路层,网络层,传输层,应用层
window.onload 和 DOMContentLoaded 的区别
在js中DOMContentLoaded方法是在HTML文档被完全的加载和解析之后才会触发的事件,他并不需要等到(样式表/图像/子框架)加载完成之后再进行。在看load事件(onload事件),用于检测一个加载完全的页面
Vue
vue的两个核心点
答:数据驱动、组件系统
数据驱动:ViewModel,保证数据和视图的一致性。MVVM
组件系统:应用类UI可以看作全部是由组件树构成的。
MVVM 的核心是 ViewModel 层,它就像是一个中转站,负责转换 Model 中的数据对象来让数据变得更容易管理和使用,该层向上与视图层进行双向数据绑定,向下与 Model 层通过接口请求进行数据交互,起承上启下作用。
watch 和 computed 和 methods 区别是什么?
computed和watch的区别
computed:
-
多个数据影响一个数据
-
具有缓存性,页面重新渲染值不变化,计算属性会立即返回之前的计算结果,而不必再次执行函数
-
最典型的栗子: 购物车商品结算的时候
watch:
-
一个数据影响多个数据
-
无缓存性,页面重新渲染时值不变化也会执行
-
栗子:搜索数据
computed 和 methods
computed
是计算属性,methods
是方法,都可以实现对 data 中的数据加工后再输出。不同的是 computed
计算属性是基于它们的依赖进行缓存的, 只有在它的相关依赖发生改变时才会重新求值。
数据量大,需要缓存的时候用 computed
;每次确实需要重新加载,不需要缓存时用 methods
。
Vue 有哪些生命周期钩子函数?分别有什么用?
-
beforeCreate 是new Vue()之后触发的第一个钩子,在当前阶段data、methods、computed以及watch上的数据和方法都不能被访问。
-
created 在实例创建完成后发生,当前阶段已经完成了数据观测,也就是可以使用数据,更改数据,在这里更改数据不会触发updated函数。可以做一些初始数据的获取,在当前阶段无法与Dom进行交互,如果非要想,可以通过vm.$nextTick来访问Dom。
-
beforeMount 发生在挂载之前,当前阶段虚拟Dom已经创建完成,即将开始渲染。在此时也可以对数据进行更改,不会触发updated。
-
mounted 真实的Dom挂载完毕,数据完成双向绑定,可以访问到Dom节点,使用$refs属性对Dom进行操作。
-
**beforeUpdate ** 数据更新时会调用的钩子函数,此时虽然响应式数据更新了,但是对应的真实 DOM 还没有被渲染
-
updated 此时 DOM 已经根据响应式数据的变化更新了。
-
beforeDestroy 发生在实例销毁之前,这时进行善后收尾工作,比如清除计时器。
-
destroyed Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。
Vue中组件生命周期调用顺序说一下
组件的调用顺序都是先父后子
,渲染完成的顺序是先子后父
。
组件的销毁操作是先父后子
,销毁完成的顺序是先子后父
。
加载渲染过程
父beforeCreate -> 父created-> 父beforeMount-> 子beforeCreate-> 子created-> 子beforeMount- > 子mounted-> 父mounted
子组件更新过程
父beforeUpdate->子beforeUpdate->子updated->父updated
父组件更新过程
父 beforeUpdate -> 父 updated
销毁过程
父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
Vue 如何实现组件间通信?⭐
方法一:props
/$emit
父组件A通过props的方式向子组件B传递,B to A 通过在 B 组件中 $emit, A 组件中 v-on 的方式实现。
方法二:eventBus
eventBus
又称为事件总线,在vue中可以使用它来作为沟通桥梁的概念, 就像是所有组件共用相同的事件中心,可以向该中心注册发送事件或接收事件, 所以组件都可以通知其他组件。
创建一个eventBus
import Vue from 'vue'
export const EventBus = new Vue()
//在要用的组件中导入
import {EventBus} from './event-bus.js'
发送事件
methods:{additionHandle(){EventBus.$emit('addition', {num:this.num++})}}
接受事件
mounted() {EventBus.$on('addition', param => {this.count = this.count + param.num;})}
方法三:provide
/inject
// A.vue
export default {provide: {name: '传递内容'}
}
// B.vue
export default {inject: ['name'],mounted () {console.log(this.name); // 传递内容}
}
方法四:Vuex
方法五:$children
/ $parent
Vuex是什么?
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。
核心概念:State Getter Mutation Action Moudle
state => 提供唯一的公共数据源,所有共享的数据都要统一放到Store的state中进行储存。this.$store.state.名称
getters => 用于对store中的数据进行加工处理形成新的数据,类似于Vue中的计算属性,当store中数据发生变化时,getter也会发生变化this.$store.getters.名称
mutations => 提交更改数据的方法,同步!this.$store.commit('add')
--只能通过mutation变更Store数据,不可以直接操作Store的数据
actions => 像一个装饰器,包裹mutations,使之可以异步。 this.$store.dispatch('add')
modules => 模块化Vuex
MVC与MVVM区别⭐
MVC(单向通信)
-
View
传送指令到Controller
-
Controller
完成业务逻辑后,要求Model
改变状态 -
Model
将新的数据发送到View
,用户得到反馈
MVVM(双向通信)
MVVM是Model-View-ViewModel
缩写,也就是把MVC
中的Controller
演变成ViewModel
。Model层代表数据模型,View代表UI组件,ViewModel是View和Model层的桥梁。View
和ViewModel
是进行绑定的,View
的变动,自动反映在 ViewModel
,反之亦然。
而View
会把事件传递给ViewModel
,ViewModel
去对Model
进行操作并接受更新。从而实现双向通信。
Vue 数据响应式怎么做到的?⭐
Vue在初始化数据时,会使用**Object.defineProperty
重新定义data中的所有属性,当页面使用对应属性时,首先会进行依赖收集**(收集当前组件的watcher
),如果属性发生变化会通知相关依赖进行更新操作(发布订阅
)。
要点
- 使用 Object.defineProperty 把对象属性全部转为 getter/setter。这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在 property 被访问和修改时通知变更。
- Vue 不能检测到对象属性的添加或删除,解决方法是手动调用 Vue.set 或者 this.$set
Vue.set 是做什么用的?
https://cn.vuejs.org/v2/guide/reactivity.html
Vue 无法检测 property 的添加或移除。由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在 data
对象上存在才能让 Vue 将它转换为响应式的。例如:
var vm = new Vue({data:{a:1}
})// `vm.a` 是响应式的vm.b = 2
// `vm.b` 是非响应式的
对于已经创建的实例,Vue 不允许动态添加根级别的响应式 property。但是,可以使用 Vue.set(object, propertyName, value)
方法向嵌套对象添加响应式 property。例如,对于:
Vue.set(vm.someObject, 'b', 2)
您还可以使用 vm.$set
实例方法,这也是全局 Vue.set
方法的别名:
this.$set(this.someObject,'b',2)
有时你可能需要为已有对象赋值多个新 property,比如使用 Object.assign()
或 _.extend()
。但是,这样添加到对象上的新 property 不会触发更新。在这种情况下,你应该用原对象与要混合进去的对象的 property 一起创建一个新的对象。
// 代替 `Object.assign(this.someObject, { a: 1, b: 2 })`
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
Vue的单向数据流
数据从父级组件传递给子组件,只能单向绑定
父级 prop
的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外改变父级组件的状态
子组件内部不能直接修改从父级传递过来的数据。
这个 prop
用来传递一个初始值;这个子组件接下来希望将其作为一个本地的 prop
数据来使用。在这种情况下,最好定义一个本地的 data
属性并将这个 prop
用作其初始值:
Vue的slot的理解
Vue的插槽slot,分为3种
- 匿名插槽 (默认)
- 具名插槽
<slot name="xxx"></slot>
<template v-slot:xxx></templete>
- 作用域插槽
前两种很好理解,无论就是子组件里定义一个slot占位符,父组件调用时,在slot对应的位置填充模板就好了。
作用域插槽的慨念,文档却只有一句简单的描述
有的时候你希望提供的组件带有一个可从子组件获取数据的可复用的插槽。
keep-alive了解吗
keep-alive
是vue2.0
加入的一个特性, 能缓存某个组件,或者某个路由。
1、节省性能消耗,避免一个组件频繁重新渲染,节省开支
2、保存用户状态,比如说:我们在填写收货地址的页面,需要跳转到另一个页面通过定位选择地址信息再返回继续填写,这时候需要缓存收货地址页面,避免跳转页面导致用户数据丢失。
他有2个属性
- include - 字符串或正则表达,只有匹配的组件会被缓存
- exclude - 字符串或正则表达式,任何匹配的组件都不会被缓存
<keep-alive include="a"><component><!-- name 为 a 的组件将被缓存! --></component>
</keep-alive>
钩子函数
我们都知道vue
组件的生命周期会触发beforeCreate、created 、beforeMount、 mounted
这些钩子函数,但是被缓存的组件或者页面在第一次渲染之后,再次进入不会再触发上面的钩子函数了,而是触发activated
钩子函数,可以将逻辑放到这里面去做。
同理:离开缓存组件的时候,beforeDestroy和destroyed
并不会触发,可以使用deactivated
离开缓存组件的钩子来代替。
v-show与v-if区别⭐
v-show
只是在 display: none
和 display: block
之间切换。无论初始条件是什么都会被渲染出来,后面只需要切换 CSS,DOM 还是一直保留着的。所以总的来说 v-show
在初始渲染时有更高的开销,但是切换开销很小,更适合于频繁切换的场景。
v-if
的话,当属性初始为 false
时,组件就不会被渲染,直到条件为 true
,并且切换条件时会触发销毁/挂载组件,所以总的来说在切换时开销更高,更适合不经常切换的场景。
如何获取dom?
ref=“domName” 用法;this.$refs.domName
v-model的原理
v-model用于表单数据的双向绑定,其实它就是一个语法糖,这个背后就做了两个操作:
- 给当前元素绑定一个
value
属性; - 给当前元素绑定
input
事件。
<input type="text" :value="price" @input="price=$event.target.value">
Vue Router
单页面跳转
https://router.vuejs.org/zh/guide/#html
NextTick的理解⭐
当你修改了data的值然后马上获取这个dom元素的值,是不能获取到更新后的值,
你需要使用$nextTick
这个回调,让修改后的data值渲染更新到dom元素之后在获取,才能成功。
使用
<button @click="change()">按钮</button><h1 ref="gss">{{msg}}</h1>
//JS
export default{name:"app",data(){return {msg:"123"}},methods:{change(){this.msg = "456";console.log(this.refs["gss"].innerHTML)//123this.$nextTick(function(){console.log(this.refs["gss"].innerHTML)//456})}}}
data为什么必须是一个函数?
一个组件被复用多次的话,也就会创建多个实例。本质上,这些实例用的都是同一个构造函数。如果data是对象的话,对象属于引用类型,改动一个实例会影响到其他所有的实例。所以为了保证组件不同的实例之间data不冲突,data必须是一个函数。。
vue常用的修饰符
.stop:等同于JavaScript中的event.stopPropagation(),防止事件冒泡;
.prevent:等同于JavaScript中的event.preventDefault(),防止执行预设的行为(如果事件可取消,则取消该事件,而不停止事件的进一步传播);
.capture:与事件冒泡的方向相反,事件捕获由外到内;
.self:只会触发自己范围内的事件,不包含子元素;
.once:只会触发一次。
Vue单页面应用(SPA)
单页面应用(SPA)
指一个系统只加载一次资源,然后下面的操作交互、数据交互是通过router、ajax来进行,页面并没有刷新;
Vue只有一个html页面,跳转的方式是组件之间的切换
优点:页面切换快(页面每次切换跳转时,并不需要做html
文件的请求,这样就节约了很多http
发送时延)
缺点:首屏时间慢(首屏时需要请求一次html
,同时还要发送一次js
请求,两次请求回来了,首屏才会展示出来。相对于多页应用,首屏时间慢。),SEO差(搜索引擎只认识html
里的内容,不认识js
的内容,而单页应用的内容都是靠js
渲染生成出来的,搜索引擎不识别这部分内容)
多页应用(MPA)
每一次页面跳转的时候,后台服务器都会给返回一个新的html
文档,这种类型的网站也就是多页网站,也叫做多页应用。
优点:首屏时间快,SEO效果好
缺点:页面切换慢
Vue监测数组和对象的变化
由于 JavaScript 的限制,Vue 不能检测数组和对象的变化。尽管如此我们还是有一些办法来回避这些限制并保证它们的响应性。
对于对象
对于已经创建的实例,Vue 不允许动态添加根级别的响应式 property。但是,可以使用 Vue.set(object, propertyName, value)
方法向嵌套对象添加响应式 property。例如,对于:
Vue.set(vm.someObject, 'b', 2)
还可以使用 vm.$set
实例方法,这也是全局 Vue.set
方法的别名:
this.$set(this.someObject,'b',2)
对于数组
Vue 不能检测以下数组的变动:
- 当你利用索引直接设置一个数组项时,例如:
vm.items[indexOfItem] = newValue
解决办法:
// Vue.set
Vue.set(vm.items, indexOfItem, newValue)
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)
- 当你修改数组的长度时,例如:
vm.items.length = newLength
解决办法
vm.items.splice(newLength)
虚拟DOM(O(n))
虚拟DOM简单讲就是讲真实的dom节点用JavaScript来模拟出来,而Dom变化的对比,放到 Js 层来做
具体讲,就是把真实的DOM操作放在内存当中,在内存中的对象里做模拟操作。
当页面打开时浏览器会解析HTML元素,构建一颗DOM树,将状态全部保存起来,在内存当中模拟我们真实的DOM操作,操作完后又会生成一颗dom树,再根据diff算法比较两颗DOM树不同的地方,只渲染一次不同的地方。
优点:
- **保证性能下限:**框架的虚拟 DOM 需要适配任何上层 API 可能产生的操作,他的一些 DOM 操作的实现必须是普通的,所以它的性能并不是最优的;但是比起粗暴的 DOM 操作性能要好很多,因此框架的虚拟 DOM 至少可以保证在你不需要手动优化的情况下,依然可以提供还不错的性能,即保证性能的下限。
- **无需手动操作 DOM:**我们不再需要手动去操作 DOM ,只需要写好 View-Model 的代码逻辑,框架会根据虚拟 DOM 和数据双向绑定,帮我们以可预期的方式更新视图,极大提高我们的开发效率。
- **跨平台:**虚拟 DOM 本质上是 javascript 对象,而 DOM 与平台强相关,相比之下,虚拟 DOM 可以进行更方便的跨平台操作,例如服务器渲染、weex 开发等等。
缺点:
- **无法进行极致优化:**虽然虚拟 DOM + 合理的优化,足以应对绝大部分应用的性能需求,但在一些性能要求极高的应用中,虚拟 DOM 无法进行针对性的极致优化。比如 VScode 采用直接手动操作 DOM 的方式进行极端的性能优化。
Diff算法(O(n))
diff算法:找出有必要更新的节点更新,没有更新的节点就不要动
内容:
- 只比较同一级,不跨级比较
- tag不相同,直接删掉重建,不再深度比较
- tag和key,两者都相同,则认为是相同节点,不在深度比较
原先的复杂度是n^3,需要进行三步操作,插入,删除,替换
之前在Virtual DOM中讲到当状态改变了,vue便会重新构造一棵树的对象树,然后用这个新构建出来的树和旧树进行对比。这个过程就是patch。比对得出「差异」,最终将这些「差异」更新到视图上。
https://juejin.im/post/5c4a76b4e51d4526e57da225
为什么要使用key?
vue是通过比对组件自身新旧vdom进行更新的。
key的作用是给每个节点做一个唯一标识,辅助判断新旧vdom节点在逻辑上是不是同一个对象。
作用主要是为了更高效的更新虚拟DOM
vue-router的两种模式
hash(默认)—— 即地址栏 URL 中的 # 符号
- 原理是onhashchage事件,可以在window对象上监听这个事件
window.onhashchange = function(event){console.log(event.oldURL, event.newURL)let hash = location.hash.slice(1)
}
history
- 利用了HTML5 History Interface 中新增的pushState()和replaceState()方法。
- 需要后台配置支持。如果刷新时,服务器没有响应响应的资源,会刷出404
webpack
前端模块化的了解
什么是模块化
- 将一个复杂的程序依据一定的规则(规范)封装成几个块(文件), 并进行组合在一起
- 块的内部数据与实现是私有的, 只是向外部暴露一些接口(方法)与外部其它模块通信
模块化的好处
- 避免命名冲突(减少命名空间污染)
- 更好的分离, 按需加载
- 更高复用性
- 高可维护性
我们需要重点掌握的是ES6和CommonJS,AMD与CMD只需了解,现在我们实际开发中已经基本不会用到了。
commonjs
在的前端开发中我们经常会用到node,node中的模块化就需要使用commonjs,所以掌握commonjs是必须的。
通过module.exports导出模块
var x = 5;
var addX = function (value) {return value + x;
};
module.exports.x = x;
module.exports.addX = addX;//同时导出多个
module.exports = {x,addX
}
通过require导入模块
var example = require('./example.js');console.log(example.x); // 5
console.log(example.addX(1)); // 6
ES6模块化
ES6推出了自己的模块化规范,使用export和import来进行导出和导入。
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;export {firstName, lastName, year};
import {firstName, lastName, year} from './profile.js';
有哪些常见的Loader?他们是解决什么问题的?
- file-loader:把文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件
- url-loader:和 file-loader 类似,但是能在文件很小的情况下以 base64 的方式把文件内容注入到代码中去
- css-loader:加载 CSS,支持模块化、压缩、文件导入等特性
- sass-loader
- style-loader:把 CSS 代码注入到 JavaScript 中,通过 DOM 操作去加载 CSS。
- image-loader:加载并且压缩图片文件
- babel-loader:把 ES6 转换成 ES5
- eslint-loader:通过 ESLint 检查 JavaScript 代码
有哪些常见的Plugin?他们是解决什么问题的?
- html-webpack-plugin:简单创建HTML文件,用于服务器访问
- define-plugin:定义环境变量
- commons-chunk-plugin:提取公共代码
- uglifyjs-webpack-plugin:通过
UglifyES
压缩ES6
代码
Loader和Plugin的不同?
loader译为加载器,主要用于加载资源文件,webpack默认只支持加载js文件,使用loader可以实现css,less等文件的资源转化
plugin主要用于拓展webpack的功能,例如打包压缩,定义环境变量等功能
不同的作用
- Loader直译为"加载器"。Webpack将一切文件视为模块,但是webpack原生是只能解析js文件,如果想将其他文件也打包的话,就会用到
loader
。 所以Loader的作用是让webpack拥有了加载和解析非JavaScript文件的能力。 - Plugin直译为"插件"。Plugin可以扩展webpack的功能,让webpack具有更多的灵活性。 在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。
不同的用法
- Loader在
module.rules
中配置,也就是说他作为模块的解析规则而存在。 类型为数组,每一项都是一个Object
,里面描述了对于什么类型的文件(test
),使用什么加载(loader
)和使用的参数(options
) - Plugin在
plugins
中单独配置。 类型为数组,每一项是一个plugin
的实例,参数都通过构造函数传入。
安全
什么是 XSS?如何预防?⭐
XSS(跨站脚本攻击)能注入恶意的HTML/JavaScript代码到用户浏览的网页上,从而达到Cookie资料窃取、会话劫持、钓鱼欺骗等攻击。
一般情况下,利用保存型 XSS 漏洞的攻击至少需要向服务器提出两个请求,第一个请求时是传送包含危险脚本的请求,这个请求可以将危险脚本存储在服务器中,第二个请求是受害者查看包含危险脚本的页面,此时危险脚本开始执行。
防范
- 将重要的cookie标记为http only, 这样的话Javascript 中的document.cookie语句就不能获取到cookie了.
- 表单数据规定值的类型,例如:年龄应为只能为int、name只能为字母数字组合。。。。
- 对数据进行Html Encode 处理,例如 将可疑的符号 < 符号变成 < >(HTML实体)就行
- 过滤或移除特殊的Html标签, 例如:
举例:
一个博客网站,我发布一篇博客,其中嵌入脚本
脚本内容:获取cookie,发送到我的服务器(服务器配合跨域)
发布这篇博客,有人查看他,我就能获取用户的cookie
什么是 CSRF?如何预防?⭐
CSRF(Cross-site request forgery)跨站请求伪造
CSRF则通过伪装来自受信任用户的请求来利用受信任的网站。
防范
- HTTP头中有一个字段叫作
Referer
,它记录了HTTP请求来源的地址。在服务端检查 - 增加验证,例如密码、短信验证码、指纹等(请求参数里加一个随机token,在服务端验证)
举例:
1 某购物网站的付费接口xxx.com/pay?id=100,但没有验证,且用户已登录
2 攻击者发送一封邮件,验证正文隐藏这个付费接口
3 查看邮件后就会付费扣款
编程题
JS实现千分分隔符
方法1:自带函数 toLocaleString
方法2:正则
function numFormat(num){var res=num.toString().replace(/\d+/, function(n){ // 先提取整数部分return n.replace(/(\d)(?=(\d{3})+$)/g,function($1){console.log($1)return $1+",";});})return res;}var a=1234567894532;var b=673439.4542;console.log(numFormat(a)); // "1,234,567,894,532"
冒泡排序 n2 稳定
function bubbleSort(arr) {var len = arr.length;for(var i = 0;i < len;i++) {for(var j = 0;j < len-1-i;j++) {if(arr[j] > arr[j+1]) {// 相邻元素两两比较var temp = arr[j+1];// 元素交换arr[j+1] = arr[j];arr[j] = temp;}}}return arr;
}
快速排序 nlog2n 不稳定
主要思想:
找个中间数,将各个元素与之比较,小的放前面,大的放后面
对小的部分和大的部分重复上面步骤(递归)
function quick(arr){if(arr.length<=1){return arr}var midIndex=parseInt(arr.length/2)var mid=arr.splice(midIndex,1)[0]var left=[]var right=[]for(var i=0;i<arr.length;i++){if(arr[i]<=mid){left.push(arr[i])}else{right.push(arr[i])}}return quick(left).concat(mid,quick(right))
}
插入排序 n2 稳定
每次将一个待排序的数字按其关键码的大小插入到一个已排好的有序序列中,直到全部数字排好序。
let arr =[12,8,25,16,1]
function bubble(arr){ arr2=[];arr2.push(arr[0])for(var i=1;i<arr.length;i++){let A=arr[i];for(var j=arr2.length;j>=0;j--){let B=arr2[j]if(A>B){arr2.splice(j+1,0,A)break;}if(j===0){arr2.unshift(A)// break;}}}return arr2
}
选择排序 n2 不稳定
其基本思想是:首先在待排序序列中选出最小值,存放在排序序列起始位置,然后再从剩余未排序元素中继续寻找最小元素,放到已排序序列末尾。以此类推,直到所有元素均排序完毕。
function selectionSort(arr) {var len = arr.length;var minIndex, temp;for(var i = 0; i< len-1;i++){minIndex = i;for(var j = i+1;j<len;j++) {if(arr[j] < arr[minIndex]) {// 寻找最小的数minIndex = j;}}temp = arr[i];arr[i] = arr[minIndex];arr[minIndex] = temp;}return arr;
}
Object.key() 和 for in 区别
function Person(name, age) {this.name = name;this.age = age;
}
Person.prototype.hobbies = ['eat'];
let p = new Person;
// 不遍历原型上的属性
console.log(Object.keys(p));// [ 'name', 'age' ]
// 可遍历原型链上的可枚举属性
for (let item in p) {console.log(item);// name age hobbies
}
map和forEach区别
map可以用来遍历并返回一个由return值组成的新数组,forEach只用来遍历,返回值为undefined
let arr = [1, 2, 3];
let newArr = arr.forEach(function (item, index) {return item * item;
});
console.log(arr, newArr);// [ 1, 2, 3 ] undefinedarr = [1, 2, 3];
newArr = arr.map(function (item, index) {return item * item;
});
console.log(arr, newArr);// [ 1, 2, 3 ] [ 1, 4, 9 ]
promise实现打印红绿灯,循环3次
function timeout(timer){return function(){return new Promise(function(resolve,reject){ setTimeout(resolve,timer)}) }
}var green=timeout(1000)
var yellow=timeout(1000)
var red=timeout(1000)var n=0
function restart(){n++console.log('green')green().then(function(){console.log('yellow')return yellow()}).then(function(){console.log('red')return red()}).then(function(){if(n<3){restart()}})
}
restart()
Promise.all题目
公司放映系统最近要上线一个『预定随机推荐电影』功能,每天用户通过系统预定名额,由系统每日推荐一部电影,按时推送到用户。现在,在系统已有如下异步方法封装的前提下
• getTodayUsers ( callback ): 获取今日预定的用户id列表,使用如下getTodyUsers(userIds=>{ console.log(userIds )})
, 即回调中拿到用户id列表
• getTodayMovie(callback): 获取今日推荐的电影id, 使用如下getTodayMovie( movieId=> {console.log(movieId )})
,即回调中拿到今日的电影id
• bookMovieForUsers(userIds, movieId, callback): 使用用户id列表预定某部电影,使用如下bookMovieForUsers([1,2,3], 1000, ()=>{console.log(‘预定成功了’)})
请封装一个bookTodayMovieForTodayUser()的方法,它的作用是为今天预定的用户订阅今天系统推荐的电影。它返回一个promise, 这个promise在请求后被resolve. 使用方式如下
bookTodayMovieForTodayUser().then( ()=> console.log('预定成功’) )
注: 简单起见,所有情况都不需要考虑失败情况
function bookTodayMovieForTodayUser(){let m=new Promise((resolve,reject)=>{getTodyUsers(userIds=>{resolve(userIds)})})let n=new Promise((resolve,reject)=>{getTodayMovie(movieId=>{resolve(movieId)})})return new Promise.all([m,n]).then((result)=>{bookMovieForUsers(...result,resolve)})
}
手写题
手写call
Function.prototype.myCall=function(ctx,...args){//1、将方法挂载到我们传入的ctx//2、将挂载以后的方法调用//3、将我们添加的这个属性删除ctx.fn=this ctx.fn(...args)delete ctx.fn
}//测试
function people(...args){console.log(args)console.log(this.name)
}
people.myCall({name:'lucy'},'father','mother')
手写apply
Function.prototype.myApply=function(ctx,args=[]){//1、将方法挂载到我们传入的ctx//2、将挂载以后的方法调用//3、将我们添加的这个属性删除if(args && !(args instanceof Array)){throw('myApply 只接受数组作为参数')}ctx.fn=thisctx.fn(...args)delete ctx.fn
}//测试
function people(...args){console.log(args)console.log(this.name)
}
people.myCall({name:'lucy'},['father','mother'])
手写bind
Function.prototype.bind = function(obj){var arg1 = [].slice.call(arguments,1); //用arg1 保留了当函数调用bind方法时候传入的参数,因为arguments是类数组对象,我们借用了数组的slice方法var fn = this; // fun —> bind调用者(也就是某个函数)return function(){fn.apply(obj,arg1.concat([].slice.call(arguments,1)));}
}
手写new
new做了什么
- 创建一个新的对象
- 继承父类原型上的方法
- 添加父类的属性到新的对象上并初始化. 保存方法的执行结果
- 如果执行结果有返回值并且是一个对象, 返回执行的结果, 否则, 返回新创建的对象
function _new(obj, ...rest){// 基于obj的原型创建一个新的对象const newObj = Object.create(obj.prototype);// 添加属性到新创建的newObj上, 并获取obj函数执行的结果.const result = obj.apply(newObj, rest);// 如果执行结果有返回值并且是一个对象, 返回执行的结果, 否则, 返回新创建的对象return typeof result === 'object' ? result : newObj;
}
手写函数防抖和函数节流⭐
节流(高频触发的事件,在单位时间,只响应第一次)
function throttle(fn, delay){let canUse = truereturn function(){if(canUse){fn.apply(this, arguments)canUse = falsesetTimeout(()=>canUse = true, delay)}}}
防抖(高频触发的事件,在单位时间,只响应最后一次,如果在指定时间再次触发,则重新计算时间)
function debounce(fn, delay){let timer = nullreturn function(){if(timer){window.clearTimeout(timer)}timer = setTimeout(()=>{fn.apply(this, arguments)timer = null},delay)}}const debounced = debounce(()=>console.log('hi'))debounced()debounced()
手写AJAX⭐
var request = new XMLHttpRequest()request.open('GET', '/a/b/c?name=ff', true);request.onreadystatechange = function () {if(request.readyState === 4 && request.status === 200) {console.log(request.responseText);}};request.send();
第1步:创建XMLHttpRequest对象,也就是创建一个异步调用对象
第2步:创建一个新的HTTP请求,并指定该HTTP请求的方法、URL以及验证信息。
第3步:设置响应HTTP状态变化的函数。
第4步:发送HTTP请求。
补充:
- 0:请求未初始化(还没有调用
open()
)。 - 1:请求已经建立,但是还没有发送(还没有调用
send()
)。 - 2:请求已发送,正在处理中(通常现在可以从响应中获取内容头)。
- 3:请求在处理中;通常响应中已有部分数据可用了,但是服务器还没有完成响应的生成。
- 4:响应已完成;您可以获取并使用服务器的响应了。
jQuery实现jsonp------不是真正的ajax,没有用到xhr
$.ajax({url:'http://api.json',dataType:'jsonp',jsonpCallback:'callback',success:function(data){console.log(data)}
})
如何实现浅拷贝,深拷贝?⭐
https://juejin.im/post/6844904205937803277
浅拷贝
复制一份,不会引起连锁改变
- 方法一:
Object.assign
实现
let a = {age: 1
}
let b = Object.assign({}, a)
a.age = 2
console.log(b.age) // 1
- 方法二:通过展开运算符
...
来实现浅拷贝
let a = {age: 1
}
let b = { ...a }
a.age = 2
console.log(b.age) // 1
深拷贝
背代码,要点:https://juejin.im/post/5ece6dfbe51d4578a6796fd8
https://segmentfault.com/a/1190000020255831
- 递归
- 判断类型
- 检查环(也叫循环引用)
- 需要忽略原型
function deepClone(obj = {}) {if (typeof obj !== 'object' || obj == null) {return obj}// 初始化返回结果let resultif (obj instanceof Array) {result = []} else {result = {}}for (let key in obj) { if (obj.hasOwnProperty(key)) {// 保证 key 不是原型的属性 result[key] = deepClone(obj[key])// 递归调用!!!}}// 返回结果return result
}
简易版:JSON.parse(JSON.stringify(obj))
不用 class 如何实现继承?用 class 又如何实现?⭐
定义一个父类
function People(name){//属性this.name = name || Annie//实例方法this.sleep=function(){console.log(this.name + '正在睡觉')}
}
//原型方法
People.prototype.eat = function(food){console.log(this.name + '正在吃:' + food);
}
原型链继承
父类的实例作为子类的原型
function Woman(){
}
Woman.prototype= new People();
Woman.prototype.name = 'haixia';
let womanObj = new Woman();
父类的新增的实例与属性子类都能访问, 无法实现多继承 创建子类实例时,不能向父类构造函数中传参数
构造函数继承(伪造对象、经典继承)
复制父类的实例属性给子类
function Woman(name){//继承了PeoplePeople.call(this); //People.call(this,'wangxiaoxia'); this.name = name || 'renbo'
}
let womanObj = new Woman();
子类构造函数向父类构造函数中传递参数 可以实现多继承 不能继承原型属性/方法,只能继承父类的实例属性和方法
实例继承(原型式继承)
function Woman(name){let instance = new People();instance.name = name || 'wangxiaoxia';return instance;
}
let womanObj = new Woman();
组合式继承
调用父类构造函数,继承父类的属性,通过将父类实例作为子类原型,实现函数复用
寄生组合继承
ES6继承
class Animal{constructor(color){this.color = color}move(){}}class Dog extends Animal{constructor(){super()}}
ES5继承和ES6继承的区别
es5继承首先是在子类中创建自己的this指向,最后将方法添加到this中
Child.prototype=new Parent() || Parent.apply(this) || Parent.call(this)
es6继承是使用关键字先创建父类的实例对象this,最后在子类class中修改this
JS实现邮箱的正则表达式
var pattern = /^([A-Za-z0-9_\-\.])+@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/;console.log(pattern.test('12343qqbc@163.dm'))
数组扁平化
将一个嵌套多层的数组array(嵌套可以是任何层数)转换为只有一层的数组
flatten([1,[2,[3,4]]]) //[1,2,3,4]
如何实现数组去重?⭐
https://www.cnblogs.com/wisewrong/p/9642264.html
1、哈希
2、双重 for 循环(最烂)
3、for…of + indexOf()或includes() ,创建一个空数组,元素不存在时push进去
4、Array.sort() 比较相邻元素是否相等,从而排除重复项
5、new Set([iterable])
Promise、Promise.all、Promise.race 怎么用
- 背代码 Promise 用法
//创建一个Promise实例
const promise = new Promise(function(resolve, reject) {// ... some codeif (/* 异步操作成功 */){resolve(value);} else {reject(error);}
});promise.then(function(value) {// success
}, function(value) {// failure
});
- 背代码 Promise.all 用法
promise1和promise2都成功才会调用success1
Promise.all([promise1, promise2]).then(success1, fail1)
-
背代码 Promise.race 用法
promise1和promise2只要有一个成功就会调用success1;
promise1和promise2只要有一个失败就会调用fail1;
总之,谁第一个成功或失败,就认为是race的成功或失败。
Promise.race([promise1, promise2]).then(success1, fail1)
手写promise all
Promise.all_ = function(arr) {return new Promise((resolve, reject) => {// Array.from()可将可迭代对象转化为数组arr = Array.from(arr);if(arr.length===0) {resolve([]);} else {let result = [];let index = 0;for(let i=0; i<arr.length; i++) {// 考虑到arr[i]可能是thenable对象也可能是普通值Promise.resolve(arr[i]).then(data => {result[i] = data;index++if(++index===arr.length) {// 所有的promise状态都是fulfilled,promise.all返回的实例才变成fulfilled状态resolve(result);}}, err => {reject(err);return;})}}})
}
手写promise race
Promise.race_= function(arr) {arr = Array.from(arr);return new Promise((resolve, reject) => {if(arr.length===0) {return;} else {for(let i=0; i<arr.length; i++) {Promise.resolve(arr[i]).then(data => {resolve(data);return;}, err => {reject(err);return;})}}})
}
模拟Object.create实现
Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象__proto__
function simulateCreate (proto) {var F = function () {};F.prototype = proto;return new F();
}
其他
前端性能优化
服务端:
- 使用 CDN,CDN 可以通过 DNS 负载均衡技术将用户的请求转移到就近的 cache 服务器上,提高请求返回速度
- 利用静态资源缓存,给返回头加上 Cache-Control 或者 Etag 头
网络:
- 通过雪碧图、合并请求等方法减少HTTP请求数
- 减少文件大小:压缩 CSS、JS 和图片,在服务器(Nginx)中开启 Gzip:也就是先在服务端进行压缩,再在客户端进行解压
客户端:
- 使用外联 CSS 和 JS,CSS 放头,JS 放在文档尾部,防止页面加载阻塞以减少对并发下载的影响,尽早给用户展示出页面
- 懒加载(图片懒加载,上滑加载更多)
- 对DOM查询进行缓存,频繁DOM操作,合并到一起插入DOM结构
- 节流throttle 防抖 debounce
- JS: 使用事件委托、减少重绘回流,如设置 class 更新样式
什么是进程 线程
知乎的答案,很舒服
看了一遍排在前面的答案,类似”**进程是资源分配的最小单位,线程是CPU调度的最小单位“**这样的回答感觉太抽象,都不太容易让人理解。
做个简单的比喻:进程=火车,线程=车厢
- 线程在进程下行进(单纯的车厢无法运行)
- 一个进程可以包含多个线程(一辆火车可以有多个车厢)
- 不同进程间数据很难共享(一辆火车上的乘客很难换到另外一辆火车,比如站点换乘)
- 同一进程下不同线程间数据很易共享(A车厢换到B车厢很容易)
- 进程要比线程消耗更多的计算机资源(采用多列火车相比多个车厢更耗资源)
- 进程间不会相互影响,一个线程挂掉将导致整个进程挂掉(一列火车不会影响到另外一列火车,但是如果一列火车上中间的一节车厢着火了,将影响到所有车厢)
- 进程可以拓展到多机,进程最多适合多核(不同火车可以开在多个轨道上,同一火车的车厢不能在行进的不同的轨道上)
- 进程使用的内存地址可以上锁,即一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。(比如火车上的洗手间)-“互斥锁”
- 进程使用的内存地址可以限定使用量(比如火车上的餐厅,最多只允许多少人进入,如果满了需要在门口等,等有人出来了才能进去)-“信号量”
纯函数与非纯函数
纯函数
1、不改变源数组(没有副作用);2、返回一个数组
concat map filter slice
非纯函数
push pop shift unshift
foreach some every splice(剪接) reduce
阻止事件冒泡和默认行为
event.stopPropagation()
event.preventDefault()
什么是JSON⭐
-
json是一种数据格式,本质是一段字符串
-
json格式和JS对象结构一致,对JS语言更友好
-
window.JSON是一个全局对象:JSON.stringify JSON.parse
什么是 JSONP,什么是 CORS(内容重复待定)
JSONP
-
JSONP是通过 script 标签加载数据的方式去获取数据当做 JS 代码来执行
-
提前在页面上声明一个函数,函数名通过接口传参的方式传给后台,后台解析到函数名后在原始数据上「包裹」这个函数名,发送给前端。换句话说,JSONP 需要对应接口的后端的配合才能实现。
CORS()
CORS是一种协议,它用来约定服务端和客户端那些行为是被服务端允许的。尽管服务端是可以进行验证和认证的,但基本上这是由客户端浏览器来保证的。这些对行为的允许是放在应答包的header里面的。
它允许浏览器向跨源服务器,发出 XMLHttpRequest 请求,从而克服了 Ajax 只能同源使用的限制。
**以下是MDN的解释: **
跨域资源共享(CORS) 是一种机制,它使用额外的 HTTP 头来告诉浏览器 让运行在一个 origin (domain) 上的Web应用被准许访问来自不同源服务器上的指定的资源。当一个资源从与该资源本身所在的服务器不同的域、协议或端口请求一个资源时,资源会发起一个跨域 HTTP 请求。
比如,站点 http://domain-a.com 的某 HTML 页面通过 的 src 请求 http://domain-b.com/image.jpg。网络上的许多页面都会加载来自不同域的CSS样式表,图像和脚本等资源。
出于安全原因,浏览器限制从脚本内发起的跨源HTTP请求。 例如,XMLHttpRequest和Fetch API遵循同源策略。 这意味着使用这些API的Web应用程序只能从加载应用程序的同一个域请求HTTP资源,除非响应报文包含了正确CORS响应头。
(译者注:这段描述不准确,并不一定是浏览器限制了发起跨站请求,也可能是跨站请求可以正常发起,但是返回结果被浏览器拦截了。)
跨域资源共享( CORS )机制允许 Web 应用服务器进行跨域访问控制,从而使跨域数据传输得以安全进行。现代浏览器支持在 API 容器中(例如 XMLHttpRequest
或 Fetch )使用 CORS,以降低跨域 HTTP 请求所带来的风险。
理解TCP长连接(Keepalive)
长连接的环境下,进行一次数据交互后,很长一段时间内无数据交互时,客户端可能意外断电、死机、崩溃、重启,还是中间路由网络无故断开,这些TCP连接并未来得及正常释放,那么,连接的另一方并不知道对端的情况,它会一直维护这个连接,长时间的积累会导致非常多的半打开连接,造成端系统资源的消耗和浪费,且有可能导致在一个无效的数据链路层面发送业务数据,结果就是发送失败。所以服务器端要做到快速感知失败,减少无效链接操作,这就有了TCP的Keepalive(保活探测)机制。
TCP Keepalive作用
- 探测连接的对端是否存活
在应用交互的过程中,可能存在以下几种情况:
(1)客户端或服务器意外断电,死机,崩溃,重启。
(2)中间网络已经中断,而客户端与服务器并不知道。
利用保活探测功能,可以探知这种对端的意外情况,从而保证在意外发生时,可以释放半打开的TCP连接。
- 防止中间设备因超时删除连接相关的连接表
中间设备如防火墙等,会为经过它的数据报文建立相关的连接信息表,并为其设置一个超时时间的定时器,如果超出预定时间,某连接无任何报文交互的话,
中间设备会将该连接信息从表中删除,在删除后,再有应用报文过来时,中间设备将丢弃该报文,从而导致应用出现异常。
TCP Keepalive HTTP Keep-Alive 的关系
在HTTP/1.0中,默认使用的是短连接。也就是说,浏览器和服务器每进行一次HTTP操作,就建立一次连接,但任务结束就中断连接。如果客户端浏览器访问的某个HTML或其他类型的 Web页中包含有其他的Web资源,如JavaScript文件、图像文件、CSS文件等;当浏览器每遇到这样一个Web资源,就会建立一个HTTP会话。
但从 HTTP/1.1起,默认使用长连接,用以保持连接特性。使用长连接的HTTP协议,会在响应头加上Connection、Keep-Alive字段
HTTP协议的Keep-Alive意图在于TCP连接复用,同一个连接上串行方式传递请求-响应数据;
TCP的Keepalive机制意图在于探测连接的对端是否存活(探测保活)。
HTTP1.1 keep-alive重点:连接复用
HTTP1.1规定了默认保持长连接,数据传输完成了保持TCP连接不断开(不发RST包、不四次握手),等待在同域名下继续用这个通道传输数据;相反的就是短连接。
在timeout空闲时间内,连接不会关闭,相同重复的request将复用原先的connection,减少握手的次数,大幅提高效率。
并非keep-alive的timeout设置时间越长,就越能提升性能。长久不关闭会造成过多的僵尸连接和泄露连接出现。
什么是柯里化 (curry)?
柯里化,即 currying,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
假设有一个curry
函数可以将其他函数柯里化,其用法如下:
// 一个接受两个参数的函数
const sum = (x, y) => x + y
// 用 20 来固定其中的一个参数,返回一个新的函数
const sumWith20 = curry(sum)(20)
// 再传入第二个参数
sumWith20(30) // 50
JS模块化规范
AMD和CMD都是浏览器端的js模块化规范,分别由require.js和sea.js实现。 CommonJS是服务器端的js模块化规范,由NodeJS实现。
什么是单例模式
**单例模式:**保证一个类仅有一个实例,并提供一个访问它的全局访问点。
经典的实现方式是,创建一个类,这个类包含一个方法,这个方法在没有对象存在的情况下,将会创建一个新的实例对象。如果对象存在,这个方法只是返回这个对象的引用。
实现代码:
var SingleTon = function(name){ //创建一个对象this.name = name;this.instance = null; //通过这个变量来标志是否创建过对象
};
SingleTon.prototype.getName = function(){alert(this.name);
};
SingleTon.getInstance = function(name){if(!this.instance){this.instance = new SingleTon(name); //在没有对象存在的情况下,将会创建一个新的实例对象}return this.instance;
};var a = SingleTon.getInstance( 'instance1' );
var b = SinleTon.getInstance( 'instance2' );
alert( a === b); //返回true
消息推送的几种实现方式
1、轮询
客户端定时向服务器发送Ajax请求,服务器接到请求后马上返回响应信息,并关闭连接。
优点:后端程序编写比较容易
缺点:请求中大半是无用的,浪费带宽和服务器资源
实例:适用于小型应用
2.长轮询:
客户端向服务器发送Ajax请求,服务器接到请求后Hold住连接,直到有新消息才返回响应信息,并关闭连接;客户端处理完响应信息后再向服务器发送新的请求。
优点:在无消息的情况下不会频繁的请求,耗费的资源少
缺点:服务器Hold住连接会消耗资源,返回数据顺序无法保证,难于管理和维护
实例:扫码登录,微信网页端获取消息等。
长连接:
客户端和服务端建立长链接,基于http1.1 ,keepalive ,websocket,comet,iframe等,基于socket的需要维持心跳
优点:通信及时,通信模式采用双工,类似于打电话
缺点:服务端和客户端需要同时改造,当链接过多时,消耗服务端资源比较大。
使用场景:实时性要求很高,银行系统,股票系统等
面向对象的三大特性
封装、继承、多态
一、封装:
封装:将内容封装到某个地方,之后调用的时候直接调用被封装在某处的内容
二、继承:
继承,面向对象中的继承和现实生活中的继承相同,即:子可以继承父的内容。
三:多态
多态的具体表现为方法重载和方法重写:
方法重载:重载是指不同的函数使用相同的函数名,但是函数的参数个数或类型不同。调用的时候根据函数的参数来区别不同的函数
方法重写:重写(也叫覆盖)是指在派生类中重新对基类中的虚函数(注意是虚函数)重新实现。即函数名和参数都一样,只是函数的实现体不一样
栈和队列的区别:
-
栈的插入和删除操作都是在一端进行的,而队列的操作却是在两端进行的。
-
栈是先进后出,队列是先进先出。
-
栈只允许在表尾一端进行插入和删除,队列只允许在表尾一端进行插入,在表头一端进行删除。
栈和堆的区别:
栈区:由编辑器自动分配释放,存放函数的参数值,局部变量的值等(基本类型值)。
堆区:由程序员分配释放,若程序员不释放,程序结束时可能有OS回收(引用类型值)。
栈(数据结构):一种先进后出的数据结构。
堆(数据结构):堆可以被看成是一棵树,如:堆排序。