2023前端知识汇总

292 阅读1小时+

HTML和CSS

如何理解语义化标签?

A:语义化标签的主要目的就是发挥标签和属性的用途及作用,通过标签本身的意义合作来优化 HTML 文档结构。

常见的语义化标签?

A:header 标签、nav 标签、article 标签、aside 标签、section 标签、footer 标签

  • aside 元素:侧边栏、广告、导航条等其他类似的有别于主要内容的部分。
  • section 标签:一个 section 元素通常由内容和标题组成。

语义化的优点?

A:以下为语义化的优点:

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

CSS 选择器及优先级

选择器

  • id 选择器(#myid)
  • 类选择器(.myclass)
  • 属性选择器(a[rel="external"])
  • 伪类选择器(a:hover, li:nth-child)
  • 标签选择器(div, h1,p)
  • 相邻选择器(h1 + p)
  • 子选择器(ul > li)
  • 后代选择器(li a)
  • 通配符选择器(*)

优先级

!important >行内样式> ID 选择器「如:#header」> 类选择器「如:.foo」> 标签选择器「如:h1」 > 通配符选择器(*)

position 属性的值有哪些及其区别

  • position属性取值:static(默认)、relative、absolute、fixed、inherit。
  • float属性取值:none(默认)、left、right、inherit。
  • display属性取值:none、inline、inline-block、block、table相关属性值、inherit。
  • 固定定位 fixed: 元素的位置相对于浏览器窗口是固定位置,即使窗口是滚动的它也不会移动。Fixed 定 位使元素的位置与文档流无关,因此不占据空间。 Fixed 定位的元素和其他元素重叠。(脱离文档流)
  • 相对定位 relative: 如果对一个元素进行相对定位,它将出现在它所在的位置上。然后,可以通过设置垂直 或水平位置,让这个元素“相对于”它的起点进行移动。 在使用相对定位时,无论是 否进行移动,元素仍然占据原来的空间。因此,移动元素会导致它覆盖其它框。
  • 绝对定位 absolute: 绝对定位的元素的位置相对于最近的已定位父元素,如果元素没有已定位的父元素,那么它的位置相对于。absolute 定位使元素的位置与文档流无关,因此不占据空间。 absolute 定位的元素和其他元素重叠。(脱离文档流)
  • 粘性定位 sticky: 元素先按照普通文档流定位,然后相对于该元素在流中的 flow root(BFC)和 containing block(最近的块级祖先元素)定位。而后,元素定位表现为在跨越特定阈值前为相对定 位,之后为固定定位。
  • 默认定位 Static: 默认值。没有定位,元素出现在正常的流中(忽略 top, bottom, left, right 或者 z-index 声 明)。 inherit: 规定应该从父元素继承 position 属性的值。

BFC(块级格式化上下文)

1. 概念

  • 把 BFC 理解成一块独立的渲染区域,BFC 看成是元素的一种属性,当元素拥有了 BFC 属性后,这个元素就可以看做成隔离了的独立容器。
  • BFC 就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。反之也如此。

2. 实现 BFC 属性的方法:

  • 浮动元素,float 除 none 以外的值
  • 定位元素,position 的值不是 static 或者 relative。
  • display 为 inline-block 、table-cell、table-caption、table、table-row、table-row-group、
  • table-header-group、table-footer-group、inline-table、flow-root、flex 或 inline-flex、grid 或 >- inline-grid
  • overflow 除了 visible 以外的值(hidden,auto,scroll)
  • 根元素 <html> 就是一个 BFC

3. BFC的使用场景

  • 去除边距重叠现象(避免 margin 重叠)
  • 清除浮动(让父元素的高度包含子浮动元素)
  • 避免某元素被浮动元素覆盖
  • 避免多列布局由于宽度计算四舍五入而自动换行

box-sizing 属性

box-sizing 规定两个并排的带边框的框,语法为 box-sizing:content-box/border-box/inherit

  • content-box:宽度和高度分别应用到元素的内容框,在宽度和高度之外绘制元素的内边距和边框。【标准盒子模型】
  • border-box:为元素设定的宽度和高度决定了元素的边框盒。【IE 盒子模型】
  • inherit:继承父元素的 box-sizing 值。

ie 怪异盒子模型

IE 怪异盒模型:border-box = content + padding + border

image-20221107193822807

标准盒模型:content-box = content

image-20221107193828708

怎么解决父元素高度塌陷( 清除浮动)

1. 给父元素末尾添加一个空元素,并设置成清除浮动,即:

<div style="clear:both;"></div>

优点:通俗易懂,易于掌握

缺点:添加了无意义标签,不易于后期维护,违背了结构和表现分离的标准

2. 给父元素添加 overflow:auto;

3. 让父元素也浮动

缺点:影响整体页面布局,若父元素也有父元素呢?总不能一直浮动到 body 吧?

4. 使用 after 伪元素

给父元素添加一个类,来添加一个看不见的块元素,以实现清除浮动。

5. 最优解

首先我们说一个 CSS 的概念——BFC 块级格式上下文,简单来说就是只要样式或方法触发了 BFC 就可以防止高度塌陷。

让一个元素水平垂直居中

1. 绝对定位方法:不确定当前 div 的宽度和高度,

采用 left: 50%; top: 50%; transform :translate(-50%,-50%); 当前 div 的父级添加相对定位(position: relative;)

(未知宽高)
div{
position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%,-50%);
}


2. 绝对定位方法:确定了当前 div 的宽度,margin 值为当前 div 宽度一半的负值

已知宽度
div{
position: absolute;
width:120px;
  left: 50%;
  top: 50%;
  transform: translate(-50%,-50%);
  margin-left:-60px;
}

3. 绝对定位方法:绝对定位下 top left right bottom 都设置0 ,margin 设置为 auto

(已知宽高)
div{
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  top: 0;
  margin: auto;
}


4. flex 布局方法:当前 div 的父级添加 flex css 样式。

display: flex;
align-items: center;      //项目在竖轴的对齐方式
justify-content: center;  //主轴上的对齐方式

5. table-cell 实现水平垂直居中:table-cell middle center 组合使用

display: table-cell;            
vertical-align: middle;
text-align: center;

6. 绝对定位:calc() 函数动态计算实现水平垂直居中

隐藏页面中某个元素的方法(visibility: hidden

  • opacity:0; 继续占据空间,内容不可⻅。如果该元素已经绑定一些事件,如click 事件,那么点击该区域,也能触发点击事件的。
  • visibility:hidden; 继续占据空间,内容不可⻅ ;但是不会触发该元素已经绑定的事件;继承属性;(重绘)
  • display:none ; 不占据任何空间;⾮继承属性(回流 + 重绘)

页面布局

1. Flex 布局(弹性布局)

布局的传统解决方案,基于盒状模型,依赖 display 属性 + position 属性 + float 属性。

position属性取值: static(默认)、relative、absolute、fixed、inherit。

float属性取值: none(默认)、left、right、inherit。

display属性取值: none、inline、inline-block、block、table相关属性值、inherit。

容器的属性:

flex-direction: 决定主轴的方向(即子 item 的排列方法)flex-direction: row | row-reverse | column | column-reverse;

flex-wrap: 决定换行规则 flex-wrap: nowrap | wrap | wrap-reverse;

flex-flow: .box { flex-flow: || ; }

justify-content: 对其方式,水平主轴对齐方式

align-items: 对齐方式,竖直轴线方向, 项目在竖轴的对齐方式

align-content: 项目在多行容器竖轴上的对齐方式

缺点:易用性不够好。 它有很多概念:主轴、交叉轴、方向、align-item 、justify-content 、flex-start 、flex-end 、flex-center 等。

2. Rem 布局

rem 布局的本质是等比缩放,一般是基于宽度。

优点:可以快速适用移动端布局,字体,图片高度

缺点:

目前 ie 不支持,对 pc 页面来讲使用次数不多;

数据量大:所有的图片,盒子都需要我们去给一个准确的值;才能保证不同机型的适配;

在响应式布局中,必须通过 js 来动态控制根元素 font-size 的大小。也就是说 css 样式和 js 代码有一定的耦合性。且必须将改变 font-size 的代码放在 css 样式之前。

3. 百分比布局

通过百分比单位 " % " 来实现响应式的效果。通过百分比单位可以使得浏览器中的组件的宽和高随着浏览器的变化而变化,从而实现响应式的效果。

4. 浮动布局

当元素浮动以后可以向左或向右移动,直到它的外边缘碰到包含它的框或者另外一个浮动元素的边框为止。元素浮动以后会脱离正常的文档流,所以文档的普通流中的框就变的好像浮动元素不存在一样。

伪类和伪元素

1、定义

伪类---- 点击操作 ----是一个以冒号作为前缀,被添加到一个选择器末尾的关键字,当你希望样式在特定状态才被呈现到指定的元素时,你可以往元素的选择器后面加上对应的伪类。

伪元素 ---- after、before ----用于创建一些不在文档树中的元素,并为其添加样式。比如说,我们可以通过 ::before 来在一个元素前添加一些文本,并为这些文本添加样式。虽然用户可以看到这些文本,但是这些文本实际不在文档树中。

2、区别 – 他们是否创造了新的元素

伪类:通过在元素选择器上加入伪类改变元素状态。

伪元素:通过对元素的操作进行对元素的改变。

3、举例

  1. HTML&&CSS image-20221107213959262.png
  2. 效果

image-20221107214003145.png

4、详细说明

伪类:

:active,将样式添加到被激活的元素。

:focus,将样式添加到被选中的元素。

:hover,当鼠标悬浮在元素上方是,向元素添加样式。

:link,将特殊的样式添加到未被访问过的链接。

:visited,将特殊的样式添加到被访问的链接。

:first-child,将特殊的样式添加到元素的第一个子元素。

:lang,允许创作者来定义指定的元素中使用的语言。

伪元素:

:first-letter,将特殊的样式添加到文本的首字母。

:first-line,将特殊的样式添加到文本的首行。

:before,在某元素之前插入某些内容。

:after,在某元素之后插入某些内容。

image.png

transition

transition-property

设置过渡的属性,比如:width height background-color

transition-duration

设置过渡的时间,比如:1s 500ms

transition-timing-function

设置过渡的运动方式,常用有 linear(匀速)|ease(缓冲运动)

transition-delay

设置动画的延迟

transition:

property duration timing-function delay 同时设置四个属性

CSS 的可继承性

1、常用的css不可继承的属性

  • display:规定元素应该生成的框的类型
  • text-decoration:规定添加到文本的装饰
  • text-shadow:文本阴影效果
  • white-space:空符的处理
  • 盒子模型的属性:width、height、margin 、border、padding
  • 背景属性:background
  • 定位属性:float、clear、position、top、right、bottom、left、min-width、min-height、max-width、max-height、overflow、clip、z-index

2、常用的css可继承的属性:

  • font:组合字体
  • font-family:规定元素的字体系列
  • font-weight:设置字体的粗细
  • font-size:设置字体的尺寸
  • font-style:定义字体的风格
  • text-indent:文本缩进
  • text-align:文本水平对齐
  • line-height:行高
  • color:文本颜色
  • visibility:元素可见性
  • 光标属性:cursor

CSS 预处理器

  • CSS 预处理器是一种专门的编程语言,用来为 CSS 增加一些编程特性(CSS 本身不是编程语言)。 不需要考虑浏览器兼容问题,因为 CSS 预处理器最终编译和输出的仍是标准的 CSS 样式。
  • 可以在 CSS 预处理器中:使用变量、简单逻辑判断、函数等基本编程技巧。
  • 关于 CSS 预处理器:sass、less、stylus

行内元素/块级元素/空元素(空元素没有闭合标签 br)

CSS举例设置高宽独占一行
block块级address、center、h1~h6、hr、p、pre、ul、ol、dl、table、div、form可以
inline行内span、a、br、b、strong、img、input、textarea、select、sup、sub、em、del不可以不会
inline-block行内块img,input,textarea,select,button,canvas,svg可以不会

空元素:br hr img input link meta

href 和 src 的区别

href,表示超文本引用。用来建立当前元素和文档之间的链接。常用的有:link、a。例如:

<link href="reset.css" rel=”stylesheet“/>
<a href="javascript:;"></a>

src 指向的内容会嵌入到文档中当前标签所在的位置(src 用于替换当前元素)。常用的有:img、script、iframe。 当浏览器解析到该元素时,会暂停浏览器的渲染,直到该资源加载完毕。

<script src="script.js"></script> 
<img src="smiley.gif" alt="Smiley face">

获取表单元素的事件

根据id 获取元素

document.getElementById("id属性的值");

根据标签名字获取元素

document.getElementsByTagName("标签的名字");

根据name 属性的值获取元素

document.getElementsByName("name属性的值");

根据类样式的名字获取元素

document.getElementsByClassName("类样式的名字");

根据选择器获取元素

document.querySelector("选择器"); document.querySelectorAll("选择器");

html dom event 事件

    1. onblur:失去焦点
    2. onfocus:元素获得焦点。
    3. 加载事件:
        1. onload:一张页面或一幅图像完成加载。
    4. 鼠标事件:
        1. onmousedown    鼠标按钮被按下。
        2. onmouseup    鼠标按键被松开。
        3. onmousemove    鼠标被移动。
        4. onmouseover    鼠标移到某元素之上。
        5. onmouseout    鼠标从某元素移开。
    5. 键盘事件:
        1. onkeydown    某个键盘按键被按下。    
        2. onkeyup        某个键盘按键被松开。
        3. onkeypress    某个键盘按键被按下并松开。
    6. 选择和改变
        1. onchange    域的内容被改变。
        2. onselect    文本被选中。
    7. 表单事件:
        1. onsubmit    确认按钮被点击。
        2. onreset    重置按钮被点击。


按钮事件:

  1. onabort 图像的加载被中断。
  2. onblur 元素失去焦点。
  3. onchange 域的内容被改变。
  4. onclick 当用户点击某个对象时调用的事件句柄。
  5. ondblclick 当用户双击某个对象时调用的事件句柄。
  6. onerror 在加载文档或图像时发生错误。
  7. onfocus 元素获得焦点。
  8. onkeydown 某个键盘按键被按下。
  9. onkeypress 某个键盘按键被按下并松开。
  10. onkeyup 某个键盘按键被松开。
  11. onload 一张页面或一幅图像完成加载。
  12. onmousedown 鼠标按钮被按下。
  13. onmousemove 鼠标被移动。
  14. onmouseout 鼠标从某元素移开。
  15. onmouseover 鼠标移到某元素之上。
  16. onmouseup 鼠标按键被松开。
  17. onreset 重置按钮被点击。
  18. onresize 窗口或框架被重新调整大小。
  19. onselect 文本被选中。
  20. onsubmit 确认按钮被点击。

<img>titlealt 有什么区别

  • title: ⿏标滑动到元素上的时候显示
  • alt: 图⽚⽆法加载时显示

媒体查询

媒体查询是 CSS 样式表最重要的功能之一,所谓媒体查询指的就是根据不同的媒体类型(设备类型)和条件来区分各种设备(例如:电脑、手机、平板电脑、盲文设备等),并为它们分别定义不同的 CSS 样式。

em 和 rem 比较

单位特点
em (段落前空两格)相对单位 em 是相对于元素本身的字体大小的。在 CSS 中唯一例外的是 font-size 属性,它的 em 和 ex 值指的是相对父元素的字体大小。
ex相对于元素字体的 x-height, 这个 x-height 取自字符x的高度的意思。与 em 有所区别的是,当你改变字体 font-family 的时候,使用 em 单位的其大小不会受到影响,而ex 会根据字体重新计算出新的大小。
rem相对根元素进行计算的。只要在 html 元素上指定了其 font-size 大小,后面的元素都将使用这个大小作为基准进行计算。
vw/vhvw 是 viewport's width 的简写,表明它的值是根据视口的宽度计算而来的,换算关系是 1vw 等于百分之一的 window.innerWidth。与 vw 类似,vh 是根据视口高度计算出来的。

JS、TS、ES6

JS 中的 8 种数据类型及区别

  • 在 JS 中,我们已知有 5 种基本数据类型:StringNumberBooleanUndefinedNull
  • 当ES6问世,新增基本数据类型:Symbol(ES6) BigInt(ES10)
  • 引用数据类型:- object

一、Number 类型:

专门保存数字的类型,可用于进行数学计算等的数值。

二、String 类型:

专门用来保存字符串的类型;" ",用来存储字符串类型的文本。

三、Boolean 类型:

专门用来保存真或者假的类型,值二选一,true or false。

四、undefined 类型:

只有一个值 undefined,没有赋值的变量的默认值。

五、null类型:

不知向任何地址,手动赋值,清空内容等....

六、object / Symbol

Symbol 在 ES6 中新定义,符号类型是唯一的并且不可修改。Symbol 指的是独一无二的值。

什么是 Symbol?

引入 Symbol 的原因:

ES6 之前的对象属性名都是字符串,这容易造成属性名的冲突。

比如说你引入一个同事写的 JavaScript 代码,你往他 JavaScript 代码里面一个很复杂的对象里面添加新的属性方法,如果是用属性名还是使用字符串的方式,你加的方法就有可能和你同事加的方法重名了。

即使是传入相同的参数,生成的 Symbol 值也是不相等的,因为 Symbol 本来就是独一无二的意思。

    const co = Symbol('co')
    const ko = Symbol('ko')
    console.log(co === ko); // false

ES6 引入 Symbol 类型让对象的属性名可以有两种类型,一种是原来就有的字符串,另一种就是新增的 Symbol 类型。凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。

JS 中数据类型检测

image-20221107140954103.png

typeof -> typeof 只能准确判断除 null 以外的基本类型 + function

优点:能够快速区分基本数据类型 缺点:不能将Object、Array和Null区分,都返回object

  • 可以判断: typeof 只能准确判断除 null 以外的基本类型、function
  • 不可以判断:(Array, Error, null )-> object
console.log(typeof 1);               // number
console.log(typeof true);            // boolean
console.log(typeof 'mc');            // string
console.log(typeof Symbol)           // function
console.log(typeof function(){});    // function
console.log(typeof console.log());   // undefined
console.log(typeof []);              // object 
console.log(typeof {});              // object
console.log(typeof null);            // object
console.log(typeof undefined);       // undefined

instanceof -> instanceof 是用来判断数据是否是某个对象的实例,返回一个布尔值

  • 优点:能够区分Array、Object和Function,适合用于判断自定义的类实例对象
  • 缺点:Number,Boolean,String基本数据类型不能判断

instanceof 是用来 判断数据是否是某个对象的实例,返回一个布尔值。是不是原型链上的。

// 判断 obj 是否为 Object 的实例
function Person(name) {
 this.name = name
}
const p = new Person('sunshine')
p instanceof Person // true

// 这里的 p 是 Person 函数构造出来的,所以顺着 p 的原型链可以找到Object的构造函数
p.__proto__ === Person.prototype // true
p.__proto__.__proto__ === Object.prototype // true


console.log(1 instanceof Number);                    // false
console.log(true instanceof Boolean);                // false
console.log('str' instanceof String);                // false
console.log([] instanceof Array);                    // true
console.log(function(){} instanceof Function);       // true
console.log({} instanceof Object);                   // true

因为原型链继承的关系,instanceof 会把数组都识别为 Object 对象

用来检测某个实例对象的原型链上是否存在构造函数的 prototype 属性

constructor

  • 优点:对基本类型和引入类型都可以判断。
  • 缺点:无法判断 null 和 undefined ,而且 constructor 是可以修改的,会导致检查结果不准确。

Object.prototype.toString.call()

  • 优点:精准判断数据类型
  • 缺点:写法繁琐不容易记,推荐进行封装后使用
var toString = Object.prototype.toString;
console.log(toString.call(1));                      //[object Number]
console.log(toString.call(true));                   //[object Boolean]
console.log(toString.call('mc'));                   //[object String]
console.log(toString.call([]));                     //[object Array]
console.log(toString.call({}));                     //[object Object]
console.log(toString.call(function(){}));           //[object Function]
console.log(toString.call(undefined));              //[object Undefined]
console.log(toString.call(null));                   //[object Null]

var/cosnt/let 三者之间的区别

ES6 之前创建变量用的是 var,之后创建变量用的是 let/const

  • var 存在变量提升,而 const 和 let 不存在变量提升;没有块的概念,可以跨块访问, 不能跨函数访问。
  • let 不能重复定义 ; 关键字允许值的修改;只能在块作用域里访问,不能跨块访问,也不能跨函数访问。
  • const 不能重复定义 ; 关键字不允许的修改;用来定义常量,使用时必须初始化(即必须赋值),只能在块作用域里访问,且不能修改。
  • var是允许在相同作用域内重复声明同一个变量的,而let与const不允许这一现象。

image-20221107141154921.png

JS垃圾回收机制

项目中,如果存在大量不被释放的内存(堆/栈/上下文),页面性能会变得很慢。当某些代码操作不能被合理释放,就会造成内存泄漏。我们尽可能减少使用闭包,因为它会消耗内存。

浏览器垃圾回收机制/内存回收机制:

浏览器的Javascript具有自动垃圾回收机制(GC:Garbage Collecation),垃圾收集器会定期(周期性)找出那些不在继续使用的变量,然后释放其内存。

  • 标记清除:在js中,最常用的垃圾回收机制是标记清除:当变量进入执行环境时,被标记为“进入环境”,当变量离开执行环境时,会被标记为“离开环境”。垃圾回收器会销毁那些带标记的值并回收它们所占用的内存空间。
  • 谷歌浏览器:“查找引用”,浏览器不定时去查找当前内存的引用,如果没有被占用了,浏览器会回收它;如果被占用,就不能回收。
  • IE浏览器:“引用计数法”,当前内存被占用一次,计数累加1次,移除占用就减1,减到0时,浏览器就回收它。

优化手段:内存优化 ; 手动释放:取消内存的占用即可。

(1)堆内存:fn = null 【null:空指针对象】

(2)栈内存:把上下文中,被外部占用的堆的占用取消即可。

内存泄漏

在 JS 中,常见的内存泄露主要有 4 种,全局变量、闭包、DOM 元素的引用、定时器

setTimeout/Promise/Async/Await 的区别

1、setTimeout

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

2、Promise

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

image-20221107142916442.png

3、async/await

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

image-20221107142924196.png

异步编程的发展历程

一个任务分成两段,先执行一段,转而执行其他任务,等做好了准备转而执行第二段。

回调函数 -> Promise -> Generator -> async,await

1. 回调函数(callback)

函数 B 作为参数(函数引用)传递到另一个函数 A 中,并且这个函数 A 执行函数 B。我们就说函数 B 叫做回调函数。

image-20221107143015482.png

  • 优点: 解决了异步问题
  • 缺点: 回调地狱,多个回调函数嵌套的情况,使代码看起来十分混乱,不易于维护。

2. Promise

用于处理异步操作的,异步处理成功了就执行成功的操作,异步处理失败了就捕获错误或者停止后续操作。

image-20221107143020673.png

Api

Promise.prototype.then: 实例化后的Promise对象可以进行链式调用

Promise.prototype.catch : 捕获异步操作时出现的异常

Promise.all(): 将数组中所有的任务执行完成之后, 才执行.then 中的任务。全部为fulfilled时,它才会变成fulfilled,否则变成rejected。

Promise.race(): 一旦参数内有一个值的状态发生的改变,那么该Promise的状态就是改变的状态。

Promise.resolve(): 接受一个参数值,可以是普通的值,具有then()方法的对象和Promise实例.

Promise.reject(): 它接收一个参数值reason,即发生异常的原因

  • 优点:

可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数

  • 缺点:

无法取消 Promise;

错误需要通过回调函数来捕获;

当处于 pending 状态时,无法得知目前进展到哪一个阶段;

3. Generator

  • 在 function 关键字后加一个 *,那么这个函数就称之为 generator 函数
  • 函数体有关键字 yield,后面跟每一个任务,也可以有 return 关键字,保留一个数据
  • 通过 next 函数调用,几个调用,就是几个人任务执行

image-20221107143031215.png

image-20221107143053676.png

yield 后面的星号也跟生成器有关:

  • 优点: 没有了Promise 的一堆 then(),异步操作更像同步操作,代码更加清晰
  • 缺点: 不能自动执行异步操作,需要写多个 next() 方法,需要配合使用 Thunk 函数和 co 模块才能做到自动执行

4. async,await

(与 generator 有关) async await 是语法糖,内部是 generator+promise 实现 async 函数就是将 Generator 函数的星号(*)替换成 async,将 yield 替换成 await

(与 promise 有关)

放在一个函数前的 async 有两个作用:

使函数总是返回一个 promise

允许在这其中使用 await

promise 前面的 await 关键字能够使 JavaScript 等待,直到 promise 处理结束。然后:

如果它是一个错误,异常就产生了,就像在那个地方调用了 throw error 一样。

否则,它会返回一个结果,我们可以将它分配给一个值

image-20221107143132039.png

5. 为什么Async/Await更好?

1. 简洁

由示例可知,使用 Async/Await 明显节约了不少代码。我们不需要写 .then,不需要写匿名函数处理Promise 的 resolve 值,也不需要定义多余的 data 变量,还避免了嵌套代码。这些小的优点会迅速累计起来,这在之后的代码示例中会更加明显。

2. 错误处理

Async/Await让try/catch 可以同时处理同步和异步错误。在下面的 promise 示例中,try/catch 不能处理 JSON.parse 的错误,因为它在 Promise 中。我们需要使用 .catch,这样错误处理代码非常冗余。并且,在我们的实际生产代码会更加复杂。

3. 可读性

使用 async/await 编写可以大大地提高可读性:

4. 错误栈

Promise 链中返回的错误栈没有给出错误发生位置的线索。更糟糕的是,它会误导我们;错误栈中唯一的函数名为 callAPromise,然而它和错误没有关系。(文件名和行号还是有用的)。

然而,async/await 中的错误栈会指向错误所在的函数:

5. 调试

最后一点,也是非常重要的一点在于,async/await 能够使得代码调试更简单。

this 指向的五种情况

---- 谁调用指向谁

  1. 作为普通函数执行时,this 指向 window。
  2. 当函数作为对象的方法被调用时,this 就会指向该对象。
  3. 构造器调用,this 指向返回的这个对象。 构造函数中,嵌套超过一级及以上的函数,this指向的都是 window。
  4. 箭头函数 箭头函数的 this 绑定看的是 this 所在函数定义在哪个对象下,就绑定哪个对象。如果有嵌套的情况,则 this 绑定到最近的一层对象上。
  5. 基于 Function.prototype 上的 apply 、 call 和 bind 调用模式,这三个方法都可以显示的指定调用函数的 this 指向。

1、给元素绑定某事件。

事件触发时,回调函数里面的 this 指向的一般是该元素本身。具体情况可看下面代码。

image-20221107143243520.png

点击后的回调函数里面的 this 指向了该元素,也就是这个按钮,利用 this,给元素内容重新赋值。

image-20221107143247999.png

2、纯粹的普通函数调用

你调用的那个方法前面有没有点 “.”,点 “.” 前面是谁,你执行的那个方法里面的 THIS 指向的就是谁。如果函数前面没有点 “.”,就指向 window

image-20221107143257213.png 函数最普通用法,此时属于全局调用,函数内 this 指向全局对象 window。

image-20221107143302191.png

3、构造函数中的this指向问题

构造函数中,嵌套超过一级及以上的函数,this 指向的都是 window.

构造函数中的一级函数,this 指向通过构造函数 new 出来的实例(例子中的 person)

image-20221107143313785.png 构造函数中的二级(及以上)函数,this 指向的是 window

image-20221107143320670.png

4、箭头函数(=>)

箭头函数 this 的指向不会发生改变,也就是说在创建箭头函数时就已经确定了它的 this 的指向了;它的指向永远指向箭头函数外层的 this。

箭头函数中没有自身的 this,要想在里面写 this 就要看一下他的上下文中(创建这个箭头函数时)的 this 指向的是谁。

例子1:

image-20221107143344619.png

例子2:

image-20221107143347984.png

这个 obj 对象中的 this 指向的是 window,所以 getName 这个箭头函数中的 this 指向的也是 window。

5、call,apply,bind的this指向

基于 call,apply,bind,可以强行改变函数中的 this 指向,但是不能改变箭头函数里面的,因为箭头函数里面没有 this。

call/apply/bind 三者的区别的作用和区别?

call、apply、bind都是用来改变this指向的,

call和apply调用时候立即执行,bind调用返回新的函数。

当需要传递参数时候,call直接写多个参数,apply将多个参数写成数组。

bind在绑定时候需要固定参数时候,也是直接写多个参数。

1、call,apply,bind 的相同点:

  • 都是改变 this 指向的;
  • 第一个参数都是 this 要指向的对象;
  • 都可以利用后续参数传参;

2、call,apply,bind 的区别:

  • call 和 bind 的参数是依次传参,一一对应的;
  • 但 apply 只有两个参数,第二个参数为数组;
  • call 和 apply 都是对函数进行直接调用,而 bind 方法返回的仍是一个函数;

image-20221107143600985.png

EventLoop 事件循环

JS`是单线程的,为了防止一个函数执行时间过长阻塞后面的代码,所以会先将同步代码压入执行栈中,依次执行,将异步代码推入异步队列,异步队列又分为宏任务队列和微任务队列,因为宏任务队列的执行时间较长,所以微任务队列要优先于宏任务队列。微任务队列的代表就是,`Promise.then`,`MutationObserver`,宏任务的话就是`setImmediate setTimeout setInterval

事件循环是js引擎执行js的机制,用来实现js的异步特性。

事件循环的过程为:当执行栈空的时候,就会从任务队列中,取任务来执行。共分3步:

  • 取一个宏任务来执行。执行完毕后,下一步。
  • 取一个微任务来执行,执行完毕后,再取一个微任务来执行。直到微任务队列为空,执行下一步。
  • 更新UI渲染。

1、JS 是单线程 浏览器是多进程的

♥ JavaScript 的主要用途是与用户互动,以及操作 DOM。比如,假定 JavaScript 同时有两个线程,一个线程在某个 DOM 节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?

♥ 我们使用 Chrome 打开很多标签页不关,电脑会越来越卡,不说其他,首先就很耗 CPU。

2、宏任务和微任务的概念是什么?

同步:会立即执行的任务。直接进入到主线程中执行。(主执行栈:后进先出)

异步:(先同步后异步)不会立即执行的任务(宏任务与微任务) (任务队列:先进先出)

宏任务(macrotask):是指消息队列中的等待被主线程执行的事件,宏任务执行时都会重新创建栈,然后调用宏任务中的函数,栈也会随着变化,但宏任务执行结束时,栈也会随之销毁。 类型:script,setTimeout,setInterval, setImmediate,I/O,UI rendering

微任务(microtask):可以把微任务看成是一个需要异步执行的函数,执行时机是在主函数执行结束之后、当前宏任务结束之前。 类型:原生 Promise.then()、process.nextTick、MutationObserver

注:

执行栈 -> 微任务队列(清空) -> 一个宏任务队列入执行栈 -> 微任务队列(清空) -> 一个宏任务队列入执行栈 -> 微任务队列(清空) -> … …

宏任务 setTimeout setInterval setImmediate 也有先后顺序,先执行 setTimeout,后执行 setImmediate

微任务 promise.then() process.nextTick() 有先后顺序,promise.then() 后执行

3、事件循环(Event Loop)

1)所有的同步任务都在主线程上执行,行成一个执行栈。

2)除了主线程之外,还存在一个任务列队,只要异步任务有了运行结果,就在任务列队中植入一个时间标记。

3)主线程完成所有任务(执行栈清空),就会读取任务列队,、Event Loop 把微任务队列执行清空。

4)微任务队列清空后,进入宏任务队列,取队列的第一项任务放入 Stack(栈)中执行,执行完成后,查看微任务队列是否有任务,有的话,清空微任务队列。重复 4,继续从宏任务中取任务执行,执行完成之后,继续清空微任务,如此反复循环,直至清空所有的任务。

image-20221107143650811.png

image-20221213155921711

image-20221107143701169.png

防抖节流

防抖【一定时间连续触发,只在最后执行一次】

多次触发事件,事件处理函数只能执行一次,并且是在触发操作结束时执行。也就是说,当一个事件被触发准备执行事件函数前,会等待一定的时间(这时间是码农自己去定义的,比如 1 秒),如果没有再次被触发,那么就执行,如果被触发了,那就本次作废,重新从新触发的时间开始计算,并再次等待 1 秒,直到能最终执行!

/**
 * 防抖函数  一个需要频繁触发的函数,在规定时间内,只让最后一次生效,前面的不生效
 * @param fn要被防抖的函数
 * @param delay规定的时间
 */
function debounce(fn, delay) {
    //记录上一次的延时器
    var timer = null;
    return function () {
       //清除上一次的演示器
        clearTimeout(timer);
        //重新设置新的延时器
        timer = setTimeout(()=>{
            //修正this指向问题
            fn.apply(this);
        }, delay); 
    }
}
document.getElementById('btn').onclick = debounce(function () {
    console.log('按钮被点击了' + Date.now());
}, 1000);

节流

事件触发后,规定时间内,事件处理函数不能再次被调用。也就是说在规定的时间内,函数只能被调用一次,且是最先被触发调用的那次。

/**
 * 节流函数 一个函数执行一次后,只有大于设定的执行周期才会执行第二次。有个需要频繁触发的函数,出于优化性能的角度,在规定时间内,只让函数触发的第一次生效,后面的不生效。
 * @param fn要被节流的函数
 * @param delay规定的时间
 */
function throttle(fn, delay) {
    //记录上一次函数触发的时间
    var lastTime = 0;
    return function(){
        //记录当前函数触发的时间
        var nowTime = Date.now();
        if(nowTime - lastTime > delay){
            //修正this指向问题
            fn.call(this);
            //同步执行结束时间
            lastTime = nowTime;
        }
    }
}

document.onscroll = throttle(function () {
    console.log('scllor事件被触发了' + Date.now());
}, 200); 


闭包使用场景

1、闭包的概念

  • 闭包是指有权访问另一个函数作用域中的变量的函数
  • 什么是:闭包 =『函数』和『函数体内可访问的变量总和』
  • 稍全面的回答: 在 js 中变量的作用域属于函数作用域,在函数执行完后,作用域就会被清理,内存也会随之被回收,但是由于闭包函数是建立在函数内部的子函数,由于其可访问上级作用域,即使上级函数执行完,作用域也不会随之销毁,这时的子函数(也就是闭包),便拥有了访问上级作用域中变量的权限,即使上级函数执行完后作用域内的值也不会被销毁。

2、闭包的特性:

  1. 内部函数可以访问定义他们外部函数的参数和变量。(作用域链的向上查找,把外围的作用域中的变量值存储在内存中而不是在函数调用完毕后销毁)设计私有的方法和变量,避免全局变量的污染。
  • 闭包是密闭的容器,类似于 set、map 容器,存储数据的
  • 闭包是一个对象,存放数据的格式为 key-value 形式
  1. 函数嵌套函数
  2. 本质是将函数内部和外部连接起来。优点是可以读取函数内部的变量,让这些变量的值始终保存在内存中,不会在函数被调用之后自动清除

3、闭包形成的条件:

  • 函数的嵌套
  • 内部函数引用外部函数的局部变量,延长外部函数的变量生命周期

4、闭包的用途:

作用:内部函数总是可以访问其所在的外部函数中声明的参数和变量

  • 模仿块级作用域
  • 保护外部函数的变量 能够访问函数定义时所在的词法作用域(阻止其被回收)
  • 封装私有化变量
  • 创建模块

5、闭包应用场景

  • 闭包的两个场景,闭包的两大作用:保存/保护。
  • 在开发中, 其实我们随处可见闭包的身影, 大部分前端 JavaScript 代码都是“事件驱动”的。
  • 在开发中, 其实我们随处可见闭包的身影, 大部分前端JavaScript 代码都是“事件驱动”的,即一个事件绑定的回调方法; 发送ajax请求成功|失败的回调;setTimeout的延时回调;或者一个函数内部返回另一个匿名函数,这些都是闭包的应用。

1. setTimeout 的延时回调:

原生的 setTimeout 传递的第一个函数不能带参数,通过闭包可以实现传参效果。

function f1(a) {
    function f2() {
        console.log(a);
    }
    return f2;
}
var fun = f1(1);
setTimeout(fun,1000);//一秒之后打印出1

2. 函数防抖

在事件被触发 n 秒后再执行回调,如果在这 n 秒内又被触发,则重新计时。

实现的关键就在于 setTimeout 这个函数,由于还需要一个变量来保存计时,考虑维护全局纯净,可以借助闭包来实现。

function debounce(fn,delay){
    let timer = null
    //借助闭包
    return function() {
        if(timer){
            clearTimeout(timer) //进入该分支语句,说明当前正在一个计时过程中,并且又触发了相同事件。所以要取消当前的计时,重新开始计时
            timer = setTimeOut(fn,delay)
        }else{
            timer = setTimeOut(fn,delay) // 进入该分支说明当前并没有在计时,那么就开始一个计时
        }
    }
}

3. 封装私有变量

如下面代码:用 js 创建一个计数器

1.一个事件绑定的回调方法

2.发送 ajax 请求成功|失败的回调

3.或者一个函数内部返回另一个匿名函数。

4. 闭包的优点:

1、延长局部变量的生命周期,让父函数中变量的值始终保存在内存中。

2、创建一个安全的环境,保证内部代码不受到外部的干涉,(实现私有成员,对外只暴露几个接口)。

5. 闭包缺点:

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

如何理解闭包,为什么有这种特性?为什么需要闭包?闭包的缺点?

对闭包的理解:

  1. 什么是闭包?函数和函数内部能访问到的变量的总和,就是一个闭包。
  2. 如何生成闭包? 函数嵌套 + 内部函数被引用。
  3. 闭包作用?隐藏变量,避免放在全局有被篡改的风险。
  4. 使用闭包的注意事项?不用的时候解除引用,避免不必要的内存占用。

为什么有闭包的这种特性:如果形成闭包,外部函数执行完后,其中的局部变量可能被内部函数使用,所以不能销毁,因此内部函数能一致访问到这些局部变量,直到引用解除。

为什么需要闭包?隐藏变量,避免放在全局有被篡改的风险。 闭包的缺点:使用时候不注意的话,容易产生内存泄漏。

作用域与作用域链

作用域 就是变量与函数的可访问范围。

作用域链 的作用是保证执⾏环境⾥有权访问的变量和函数是有序的,作⽤域链的变量只能向上访问。变量到创建该变量的函数的作用域中取值。但是如果在当前作用域中没有查到,就会向上级作用域去查,直到查到全局作用域,这么一个查找过程形成的链条就叫做作用域链。

  • 1、全局作用域:代码在程序的任何地方都能被访问,window 对象的内置属性都拥有全局作用域。
  • 2、函数作用域:在固定的代码片段才能被访问。

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

原型与原型链

  • 原型 prototype(破 肉 头 太 pe)
  • 内置属性 proto (破 肉 头)

原型关系:

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

原型: 在 JS 中,每当定义一个对象(函数也是对象)时,对象中都会包含一些预定义的属性。其中每个函数对象都有一个prototype 属性,这个属性指向函数的原型对象

原型链: 函数的原型链对象constructor默认指向函数本身,原型对象除了有原型属性外,为了实现继承,还有一个原型链指针__proto__,该指针是指向上一层的原型对象,而上一层的原型对象的结构依然类似。因此可以利用__proto__一直指向Object的原型对象上,而Object原型对象用Object.prototype.__ proto__ = null表示原型链顶端。如此形成了js的原型链继承。同时所有的js对象都有Object的基本防范

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

构造函数有个prototype对象(原型),该对象有个“constructor”属性,指向构造函数。 每个对象都有一个“proto”属性,指向它的构造函数的“prototype”属性。

构造函数的prototype对象,也有一个“proto”对象,它指向Object的prototype对象。 当我们访问对象中的属性时候,会先访问该对象中的本身的属性(私有属性),如果访问不到,会查找对象的“proto”指向的构造函数的prototype对象,如果其中有要访问的属性,就使用该值,否则继续访问prototype的“proto”,在其中查找要访问属性。这样一直上溯到Object对象。这个就是“原型链”。

1、构造函数:

function Person(name, age, gender) {
        this.name = name
        this.age = age
        this.gender = gender
        this.sayName = function () {
            alert(this.name);
        }
    }
    var per = new Person("孙悟空", 18, "男");
    function Dog(name, age, gender) {
        this.name = name
        this.age = age
        this.gender = gender
    }
    var dog = new Dog("旺财", 4, "雄")
    console.log(per);//当我们直接在页面中打印一个对象时,事件上是输出的对象的toString()方法的返回值
    console.log(dog);

上面代码中,Person 、Dog 就是构造函数。为了与普通函数区别,构造函数名字的第一个字母通常大写。

new 命令的作用,就是执行构造函数,返回一个实例对象。per、dog 就是一个实例对象。

构造函数的特点有两个:

函数体内部使用了 this 关键字,代表了所要生成的对象实例。

生成对象的时候,必须使用 new 命令。

每创建一个 Person 构造函数,在 Person 构造函数中,为每一个对象都添加了一个 sayName 方法,也就是说构造函数每执行一次就会创建一个新的 sayName 方法。这样就导致了构造函数执行一次就会创建一个新的方法,执行 10000 次就会创建 10000 个新的方法,而 10000 个方法都是一模一样的,为什么不把这个方法单独放到一个地方,并让所有的实例都可以访问到呢?这就需要原型(prototype)。

2、New的实现机制

首先创建了一个新的空对象

设置原型,将对象的原型设置为函数的 prototype 对象。

让函数的 this 指向这个对象,执行构造函数的代码(为这个新对象添加属性)

判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。

3、原型

在 JavaScript 中,每当定义一个函数数据类型(普通函数、类)时候,都会天生自带一个 prototype 属性,这个属性指向函数的原型对象,并且这个属性是一个对象数据类型的值。

image-20221107145147386.png

4、原型链

1、proto 和 constructor

每一个对象数据类型(普通的对象、实例、prototype......)也天生自带一个属性__proto__,属性值是当前实例所属类的原型(prototype)。原型对象中有一个属性 constructor, 它指向函数对象。

image-20221107145155539.png

 function Person() {}
    var person = new Person()
    console.log(person.__proto__ === Person.prototype)//true
    console.log(Person.prototype.constructor===Person)//true
    //顺便学习一个ES5的方法,可以获得对象的原型
    console.log(Object.getPrototypeOf(person) === Person.prototype) // true

2、什么是原型链?

当我们访问对象的一个属性或方法时,它会先在对象自身中寻找,如果有则直接使用,如果没有则会去原型对象中寻找,如果找到则直接使用。如果没有则去原型的原型中寻找,直到找到 Object 对象的原型,Object 对象的原型没有原型,如果在 Object 原型中依然没有找到,则返回 undefined

image-20221107145217376.png

5、原型链快问快答!

Q: 原型链的终点?

“原型链的终点是 null” Object.prototype.proto=== null,

Q: 什么是原型链?prototype

而原型链就是实例对象和原型对象之间的链接。

Q: 为什么要使用原型链呢?

为了实现继承,简化代码,实现代码重用!只要是这个链条上的内容,都可以被访问和使用到! 避免了代码冗余,公用的属性和方法,可以放到原型对象中,这样,通过该构造函数实例化的所有对象都可以使用该对象的构造函数中的属性和方法! 3.减少了内存占用

Q: 原型链的特点

每个继承父函数的实例对象都包含一个内部属性 proto。该属性包含一个指针,指向父函数的 prototype。若父函数的原型对象的 proto 属性为再上一层函数。在此过程中就形成了原型链

Q: 总结

每个函数(class)都有 prototype 属性

每个对象(实例)都有 proto 属性,指向了创建该对象的构造函数的原型。

实例的 proto 指向对应 class 的 prototype

对象可以通过 proto 来寻找属于该对象的属性,proto 将对象连接起来组成了原型链。

js有哪些继承方法?

主要有几种

  1. 原型继承
  2. 借用构造函数继承
  3. 组合继承
  4. 寄生组合式继承

null和undefined的区别

本身都表示“没有”,但null表示引用类型的对象为空,undefined则表示变量未定义。

在相等判断时候,null和undefined是相等的

但null和undefined在很多方面有区别。

含义不同

null表示对象空指针,undefined表示变量未定义。

类型不同

typeof null // 'object'
typeof undefined // 'undefined'


Number(null) // 0
Number(undefined) // NaN

应用场景不同

null:

作为对象原型链的终点

undefined:

定义了变量,没有初始化,默认是undefined

函数不return,或者return后面没有值,则函数默认返回undefined

函数参数如果不传,默认是undefined

this指向、new关键字

this对象是是执行上下文中的一个属性,它指向最后一次调用这个方法的对象,在全局函数中,this等于window,而当函数被作为某个对象调用时,this等于那个对象。在实际开发中,this 的指向可以通过四种调用模式来判断。

  1. 函数调用,当一个函数不是一个对象的属性时,直接作为函数来调用时,this指向全局对象。
  2. 方法调用,如果一个函数作为一个对象的方法来调用时,this指向这个对象。
  3. 构造函数调用,this指向这个用new新创建的对象。
  4. 第四种是 apply 、 call 和 bind 调用模式,这三个方法都可以显示的指定调用函数的 this 指向。apply接收参数的是数组,call接受参数列表,`` bind方法通过传入一个对象,返回一个 this 绑定了传入对象的新函数。这个函数的 this指向除了使用new `时会被改变,其他情况下都不会改变。

*new*

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

普通函数和箭头函数区别

区别:

  1. 箭头函数在一些情况下书写更简洁(如只有一个参数、函数体直接返回值时候)。

  2. 箭头函数没有自己的this,箭头函数内的this变量指向外层非箭头函数的函数的this,或者将该箭头函数作为属性的对象。箭头函数不能通过 bind、call、apply 来改变 this 的值,但依然可以调用这几个方法(只是 this 的值不受这几个方法控制)

  3. 箭头函数内部不可以使用arguments对象。

  4. 最大的区别在于 this 的指向问题(严格来说,其实箭头函数没有自己的 this)普通函数中的 this 是动态的,而箭头函数中的 this 指向的是紧紧包裹箭头函数的那个对象(定义时决定的)。

  5. 箭头函数不能用 new 来创建构造函数的实例,普通函数可以(因为箭头函数创建的时候程序不会为它创建 construct 方法,也就是没有构造能力,用完就丢掉了,不像普通函数重复利用,因此也不需要构造函数原型,也就是不会自动生成 prototype 属性)

  6. 箭头函数不可以当做构造函数。

为什么不能用作构造函数:

构造函数是通过new关键字来生成对象实例,生成对象实例的过程也是通过构造函数给实例绑定this的过程,而箭头函数没有自己的this。创建对象过程,new 首先会创建一个空对象,并将这个空对象的__proto__指向构造函数的prototype,从而继承原型上的方法,但是箭头函数没有prototype。因此不能使用箭头作为构造函数,也就不能通过new操作符来调用箭头函数。

forin forof的区别

1、for...of : 数组

  • 循环用来遍历数组
  • 会遍历获取对象的 键值 value;
  • 只遍历当前对象
  • 返回数组下标对应的属性值

image-20221213171435256

对于外界给其作为对象而添加的属性值则不会输出(这里 name 的 jake 没有输出)

for(let value of p){ // 会遍历获取对象的键值 console.log(value); }

2、for...in : 对象

  • 适用于遍历对象而产生的,不适用于遍历数组。

  • 获取对象的 键名 key;

  • 会遍历整个对象的原型链;

  • 返回数组中所有可枚举的属性名;

image-20221213171354396

这里之所以输出 name,是因为 for-in 本身遍历的是属性名,而数组本身是一个对象,属性名即下标,所以输出的都是下标,也自然输出了一个属性名 name。

== 和 === 的区别

  • == 表示相等 (值相等)

  • === 表示恒等(类型和值都要相等)

  • js 在比较的时候如果是 == 会先做类型转换,再判断值得大小,如果是 === 类型和值必须都相等。

数组的操作方法

1.shift

删除原数组第一项,并返回删除元素的值;如果数组为空则返回 undefined

2.unshift

将参数添加到原数组开头,并返回数组的长度

3.pop

删除原数组最后一项,并返回删除元素的值;如果数组为空则返回 undefined

4.push

将参数添加到原数组末尾,并返回数组的长度

5.concat()

可以将两个数组合并在一起,如果是使用ES6语法也可以用扩展运算符 … 来代替

6.splice

(start,deleteCount,val1,val2,…):从start位置开始删除deleteCount项,并从该位置起插入。原数组改变

var a = [1,2,3,4,5];
var b = a.splice(2,2,7,8,9); //a:[1,2,7,8,9,5] b:[3,4]

7.reverse

将数组反序

8.sort(orderfunction)

按指定的参数对数组进行排序

9.slice(start,end)

可以截取出数组某部份的元素为一个新的数组,有两个必填的参数,第一个是起始位置,第二个是结束位置(操作时数字减 1)原数组不改变

newArr = arr.slice(0, 5) // newArr [0,1,2,3,4]

10.join(separator)

将数组的元素组起一个字符串,以 separator 为分隔符,省略的话则用默认用逗号为分隔符

var a = [1,2,3,4,5];
var b = a.join("|"); //a:[1,2,3,4,5] b:"1|2|3|4|5"

11.Array 和 Object 的特性

//Array:
/*新建:*/var ary = new Array(); 或 var ary = [];
/*增加:*/ary.push(value);
/*删除:*/delete ary[n];
/*遍历:*/for ( var i=0 ; i < ary.length ; ++i ) ary[i];
//Object:
/*新建:*/var obj = new Object(); 或 var obj = {};
/*增加:*/obj[key] = value; (key为string)
/*删除:*/delete obj[key];
/*遍历:*/for ( var key in obj ) obj[key];
Object 完全可以作为一个集合来使用



12.map()

let a = [1,2,3,4,5,6,7,8];
let b = a.map(e => {
    return e + 10;
});
console.log(b); // [11, 12, 13, 14, 15, 16, 17, 18]


功能:map 作用是映射调用此方法的数组。按照原始数组元素顺序依次处理元素。

参数:(见下面代码)

返回值:不会改变原始数组,返回新数组,长度和原始数组一致

Array.map((item,index,arr)=>{
 //item => 数组的每一项
 //index => 数组每一项的索引
 //arr => 原数组
})
实例:
let arr = [1,2,3]
let newArr = arr.map((item,index,arr)=>{
         return item+1
})
//newArr = [2,3,4]

数组转化为字符串

toString()

将数组转换成一个字符串

var a = [1,2,3,4,5,6,7,8,9,0];  //定义数组
var s = a.toString();  //把数组转换为字符串
console.log(s);  //返回字符串 “1,2,3,4,5,6,7,8,9,0”
console.log(typeof s);  //返回字符串 string,说明是字符串类型

toLocalString()

把数组转换成本地约定的字符串

join()

指定分隔符:可以传递一个参数作为分隔符来连接每个元素。如果省略参数,默认使用逗号作为分隔符

var a = [1,2,3,4,5];  //定义数组
var s = a.join("==");  //指定分隔符
console.log(s);  //返回字符串 “1==2==3==4==5”

♥ split() 方法是 String 对象方法,与 join() 方法操作正好相反。

第 1 个参数为分隔符,指定从哪儿进行分隔的标记;第 2 个参数指定要返回数组的长度。

事件冒泡 和 事件委托和事件捕获

事件委托是最好理解的那个,我们要给每一个按钮绑定一个事件,但是这样遍历,太消耗性能了,于是我们直接给父元素绑定即可完成。

事件冒泡,就是点击最里面的元素,会触发父元素的方法。

事件捕获(event capturing):当鼠标点击或者触发 dom 事件时(被触发 dom 事件的这个元素被叫作事件源),浏览器会从根节点 =>事件源(由外到内)进行事件传播。在捕获的过程中,最外层(根)元素的事件先被触发,然后依次向内执行,直到触发最里面的元素(事件源)。

阻止事件冒泡,阻止默认事件

event.stopPropagation() :阻止事件的冒泡方法

event.preventDefault() :阻止默认事件的方法

return false :同时阻止事件冒泡也会阻止默认事件

nodejs 和传统 js 有什么区别

  • (前端 JS) —— 脚本语言 —— 前端浏览器
  • (后端 Node.js)—— 让 JavaScript 运行在服务端的开发平台

♥ JavaScript: JavaScript = ECMAScript + DOM + BOM

ECMAScript(语言基础,如:语法、数据类型结构以及一些内置对象)

DOM(一些操作页面元素的方法)

BOM(一些操作浏览器的方法)

♥ Node.js:

ECMAScript(语言基础,如:语法、数据类型结构以及一些内置对象)

OS(操作系统) 后台语音有操作系统的能力,于是扩展 os

file(文件系统) 需要有操作文件的能力,于是扩展出 file 文件系统、

net(网络系统) 需要操作网络,于是扩展出 net 网络系统

database(数据库)需要操作数据,于是要扩展出 database 的能力

js 正则表达式里 ?/*/+ 代表什么含义

\* 匹配前一个表达式 0 次或多次
+ 匹配前面一个表达式 1 次或者多次。等价于 {1,}
? 匹配前面一个表达式 0 次或者 1 次。等价于{0,1}

js 中取整办法

  • 向上取整:ceil

  • 向下取整:floor

  • 四舍五入:round

  • 固定精度:toFixed 100.456001.toFixed(2);// 100.46

  • 固定长度:toPrecision 99.456001.toPrecision(5); // 99.456

  • 取整:parseInt、位运算 parseInt('100.78');// 123

函数提升和变量提升

变量提升即将变量声明提升到它所在作用域的最开始的部分。

函数提升会把 fun 函数提升到最前面,所以我们在 fun 函数定义之前就可以使用 fun 函数。

原生ajax

ajax是一种异步通信的方法,从服务端获取数据,达到局部刷新页面的效果。过程:

  1. 创建XMLHttpRequest对象;
  2. 调用open方法传入三个参数 请求方式(GET/POST)、url、同步异步(true/false);
  3. 监听onreadystatechange事件,当readystate等于4时返回responseText;
  4. 调用send方法传递参数。

事件冒泡、捕获(委托)

  • 事件冒泡指在在一个对象上触发某类事件,如果此对象绑定了事件,就会触发事件,如果没有,就会向这个对象的父级对象传播,最终父级对象触发了事件。
  • 事件委托本质上是利用了浏览器事件冒泡的机制。因为事件在冒泡过程中会上传到父节点,并且父节点可以通过事件对象获取到目标节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件,这种方式称为事件代理event.stopPropagation() 或者 ie下的方法 event.cancelBubble = true; //阻止事件冒泡

浏览器

从输入 URL 到页面加载的全过程

1 面试套话:

“检查缓存、先解析 URL、然后 DNS 域名解析、再发起 HTTP 请求建立 TCP 连接、服务端响应返回页面资源进行渲染、然后断开 TCP 连接”

三个方面:
网络篇:
         构建请求
         查找强缓存
         DNS解析
         建立TCP连接(三次握手)
         发送HTTP请求(网络请求后网络响应)
浏览器解析篇:
        解析html构建DOM树
        解析css构建CSS树、样式计算
        生成布局树(Layout Tree)
浏览器渲染篇:
            建立图层树(Layer Tree)
            生成绘制列表
            生成图块并栅格化
            显示器显示内容
            最后断开连接:TCP 四次挥手
            (浏览器会将各层的信息发送给GPU,GPU会将各层合成,显示在屏幕上)

2 详细过程:

  • (找)DNS 解析 -> 寻找哪台机器上有你需要资源 根据网址找 IP
  • TCP 连接 -> 客户端和服务器,TCP 作为其传输层协议
  • 发送 HTTP 请求 -> HTTP 报文是包裹在 TCP 报文中发送的 请求行,请求报头,请求正文
  • 服务器处理请求并返回 HTTP 报文 -> 状态码,响应报头和响应报文。
  • 浏览器解析渲染页面 -> 浏览器在收到 HTML,CSS,JS 文件后依次渲染
  • 连接结束 -> 断开 TCP 连接 四次挥手

3 具体实现:

1)DNS解析 -> 寻找哪台机器上有你需要资源

所以互联网设计者需要在用户的方便性与可用性方面做一个权衡,这个权衡就是一个网址到IP地址的转换,这个过程就是DNS解析。

image.png

. -> .com. -> google.com. -> www.google.com.

2)TCP连接

HTTP协议是使用TCP作为其传输层协议的,当TCP出现瓶颈时,HTTP也会受到影响。

TCP提供一种可靠的传输,这个过程涉及到三次握手,四次挥手 -> 客户端和服务器

建立连接的过程是利用客户服务器模式,假设主机A为客户端,主机B为服务器端。

3)发送HTTP请求

连接建立成功之后,浏览器会构建请求行、cookie等数据附加到请求头中,发给服务器,服务器接受请求并解析

HTTP报文是包裹在TCP报文中发送的,服务器端收到TCP报文时会解包提取出HTTP报文。

发送HTTP请求的过程就是构建HTTP请求报文并通过TCP协议中发送到服务器指定端口。

♥ 请求报文由请求行,请求报头,请求正文组成。

请求行:请求行的格式为Method Request-URL HTTP-Version CRLFeg: GET index.html HTTP/1.1常用的方法有:GET,POST,PUT,DELETE,OPTIONS,HEAD。

请求报头:请求报头允许客户端向服务器传递请求的附加信息和客户端自身的信息。

请求正文:当使用POST, PUT等方法时,通常需要客户端向服务器传递数据。这些数据就储存在请求正文中。

4)服务器处理请求并返回HTTP报文

♥ HTTP响应报文也是由三部分组成:状态码,响应报头和响应报文。

状态码:404之类的

响应报头:常见的响应报头字段有: Server, Connection...。

响应报文:服务器返回给浏览器的文本信息,通常HTML, CSS, JS, 图片等文件就放在这一部分。

5)浏览器解析渲染页面

浏览器在收到HTML,CSS,JS文件后,它是如何把页面呈现到屏幕上的?

image.png

这个图就是Webkit解析渲染页面的过程。

解析HTML形成DOM树 解析CSS形成CSSOM 树 合并DOM树和CSSOM树形成渲染树 浏览器开始渲染并绘制页面 这个过程涉及两个比较重要的概念:回流重绘,DOM节点都是以盒模型形式存在,需要浏览器去计算位置和宽度等,这个过程就是回流。等到页面的宽高,大小,颜色等属性确定下来后,浏览器开始绘制内容,这个过程叫做重绘。浏览器刚打开页面一定要经过这两个过程的,但是这个过程非常非常非常消耗性能,所以我们应该尽量减少页面的回流和重绘

6)连接结束 断开TCP连接 四次挥手

http三次握手

  • 第一步:客户端发送SYN报文到服务端发起握手,发送完之后客户端处于SYN_Send状态
  • 第二步:服务端收到SYN报文之后回复SYN和ACK报文给客户端
  • 第三步:客户端收到SYN和ACK,向服务端发送一个ACK报文,客户端转为established状态,此时服务端收到ACK报文后也处于established状态,此时双方已建立了连接

http四次挥手

刚开始双方都处于 establised 状态,假如是客户端先发起关闭请求,则:

  1. 第一次挥手:客户端发送一个 FIN 报文,报文中会指定一个序列号。此时客户端处于FIN_WAIT1状态。
  2. 第二次挥手:服务端收到 FIN 之后,会发送 ACK 报文,且把客户端的序列号值 + 1 作为 ACK 报文的序列号值,表明已经收到客户端的报文了,此时服务端处于 CLOSE_WAIT状态。
  3. 第三次挥手:如果服务端也想断开连接了,和客户端的第一次挥手一样,发给 FIN 报文,且指定一个序列号。此时服务端处于 LAST_ACK 的状态。
  4. 第四次挥手:客户端收到 FIN 之后,一样发送一个 ACK 报文作为应答,且把服务端的序列号值 + 1 作为自己 ACK 报文的序列号值,此时客户端处于 TIME_WAIT 状态。需要过一阵子以确保服务端收到自己的 ACK 报文之后才会进入 CLOSED 状态
  5. 服务端收到 ACK 报文之后,就处于关闭连接了,处于 CLOSED 状态。

浏览器重绘与回流

1、浏览器重绘与回流的区别?

重排/回流(Reflow)

重排/回流(Reflow):当 DOM 的变化影响了元素的几何信息,浏览器需要重新计算元素的几何属性,将其安放在界面中的正确位置,这个过程叫做重排。表现为重新生成布局,重新排列元素。

回流:

  • 1、布局引擎会根据各种样式计算每个盒子在页面上的大小与位置。

  • 2、改变元素的位置和尺寸大小都会引发回流

image-20221107214653207.png

重绘(Repaint)

当一个元素的外观发生改变,但没有改变布局,重新把元素外观绘制出来的过程,叫做重绘。表现为某些元素的外观被改变。

  • 1、当计算好盒模型的位置、大小及其他属性后,浏览器根据每个盒子特性进行绘制。

  • 2、『回流』必定会发生『重绘』,『重绘』不一定会引发『回流』。

image-20221107214658086.png

2、如何触发重排和重绘?

  • 任何改变用来构建渲染树的信息都会导致一次重排或重绘:

  • 添加、删除、更新 DOM 节点

  • 通过 display: none 隐藏一个 DOM 节点-触发重排和重绘

  • 通过 visibility: hidden 隐藏一个 DOM 节点-只触发重绘,因为没有几何变化

  • 移动或者给页面中的 DOM 节点添加动画

  • 添加一个样式表,调整样式属性

  • 用户行为,例如调整窗口大小,改变字号,或者滚动

3、如何避免重绘或者重排?

  • 集中改变样式,不要一条一条地修改 DOM 的样式。

  • 不要把 DOM 结点的属性值放在循环里当成循环里的变量。

  • 为动画的 HTML 元件使用 fixed 或 absoult 的 position,那么修改他们的 CSS 是不会 reflow 的。

  • 不使用 table 布局。因为可能很小的一个小改动会造成整个 table 的重新布局。

  • 尽量只修改 position:absolute 或 fixed 元素,对其他元素影响不大

  • 动画开始 GPU 加速,translate 使用 3D 变化

浏览器 http 状态码

  • 1开头的 http 状态码 -> 表示临时响应并需要请求者继续执行操作的状态代码。

  • 2 开头的 http 状态码 -> 表示请求成功

  • 3 开头的 http 状态码 -> 重定向代码,也是常见的代码

  • 4 开头的 http 状态码 -> 表示请求出错

  • 5 开头的 http 状态码 -> 服务器本身的错误,而不是请求出错

比如:
    200响应成功
    301永久重定向
    302临时重定向
    304资源缓存
    403服务器禁止访问
    404服务器资源未找到
    500 502服务器内部错误
    504 服务器繁忙
    1xx    Informational(信息状态码)      接受请求正在处理
    2xx    Success(成功状态码)            请求正常处理完毕
    3xx    Redirection(重定向状态码)         需要附加操作已完成请求
    4xx    Client Error(客户端错误状态码)    服务器无法处理请求
    5xx    Server Error(服务器错误状态码)    服务器处理请求出错

浏览器的缓存机制

1、浏览器缓存机制

浏览器发起 HTTP 请求 – 服务器响应该请求。那么浏览器第一次向服务器发起该请求后拿到请求结果,会根据响应报文中 HTTP 头的缓存标识,决定是否缓存结果,是则将请求结果和缓存标识存入浏览器缓存中,简单的过程如下图:

image-20221107214735475.png

由上图我们可以知道:

  • 浏览器每次发起请求,都会先在浏览器缓存中查找该请求的结果以及缓存标识

  • 浏览器每次拿到返回的请求结果都会将该结果和缓存标识存入浏览器缓存中

根据是否需要向服务器重新发起 HTTP 请求将缓存过程分为两个部分,分别是强制缓存和协商缓存。

2、强制缓存

强制缓存就是向浏览器缓存查找该请求结果,并根据该结果的缓存规则来决定是否使用该缓存结果的过程。

image-20221107214742279.png

  1. 不存在该缓存结果和缓存标识,强制缓存失效,则直接向服务器发起请求(跟第一次发起请求一致)。

  2. 存在该缓存结果和缓存标识,但该结果已失效,强制缓存失效,则使用协商缓存。

  3. 存在该缓存结果和缓存标识,且该结果尚未失效,强制缓存生效,直接返回该结果

3、协商缓存

协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程

image-20221107214748208.png

  1. 协商缓存生效,返回 304

  2. 协商缓存失效,返回 200 和请求结果结果

  1. 强缓存==>Expires(过期时间)/Cache-Control(no-cache)(优先级高) 协商缓存 ==>Last-Modified/Etag(优先级高)Etag适用于经常改变的小文件 Last-Modefied适用于不怎么经常改变的大文件
  2. 强缓存策略和协商缓存策略在缓存命中时都会直接使用本地的缓存副本,区别只在于协商缓存会向服务器发送一次请求。它们缓存不命中时,都会向服务器发送请求来获取资源。在实际的缓存机制中,强缓存策略和协商缓存策略是一起合作使用的。浏览器首先会根据请求的信息判断,强缓存是否命中,如果命中则直接使用资源。如果不命中则根据头信息向服务器发起请求,使用协商缓存,如果协商缓存命中的话,则服务器不返回资源,浏览器直接使用本地资源的副本,如果协商缓存不命中,则浏览器返回最新的资源给浏览器。

进程、线程和协程

  • 进程: 是并发执行的程序在执行过程中分配和管理资源的基本单位,是一个动态概念,竞争计算机系统资源的基本单位。

  • 线程: 进程的一个执行单元,是进程内科调度实体。比进程更小的独立运行的基本单位。线程也被称为轻量级进程。

  • 协程: 一种比线程更加轻量级的存在。一个线程也可以拥有多个协程。其执行过程更类似于子例程,或者说不带返回值的函数调用。

图片懒加载

1、第一步

首先我们需要让我们 html 中需要懒加载的 img 标签的 src 设置缩略图或者不设置 src,然后自定义一个属性,值为真正的图片或者原图的地址(比如下面的 data-src),并且定义一个类名,表示该图片是需要懒加载的(比如下面例子的 lazy-image),这有两个作用:

为以后获取需要懒加载图片的 img 元素

可以给这个类名设置背景图片,作为图片未加载前的过度图片,比如显示为 loading 的图片。

image-20221107214822022.png

2、第二步

页面加载完后,我们需要获取所有需要懒加载的图片的元素集合,判断是否在可视区域,如果是在可视区域的话,设置元素的 src 属性值为真正图片的地址。

image-20221107214849692.png

3、第三步 当用户滚动窗口的时候,遍历所有需要懒加载的元素,通过每个元素的 BoundingClientRect 属性来判断元素是否出现在可视区域内,判断方法同第二步一样。

document.addEventListener('scroll', inViewShow)

这里我们可以优化下,可以通过函数节流优化滚动事件的处理函数。

Cookie、sessionStorage、localStorage

三者的相同点

存储在客户端

三者的区别

  • 内存不同:

  • cookie 数据大小不能超过 4k;

  • sessionStorage 和 localStorage 的存储比 cookie 大得多,可以达到 5M+;

  • 过期时间不同:

  • cookie 设置的过期时间之前一直有效;

  • localStorage 永久存储,浏览器关闭后数据不丢失除非主动删除数据;

  • sessionStorage 数据在当前浏览器窗口关闭后自动删除。

  • 数据保存位置不同:

  • cookie 的数据会自动的传递到服务器;

  • sessionStorage 和 localStorage 数据保存在本地;

网页的性能优化

1.减少一个页面访问所产生的 http 连接次数(减少 HTTP 请求)

  • 删除不必要的图像

  • 缩小图像尺寸

  • 实施延迟加载技术 -> 下面没滚到的地方先不要加载出来

  • 忽略页面上无关的资源

  • 缩小 CSS 和 JavaScript 文件

  • 合并 CSS 和 JavaScript 文件

  • 减少外部脚本的数量

2.使用 gzip 压缩网页内容

使用 gzip 来压缩网页中的静态内容,能够显著减少用户访问网页时的等待时间(据说可达到 60%)。

3.将 CSS 放在页面顶端,JS 文件放在页面底端。

或者将 CSS 或 JavaScript 放到外部文件中,避免使用 style 或 script 标签直接引入。

4.使 JS 文件内容最小化 / 优化 js 代码结构,减少冗余代码

具体来说就是使用一些 javascript 压缩工具对 js 脚本进行压缩,去除其中的空白字符、注释,最小化变量名等。在使用 gzip 压缩的基础上,对 js 内容的压缩能够将性能再提高 5%。

5.使用字体图标 iconfont 代替图片图标

6.减少页面重定向:

页面每次重定向都会延长页面内容返回的等待延时,一次重定向大约需要 200 毫秒不等的时间开销(无缓存),为了保证用户尽快看到页面内容,要尽量避免页面重定向;

举例: 先举个例子吧,我们单位原来叫 aa.com,上面有张公司的主页 default.html,今年发现一个更好的域名 a.com 了,那我们就把 default.html 拷贝到 a.com 的 web 服务器上,那问题来了,原来公司的名片上都印的 aa.com 没办法收回重新发了,所以我们把 aa.com 上的 default.html 让它指向 a.com,这样原来的那些客户就可以 ie 自动跳转到我们公司的新域名 a.com 上了。

7.善用缓存,不重复加载相同的资源

8.使用事件委托(事件代理)

事件代理就是,本来应该加在子元素身上的事件,我们却把事件加在了其父级身上。

9.预加载

预加载简单来说就是将所有所需的资源提前请求加载到本地,这样后面在需要用到时就直接从缓存取资源。

在网页全部加载之前,对一些主要内容进行加载,以提供给用户更好的体验,减少等待的时间。

10.防抖、节流

文档流

1、什么是文档流?

网页在解析时,遵循从上向下,从左向右的顺序。

从上至上,从左至右的布局。

符合 html 中标签本身含义的布局,比如某些标签独占一行。有些标签属于行内元素等。

2、怎么脱离文档流?

float (浮动)

position:absolute(position)

fixed(position)

3、脱离文档流会造成什么问题?

脱离文档流是指,这个标签脱离了文档流的管理。不受文档流的布局约束了,并且更重要的一点是,这个标签在原文档流中所占的空间也被清除了。

脱离文档流的元素处于浮动状态(可以理解为漂浮在文档流的上方)。

理解xss,csrf,ddos攻击原理以及避免方式

XSS(Cross-Site Scripting*跨站脚本攻击*)

是一种代码注入攻击。攻击者在目标网站上注入恶意代码,当被攻击者登陆网站时就会执行这些恶意代码,这些脚本可以读取 cookie,session tokens,或者其它敏感的网站信息,对用户进行钓鱼欺诈,甚至发起蠕虫攻击等。

*XSS避免方式:*

  1. url参数使用encodeURIComponent方法转义
  2. 尽量不是有InnerHtml插入HTML内容
  3. 使用特殊符号、标签转义符。

CSRFCross-site request forgery*跨站请求伪造*

攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求。利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证,达到冒充用户对被攻击的网站执行某项操作的目的。

CSRF避免方式:

  1. 添加验证码

  2. 使用token

    • 服务端给用户生成一个token,加密后传递给用户
    • 用户在提交请求时,需要携带这个token
    • 服务端验证token是否正确

DDoS又叫分布式拒绝服务

全称 Distributed Denial of Service,其原理就是利用大量的请求造成资源过载,导致服务不可用。

***\*DDos\*****避免方式:*

  1. 限制单IP请求频率。
  2. 防火墙等防护设置禁止ICMP包等
  3. 检查特权端口的开放

HTTP 和 HTTPS

http 和 https 基本概念

1、http 和 https基本概念

http:是一个客户端和服务器端请求和应答的标准(TCP),用于从 WWW 服务器传输超文本到本地浏览器的超文本传输协议。

https:是以安全为目标的 HTTP 通道,即 HTTP 下 加入 SSL 层进行加密。其作用是:建立一个信息安全通道,来确保数据的传输,确保网站的真实性。

image-20221107222437290.png

2、http 和 https 的区别

端口 https 的端口是 443,而 http 的端口是 80,当然两者的连接方式也是不太一样的。

传输数据 http 超文本传输协议,传输是明文的,而 https 是用 ssl 进行加密的。https 具有安全性。

申请证书 https 传输一般是需要申请 ca 证书,申请证书可能会需要一定的费用,功能越强大的证书费用越高。

性能 http 的连接很简单,是无状态的。 https 握手阶段比较费时,会使页面加载时间延长 50%,增加 10%~20% 的耗电。

缓存 https 缓存不如 http 高效,会增加数据开销。

工作层

在OSI 网络模型中,HTTP工作于应用层,而HTTPS 的安全传输机制工作在传输层

3.http 的主要特点

  1. 简单快捷: 当客户端向服务器端发送请求时,只是简单的填写请求路径即可,然后就可以通过浏览器或其他方式将该请求发送就行了。 比较常用的请求方法有三种,分别是:GET、HEAD、POST。不同的请求方法使得客户端和服务器端联系的方式各不相同。 因为HTTP 协议比较简单,所以HTTP服务器的程序规模相对比较小,从而使得通信的速度非常快。

  2. 灵活 : Http 协议允许客户端和服务器端传输任意类型任意格式的数据对象。这些不同的类型由 Content-Type 标记。

  3. 无连接: 每次建立的连接只处理一个客户端请求,当服务器处理完客户端的请求之后,服务器端立即断开连接。

  4. 无状态: 无状态是指协议对于请求的处理没有记忆功能。

User-Agent 请求报头域 允许客户端将它的操作系统、浏览器和其它属性告诉服务器。不过,这个报头域不是必需的,如果我们自己编写一个浏览器,不使用User-Agent请求报头域,那么服务器端就无法得知我们的信息了。

4.HTTP 协议

http 协议是一个应用层协议,其报文分为请求报文和响应报文。

请求报文: 请求行 请求头 空行 请求数据

响应报文: 状态行 响应头 空行 响应数据/正文

image-20221107222448466.png

当客户端请求一个网页时,会先通过 http 协议将请求的内容封装在 http 请求报文之中,服务器收到该请求报文后根据协议规范进行报文解析,然后向客户端返回响应报文。

image-20221107222458689.png

HTTP 请求跨域问题

什么是同源策略

一个域下的js脚本未经允许的情况下,不能访问另一个域下的内容。通常判断跨域的依据是协议、域名、端口号是否相同,不同则跨域。同源策略是对js脚本的一种限制,并不是对浏览器的限制,像img,script脚本请求不会有跨域限制。

前后端如何通信

Ajax : 短连接
Websocket : 长连接,双向的。
Form表单(最原始的)

1、跨域的原理

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

2、跨域的解决方法

1.JSONP(利用script标签没有跨域限制的漏洞实现。缺点:只支持GET请求)

最早的解决方案,利用script标签可以跨域的原理实现。

限制:

  • 需要服务的支持,这是比较早的解决方案,我们的script标签的src还是img标签的src,或者说link标签的href他们没有被同源策略所限制。它就是通过通过 script 标签加载并执行其他的域的事件之类。
  • 缺点:只支持 get 和 http 请求;在请求完毕后可以通过调用 callback 的方式返回结果,如果请求失败,不会返回状态码。

首先我们先弄明白 jsonp 的原理,通俗的讲就是动态生成一个 script,由于 script 不受同源策略的限制,就可以去请求不同域的接口了。

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

步骤:

A. 去创建一个 script 标签

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

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

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

image-20221107222539630.png

2. CORS(设置Access-Control-Allow-Origin:指定可访问资源的域名)

跨域资源共享。就是在后台服务器端允许某个域名下的请求

修改响应头:

image-20221107222543899.png

**3. nginx:**反向代理

通过服务器反向代理,将前端访问域名跟后端服务域名映射到同源的地址下,从而实现前端服务和后端服务的同源,那自然不存在跨域的问题了。

利用nginx反向代理把跨域为不跨域,支持各种请求方式。 反向代理,其实客户端对代理是无感知的,因为客户端不需要任何配置就可以访问,我们只需要将请求发送到反向代理服务器,由反向代理服务器去选择目标服务器获取数据后,在返回给客户端,此时反向代理服务器和目标服务器对外就是一个服务器,暴露的是代理服务器地址,隐藏了真实服务器IP地址。 缺点:需要在nginx进行额外配置,语义不清晰

日常工作中用的最对的跨域方案是CORS和Nginx反向代理

vue开发环境的proxyTable

proxyTablevue-cli 脚手架在开发模式下,为我们提供的一个跨域的代理中转服务器服务.基于 (http-proxy-middleware插件)。proxyTable 就是webpack在开发环境给我们提供的一个代理服务器,(使用的是 http-proxy-middleware)。

配置:

image-20221214224415767

get 和 post 的区别

get 和 post 的区别

GET:发送⼀个请求来取得服务器上的某⼀资源 检索 -> 获取

POST :向 URL 指定的资源提交数据或附加新的数据 创建->更新

post 用于修改和写入数据,get 一般用于搜索排序和筛选之类的操作(淘宝,支付宝的搜索查询都是 get 提交),目的是资源的获取,读取数据。

get 和 post 的区别

存放参数位置是不同

GET:参数包含在 URL 中。以?开始以 param=value&parame2=value2 的形式附加在URI字段之后。

POST:参数存放在请求数据包的消息体中。request body,post 更安全

image-20221108095411828.png

hash 模式 和 history 模式

www.xxx.com#plan/index //hash 模式路由

www.xxx.com/plan/index //history 模式路由

路由器hash 模式history 模式
兼容性兼容性较好兼容性略差
URL出现 url 中出现 url 中,不在 HTTP 请求中 与 HTTP 保持一致以 / 开头
服务器hash 值不会带给服务器。应用部署上线时需要后端人员支持,解决刷新页面服务端 404 的问题。
美观性地址中永远带着 # 号地址干净,美观
加载页面不会重新加载页面重新加载页面

XMLHttpRequest 对象在调用 send() 前需要调用哪个方法?

答案是:open()

/*
Ajax 技术的工作原理:
1)创建 Ajax 对象:var xhr = new XMLHttpRequest();
2)xhr 发送请求:xhr.open('get','test.html','true');  与服务器建立连接
                 xhr.send();        需要什么内容,告诉服务器
3)xhr 获取响应:
*/
xhr.onreadystatechange = function(){     监听服务器端的通信状态
if(xhr.readystate == 4){//请求的状态码  请求完全处理完成
/*
0:请求还没有建立(open 执行前)
1:请求建立了还没发送(执行了 open)
2:请求正式发送(执行了 send)
3:请求已受理,有部分数据可以用,但还没有处理完成
4:请求完全处理完成
*/
alert(xhr.responseText);//返回的数据
}
}
因此,可以看到,send()前是open()


Ajax 工作原理 —— XMLHttpRequest 对象

Ajax 不是新的编程语言,而是一门提供网页局部刷新的技术。

Ajax 最大的优点是在不重新加载整个页面的情况下,与服务器交换数据并更新部分网页内容。

流程图:

image-20221108095600052.png

image-20221108095605547.png

html5 有哪些新特性

(1) Canvas 绘图

(2) SVG 绘图

(3) 地理定位

(4) Web Worker

  • web worker 是运行在后台的 JS,独立于其他脚本,不会影响页面的性能。

(5) Web Storage

  • Cookie 技术 (兼容性好,数据不能超 4kb,操作复杂)

  • sessionStorage(兼容性差,数据 8MB,操作简单)

  • localStorage

(6)Web Socket

  • WebSocket 协议是基于 TCP 的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通信——允许服务器主动发送信息给客户端

理解xss,csrf,ddos攻击原理以及避免方式

XSS(Cross-Site Scripting跨站脚本攻击)

是一种代码注入攻击。攻击者在目标网站上注入恶意代码,当被攻击者登陆网站时就会执行这些恶意代码,这些脚本可以读取 cookie,session tokens,或者其它敏感的网站信息,对用户进行钓鱼欺诈,甚至发起蠕虫攻击等。

XSS避免方式:

  1. url参数使用encodeURIComponent方法转义
  2. 尽量不是有InnerHtml插入HTML内容
  3. 使用特殊符号、标签转义符。

CSRFCross-site request forgery跨站请求伪造

攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求。利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证,达到冒充用户对被攻击的网站执行某项操作的目的。

CSRF避免方式:

  1. 添加验证码

  2. 使用token

    • 服务端给用户生成一个token,加密后传递给用户
    • 用户在提交请求时,需要携带这个token
    • 服务端验证token是否正确

DDoS又叫分布式拒绝服务

全称 Distributed Denial of Service,其原理就是利用大量的请求造成资源过载,导致服务不可用。

DDos避免方式:

  1. 限制单IP请求频率。
  2. 防火墙等防护设置禁止ICMP包等
  3. 检查特权端口的开放

软件开发的模式

各大模型对比

image-20221108100138817.png

传统的瀑布式开发,也就是从需求到设计,从设计到编码,从编码到测试,从测试到提交大概这样的流程,要求每一个开发阶段都要做到最好。

特别是前期阶段,设计的越完美,提交后的成本损失就越少。

迭代式开发,不要求每一个阶段的任务做的都是最完美的,而是明明知道还有很多不足的地方,却偏偏不去完善它,而是把主要功能先搭建起来为目的,以最短的时间,

最少的损失先完成一个“不完美的成果物”直至提交。然后再通过客户或用户的反馈信息,在这个“不完美的成果物”上逐步进行完善。

螺旋开发,很大程度上是一种风险驱动的方法体系,因为在每个阶段之前及经常发生的循环之前,都必须首先进行风险评估。

敏捷开发,相比迭代式开发两者都强调在较短的开发周期提交软件,但是,敏捷开发的周期可能更短,并且更加强调队伍中的高度协作。

敏捷方法有时候被误认为是无计划性和纪律性的方法,实际上更确切的说法是敏捷方法强调适应性而非预见性。

适应性的方法集中在快速适应现实的变化。当项目的需求起了变化,团队应该迅速适应。这个团队可能很难确切描述未来将会如何变化。

瀑布模型

瀑布模型将软件生命周期划分为制定计划、需求分析、软件设计、程序编写、软件测试和运行维护等六个基本活动,并且规定了它们自上而下、相互衔接的固定次序,如同瀑布流水,逐级下落。

在瀑布模型中,软件开发的各项活动严格按照线性方式进行,当前活动接受上一项活动的工作结果,实施完成所需的工作内容。当前活动的工作结果需要进行验证,如验证通过,则该结果作为下一项活动的输入,继续进行下一项活动,否则返回修改。

优点: 严格遵循预先计划的步骤顺序进行,一切按部就班比较严谨。

缺点:

  • 1) 各个阶段的划分完全固定,阶段之间产生大量的文档,极大地增加了工作量;

  • 2) 由于开发模型是线性的,用户只有等到整个过程的末期才能见到开发成果,从而增加了开发的风险;

  • 3) 早期的错误可能要等到开发后期的测试阶段才能发现,进而带来严重的后果。

  • 4) 各个软件生命周期衔接花费时间较长,团队人员交流成本大。

  • 5) 瀑布式方法在需求不明并且在项目进行过程中可能变化的情况下基本是不可行的。

迭代模型

每一次迭代都包括了需求分析、设计、实现与测试。采用这种方法,开发工作可以在需求被完整地确定之前启动,并在一次迭代中完成系统的一部分功能或业务逻辑的开发工作。再通过客户的反馈来细化需求,并开始新一轮的迭代。

迭代一般指某版本的生产过程,包括从需求分析到测试完成;

版本一般指某阶段软件开发的结果,一个可交付使用的产品。

优点:

  • 1) 降低了在一个增量上的开支风险。如果开发人员重复某个迭代,那么损失只是这一个开发有误的迭代的花费。

  • 2) 降低了产品无法按照既定进度进入市场的风险。通过在开发早期就确定风险,可以尽早来解决而不至于在开发后期匆匆忙忙。

  • 3) 加快了整个开发工作的进度。因为开发人员清楚问题的焦点所在,他们的工作会更有效率。

  • 4) 由于用户的需求并不能在一开始就作出完全的界定,它们通常是在后续阶段中不断细化的。因此,迭代过程这种模式使适应需求的变化会更容易些。因此复用性更高。

敏捷软件开发

敏捷开发是一种以人为核心、迭代、循序渐进的开发方法。在敏捷开发中,软件项目的构建被切分成多个子项目,各个子项目的成果都经过测试,具备集成和可运行的特征。换言之,就是把一个大项目分为多个相互联系,但也可独立运行的小项目,并分别完成,在此过程中软件一直处于可使用状态。

  敏捷开发小组主要的工作方式可以归纳为:作为一个整体工作; 按短迭代周期工作; 每次迭代交付一些成果,关注业务优先级,检查与调整。

  敏捷软件开发要注意项目规模,规模增长,团队交流成本就上去了,因此敏捷软件开发暂时适合不是特别大的团队开发,比较适合一个组的团队使用。

前端工程化

webpack配置,webpack4.0有哪些优化点

module.exports={
    entry: {},
    output: {},
    plugins: [],
    module: [rules:[{}]]
}

webpack如何实现代码分离

  • 入口起点:使用 entry 配置手动地分离代码。
  • 防止重复:使用 CommonsChunkPlugin 去重和分离 chunk
  • 动态导入:通过模块的内联函数调用来分离代码。

常见的Webpack Loader? 如何实现一个Webpack Loader(NO)

loader: 是一个导出为函数的javascript模块,根据rule匹配文件扩展名,处理文件的转换器。

file-loader:把文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件 (处理图片和字体)

url-loader: 与file-loader类似,区别是用户可以设置一个阈值,大于阈值会交给file-loader处理,小于阈值时返回文件base64 形式编码 (处理图片和字体)

image-loader:加载并且压缩图片文件

babel-loader`:把 `ES6 `转换成` ES5
sass-loader`:将`SCSS/SASS`代码转换成`CSS

css-loader:加载 CSS,支持模块化、压缩、文件导入等特性

style-loader`:把 `CSS` 代码注入到 `JavaScript `中,通过` DOM` 操作去加载 `CSS

postcss-loader:扩展 CSS 语法,使用下一代 CSS,可以配合 autoprefixer 插件自动补齐 *CSS3 前缀*eslint-loader:通过 ESLint 检查 JavaScript 代码

loader和plugin对比?

  • Loadermodule.rules 中配置,作为模块的解析规则,类型为数组。每一项都是一个 Object,内部包含了 test(类型文件)、loader、options (参数)等属性。
  • Plugin plugins 中单独配置,类型为数组,每一项是一个Plugin的实例,参数都通过构造函数传入。

前端模块化,CMD、AMD、CommonJS

CommonJS

CommonJS是服务器端模块的规范,由Node推广使用,webpack也采用这种规范编写

commonJs规范:

CommonJS模块规范主要分为三部分:模块定义、模块标识、模块引用

  • 模块定义:module对象:在每一个模块中,module对象代表该模块自身。 export属性:module对象的一个属性,它向外提供接口。输出模块变量的最好方法是使用module.exports对象。一个单独的文件就是一个模块。每一个模块都是一个单独的作用域,也就是说,在该模块内部定义的变量,无法被其他模块读取,除非定义为global对象的属性。
  • 模块标识:传递给require方法的参数,必须是符合小驼峰命名的字符串,或者以 . 、.. 、开头的相对路径,或者绝对路径。
  • 模块引用:加载模块使用require(同步加载),该方法读取一个文件并执行,返回文件内部的module.exports对象。

优势:

在后端,JavaScript的规范远远落后并且有很多缺陷,这使得难以使用JavaScript开发大型应用。比如:没有模块系统、标准库较少、没有标准接口、缺乏包管理系统、列表内容

  1. CommonJS模块规范很好地解决变量污染问题,每个模块具有独立空间,互不干扰,命名空间相比之下就不太好。
  2. CommonJS规范定义模块十分简单,接口十分简洁。
  3. CommonJS模块规范支持引入和导出功能,这样可以顺畅地连接各个模块,实现彼此间的依赖关系
  4. CommonJS规范的提出,主要是为了弥补JavaScript没有标准的缺陷,已达到像Python、Ruby和Java那样具备开发大型应用的基础能力,而不是停留在开发浏览器端小脚本程序的阶段

缺点:

没有并行加载机制

由于CommonJS是同步加载模块,这对于服务器端是很不好的,因为所有的模块都放在本地硬盘。等待模块时间就是硬盘读取文件时间,很小。但是,对于浏览器而言,它需要从服务器加载模块,涉及到网速,代理等原因,一旦等待时间过长,浏览器处于”假死”状态。

所以浏览器端不是很适合Common.Js,出现另一种规范AMD

AMD

AMD 是运行在浏览器环境的一个异步模块定义规范 ,是RequireJS 在推广过程中对模块定义的规范化产出。

AMD规范

AMD推崇依赖前置,在定义模块的时候就要声明其依赖的模块

优点

用户体验好,因为没有延迟,依赖模块提前执行了。

CMD

CMD是一个通用模块定义规范;是SeaJs推广过程中对模块定义的规范化产出

CMD规范

CMD`推崇依赖就近,只有在用到某个模块的时候才会去`require

优点

性能好,因为只有用户需要的时候才执行。

手撕代码题

数组去重,数组对象去重

//数组
const arr = [2,7,5,7,2,8,9];
console.log([...new Set(arr)]); // [2,7,5,8,9];
//对象
const list = [{age:18,name:'张三'},{age:18,name:'李四'},{age:18,name:'王五'}]
let hash = {};
const newArr = arr.reduce((item, next) => {
    hash[next.age] ? '' : hash[next.age] = true && item.push(next);
    return item;
}, []);
console.log(list);

数组扁平化

Q: 什么是数组扁平化?

A:数组扁平化其实就是将多维数组转为一维数组。

方式一:ES6 的 flat 方法

const arr = [1,[2,[3,[4,5]]],6]
//  方法一:数组自带的扁平化方法,flat 的参数代表的是需要展开几层,如果是 Infinity 的话,就是不管嵌套几层,全部都展开
console.log(arr.flat(Infinity))

方式二:使用正则

  1. 首先是使用 JSON.stringify 把 arr 转为字符串
  2. 接着使用正则把字符串里面的 [ 和 ]去掉
  3. 然后再拼接数组括号转为数组对象
const arr = [1,[2,[3,[4,5]]],6]
const res = JSON.stringify(arr).replace(/\[|\]/g,'')
const res2 = JSON.parse('[' + res + ']')
console.log(res2)



方法三:使用递归

const array = []
const  fn = (arr)=>{
for(let i = 0;i<arr.length; i++){
                if(Array.isArray(arr[i])){
                    fn(arr[i])
                }
                else {
                    array.push(arr[i])
                }
            }
      }
fn(arr)
console.log(array)


方法四:使用 reduce

reduce 方法:可以用来给数组求和

concat() 方法用于连接两个或多个数组。

concat() 方法不会更改现有数组,而是返回一个新数组,其中包含已连接数组的值。


onst newArr = (arr)=>{
            return arr.reduce((pre,cur)=>{
                return pre.concat(Array.isArray(cur) ? newArr(cur) : cur)
            },[])
        }
console.log(newArr(arr),"reduce方法")

方法五:使用栈的思想实现 flat 函数

image-20221109160822596.png

深拷贝和浅拷贝

1、什么是数据类型?

数据分为基本数据类型(String, Number, Boolean, Null, Undefined,Symbol)和引用数据类型Object。

  • 基本数据类型的特点:直接存储在栈(stack)中的数据

  • 引用数据类型的特点:存储的是该对象在栈中引用,真实的数据存放在堆内存里

引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。

image-20221109160910959.png

2、为什么要使用深拷贝?

我们希望在改变新的数组(对象)的时候,不改变原数组(对象)

3、赋值和浅拷贝的区别?

当我们把一个对象赋值给一个新的变量时,赋的其实是该对象的在栈中的地址,而不是堆中的数据。也就是两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实都是改变的存储空间的内容,因此,两个对象是联动的。

浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。

image-20221109160942624.png

4、深拷贝和浅拷贝的示意图

image-20221109160946942.png

5、参考代码

//浅拷贝 
function shallowCopy(src) {
  var dst = {};
  for (var prop in src) {
    if (src.hasOwnProperty(prop)) {
      dst[prop] = src[prop];
    }
  }
  return dst;
}
//深拷贝    
function checkType(any) {
  return Object.prototype.toString.call(any).slice(8, -1)
}
function clone(any){
  if(checkType(any) === 'Object') { // 拷贝对象
    let o = {};
    for(let key in any) {
      o[key] = clone(any[key])
    }
    return o;
  } else if(checkType(any) === 'Array') { // 拷贝数组
    var arr = []
    for(let i = 0,leng = any.length;i<leng;i++) {
      arr[i] = clone(any[i])
    }
    return arr;
  } else if(checkType(any) === 'Function') { // 拷贝函数
    return new Function('return '+any.toString()).call(this)
  } else if(checkType(any) === 'Date') { // 拷贝日期
    return new Date(any.valueOf())
  } else if(checkType(any) === 'RegExp') { // 拷贝正则
    return new RegExp(any)
  } else if(checkType(any) === 'Map') { // 拷贝Map 集合
    let m = new Map()
    any.forEach((v,k)=>{
      m.set(k, clone(v))
    })
    return m
  } else if(checkType(any) === 'Set') { // 拷贝Set 集合
    let s = new Set()
    for(let val of any.values()) {
      s.add(clone(val))
    }
    return s
  }
  return any;
}

防抖节流

防抖:n 秒内函数只会执行一次,若在 n 秒内被重复触发,则重新计时

function debounce(fn, delay=1000) {
  // 维护一个timer
  let timer = null;
  return function () {
    clearTimeout(timer);
    timer = setTimeout(function () {
 // 通过 'this' 和 'arguments' 获取函数的作用域和参数
      fn.apply(this, arguments);
    }, delay);
  }
}
//使用方法
function sayHi() {
  console.log('Hi!');
}

// 每隔一秒打印一次
let debouncedSayHi = debounce(sayHi, 1000);

// 连续调用三次,只会打印一次
debouncedSayHi();
debouncedSayHi();
debouncedSayHi();

节流:n 秒内只运行一次,若在 n 秒内重复触发,只执行一次

function throttle(fn, delay) {
  let lastCall = 0;
  return function(...args) {
    const now = new Date().getTime();
    if (now - lastCall < delay) {
      return;
    }
    lastCall = now;
    return fn(...args);
  };
}
const throttledFn = throttle(myFn, 1000); // 将 myFn 节流为每秒最多调用一次

// 在接下来的时间内,throttledFn 只会被调用一次,即使它被多次调用
throttledFn();
throttledFn();
throttledFn();

image-20221109161053150.png

For..in 和 for..of 用法

image-20221109161110076.png

image-20221109161112872.png

从数组 [1,2,3,4,5,6] 中找出值为 2 的元素

1、filter()

filter() 方法创建一个新数组, 包含通过所提供函数实现的测试的所有元素。返回一个数组

var arr = [1, 2, 3, 4, 5, 6]
var s = arr.filter((e) => {
    return e == 2
})
console.log(s)//[2]

image-20221214153747720

2、find()

find() 方法返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined。

var arr = [1, 2, 3, 4, 5, 6]
			var s = arr.find(e => {
				return e == 2
			})
			console.log(s)//2

image-20221214153722854

image-20221109161310587.png

Vue相关

简述MVVM

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

vue的生命周期

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

create阶段:vue实例被创建 beforeCreate: 创建前,此时data和methods中的数据都还没有初始化 created: 创建完毕,data中有值,未挂载

mount阶段: vue实例被挂载到真实DOM节点 beforeMount:可以发起服务端请求,去数据 mounted: 此时可以操作DOM

update阶段:当vue实例里面的data数据变化时,触发组件的重新渲染 beforeUpdate :更新前 updated:更新后

destroy阶段:vue实例被销毁 beforeDestroy:实例被销毁前,此时可以手动销毁一些方法 destroyed:销毁后

image-20221214230052217

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

加载渲染过程

->父beforeCreate -> 父created -> 父beforeMount

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

->父mounted

子组件更新过程

->父beforeUpdate

-> 子beforeUpdate -> 子updated

-> 父updated

父组件更新过程

父beforeUpdate -> 父updated

销毁过程

-> 父beforeDestroy

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

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>

    </style>
</head>
<body>
<div id="app">
    <p>{{message}}</p>
    <keep-alive>
        <my-components :msg="msg1" v-if="show"></my-components>
    </keep-alive>
</div>
</body>
<script src="../../node_modules/vue/dist/vue.js"></script>
<script>
    var child = {
        template: '<div>from child: {{childMsg}}</div>',
        props: ['msg'],
        data: function() {
            return {
                childMsg: 'child'
            }
        },
        beforeCreate: function () {
            debugger;
        },
        created: function () {
            debugger;
        },
        beforeMount: function () {
            debugger;
        },
        mounted: function () {
            debugger;
        },
        deactivated: function(){
            alert("keepAlive停用");
        },
        activated: function () {
            console.log('component activated');
        },
        beforeDestroy: function () {
            console.group('beforeDestroy 销毁前状态===============》');
            var state = {
                'el': this.$el,
                'data': this.$data,
                'message': this.message
            }
            console.log(this.$el);
            console.log(state);
        },
        destroyed: function () {
            console.group('destroyed 销毁完成状态===============》');
            var state = {
                'el': this.$el,
                'data': this.$data,
                'message': this.message
            }
            console.log(this.$el);
            console.log(state);
        },
    };
    var vm = new Vue({
        el: '#app',
        data: {
                message: 'father',
                msg1: "hello",
                show: true
            },
        beforeCreate: function () {
            debugger;
        },
        created: function () {
            debugger;
        },
        beforeMount: function () {
            debugger;
        },
        mounted: function () {
            debugger;
        },
        beforeUpdate: function () {
            alert("页面视图更新前");

        },
        updated: function () {
            alert("页面视图更新后");
        },
        beforeDestroy: function () {
            console.group('beforeDestroy 销毁前状态===============》');
            var state = {
                'el': this.$el,
                'data': this.$data,
                'message': this.message
            }
            console.log(this.$el);
            console.log(state);
        },
        destroyed: function () {
            console.group('destroyed 销毁完成状态===============》');
            var state = {
                'el': this.$el,
                'data': this.$data,
                'message': this.message
            }
            console.log(this.$el);
            console.log(state);
        },
        components: {
            'my-components': child
        }
    });
</script>
</html>

vue组件的通信方式

vue2为例: $emit,props,$ref,$parent,$children, eventBus, provide/inject, vuex

props/$emit 父子组件通信

  • 父->子props
  • 子->父 $on、$emit
  • 获取父子组件实例 parent、children、Ref
  • 获取实例的方式调用组件的属性或者方法 父->子孙 Provide、inject 官方不推荐使用,但是写组件库时很常用

兄弟组件通信

Event Bus` 实现跨组件通信 `Vue.prototype.$bus = new Vue() Vuex

跨级组件通信

$attrs$listeners 、Provide、inject

双向绑定实现原理

当一个****Vue****实例创建时,Vue会遍历data选项的属性,用 Object.defineProperty 将它们转为 getter/setter并且在内部追踪相关依赖,在属性被访问和修改时通知变化。每个组件实例都有相应的 watcher 程序实例,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的 setter 被调用时,会通知 watcher重新计算,从而致使它关联的组件得以更新。

v-model的实现以及它的实现原理吗?

  1. vue中双向绑定是一个指令v-model,可以绑定一个动态值到视图,同时视图中变化能改变该值。v-model是语法糖,默认情况下相于:value和@input
  2. 使用v-model可以减少大量繁琐的事件处理代码,提高开发效率,代码可读性也更好
  3. 通常在表单项上使用v-model
  4. 原生的表单项可以直接使用v-model,自定义组件上如果要使用它需要在组件内绑定value并处理输入事件
  5. 我做过测试,输出包含v-model模板的组件渲染函数,发现它会被转换为value属性的绑定以及一个事件监听,事件回调函数中会做相应变量更新操作,这说明神奇魔法实际上是vue的编译器完成的。

jq和vue什么区别

Vue.js 一个核心思想是数据驱动。所谓数据驱动,是指视图是由数据驱动生成的,我们对视图的修改,不会直接操作 DOM,而是通过修改数据。它相比我们传统的前端开发,如使用 jQuery 等前端库直接修改 DOM,大大简化了代码量。特别是当交互复杂的时候,只关心数据的修改会让代码的逻辑变的非常清晰,因为 DOM 变成了数据的映射,我们所有的逻辑都是对数据的修改,而不用碰触 DOM,这样的代码非常利于维护。

  • vue数据驱动
  • jq控制dom元素
  • 简单或者很少dom元素 如果对比两者看上去差距不大 dom元素和操作的多,就会发现vue更加方便简洁
  • vue渲染优雅,代码易维护

computed与watch

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

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

*使用场景*

computed:当一个属性受多个属性影响的时候使用,例:购物车商品结算功能

watch:当一条数据影响多条数据的时候使用,例:搜索数据

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

1.一个组件被复用多次的话,也就会创建多个实例。本质上,这些实例用的都是同一个构造函数。 2.如果data是对象的话,对象属于引用类型,会影响到所有的实例。所以为了保证组件不同的实例之间data不冲突,data必须是一个函数。

v-for中key的作用

  1. key的作用主要是为了更高效的对比虚拟DOM中每个节点是否是相同节点;
  2. Vue在patch过程中判断两个节点是否是相同节点,key是一个必要条件,渲染一组列表时,key往往是唯一标识,所以如果不定义key的话,Vue只能认为比较的两个节点是同一个,哪怕它们实际上不是,这导致了频繁更新元素,使得整个patch过程比较低效,影响性能;
  3. 从源码中可以知道,Vue判断两个节点是否相同时主要判断两者的key和元素类型等,因此如果不设置key,它的值就是undefined,则可能永远认为这是两个相同的节点,只能去做更新操作,这造成了大量的dom更新操作,明显是不可取的。

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

1.当 v-for 和 v-if 处于同一个节点时,v-for 的优先级比 v-if 更高,这意味着 v-if 将分别重复运行于每个 v-for 循环中。如果要遍历的数组很大,而真正要展示的数据很少时,这将造成很大的性能浪费(Vue2.x) 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操作结果。

keep-alive的实现

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

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

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

vuex、vue-router实现原理

vuex是一个专门为vue.js应用程序开发的状态管理库。每一个 Vuex 应用的核心就是 store(仓库)。核心概念:

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

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

  1. State:定义了应用的状态数据

  2. Getter:在 store 中定义“getter”(可以认为是 store 的计算属性),就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来, 且只有当它的依赖值发生了改变才会被重新计算

  3. Mutation:是唯一更改 store 中状态的方法,且必须是同步函数

  4. Action:用于提交 mutation,而不是直接变更状态,可以包含任意异步操作

  5. Module:允许将单一的 Store 拆分为多个 store 且同时保存在单一的状态树中

你都做过哪些Vue的性能优化?

编码阶段
尽量减少data中的数据,data中的数据都会增加getter和setter,会收集对应的watcher
v-if和v-for不能连用
如果需要使用v-for给每项元素绑定事件时使用事件代理
SPA 页面采用keep-alive缓存组件
在更多的情况下,使用v-if替代v-show
key保证唯一
使用路由懒加载、异步组件
防抖、节流
第三方模块按需导入
长列表滚动到可视区域动态加载
图片懒加载
SEO优化
预渲染
服务端渲染SSR
打包优化
压缩代码
Tree Shaking/Scope Hoisting
使用cdn加载第三方模块
多线程打包happypack
splitChunks抽离公共文件
sourceMap优化
用户体验
骨架屏
PWA
还可以使用缓存(客户端缓存、服务端缓存)优化、服务端开启gzip压缩等。

你知道Vue3有哪些新特性吗?它们会带来什么影响?

  • *性能提升*更小巧、更快速 支持自定义渲染器 支持摇树优化:一种在打包时去除无用代码的优化手段 支持Fragments和跨组件渲染
  • *API变动*模板语法99%保持不变 原生支持基于class的组件,并且无需借助任何编译及各种stage阶段的特性 在设计时也考虑TypeScript的类型推断特性 重写虚拟DOM可以期待更多的编译时提示来减少运行时的开销优化插槽生成可以单独渲染父组件和子组件静态树提升降低渲染成本基于Proxy的观察者机制节省内存开销
  • *不兼容IE11*检测机制更加全面、精准、高效,更具可调试式的响应跟踪

实现双向绑定 Proxy 与 Object.defineProperty 相比优劣如何?

  1. *Object.definedProperty*的作用是劫持一个对象的属性,劫持属性的getter和setter方法,在对象的属性发生变化时进行特定的操作。而 Proxy劫持的是整个对象。
  2. *Proxy*会返回一个代理对象,我们只需要操作新对象即可,而Object.defineProperty只能遍历对象属性直接修改。
  3. *Object.definedProperty*不支持数组,更准确的说是不支持数组的各种API,因为如果仅仅考虑arry[i] = value 这种情况,是可以劫持 的,但是这种劫持意义不大。而Proxy可以支持数组的各种API。
  4. 尽管Object.defineProperty有诸多缺陷,但是其兼容性要好于Proxy。