总结

262 阅读1小时+

css

1. css选择器优先级从高到低

优先级相同时,采用就近原则,选择最后出现的样式;继承得来的属性,优先级最低

!important >行内式> id选择器(#mydiv)> 类选择器(.div)> 标签选择器(div,p,h1)> 子选择器(div>p ,带大于号>)> 后代选择器(#head .nav ul li 从父集到子孙集的选择器)> 伪类选择器(:hove,:nth-child)

水平垂直居中的多种实现方式

固定宽高

  1. 绝对定位+负margin值
<div class="box">
    <div class="children-box"></div>
</div>
.box {
    width: 200px;
    height: 200px;
    border: 1px solid red;
    position: relative;
}
.children-box {
    position: absolute;
    width: 100px;
    height: 100px;
    background: yellow;
    left: 50%;
    top: 50%;
    margin-left: -50px;
    margin-top: -50px; 
}

2.绝对定位+transform(子标签不定宽高也可用)

<div class="box">
  <div class="children-box"></div>
</div>
.box {
    width: 200px;
    height: 200px;
    border: 1px solid red;
    position: relative;
}
.children-box {
    position: absolute;
    width: 100px;
    height: 100px;
    background: yellow;
    left: 50%;
    top: 50%;
    transform: translate(-50%,-50%);
}

3.绝对定位,left/top/right/bottom + margin

<div class="box">
  <div class="children-box"></div>
</div>
.box {
    width: 200px;
    height: 200px;
    border: 1px solid red;
    position: relative;
}
.children-box {
    position: absolute;
    width: 100px;
    height: 100px;
    background: yellow;
    left: 0;
    right: 0;
    top: 0;
    bottom: 0;
    margin: auto;
}

4.flex布局(子标签不定宽高也可用)

<div class="box">
  <div class="children-box"></div>
</div>
.box {
    width: 200px;
    height: 200px;
    border: 1px solid red;
    display: flex;
    justify-content: center;
    align-items: center;
}
.children-box {
    width: 100px;
    height: 100px;
    background: yellow;
}

5.table-cell + vertical-align + inline-block/margin: auto(子标签不定宽高也可用)

<div class="box">
  <div class="children-box"></div>
</div>
.box {
    width: 200px;
    height: 200px;
    border: 1px solid red;
    display: table-cell;
    text-align: center;
    vertical-align: middle;
}
.children-box {
    width: 100px;
    height: 100px;
    background: yellow;
    display: inline-block;
    /* margin: auto; */
}

css3 新特性

  1. 颜色:新增RGBA、HSLA模式
  2. 文字阴影:(text-shadow)
  3. 边框:圆角(border-radius)边框阴影:box-shadow
  4. 盒子模型:box-sizing
  5. 背景:background-size,background-origin background-clip(削弱)
  6. 渐变:linear-gradient(线性渐变):eg: background-image: linear-gradient(100deg, #237b9f, #f2febd);radial-gradient (径向渐变)
  7. 过渡:transition可实现动画
  8. 自定义动画: animate@keyfrom
  9. 媒体查询:多栏布局@media screen and (width:800px)
  10. border-image
  11. 2D转换:transform:translate(x,y) rotate(x,y)旋转 skew(x,y)倾斜 scale(x,y)缩放
  12. 3D转换
  13. 字体图标:Font-Face
  14. 弹性布局:flex

盒模型

标准盒模型和怪异盒模型(IE盒模型)

一个盒子,会有 content,padding,border,margin 四部分,

标准的盒模型的宽高指的是 content 部分

ie 的盒模型的宽高包括了 content+padding+border

标准盒模型:  一个块的总宽度 = width+margin(左右)+padding(左右)+border(左右)

怪异盒模型:  一个块的总宽度 = width+margin(左右)(既 width 已经包含了 padding 和 border 值

我们可以通过 box-sizing 修改盒模型,box-sizing border-box content-box

margin 合并

在垂直方向上的两个盒子,他们的 margin 会发生合并(会取最大的值),比如上边盒子设置margin-bottom:20px,下边盒子设置margin-top:30px;,那么两个盒子间的间距只有30px,不会是50px

解决 margin 合并,我们可以给其中一个盒子套上一个父盒子,给父盒子设置 BFC

margin 塌陷

效果: 一个父盒子中有一个子盒子,我们给子盒子设置margin-top:xxpx结果发现会带着父盒子一起移动(就效果和父盒子设置margin-top:xxpx的效果一样)

解决: 1、给父盒子设置 border,例如设置border:1px solid red; 2、给父盒子设置 BFC

CSS3动画是什么?

动画是使元素从一种样式逐渐变化为另一种样式的效果。

您可以改变任意多的样式任意多的次数。

请用百分比来规定变化发生的时间,或用关键词 "from" 和 "to",等同于 0% 和 100%。

0% 是动画的开始,100% 是动画的完成。

为了得到最佳的浏览器支持,您应该始终定义 0% 和 100% 选择器。

CSS3 @keyframes 规则

要创建 CSS3 动画,你需要了解 @keyframes 规则。

@keyframes 规则是创建动画。

@keyframes 规则内指定一个 CSS 样式和动画将逐步从目前的样式更改为新的样式。

image.png

image.png

image.png

CSS3:过渡动画 transition

过渡 transition 是一个复合属性,包括 transition-propertytransition-durationtransition-timing-functiontransition-delay 四个子属性。

1、transition-property

该属性规定应用过渡效果的 CSS 属性的名称。(当指定的 CSS 属性改变时,过渡效果将开始)。

过渡效果通常在用户将鼠标指针浮动到元素上时发生。

取值:

  • none:没有属性会获得过渡效果
  • all:所有属性都将获得过渡效果
  • property:定义应用过渡效果的css属性名称列表,列表以逗号分隔
2、transition-duration

该属性的单位是秒s或毫秒ms。

注意:

  • 该属性不能为负值
  • 若该属性为 0s 则为默认值,若为 0 则为无效值。所以必须带单位
  • 该值为单值时,即所有过渡属性都对应同样时间;该值为多值时,过渡属性按照顺序对应持续时间
3、transition-timing-function

过渡时间函数用于定义元素过渡属性随时间变化的过渡速度变化效果

取值:

  • linear:以相同速度开始至结束的过渡效果
cubic-bezier(0,0,1,1)
  • ease:慢速开始,然后变快,然后慢速结束的过渡效果
cubic-bezier(0.25,0.1,0.25,1)
  • ease-in:以慢速开始的过渡效果
cubic-bezier(0.42,0,1,1)
  • ease-out:以慢速结束的过渡效果
cubic-bezier(0,0,0.58,1)
  • ease-in-out:以慢速开始和结束的过渡效果
cubic-bezier(0.42,0,0.58,1)
  • step-start: 直接位于结束处
steps(1,start)
  • step-end: 位于开始处经过时间间隔后结束
steps(1,end)
  • cubic-bezier(n, n, n, n) :在cubic-bezier函数中定义自己的值。可能的值是0至1之间的数值
4、transition-delay

该属性定义元素属性延迟多少时间后开始过渡效果,该属性的单位是秒s或毫秒ms。

注意:

  • 该属性若为负值,无延迟效果,但过渡元素的起始值将从 0 变成设定值(设定值=延迟时间+持续时间)。若该设定值小于等于 0,则无过渡效果;若该设定值大于 0,则过渡元素从该设定值开始完成剩余的过渡效果
  • 若该属性为0s则为默认值,若为0则为无效值。所以必须带单位
  • 该值为单值时,即所有过渡属性都对应同样时间;该值为多值时,过渡属性按照顺序对应持续时间 示例:
<style>
div {
    width: 300px;
    height: 100px;
    padding-top: 20px;
    line-height: 100px;
    margin: 200px auto 0;
    text-align: center;
    font-size: 40px;
    font-weight: bold;
    font-family: '华文行楷';
    background: #000;
    color: #ff6600;
}
</style>

<div>好好学习,天天向上</div>

然后,给 div 加上 hover 效果

<style>
div:hover {
    text-shadow: 0px 0px 2px #fff,
          0px -3px 3px #1eb,
          0px -6px 4px #01defd,
          0px -12px 6px #08f;
}
</style>

当鼠标进入这个div时,文字阴影一瞬间出现

接着,我们修改hover效果

<style>
div {
    transition: all 1s liner 0s;
}
</style>

鼠标进入 div 时,背景阴影经过了 1s 的过渡过程

background复合属性顺序_background(css复合写法)

单个属性的写法

.sample1 {

    /*背景颜色*/

    background-image: url(sample.gif); /*背景图片*/

    background-repeat: no-repeat; /*平铺(?)*/

    background-attachment: fixed; /*随文本滚动(?),很少用到*/

    background-position: center center; /*背景图片位置*/

}

复合属性的写法

background : background-color background-image background-repeat background-attachment background-position;
//默认值
background: transparent none repeat scroll 0% 0%;
background: 透明 / 无背景图片 / 平铺 / 背景图片随文本滚动(不理解的一定要自己动手试一下) / 位于元素左上角

css文本显示省略号

如果只显示一行

overflow: hidden;
text-overflow:ellipsis;
white-space: nowrap;

如果需要显示多行

word-break: break-all;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;

什么是 rem、px、em 区别

rem 是一个相对单位,rem 的是相对于 html 元素的字体大小没有继承性

em 是一个相对单位,是相对于父元素字体大小有继承性

px 是一个“绝对单位”,就是 css 中定义的像素,利用 px 设置字体大小及元素的宽高等,比较稳定和精确。

vh、vw:主要用于页面视口大小布局,在页面布局上更加方便简单

响应式布局,响应式布局有哪些实现方式?什么是响应式设计?响应式设计的基本原理是什么?

1.百分比布局,但是无法对字体,边框等比例缩放

2.弹性盒子布局 display:flex

3.rem 布局,1rem=html 的 font-size 值的大小

css3 媒体查询 @media screen and(max-width: 750px){} 5.vw+vh

6.使用一些框架(bootstrap,vant)

什么是响应式设计:响应式网站设计是一个网站能够兼容多个终端,智能地根据不同设备环境进行相对应的布局

响应式设计的基本原理:基本原理是通过媒体查询检测不同的设备屏幕尺寸设置不同的 css 样式 页面头部必须有 meta 声明的

清除浮动的方式

  • 添加额外标签
<div class="parent">
    //添加额外标签并且添加clear属性
    <div style="clear:both"></div>
    //也可以加一个br标签
</div>
复制代码
  • 父级添加overflow属性,或者设置高度
  • 建立伪类选择器清除浮动
//在css中添加:after伪元素
.parent:after{
    /* 设置添加子元素的内容是空 */
    content: '';
    /* 设置添加子元素为块级元素 */
    display: block;
    /* 设置添加的子元素的高度0 */
    height: 0;
    /* 设置添加子元素看不见 */
    visibility: hidden;
    /* 设置clear:both */
    clear: both;
}

link @import 导入 css

link 是 XHTML 标签,除了加载 CSS 外,还可以定义 RSS 等其他事务;

@import 属于 CSS 范畴, 只能加载 CSS。

link 引用 CSS 时,在页面载入时同时加载;@import 需要页面网页完全载入以后加载。link

无兼容问题;@import 是在 CSS2.1 提出的,低版本的浏览器不支持。 link 支持使用 Javascript 控制 DOM 去改变样式;而@import 不支持。

隐藏页面中某个元素的方法

1.opacity:0,该元素隐藏起来了,但不会改变页面布局,并且,如果该元素已经绑定 一些事件,如click 事件,那么点击该区域,也能触发点击事件的

2.visibility:hidden,该元素隐藏起来了,但不会改变页面布局,但是不会触发该元素已 经绑定的事件 ,隐藏对应元素,在文档布局中仍保留原来的空间(重绘)

3.display:none,把元素隐藏起来,并且会改变页面布局,可以理解成在页面中把该元素。 不显示对应的元素,在文档布局中不再分配空间(回流+重绘)

HTML

浏览器内核

IE: trident 内核

Firefox:gecko 内核

Safari: webkit 内核

Opera: 以前是 presto 内核,Opera 现已改用 GoogleChrome 的 Blink 内核

Chrome: Blink(基于 webkit,Google 与 Opera Software 共同开发)

你是怎么理解 HTML 语义化

  • 概念

    HTML5的语义化指的是合理正确的使用语义化的标签来创建页面结构。【正确的标签做正确的事】 比如表示段落用 p 标签、表示标题用 h1-h6 标签、表示文章就用 article 等。

  • 语义化标签

    header nav main article section aside footer

  • 语义化的优点:

    • 没CSS样式的情况下,页面整体也会呈现很好的结构效果
    • 代码结构清晰,易于阅读,
    • 利于开发和维护 方便其他设备解析(如屏幕阅读器)根据语义渲染网页。
    • 有利于搜索引擎优化(SEO),搜索引擎爬虫会根据不同的标签来赋予不同的权重

DOCTYPE 的作用

  1. 声明位于文档中的最前面,处于 标签之前。告知浏览器以何种模式来渲染文档。
  2. 严格模式的排版和 JS 运作模式是 以该浏览器支持的最高标准运行。
  3. 在混杂模式中,页面以宽松的向后兼容的方式显示。模拟老式浏览器的行为以防止站 点无法工作。
  4. DOCTYPE 不存在或格式不正确会导致文档以混杂模式呈现。复制代码 你知道多少种 Doctype 文档类型? 该标签可声明三种 DTD 类型,分别表示严格版本、过渡版本以及基于框架的 HTML 文档。 HTML 4.01 规定了三种文档类型:Strict、Transitional 以及 Frameset。 XHTML 1.0 规定了三种 XML 文档类型:Strict、Transitional 以及 Frameset。 Standards (标准)模式(也就是严格呈现模式)用于呈现遵循最新标准的网页,而 Quirks (包容)模式(也就是松散呈现模式或者兼容模式)用于呈现为传统浏览器而设计的网页。

html中行内元素和块级元素都有哪些

  • 行内元素 (不能设置宽高,设置宽高无效) span,a,image,b,br,button,input,label,select
  • 行内块元素:img, input
  • 块元素: div,p,ul,ol,header,footer,h1,form,video,table
  • 知名的空元素 br, hr,img, input,link,meta 可以通过 display 修改 inline-block, block, inline

什么是块级元素?

  • 总是在新行上开始;
  • 高度,行高以及外边距和内边距都可控制;
  • 宽度缺省是它的容器的100%,除非设定一个宽度。
  • 它可以容纳内联元素和其他块元素

什么是行内元素?

  • 和其他元素都在一行上;
  • 高,行高及外边距和内边距不可改变;
  • 宽度就是它的文字或图片的宽度,不可改变
  • 内联元素只能容纳文本或者其他内联元素

行内元素和块级元素的区别

1)、行内元素

①:设置宽高无效

②:对margin设置左右方向有效,而上下无效;padding设置左右方向有效,而上下无效。

③:不会自动换行

2)、块级元素

①:可以设置宽高

②:设置margin和padding都有效

③:可以自动换行

④:多个块状,默认排列从上到下

html5 新特性

  1. 语义化标签, header, footer, nav, aside,article,section ⭐⭐
  2. 增强型表单
  3. 视频 video 和音频 audio
  4. Canvas 绘图
  5. SVG绘图
  6. 地理定位
  7. 拖放 API
  8. WebWorker
  9. WebStorage( 本地离线存储 localStorage、sessionStorage )⭐⭐
  10. WebSocket

本地存储

  • cookie
  • sessionStorage
  • localStorage 区别

关于cookiesessionStoragelocalStorage三者的区别主要如下:

  • 存储大小:cookie数据大小不能超过4ksessionStoragelocalStorage虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大
  • 有效时间:localStorage存储持久数据,浏览器关闭后数据不丢失除非主动删除数据; sessionStorage数据在当前浏览器窗口关闭后自动删除;cookie设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭
  • 数据与服务器之间的交互方式,cookie的数据会自动的传递到服务器,服务器端也可以写cookie到客户端; sessionStoragelocalStorage不会自动把数据发给服务器,仅在本地保存

应用场景

在了解了上述的前端的缓存方式后,我们可以看看针对不对场景的使用选择:

  • 标记用户与跟踪用户行为的情况,推荐使用cookie
  • 适合长期保存在本地的数据(令牌),推荐使用localStorage
  • 敏感账号一次性登录,推荐使用sessionStorage
  • 存储大量数据的情况、在线文档(富文本编辑器)保存编辑历史的情况,推荐使用indexedDB

BFC

BFC(Block Formatting Context),即块级格式化上下文,它是页面中一个独立的容器,容器中的元素不会影响到外面的元素

触发条件

触发BFC的条件包含不限于:

  • 根元素,即HTML元素
  • 浮动元素:float值为left、right
  • overflow值不为 visible,为 auto、scroll、hidden
  • display的值为inline-block、inltable-cell、table-caption、table、inline-table、flex、inline-flex、grid、inline-grid
  • position的值为absolute或fixed 解决什么问题
  1. 可以用来解决两栏布局BFC 的区域不会与 float box 重叠

.left { float: flet; } .right { overflow: hidden; }

  1. 解决 margin 塌陷和 margin 合并问题

  2. 解决浮动元素无法撑起父元素

浏览器运行机制

1、创建DOM树

2、构建渲染树,CSS渲染

3、布局渲染,每个元素的大小、位置

4、绘制渲染树、再画出来

重绘:改变元素的外观属性例如div的color、background-color、等属性发生改变时

重排(回流):元素的规模尺寸、布局、隐藏改变时

代价:耗时,导致浏览器卡慢

JS

js的基本数据类型

基本数据类型

ES5的5种:Null,undefined,Boolean,Number,String, ES6新增:Symbol表示独一无二的值 ES10新增:BigInt 表示任意大的整数 一种引用数据类型:(本质上是由一组无序的键值对组成)

引用数据类型Object。包含Object、Array、 function、Date、RegExp。 JavaScript不支持创建任何自定义类型的数据,也就是说JavaScript中所有值的类型都是上面8中之一。

console.log(typeof(null))   // object

数据类型存储以及堆栈内存是什么

基本数据类型:直接存储在栈内存中,占据空间小,大小固定,属于被频繁使用的数据。指的是保存在栈内存中的简单数据段;number string 布尔

引用数据类型:同时存储在栈内存与堆内存中,占据空间大,大小不固定。

引用数据:类型将指针存在栈中,将值存在堆中。 当我们把对象值赋值给另外一个变量时,复制的是对象的指针,指向同一块内存地址,意思是,变量中保存的实际上只是一个指针,这个指针指向内存堆中实际的值,数组 对象

堆(heap)和栈(stack)有什么区别存储机制

栈: 是一种连续储存的数据结构,具有先进后出后进先出的性质。

通常的操作有入栈(压栈),出栈和栈顶元素。想要读取栈中的某个元素,就是将其之间的所有元素出栈才能完成。

堆: 是一种非连续的树形储存数据结构,具有队列优先,先进先出; 每个节点有一个值,整棵树是经过排序的。特点是根结点的值最小(或最大),且根结点的两个子树也是一个堆。常用来实现优先队列,存取随意。

什么是函数式编程? 命令式编程?声明式编程?

声明式编程:专注于”做什么”而不是”如何去做”。在更高层面写代码,更关心的是目标,而不是底层算法实现的过程。 如:css, 正则表达式,sql 语句,html, xml…

命令式编程(过程式编程) : 专注于”如何去做”,这样不管”做什么”,都会按照你的命令去做。解决某一问题的具体算法实现。

如: for()

函数式编程:把运算过程尽量写成一系列嵌套的函数调用。

如 : forEach()

数组转字符串的方法(3种)

方法转化后类型用法特点
toString()string将数组转换成一个字符串默认用“,”隔开
toLocaleString()string把数组转换成本地约定的字符串默认用“,”隔开
join()string将数组元素连接起来以构建一个字符串可以随意设定分隔符

1、toString()

<script type="text/javascript">
	var a=[1,2,3,4,5,6,7,8,9,0];//原数组
	var b=a.toString();
	console.log(a)
	console.log(b)
	console.log(typeof(a))
	console.log(typeof(b))
</script>

image.png

2、toLocaleString()

<script type="text/javascript">
	var a=[1,2,3,4,5,6,7,8,9,0];//原数组
	var d=a.toLocaleString();
	console.log(d);
	console.log(typeof(d));
</script>

image.png

3、join()

<script type="text/javascript">
	var a=[1,2,3,4,5,6,7,8,9,0];//原数组
	var c=a.join(':');
	console.log(c)
	console.log(typeof(c))
</script>

image.png

4.split 使用 split() 方法把字符串转换为数组

<script type="text/javascript">
	var a=[1,2,3,4,5,6,7,8,9,0];//原数组
	var b=a.toString();
	var c=a.join(':');
	console.log(b)
	console.log(c)
	
	var bv=b.split(',');
	var cv=c.split(':')
	console.log(bv);
	console.log(cv);
</script>

image.png

数组常用方法

下面前三种是对原数组产生影响的增添方法,第四种则不会对原数组产生影响

  • push() 接收任意数量的参数,并将它们添加到数组末尾,返回数组的最新长度
  • unshift() 开头添加
  • concat() 首先会创建一个当前数组的副本,然后再把它的参数添加到副本末尾,最后返回这个新构建的数组,不会影响原始数组

下面三种都会影响原数组,最后一项不影响原数组:

  • pop() 删除数组的最后一项,同时减少数组的length 值,返回被删除的项
  • shift() 删除数组的第一项,同时减少数组的length 值,返回被删除的项
  • splice() 传入两个参数,分别是开始位置,删除元素的数量,返回包含删除元素的数组
  • slice() 创建一个包含原有数组中一个或多个元素的新数组,不会影响原始数组

即修改原来数组的内容,常用splice

传入三个参数,分别是开始位置,要删除元素的数量,要插入的任意多个元素,返回删除元素的数组,对原数组产生影响

即查找元素,返回元素坐标或者元素值

  • indexOf() 返回要查找的元素在数组中的位置,如果没找到则返回 -1
  • includes() 返回要查找的元素在数组中的位置,找到返回true,否则false
  • find() 返回第一个匹配的元素

排序方法

数组有两个方法可以用来对元素重新排序:

  • reverse() 将数组元素方向反转

下面这个被问过,所以重点展开

  • sort(首元素地址(必填), 尾元素地址的下一个地址(必填), 比较函数(非必填));

如果直接sort(数组名),则从小到大排序(即升序),以下为倒叙

var arr4 = [30,10,111,35,1899,50,45];
arr4.sort(function(a,b){
	return b - a;
})
console.log(arr4);//输出 [1899, 111, 50, 45, 35, 30, 10]

转换方法

常见的转换方法有:

join()

join() 方法接收一个参数,即字符串分隔符,返回包含所有项的字符串

迭代方法

常用来迭代数组的方法(都不改变原数组)有如下:

  • some() 对数组每一项都运行传入的函数,如果有一项函数返回 true ,则这个方法返回 true
  • every() 对数组每一项都运行传入的函数,如果对每一项函数都返回 true ,则这个方法返回 true
  • forEach() 对数组每一项都运行传入的函数,没有返回值
  • filter() 对数组每一项都运行传入的函数,函数返回 true 的项会组成数组之后返回
  • map() 对数组每一项都运行传入的函数,返回由每次函数调用的结果构成的数组.

去重方法

1、利用ES6 Set去重(ES6中最常用)

function unique (arr) {
  return Array.from(new Set(arr))
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
 //[1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {}, {}]

2、利用for嵌套for,然后splice去重(ES5中最常用)

function unique(arr){            
        for(var i=0; i<arr.length; i++){
            for(var j=i+1; j<arr.length; j++){
                if(arr[i]==arr[j]){         //第一个等同于第二个,splice方法删除第二个
                    arr.splice(j,1);
                    j--;
                }
            }
        }
return arr;
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
    console.log(unique(arr))
    //[1, "true", 15, false, undefined, NaN, NaN, "NaN", "a", {…}, {…}]     //NaN和{}没有去重,两个null直接消失了

3、利用indexOf去重

function unique(arr) {
    if (!Array.isArray(arr)) {
        console.log('type error!')
        return
    }
    var array = [];
    for (var i = 0; i < arr.length; i++) {
        if (array .indexOf(arr[i]) === -1) {
            array .push(arr[i])
        }
    }
    return array;
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
   // [1, "true", true, 15, false, undefined, null, NaN, NaN, "NaN", 0, "a", {…}, {…}]  //NaN、{}没有去重

4、利用includes

function unique(arr) {
    if (!Array.isArray(arr)) {
        console.log('type error!')
        return
    }
    var array =[];
    for(var i = 0; i < arr.length; i++) {
            if( !array.includes( arr[i]) ) {//includes 检测数组是否有某个值
                    array.push(arr[i]);
              }
    }
    return array
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
    console.log(unique(arr))
    //[1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {…}, {…}]     //{}没有去重

字符串的方法

1chartAt( ):返回在指定位置的字符;
2concat( ):返回新的字符串**,将一个或多个字符串与原字符串连接合并
3indexOf( ):检索字符串,返回第一次出现的索引,没有出现则为-1
4lastIndexOf(searchValue[ fromIndex]) 返回从字符串尾部开始第一次出现的索引,
没有则-1,fromIndex的值相对于从尾部开始的索引
5split( ):返回一个以指定分隔符出现位置分隔而成的一个数组,数组元素不包含分隔符
6substr( ):从起始索引号提取字符串中指定数目的字符;
7substring( ):提取字符串中两个指定的索引号之间的字符;
8toLowerCase( ):字符串转小写;
9toUpperCase( ):字符串转大写;
10valueOf( ):返回某个字符串对象的原始值; 
11trim( ):删除字符串两边的空格;
12、trimeState 取出开始的空格
13、trimeEnd  去除末尾空格
14includes(searchString[, position])返回boolean,判断一个字符串是否包含在另一个字符串中,
从postition索引开始搜寻,默认0
15slice( ):提取字符串片段,并在新的字符串中返回被提取的部分;
16search(regexp)返回首次匹配到的索引,没有则-1,执行正则表达式和 String 对象之间的一个搜索匹配
17toString()返回一个表示调用对象的字符串,该方法返回指定对象的字符串形式
18trim()返回去掉两端空白后的新字符串 还有trimend trimstart
19replace() 把指定的字符串替换成为别的字符

bind、call、apply 区别

  • 1.call和apply会调用函数,且会改变函数内部的this指向
    2.call和apply传递的参数不一样,call传递参数aru1,aru2.形式 而apply必须是数组形式[arg]
    3.bind 不会调用函数,可以改变函数内部指向
  • 应用场景:
    1.call经常做继承
    2.apply经常和数组有关系,比如借助于数学对象实现数组的max、min
    3.bind不调用函数,但改变this指向,比如改变定时器内部的this指向
  • apply:调用一个对象的一个方法,用另一个对象替换当前对象。例如:B.apply(A, arguments);即 A 对象应用 B 对象的方法。 call:调用一个对象的一个方法,用另一个对象替换当前对象。例如:B.call(A, args1,args2); 即 A 对象调用 B 对象的方法。 bind 除了返回是函数以外,它的参数和 call 一样。

null 和 undefined 的区别?

相同:

在 if 语句中 null 和 undefined 都会转为false两者用相等运算符比较也是相等

首先 Undefined 和 Null 都是基本数据类型,这两个基本数据类型分别都只有一个值,就是 undefined 和 null。

不同:

undefined 代表的含义是未定义,

定义了形参,没有传实参,显示undefined

一般变量声明了但还没有定义的时候会返回 undefined

对象属性名不存在时,显示undefined

函数没有写返回值,即没有写return,拿到的是undefined

null 代表的含义是空对象。也作为对象原型链的终点

null 主要用于赋值给一些可能会返回对象的变量,作为初始化。

js数据类型判断,条件分支

if语句和逻辑运算

所有基本类型中Boolean值是false的只有6个,分别是 : 0 NaN ' ' null undefined false 引用类型Boolean值全是true.

if条件是单个值时,如果是truly值,条件成立, 如果是falsely值,条件不成立

逻辑运算符以及他们的运算规则?

    && 逻辑与    两边都是true,才返回true,否则返回false
    || 逻辑或    两边只要有一个是true,就返回true,否则返回false
    ! 逻辑非   用来取一个布尔值相反的值  

数据类型判断

typeof 对于基本数据类型判断是没有问题的,但是遇到引用数据类型(如:Array)是不起作用

console.log(typeof 2);    // number
console.log(typeof null); // object

`instanceof` 只能正确判断引用数据类型  而不能判断基本数据类型,其内部运行机制是判断在其原型链中能否找到该类型的原型

console.log([] instanceof Array);                    // true
console.log(function(){} instanceof Function);       // true
console.log({} instanceof Object);      
// true
constructor 似乎完全可以应对基本数据类型和引用数据类型 但如果声明了一个构造函数,并且把他的原型指向了 Array 的原型
,所以这种情况下,constructor 也显得力不从心

console.log((true).constructor === Boolean); // true
console.log(('str').constructor === String); // true
console.log(([]).constructor === Array); // true
console.log((function() {}).constructor === Function); // true
console.log(({}).constructor === Object); // true
console.log((2).constructor === Number); // true

Object.prototype.toString.call() 完美的解决方案,可以通过toString() 来获取每个对象的类型,

`Object.prototype.toString.call()` 使用 Object 对象的原型方法 toString 来判断数据类型:
​
var a = Object.prototype.toString;
 
console.log(a.call(2));  //-   [object Number]
console.log(a.call(true));  //-   [object Boolean]
console.log(a.call('str'));   //-   [object String]
console.log(a.call([]));   // -   [object Array]
console.log(a.call(function(){}));  //-   [object Function]
console.log(a.call({}));  //-   [object Object]
console.log(a.call(undefined));  //-   [object Undefined]
console.log(a.call(null));  //-   [object Null]
补充:基本数据类型赋值的时候  赋的是具体的值   引用数据类型传的是地址,一个变另一个跟着变

为什么typeof null 返回的是object

Js中的基本数据类型都存储在32位的二进制单元当中,低三位的数字用于表示该数据的类型。低三位数字和表示类型的对应关系如下:

000: object - 当前存储的数据指向一个对象。 1: int - 当前存储的数据是一个 31 位的有符号整数。 010:double - 当前存储的数据指向一个双精度的浮点数。 100: string - 当前存储的数据指向一个字符串。 110: boolean - 当前存储的数据是布尔值。

typeof的实现就是通过判断低三位的数字来判断值类型的。当传入一个null的时候,因为null存储时32位的数字表示都是0,所以低三位也是0,typeof就将其认定为object类型的了。

数据类型相比较objected .is ==和===

=== 属于严格判断,直接判断两者类型是否相同,如果两边的类型不一致时,不会做强制类型准换,不同则返回false如果相同再比较大小,不会进行任何隐式转换对于引用类型来说,比较的都是引用内存地址,所以===这种方式的比较,除非两者存储的内存地址相同才相等,反之false

== 二等表示值相等。判断操作符两边对象或值是否相等类型可以不同,如果两边的类型不一致,则会进行强制类型转化后再进行比较,使用Number()转换成Number类型在进行判断。例外规则,null==undefined,null/undefined进行运算时不进行隐式类型转换。通常把值转为Boolean值,进行条件判断。Boolean(null)===Boolean(undefined)>false===false 结果为true

Object.is()在===基础上特别处理了NaN,-0,+0,保证-0与+0不相等,但NaN与NaN相等

==操作符的强制类型转换规则
​
字符串和数字之间的相等比较,将字符串转换为数字之后再进行比较。
其他类型和布尔类型之间的相等比较,先将布尔值转换为数字后,再应用其他规则进行比较。
null 和 undefined 之间的相等比较,结果为真。其他值和它们进行比较都返回假值。
对象和非对象之间的相等比较,对象先调用 ToPrimitive 抽象操作后,再进行比较。
如果一个操作值为 NaN ,则相等比较返回 false( NaN 本身也不等于 NaN )。
如果两个操作值都是对象,则比较它们是不是指向同一个对象。如果两个操作数都指向同一个对象,则相等操作符返回true,否则,返回 false。
​
    '1' == 1 // true
    '1' === 1 // false
    NaN == NaN //false
    +0 == -0 //true
    +0 === -0 // true
    Object.is(+0,-0) //false
    Object.is(NaN,NaN) //true

什么是防抖和节流?

  • 防抖: 高频事件后n秒内只会执行一次,如果n秒内再次被触发,则重新计算时间。(关注于最后一次的事件触发)
    使用场景1. 搜索联想,用户不断输入值,用防抖来节约请求资源;2.不断的调整浏览器窗口大小会不断的触发这个事件,用防抖来让其只触发一次

  • 节流: n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效(在规定的时间里只执行一次)
    使用场景:节流:鼠标不断点击触发,滚动加载更多、表单重复提交

原型,原型链 ? 有什么特点?

原型关系:

  • 每个 class都有显示原型 prototype
  • 每个实例都有隐式原型 _ proto_
  • 实例的_ proto_指向对应 class 的 prototype

原型:  在 JS 中,每当定义一个对象(函数也是对象)时,对象中都会包含一些预定义的属性。其中每个函数对象都有一个prototype 属性,这个属性指向函数的原型对象。“prototype”作为函数对象的内部属性,是不能被直接访问的。所以为了方便查看一个对 象的原型,Firefox 和 Chrome 内核的 JavaScript 引擎中提供了”proto“这个非标准的访问器 原型的主要作用就是为了实现继承与扩展对象

原型链:当访问一个对象的某个属性时,会先在这个对象本身属性上查找,如果没有找到,则会去 它的__proto__隐式原型上查找,即它的构造函数的 prototype,如果还没有找到就会再在构 造函数的 prototype 的__proto__中查找,这样一层一层向上查找就会形成一个链式结构,我 们称为原型链

特点:  JavaScript对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变。

A类的prototype指向另一个类B,B的prototype又可以指向C,这种结构叫做原型链。

作用域和作用域链

创建函数的时候,已经声明了当前函数的作用域==>当前创建函数所处的上下文。如果是在全局下创建的函数就是[[scope]]:EC(G),函数执行的时候,形成一个全新的私有上下文EC(FN),供字符串代码执行(进栈执行)

定义:简单来说作用域就是变量与函数的可访问范围,由当前环境与上层环境的一系列变量对象组成
1.全局作用域:代码在程序的任何地方都能被访问,window 对象的内置属性都拥有全局作用域。
2.函数作用域:在固定的代码片段才能被访问

作用:作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。

作用域链参考链接一般情况下,变量到 创建该变量 的函数的作用域中取值。但是如果在当前作用域中没有查到,就会向上级作用域去查,直到查到全局作用域,这么一个查找过程形成的链条就叫做作用域链。

垃圾回收机制和内存机制

垃圾回收

浏览器的js具有自动垃圾回收机制,垃圾回收机制也就是自动内存管理机制,垃圾收集器会定期的找出那些不在继续使用的变量,然后释放内存。但是这个过程不是实时的,因为GC开销比较大并且时停止响应其他操作,所以垃圾回收器会按照固定的时间间隔周期性的执行。

内存泄露

如果 那些不再使用的变量,它们所占用的内存 不去清除的话就会造成内存泄漏

内存泄露其实就是我们的程序中已经动态分配的堆内存,由于某些原因没有得到释放,造成系统内存的浪费导致程序运行速度减慢甚至系统崩溃等严重后果。

比如说:

1、闭包:在闭包中引入闭包外部的变量时,当闭包结束时此对象无法被垃圾回收(GC)。

2、DOM:当原有的DOM被移除时,子结点引用没有被移除则无法回收

3、Times计时器泄露

引用数据类型 object

object的方法

Object.is() 是一种判断两个值是否相同的方法。
语法:Object.is(value1, value2);
参数:value1:要比较的第一个值。value2:要比较的第二个值。
返回值:一个布尔表达式,指示两个参数是否具有相同的值。
 
Object.assign() 方法用于将所有可枚举的自身属性从一个或多个源对象复制到目标对象。
语法:Object.assign(target, ...sources)
参数:target:目标对象——应用源属性的对象,修改后返回。sources:源对象——包含你要应用的属性的对象。
返回值:修改后的目标对象。
 
 
Object.entries() ES8的Object.entries是把对象转成键值对数组, [key, value] 对的数组。
语法:Object.entries(obj)
参数:obj:要返回其自己的可枚举字符串键属性 [key, value] 对的对象。返回值:给定对象自己的可枚举字符串键属性 [key, value] 对的数组。
Object.fromEntries则相反,是把键值对数组转为对象
 
Object.values() 方法返回给定对象自己的可枚举属性值的数组,其顺序与 for...in 循环提供的顺序相同。
语法:Object.values(obj)
参数:obj:要返回其可枚举自身属性值的对象。返回值:包含给定对象自己的可枚举属性值的数组。
 
Object.prototype.hasOwnProperty()
hasOwnProperty() 方法返回一个布尔值,指示对象是否具有指定的属性作为它自己的属性。
如果指定的属性是对象的直接属性,则该方法返回 true — 即使值为 null 或未定义。如果该属性是继承的或根本没有声明,则返回 false。
语法:hasOwnProperty(prop)
参数:prop:要测试的属性的字符串名称或符号。
返回值:如果对象将指定的属性作为自己的属性,则返回true;否则为false。
 
Object.keys()
Object.keys() 方法用于返回给定对象自己的可枚举属性名称的数组,以与普通循环相同的顺序迭代。
语法:Object.keys(obj)
参数:obj:要返回可枚举自身属性的对象。
返回值:表示给定对象的所有可枚举属性的字符串数组。
 
Object.prototype.toString()
toString() 方法返回一个表示对象的字符串。当对象将被表示为文本值或以期望字符串的方式引用对象时,将自动调用此方法 id。默认情况下,toString() 方法由从 Object 继承的每个对象继承。
语法:toString()
返回值:表示对象的字符串。
 
Object.freeze()
Object.freeze() 方法冻结一个对象,这意味着它不能再被更改。冻结对象可防止向其添加新属性,防止删除现有属性,防止更改现有属性的可枚举性、可配置性或可写性,并防止更改现有属性的值。它还可以防止其原型被更改。
语法:Object.freeze(obj)
参数:obj:要冻结的对象。返回值:传递给函数的对象。
 
Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。 (请打开浏览器控制台以查看运行结果。)
语法:const me = Object.create(person);
参数:
proto:新创建对象的原型对象。
propertiesObject
可选。需要传入一个对象,该对象的属性类型参照Object.defineProperties()的第二个参数。如果该参数被指定且不为 undefined,该传入对象的自有可枚举属性(即其自身定义的属性,而不是其原型链上的枚举属性)将为新创建的对象添加指定的属性值和对应的属性描述符。
返回值
一个新对象,带着指定的原型对象和属性。

对象和面向对象

对象:属性和方法的集合叫做对象(万物皆对象)。

面向对象:首先就是找对象,如果该对象不具备所需要的方法或属性,那就给它添加。 面向对象是一种编程思维的改变。通过原型的方式来实现面向对象编程。

创建对象的方式(4种):new Object、字面量、构造函数、原型。

JS 中 this 的五种情况

  • 作为普通函数执行时,this指向window

  • 当函数作为对象的方法被调用时,this就会指向该对象

  • 构造器调用,this指向返回的这个对象

  • 箭头函数 箭头函数的this绑定看的是this所在函数定义在哪个对象下,就绑定哪个对象。如果有嵌套的情况,则this绑定到最近的一层对象上。

  • 基于Function.prototype上的 apply 、 call 和 bind 调用模式,这三个方法都可以显示的指定调用函数的 this 指向。apply接收参数的是数组,call接受参数列表,`` bind方法通过传入一个对象,返回一个 this 绑定了传入对象的新函数。这个函数的 this指向除了使用new `时会被改变,其他情况下都不会改变。若为空默认是指向全局对象window。

事件委托

事件委托,又名事件代理。事件委托就是利用事件冒泡,就是把子元素的事件都绑定到父元素上。如果子元素阻止了事件冒泡,那么委托也就没法实现了

阻止事件冒泡

event.stopPropagation() .stop修饰符

addEventListener(‘click',函数名,true/false) 默认值为false(即 使用事件冒泡)true 事件捕获

好处:提高性能,减少了事件绑定,从而减少内存占用

举例:最经典的就是 ul 和 li 标签的事件监听,比如我们在添加事件时候,采用

事件委托机制,不会在 li 标签上直接添加,而是在 ul 父元素上添加。

好处:比较合适动态元素的绑定,新添加的子元素也会有监听函数,也可以有事

件触发机制

应用场景 在vue中事件委托:

我们经常遇到vue中v-for一个列表,列表的每一项都绑定了@click处理事件。我们都知道绑定这么多监听,从性能方面来说是不太好的。那我们我们可以通过把每个item的click事件委托给父元素的形式来实现

事件冒泡

什么是事件冒泡

事件冒泡就是指父元素和子元素有相同的事件,当触发子元素事件时,会向上冒泡,同时也会触发父元素事件

事件冒泡的三个阶段

1、捕获阶段:从window对象传导到目标节点(上层传到底层)称为“捕获阶段”,捕获阶段不会响应任何事件;

2、目标阶段:在目标节点上触发称为“目标阶段”;

3、冒泡阶段:从目标节点传导回window对象(从底层传回上层),称为“冒泡阶段”;

事件冒泡的原理

事件委托原理就是利用事件冒泡机制把里层所需要响应的事件绑定到外层 事件冒泡传递 事件从dom 树的底层 层层往上传递,直至传递到dom的根节点。

说说你对闭包的理解?闭包使用场景

闭包就是函数中包含另一个函数,可以让你在函数外部读取到内部的变量(就是在函数内部再定义一个函数),让这些变量的值始终保持在内存中,可以达到延长变量生命周期的效果,过多使用会导致内存泄漏的问题

闭包用途:

  • 能够访问函数定义时所在的词法作用域(阻止其被回收)
  • 私有化变量
  • 模拟块级作用域
  • 创建模块 闭包缺点:

会导致函数的变量一直保存在内存中,过多的闭包可能会导致内存泄漏

闭包原理

函数执行分成两个阶段(预编译阶段和执行阶段)。

1.在预编译阶段,如果发现内部函数使用了外部函数的变量,则会在内存中创建一个“闭包”对象并保存对应变量值, 如果已存在“闭包”,则只需要增加对应属性值即可。

2.执行完后,函数执行上下文会被销毁,函数对“闭包”对象的引用也会被销毁,但其内部函数还持用该“闭包”的引用, 所以内部函数可以继续使用“外部函数”中的变量

利用了函数作用域链的特性,一个函数内部定义的函数会将包含外部函数的活动对象添加到它的作用域链中,函数执行完毕,其执行作用域链销毁, 内部函数的作用域链仍然在引用这个活动对象,所以其活动对象不会被销毁,直到内部函数被烧毁后才被销毁

内存泄露

如果 那些不再使用的变量,它们所占用的内存 不去清除的话就会造成内存泄漏

比如说:

1、闭包:在闭包中引入闭包外部的变量时,当闭包结束时此对象无法被垃圾回收(GC)。

2、DOM:当原有的DOM被移除时,子结点引用没有被移除则无法回收

3、Times计时器泄露

什么是高阶函数?

高阶函数只是,将函数作为参数 , 函数的返回值返回值是函数

function higherOrderFunction(param,callback){ return callback(param); }

for...in 迭代和 for...of 有什么区别

1、 推荐在循环对象属性的时候,使用 for...in,在遍历数组的时候的时候使用for...of。

2、 for in遍历的是数组的索引,而for of遍历的是数组元素值

3、for...of 不能循环普通的对象,需要通过和 Object.keys()搭配使用

4、for...in 便利顺序以数字为先 无法便利 symbol 属性 可以便利到公有中可枚举的

5、从遍历对象的角度来说,for···in会遍历出来的为对象的key,但for···of会直接报错。

forEach和map的区别

1、相同点

(1)都是循环遍历数组中的每一项。

(2)每次执行匿名函数都支持三个参数,参数分别为item(当前每一项),index(索引值),arr(原数组)。

(3)匿名函数中的this都是指向window。

(4)只能遍历数组。

2、不同点

(1)map会返回一个新的数组,不会改变原来的数组,forEach不会返回新数组,允许对原数组进行修改。

(2)forEach()允许callback更改原始数组的元素。map()返回新的数组。

//forEach 使⽤场景:并不打算改变数据的时候,
//⽽只是想⽤数据做⼀些事情 ,⽐如存⼊数据库或则打印出来。
var arr1 = [0,2,4,6,8];
var newArr1 = arr1.forEach(function(item,index,arr1){
  console.log('1',this); 
  console.log('2',arr1);
   arr1[index] = item/2; 
},this);
console.log('3',arr1);
console.log('4',newArr1);

//map  使⽤场景:map()适⽤于你要改变数据值的时候。不仅仅在于它更快,
//⽽且返回⼀个新的数组。

var arr = [0,2,4,6,8];
 var newArr = arr.map(function(item,index,arr){
            console.log('5',this);
            console.log('6',arr);
            return item/2;
 },this);
 console.log('7',newArr)
//执行顺序
// 1 undefined
// 2 0,2,4,6,8
// 1 undefined
// 2 0,2,4,6,8
// 1 undefined
// 2 0,1,4,6,8
// 1 undefined
// 2 0,1,2,6,8
// 1 undefined
// 2 0,1,2,3,8
// 3 0,1,2,3,4
// 4 undefined
// 5 undefined
// 6 0,2,4,6,8
// 5 undefined
// 6 0,2,4,6,8
// 5 undefined
// 6 0,2,4,6,8
// 5 undefined
// 6 0,2,4,6,8
// 5 undefined
// 6 0,2,4,6,8
// 7 0,1,2,3,4

构造函数(new的原理)

new实际上是在堆内存中开辟一个空间。
    ①创建一个空对象,构造函数中的this指向这个空对象;
    ②这个新对象被执行[ [ 原型 ] ]连接;
    ③执行构造函数方法,属性和方法被添加到this引用的对象中;
    ④如果构造函数中没有返回其它对象,那么返回this,即创建的这个的新对象,
    否则,返回构造函数中返回的对象。
​
function _new(){
    let target = {};   //创建的新对象
    let [constructor,...args] = [...arguments];
       //执行[[原型]]连接,target是constructor的实例
    target.__proto__ = constructor.prototype;
        //执行构造函数,将属性或方法添加到创建的空对象上
    let result = constructor.prototype;
    if(result && (typeof (result) == "object" || typeof (result) == "function")){
           //如果构造函数执行的结构返回的是一个对象,那么返回这个对象
        return result;
    }
       //如果构造函数返回的不是一个对象,返回创建的对象
    return target;
}
​
​
自己理解的newnew实际上是在堆内存中开辟一个新的空间。首先创建一个空对象obj,然后呢,
    把这个空对象的原型(__proto__)和构造函数的原型对象(constructor.prototype)
    连接(说白了就是等于);
    然后执行函数中的代码,就是为这个新对象添加属性和方法。最后进行判断其返回值,
    如果构造函数返回的是一个对象,
    那就返回这个对象,如果不是,那就返回我们创建的对象。

new运算符的实现机制

  1. 首先创建了一个新的空对象
  2. 设置原型,将对象的原型设置为函数的prototype对象。
  3. 让函数的this指向这个对象,执行构造函数的代码(为这个新对象添加属性)
  4. 判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。

setTimeout、Promise、Async/Await 的区别

  • setTimeout

    settimeout的回调函数放到宏任务队列里,等到执行栈清空以后执行。

  • Promise

    Promise本身是同步的立即执行函数, 当在executor中执行resolve或者reject的时候, 此时是异步操作, 会先执行then/catch等,当主栈完成后,才会去调用resolve/reject中存放的方法执行。

    console.log('script start')
    let promise1 = new Promise(function (resolve) {
        console.log('promise1')
        resolve()
        console.log('promise1 end')
    }).then(function () {
        console.log('promise2')
    })
    setTimeout(function(){
        console.log('settimeout')
    })
    console.log('script end')
    // 输出顺序: script start->promise1->promise1 end->script end->promise2->settimeout
    复制代码
    
  • async/await

    async 函数返回一个 Promise 对象,当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再执行函数体内后面的语句。可以理解为,是让出了线程,跳出了 async 函数体。

    async function async1(){
       console.log('async1 start');
        await async2();
        console.log('async1 end')
    }
    async function async2(){
        console.log('async2')
    }
    
    console.log('script start');
    async1();
    console.log('script end')
    
    // 输出顺序:script start->async1 start->async2->script end->async1 end
    

什么是深拷贝,浅拷贝,浅拷贝 赋值的区别,如何实现

深拷贝和浅拷贝是针对复杂数据类型来说的,浅拷贝只拷贝一层,而深拷贝是层层拷贝。

1.浅拷贝:

将原对象或原数组的引用直接赋给新对象,新数组,新对象只是对原对象的一个引用,
而不复制对象本身,新旧对象还是共享同一块内存

如果属性是一个基本数据类型,拷贝就是基本类型的值,如果属性是引用类型,
拷贝的就是内存地址,

2.深拷贝:

创建一个新的对象和数组,将原对象的各项属性的“值”(数组的所有元素)拷贝过来,
是“值”而不是“引用”

深拷贝就是把一个对象,从内存中完整的拷贝出来,从堆内存中开辟了新区域,
用来存新对象,并且修改新对象不会影响原对象

3、赋值:

当我们把一个对象赋值给一个新的变量时,赋的是该对象在栈中的内存地址,
而不是堆中的数据。也就是两个对象
 
  
浅拷贝的实现方式:
   1object.assign()  (“只有一层的情况下,
   object.assign()是浅拷贝、展开运算符是深拷贝。多层情况下,两中没有区别,都是浅拷贝。 ”)
   2、lodash 里面的 _.clone 
   3、...扩展运算符
   4Array.prototype.concat 
   5Array.prototype.clice
​
    深拷贝的实现方式
    1JSON.parse(JSON.stringify())
    2、递归操作
    3、cloneDeep
    4Jquery.extend()   

JSON.parse(JSON.stringify())# 深拷贝的问题

  1. 如果obj里面有时间对象,则JSON.stringify后再JSON.parse的结果,时间将只是字符串的形式,而不是对象的形式
var t = {time: new Date()};
var t2 = JSON.parse(JSON.stringify(t));
console.log(t,t2)

image.png 2. 如果obj里有RegExp(正则表达式的缩写)、Error对象,则序列化的结果将只得到空对象;

var r = {a: /^name.+/, b:2, c: new Error('错误'), d: new RegExp('\w+')};
var r2 = JSON.parse(JSON.stringify(r));
console.log(r, r2)

image.png

  1. 如果obj里有函数,undefined,则序列化的结果会把函数或 undefined丢失;
var q = {fn: function(){}, u: undefined, i: null, g: ''};
var q2 = JSON.parse(JSON.stringify(q));
console.log(q, q2)

image.png

  1. 如果obj里有NaN、Infinity和-Infinity,则序列化的结果会变成null
var w = {a: NaN, b: Infinity, c: -Infinity, d: 3};
var w2 = JSON.parse(JSON.stringify(w));
console.log(w, w2)

image.png

  1. JSON.stringify()只能序列化对象的可枚举的自有属性,例如 如果obj中的对象是有构造函数生成的, 则使用JSON.parse(JSON.stringify(obj))深拷贝后,会丢弃对象的constructor;
function Person(name){this.name=name};
Person.prototype.age= 14;
var p = new Person('zs');
var o = {a: p};
var o2 = JSON.parse(JSON.stringify(o));
console.log(o ,o2)

image.png

  1. 如果对象中存在循环引用的情况也无法正确实现深拷贝;
var a = {};a.b=a;

image.png

通俗点说,JSON.parse(JSON.stringfy(X)),其中X只能是Number, String, Boolean, Array, 扁平对象,即那些能够被 JSON 直接表示的数据结构

js实现继承的几种方式

blog.csdn.net/leilei7407/…

  • 原型链继承
  • 借用构造函数来继承
  • 构造函数+原型链 组合继承
  • 寄生组合式继承
  • 原型式继承
  • 寄生式继承
  • class+extends继承

服务端渲染

解释:服务端渲染的模式下,当用户第一次请求页面时,由服务器把需要的组件或页面渲染成 HTML 字符串,然后把它返回给客户端。客户端拿到手的,是可以直接渲染然后呈现给用户的 HTML 内容,不需要为了生成 DOM 内容自己再去跑一遍 JS 代码。使用服务端渲染的网站,可以说是“所见即所得”,页面上呈现的内容,我们在 html 源文件里也能找到。有了服务端渲染,当请求用户页面时,返回的body里已经有了首屏的html结构,之后结合css显示出来。

优点: ①首屏渲染快(关键性问题):相比于加载单页应用,我只需要加载当前页面的内容,而不需要像 React 或者 Vue 一样加载全部的 js 文件; ②SEO(搜索引擎)优化:不同爬虫工作原理类似,只会爬取源码,不会执行网站的任何脚本 ③可以生成缓存片段、节能;

缺点:用户体验较差,不容易维护、通常前端改了部分html或者css,后端也需要改;

使用场景:vue全家桶或者react全家桶,都是推荐通过服务端渲染来实现路由的。

同步和异步的区别?各举一个Js中同步和异步的案例?

同步:上一件事情没有完成,继续处理上一件事情,只有上一件事情完成了,才会做下一件事情

异步: 规划要做一件事情,如果是异步事情,不是当前立马去执行这件事情,需要等一定的时间,这样的话,我们不会等着他执行,而是继续执行下面的操作

对于写程序,同步往往会阻塞,没有数据过来,我就等着,异步则不会阻塞,没数据来我干别的事,有数据来去处理这些数据。

同步案例:for循环语句,alert(),console.log()等 js大部分都是同步编程

异步案例:所有定时器,ajax异步请求,所有的事件绑定都是异步;

举例子

同步,就是实时处理(如打电话),比如服务器一接收客户端请求,马上响应,这样客户端可以在最短的时间内得到结果,但是如果多个客户端,或者一个客户端发出的请求很频繁,服务器无法同步处理,就会造成涌塞。

同步如打电话,通信双方不能断(我们是同时进行,同步),你一句我一句,这样的好处是,对方想表达的信息我马上能收到,但是,我在打着电话,我无法做别的事情。

异步,就是分时处理(如收发短信),服务器接收到客户端请求后并不是立即处理,而是等待服务器比较空闲的时候加以处理,可以避免涌塞。

BOM浏览器对象模型

js操作BOM

浏览器对象模型(BOM :Browser Object Model)是JavaScript的组成之一,它提供了独立于内容与浏览器窗口进行交互的对象,使用浏览器对象模型可以实现与HTML的交互。它的作用是将相关的元素组织包装起来,提供给程序设计人员使用,从而降低开发人员的劳动量,提高设计Web页面的能力。

window : alert() , prompt() , confirm() , setInterval() , clearInterval() , setTimeout() , clearTimeout() ;

history : go(参数) , back() , foward() ;

location : herf属性.

1、window.location.href = '你所要跳转到的页面';

2、window.open('你所要跳转到的页面’);

3、window.history.back(-1):返回上一页

4、window.history.go(-1/1):返回上一页或下一页五、

5、history.go("baidu.com");

说出5个以上Math对象中的成员。

Math.PI 圆周率

Math.floor() 向下取整

Math.ceil() 向上取整

Math.round() 四舍五入版 就近取整

Math.abs() 绝对值

Math.max()/Math.min() 求最大和最小值

Math.random() 获取范围在[0,1)内的随机值

setTimeout与setInterval区别与机制

setTimeout()和setInterval()经常被用来处理延时和定时任务。

setTimeout() 方法用于在指定的毫秒数后调用函数或计算表达式

setInterval()则可以在每隔指定的毫秒数循环调用函数或表达式,直到clearInterval把它清除。

机制:

因为js是单线程的。浏览器遇到setTimeout 和 setInterval会先执行完当前的代码块,在此之前会把定时器推入浏览器的 待执行时间队列里面,等到浏览器执行完当前代码之后会看下事件队列里有没有任务,有的话才执行定时器里的代码

DOM文档对象模型

DOM是 document 用来表示文档中对象的标准模型,他是由节点和对象组成的结构集合。在浏览器解析HTML标签时,会构建一个DOM树结构。

操作方法


拿到指定节点
var id = document.getElementById("id");  //返回带有指定id的元素
var name = document.getElementByTagName("li"); //返回带有指定标签的元素
var class = document.getElementByClassName("class"); //返回带有包含执行类名的所有元素节点列表。`
 创建DOM节点
var node = document.createElement("div");
var attr = document.createAttribute("class");
var text = document.createTextNode("菜呀菜");`
 插入DOM节点
node.appendChild(text) //插入新的子节点
node.insertBefore(pre,child) //在node元素内child前加入新元素`
 删除DOM节点
node.removeChild(text) //从父元素删除子元素节点
 修改DOM节点
node.setAttribute("class","name") //修改设置属性节点
node.replaceChild(pre,child)  //父节点内新子节点替换旧子节点`
 常用DOM属性
node.innerHtml  //获取/替换元素内容
node.parentNode  //元素节点的父节点
node.parentElement  //元素节点的父元素节点(一般与Node节点相同)
node.firstChild  //属性的第一个节点
node.lastChild   //属性的最后一个节点
node.nextSibling //节点元素后的兄弟元素(包括回车,空格,换行)
node.nextElementSibling //节点元素后的兄弟元素节点
node.previousSibling //获取元素的上一个兄弟节点(元素,文本,注释)
node.previousElementSibling //获取元素的上一个兄弟节点(只包含元素节点)
node.childNodes  //元素节点的子节点(空格,换行默认为文本节点)
node.children    //返回当前元素的所有元素节点
node.nodeValue   //获取节点值
node.nodeName    //获取节点名字
node.attributes  //元素节点的属性节点
node.getAttribute("name")  //元素节点的某个属性节点
node.style.width = "200px"  //设置css样式`

常用的api

offset、client、scroll的用法?

offset系列 经常用于获得元素位置 offsetLeft offsetTop

client经常用于获取元素大小 clientWidth clientHeight

scroll 经常用于获取滚动距离 scrollTop scrollLeft

ES6

ES6 新增特性

新增了块级作用域(let,const)

提供了定义类的语法糖(class)

1.class是ES6提供的一个语法糖,本质是一个函数。
2.在class中声明方法,不需要function关键字。
3.class存在暂时型死区,在声明之前不能调用。
4.class默认有constructor、static方法。
5.class构造时必须使用new关键字。

新增了一种基本数据类型(Symbol)

新增了变量的解构赋值

函数参数允许设置默认值,引入了 rest 参数

新增了箭头函数

数组新增了一些 API,如 isArray / from / of 方法;

数组实例新增了entries(),keys() 和 values() 等方法

对象和数组新增了扩展运算符

ES6 新增了模块化(import/export)

ES6 新增了 Set 和 Map 数据结构

ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例

ES6 新增了生成器(Generator)和遍历器(Iterator)

箭头函数与普通函数有什么区别

  1. 箭头函数没有this,this是继承于当前的上下文,不能通过call,apply,bind去改变 this
  2. 箭头函数没有自己的 arguments 对象,但是可以访问外围函数的 arguments 对象
  3. 不能通过 new 关键字调用(不能作为构造函数),同样也没有 new.target 和原型

1、 箭头函数是匿名函数不能作为构造函数,不能使用new

2、 箭头函数不绑定arguments,取而代之用rest参数…解决,

3、 this指向不同,箭头函数的this在定义的时候继承自外层第一个普通函数的this

4、 箭头函数通过call()或apply()调用一个函数,只传入了一个参数,对this并没有影响.

5、 箭头函数没有prototype(原型),所以箭头函数本身没有this

6、 箭头函数不能当做Generator函数,不能使用yield关键字、

7、 写法不同,箭头函数把function省略掉了 ()=> 也可以吧return 省略调 写法更简洁

8、箭头函数不能通过call()、apply()、bind()方法直接修改它的this指向。

arguments 的对象是什么?

arguments 当我们不知道有多少个参数传进来的时候就用 arguments 来接收,是一个类似于数组的对象,他有length属性,可以arguments[ i ]来访问对象中的元素, 但是它不能用数组的一些方法。 例如push、pop、slice等。arguments虽然不是一个数组,但是它可以转成一个真正的数组。

取之可以用 展开运算符来 数组和类数组类数组: ①拥有length属性,其它属性(索引)为非负整数;箭头函数里没有arguments ②不具有数组所具有的方法; ③类数组是一个普通对象,而真实的数组是Array类型。

常见的类数组:arguments,document.querySelectorAll得到的列表,jQuery对象($("div"));

使用场景:针对同一个方法被多处调用,但是参数数量不确定的情况下,可以更具arguments索引进行判断。

例如:

function func1(a, b, c) { 
    console.log(arguments[0]); // 1 
    console.log(arguments[1]); // 2 
    console.log(arguments[2]); // 3 
} 
func1(1, 2, 3);

require与import的区别和使用(CommonJS规范和es6规范)

1、import是ES6中的语法标准也是用来加载模块文件的,import函数可以读取并执行一个JavaScript文件,然后返回该模块的export命令指定输出的代码。export与export default均可用于导出常量、函数、文件、模块,export可以有多个,export default只能有一个。

2、require 定义模块:module变量代表当前模块,它的exports属性是对外的接口。通过exports可以将模块从模块中导出,其他文件加载该模块实际上就是读取module.exports变量,他们可以是变量、函数、对象等。在node中如果用exports进行导出的话系统会系统帮您转成module.exports的,只是导出需要定义导出名。

require与import的区别

1,require是CommonJS规范的模块化语法,import是ECMAScript 6规范的模块化语法;

2,require是运行时加载,import是编译时加载;

3,require可以写在代码的任意位置,import只能写在文件的最顶端且不可在条件语句或函数作用域中使用;

4,require通过module.exports导出的值就不能再变化,import通过export导出的值可以改变;

5;require通过module.exports导出的是exports对象,import通过export导出是指定输出的代码;

6,require运行时才引入模块的属性所以性能相对较低,import编译时引入模块的属性所所以性能稍高。

es6处理异步的方法

1.Promise

Promise 是异步编程的⼀种解决⽅案,⽐传统的解决⽅案——回调函数和事件——更合理和更强⼤。 ⼀个 Promise 必然处于以下⼏种状态之⼀:

待定(pending): 初始状态,既没有被兑现,也没有被拒绝。

已兑现(fulfilled): 意味着操作成功完成。

已拒绝(rejected): 意味着操作失败

1. 构造函数

创建⼀个新的 Promise 对象。该构造函数主要⽤于包装还没有添加 promise ⽀持的函数。 Promise 的实现会⽴即执⾏ executor ,并传⼊ resolve 和 reject 函数 。 resolve 函数的作⽤是将 Promise 对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调⽤,并将异步操作的结果,作为参数传递出去; reject 函数的作⽤是,将 Promise 对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected)

let promise = new Promise(excutor)

2. 实例⽅法

Promise.prototype.then() 为 Promise 实例添加状态改变时的回调函数,该函数的第⼀个参数是 resolved 状态的回 调函数,第⼆个参数是 rejected 状态的回调函数,它们都是可选的。

Promise.prototype.catch() 为 Promise 实例指定发⽣错误时的回调函数。是then(null,callback)的别名。

Promise.prototype.finally() 该⽅法⽤于指定不管 Promise 对象最后状态如何,都会执⾏的操作

3.静态方法

iteratiorable表示可迭代的对象  常见数组或set...
Promise.all(iteratorable)
Promise.all([p1,p2])(p1,p2是promise实例)
当p1 p2 ...全部执行完毕并且状态resolved的时候,promise的then才会被调用,该then的回调函数的参数是p1,p2...的运行结果 ,如果报错是哪个先报错就返回哪个,只返回一个
 这个新的promise对象在触发成功状态以后,会把⼀个包含iterable⾥所有promise返回值的数组作为成功回调的返回值,顺序跟iterable的顺序保持⼀致;如果这个新的promise对象触发了失败状态,它会把iterable⾥第⼀个触发失败的promise对象的错误信息作为它的失败错误信息
 案例: 当查询完所有的班级,渠道后再调用查询学生的接口,查询学生的时候默认查询第一个班级
     Promise.all([p1,p2]).then((result)=>{
        let defaultClass=result[0].data.data[0]
        loadStudent();
    })
    Promise.race(iteratorable)
        返回率先改变状态的promise结果
    Promise.allSettled(iteratorable)
        返回值为promise 与all不同,当所有的承诺对象状态已经确认的时候,会执行then then的参数为promise对象的结果
    Promise.resolve()
        直接返回一个承诺对象,状态为成功
    Promise.reject()
        直接返回一个承诺对象,状态为失败

2. 迭代器

当需要对⼀个对象进⾏迭代时(⽐如开始⽤于⼀个 for..of 循环中),它的 Symbol.iterator ⽅法都会在不传参情况下被调⽤,返回的迭代器⽤于获取要迭代的值。 ⼀些内置类型 拥有默认的迭代器⾏为,如下:

Array.prototypeSymbol.iterator

TypedArray.prototypeSymbol.iterator

String.prototypeSymbol.iterator

Map.prototypeSymbol.iterator

Set.prototypeSymbol.iterator

ES6可迭代的对象 数组 字符串 set map 因为:他们的实例对象可以直接访问Symbol.iterator ,构造函数实现了Symbol.iterator接口

Iterator 的遍历过程如下:

  1. 创建⼀个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是⼀ 个指针对象。

  2. 第⼀次调⽤指针对象的 next ⽅法,可以将指针指向数据结构的第⼀个成员。

  3. 第⼆次调⽤指针对象的 next ⽅法,指针就指向数据结构的第⼆个成员。

  4. 不断调⽤指针对象的 next ⽅法,直到它指向数据结构的结束位置。 每⼀次调⽤ next ⽅法,都会返回数据结构的当前成员的信息。具体来说,就是返回⼀个 包含 value 和 done 两个属性的对象。其中, value 属性是当前成员的值, done 属性是 ⼀个布尔值,表示遍历是否结束。

调⽤ Iterator 接⼝的场合

 扩展运算符

yield *

for...of

Array.from()

Map(), Set(), WeakMap(), WeakSet()(⽐如new Map([['a',1],['b',2]]))

Promise.all()

Promise.race() 

3.generator函数

形式上来看,Generator 函数是⼀个普通函数,但是有两个特征。⼀是, function 关键 字与函数名之间有⼀个星号;⼆是,函数体内部使⽤ yield 表达式,定义不同的内部状态

function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
返回值是个迭代器,使得异步操作同步化
1.迭代器函数本质上就是一个generator函数
调⽤ Generator 函数,返回⼀个遍历器对象,代表 Generator 函数的内部指针。以后,每 次调⽤遍历器对象的 next ⽅法,就会返回⼀个有着 value 和 done 两个属性的对象。 value 属性表示当前的内部状态的值,是 yield 表达式后⾯那个表达式的值; done 属性是 ⼀个布尔值,表示是否遍历结束。
2.next参数

    next参数可以作为上一个yield表达式的返回值
    let iterator = foo()
    iterator.next(1) 

    function* gen(x){
    let y = yield x+1;
    return y
    }
    let iterator = gen(5);
    iterator.next() // 6
    iterator.next(8) //8

3.generator函数实现⾃定义迭代器

创建⼀个类数组对象的类,⽤于⽣成类数组对象,并且实现类数组对象的迭代器函数。

class ArrayLike{
constructor(args){
for(let i=0;i<args.length;i++){
let val = args[i]
this[i] = val;
}
Object.defineProperty(this,'length',{
configurable:true,
enumerable:false,
value:args.length
})
}
// 请你实现迭代函数
* [Symbol.iterator](){
for(let k in this){
let v = this[k]
let index = parseInt(k)
yield {value:v,done:index<this.length?false:true}
}
}
}
let arr = new ArrayLike(['terry','larry','tom']);
console.log(arr);
for(let a of arr){
console.log(a);
}

4. 异步任务封装

axios的get⽅法可以返回⼀个promise对象,通过yield表达式可以阻塞ajax代码的执⾏,如 下代码中,我们需要获取yield表达式的结果,这时候就需要为next⽅法传递参数。

let axios = require('axios');
function* foo(){
let url = 'http://121.199.29.84:8001/index/carousel/findAll'
let resp = yield axios.get(url)
console.log(resp.data);
}
// 这里的执行是比较麻烦的
let iterator = foo();
iterator.next().value.then(resp => {
iterator.next(resp);
});

5. co模块

co模块是著名程序员 TJ Holowaychuk 于 2013 年 6 ⽉发布的⼀个⼩⼯具,⽤于 Generator 函数的⾃动执⾏。该函数返回⼀个 Promise 对象,因此可以⽤ then ⽅法添加回调函数。

let axios = require('axios');
let co = require('co')
function* foo(){
let url = 'http://121.199.29.84:8001/index/carousel/findAll'
let resp = yield axios.get(url)
console.log(resp.data);
}
co(foo)

4. Async函数

异步函数

async 函数就是 Generator 函数的语法糖。使得异步操作变得更加⽅便。 async 函数就 是将 Generator 函数的星号( * )替换成 async ,将 yield 替换成 await ,仅此⽽已。 异步函数的运⾏结果为Promise对象,异步函数内部的返回值会作为 then ⽅法回调函数 的参数。

let axios = require('axios');
async function foo(){
let url = 'http://121.199.29.84:8001/index/carousel/findAll'
let resp = await axios.get(url)
console.log(resp.data);
}
foo();

await 命令后⾯是⼀个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就 直接返回对应的值。

async/await(ES7)

原理

image.png

// 使用async/await获取成功的结果

// 定义一个异步函数,3秒后才能获取到值(类似操作数据库)
function getSomeThing(){
    return new Promise((resolve,reject)=>{
        setTimeout(()=>{
            resolve('获取成功')
        },3000)
    })
}

async function test(){
    let a = await getSomeThing();
    console.log(a)
}
test(); // 3秒后输出:获取成功

 
  async function testSync() {
   const datas= await new Promise((resolved, rejected) => {
       setTimeout(() => {
           resolved("async await test...");
         }, 1000);
    });
      console.log(datas);
 }
 
testSync();//async await test...
 
 
// async/await方式调用
function sayHi(name) {
  return new Promise((resolved, rejected) => {
    setTimeout(() => {
      resolved(name);
    }, 2000)
  })
}
 
//async function 声明用于定义一个返回 AsyncFunction 对象的异步函数。
//异步函数是指通过事件循环异步执行的函数,它会通过一个隐式的 Promise 返回其结果。
 
async function sayHi_async(name) {
  const sayHi_1 = await sayHi(name)
  console.log(`你好, ${sayHi_1}`)
  const sayHi_2 = await sayHi('李四')
  console.log(`你好, ${sayHi_2}`)
  const sayHi_3 = await sayHi('王二麻子')
  console.log(`你好, ${sayHi_3}`)
}
 
sayHi_async('张三')
// 你好, 张三
// 你好, 李四
// 你好, 王二麻子
 
// vue:
// import Api from "../../utils/http";
 
// async search() {
//       // api调用
//       let datas=await Api.get(
//         "/refer",
//         {
//           name: this.searchValue
//         },
//         null
//       );
//       this.$store.state.common.searchValue = this.searchValue;
//     },

async/await如何通过同步的方式实现异步

Async/Await就是一个自执行的generate函数。利用generate函数的特性把异步的代码写成“同步”的形式,第一个请求的返回值作为后面一个请求的参数,其中每一个参数都是一个promise对象.

promise(ES6)

我在项目中: 需求:执行第一步,将执行第一步的结果返回给第二步使用。在ajax中先拿到一个接口的返回数据,然后使用第一步返回的数据执行第 二步操作的接口调用,达到异步操作。

product.pconline.com.cn/itbk/softwa…

1、Promise 是异步编程的一种解决方案,主要用于异步计算,支持链式调用,可以解决回调地狱 的问题,自己身上有all、reject、resolve、race 等方法,原型上有then、catch等方法。

2、可以将异步操作队列化,按照期望的顺序执行,返回符合预期的结果,可以在对象之间传递和操作 promise,帮助我们处理队列

3、promise 有三个状态:pending[待定]初始状态,fulfilled[实现]操作成功,rejected[被否决]操作失败

4、Promise 对象状态改变:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了

5、如果不设置回调函数,Promise内部抛出的错误,不会反应到外部,但是写了then 和 catch ,会被then的第二个参数 或 catch所捕获

解构赋值

ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构赋值

常见的几种方式有

1.默认值

2.交换变量

3.将剩余数组赋给一个变量

结构数组和对象字符串区别

对象的解构与数组类似,但有所不同。数组的元素是按次序排列的,变量的取值由它的位置决定;

而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。字符串也是可以解构赋值的。字符串被转换成了一个类似数组的对象.

我在项目中:就是从目标对象或数组中提取自己想要的变量。最常用的场景是:element-ui,vant-ui按需引入,请求接口返回数据,提取想要数据。

———————————————— 版权声明:本文为CSDN博主「有两把刷子」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:blog.csdn.net/qq\_5475356…

var && let && const

三者区别

  • var let 是用来声明变量的,而const是声明常量的 var

1.var声明的变量存在变量提升,即变量可以在声明之前调用,值为undefined ​

2、一个变量可多次声明,后面的声明会覆盖前面的声明 ​

3、在函数中使用var声明变量的时候,该变量是局部的作用域只在函数内部,而如果在函数外部使用 var,该变量是全局的

  • let

1、不存在变量提升,let声明变量前,该变量不能使用。就是 let 声明存在暂时性死区

2、let命令所在的代码块内有效,在块级作用域内有效,作用域只是在花括号里面

3、let不允许在相同作用域中重复声明,注意是相同作用域,不同作用域有重复声明不会报错

  • const

1、const声明一个只读的常量,声明后,值就不能改变

2、let和const在同一作用域不允许重复声明变量const声明一个只读的常量。一旦声明,常量的值就不能改变,但对于对象和数据这种 引用类型,内存地址不能修改,可以修改里面的值。

3、let和const不存在变量提升,即它们所声明的变量一定要在声明后使用,否则报错

4、能用const的情况下尽量使用const,大多数情况使用let,避免使用var。 const > let > var const声明的好处,一让阅读代码的人知道该变量不可修改,二是防止在修改代码的过程中无意中修改了该变量导致报错,减少bug的产生

变量提升情况

  • var有变量提升,提升到当前作用域顶部并被初始化为undefined
  • 函数整个都会被提升,所以可以在函数定义之前直接调用并能正常执行
  • let、const有暂时性死区,所以提升了但是无效

变量提升概念

  • 只提升创建(也可以叫声明或注册),不提升赋值
  • 提升时会让变量在作用域的顶层注册 var a;console.log(a); //undefind

var在注册阶段初始化是一起的,会被赋值为undefined,所以变量提升,打印出的是undefined

function在注册阶段初始化、执行都一起了,所以函数不管写在哪里都可以直接调用

let在注册阶段和初始化是解耦的,只是创建了但是并未初始化,所以出现了暂时性死区,打印出的是'x is not defined'

const的生命周期与let一样,只不过没有赋值阶段

  1. var定义的变量,没有块的概念,可以跨块访问, 不能跨函数访问。
    let定义的变量,只能在块作用域里访问,不能跨块访问,也不能跨函数访问。
    const用来定义常量,使用时必须初始化(即必须赋值),只能在块作用域里访问,且不能修改。
  2. var可以先使用,后声明,因为存在变量提升;let必须先声明后使用。
  3. var是允许在相同作用域内重复声明同一个变量的,而let与const不允许这一现象。
  4. 在全局上下文中,基于let声明的全局变量和全局对象GO(window)没有任何关系 ;
    var声明的变量会和GO有映射关系;
  5. 会产生暂时性死区

暂时性死区是浏览器的bug:检测一个未被声明的变量类型时,不会报错,会返回undefined
如:console.log(typeof a) //undefined
而:console.log(typeof a)//未声明之前不能使用
let a

  1. let /const/function会把当前所在的大括号(除函数之外)作为一个全新的块级上下文,应用这个机制,在开发项目的时候,遇到循环事件绑定等类似的需求,无需再自己构建闭包来存储,只要基于let的块作用特征即可解决

set和map数据结构有哪些常用的属性和方法?

set数据的特点是数据是唯一的

const set1 = new Set()
 
增加元素 使用 add
set2.add(4)
 
是否含有某个元素 使用 has
console.log(set2.has(2)) 
 
查看长度 使用 size
console.log(set2.size) 
 
删除元素 使用 delete
set2.delete(2)
 
size: 返回Set实例的成员总数。
add(value):添加某个值,返回 Set 结构本身。
delete(value):删除某个值。
clear():清除所有成员,没有返回值。

Set的不重复性

传入的数组中有重复项,会自动去重
const set2 = new Set([1, 2, '123', 3, 3, '123'])
 
Set`的不重复性中,要注意`引用数据类型和NaN
两个对象都是不用的指针,所以没法去重
const set1 = new Set([1, {name: '孙志豪'}, 2, {name: '孙志豪'}])
 
如果是两个对象是同一指针,则能去重
const obj = {name: '我们一样'}
const set2 = new Set([1, obj, 2, obj])
 
NaN !== NaNNaN是自身不等于自身的,但是在Set中他还是会被去重
const set = new Set([1, NaN, 1, NaN])

map数据结构

Map对比object最大的好处就是,key不受类型限制

 
定义map
const map1 = new Map()
 
新增键值对 使用 set(key, value)
map1.set(true, 1)
 
判断map是否含有某个key 使用 has(key)
console.log(map1.has('哈哈')) 
 
获取map中某个key对应的value
console.log(map1.get(true)) 
 
删除map中某个键值对 使用 delete(key)
map1.delete('哈哈')
 
 
定义map,也可传入键值对数组集合
const map2 = new Map([[true, 1], [1, 2], ['哈哈', '嘻嘻嘻']])
console.log(map2) // Map(3) { true => 1, 1 => 2, '哈哈' => '嘻嘻嘻' }

proxy 的理解

Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。

Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。

———————————————— 版权声明:本文为CSDN博主「有两把刷子」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:blog.csdn.net/qq\_5475356…

Es6中新的数据类型symbol

symbol 是es6 加入的,是一个基本数据类型,它代表的是一个独一无二的值,SYMBOL 值是由 SYMBOL函数生成,也就是说现在我们定义对象的属性名字可以是原有的字符串 也可以是 symbol 类型的,symbol 可以保证不与其他属性名冲突,减少了bug的产生,

如果那 symbol 对比的话 就是会返回 false

symbol 他是一个原始类型的值就,不可以使用 new 关键字,symbol不是对象 没有迭代器的接口 不能去添加属性值,他是类似于字符串的一种类型

symbol 不能用来四则运算,否则会报错,只能用显示的方式转为字符串

symbol 参数里的 a 表示一种修饰符 对当前创建的 symbol 的一种修饰,作为区分 ,否则会混淆 ———————————————— 版权声明:本文为CSDN博主「有两把刷子」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:blog.csdn.net/qq\_5475356…

Object.assign

Object.assign可以实现对象的合并。它的语法是这样的: Object.assign(target, ...sources)

Object.assign会将source里面的可枚举属性复制到target。如果和target的已有属性重名,则会覆盖。同时后续的source会覆盖前面的source的同名属性。

Object.assign复制的是属性值,如果属性值是一个引用类型,那么复制的其实是引用地址,就会存在引用共享的问题

Array.from()方法就是将一个类数组对象或者可遍历对象转换成一个真正的数组。

那么什么是类数组对象呢?所谓类数组对象,最基本的要求就是具有length属性的对象。 1、将类数组对象转换为真正数组:

let arrayLike = {
    0: 'tom', 
    1: '65',
    2: '男',
    3: ['jane','john','Mary'],
    'length': 4
}
let arr = Array.from(arrayLike)
console.log(arr) // ['tom','65','男',['jane','john','Mary']]

那么,如果将上面代码中 length 属性去掉呢?实践证明,答案会是一个长度为0的空数组。

这里将代码再改一下,就是具有 length 属性,但是对象的属性名不再是数字类型的,而是其他字符串型的,代码如下:

let arrayLike = {
    'name': 'tom', 
    'age': '65',
    'sex': '男',
    'friends': ['jane','john','Mary'],
    length: 4
}
let arr = Array.from(arrayLike)
console.log(arr)  // [ undefined, undefined, undefined, undefined ]

会发现结果是长度为4,元素均为 undefined 的数组

由此可见,要将一个类数组对象转换为一个真正的数组,必须具备以下条件:

1、该类数组对象必须具有 length 属性,用于指定数组的长度。如果没有 length 属性,那么转换后的数组是一个空数组。

2、该类数组对象的属性名必须为数值型或字符串型的数字 ———————————————— 版权声明:本文为CSDN博主「有两把刷子」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:blog.csdn.net/qq\_5475356…

16、谈谈你对模块化开发的理解?

我对模块的理解是,一个模块是实现一个特定功能的一组方法。在最开始的时候,js 只实现一些简单的功能,所以并没有模块的概念 ,但随着程序越来越复杂,代码的模块化开发变得越来越重要。

由于函数具有独立作用域的特点,最原始的写法是使用函数来作为模块,几个函数作为一个模块,但是这种方式容易造成全局变量的污 染,并且模块间没有联系。

后面提出了对象写法,通过将函数作为一个对象的方法来实现,这样解决了直接使用函数作为模块的一些缺点,但是这种办法会暴露所 有的所有的模块成员,外部代码可以修改内部属性的值。

现在最常用的是立即执行函数的写法,通过利用闭包来实现模块私有作用域的建立,同时不会对全局作用域造成污染。

js 的几种模块规范?

js 中现在比较成熟的有四种模块加载方案:

第一种是 CommonJS 方案,它通过 require 来引入模块,通过 module.exports 定义模块的输出接口。这种模块加载方案是服务器端的解决方案,它是以同步的方式来引入模块的,因为在服务端文件都存储在本地磁盘,所以读取非常快,所以以同步的方式加载没有问题。但如果是在浏览器端,由于模块的加载是使用网络请求,因此使用异步加载的方式更加合适。

第二种是 AMD 方案,这种方案采用异步加载的方式来加载模块,模块的加载不影响后面语句的执行,所有依赖这个模块的语句都定义在一个回调函数里,等到加载完成后再执行回调函数。require.js 实现了 AMD 规范。

第三种是 CMD 方案,这种方案和 AMD 方案都是为了解决异步模块加载的问题,sea.js 实现了 CMD 规范。它和require.js的区别在于模块定义时对依赖的处理不同和对依赖模块的执行时机的处理不同。

第四种方案是 ES6 提出的方案,使用 import 和 export 的形式来导入导出模块。 ———————————————— 版权声明:本文为CSDN博主「有两把刷子」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:blog.csdn.net/qq\_5475356…

vue

vue.js的两个核心是什么

灵活的组件应用,高效的数据绑定

vue虚拟dom,diff算法

vue虚拟dom

虚拟 DOM,其实就是用对象的方式取代真实的 DOM 操作,把真实的 DOM 操作放在内存当中,在内存中的对象里做模拟操作。当页面打开时浏览器会解析 HTML 元素,构建一颗 DOM 树,将状态全部保存起来,在内存当中模拟我们真实的 DOM 操作,操作完后又会生成一颗 dom 树,两颗 DOM 树进行比较,根据 diff 算法比较两颗 DOM 树不同的地方,只渲染一次不同的地方。

(个人理解)虚拟dom他不并不是真实的 dom ,是根据模板生成一个js对象(使用createElement,方法),根据这个js对象再去生成真实的dom,对复杂的文档DOM结构,提供一种方便的工具,进行最小化的DOM操作 ,是可以快速的渲染和高效的更新元素,提高浏览器的性能,

例如,一个 ul 标签下很多个 li 标签,其中只有一个 li 有变化,这种情况下如果使用新的 ul 去替代旧的 ul,因为这些不必要的 DOM 操作而造成了性能上的浪费,但是如果直接使用虚拟节点覆盖旧节点的话,减少了很多的不必要的 DOM 操作。

我们在渲染页面的时候 会对新的虚拟dom和旧的虚拟dom进行对比 只渲染不同的地方,而不再是像之前只要发生变化,全部的真实dom都要重新渲染,所以提高了渲染的效率。

diff算法

diff算法 当data发生改变 会根据新的数据生成一个新的虚拟dom ,新的虚拟dom和旧的虚拟dom进行对比,这个对比的过程就是diff算法,会找到不同地方,只去渲染不同的地方,总的来说就是减少DOM,重绘和回流。

什么使用虚拟DOM(常问)

  • 创建真实DOM的代价高:真实的 DOM 节点 node 实现的属性很多,而 vnode 仅仅实现一些必要的属性,相比起来,创建一个 vnode 的成本比较低。
  • 触发多次浏览器重绘及回流:使用 vnode ,相当于加了一个缓冲,让一次数据变动所带来的所有 node 变化,先在 vnode 中进行修改,然后 diff 之后对所有产生差异的节点集中一次对 DOM tree 进行修改,以减少浏览器的重绘及回流。
  • 虚拟dom由于本质是一个js对象,因此天生具备跨平台的能力,可以实现在不同平台的准确显示。
  • Virtual DOM 在性能上的收益并不是最主要的,更重要的是它使得 Vue 具备了现代框架应有的高级特性。

Vue模版编译原理

vue中的模板template无法被浏览器解析并渲染,因为这不属于浏览器的标准,不是正确的HTML语法,所有需要将template转化成一个JavaScript函数,这样浏览器就可以执行这一个函数并渲染出对应的HTML元素,就可以让视图跑起来了,这一个转化的过程,就成为模板编译。

Vue 的编译过程就是将 template 转化为 render 函数的过程 分为以下三步

第一步是将 模板字符串 转换成 element ASTs(解析器)

第二步是对 AST 进行静态节点标记,主要用来做虚拟DOM的渲染优化(优化器)

第三步是 使用 element ASTs 生成 render 函数代码字符串(代码生成器) ———————————————— 版权声明:本文为CSDN博主「有两把刷子」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:blog.csdn.net/qq\_5475356…

响应式原理

vue的响应式原理?

 vue的响应式原理?
   什么是响应式,“响应式”,是指当数据改变后,Vue 会通知到使用该数据的代码。
   例如,视图渲染中使用了数据,数据改变后,视图也会自动更新。
   Vue 的响应式原理是核心是通过 ES5 的保护
   对象的 Object.defindeProperty 中的访问器属性中的 getset 方法,
   data 中声明的属性都被添加了访问器属性,当读取 data 中的数据时自动调用 get 方法,
   当修改 data 中的数据时,自动调用 set 方法,检测到数据的变化,
   会通知观察者 Wacher,观察者 Wacher自动触发重新render 当前组件(子组件不会重新渲染),
   生成新的虚拟 DOM 树,Vue 框架会遍历并对比新虚拟 DOM 树和旧虚拟 DOM 树中每个节点的差别,
   并记录下来,最后,加载操作,将所有记录的不同点,局部修改到真实 DOM 树上。
​
   
   
Object.defineProperty怎么用, 三个参数?,有什么作用啊?
     Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
     Object.defineProperty(obj, prop, {})
     obj:需要定义属性的对象
     prop:需要定义的属性
     {}:要定义或修改的属性描述符。
     value: "18",         // 设置默认值得
     enumerable: true,    //这一句控制属性可以枚举 enumerable 改为true 就可以参与遍历了   默认值false
     writable: true,      // 控制属性可以被修改   默认值false
     configurable: true,  // 控制属性可以被删除   默认值false
      get // 当有人读取 prop 的时候  get函数就会调用,并且返回就是 sss 的值
      set // 当有人修改 prop 的时候  set函数就会调用, 有个参数这个参数就是修改后的值
​
     
Object.defineProperty 能定义symbol类型吗? 
       在ES6中,由于 Symbol类型的特殊性,用Symbol类型的值来做对象的key与常规的定义或修改不同,
       而Object.defineProperty 是定义key为Symbol的属性的方法之一。
    
   
    
vue2和vue3的响应式原理都有什么区别呢?
vue2 用的是 Object.defindProperty 但是vue3用的是Proxy 
Object.defindProperty虽然能够实现双向绑定了,但是还是有缺点,只能对对象的属性进行数据劫持,
所以会深度遍历整个对象,不管层级有多深,只要数组中嵌套有对象,
就能监听到对象的数据变化无法监听到数组的变化,Proxy就没有这个问题,
可以监听整个对象的数据变化,所以用vue3.0会用Proxy代替definedProperty。
​
上面就是一个典型的例子,当我们点击按钮想要根据数组 arr 的下标改变其元素的时候,你会发现 data 中的数据改变了,
但是页面中的数据并没有改变。
         我会用  this.$set( target, key, value ) 来解决
       参数:
         {Object | Array} target
         {string | number} propertyName/index
         {any} value 
         第一参数时指定要修改的数据 (target)
         第二个参数就是你要设置数据的下标或者是属性名
         第三个参数就是现在要修改的数据 (重新赋的值)
改变/添加 对象属性的时候:this.$set(data 实例,"属性名(添加的属性名)","属性值(添加的属性值)")
改变/添加 数组属性的时候:this.\$set(data 实例,数组下标,"改变后的元素(添加的元素)")
​
原因 : vue在创建实例的时候把data深度遍历所有属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。
让 Vue 追踪依赖,在属性被访问和修改时通知变化。所以属性必须在 data 对象上存在才能让 Vue 转换它,这样才能让它是响应的。
​
为什么要用  this.$set  呢? this.$set是干什么的?
      当你发现你给对象加了一个属性,在控制台能打印出来,但是却没有更新到视图上时,
      也许这个时候就需要用到this.$set()这个方法了,简单来说this.$set的功能就是解决这个问题的啦。
      官方解释:向响应式对象中添加一个属性,并确保这个新属性同样是响应式的,且触发视图更新。
      它必须用于向响应式对象上添加新属性,因为 Vue 无法探测普通的新增属性 
      (比如 this.myObject.newProperty = 'hi'),你会发现vue官网是vue.set,vue.set的用法
​
​
       
那 Vue.setthis.$set 有什么区别 ?
    Vue.set( ) 是将 set 函数绑定在 Vue 构造函数上,this.$set() 是将 set 函数绑定在 Vue原型上。 

v-module的实现原理(### vue双向数据绑定原理?### Vue底层实现原理)

vue.js是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter和getter,在数据变动时发布消息给订阅者,触发相应的监听回调
Vue是一个典型的MVVM框架,模型(Model)只是普通的javascript对象,修改它则试图(View)会自动更新。这种设计让状态管理变得非常简单而直观

Observer(数据监听器) : Observer的核心是通过Object.defineProprtty()来监听数据的变动,这个函数内部可以定义setter和getter,每当数据发生变化,就会触发setter。这时候Observer就要通知订阅者,订阅者就是Watcher

Watcher(订阅者) : Watcher订阅者作为Observer和Compile之间通信的桥梁,主要做的事情是:

  1. 在自身实例化时往属性订阅器(dep)里面添加自己
  2. 自身必须有一个update()方法
  3. 待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调

Compile(指令解析器) : Compile主要做的事情是解析模板指令,将模板中变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加鉴定数据的订阅者,一旦数据有变动,收到通知,更新试图

vue的getter和setter的原理

get和set就是关键字 它们后面各自对应一个函数,这个函数就是上面红字部分所讲的,存储器属性。

get对应的方法称为getter,负责获取值,它不带任何参数。set对应的方法为setter,负责设置值,在它的函数体中,一切的return都是无效的。

 
    var obj = {};
    obj.name = 'yz';
    console.log(obj.name);
 
    // 通过Object.defineProperty()的set和get方法,使打印出来的name名前后都加上长长的*********
    var obj = {};
    var temp = '';
    Object.defineProperty( obj , 'name' , {
        set : function(value){
            temp = value;
            console.log('名字是' + value)
        },
        get : function(){
            console.log('get监听');
            return `***** ${temp} *****`
        }
    })
    obj.name = 'dddd';
    console.log(obj.name);
 

Vue开发模式:发布-订阅模式(发布机制) blog.csdn.net/weixin\_424…

在Vue开发中,一些大型项目或者涉及到一些很复杂的组件,需要在不同父组件下面的子组件或者孙组件之间传递数据的。如下图:

可以看出,数据从2号传到4号,就得经过7号,10号,再从10号传到8号,再传到4号,过程非常复杂,代码也显得非常臃肿,难以维护。显然仅仅用单向数据流或者双向绑定,并不能很好的满足我们的开发要求。

这时候,我就推荐使用全新的开发模式,发布-订阅模式,它是基于Vue的on,on,off,$emit这三个API开发。原理是创建一个事件中心绑定到每个组件上面,只要有一个组件的有新的数据发布到上面,其他已经订阅的组件就可以同步获取。如图:

这样一来,1号组件向4号组件发送数据,只要经过事件中心就行了,而4号组件只要$on订阅,这样就能获取数据。

好的,原理就先说到这里,下面来个栗子:

父组件://创建事件中心
data(){
  return {
    eventBus: new Vue()
  }
},
provide(){
  return {
  eventBus: this.eventBus
  }
}

\

子组件://注入事件中心
inject: ['eventBus']

然后,无论子组件还是父组件都能通过这样来传递数据:

this.eventBus.$emit() // 发布
this.eventBus.$on() // 订阅
this.eventBus.$off() // 取消订阅

v-if和v-show的区别及使用场景?

v-if 动态的创建或者销毁元素,为true的时候为显示,为false的时候不显示,要使用v-else必须和v-if紧挨着

v-show 是控制元素的显示或者隐藏,在我们的标签上会看到有display:block,none

v-if 有更高的切换消耗,而 v-show 有更高的初始化渲染消耗,一般推荐频繁切换的时候使用 v-show 更好,当我们的判断分支比较多的时候,和首次渲染的时候 使用v-if

vue2和vue3的数据绑定区别(vue3的Proxy 相比于 vue2的defineProperty 的优势)

在vue3 中 Vue3是通过Object.define.proxy 对对象进行代理,从而实现数据劫持。

使用Proxy 的好处是它可 以完美的监听到任何方式的数据改变,唯一的缺点是兼容性的问题,因为 Proxy 是 ES6 的语法

Vue3.0 摒弃了 Object.defineProperty,改为基于 Proxy 的观察者机制探索。

首先说一下 Object.defineProperty 的缺点:

① Object.defineProperty 无法监控到数组下标的变化,导致直接通过数组的下标给数组设置值, 不能实施响应。 this.$set()解决

② Object.defineProperty 只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。

Vue2.X 里,是通过递归 + 遍历 data 对象来实现对数据的监控的, 如果属性值也是对象那么需要深度遍历,显然如果能劫持一个完整的对象才是更好的选择。

而要取代它的 Proxy 有以下两个优点 可以劫持整个对象,并返回一个新对象。有多种劫持操作(13 种) 补充: Proxy 用于创建一个对象的代理,从而实现基本操作的拦截和自定义 (如属性查找、赋值、枚举、函数调用等)。

Proxy 是 ES6 新增的一个属性,翻译过来的意思就是代理, 用在这里表示由它来“代理”某些操作。Proxy 让我们能够以简洁易懂的方式控制外部对象的访问, 其功能非常类似于设计模式中的代理模式。

1、vue 中数组中的某个对象的属性发生变化,视图不更新如何解决? Object.defineProperty 无法监控到数组下标的变化,导致直接通过数组的下标给数组设置值,不能实施响应。 this.$set()解决

问题原因:因为 vue 的检查机制在进行视图更新时无法监测 数组中的对象的某个属性值的变化。

解决方案如下 方案一:利用 this.set(this.obj,key,val)

例:this.set(this.obj,‘k1’,‘v1’)

方案二:就利用 Object.assign({},this.obj)创建新对象

如果是数组就 Object.assign([],this.obj)

如果是对象就 Object.assign({},this.obj)。

vue3.0 与 vue2.0 的区别

1.性能提升

更小巧,更快速;支持摇树优化。支持 Fragments (支持多个根节点)和跨组件渲染;支持自定义渲染器。

2.API 变动

Vue2使用 选项类型API(Options API) 对比Vue3 合成型API(Composition API)

optionsApi 使用传统api中,新增一个需求,要在data,methods,computed中修改

compositionApi 我们可以更加优雅的组织我们的代码,函数,让我们的代码更加有序的组合在一起

3.重写虚拟 DOM (Virtual DOM Rewrite)

随着虚拟 DOM 重写,减少 运行时(runtime)开销。重写将包括更有效的代码来创建虚拟节点。

vue3 没有了过滤器

双向数据绑定 从 Object.defineProperty() 变成了 proxy,通过下标修改数组变化了试图数据没发生变化 this.$set() vue3不需要

双向数据绑定原理发生了改变,使用proxy替换Object.defineProerty,使用Proxy的优势:

可直接监听数组类型的数据变

监听的目标为对象本身,不需要像Object.defineProperty一样遍历每个属性,有一定的性能提升

可直接实现对象属性的新增/删除

setup 函数

3.0新加入了TypeScript以及PWA支持

默认使用懒加载

可以不用加上key

vue3 的watch监听可以进行终止监听 ———————————————— 版权声明:本文为CSDN博主「有两把刷子」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:blog.csdn.net/qq\_5475356…

vue 与 react的区别

相同点 :

都是使用了虚拟dom

组件化开发

父子之间通信单项数据流

都支持服务端渲染

不同点:

reacct 的jsx vue的是 template

数据变化,react 手动 setState vue自动响应式处理 proxy object.DefineProperty

react 单向数据流 ,vue双向数据流

react 的 redux mobx vue 的vuex 。pinia ———————————————— 版权声明:本文为CSDN博主「有两把刷子」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:blog.csdn.net/qq\_5475356…

MVVM

什么是MVVM?

视图模型双向绑定,是Model-View-ViewModel的缩写,也就是把MVC中的Controller演变成ViewModel。Model层代表数据模型,View代表UI组件,ViewModelViewModel层的桥梁,数据会绑定到viewModel层并自动将数据渲染到页面中,视图变化的时候会通知viewModel层更新数据。以前是操作DOM结构更新视图,现在是数据驱动视图

MVVM的优点:

1.低耦合。视图(View)可以独立于Model变化和修改,一个Model可以绑定到不同的View上,当View变化的时候Model可以不变化,当Model变化的时候View也可以不变;
2.可重用性。你可以把一些视图逻辑放在一个Model里面,让很多View重用这段视图逻辑。
3.独立开发。开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于页面设计。
4.可测试

mvc mvvm和mvp的区别

MVVM 就是 Model-View-ViewModel 的缩写,MVVM 将视图和业务逻辑分开。

View:视图层,Model 数据模型,而 ViewModel 是把两者建立通信的桥梁。

在 MVVM 框架下,View 和 Model 之间没有直接的联系,而是通过 ViewModel 进行交互。View 和 ViewModel 之间以及 Model 和 ViewModel 之间的交互都是双向的,因此 view 数据的变化会同步到 Model 中,而 Model 数据的变化也会立即反映到 View 上。可以说它们两者是实时更新的,互相影响。 ViewModel 通过双向数据绑定把 View 层和 Model 层连接了起来,而 View 和 Model 之间的同步工作完全是自动的,因此开发者只需要关注业务逻辑,不需要手动操作 DOM,也不需要关注数据状态的同步问题,这些都由 MVVM 统一管理,

整体看来,MVVM比MVC精简很多,不仅简化了业务与界面的依赖,还解决了数据频繁更新的问题,不用再用选择器操作DOM元素。因为在MVVM中,View不知道Model的存在,Model和ViewModel也观察不到View,这种低耦合模式提高代码的可重用性。

【优点】

数据源和视图实现了双向绑定,很好的做到了数据的一致性 相比于mvp各层的耦合度更低,一个viewmodel层可以给多个view层共用。

【缺点】

因为使用了dataBinding,增加了大量的内存开销,增加了程序的编译时间,项目越大内存开销越大。 数据绑定使得 Bug 很难被调试。你看到界面异常了,有可能是你 View 的代码有 Bug,也可能是 Model 的代码有问题

MVC 全名是 Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范

  • Model(模型):是应用程序中用于处理应用程序数据逻辑的部分。通常模型对象负责在数据库中存取数据
  • View(视图):是应用程序中处理数据显示的部分。通常视图是依据模型数据创建的
  • Controller(控制器):是应用程序中处理用户交互的部分。通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据,也可以将Model的数据用View显示出来

【优点】

耦合性低,方便维护,可以利于分工协作 重用性高

【缺点】

使得项目架构变得复杂,对开发人员要求高

MVP MVP 是从经典的模式MVC演变而来,它们的基本思想有相通的地方Controller/Presenter负责逻辑的处理,Model提供数据,View负责显示,在MVP中View并不直接使用Model,它们之间的通信是通过Presenter (MVC中的Controller)来进行的

———————————————— 版权声明:本文为CSDN博主「有两把刷子」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:blog.csdn.net/qq\_5475356…

vue 组件之间的传值(12种方法

// Vue组件间的传值的几种方式 
1. props/emit 
2. $attrs/$listeners // $attrs 除了父级作用域 props、class、style 之外的属性 // $listeners 父组件里面的所有的监听方法 
3. $refs/$parent/$children/$root/ 
4. vuex 
5. 事件总线,通过new Vue去实现 / mitt <==> vue3 
6. provide/inject // 父组件 props: {}, provide() { name: this.name, user: this.user } // 子组件 props: {}, inject: ['user'] 
7. 本地存储、全局变量

vue的修饰符

.stop  阻止事件冒泡
.cpture  设置事件捕获
.self  只有当事件作用在元素本身才会触发
.prevent  阻止事件默认行为,比如超链接跳转
.once  事件只能执行一次
.native  触发js原生的事件
.number  把文本框内容转化为数字
.trim  去除文本框左右空格
.lazy   懒加载

vue的指令

⑴v-bind:给元素绑定属性 
⑵v-on:给元素绑定事件 
⑶v-html:给元素绑定数据,且该指令可以解析 html 标签 
⑷v-text:给元素绑定数据,不解析标签 
⑸v-model:数据双向绑定 
⑹v-for:遍历数组 
⑺v-if:条件渲染指令,动态在 DOM 内添加或删除 DOM 元素 
⑻v-else:条件渲染指令,必须跟 v-if 成对使用 
⑼v-else-if:判断多层条件,必须跟 v-if 成对使用 
⑽v-cloak:解决插值闪烁问题 
⑾v-once:只渲染元素或组件一次 
⑿v-pre:跳过这个元素以及子元素的编译过程,以此来加快整个项目的编译速度 
⒀v-show:条件渲染指令,将不符合条件的数据隐藏(display:none)

生命周期

每个Vue实例在创建时都会经过一系列的初始化过程,vue的生命周期钩子,就是说在达到某一阶段或条件时去触发的函数,目的就是为了完成一些动作或者事件

beforeCreate() 创建前,这个时候data中的数据,还未定义,所以不能使用 
created()创建后,最早开始使用 data和methods中数据的钩子函数
beforeMount()挂载前,指令已经解析完毕内存中已经生成dom树,但是尚未挂载到页面中去,此时
页面还是旧的。 
mounted()挂载后,dom已经渲染完毕,此时页面和内存中都是最新的数据,最早可以操作DOM元素钩子函数
beforeUpdate()更新前,当视图层的数据发生改变会执行这个钩子,内存更新,
但是DOM节点还未更新,数据没有与页面同步 
updated()更新后,数据更新完成以后触发的方法,DOM节点已经更新
beforeDestroy()即将销毁 ,data和methods中的数据此时还是可以使用的,
可以做一些释放内存的操作 
destroyed()销毁完毕,组件已经全部销毁,Vue实例已经被销毁,Vue中的任何数据都不可用
其他三个: 
activated 被 keep-alive 缓存的组件激活时调用。 
deactivated 被 keep-alive 缓存的组件停用时调用。 
errorCaptured 2.5.0+ 新增当捕获一个来自子孙组件的错误时被调用
  • vue的实例加载完成是在哪个声明周期完成呢 beforeCreate
  • vue的dom挂载完成是在哪个声命周期里呢 mounted

created mounted 的区别?

created 模板渲染成html前调用,即通常初始化某些属性值,然后再渲染成视图。

mounted:在模板渲染成html后调用,通常是初始化页面完成后,再对html的dom节点进行一些需要的操作。

a页面跳转到b页面周期执行

页面a----beforeCreate undefined 
页面a----created 1 
页面a----beforeMount 1 
页面a----mounted 1 
页面b----beforeCreate undefined 
页面b----created 1 
页面b----beforeMount 1 
页面a----beforeDestroy 1 
页面a----destroyed 1 
页面b----mounted 1

组件 和 页面周期 的执行顺序

- 页面beforeCreate undefined 
- 页面created 1 
- 页面beforeMount 1 
- 组件beforeCreate undefined 
- 组件created 5555 
- 组件beforeMount 5555 
- 组件mounted 5555 
- 页面mounted 1

父子组件生命周期执行顺序

加载渲染过程 

父beforeCreate->父created->父beforeMount->
子beforeCreate->子created->子beforeMount->子mounted->父mounted 

代码更新过程 

父beforeUpdate->子beforeUpdate->子updated->父updated 

代码销毁过程 

父beforeDestroy->子beforeDestroy->子destroyed->父destroyed 

代码常用钩子简易版

父create->子created->子mounted->父mounted

单一组件钩子执行顺序

activated, 
deactivated 是组件keep-alive时独有的钩子
beforeCreate
created
beforeMount
mounted
beforeUpdate
updated
activated
deactivated
beforeDestroy
destroyed
errorCaptured

vue3生命周期

beforeCreate -> setup() 开始创建组件之前,创建的是data和method 
created -> setup() 
beforeMount -> onBeforeMount 组件挂载到节点上之前执行的函数。
mounted -> onMounted 组件挂载完成后执行的函数 
beforeUpdate -> onBeforeUpdate 组件更新之前执行的函数。
Update - > onUpdated组件更新完成之后执行的函数。
beforeDestroy -> onBeforeUnmount 组件挂载到节点上之前执行的函数。 
destroyed -> onUnmounted 组件卸载之前执行的函数。

computed与watch

通俗来讲,既能用 computed 实现又可以用 watch 监听来实现的功能,推荐用 computed, 重点在于 computed 的缓存功能 computed 计算属性是用来声明式的描述一个值依赖了其它的值,当所依赖的值或者变量 改变时,计算属性也会跟着改变; watch 监听的是已经在 data 中定义的变量,当该变量变化时,会触发 watch 中的方法。

watch 属性监听 是一个对象,键是需要观察的属性,值是对应回调函数,主要用来监听某些特定数据的变化,从而进行某些具体的业务逻辑操作,监听属性的变化,需要在数据变化时执行异步或开销较大的操作时使用

computed 计算属性 属性的结果会被缓存,当computed中的函数所依赖的属性没有发生改变的时候,那么调用当前函数的时候结果会从缓存中读取。除非依赖的响应式属性变化时才会重新计算,主要当做属性来使用 computed中的函数必须用return返回最终的结果 computed更高效,优先使用。data 不改变,computed 不更新。

使用场景 computed:当一个属性受多个属性影响的时候使用,例:购物车商品结算功能 watch:当一条数据影响多条数据的时候使用,例:搜索数据

组件中的data为什么是一个函数?

1.一个组件被复用多次的话,也就会创建多个实例。本质上,这些实例用的都是同一个构造函数。

2.如果data是对象的话,对象属于引用类型,会影响到所有的实例。所以为了保证组件不同的实例之间data不冲突,data必须是一个函数。

(一个函数return一个对象,这个对象就是一个新的内存地址,而且这个 内存地址也会一直使用,不会被释放,这样每个组件的data值都是一个 新的内存地址,这样就不会影响。)

Vue中key是用来做什么的?为什么不推介使用index作为key?

1、key的作用主要是为了高效的更新虚拟DOM(使用key,它会基于key的变化重新排列元素顺序,并且会移除key不存在的元素)

2、当以数组的下标index作为index值时,其中一个元素(如增删改查)发生了变化就有可能导致所有元素的key值发生变化

为什么v-for和v-if不建议用在一起

1.当 v-for 和 v-if 处于同一个节点时,v-for 的优先级比 v-if 更高,这意味着 v-if 将分别重复运行于每个 v-for 循环中。如果要遍历的数组很大,而真正要展示的数据很少时,这将造成很大的性能浪费
2.这种场景建议使用 computed,先对数据进行过滤

注意:3.x 版本中 v-if 总是优先于 v-for 生效。由于语法上存在歧义,建议避免在同一元素上同时使用两者。比起在模板层面管理相关逻辑,更好的办法是通过创建计算属性筛选出列表,并以此创建可见元素。

nextTick的实现

  1. nextTickVue提供的一个全局API,是在下次DOM更新循环结束之后执行延迟回调,在修改数据之后使用$nextTick,则可以在回调中获取更新后的DOM
  2. Vue在更新DOM时是异步执行的。只要侦听到数据变化,Vue将开启1个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个watcher被多次触发,只会被推入到队列中-次。这种在缓冲时去除重复数据对于避免不必要的计算和DOM操作是非常重要的。nextTick方法会在队列中加入一个回调函数,确保该函数在前面的dom操作完成后才调用;
  3. 比如,我在干什么的时候就会使用nextTick,传一个回调函数进去,在里面执行dom操作即可;
  4. 我也有简单了解nextTick实现,它会在callbacks里面加入我们传入的函数,然后用timerFunc异步方式调用它们,首选的异步方式会是Promise。这让我明白了为什么可以在nextTick中看到dom操作结果。

nextTick的实现原理是什么?

在下次 DOM 更新循环结束之后执行延迟回调,在修改数据之后立即使用 nextTick 来获取更新后的 DOM。 nextTick主要使用了宏任务和微任务。 根据执行环境分别尝试采用Promise、MutationObserver、setImmediate,如果以上都不行则采用setTimeout定义了一个异步方法,多次调用nextTick会将方法存入队列中,通过这个异步方法清空当前队列。

this.$nextTick()使用情景

  1. created 和 mounted 阶段,如果需要操作渲染后的视图,要使用 nextTick 方法。因为mounted不会承诺其子组件也挂载完毕
  2. 通过v-show切换到输入框后,自动聚焦用得到。
  3. 点击获取某个元素的style样式时用得到

image.png

keep-alive的实现

作用:实现组件缓存,保持这些组件的状态,以避免反复渲染导致的性能问题。 需要缓存组件 频繁切换,不需要重复渲染

场景:tabs标签页 后台导航,vue性能优化

原理:Vue.js内部将DOM节点抽象成了一个个的VNode节点,keep-alive组件的缓存也是基于VNode节点的而不是直接存储DOM结构。它将满足条件(pruneCache与pruneCache)的组件在cache对象中缓存起来,在需要重新渲染的时候再将vnode节点从cache对象中取出并渲染。


作者:Gaby
链接:juejin.cn/post/701659… 来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

路由对象route和router的区别

route (读取 路由参数接收)是“路由信息对象”,包括 path,params,hash,query,fullPath,matched,name 等路由信息参数。

router(操作路由跳转) 是“路由实例对象”,包括了路由的跳转方法(push、go),钩子函数等

vue-router

vue-router 路由模式

路由有两种模式 hash和history模式 默认是hash

vue-router的实现原理(核心):更新视图但不重新请求页面。

1、hash ——即地址栏 URL 中的#符号,它的特点在于:hash 虽然出现 URL 中, 但不会被包含在 HTTP 请求中,对后端完全没有影 响,因此改变 hash 不会重新加载页面。

2、history ——在浏览器中没有# 有浏览器兼容问题,history 模式下,前端的 URL 必须和实际向后端发起请求的 URL 一致,否则返回 404 错误。

vue-router 中常用的路由模式实现原理

hash 模式

location.hash 的值实际就是 URL 中#后面的东西

它的特点在于:hash 虽然出现 URL 中,但不会被包含在 HTTP 请求中,对后端完全没有影响,因此改变 hash 不会重新加载页面。

可以为 hash 的改变添加监听事件 window.addEventListener("hashchange", funcRef, false); 每一次改变 hash(window.location.hash),都会在浏览器的访问历史中增加一个 记录

利用 hash 的以上特点,就可以来实现前端路由“更新视图但不重新请求页面”的功能了 特点:兼容性好但是不美观

history 模式

利用了 HTML5 History Interface 中新增的 pushState()replaceState() 方法。

这两个方法应用于浏览器的历史记录站,在当前已有的 back、forward、go 的基础之上, 它们提供了对历史记录进行修改的功能。

这两个方法有个共同的特点: 当调用他们修改浏览器历史记录栈后,虽然当前 URL 改变了,但浏览器不会刷新页面 这就为单页应用前端路由“更新视图但不重新请求页面”提供了基础。

vue-router可以设置两种模式:hash和history
const router = new VueRouter({ mode: "hash", // mode: "history", routes });

如果使用hash模式,一般无需特殊配置;

但如果要使用history模式,则前端和服务端要做一定的设置;

使用history模式通常本地调试没有什么问题,但是一旦发布到测试或生产环境,则会出现页面白屏或者刷新页面白屏的现象,这种问题的出现是因为前端和服务端没有做相应的配置。

二、实现history模式需要怎样配置

首先要设置路由的mode和base两个值,如下:

const routes = [...] 
const router = 
    new VueRouter({ mode: "history", base: process.env.BASE_URL, 
    // 如果使用history模式,必须设置base参数 routes }); 
export default router; 
//NODE_ENV - 会是 "development"、"production" 或 "test" 中的⼀个。 
//具体的值取决于应⽤运⾏的。 
//BASE_URL - 会和 vue.config.js 中的 publicPath 选项相符,
即你的应⽤会部署到的基础路径

其次要设置vue.config.js里的publicPath,如下:

module.exports = {
    /**publicPath默认值是'/',即你的应用是被部署在一个域名的根路径上设置为'./',
    可以避免打包后的静态页面空白 ,当在非本地环境时,这里以项目test-daily为例,
    即打包后的h5项目部署服务器的 /test-daily目录下, 
    那么这里就要把publicPath设置为/test-daily/,表示所有的静态资源 
    都在/test-daily/里,
    打包部署后,会发现index.html引用的静态资源都添加了路径/test-daily/ **/
    
    publicPath: process.env.NODE_ENV == 'development' ? './' : '/test-daily/', 
    ...... 
}

vue-router 路由守卫

1.导航守卫是什么

简单的说,导航守卫就是路由跳转过程中的一些钩子函数。路由跳转是一个大的过程,这个大的过程分为跳转前中后等等细小的过程,在每一个过程中都有一函数,这个函数能让你操作一些其他的事儿的时机,这就是导航守卫。

Vue导航守卫的钩子函数有哪些?
全局守卫
  • router.beforeEach:全局前置守卫,进入路由之前
  • router.beforeResolve:全局解析守卫,在beforeRouteEnter调用之后调用
  • router.afterEach:全局后置钩子,进入路由之后
路由组件内的守卫
  • beforeRouteEnter():进入路由前
  • beforeRouteUpdate():路由复用同一个组件时
  • beforeRouteLeave():离开当前路由时
vue-router 路由钩子函数执行顺序是什么执行顺序
一、打开页面的任意一个页面,没有发生导航切换。
全局前置守卫beforeEach (路由器实例内的前置守卫)
路由独享守卫beforeEnter(激活的路由)
组件内守卫beforeRouteEnter(渲染的组件)
全局解析守卫beforeResolve(路由器实例内的解析守卫)
全局后置钩子afterEach(路由器实例内的后置钩子)
​
二、如果是有导航切换的(从一个组件切换到另外一个组件)
组件内守卫beforeRouteLeave(即将离开的组件)
全局前置守卫beforeEach (路由器实例内的前置守卫)
组件内守卫beforeRouteEnter(渲染的组件)
全局解析守卫beforeResolve(路由器实例内的解析守卫)
全局后置钩子afterEach(路由器实例内的后置钩子)
​
完整的导航解析流程(执行顺序)
导航被触发。
在失活的组件里调用 beforeRouteLeave 守卫。
调用全局的 beforeEach 守卫。
在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
在路由配置里调用 beforeEnter。
解析异步路由组件。
在被激活的组件里调用 beforeRouteEnter。
调用全局的 beforeResolve 守卫 (2.5+)。
导航被确认。
调用全局的 afterEach 钩子。
触发 DOM 更新。
调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。

vue路由跳转

  1. router-link

  2. this.$router.push()

  3. this.$router.replace()  (用法同上,push)

  4. this.$router.go(n)

总结区别:

this.$router.push
跳转到指定url路径,并想history栈中添加一个记录,点击后退会返回到上一个页面

this.$router.replace
跳转到指定url路径,但是history栈中不会有记录,点击返回会跳转到上上个页面 (就是直接替换了当前页面)

this.$router.go(n)
向前或者向后跳转n个页面,n可为正整数或负整数

vue编程式的导航跳转传参的方式有哪些?

// 命名的路由
router.push({ name: 'user', params: { userId: '123' }})
​
// 带查询参数,变成 /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }})

原生js的页面跳转

  1. window.location.href
  2. location.replace

window.location.href 和 location.replace的区别:

  1. 有3个页面 a,b,c。 如果当前页面是c页面,并且c页面是这样跳转过来的:a->b->c
  2. b->c 是通过 window.location.replace("..xx/c") 此时b页面的url会被c页面代替,并且点击后退按钮时会回退到a页面(最开始的页面)
  3. b->c是通过 window.location.href("..xx/c") 此时b页面的路径会被c页面代替,但是点击回按钮后页面回退的是b页面
  1. window.navigate("www.baidu")

window.location.href 和 window.navigate的区别:

  1. 首先说明的是 window.navigatewindow.location.href 都是实现页面链接跳转的。
  2. window.navigate 这个方法是只针对IE的,不适用于火狐等其他浏览器,在HTML DOM Window Object中,根本没有列出window.navigate这个方法,所以这个方法尽量少用,遗忘最好。
  3. window.location.href 兼容所有浏览器的。因此在实现页面跳转的时候还是使用这个比较靠谱。
  1. window.open("www.baidu.com")

动态路由

www.proyy.com/69632110388…

什么是slot?什么是命名slot?slot怎么使用?

插槽就是父组件往子组件中插入一些内容。

有三种方式,默认插槽,具名插槽,作用域插槽

默认插槽就是把父组件中的数据,显示在子组件中,子组件通过一个slot插槽标签显示父组件中的数据

具名插槽是在父组件中通过slot属性,给插槽命名,在子组件中通过slot标签,根据定义好的名字填充到对应的位置。这样就可以指定多个可区分的slot,在使用组件时灵活地进行插值。

作用域插槽是带数据的插槽,子组件提供给父组件的参数,父组件根据子组件传过来的插槽数据来进行不同的展现和填充内容。在标签中通过v-slot=""要穿过来的数据“来接受数据。

scoped 原理及穿透方法

vue 中的 scoped 通过在 DOM 结构以及 css 样式上加唯一不重复的标记:data-v-hash 的方式,以保证唯一(通过 PostCSS 转译),达到样式私有模块化的目的。

scoped 的 3 条渲染规则: ① 给 HTML 的 DOM 节点加一个不重复的 data 属性,来表示它的唯一性; ② 在每句 css 选择器末尾(编译后的生成的 css 语句)加一个当前组件的 data 属性选择器来私有化样式; ③ 如果组件内部包含有其他组件,只会给其他组件的最外层标签加上 ddan 当前组件的 data 属性。 补充:

在做项目中,会遇到这么一个问题,即:引用了第三方组件,需要在组件中局部修改第三方组件的样式,而又不想去除scoped属性造成组件之间的样式污染。那么有哪些解决办法呢?

①不使用scopeds省略(不推荐);

② 在模板中使用两次style标签。

③scoped穿透:/deep/ >>>

Vuex

Vuex的理解及使用场景

Vuex 是一个专为 Vue 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。

  1. Vuex 的状态存储是响应式的;当 Vue 组件从 store 中读取状态的时候,

若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新 2. 改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation, 这样使得我们可以方便地跟踪每一个状态的变化

vuex有哪几种属性

有五种,分别是State , Getter , Mutation , Action , Module (就是mapAction)

  1. state:vuex的基本数据,用来存储变量
  2. geeter:从基本数据(state)派生的数据,相当于state的计算属性
  3. mutation:提交更新数据的方法,必须是同步的(如果需要异步使用action)。每个mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数,提交载荷作为第二个参数。
  4. action:和mutation的功能大致相同,不同之处在于 ==》1. Action 提交的是 mutation,而不是直接变更状态。 2. Action 可以包含任意异步操作。
  5. modules:模块化vuex,可以让每一个模块拥有自己的state、mutation、action、getters,使得结构非常清晰,方便管理。

Vuex 的mutations和actions的区别

commit是提交执行mutations中的方法,Mutations 是修改数据的,必须同步,只有mutation才能正真改变VUEX stroe中的state。

dispatch是提交执行actions中的方法,action 提交的是Mutations,可以是异步操作。action不可以修改store中的数据,需要commit mutation中的方法进行数据修改

当你的操作行为中含有异步操作,比如向后台发送请求获取数据,就需要使用action的dispatch去完成了。

// state 
this.$store.state.userInfo; 
// getters 
this.$store.getters.userInfo; 
// mutations 
this.$store.commit("SET_USER_INFO", "传递数据"); 
// actions 
this.$store.dispatch("logout").then((res) => {}); 
// ----------------------------------- 
// modules > user 
// namespaced: true, 
// state 拿 name 
this.$store.state.user.avatar; 
// getters 
this.$store.getters.user.avatar; 
// mutations 
this.$store.commit("user/SET_TOKEN", "传递数据"); 
// actions 
this.$store.dispatch("user/login").then((res) => {}); 
// ----------------------------------- 
// modules > user 
// namespaced: false, 
// state 拿 name 
this.$store.state.user.avatar; 
// getters 
this.$store.getters.user.avatar; 
// mutations 
this.$store.commit("SET_TOKEN", "传递数据"); 
// actions 
this.$store.dispatch("login").then((res) => {});

Vuex 页面刷新数据丢失怎么解决

需要做 vuex 数据持久化 一般使用本地存储的方案来保存数据 可以自己设计存储方案 也可以使用第三方插件 推荐使用 vuex-persist 插件,它就是为 Vuex 持久化存储而生的一个插件。不需要你手动存取 storage ,而是直接将状态保存至 cookie 或者 localStorage 中

webpack

webpack 做过哪些优化,开发效率方面、打包策略方面等等

1)优化 Webpack 的构建速度

  • 使用高版本的 Webpack (使用webpack4)

  • 多线程/多实例构建:HappyPack(不维护了)、thread-loader

  • 缩小打包作用域:

    • exclude/include (确定 loader 规则范围)
    • resolve.modules 指明第三方模块的绝对路径 (减少不必要的查找)
    • resolve.extensions 尽可能减少后缀尝试的可能性
    • noParse 对完全不需要解析的库进行忽略 (不去解析但仍会打包到 bundle 中,注意被忽略掉的文件里不应该包含 import、require、define 等模块化语句)
    • IgnorePlugin (完全排除模块)
    • 合理使用alias
  • 充分利用缓存提升二次构建速度:

    • babel-loader 开启缓存
    • terser-webpack-plugin 开启缓存
    • 使用 cache-loader 或者 hard-source-webpack-plugin
      注意:thread-loader 和 cache-loader 兩個要一起使用的話,請先放 cache-loader 接著是 thread-loader 最後才是 heavy-loader
  • DLL:

    • 使用 DllPlugin 进行分包,使用 DllReferencePlugin(索引链接) 对 manifest.json 引用,让一些基本不会改动的代码先打包成静态资源,避免反复编译浪费时间。

2)使用webpack4-优化原因

  • (a)V8带来的优化(for of替代forEach、Map和Set替代Object、includes替代indexOf)
  • (b)默认使用更快的md4 hash算法
  • (c)webpacks AST可以直接从loader传递给AST,减少解析时间
  • (d)使用字符串方法替代正则表达式

①noParse

  • 不去解析某个库内部的依赖关系
  • 比如jquery 这个库是独立的, 则不去解析这个库内部依赖的其他的东西
  • 在独立库的时候可以使用
module.exports = {
  module: {
    noParse: /jquery/,
    rules:[]
  }
}
复制代码

②IgnorePlugin

  • 忽略掉某些内容 不去解析依赖库内部引用的某些内容
  • 从moment中引用 ./locol 则忽略掉
  • 如果要用local的话 则必须在项目中必须手动引入
import 'moment/locale/zh-cn'
module.exports = {
    plugins: [
        new Webpack.IgnorePlugin(/./local/, /moment/),
    ]
}
复制代码

③dillPlugin

  • 不会多次打包, 优化打包时间
  • 先把依赖的不变的库打包
  • 生成 manifest.json文件
  • 然后在webpack.config中引入
  • webpack.DllPlugin Webpack.DllReferencePlugin

④happypack -> thread-loader

  • 大项目的时候开启多线程打包
  • 影响前端发布速度的有两个方面,一个是构建,一个就是压缩,把这两个东西优化起来,可以减少很多发布的时间。

⑤thread-loader
thread-loader 会将您的 loader 放置在一个 worker 池里面运行,以达到多线程构建。
把这个 loader 放置在其他 loader 之前(如下图 example 的位置), 放置在这个 loader 之后的 loader 就会在一个单独的 worker 池(worker pool)中运行。

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /.js$/,
        include: path.resolve("src"),
        use: [
          "thread-loader",
          // 你的高开销的loader放置在此 (e.g babel-loader)
        ]
      }
    ]
  }
}
复制代码

每个 worker 都是一个单独的有 600ms 限制的 node.js 进程。同时跨进程的数据交换也会被限制。请在高开销的loader中使用,否则效果不佳

⑥压缩加速——开启多线程压缩

  • 不推荐使用 webpack-paralle-uglify-plugin,项目基本处于没人维护的阶段,issue 没人处理,pr没人合并。
    Webpack 4.0以前:uglifyjs-webpack-plugin,parallel参数
module.exports = {
  optimization: {
    minimizer: [
      new UglifyJsPlugin({
        parallel: true,
      }),
    ],
  },};
复制代码
  • 推荐使用 terser-webpack-plugin
module.exports = {
  optimization: {
    minimizer: [new TerserPlugin(
      parallel: true   // 多线程
    )],
  },
};
复制代码

2)优化 Webpack 的打包体积

  • 压缩代码
  • 提取页面公共资源:
  • Tree shaking
  • Scope hoisting
  • 图片压缩
  • 动态Polyfill

3)speed-measure-webpack-plugin
简称 SMP,分析出 Webpack 打包过程中 Loader 和 Plugin 的耗时,有助于找到构建过程中的性能瓶颈。 开发阶段

开启多核压缩 插件:** terser-webpack-plugin **

const TerserPlugin = require('terser-webpack-plugin')
module.exports = {
    optimization: {
        minimizer: [
            new TerserPlugin({
                parallel: true,
                terserOptions: {
                    ecma: 6,
                },
            }),
        ]
    }
}
复制代码

传送门 ☞# 工程化专题

Webpack 一些核心概念:

【万字】透过分析 webpack 面试题,构建 webpack5.x 知识体系

Entry:入口,指示 Webpack 应该使用哪个模块,来作为构建其内部 依赖图(dependency graph) 的开始。

Output:输出结果,告诉 Webpack 在哪里输出它所创建的 bundle,以及如何命名这些文件。

Module:模块,在 Webpack 里一切皆模块,一个模块对应着一个文件。Webpack 会从配置的 Entry 开始递归找出所有依赖的模块。

Chunk:代码块,一个 Chunk 由多个模块组合而成,用于代码合并与分割。

Loader:模块代码转换器,让 webpack 能够去处理除了 JS、JSON 之外的其他类型的文件,并将它们转换为有效 模块,以供应用程序使用,以及被添加到依赖图中。

Plugin:扩展插件。在 webpack 运行的生命周期中会广播出许多事件,plugin 可以监听这些事件,在合适的时机通过 webpack 提供的 api 改变输出结果。常见的有:打包优化,资源管理,注入环境变量。

Mode:模式,告知 webpack 使用相应模式的内置优化

hash: 每次构建的生成唯一的一个 hash,且所有的文件 hash 串是一样的 chunkhash: 每个入口文件都是一个 chunk,每个 chunk 是由入口文件与其依赖所构成,异步加载的文件也被视为是一个 chunk, chunkhash是由每次编译模块,根据模块及其依赖模块构成 chunk 生成对应的 chunkhash, 这也就表明了每个 chunk 的 chunkhash 值都不一样, 也就是说每个 chunk 都是独立开来的,互不影响,每个 chunk 的更新不会影响其他 chunk 的编译构建

contenthash:由文件内容决定,文件变化 contenthash 才会变化,一般配合 mini-css-extract-plugin插件提取出 css

const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const HTMLWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          {
            // 把 style-loader替换掉,不要使用 style-loader了
            loader: MiniCssExtractPlugin.loader,
            options: {
              outputPath: "css/",
            },
          },
          "css-loader",
        ],
      },
    ],
  },
  plugins: [
    // ....
    new MiniCssExtractPlugin({
      filename: "css/[name].[contenthash].css",
    }),
  ],
};

学习 Webpack5 之路(优化篇)

  • 速度分析,可以使用 speed-measure-webpack-plugin

  • 提升基础环境,nodejs 版本,webpack 版本

  • CDN 分包 html-webpack-externals-plugin, externals

  • 多进程、多实例构建 thread-loader happypack(不再维护)

  • 多进程并行构建打包uglifyjs-webpack-plugin terser-webpack-plugin

  • 缓存: webpack5 内置了cache模块 、babel-loader 的 cacheDirectory 标志、cache-loader, HardSourceWebpackPlugin

module.exports = {
  // webpack5内置缓存
  cache: {
    type: "filesystem", // 使用文件缓存
  },
};
  • 构建缩小范围 include,exclude

  • 加快文件查找速度resolve.alias,resolve.extensions, module.noParse

  • DllPlugin

  • babel 配置的优化

webpack 常用 loader,plugin

loader

  • babel-loader 将 es6 转换成 es5 , ts-loader、vue-loader

  • eslint-loader 配置 enforce: 'pre' 这个 loader 最先执行

  • css-loader、style-loader、postcss-loader、less-loader、sass-loader

  • file-loader 把文件转换成路径引入, url-loader(比file-loader多了小于多少的能转换成 base64)

  • image-loader

  • svg-sprite-loader 处理 svg

  • thread-loader 开启多进程 ,会在一个单独的 worker 池(worker pool)中运行

  • cache-loader 缓存一些性能开销比较大的 loader 的处理结果 plugin

  • html-webpack-plugin 将生成的 css,js 自动注入到 html 文件中,能对 html 文件压缩

  • copy-webpack-plugin 拷贝某个目录

  • clean-webpack-plugin 清空某个目录

  • webpack.HotModuleReplacementPlugin 热重载

  • webpack.DefinePlugin 定义全局变量

  • mini-css-extract-plugin 提取 CSS 到独立 bundle 文件。 extract-text-webpack-plugin

  • optimize-css-assets-webpack-plugin 压缩 css webpack5 推荐css-minimizer-webpack-plugin

  • purgecss-webpack-plugin 会单独提取 CSS 并清除用不到的 CSS(会有问题把有用的 css 删除)

  • uglifyjs-webpack-plugin ❌(不推荐) 压缩 js、多进程 parallel: true

  • terser-webpack-plugin 压缩 js, 可开启多进程压缩、推荐使用

module.exports = {
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        parallel: true, // 多进程压缩
      }),
    ],
  },
};
  • HardSourceWebpackPlugin 缓存

  • speed-measure-webpack-plugin 打包构建速度分析、查看编译速度

  • webpack-bundle-analyzer 打包体积分析

  • compression-webpack-plugin gzip 压缩 ———————————————— 版权声明:本文为CSDN博主「zh阿飞」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:blog.csdn.net/z1832729975…

模块打包原理知道吗?

Webpack 实际上为每个模块创造了一个可以导出和导入的环境,本质上并没有修改 代码的执行逻辑,代码执行顺序与模块加载顺序也完全一致。

说一下 Webpack 的热更新原理吧

Webpack 的热更新又称热替换(Hot Module Replacement),缩写为 HMR。 这个机制可以做到不用刷新浏览器而将新变更的模块替换掉旧的模块。

HMR的核心就是客户端从服务端拉去更新后的文件,准确的说是 chunk diff (chunk 需要更新的部分),实际上 WDS 与浏览器之间维护了一个 Websocket,当本地资源发生变化时,WDS 会向浏览器推送更新,并带上构建时的 hash,让客户端与上一次资源进行对比。客户端对比出差异后会向 WDS 发起 Ajax 请求来获取更改内容(文件列表、hash),这样客户端就可以再借助这些信息继续向 WDS 发起 jsonp 请求获取该chunk的增量更新。

后续的部分(拿到增量更新之后如何处理?哪些状态该保留?哪些又需要更新?)由 HotModulePlugin 来完成,提供了相关 API 以供开发者针对自身场景进行处理,像react-hot-loadervue-loader 都是借助这些 API 实现 HMR。


作者:童欧巴
链接:juejin.cn/post/684490… 来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

Babel原理

大多数JavaScript Parser遵循 estree 规范,Babel 最初基于 acorn 项目(轻量级现代 JavaScript 解析器) Babel大概分为三大部分:

  • 解析:将代码转换成 AST

    • 词法分析:将代码(字符串)分割为token流,即语法单元成的数组
    • 语法分析:分析token流(上面生成的数组)并生成 AST
  • 转换:访问 AST 的节点进行变换操作生产新的 AST

    • Taro就是利用 babel 完成的小程序语法转换
  • 生成:以新的 AST 为基础生成代码

HTTP

1. HTTP 和 HTTPS

1.http 和 https 的基本概念

http: 是一个客户端和服务器端请求和应答的标准(TCP),用于从 WWW 服务器传输超文本到本地浏览器的超文本传输协议。
https:是以安全为目标的 HTTP 通道,即 HTTP 下 加入 SSL 层进行加密。其作用是:建立一个信息安全通道,来确保数据的传输,确保网站的真实性。

2.http 和 https 的区别及优缺点?

  • http 是超文本传输协议,信息是明文传输,HTTPS 协议要比 http 协议安全,https 是具有安全性的 ssl 加密传输协议,可防止数据在传输过程中被窃取、改变,确保数据的完整性(当然这种安全性并非绝对的,对于更深入的 Web 安全问题,此处暂且不表)。
  • http 协议的默认端口为 80,https 的默认端口为 443。
  • http 的连接很简单,是无状态的。https 握手阶段比较费时,会使页面加载时间延长 50%,增加 10%~20%的耗电。
  • https 缓存不如 http 高效,会增加数据开销。
  • Https 协议需要 ca 证书,费用较高,功能越强大的证书费用越高。
  • SSL 证书需要绑定 IP,不能再同一个 IP 上绑定多个域名,IPV4 资源支持不了这种消耗。

3.https 协议的工作原理

客户端在使用 HTTPS 方式与 Web 服务器通信时有以下几个步骤:

  1. 客户端使用 https url 访问服务器,则要求 web 服务器建立 ssl 链接
  2. web 服务器接收到客户端的请求之后,会将网站的证书(证书中包含了公钥),传输给客户端
  3. 客户端和 web 服务器端开始协商 SSL 链接的安全等级,也就是加密等级。
  4. 客户端浏览器通过双方协商一致的安全等级,建立会话密钥,然后通过网站的公钥来加密会话密钥,并传送给网站。
  5. web 服务器通过自己的私钥解密出会话密钥
  6. web 服务器通过会话密钥加密与客户端之间的通信

TCP三次握手

  • 第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。

  • 第二次握手:服务器收到syn包并确认客户的SYN(ack=j+1),同时也发送一个自己的SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;

  • 第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。

TCP 四次挥手

  1. 客户端进程发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入FIN-WAIT-1(终止等待1)状态。 TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。

2)服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。

3)客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最 后的数据)。

4)服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。

5)客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2∗∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态

6)服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些


作者:Gaby
链接:juejin.cn/post/701659… 来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

HTTP 请求跨域问题

  1. 跨域的原理

    跨域,是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的。
    同源策略,是浏览器对 JavaScript 实施的安全限制,只要协议、域名、端口有任何一个不同,都被当作是不同的域。
    跨域原理,即是通过各种方式,避开浏览器的安全限制

  2. 解决方案

最初做项目的时候,使用的是jsonp,但存在一些问题,使用get请求不安全,携带数据较小,后来也用过iframe,但只有主域相同才行,也是存在些问题,后来通过了解和学习发现使用代理和proxy代理配合起来使用比较方便,就引导后台按这种方式做下服务器配置,在开发中使用proxy,在服务器上使用nginx代理,这样开发过程中彼此都方便,效率也高;现在h5新特性还有 windows.postMessage()

  • JSONP
    ajax 请求受同源策略影响,不允许进行跨域请求,而 script 标签 src 属性中的链 接却可以访问跨域的 js 脚本,利用这个特性,服务端不再返回 JSON 格式的数据,而是 返回一段调用某个函数的 js 代码,在 src 中进行了调用,这样实现了跨域。

    步骤:

    1. 去创建一个script标签

    2. script的src属性设置接口地址

    3. 接口参数,必须要带一个自定义函数名,要不然后台无法返回数据

    4. 通过定义函数名去接受返回的数据

//动态创建 script
var script = document.createElement('script');

// 设置回调函数
function getData(data) {
    console.log(data);
}

//设置 script 的 src 属性,并设置请求地址
script.src = 'http://localhost:3000/?callback=getData';

// 让 script 生效
document.body.appendChild(script);

  • JSONP 的缺点:
    JSON 只支持 get,因为 script 标签只能使用 get 请求; JSONP 需要后端配合返回指定格式的数据。

  • document.domain 基础域名相同 子域名不同

  • window.name 利用在一个浏览器窗口内,载入所有的域名都是共享一个window.name

  • CORS CORS(Cross-origin resource sharing)跨域资源共享 服务器设置对CORS的支持原理:服务器设置Access-Control-Allow-Origin HTTP响应头之后,浏览器将会允许跨域请求

  • proxy代理 目前常用方式,通过服务器设置代理

  • window.postMessage() 利用h5新特性window.postMessage()


作者:Gaby
链接:juejin.cn/post/701659… 来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

常见http状态码分类:

200响应成功

301永久重定向

302临时重定向

304资源缓存

403服务器禁止访问

404服务器资源未找到

500 502服务器内部错误

504 服务器繁忙

1xx Informational(信息状态码) 接受请求正在处理

2xx Success(成功状态码) 请求正常处理完毕

3xx Redirection(重定向状态码) 需要附加操作已完成请求

4xx Client Error(客户端错误状态码) 服务器无法处理请求

5xx Server Error(服务器错误状态码) 服务器处理请求出错

浏览器从输入url到渲染页面,发生了什么?从输入URL到页面加载的全过程

  • 首先在浏览器中输入URL

  • 查找缓存:浏览器先查看浏览器缓存-系统缓存-路由缓存中是否有该地址页面,如果有则显示页面内容。如果没有则进行下一步。

    • 浏览器缓存:浏览器会记录DNS一段时间,因此,只是第一个地方解析DNS请求;
    • 操作系统缓存:如果在浏览器缓存中不包含这个记录,则会使系统调用操作系统, 获取操作系统的记录(保存最近的DNS查询缓存);
    • 路由器缓存:如果上述两个步骤均不能成功获取DNS记录,继续搜索路由器缓存;
    • ISP缓存:若上述均失败,继续向ISP搜索。
  • DNS域名解析:浏览器向DNS服务器发起请求,解析该URL中的域名对应的IP地址。DNS服务器是基于UDP的,因此会用到UDP协议

  • 建立TCP连接:解析出IP地址后,根据IP地址和默认80端口,和服务器建立TCP连接

  • 发起HTTP请求:浏览器发起读取文件的HTTP请求,,该请求报文作为TCP三次握手的第三次数据发送给服务器

  • 服务器响应请求并返回结果:服务器对浏览器请求做出响应,并把对应的html文件发送给浏览器

  • 关闭TCP连接:通过四次挥手释放TCP连接

  • 浏览器渲染:客户端(浏览器)解析HTML内容并渲染出来,浏览器接收到数据包后的解析流程为:

    • 构建DOM树:词法分析然后解析成DOM树(dom tree),是由dom元素及属性节点组成,树的根是document对象
    • 构建CSS规则树:生成CSS规则树(CSS Rule Tree)
    • 构建render树:Web浏览器将DOM和CSSOM结合,并构建出渲染树(render tree)
    • 布局(Layout):计算出每个节点在屏幕中的位置
    • 绘制(Painting):即遍历render树,并使用UI后端层绘制每个节点。

GET和POST区别(高频)

1.GET在浏览器回退不会再次请求,POST会再次提交请求

2.GET请求会被浏览器主动缓存,POST不会,要手动设置

3.GET请求参数会被完整保留在浏览器历史记录里,POST中的参数不会

4.GET请求在URL中传送的参数是有长度限制的,而POST没有限制

5.GET参数通过URL传递,POST放在Request body中

6.GET参数暴露在地址栏不安全,POST放在报文内部更安全

7.GET一般用于查询信息,POST一般用于提交某种信息进行某些修改操作

8.GET产生一个TCP数据包;POST产生两个TCP数据包

Ge和post的选择: 1.私密性的信息请求使用post(如注册、登陆)。 2.查询信息使用get。

git

Git 常用命令

查看分支:git branch
创建分支:git branch
切换分支:git checkout
创建+切换分支:git checkout -b
合并某分支到当前分支:git merge
删除分支:git branch -d

前端优化

1、降低请求量:合并资源,减少HTTP请求数,minify/gzip压缩

2、加快请求速度:预解析DNS,减少域名数,并行加载

3、缓存:HTTP协议缓存请求,离线缓存manifest,离线数据缓存localStorage

4、渲染:JS/CSS优化,加载顺序,服务端渲染

项目优化

移除生产环境的控制台打印。方案很多,esling+pre-commit、使用插件自动去除,插件包括babel-plugin-transform-remove-console、uglifyjs-webpack-plugin、terser-webpack-plugin。最后选择了terser-webpack-plugin,脚手架vue-cli用这个插件来开启缓存和多线程打包,无需安装额外的插件,仅需在configureWebpack中设置terser插件的drop_console为true即可。最好还是养成良好的代码习惯,在开发基本完成后去掉无用的console,vscode中的turbo console就蛮好的。

第三方库的按需加载。echarts,官方文档里是使用配置文件指定使用的模块,另一种使用babel-plugin-equire实现按需加载。element-ui使用babel-plugin-component实现按需引入。

前后端数据交换方面,推动项目组使用蓝湖、接口文档,与后端同学协商,规范后台数据返回。

雅虎军规提到的,避免css表达式、滤镜,较少DOM操作,优化图片、精灵图,避免图片空链接等

性能问题:页面加载性能、动画性能、操作性能。Performance API,记录性能数据。

winter重学前端 优化技术方案:

缓存:客户端控制的强缓存策略

降低请求成本:DNS 由客户端控制,隔一段时间主动请求获取域名IP,不走系统DNS(完全看不懂)。TCP/TLS连接复用,服务器升级到HTTP2,尽量合并域名。

减少请求数:JS、CSS打包到HTML。JS控制图片异步加载、懒加载。小型图片使用data-uri。

较少传输体积:尽量使用SVG\gradient代替图片。根据机型和网络状况控制图片清晰度。对低清晰度图片使用锐化来提升体验。设计上避免大型背景图。

使用CDN加速,内容分发网络,是建立再承载网基础上的虚拟分布式网络,能够将源站内容缓存到全国或全球的节点服务器上。用户就近获取内容,提高了资源的访问速度,分担源站压力。

微信小程序

微信授权

调用接口获取登录凭证(code)。通过凭证进而换取用户登录态信息,包括用户在当前小程序的唯一标识(openid)、微信开放平台帐号下的唯一标识(unionid,若当前小程序已绑定到微信开放平台帐号)及本次登录的会话密钥(session_key)等。用户数据的加解密通讯需要依赖会话密钥完成

  1. 通过wx.login获取code,传递给后台,拿到后台返回的openidsession_key
wx.login({
    success: res => {
      console.log('code', res)
      if (res.code) {
        this.globalData.code = res.code
        //发起网络请求
        LoginService.loginCode(res.code).then((res) => {
          this.globalData.openId = res[1].openId;
          this.globalData.sessionKey = res[1].sessionKey;
        })
      } else {
        console.log('登录失败!' + res.errMsg)
      }
    }
  })

2.获取用户信息,使用button按钮,bindtap="getUserProfile"绑定事件,通过wx.getUserProfile来获取用户信息

//新
<button wx:if="{{canIUseGetUserProfile}}" bindtap="getUserProfile"> 获取头像昵称 </button>
<button wx:else open-type="getUserInfo" bindgetuserinfo="getUserInfo"> 获取头像昵称 </button>
Page({
  data: {
    userInfo: {},
    hasUserInfo: false,
    canIUseGetUserProfile: false,
  },
  onLoad() {
    if (wx.getUserProfile) {
      this.setData({
        canIUseGetUserProfile: true
      })
    }
  },
  getUserProfile(e) {
    // 推荐使用 wx.getUserProfile 获取用户信息,开发者每次通过该接口获取用户个人信息均需用户确认
    // 开发者妥善保管用户快速填写的头像昵称,避免重复弹窗
    wx.getUserProfile({
      desc: '用于完善会员资料', // 声明获取用户个人信息后的用途,后续会展示在弹窗中,请谨慎填写
      success: (res) => {
        this.setData({
          userInfo: res.userInfo,
          hasUserInfo: true
        })
      }
    })
  },
  getUserInfo(e) {
    // 不推荐使用 getUserInfo 获取用户信息,预计自2021年4月13日起,getUserInfo将不再弹出弹窗,并直接返回匿名的用户个人信息
    this.setData({
      userInfo: e.detail.userInfo,
      hasUserInfo: true
    })
  },
})

实现上拉加载,下拉刷新

onReachBottom上拉加载

onReachBottom: function () {
    let that = this
    if (!that.data.loadMore) {
      that.setData({
        loadMore: true, //加载中  
      });
      that.setData({
        currPage: that.data.currPage + 1 // 滑动到底部页数+1
      });

      let { totalPage, currPage } = that.data;
      if (totalPage == 1 || currPage > totalPage) {
        App.myToast('已加载全部');
        that.setData({
          loadMore: false
        })
        return;
      }
      // 加载更多,这里做下延时加载
      setTimeout(function () {
        that.getMsgNoticeList()
      }, 1000)

    }
  },

})

onPullDownRefresh下拉刷新

onPullDownRefresh: function () {
    this.setData({ // 还原数据
      totalPage: 1,
      currPage: 1,
      isInit:false
    });
    this.getMessageList();
    wx.stopPullDownRefresh();
  },

微信小程序之性能优化

1、控制项目包大小:小程序代码包大小不能超过2M,所以控制代码包大小尤为重要,减小项目代码包的措施有

1).清理无用代码

2).采用分包策略

3).压缩图片,使用适当图片格式

4).精简代码,去掉不必要的WXML结构和未使用的WXSS定义

2、图片优化

1).大图尽量存储在服务器端或者使用云存储进行链接式加载

2).多图片可采用懒加载方案

3、内存优化

1).离开页面时回收当前页面的定时器,释放内存资源

2).onPageScroll 事件回调必须使用节流函数,并在它的回调中避免使用 setData。部分场景尽量使用 IntersectionObserver API。

4、提升渲染性能

1).在进入页面就显示的数据可在onLoad 阶段就可以发起请求,不用等ready

2).请求结果放在缓存中, 下次接着用

3).减少setData次数

4).合并setData请求,减少通讯次数

5).列表的局部更新可采用setData进行局部刷新方法

6).使用自定义组件加载

7).合理使用wx:if和hidden