css部分
1. 行内标签通过设置position:absolute/float可以设置行内元素支持宽高
先将一下position的各个属性
- static: 使用正常的布局行为,即此时的top, right, left, bottom和z-index属性无效
- relative: 相对于自己的位置进行偏移,在不改变页面布局的前提下调整元素的位置,因此会在元素未添加定位的时候,坐在位置留下空白。postition:relative对table-*-group, table-row, table-column, table-cell, table-caption元素无效
- fixed: 元素相对于屏幕视口的位置来指定元素的位置,元素在屏幕滚动的时候也不会改变。fixed会创建新的层叠上下文,当元素祖先的transfrom, perspective活filter属性非none时候,容器由视口改为祖先元素
- sticky: 根据正常文档流进行定位,相对它的最近滚动祖先
- 总是创建一个新的层叠上下文
- 一个sticky元素会在固定在离它最近的一个拥有滚动机制的祖先上,(当这个祖先的overflow:hidden, scroll,auto或overlay时)
float元素:指定一个元素沿着其容器的左侧或者右侧放置,允许文本和内联元素从网页的正常流程文档流中移除,但是仍然保持部分的流动性。 浮动元素意味着使用块布局,再某些情况下会修改display值的计算值
再回到本题, 先说总结: position:absolute和float会隐式的改变display类型,不论之前什么类型的元素display:none除外,只要设置了position:absolute/float,就会让元素以display:inline-block的方式显示,可以设置行内元素的宽高,默认宽度不沾满父元素 效果:
2. css优先级
- !important: 1000,优先级最大
- id选择器: 100,
- 类选择器,属性选择器: 10
- 标签选择器,伪元素选择器:1
- 相邻兄弟选择器,子代选择器,后端选择器,通配符选择器:0
另外几点
- !import声明的样式优先级最高
- 如果优先级相同的情况,后面出现的样式会覆盖前面,后面的生效
- 继承得到的样式的优先级最低
- 样式来源的优先级:内联样式 > 内部样式 > 外部样式> 浏览器用户自定义样式> 浏览器默认样式
3. css中的继承
3.1 可继承属性
- 字体系列: font-family, font-weight, font-size, font-style(家族,重量,大小,样式)
- 文本系列: text-indent, text-align, line-height, word-spacing, letter-spacing, color, text-transform(缩进,对齐,行高,单词间距,字母之间间距,文本颜色) 3.元素可见性: visibility
- 列表布局属性:list-style:列表风格,list-style-type, list-style-image等
- 光标属性:cursor:光标显示的形态
3.2不可继承属性
- display
- 文本属性:vertical-align, text-decoration, text-shadow, white-space,unicode-bidi
- 盒子模型:wi dth,height, margin, border, padding
- 背景属性:bacground, background-color/image/repeat/position/attachment
- 定位属性:float,,clear, position, top,left, right, bottom, width, height, max-width, min-width, max-height, min-height, overflow, z-index, clip,
- 生成内容属性:content,
- 轮廓样式属性:outline-style, outline-width,outline, outline-color
- 声音样式属性: pause-before, pause-after, pause, cue-before、cue-after、cue、play-during
- 页面属性:size, page-break-before, page-break-after
4. 隐藏元素的方法
- display:none,不占据位置,不响应绑定的监听事件;display非继承属性,修改display会造成重排
- visibility:hidden, 仍占据位置,但是不响应绑定的监听事件;visibility属于可继承属性,visibility会触发重绘
- position:absolute,绝对定位,将元素移除可视区域达到隐藏效果
- z-index:负数值,视觉上用其他元素遮盖住该元素
- transform:scale(0,0),元素缩小为0,该方式仍占据位置,但是不响应绑定的监听事件
- opacity:0
5. 伪类和伪元素区别作用
- 伪类:将特殊的效果添加到特定的选择器上,浏览器检查元素鼠标右键,可设置某些伪类属性;即通过在元素选择器上添加伪类改变元素状态
- 伪元素:在内容元素的前面,后面插入额外的元素或者样式,但是这些元素实际上不在文档中生成,如
::before,::after,::first-line,::first-letter;通过对元素的操作进行对元素的改变
6. ::before 和 :after 的双冒号和单冒号区别
双冒号用于css3伪元素, 单冒号用于css3伪类
:before和 :after 这两个伪元素在css2.1出现,起初,伪元素的前缀是单冒号,但是随着web的进化,在css3规范里,伪元素的语法被修改成双冒号
7. li元素之间有看不见的空白间隙的原因
浏览器会把inline内联元素间的空白字符(空格, 换行,Tab等)渲染成一个空格。我们在写代码的时候,为了美观手动换行,导致标签li换行后产生换行符,被渲染成一个空格,占用了一个字符的宽度 一般的解决方案有:
- 设置字符串间隔letter-spacing,
- 将ul内的字符大小font-size:0,缺点:ul内的字符被统一设置font-size为0了,所以需要额外的设置其他字符字体大小,在Safari浏览器依然会出现空白间隔
- 手动将标签li写在同一行,缺点:看起来不好看
- li设置浮动float:left, 缺点:有些容器不能设置浮动
8. css3中新增了哪些新特性
- 边框属性:圆角border-radius, border-image, box-shadow, border-color
- 文字特效:text-shadow
- 文字渲染: text-decoration
- 变换效果: tranform
- 过渡效果:transition
- 渐变: gradient
- 阴影: shadow
- 动画:animation
- 媒体查询
- 多列布局:
- 伪元素选择器,
:not等
9.替换元素
- 可替换元素展现效果不是由css来控制的,css可以影响可替换元素的位置,但是不会影响可替换元素自身的内容
- 可替换元素有:iframe, video, embed, img,canvas,Object
- form表单类型如: input, select, option, 等
- 修改可替换元素的外观,需要类似appearance属性,或者浏览器自身暴露的一些样式接口。
- 有自己的默认尺寸大小video,宽320, 高240
- 所有的可替换元素都是内联水平元素
10. line-height
- 一行文本的高度,包含字间距,即下一行基线到上一行基线的距离
- 如果一个标签没有定义height属性,最终表现的高度有line-height决定
- 一个容器没有设置高度,那么撑开容器的高度是line-height, 而不是容器的文本内容
- line-height, height的值设置一样大小可以实现单行文字垂直居中
- line-height赋值方式:
- 有单位的:px固定值,em, 根据父元素font-size计算自身行高
- 纯数字:把比例传递给后代,父级行高为 1.5,子元素字体为 18px,则子元素行高为 1.5 * 18 = 27px
- 百分比:会将计算后的值传递给后代
11. z-index失效的情况
- 父元素position:relative的时候,子元素的z-index失效,解决方法:父元素position:absolute或static
- 元素在设置了z-index的同时还设置的了float属性,解决方法:去掉float, 改为display:inlone-block
- 元素没有设置position属性为非static属性,解决方法:设置该元素的position属性为relative,absolute或是fixed中的一种
12. 元素的层叠顺序
浏览器在请求资源回来后,会进行cssom树的构建,其中涉及到样式标准化(属性值标准化),样式继承,样式层叠优先级的处理,后面进行页面绘制(构建图层)
但是,并不是渲染树上的每个节点都包含一个图层,如果一个节点没有对应的图层,那这个节点就会属于其父节点的图层。那什么样的节点才能让浏览器引擎为其创建一个新的图层呢,
- 拥有层叠上下文属性的元素,如下图
- 需要裁剪的元素:假如有一个固定宽高的div盒子,而里面的文字较多超过了盒子的高度,这时就会产生裁剪,浏览器渲染引擎会把裁剪文字内容的一部分用于显示在 div 区域。当出现裁剪时,浏览器的渲染引擎就会为文字部分单独创建一个图层,如果出现滚动条,那么滚动条也会被提升为单独的图层
在构建完图层树之后,渲染引擎会对图层树的每个图层进行绘制,绘制的时候,会把一个图层分成很多绘制指令,让把这些指令按照顺序组成一个待绘制的列表
绘制列表只是用来绘制顺序和绘制指令的列表,而绘制操作是由渲染引擎中的合成线程来完成的,当图层绘制列表准备好之后,主线程就会把该绘制列表交给合成线程。(合成操作是在合成线程上操作的,所以合成操作不会影响到主线程的执行)
在图层很大的情况下,比如一篇很长的文章, 用户只能看到视口的部分内容,所以渲染策略,合成线程会把图层划分为图块,这些图块的大小通常是256256的,或者512512的尺寸。合成线程会优先把视口附近的图块生成位图。即在光栅化阶段按照绘制列表中的指令生成的图片 当所有的图块都被光栅化之后,合成线程就会生成一个绘制图块的命令,浏览器相关进程接受到这个指令之后,就会把页面内容绘制在内存中,最后将内存显示在屏幕上,就完成了页面的绘制。
这里顺带总结一下浏览器渲染流程:
- 将HTML内容构建成DOM树(DOM树描述的是HTML标签的层级关系)
- 将css内容构建成CSSOM树(选择器之间的层级关系)
- 提供给javascript操作样式的能力
- 为渲染树的合成提供基础的样式信息
- 将DOM树和CSSOM树合成渲染树
- 根据渲染树进行页面元素的布局
- 对渲染树进程分层,并生成分层树
- 为每一个图层生成绘制列表,并提交到合成线程
- 合成线程将图层分成不同的图块,并且通过栅格化将图块转化为位图
- 合成线程给浏览器发送绘制图块的指令
- 浏览器进程生成页面,显示在屏幕上
13. BFC
块格式化上下文,独立的布局环境,在一个容器里面,按照一定规则,从上到下,从左到右布局,不会影响到其他环境中的物品, 1.触发BFC条件
- 根元素:body
- 设置浮动:设置float不为none的值、
- 绝对定位:position:absolute/fixed
- overflow:hidden, auto, scroll
2.作用
- 可以解决margin在垂直方向重叠问题 => 两个块级元素的上外边距和下外边距可能会合并(折叠)为一个外边距,其大小会取其中外边距值大的那个(由于BFC是一个独立的区域,内部的元素和外部的元素互不影响,)
- 解决内容高度塌陷的问题:子元素浮动,父元素未设置高度的情况下,父元素会出现内容塌陷(计算BFC的高度时,需要计算浮动元素的高度,父亲设置overflow:hidden)
14.清除浮动的原理和方式
- 原理:浮动元素脱离文档流,不占据空间,从而引起了高度塌陷的现象;浮动元素碰到了包含它的边框或者其他浮动元素而停留
- 浮动元素可能引起的问题
- 父亲元素的高度无法撑开,影响与父元素同级的元素
- 与浮动元素同一级的非浮动元素会跟随其后
- 如果浮动的元素不是第一个元素,那么该元素之前的元素也都要浮动,否则会影响页面的显示结构
- 清除浮动的方式有:
- 给父元素设置height属性
- 最后一个浮动的元素添加一个空的div, 设置
clear:both - 给包含浮动元素的父级元素甚至overflow:hidden,或overflow:auto
- 使用:after,对于IE6-7不支持 :after的,可使用 zoom:1 触发 hasLayout
clear: 元素盒子不能和前面的浮动元素相邻,即clear属性对“后面的”浮动元素是不闻不问的
15.水平垂直居中有哪些方案
<div id="container">
<div id="child">我是行内元素</div>
</div>
- flex布局
.container {
display:flex;
justify-content:center;
align-items:center
}
- 绝对定位+transform(不知宽高的情况下)
.container{
position:relative;
}
.child {
position: absolute;
top: 50%;
left:50%;
transform: translate(-50%,-50%);
}
- 已知宽高的情况下,绝对定位+margin负边距拉回宽度值的一半的方式
.container {
position: relative;
}
.child{
height: 200px;
width:200px
position: absolute;
top: 50%;
margin-left:-100px;
margin-top: -100px;
}
- 已知宽高度的情况下,绝对定位+margin:auto的方式
.container {
position: relative;
height: 400px;
width:400px
}
.child{
width: 100px;
height: 100px;
position: absolute;
top: 0;
left:0;
bottom:0;
right:0;
}
- grid网格布局
16.百分比单位
百分比,不同的属性使用可能效果不一样,都需要一个参照值
- 盒子模型中使用百分比
- width, max-width, min-width使用百分比,它的值计算是相对于包含块的width进行计算的
- height,max-height, min-height:使用百分比,它的值计算是相对于包含块的height进行计算的
- padding, margin使用百分比,如果是水平方向的值,它的值计算是相对于包含块的width进行计算的;如果是垂直方向的,是相对于包含块的 height 进行计算
- 定位中的百分比: position中的top,bottom,left,right,使用百分比,其参照物的包含块的同的width和height,根据定位的不同:
- static, relative定位属性,其参照物是其父容器,
- 绝对定位:包含块就是离它最近的position为absolute 、 relative 或 fixed 的祖先元素;
- 元素为fixed:包含块就是视窗( viewport )
- 变换中的百分比 transform 属性中的 translate 和 transform-origin 值也可设置百分比
- translateX() 根据容器的 width 计算,translateY() 根据容器的 height 计算
- 在 translate 还有z 轴的函数 translateZ(),它是不接受百分比为单位的值
- transform-origin 中横坐标( x )相对于容器的 width 计算;纵坐标( y )相对于容器的 height 计算
- 文本中的百分比
- font-size:父元素的 font-size计算
- line-height:根据font-size进行计算
- vertical-align:根据line-height计算
- text-align:水平根据width, 垂直方向根据height计算
17.flex布局
弹性盒子布局,提供一个更有效的布局,对齐方式,能够是父元素在子元素大小未知的情况下也能分配好子元素之间的间隙
-
适合小规模的布局,当遇到不定宽度,分布对齐的场景,可优先考虑弹性布局
-
设置为flex之后,子元素的float, clear, vertical-align失效
-
作用于容器的属性,使用flex布局,需要先给父元素设置flex布局display:flex|inline-flex ** 6个属性**:
- flex-direction:主轴方向,
- row | row-reverse | column | column-reverse
- justify-content:元素在主轴的对齐方式
- flex-start 元素在主轴上左对齐(上对齐)
- flex-end 元素在主轴右对齐(下对齐)
- center 元素在主轴上居中对齐
- space-between,两端对齐,元素之间的间隔相等
- space-around, 每个项目两侧的间隔相等,项目之间的间隔比项目与边框的间距大一倍
- align-items:元素在交叉轴上的对齐方式
- flex-start | flex-end | center | baseline | stretch;
- align-content:多根轴线对齐方式。如果元素只有一根轴线,该属性不起作用
- flex-wrap: 容器内元素是否可以换行,
- nowrap | wrap | wrap-reverse;
- flex-flow
- flex-direction:主轴方向,
-
作用在子元素上的属性6个
- order:项目的排列顺序。数值越小,排列越靠前,默认值0
- flex-grow:项目放大比例,默认0, 如果存在剩余空间也不放大
- 如果项目设置了flex-basis,如果有剩余空间,flex-grow属性设置的值才能生效
- flex-basis:在分配多余空间之前,项目占据主轴空间,浏览器会根据这个属性来计算主轴是否有多余的空间,默认auto;
- flex-basis需要和flex-grow, flex-shrink配合使用才能生效,当主轴设置为水平时,当设置了 ****flex-basis,设置的项目宽度值会失效,
- flex-basis为0%, 项目尺寸会被认为0
- 当 flex-basis 值为 auto 时,则跟根据尺寸的设定值来设置大小
- flex-shrink:项目缩小比例,如果空间不足的时候,该项目缩小,默认为1,不能设置负数
- flex:flex-grow, flex-shrink, flex-basis的简写,
- 默认值flex:0 1 auto, 即使有剩余空间,因为只放大不缩小
- flex:0 有剩余空间的时候,项目的宽度为内容的宽度,最终尺寸表现为最小内容宽度。
- flex: none, 有剩余空间时,不放大也不缩小,最终尺寸通常表现为最大内容宽度
- flex-grow:0; flex-shrink:0; flex-basis:auto;
- flex单位:
- 一个无单位数:flex:2,表示flex-grow的值
- 一个有单位的数:对应设置的是flex-basis
- 两个值,如果都是无单位的,则设置的是flex-grow, flex-shrink的值
- 两个值,一个有单位,一个无单位,则,无单位设置的是flex-grow的值,有单位设置的是flex-basis的值
- align-self:单个项目与其他项目不一样的对齐方式,
- 默认值为
auto,这个属性和align-items属性的效果是一样的,只不过这个属性只对单个项目生效
- 默认值为
18 变量
自定义属性,以两个减号(--)开始,通常,最佳实践是将CSS变量定义在根伪类:root下,这样就可以在HTML文档的任何地方访问到它。在使用CSS变量时,使用 var() 函数包裹所需变量的变量名即可:
:root {
--main-bg-color: #C92E33;
}
div {
background-color: var(--main-bg-color)
color: var(--main-bg-color, red);// var()第二个参数作为备用值
}
- css变量存在全局变量,局部变量,局部作用域是优先于全局作用域的
- 通过内联样式来设置 CSS 变量:
<div style="--color: red"></div>,也可通过js代码设置element.style.getPropertyValue('--color')
19.文本换行
- 当文本所在容器的宽度固定时,可以使用
overflow-wrap: break-word;和overflow-wrap: anywhere;来实现文本的自动换行;如果容器宽度为min-content,就只能使用overflow-wrap: break-word;实现文本换行;overflow-wrap: break-word;也可以用于长标点符号的换行。 word-break: break-all;也可以用于文本换行,但是该属性不能让长标点符号换行;white-space: nowrap;可以用于防止文本自动换行;line-break: anywhere可以用于将长标点符号进行换行;hyphens: auto;可以用于使用连字符连接单词
20.常见溢出问题
- 固定宽度元素:不要固定会在多个视口大小下显示的元素的宽度。
- flex布局,不允许项目换行的情况下发生溢出
- 长单词:不适合视口宽度的长单词,由于视口的宽度,这种情况在移动设备上发生得更多
- 没有 max-width 的图片,不提前处理大图像,可能会看到溢出,确保在所有图像上设置 max-width: 100%
- CSS Flex 中的最小内容大小 => Flex 项目不会缩小到其最小内容大小(最长单词或固定大小元素的长度)以下。要更改此设置,需要设置 min-width 或者 min-height 属性=> 解决设置 min-width: 0;overflow-wrap: break-word;
进阶技巧
毛玻璃效果 backdrop-filter:bulr(npx)
实现平滑滚动,scroll-behavior: smooth
悬停放大:hover, transform的scale配合
实现三角形
3种方式:
- border:给定一个宽度和高度都为 0 的元素,通过border 控制三角形
- 要一个指向下面的三角形,可以让 border 的上边可见,其他边都设置为透明
- 要一个指向右面的三角形,可以让 border 的左边可见,其他边都设置为透明
- linear-gradient;
- clip-path。
// 等边三角形
.triangle {
width: 0;
height: 0;
border-left: 69px solid transparent;
border-right: 69px solid transparent;
border-bottom: 120px solid skyblue;
}
实现九宫格
// HTML结果
<div class="box">
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
<li>7</li>
<li>8</li>
<li>9</li>
</ul>
</div>
// 公共样式:
ul {
padding: 0;
}
li {
list-style: none;
text-align: center;
border-radius: 5px;
background: skyblue;
}
flex方案实现
ul{
display: flex;
flex-wrap:wrap;
width: 100%;
height:100%
}
li {
width: 30%;
height: 30%;
margin-right: 5%;
margin-bottom: 5%;
}
li:nth-of-type(3n){
margin-right:0;
}
li:nth-of-type(7+n){
margin-bottom:0;
}
float实现
给父元素的div设置一个宽度,宽度值为:盒子宽 * 3 + 间距 * 2; 然后给每个盒子设置固定的宽高,为了让他换行,使用float实现,由子元素的浮动,形成了BFC,所以父元素ul使用overflow:hidden;来消除浮动带来的影响
ul {
width: 100%;
height: 100%;
overflow: hidden;
}
li {
float: left;
width: 30%;
height: 30%;
margin-right: 5%;
margin-bottom: 5%;
}
li:nth-of-type(3n){
margin-right: 0;
}
li:nth-of-type(n+7){
margin-bottom: 0;
}
css优化
- 避免高消耗属性,以下属性都是对性能要求比较高
- box-shadow
- border-radius
- transform
- :nth-child
- position:fixed
- filter
- 使用link代替@import
@import 规则主要用于导入资源或者CSS文件。它会阻止其他文件并行下载,并可能会导致网站速度变慢;可以使用多个HTML 中的
<link>标签来代替@import,它将并行加载CSS文件,可以在一定程度上提高应用的加载的速度 - 简化选择器,不要嵌套太深的选择器,复杂的 CSS 选择器可能需要几毫秒的时间来解析
- 避免使用 !Important,如果CSS的规则中 !Important 太多,浏览器就必须对代码进行额外的检查,这可能会降低页面的加载速度;可以通过选择器来实现样式重写的,除非是想要对第三方库的CSS进行重写
- 压缩 CSS,压缩CSS文件来删除文件中所有的空白和不必要的代码来减少文件的大小
- 避免过多 font-family,
- 减少重复代码,两个元素或选择器具有相同的 CSS 属性时,可以使用逗号来组合这些选择器,而不是重复声明样式,这样它们将共享 CSS 样式
- 使用0而不是0px,没有单位会比有单位时文件小一点
场景题
解决1px问题
js部分
数据类型
位运算
闭包
概念:
MDN: 一个函数和对其周围状态的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。
通俗来讲,闭包其实就是一个可以访问其他函数内部变量的 函数 。即一个定义在函数内部的函数,或者说闭包是个内嵌函数。,使变量私有化 函数内部是无法在外部访问的,即全局变量和局部变量的区别,使用闭包就能实现外部访问某个函数内部的变量,延长变量的存在周期,使这些内部变量的值始终保存在内存中
闭包产生的原因
因为作用域的概念,当访问一个变量时, 代码解释器会首先在当前的作用域查找,如果没有找到,就会去父级作用域去查找,知道找到该变量或者不存在父级作用域中,这个链路就是作用域链;** 闭包产生的本质就是在当前环境变量中存在指向父级作用域的引用**
闭包的应用场景
- 定时器,事件监听,Ajax请求,任何的异步中,只要使用了回调函数,实际上都是在使用闭包
- 防抖,截流函数,IIFE立即执行函数,webpack打包之后的
- 利用闭包的特性,缓存函数
/**
* 1.编写calc函数
-实现多参数累乘功能(参数数量不限)
-对于相同传入的参数(不计先后)可以缓存结果
-缓存在空间上不能无限膨胀,如何对缓存策略做优化
Calc(1,2,3) //6
Calc(4,2) //8
Calc(9,1,3, 2) //54
*/
解题思路
- reduce实现累积
- 利用闭包的特性实现缓存
- 利用WeakMap特性做策略优化
this使用技巧
this的运用首先会涉及到执行上下文知识,
- 全局执行上下文:执行window
- 函数执行上下文:涉及到执行上下文栈(函数执行为这个函数创建函数,执行完毕之后,移除执行上下文栈)
- eval函数执行上下为
在大多数情况下,函数的调用方式决定了this的值,运行时绑定; 在非严格模式下,总是指向一个对象,在严格模式下可以为任意值
es5中可以通过bind方法来设置函数的this的值,es6引入箭头函数,箭头函数不提供自身的this绑定,this的值将保持为闭合词法上下文的值
this的几种使用场景
- 全局上下文
- 函数上下文
- 类上下文
- 派生类
- bind,apply,call改变this
派生类的构造函数没有初始的this绑定,所以在构造函数中调用super()会生成一个this绑定,like this:
this = new Base(), Base为基类 所以我们在class中constructor里面需要调用super的用意便是如此,在调用super()之前引用this会抛出错误
this 函数调用
在函数内部,this的取值取决于函数被调用的方式 ES5中里面有3种形式的函数调用
1. func(arg1, arg2) // 直接函数调用
2. obj.childProp.method(arg1,arg2) // 对象方式调用
3. func.call(context, arg1,arg2)
可以说第1,2种方式是第三种方式的语法糖, 都可以等价的转换为call的方式
func(arg1, arg2) => func.call(undefined, arg1,arg2) obj.childProp.method.call(obj.childProp,arg1,arg2)
即记住func.call(context, arg1,arg2)形式,理解this指向就容易多了
举例子解释:
在非严格模式下,使用call, apply,如果用做this的值不是对象,就会被尝试转换为对象; null,undefined被转换为全局对象
function fun1(){
console.log(this)
}
// ======== 简写为 =====:
fun1.call(undefined) // 浏览器的规则,如果你传的context是undefined或者null,那么window就是默认的context, 严格模式下context是undefine
ECMAScript262规范规定:如果第一个参数传入的对象调用者是null或者undefined,call方法将把全局对象(浏览器上是window对象)作为this的值
作为对象的方法
在函数作为对象的方法调用时,this被设置为调用函数的对象,this的绑定只受最接近的成员引用的影响
接下来看我们的上面所说的第二种当时obj.childProp.method(arg1,arg2)
let obj = {
name: function(){
console.log(this)
}
}
// 对象调用方法
obj.name()
// 可以套用我们的框架 obj.name.call(obj),所以此时的this就是obj
// =========== 延伸1: []语法的this指向:===================
function func(){
console.log(this)
}
function fn(){console.log(11)}
let array = [func, fn]
array[0]() // 即array.0() 转换为我们的框架形式: => array.0.call(array),最接近的成员引用array
- 或许我们经常犯错误的点在这样一个场景:将一个方法从对象中拿出来,然后再调用,期望方法中的this是原来的对象,比如回调中传入这个方法,立即执行函数,但是实际上,如果我们不做特殊处理,一般会丢失原来的对象
2.匿名函数的this是指向全局对象的
3.this绑定的优先级: new绑定 > 显式绑定 > 隐式绑定 > 默认绑定。 new 绑定是比隐式绑定优先级高
//========== 延伸1.: 只是引用对象的方法,但是没有调用它的情况:================
this.age = 18;
let obj = {
age:12,
name: function(){
console.log(this.age)
}
}
let obj2 = obj.name
obj2() // 18, // 转换为我们的框架形式:obj.call(), 没有表明context, 默认undefined, 即指向window对象
延伸-箭头函数this问题
箭头函数没有this, 如果你在箭头函数里面看到了this,直接把它看成是箭头函数外面的this就行,外面的this是啥,箭头函数中的this就是啥,箭头函数内外部的this是同一个东西。
this题 小练习1
function foo() {
console.log( this.a );
}
function doFoo() {
foo(); // 可以等价于window.foo()
}
var obj = {
a: 1,
doFoo: doFoo
};
var a = 2;
obj.doFoo() // 2
// 题目解析
执行foo的时候,执行环境是doFoo函数,执行环境为全局,即window.foo(), this指向window
this题 小练习2
var a = 10
var obj = {
a: 20,
say: () => { // 箭头函数,这里的this就是函数外部父级所处的上下文的this => window, 如果这里改成普通函数,this指向的就是obj
console.log(this.a)
},
say1: function() {
var f1 = () => {
console.log("1111", this);
}
f1();
},
}
obj.say()
var o = obj.say1;
o() // 1111, window => f1是箭头函数,它是没有绑定this的,它的this指向其父级的this,其父级say方法的this指向的是全局作用域,所以会打印出window
obj.say1(); // 1111 obj对象 => 谁调用say,say 的this就指向谁
var anotherObj = { a: 30 }
obj.say.apply(anotherObj) // 箭头函数使用apply改变this无效
// 输出结果:10 10
this题 小练习3
var a = 1;
function printA(){
console.log(this.a);
}
var obj={
a:2,
foo:printA,
bar:function(){
// console.log(this.a) // 2
printA();
},
getA: function(){
var a = 5;
return function(){
return this.a;
}()
}
}
console.log(obj.getA()) // 1, 匿名函数的this是指向全局对象的,所以this指向window
obj.foo(); // 2 => foo的this指向obj
obj.bar(); // 1 => printA在bar方法中执行, printA的this指向的是window
var foo = obj.foo;
foo(); // 1 => 将一个方法从对象中拿出来,然后再调用,this改变了,指向window
原型链
new 一个对象
promise
async await
Object, map区别
axios,fetch, XMLHttpRequest,ajax区别
浅拷贝
深拷贝
箭头函数,普通函数区别
- 箭头函数比普通函数更加简洁 2.箭头函数没有自己的this
事件代理?应用场景
事件代理(事件委托), 利用事件冒泡,只能指定一个事件处理程序,就可以管理某一类型的所有事件
利用冒泡的原理,把事件加到父元素或祖先元素上,触发执行效果
DOM Level 2 Events规定的事件流有三个阶段:
- 事件捕获阶段、
- 处于目标阶段
- 事件冒泡阶段
Event对象提供了一个属性叫target,可以返回事件的目标节点,我们称为事件源,即target可以表示为当前的事件操作的dom
在JavaScript中,添加到页面上的事件处理程序的数量会直接影响页面的整体运行性能, 因为需要不断的与dom节点进行交互,当访问dom的次数越多,引起浏览器重绘与重排的次数也就越多,导致延长整个页面的交互就绪时间, 这也是我们经常提及的性能优化的主要思想之一就是减少DOM操作的原因; 用事件委托,把一个或者一组元素的事件委托到它的父层或者更外层元素上,真正绑定事件的是外层元素,而不是目标元素,这样dom的操作就只需要交互一次,减少与dom的交互次数,提高性能;
- 适合事件委托的事件:click, mousedown, mouseup, keydown,keyup,keypress
mouseout、mouseover等不太适合使用事件委托(虽然mouseout这些事件也冒泡,但通常需要确定元素的位置,所以不太推荐使用事件委托)
事件循环
异步执行原理
javascript是单线程语言,
javascript单线程的原因
在js运行的时候,可能会阻止UI渲染,这说明Ui渲染线程和js引擎线程是互斥的=> js可以修改DOM,如果在js执行的时候,UI线程还在工作,就可能导致不能安全的渲染UI; 利用js单线程运行,可以达到节省内存,节约上下文切换时间
浏览器是多线程的
浏览器多线程包括主线程,网络进程,渲染进程,插件进程,GPU进程等
在主线程需要发送数据请求时,会把这个任务交给异步http请求去执行,等数据请求回来之后,再将callback里面需要执行的js回调交给js引擎线程去执行=> js只是负责执行最后的回调,浏览器才是真正执行发送这个任务的角色
浏览器里的事件循环
javascript是单线程,由主线程负责执行同步任务 javascript的任务分为同步和异步,
- 同步任务:在主线程上排队执行的任务,只有一个任务执行完毕之后,才会执行下一个任务
- 异步任务:不进入主线程,根据任务类型放入对应的异步任务队列里面,如果有多个异步任务,则需要在任务队列里排队等待,任务队列类似缓冲区,任务下一步会被移到执行栈然后主线程执行调用栈的任务。
javascript在执行代码的时候,会将同步的代码按照顺序排在执行栈中,然后依次执行里面的函数。当遇到异步任务的时候,就将它放入到异步任务队列里,等待当前执行栈所有同步代码执行完成之后,就会从异步任务队列里面取出异步任务的回调并将其放入到执行栈中继续执行,如此往复,直到执行完成所有任务。
这里的任务队列包含了宏任务和微任务
- 宏任务:script整体代码,steTimeout, setInterval, I/O,UI交互事件,setImmediate(Nodejs环境)
- 微任务:Promise, MutationObserver, process.nextTick(Nodejs环境) 微任务是在下一轮DOM渲染之前执行,宏任务在此之后执行
- javascript引擎执行完主线程上的任务之后,就会从宏任务队列里取出一个任务来执行,当前宏任务执行完毕之后,会检查是否有微任务需要执行,如果有,则会把当前所有的微任务都执行完,如果在这一步过程中又产生了新的微任务,这些产生的新的微任务会在当前的循环中继续执行完,
- 然后再从宏任务队列中取下一个,执行完毕后,再次将 microtask queue 中的全部取出,循环往复,直到两个 queue(宏任务队列,微任务队列)中的任务都取完。 一次 Eventloop 循环会处理一个宏任务和所有这次循环中产生的微任务。
JavaScript在遇到异步任务时,会将此任务交给其他线程来执行(比如遇到setTimeout任务,会交给定时器触发线程去执行,待计时结束,就会将定时器回调任务放入任务队列等待主线程来取出执行),主线程会继续执行后面的同步任务。
对于微任务,比如promise.then,当执行promise.then时,浏览器引擎不会将异步任务交给其他浏览器的线程去执行,而是将任务回调存在一个队列中,当执行栈中的任务执行完之后,就去执行promise.then所在的微任务队列
对于微任务,比如promise.then,当执行promise.then时,浏览器引擎不会将异步任务交给其他浏览器的线程去执行,而是将任务回调存在一个队列中,当执行栈中的任务执行完之后,就去执行promise.then所在的微任务队列
Nodejs环境中的事件循环
每个阶段都是一个queue队列,
- timers:所有的定时器操作都放在这里,setTimeout, setInterval的调度回调函数
- pending callbacks: 上一轮没有执行完的,都放在这(执行延迟到下一个循环的I/O回调
- idle, prepare: 内部系统执行的一些队列
- poll: 轮询, 主要I/O操作,比如一些文件的读取成功/失败,检测新的I/O事件,执行与I/O相关的回调,(几乎所有情况下,除了关闭的回调函数,那些由计时器和setImmediate()调度之外,其余情况node将在适当的时候阻塞,如果没有其他逻辑会再次阻塞,等待时机到达,开启下一轮轮询
- check: 这里放着我们的setImmediate
- close: socket.close() 这些事件
浏览器事件循环和Node事件循环区别
浏览器环境下,microtask的任务队列是每个macrotask执行完之后执行。而在Node.js中,microtask会在事件循环的各个阶段之间执行,也就是一个阶段执行完毕,就会去执行microtask队列的任务。
异步编程
v8引擎
V8引擎,底层是c++编写的,是一个可以独立运行的模块,可以嵌入到任何 C ++应用程序中,被用于 Chrome 和 Node.js,Couchbase, MongoDB
和其他javascript引擎一样,v8会编译/执行javascript代码,管理内存,负责垃圾回收,与宿主语言的交互等,通过暴露宿主对象(变量,函数等)等到javascript,javascript可以访问到宿主环境中的对象,在脚本中完成对宿主对象的操作
V8怎么执行代码的
在V8之前,所有的javascript虚拟机所采用的都是解释性执行方式,这是javascript执行速度慢的一个主要原因。V8引入了即时编译(JIT)
V8执行回调
回调类型有两种:同步回调,异步回调。
异步回调典型代表:setTimeout, XMLHttpRequest
(1)setTimeout函数内部封装回调消息,将消息添加到消息队列,然后主线程从消息队列里面取出回调事件,执行回调函数
(2)XMLHttpRequest,会涉及到下载过程,下载过程需要放到单独的线程去执行,在执行XMLHttpRequest.send时,宿主会将实际请求移交给网络线程,然后send函数退出,主线程继续执行下面的任务。在网络线程执行下载的过程中,会将一些中间信息和回调函数封装成新的消息,并将它添加到消息队列里去,然后主线程执行完主线程上的任务之后,就去消息队列取出回调函数,执行回调函数(这里的回调就是宏任务)。
javscript引入微任务,是因为住线程在执行消息队列中的宏任务的时间颗粒度太大了,无法胜任一些对精度和实时性要求比较高的场景。微任务可以在实时性和效率直接做一个有效的权衡。 微任务是基于消息队列、事件循环、UI 主线程还有堆栈而来的,然后基于微任务,又可以延伸出协程、Promise、Generator、await/async 等现代前端经常使用的一些技术
垃圾回收
在程序运行过程中肯定会用到一些数据,这些数据会放在堆栈中,但是在程序运行结束后,这些数据就不再被使用了,这些不再被使用的数据就是垃圾,就需要被回收是否该占据的内存。
V8内存管理
- 程序运行需要分片内存
- V8也会申请内存,这种内存叫做常驻内存集合
- 常驻内存集合会分为堆和栈
栈
- 栈用于存放js中基本类型和引用类型指针
- 栈的空间是连续的,增加删除只需要移动指针,操作速度比较快
- 栈的空间是有限的,当栈满了,就会抛出一个错误
- 栈一般是在执行函数的时候创建,在函数执行完毕之后,栈就会被销毁
栈不会导致内存泄露,只会导致栈溢出,不需要垃圾回收,因为出栈之后,内存就会销毁了
堆
- 如果不需要连续的空间,或者申请的内存比较大,可以用堆
- 堆只要用于存储js中的引用类型
- 基本数据类型保存在变量对象里的(对象里面保存的是堆的内存地址), 引用数据类型要单独在单堆内存里开辟空间保存
node14版本以前是1.4G, 之后的堆空间大概是2G
垃圾回收-新生代,老生代
垃圾回收算法
- 标记清除法
- 引用计数法(存在循环引用的问题)
js内存泄漏如何检测,场景有哪些
这里就得先讲垃圾回收GC,见上面 非预期的一些垃圾回收不了,(对于预期的,闭包里面的变量,就不算内存泄漏)
在程序运行过程中肯定会用到一些数据,这些数据会放在堆栈中,但是在程序运行结束后,这些数据就不再被使用了,这些不再被使用的数据就是垃圾,就需要被回收是否该占据的内存
泄漏的情况,蓝色的线一直上升:
场景有哪些
- 被全局变量,,函数引用,组件销毁时未被清除
- 被全局事件,定时器引用,组件销毁时候未被清除
- 被自定义事件引用,组件销毁时未被清除
开发人员工程师的敏感度,内在素质:一旦遇到这种全局变量,函数引用,全局事件,定时器,自定义事件的时候一定要有敏感度,就是我是不是要去清除它,是不是要去销毁它。比如我们的算法,就要考虑时间复杂度,是不是可以弱化一些,有数据结构,我们是不是要去优化一些,有箭头函数,就要敏感监测this是不是可以用,要有敏感度,条件反射
内存泄漏扩展-WeakMap,WeakSet
WeakMap的key是弱引用类型,只能通过get方法看某个属性是不是有,但是不能用forEach, size,因为它内部有多少属性,它自己也不知道(因为引用它的数据可能立马被清除)
HTTP 0.9, HTTP1.0,HTTP1,1, HTTP2.0,HTTP3
cookie
HTTP Cookie(或web Cookie, 浏览器cookie)
-
Cookie工作原理: 因为HTTP是无状态协议,服务器不能感知用户登录身份,怎么办呢?Cookie通过服务器设置,Cookie通过在客户端记录信息确定用户身份,Session通过在服务器端记录确定用户的身份。 服务器发送给用户浏览器一小块数据保存在本地,浏览器会存储cookie,并且在下一次向同一个服务器发起请求的时候,自动带上该cookie信息添加到请求头中,用于告诉服务器两个请求是否来自同一个浏览器
-
Cookie类型--会话Cookie和持久Cookie
会话cookie(session),记录用户访问网站时的设置和偏好,但是当用户退出浏览器的时候,会话就会被删除=> 浏览器的开启到关闭就是一次会话,会话cookie就跟随浏览器关闭而销毁。 持久cookie有生命周期expires,存储在用户的硬盘中,浏览器退出或计算机重启,cookie仍然存在。比如你打开语雀记录内容,页面依然是登录的状态,即使你关闭了浏览器,再次打开的时候,依然是登录的状态,这就是cookie自动将数据发送到服务器端,再返回回来的结果,cookie的expiress决定登录状态是否过期。
Cookie的主要作用有3
- 会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息)
- 浏览器行为追踪:跟踪分析用户行为 用户在浏览网站时候,网站存储
- 个性化设置: 用户自定义设置,主题或其他设置
现实世界,可通过体貌特征,身份证件,生物特征,如指纹信息,等手段对用户进行唯一性识别, 主要通过Cookie技术,浏览器指纹,webRTCe等技术进行识别
Cookie常见属性
document.cookie = "key=value;expires=过期时间;path=路径;domain=域名;secure;"
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly
- name: Cookie的名称。Cookie一旦创建,名称便不可更改
- value:该Cookie的值。如果值为Unicode字符,需要为字符编码。如果值为二进制数据,则需要使用BASE64编码
- Domain:访问该Cookie的域名,如果设置为'www.juejin.com', 则所有的以'www.juejin.com’结尾的域名都可以访问该cookie,
- path:cookie使用路径,如果设置为'/sessionhly/',表示只有'/sessionhly'的可以访问该cookie,如果设置为'/',则本域名下的contextPath都可以访问,最后一个字符必须为
/ - Expires/Max-Age:失效时间,单位秒,默认-1;负数表示为临时Cookie,关闭浏览器就失效,如果为0,表示删除该cookie;
- size
- HttpOnly:设置了该属性,表明此类Cookie仅作用于服务器,不能被javascript获取到,
- Secure:boolean, 是否仅被使用安全协议传输。默认false, 安全协议有HTTPS, SSL等,在网络上传输需要将数据加密;只有当使用 SSL 和 HTTPS 协议的时候才会被发送;但是Secure不会阻止对cookie中敏感信息的访问
- SameSite:用于限制第三方Cookie的发送场景,可以防范CSRF攻击和用户追踪
- strict:完全禁止第三方cookie,跨战点的时候,任何情况都不会发送cookie
- Lax: 默认值,
- None:跨站点都发送,设置
None的话,必须开启Secure属性,否则会提示这个警告This Set-Cookie was blocked because it had the "SameSite=None" attribute but did not have the "Secure" attribute, which is required in order to use "SameSite=None"
- SameParty
- Partition key
- Priority
Cookie限制
- 访问cookie时候,同源限制: domain属性
- cookie的个数和尺寸限制 每个域名下可绑定的cookie的个数是有限的,不同浏览器所限制的个数不同; 当超过限制的时候再设置了cookie, 浏览器就会清除以前设置的cookie 浏览器中对单个cookie的尺寸也有限制,一般在4kb;尺寸限制影响到一个域名下的所有cookie, 而不是每个cookie单独限制
Cookie的问题
- 不可以跨域:域名www.baidu,com颁发的cookie不能提交到www.yuque.com上,这是cookie的隐私安全机制决定的=>禁止网站非法获取其他网站的cookie;另外同一个一级域名下的两个二级域名也不能交互使用cookie(如a.miihayy.com和a.miihayy.img.com不能交互使用cookie=> 二者的域名并不严格相同),如果想让所有的miihayy.com名下的二级域名都可以使用该cookie,就需要设置cookie的domain参数
CORS中的cookie
CORS默认不发送Cookie和HTTP认证信息,如果要把cookie发送到服务器,一方面要服务器同意,指定Access-Control-Allow-Credentials字段为true, 另外,也需要前端同学配合,在请求头加上 credentials(fetch请求)
Cookie 只区分域,不区分端口和协议,只要域相同,即使端口号或协议不同,cookie 也能共享。
Cookie安全
浏览器端可以通过document.cookie获取, 在浏览器开发者工具中的Application选项,在左边找到Cookies下拉菜单也能手动添加
通过document.cookie设置的cookie不会覆盖现有的cookie,除非设置的cookie的name在现有cookie集合中已经存在,并且path/domain/secure这几个选项一定要和旧cookie 保持一样。否则不会修改旧cookie,而是添加了一个新的 cookie
Cookie不提供修改、删除操作。若要修改某个Cookie,要新建一个同名的Cookie,添加到response中去覆盖原来的Cookie;若要删除某个Cookie,需要新建一个同名的Cookie,将maxAge设置为0,并添加到response中去覆盖原来的Cookie
- 会话劫持和XSS
- CSRF跨站请求伪造
解决缓解方案:
- 使用
HttpOnly属性可防止通过 JavaScript 访问 cookie 值。 - 用于敏感信息(例如指示身份验证)的 Cookie 的生存期应较短,并且
SameSite属性设置为Strict或Lax。
cookie的替代方案
在浏览器中存储数据的另一种方法是 Web Storage API。window.sessionStorage 和window.localStorage 属性与持续时间中的会话和永久 cookie 相对应,但是存储限制比 cookie 大,并且永远不会发送到服务器。可以使用 IndexedDB API 或基于它构建的库来存储更多结构化的数据
Cookie在顶级域名,二级域名,三级域名直接共享的问题
未指定domain时,默认的domain为用哪个域名访问就是哪个
设置cookie只能在本域名下或者domain级别高于自身的域名下才会生效!如设置hly.com为domain的cookie时候,只有该域名或者其子域名才能获取到这个cookie,
例如server.hly.com发送请求的时,服务器会自动带上server.hly.com以及hly.com域名下的cookie
- domain参数可以设置域名以及自身,不能设置其他域名,包括子域名,否则cookie不生效
- cookie设置为顶级域名,则全部的域名,包括顶级域名,二级域名,三级域名都可以共享该cookie
- 如果cookie设置为当前域名,则当前域名下的所有子域名都可以共享该cookie
cookie应用
- js-cookie espress中用cookie-parser
cookie,token,jwt
什么是单点登录?如何实现
JavaScript脚本延迟加载的方式
延迟加载就是等页面加载完成之后再加载javascript文件,有利于提高页面的加载速度
- 设置script的defer, async属性
- defer属性:异步加载,脚本的加载和文档的解析同步解析,然后再文档解析完成之后再指向这个脚本,页面不会被阻塞,(并一定会在onload之后执行,在DOMContentLoaded之前执行,多个defer会顺序执行)
- 多个设置了defer属性的脚本按规范来说是顺序执行的,
- async:异步下载不会阻塞页面的解析过程,脚本加载完成之后立即执行js脚本,这个时候如果文档木解析完成就阻塞
- 一定会在onload之前执行,
- 多个async属性的脚步顺序是不可预测的,一般不会按照代码的顺序依次执行 => 不按声明的顺序,只要加载完就可能立即执行,会影响渲染 2.动态创建DOM方式:对文档的加载事件进行监听,当文档加载完成之后再动态的创建script标签引入js脚本
- 使用setTimeout延迟方法:设置一个定时器来延迟js脚本文件
- 让js最后加载:将js脚本放在文档的底部,使js脚本尽可能的在最后来加载执行
数组
有哪些方法
- 数组与字符串之间的转换:toString(), join(),toLocalString()
- 改变原数组的方法:shift, unshift,pop,push,reverse, sort(默认排序是将元素转化为字符串后排序的),splice
- 不改变原数组的:concat,join,slice, filter, some, every, reduce,map,forEach,# copyWithin,find, findIndex, findLast, includes, indexOf,
- map: 返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值
- forEach:没有返回值,误以为
forEach会改变原数组,通常是因为forEach方法的回调函数中,我们自己做了更改原数组的操作(MDN:forEach不会直接改变调用它的对象,但是那个对象可能会被callbackFn函数改变。);不可链式调用
- 数组归并方法:reduce, reduceRight
- 迭代器方法:keys(), values(), entries()
- 其他方法
- for..in
- for..of
- flat
- 类数组对象
- 函数参数对象arguments
- 用 getElementsByTagName/ClassName/Name 获得的 HTMLCollection
- 用 querySelector 获得的 NodeList
特性:
拥有length属性,
Object.prototype.toString.call返回[object arguments]
应用
扁平化数组
(1)递归实现
const flatten = (arr) => {
const result = [];
arr.forEach((item) => {
if(Array.isArray(item)){
result.push(...flatten(item))
}else {
result.push(item)
}
})
return result
}
(2)reduce迭代实现
function flatten(arr){
return arr.reduce((pre,cur)=>{
return pre.concat(Array.isArray(cur) ? flatten(cur) : cur)
},[])
}
(3)扩展运算符实现
function flatten(arr){
while(arr.some(item => Array.isArray(item))){
arr = [].concat(...arr)
}
return arr
}
(4)split 和 toString
function flatten(arr) {
return arr.toString().split(',');
}
(5)ES6中flat方法 (6) 正则+JSON方法
function flatten(arr) {
let str = JSON.stringify(arr)
str = str.replace(/(\[|\])/g,'')
str = '[' + str + ']'
return JSON.parse(str)
}
数组去重
- Set方法
let arr = [1,2,2,3]
Array.form(new Set(arr))
- map实现 //用map对象来存储之前走过的对象
function uniqueArr(array){
let map = {};
let result = [];
for(let i = 0;i < array){
if(!map.hasOwnProperty(arrar[i])){
let item = array[i]
map[item] = 1;
result.push(item)
}
}
return result
}
数组乱序
function shuffle(arr){
for(let i = 0; i< arr.length;i++){
let randomIndex= Math.random(Math.random() * (arr.length - 1 + i) ) + i
[arr[i], arr[randomIndex]] = [arr[randomIndex], arr[i]]
}
return arr;
}
浅拷贝
如果拷贝的是基本类型,拷贝的就是基本类型的值,如果是引用类型,拷贝的是内存地址,此时,如果其中一个对象的引用地址发生变化了,另一个对象也会发生变化 浅拷贝实现的方法有
- 直接赋值:
let arr1 = [11,22,33]; let arr2 = arr1 - Object.assign(target, source1,source2,...)
- 如果对象有同名属性,后面的属性会覆盖前面的
- null, undefined不能转化成对象,所以第一个参数不能为null, undefined
- 不会拷贝对象的继承属性,不会拷贝对象的不可枚举属性,可以拷贝Symbol类型的属性
- 扩展运算符:
let cloneObj = { ...obj }; - slice
- concat
- 手写一个浅拷贝 实现思路:
- 基本类型直接拷贝
- 引用类型,用一个新的空间存储,拷贝其对象上的属性
function shallowCopy(obj){
if(!object || typeof. object !== 'object'){
return
}
let newObj = Array.isArray(obj) ? [] : {};
for(let key in obj){
if(obj.hasOwnProperty(key)){
newObj[key] = obj[key]
}
}
return newObj
}
深拷贝
- 暴力JSON.stringify() 原理:利用JSON.stringify对js对象的序列化, 缺点:
- 对函数,undefined, symbol等JSON.stringify处理之后会消失;
- 不能拷贝不可枚举的属性
- 不能拷贝对象的原型链
- 拷贝RegExp引用类型会变成空对象
- 对象中有NaN, infinity等,JSON序列化之后会变成null
- 无法拷贝对象的循环应用
- lodash中的cloneDeep方法
3.手写深拷贝函数
function getType(obj){
return Object.prototype.toString.call(obj).slice(8, -1).toLowercase();
}
//处理Date类型
function handleDate(source){
return new Date(source.valueOf())
}
// 处理RegExp
function handleRegExp(source){
let pattern = source.valueOf();
let flag = '';
flag += pattern.global ? 'g' : '';
flag += pattern.ingoreCase 'i' : '';
flag += pattern.multiline ? 'm' :'';
return new RegExp(pattern.source, flag)
}
function cloneDeep(source, hash = new WeakMap()){
let type = getType(source)
if(type == 'regexp){
return handleRegExp(source)
}else if(type === 'date'){
return handleDate(source)
}else if(type !== 'array' && type !== 'object'){
return source;
}
// WeakMap解决循环引用问题
if(!source){
return source
}
if(hash.has(source)){
return hash.get(source)
}
let target = Array.isArray(source) ? [...source] : {...source}
hash.set(source, target)
for(let key in source){
if(Object.propertype.hasOwnProperty.call(source, key)){
let prototypeType = getType(source)
// 根据类型分策略处理
if(prototypeType === 'regexp){
target[key] = handleRegExp(source[key])
}else if(prototypeType === 'date'){
target[key] = handleDate(source[key])
}else if(prototypeType !== 'array' && prototypeType !== 'object'){
target[key] = source[key]
}else {
target[key] = cloneDeep(source[key], hash)
}
}
}
return target
}
函数柯理化实现
sum(1,2,3),sum(1)(2,3), sum(1)(2)(3)
Array.prototype.reduce
String.prototype.trim
ES6
Map, WeakMap
- Map
- 普通Object中的键值对中的键只能是字符串,,Map结构,键不受限制,可以是任意类型,它的键可以是一个原始数据类型,只要两个键严格相同就视为一个键
- 拥有的操作方法
- size:
- set(key, value):设置键为key对应的值value, 如果key已经存在有值,就会更新值,否则就是生成新键
- get(key):如果找不到key,返回undefined
- has(key):返回boolean,判断某个键是否在Map对象中
- delete(key):删除某个键,返回true,如果删除失败,返回false
- clear():map.clear()清除所有成员,没有返回值
- Map结果提供的遍历器生成函数和遍历方法
- keys()
- values()
- entries()
- forEach()
- WeakMap
- 也是一组键值对的集合,键是弱引用,其键必须是对象,原始数据类型不能作为key值,而值可以是任意的。
- 设计的目的:使用一个对象,不需要这个对象的时候,需要手动删除这个引用,否则垃圾回收机制不会是释放这个对象占用的内存;因为WeakMap的键名所引用的对象都是弱引用,垃圾回收机制不会讲该引用考虑在内,只要改对象的引用的计数为0,WeakMap 里面的键名对象和所对应的键值对会自动消失,垃圾回收机制就会释放该对象所占有的内存,不用手动删除引用
- 拥有的方法大部分同Map, 只是没有clear()方法
map ,Object
- Map:
- Map默认情况不包含任何键,只包含显式插入的键
- 键可以是任意类型的值,包括函数、对象或任意基本类型
- key是有序的,迭代的时候,Map对象是以插入的顺序返回键值
- 可迭代,iterable
- 可以通过size读取对象属性的个数
- 在频繁的增删键值的场景性能更好
- Object
- 存在原型链上的属性,原型链上的键名可能和自己对象上设置的名冲突
- 键必须是string类型或者Symbol
- 键的顺序是无序的
- 只能手动计算对象键值的个数
- 迭代Object需要以某种方式获取它的键然后才能迭代(Object.keys(),或其他)
箭头函数
1.没有prototype,没有自己的this指向(会在自己作用域的上一层继承this,在它在定义时已经确定了),不能使用arguments,不能使用构造函数,不能使用generator创建函数,不能使用yeild关键字 2.不能使用call,apply,bind修改箭头函数的this指向 3.不能通过new 一个箭头函数 4. 写法比普通函数简洁,
- 没有参数,直接写一个空括号即可
- 只有一个参数,可省去参数的括号
- 函数体的返回值只有一句,可省略大括号
new操作法实现的步骤
- 创建一个对象
- 将构造函数的作用域赋给新对象,对象的
__proto__指向构造函数的prototype原型对象 - 指向构造函数的代码,(给这个对象添加原型上的属性和方法)
- 判断函数的返回值类型,如果是值类型,返回创建的对象,如果是引用类型,就返回中国引用类型的对象
代码实现:
function myNew(){
let newObj = null;
let constructor = Array.prototype.shift.call(arguments)
let result = null;
if(typeof constructor !== 'function'){
return;
}
// 新建一个空对象,对象的原型为构造函数的prototype
newObj = Object.create(constructor.prototype)
// 将this指向新建对象,并指向函数
result = constructor.apply(newObj, arguments)
let flag = result&&(typeof result === 'object' || typeof result === 'function')
return flag ? result : newObj
}
// 使用
function Student (name, age){
this.name = name;
this.age = age;
}
// myNew(构造函数,初始化参数)
myNew(Student,'hanson', 12)
const ,let ,var区别
- 块级作用域:let, const具有块级作用域,块级作用域解决了ES5中的两个问题(1)内层变量可能覆盖外层变量(2)用来计数的变量泄漏为全局变量
- 变量提升:let const不存在变量提升,只能先声明后使用,否则会报错;var存在变量提升,先使用后声明,变量会提升定义undefined
- 全局添加属性:var声明的变量为全局变量,给该变量添加全局对象的属性;浏览器的全局对象是window, Node中全局对象是global;
- 重复声明:var声明的变量,可以重复声明,后声明的同名变量会覆盖之前的;const let不允许重复声明变量
- 暂时性死区:let, const声明变量之前,该变量是不可用的,即称为暂时性死区
- 初始值设置:在变量声明的时,var,let可以不用设置初始值,但是const声明变量需要设置初始值
- 指针指向:let const都是ES6语法,let创建的变量是可以更改指针指向(可以重新赋值)。但const声明的变量是不允许改变指针的指向;const定义的变量,指向的那个内存地址不能改动,对于基本类型的数据--数值,字符串,布尔值,它的值就保存在指针指向的内存地址,等同于常量;对于引用类型,地址固定不能改,保存的数据结构是可以修改的
Proxy
Proxy对象,用于创建一个对象的代理,从而实现基本操作的拦截和自定义(属性查找,赋值, 枚举,函数调用) => 修改程序的默认行为,属于元编程(操纵其他程序或自身作为他们的数据,或在运行时完成部分本该在编译时完成的工作)
格式:let proxy = new Proxy(target, handler)
- new Proxy(: 表示生成一个Proxy实例
- tarhet:要拦截的目标对象,任何类型的对象,包括原生数组,函数,甚至是另一个代理
- handler:通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理的行为
- 要使proxy起作用,需要针对Proxy实例进行操作,而不是针对目标对象进行操作
- 同一个拦截器函数,可以设置多个拦截操作(handler里面定义多个方法)
Proxy支持的拦截操作(13种拦截方法) --阮一峰ES6
- get(target, propKey, receiver), 拦截对象属性的读取, proxy.name,proxy['name'] 2.set(target, propKey, value, receiver), 拦截设置对象的属性=> proxy.name = v, 返回一个布尔值 3.has(target, propKey), 拦截propKey in proxy的操作,返回布尔值
- deleteProperty 5.apply(target, object, args) 6.constructor(target, args),拦截Proxy实例作为函数调用的操作,new proxy(...args)
proxy会改变target中的this指向
proxy会改变target中的this指向,一旦proxy代理了target,target内部的this则指向了proxy,而不是target
let target = {
get: function(){
console.log(this === proxy)
}
}
const handler = {};
const proxy = new Proxy(target, handler)
target.get() // false
proxy,get() // true
porxy 无法代理原生对象的属性
getDate()方法只能在Date对象的实例对象上获取到,即这里的this不是Date,=> 可以通过修改this的指向达到目标
let target = new Date('2023-01-02');
const handler = {
get(target, props){
if(props === 'getDate'){
return target.getDate.bind(target)
}
return Reflect.get(target, handler)
}
}
const proxy = new Proxy(target, handler)
proxy.getDate()
vue中引入proxy解决了什么问题,为什么
前端模块化
开发技巧
工具函数
eslint
lodash 常用API手写
lodash get
loash debounce,throttle
逻辑编程题
实现一个LRU 缓存
Promise.map控制并发数
function promiseMap(list, mapper, concurrency = Infinity){
list = Array.from(list)
return new Promise((resolve, reject)=> {
// 这里类似promise.all的实现方式
let currentIndex = 0;
let result = [];
let reslovveCount = 0;
let len = list.length
function next(){
const idx = currentIndex
currentIndex ++;
Promise.resolve(list[idx)
.then(o => mapper(o, idx))
.then(o => {
result[idx] = o;// 保存对象的值
reslovveCount++;
if(reslovveCount === len){
// 当达到list的length
resolve(result)
}
if(reslovveCount < len){
next()
}
})
}
for(let i = 0; i<concurrency && i < len;i++ ){
next()
}
})
}
promiseMap([1, 2, 3,4,5], x => x + 2).then(i => console.log(i))
排序版本号
实现无限累加sum函数
统计数组中最大数/第二大数
JSONP实现原理
给数字添加千位符
判断两个链表是否相交
统计字符串中出现次数最多的字符
实现一个发布订阅模式
异步sum/add
实现一个函数对URL的querystring进行编码解码
React
React常用的数据同步技术,redux, flux有什么不同
flux有多个store, redux只有一个store
常见的缓存数据的淘汰策略有哪些
- 定时去清理过期的缓存
- 缓存服务器自带的缓存自动失效策略
- 当有用户请求时,再判断该请求的缓存是否过期,如果过期则去底层系统得到新数据并更新缓存
babel编译原理的理解
- 通过babylon将ES6/Es7代码解析成AST
- babel-traverse对AST进行遍历编译,得到新的AST
- 新的AST通过babel-generator换成ES5
想要跳转到同一个目录下的名称为success.html文件名为show的锚点
<a href="success.html#show">跳转</a>
HTTP请求头控制参数
- Accept:浏览器支持的MIME媒体类型,如text/html, application/json,image/webp等
- Accept-Encoding:浏览器发给服务器,声明浏览器支持的编码类型
- Origin:告诉服务器请求从哪里发起的,仅包括协议和域名CORS跨域请求中可以看到response有对应的header
- user-Agent:服务器通过这个请求头, 判断用户的应用类型,操作系统,软件开发商,版本号,内核信息,风控系统,发作弊系统,反爬虫系统等都会采集这类的信息作为参考
- Cache-control的值
- max-age:缓存过期时间,是一个相对时间
- public:表示客户端和代理服务器都会缓存
- private: 表示只在客户端缓存
- no-cache:协商缓存的标识符,表示文件会被缓存但是需要和服务器协商
- no-store: 表示文件不会被缓存
性能优化
- 使用CDN
- 减少DOM元素数量,减少DOM操作
- script标签的加载方式
- defer:异步下载,并一定会在onload之后执行,在DOMContentLoaded之前执行,多个defer会顺序执行
- async:异步下载,并一定会在onload之前执行,不按声明的顺序,只要加载完就可能立即执行,会影响渲染
HOC
- 是纯函数
- 是React中复用组件逻辑的一种高级技巧
- 高阶组件是参数为组件,返回值为新组件的函数
- HOC自身不是React API 的一部分,他是一种基于React的组合特性而形成的设计模式
React中的setState
- 调用setState后会将传入的参数与组件已有的状态进行合并,这期间会有一个调合的过程
- 通过diff运算,新的元素树会和旧的树进行对比,对界面进行最小化的重新渲染
- 构建新的元素树,并着手重新渲染UI界面
webpack中配置优化哪些可以缩小文件搜索范围
- modules告诉webpack去哪些目录下查找引用 的模块,默认值是['node_modules']
- resolve.alias
- 使用noParse
- 通过配置loader的include和exclude
- alias通过创建import或者require的别名,把原理导入模块的路径映射成一个新的导入路径,它的作用是用别名,它的作用是别名代替前面的路径
- resolve.modules
- 使用正则表达式或者一个函数标记模块
- include指明需要执行babel-loader的文件,exclude执行哪些文件不想要执行babel-loder
衡量模块的独立程度
软件设计中,通常用耦合度和内聚度作为衡量模块独立程度,划分模块的一个准则就是高内聚低耦合
避免重排重绘
React中state, props都可以从父组件中接收初始值
Vue
性能优化
前端工程化
webpack
vite
nodejs如何开启进程。进程如何通讯
考察知识点:js单线程,进程,线程,怎么多线程的方式 进程,OS进行资源分配和调度的最小单元,有独立内存空间 线程:OS进行运算调度的最小单元,共享进程的内容空间 js是单进程,但是可以开启多个进程,如webworker (js不可以开启多线程)
为什么需要多进程
- 多核CPU,更适合处理多进程
- 内存较大,多个进程能更好的利用,单进程有内存上限
- 总之,压榨机器资源,更快,更节省
- 开启子进程child_process.fork, cluster.fork
- 使用send, on传递消息 实际工作中,用PM2来开启子进程,进程守护
待补充..., 后面添加