2023年及以前高频前端面试题的大致总结

基于以下网址整理

2023年及以前高频前端面试题

html与css

1. <img>的title与alt有什么区别

title 鼠标悬浮的提示文字(所有标签都存在)
alt 图片加载失败时展示的提示文字

2. label的作用是什么?怎么用

1. <label for="id1">id</label><input type="text" id="id1">
2. <label>IDD <input type="text"></label>
label配合form表单的input/button,通过id或者行内联系,会自动对焦

3. input的type属性

<input type="password"> 密码
<input type="text"> 文本
<input type="radio" name="this_radio"> 单选 name属性唯一时才能正确触发
<input type="checkbox"> 复选
<input type="reset"> 重置
<input type="submit"> 发送

4. title与h1区别,b与strong的区别,i与em的区别

title 不是内置标签, 没有确切意义
h1 内置标签,第一标题 字体加粗、加大 h2,h3,h4,h5,h6

b 加粗标签
strong 加粗标签,更加语义化

i 倾斜字体
em 倾斜字体,更加语义化

5. 页面导入样式时,使用 link 和 @import 有什么区别

link html内置标签,可加载css/ico等等。页面加载时则加载link标签的信息
@import css提供写法,仅能加载css。在页面加载完毕后才能执行加载

6. 如何实现浏览器内多个标签之间的通信

1. 使用 localStorage 监听变化

a.html
<script>
    window.addEventListener("storage", function (e) {
        console.log(e);
    });
</script>

b.html
<script>
    window.localStorage.setItem("key", "value");
</script>

b.html 和 a.html 在浏览器两个标签打开有效。
b.html 触发 setItem 时,a.html 会监听到变化,同页面不会监听到变化。

2. cookie + setInterval
<script>
    // 这里监听
    setInterval(function () {
        console.log(document.cookie);
    }, 1000);
    // 这里设置cookie值
    document.cookie = Math.random().toString();
</script>

7. 浏览器内核的理解

浏览器主要分为两个部分:渲染引擎、js引擎。
渲染引擎:获取页面内容和渲染排版页面内容。
也叫做布局引擎或者浏览器引擎,负责解析html、css代码,并将其转换为可视化的网页。
html解析:将html解析为dom(文本对象模型)树,表示网页的结构。
css解析:将css应用于dom树,确定网页布局以及样式。
布局和绘制:根据dom和css的解析结果计算出每个元素在页面上的大小、位置,并且绘制出来。
js引擎:解析和执行js来实现页面的动态效果,以及交互内容。
专门用于js执行后应用到布局中,显示效果。

8. 简述一下你对HTML 语义化的理解

更容易理解代码含义。
便于搜索引擎。

9. 浏览器是怎么对 HTML5 进行离线缓存资源进行管理和加载的

1. html 标签的manifest属性。
<html manifest="a.appcache" lang="zh"></html>
首次加载页面会缓存,再次离线访问会访问缓存文件,更改.appcache文件后会更新本地缓存。

2. Service Worker
基于web worker实现,增加web worker缓存功能,可实现离线缓存功能,支持生命周期。

10. 描述sessionStorage与localStorage、cookie的区别

sessionStorage、localStorage 储存在本地缓存中,不会发送到服务器。
cookie 储存本地缓存中,会发送到服务器。
共同:都是本地缓存中。

localStorage 数据关闭浏览器不清空,多个页面标签共享(数据需要长时间保存、大量数据时使用,5M string)。
sessionStorage 关闭浏览器清空,多个页面标签不同共享(数据关闭浏览器就清空时使用,string)。
cookie 过期时间前不失效,多个页面标签共享(4k ASCII)。
多个页面标签共享(同源的情况下)。

11. 行内元素,块级元素和空元素有哪些

行内元素
<a href="">链接</a>
<b>加粗</b>
<span>文本</span>
<img src="" alt="">
<input type="text">
<select name="" id="">选择框</select>
<strong>加粗</strong>
<i>斜体</i>
<em>斜体</em>

块元素
<div></div>
<p></p>
<ul>
    <li></li>
</ul>
<ol>
    <li></li>
</ol>
<dl>
    <dt></dt>
    <dd></dd>
</dl>
<h1></h1>
<h2></h2>
<h3></h3>
<h4></h4>
<h5></h5>
<h6></h6>
<table>
    <tr>
        <td></td>
    </tr>
</table>

空元素
<br>
<hr>
<img src="" alt="">
<link rel="stylesheet" href="">
<meta>

CSS

1. 什么是盒模型

盒模型两总:IE、W3C(标准盒模型)
Ie: Width、Height 包含了 margin、padding、content
W3C:Width、Height 只包含 content

2. css如何转换盒子模型

ie
<style>
    body {
        box-sizing: border-box;
    }
</style>
w3c
<style>
    body {
        box-sizing: content-box;
    }
</style>

3. 行内元素有哪些?块级元素有哪些?区别

行内元素 一行多个,width(内容宽度)、height(内容高度)设置无效。
只能放置行内元素、文本,链接里面不能放置链接。
<span></span>
<i></i>
<em></em>
<s></s>
<b></b>
<storage></storage>
<img src="" alt="">
<input type="text">
<select name="" id=""></select>

块级元素 一行一个,修改width、height、margin、padding,默认width 100%。
除了p、h系列标签外的,可放置其他元素
<div></div>
<p></p>
<h1></h1>
***
<h6></h6>
<table>
    <tr>
        <td></td>
    </tr>
</table>
<ul>
    <li></li>
</ul>
<ol>
    <li></li>
</ol>

4. 如何解决盒子塌陷

盒子塌陷是什么?
本应该在父盒子内部的元素跑到了外部。
为什么会出现盒子塌陷?
当父元素没设置足够大小的时候,而子元素设置了浮动的属性,子元素就会跳出父元素的边界(脱离文档流),
尤其是当父元素的高度为auto时,而父元素中又没有其它非浮动的可见元素时,
父盒子的高度就会直接塌陷为零, 我们称这是CSS高度塌陷。
解决:
1. 设置父元素的高宽
2. 浮动元素的下一个兄弟元素设置 clear属性
<style>
    div {
        clear: both;
    }
</style>
3. 设置父元素浮动
4. 父元素设置溢出隐藏
<style>
    div {
        overflow: hidden;
    }
</style>
5. 父元素 ::after 伪元素设置 clear 属性
<style>
    div::after {
        clear: both;
        content: "";
        display: block;
    }
</style>

5. display 有哪些哪些值?说明他们的作用

div {
    /* 设置为盒子模型 */
    display: flex;
    /* 设置为块元素 */
    display: block;
    /*设置为行内元素*/
    display: inline;
    /*设置为行内块元素*/
    display: inline-block;
    /*设置为表格*/
    display: table;
    /*设置为网格*/
    display: grid;
    /*设置为不显示*/
    display: none;
    /*继承父元素*/
    display: inherit;
}

6. CSS 选择器有哪些

/*id选择器*/
#id {
}

/*class 选择器*/
.class {
}

/*标签选择器*/
p {
}

/*通配符*/
* {
}

/*复合选择器*/
ul li {
}

/*子级选择器*/
ul > li {
}

/*后代选择器*/
div p {
}

/*并集选择器*/
div, p, ul {
}

/*伪类、伪元素选择器*/
div:hover,
div:active,
div::after,
div::before {
    content: "";
}

7. css 选择器权重

选择器        |   权值    |  权级
--------------------------------
!important   |   10000  |  5
--------------------------------
内联          |   1000   |  4
--------------------------------
id           |   100    |  3
--------------------------------
class        |   10     |  2
--------------------------------
元素          |   1      |  1
--------------------------------
*            |   0      |  0
--------------------------------
就近原则,后面的样式大于前面的样式

8. 浮动

浮动特性
1. 浮动的元素会脱离标准源
2. 浮动的元素会一行显示并且顶部对齐(上边缘对齐,一行显示,溢出换行)
3. 浮动的元素会具有行内块的特性
4. 脱离标准流的浮动盒子会移动到指定位置
5. 浮动的盒子不再保留预先的位置
6. 清除浮动的高、宽度影响
<style>
    p {
        clear: both;
    }
</style>

9. 浮动产生的问题

当父元素不设置高度时,内部元素浮动。
则父元素的高度会为0,影响父元素后面的标准流。

10. 清除浮动的方式

1. 在浮动元素后面添加块级元素设置css属性
<style>
    p {
        clear: both;
    }
</style>
2. 父元素添加
<style>
    div {
        overflow: hidden;
    }
</style>
3. 父元素添加伪元素
<style>
    div::after {
        content: "";
        display: block;
        clear: both;
    }
</style>

11. 伪类选择器和伪元素的区别

伪类
伪类是添加到选择器的关键字,用于指定所选元素的特殊状态(单冒号)。
<h6>以下常用</h6>
:active 被用户激活的元素
:focus 获取到焦点的元素
:hover 用户使用指针设备与元素进行交互时匹配,但不一定激活
:link 尚未被访问的元素,匹配每个具有href属性的未访问的a、area元素
:visited 在用户访问链接后使用
:first-child 在一组兄弟元素中的第一个元素
:not() 表示其参数列表中未选择的其他元素

伪元素
伪元素是一个附加到选择器末尾的关键词,允许对选择的元素的特定部分修改样式。
::after 创建一个伪元素,成为选择的元素的最后一个子元素
::before 创建一个伪元素,成为选择的元素的第一个子元素
::first-line 在块级元素的第一行应用的样式

12. css定位position方式

/*常用方式*/
div {
    /*正常文本流布局*/
    position: static;
    /*相对定位*/
    /*再不改变页面布局的前提下调整元素的位置,相对于自身的原本位置,会在原来的位置添加空白*/
    position: relative;
    /*绝对定位*/
    /*移出文本流,不预留空间。指定元素通过相对于最近的非static定位的祖先元素的偏移来定位。*/
    position: absolute;
    /*固定定位*/
    /*移除文本流,不预留空间。通过屏幕视口的位置来指定元素的位置。滚动时也不会更改位置。*/
    position: fixed;
    /*粘性定位*/
    /*正常文本流布局。根据相对于最近的滚动祖先元素,进行偏移。*/
    /*通常用于元素滚动置顶*/
    position: sticky;
}

13. css三角形

div {
    width: 0;
    height: 0;
    position: absolute;
    border: 10px solid transparent;
    border-bottom-width: 0;
    border-top-color: red;
}

14. css实现单行文本显示

div {
    white-space: nowrap;
    width: 100px;
    overflow: hidden;
    text-overflow: ellipsis;
}

15. display 和 visibility显示与隐藏的区别

display: none; 隐藏后无占位
visibility: visible/hidden 隐藏后占位
overflow: hidden/visible 溢出部分隐藏

16. 解释一下CSS3的flexbox(弹性盒布局模型),以及使用场景

一种一维的布局模型。给子元素提供了强大的空间分布和对齐能力。
(简化网页布局开发)
特点
1. 主轴与交叉轴:主轴(main axis)交叉轴(cross axis),默认主轴水平,交叉轴垂直。
2. 弹性容器:父元素设置为flex、inline-flex 来创建。
    子元素都是弹性项目,并且可以自定义在主轴上的大小、排序、对齐方式。
3. 主轴对齐:弹性项目可在主轴上按照一定比例分配空间,justify-content定义主轴对齐方式。
4. 交叉轴对齐:弹性项目可在交叉轴进行对齐,包括顶部、底部、居中对齐等,align-items定义交叉轴对齐方式。
5. 换行与自动调整:可控制弹性项目是否换行,并且具备自动调整元素大小的能力

常用属性
    display: flex;              设置为弹性容器
    flex-direction: row;        设置主轴方向
                                    row(default)    水平,主轴跟文字方向一样,
                                                    主轴起点和主轴终点与内容方向相同
                                    row-reverse     row 一样,切换主轴终点与起点的位置
                                    column          垂直,主轴跟块轴方向一致,
                                                    主轴起点与终点和书写模式的前后点相同
                                    column-reverse  column 一样,切换主轴终点与起点的位置
    justify-content: center;    沿着弹性容器的主轴分配元素之间的空间
                                    flex-start      排列在主轴的起始位置
                                    flex-end        排列在主轴的结束位置
                                    center          排列在主轴的中间位置
                                    space-between   沿着主轴均匀分布,相邻元素距离相同。首尾元素距离其起始、结束的位置无
                                    space-around    沿着主轴均匀分布,相邻元素距离相同。首尾元素距离其起始、结束的位置为相邻元素的距离一半
                                    space-evenly    所有相邻的元素距离相同,包括起始、结束位置
    justify-items: center;      同align-items一样,表现为主轴对齐方式
    align-self: center;         同align-self一样,表现为主轴方向
    align-items: center;        设置所有子元素的align-self值作为一个组,控制子元素的交叉轴对齐方式
                                    stretch     小于容器的大小时,会自动调节子元素大小,填满容器,同时这些元素保持其宽高比例的约束
                                    center      交叉轴中间
                                    flex-start  交叉轴的起始位置
                                    flex-end    交叉轴的结束位置
    align-self: center;         对齐当前的grid或者flex行中的元素在交叉轴的方向,并且覆盖align-items
                                    auto        设置为父元素的align-items值
                                    center      交叉轴中间
                                    flex-start  交叉轴的起始位置
                                    flex-end    交叉轴的结束位置
                                    
    align-content: center;      沿着弹性盒子布局的`交叉轴`和网格布局的主轴在内容项的周围分配空间
                                    仅多行生效
                                    (存在额外高度的情况下)center 则将内容居中...
    flex-wrap: wrap;            指定弹性项目的排列方式,单行还是多行,多行可控制堆叠方向
                                    nowrap  单行显示,可能溢出
                                    wrap    多行显示
                                    wrap-reverse    多行显示,倒叙
    flex-grow: 1;               拉伸弹性属性
    flex-shrink: 1;             收缩弹性属性
    flex-basis: 1;              弹性属性的初始大小
    flex: 1;                    flex-grow flex-shrink flex-basis 三合一属性
    flex-flow: row wrap;        flex-direction,flex-wrap 二合一属性

17. 图片懒加载

配置观察者链接

/* 表现问题:多张图片同时加载,导致服务器压力、页面卡顿
 解决方案:在可视区域展示的图片dom才进行加载 */

// 1. 创建观察者 //

// observer配置项
var observerOptions = {
    threshold: .5, //目标元素与视窗重叠的阈值(0~1)
    root: null // 目标视窗即目标元素的父元素,如果没有提供,则默认body元素
};

//实例化观察者对象
var observer = new IntersectionObserver(observerCallback, observerOptions);

//observer 回调函数
const observerCallback = (entries) => {
    entries.forEach(item => {
        /*
         * item.time发生相交到相应的时间,毫秒
         * item.rootBounds:根元素矩形区域的信息,如果没有设置根元素则返回 null,图中蓝色部分区域。
         * item.boundingClientRect:目标元素的矩形区域的信息,图中黑色边框的区域。
         * item.intersectionRect:目标元素与视口(或根元素)的交叉区域的信息,图中蓝色方块和粉红色方块相交的区域。
         * item.isIntersecting:目标元素与根元素是否相交
         * item.intersectionRatio:目标元素与视口(或根元素)的相交比例。
         * item.target:目标元素,图中黑色边框的部分。
         */
        // 当前元素可见
        if (item.isIntersecting) {
            {
                // ...业务逻辑
            }
            // 解除观察当前元素 避免不可见时候再次调用callback函数
            observer.unobserve(item.target);
        }
    });
};

// 2. 传入被观察者 //

//获取目标元素
const target = document.getElementById(elemtentId);

//将目标元素传入观察对象
observer.observe(target);

18. 常见的内存泄漏

内存泄露会导致:运行缓慢,崩溃,高延迟
说明:访问不了的变量使用了巨大的内存空间,不能再次访问
问题出现:
    1. 全局变量使用问题
    2. 闭包导致
    3. 定时器、延时器未处理
    4. 没有处理的dom元素引用(清空、删除时,未清除引用)

19. 网站页面常见优化的手段

1. 优化图片策略 webp>jpg>png>bmp
2. 使用压缩文件 js.min.js、css.min.css
3. 使用浏览器缓存 cookie、localStorage,尽量不使用浏览器的sql
4. 减少请求次数
5. 使用图片懒加载
6. 样式:避免使用@import,选择器复杂程度尽量控制3层 【.div1 .div2 .div3{这种}】

20. px与em的区别

px em 都是长度单位
px是固定值,em是相对父级文字大小的相对值
rem 是css3新增单位,相对于根元素的文字大小的相对值

21. 同步与异步

同步是阻塞模式(需要等待执行结束才能继续执行),异步是非阻塞模式(不需要等待结果就能继续执行)

22. 盒子水平垂直居中方法(高频)

1. 使用绝对定位 + transform,父元素相对定位,子元素绝对定位 left、top 50%,transform (x,y)(-50%,-50%)
2. 父元素flex,设置 align-items: center;(垂直居中) justify-content: center;(水平布局)
3. 父元素设置 display: table-cell;
    父元素:
        display: table-cell;
        vertical-align: middle;
        text-align: center;
    子元素:
        display: inline-block;

23. CSS3与HTML新属性

1. html5
1. 语义化标签
<header>头部</header>
<footer>尾部</footer>
<section>区域</section>
<nav>导航</nav>
<aside>侧边栏</aside>
<article>内容</article>
2. 增强型表单
<input type="email"> 邮箱
<input type="url"> 链接
<input type="search"> 搜索
<input type="date"> 日期
<input type="time"> 时间
<input type="month"><input type="week"><input type="number"> 数字
<input type="tel"> 手机号码
<input type="color"> 颜色选择器
3. 表单属性
placeholder 提示文本、required 必填
4. 音频视频
<audio src=""></audio>
<video src=""></video>
5. 画布
<canvas></canvas>
6. 地理定位
7. 拖拽
8. 本地储存
localStorage/session
9. 新事件
onresize ondrag onscroll onmousewheel onerror onplay onpause
10. webSocket 长连接
2. css3
1. 新增选择器
. 属性选择器
. 结构伪类选择器
. 伪元素选择器

2. 属性选择器
<style>
    div[attr] {
        /*选择具有attr属性的div元素*/
    }

    div[attr="v"] {
        /*选择attr为v的元素*/
    }

    div[attr^="v"] {
        /*选择attr开头值为v的元素*/
    }

    div[attr$="v"] {
        /*选择attr结尾值为v的元素*/
    }

    div[attr*="v"] {
        /*选择attr中含有v的元素*/
    }
</style>

3. 结构伪类选择器
<style>
    ul li:first-child {
        /*在一组兄弟元素的第一个元素*/
    }

    ul li:last-child {
        /*在一组兄弟元素的最后一个元素*/
    }

    ul li:nth-child(n) {
        /*根据父元素内的所有子元素的位置来选择子元素。所有父元素的所有子元素中的第n个元素*/
    }

    ul li:first-of-type {
        /*一组兄弟元素中其类型的第一个元素*/
    }

    ul li:last-of-type {
        /*一组兄弟元素中其类型的最后一个元素*/
    }

    ul li:nth-of-type(n) {
        /*一组兄弟元素中其类型的第n个元素*/
    }
</style>

4. 伪元素选择器
<style>
    div::before {
        /*创建一个伪元素,作为当前元素的第一个子元素。默认行内样式,需要配置content属性*/
    }

    div::after {
        /*创建一个伪元素,作为当前元素的最后一个子元素。默认行内样式,需要配置content属性*/
    }
</style>

5. 盒子模型
<style>
    div {
        /*固定盒子width、height,padding、margin不影响盒子自身大小*/
        box-sizing: border-box;
        /*固定盒子width、height,padding、margin会影响到盒子自身大小*/
        box-sizing: content-box;
    }
</style>

6. 渐变
<style>
    div {
        /*线性渐变*/
        background: linear-gradient(45deg, black, transparent);
        /*径向渐变*/
        background: radial-gradient(black, white);
    }
</style>
7. 过渡
<style>
    div {
        transition: 1s;
    }
</style>
8. 2d、3d
<style>
    div {
        transform: translate(1px);
    }
</style>
9. 动画 animation
<style>
    div {
        animation: animation01 1s;
    }

    @keyframes animation01 {
        0% {
            color: white;
        }
        29% {
            color: red;
        }
        100% {
            color: blue;
        }
    }
</style>

JS

1. 数据类型有哪些

// ***** ts写法 *****
// 基本类型
const str: string = "";
const num: number = 1;
const bol: boolean = false;
const nul: null = null;
const und: undefined = undefined;
Symbol;// 附带 唯一的玩意儿,使用方式:new Symbol()。
// 引用类型
const obj: object = {};
object; /* 子类型 >> {
    array,
    function
} */
// 5个基础类型 + 1个引用类型

2. typeof返回哪些数据类型

console.log(typeof "string"); // string
console.log(typeof 1); // number
console.log(typeof .03); // number
console.log(typeof false); // boolean
console.log(typeof null); // object
console.log(typeof undefined); // undefined
console.log(typeof Symbol()); // symbol
console.log(typeof {}); // object
console.log(typeof []); // object
console.log(typeof function (a) {

}); // function
console.log(typeof Math); // object
console.log(typeof Date); // function
console.log(typeof Array); // function
console.log(typeof String); // function
console.log(typeof (2021 + undefined)); // number; 2021 + undefined => NaN; typeof NaN => number
console.log(typeof a); // undefined 此处是不存在的变量
/*
string
number
boolean
object
function
symbol
undefined
 */

3. 强制类型转换和隐式转换

// 强制转换,人为转换类型
parseInt("446");
parseFloat("5");
Number("96");
// 隐式转换,通过符号等JavaScript自动转换类型
console.log(1 + "a"); // 1a
console.log(1 - "2"); // -1
console.log(1 * "3"); // 3
console.log(1 / "4"); // 0.25
console.log(1 == "5"); // false
console.log(1 === "6"); // false
console.log(!"a"); // false

4. 三大事件

// 鼠标事件
const _div = document.createElement("div");
_div.onclick = function (e) {
    console.log("鼠标移开点击事件", e);
}
_div.ondblclick = function (e) {
    console.log("鼠标移开双击点击事件", e);
}
_div.onmousedown = function (e) {
    console.log("鼠标移开按下事件", e);
}
_div.onmouseup = function (e) {
    console.log("鼠标移开抬起事件", e);
}
_div.onmouseover = function (e) {
    console.log("鼠标移开悬浮事件", e);
}
_div.onmouseout = function (e) {
    console.log("鼠标移开事件", e);
}
_div.onmousemove = function (e) {
    console.log("鼠标移动事件", e);
}
_div.onmouseenter = function (e) {
    console.log("鼠标进入事件", e);
}
_div.onmouseleave = function (e) {
    console.log("鼠标离开事件", e);
}
// 键盘事件
const _div1 = document.createElement("div");
_div1.onkeyup = function (e) {
    console.log("按键抬起", e);
}
_div1.onkeydown = function (e) {
    console.log("按键按下", e);
}
_div1.onkeypress = function (e) {
    console.log("按键按下抬起", e);
}
// HTML事件
window.onload = function (e) {
    console.log("文档加载完成", e);
}
document.createElement("input").onselect = function (e) {
    console.log("选择器选择", e);
}
document.createElement("input").onchange = function (e) {
    console.log("内容被改变", e);
}
document.createElement("input").onfocus = function (e) {
    console.log("得到光标", e);
}
window.onresize = function (e) {
    console.log("窗口尺寸变化", e);
}
document.createElement("div").onscroll = function (e) {
    console.log("滚动事件", e);
}

5. 同源策略

一段脚本只能读取来自同一源的窗口和文档的属性,同一来源:主机号、协议、端口号。[host,http,port]

6. 数据类型转换

(11).toString();
parseInt("1111");
parseFloat("90.2");
Number("566");
Boolean("e");

7. 判断数据类型 typeof 和 instanceof 有什么区别

// 1. typeof 运算符返回一个字符串,表示变量的类型。
// 获取变量s的类型
var s = 1;
console.log(typeof s);

// 2. instanceof 用于检测某个构造函数是否出现在实例对象的原型链上

class SSS {
}

class AAA {
}

console.log(new SSS() instanceof SSS); // true
console.log(new SSS() instanceof AAA); // false

// 3. constructor 判断数据类型
true.constructor === Boolean // true

// 4. Object.prototype.toString.call(arg) 判断数据类型 返回 [object (数据类型)]
Object.prototype.toString.call(12); // [object Number]
Object.prototype.toString.call("aaa"); // [object String]

8. 数组操作方法

var array = [1, 2, 3, 4];
// 添加一个元素
array.push("666");
// 计算长度
console.log(array.length);
// 返回指定元素的下标,无则返回-1
console.log(array.indexOf(2), array.indexOf("666000"), array.indexOf("666"));
// 删除最后一个元素
array.pop();
// 数组起始位置添加一个元素
array.unshift("噢噢噢噢");
// 删除第一个元素
array.shift();
// 排序
array.sort(function (a, b) {
    // 升序
    return a - b;
    // 降序
    return b - a;
});
// 数据反转
array.reverse();
// 两个数组合并为新数组
array.concat([222]);
// 筛选
array.filter(function (value, index, array) {
    // false 抛弃 true 留下 
    return false;
});
// 遍历数组
array.forEach(function (value, index, array) {
    console.log(value);
});
// 合并数组为字符串,使用间隔符号
array.join("间隔符号");
// 截取新的数组 返回新的数组
var startIndex = 0;
var endIndex = -1;
array.slice(startIndex, endIndex);
// 删除元素 参数(起始下标,长度)
array.splice(0, 1);
// Es6 新增的方法
// 遍历数组
array.forEach();
// 遍历数组,使用返回的值组成新数组
array.map();
// 筛选
array.filter();
// 查询数组的某个元素,返回查询到的元素。搜索函数返回true则停止搜索并返回。null
array.find();
// 查询数组的某个元素的下标,同上find方法。
array.findIndex();

9. ==与===的区别

== 值对比,会出现隐式类型转换
=== 值、类型对比

10. 字符串操作办法

var string = "123456789";
// 使用分隔符号分割字符串为数组
string.split("分割符");
// 返回字符串首次出现的位置
string.indexOf("a");
// 提取起始下标到结束下标的字符串
string.substring(1, 4);
// 字符串拼接
string.concat("aaa");
// 去除两边的空格
string.trim();
string.trimStart();
string.trimEnd();
// 字符串替换
string.replace("需要替换的东西,可以是正则、字符串", "替换成的字符串");
// 从起始位置开始,截取指定长度的字符串
string.substr(1, 3);
// 大写
string.toUpperCase();
// 小写
string.toLowerCase();

11. 作用域与变量声明

作用域:变量、函数,仅在这个范围使用,其他范围不能使用的。
    分为全局作用域,局部作用域。(全局就是到处都能访问的,局部就是在固定位置才能访问的)
作用域链:
    查询变量的过程。
    先查找自己作用域是否存在变量,不存在则向上查询,直到全局作用域区域查询失败则放弃查询。在这个查询过程的一层一层关系,就是作用域链
全局变量
    全局范围声明的变量。
局部变量
    函数体内申明的变量,仅在函数体内能使用。
变量提升
    预编译阶段,编译器会将所有定义的变量提升到最顶部。

12. 预解析

js引擎会将js代码里面的所有变量、函数声明语句提升到当前作用域最前面
预解析分为:
    1. 变量提升。将所有的变量声明的var提升到当前作用域的最前面,不提供赋值操作。
    2. 函数提升。将所有的函数声明function提升到当前作用域的最前面,不调用函数

13. this指向

箭头函数:箭头函数没有自己的参数列表(argument)、this,箭头函数声明所在的作用域就是它的this。
全局中的this:window,严格模式:undefined
函数中的this:方法被调用时,函数中的this指向调用方法的对象
构造函数的this:通过构造函数新建的对象
call,apply:this就指向它们传入对象的值 => func.call(b);// func的this指向b 
函数在事件中被调用时:this指向触发事件的元素

14. call,bind,apply 区别、联系

共同:
    都可以改变this的指向。
    第一个参数都是this要指向的对象。
区别:
    call,bind 可以传入多个参数,apply仅能传入两个参数
    call,apply 都是返回一个值,bind返回函数
使用:
    call,Function实例的call方法会以给定的this值和逐个参数调用函数
        语法:function.call(thisArg,arg1,arg2...)
        也就是说普通方法调用fun(a参数),this是上下文
        调用fun函数时:fun.call(其他this,a参数),this会变成·其他this·
    bind,Function实例的bind方法创建一个新函数,当调用该新函数时,它会调用原始函数将其this关键字设置为指定的值,
        同时,还可以传入一系列指定的参数,这些参数会插入到调用新函数时传入的参数的前面。
        语法:function.bind(thisArg,arg1,arg2...)
        新函数 = 函数.bind(新this,a参数,b参数);
        新函数(c参数);=> 函数(a参数,b参数,c参数)其中的this是新this
    apply,Function实例的apply()方法会给定的this值和作为数组(或者类数组对象)提供的arguments调用该函数
        语法:function.apply(thisArg,[argsArray])
        函数.apply(新this,[a参数,b参数]);=> 函数(a参数,b参数)其中this是新this

15. 原型与原型链

原型:
    每个函数都有一个prototype属性,称为显式原型。
    每个对象都有一个__proto__属性,成为隐式原型。
    每个实例化对象的隐式原型,指向自身构造函数的显式原型。每个显示原型都有一个contructor属性,指向构造函数。
原型链:
    在访问一个对象的属性时,如果这个对象没有这个属性,则会向原型上查询,还没有就继续查询下一级原型,
        直到查询到Object.prototype(值:null)原型对象为止,这个过程则称为原型链。

16. 继承方式有哪些

//   -----------------  原型链继承  -----------------  
/* 弊端:原型链继承当原型中存在引用值时,实例可以修改值 */
function a1() {
}

a1.prototype._name = "";
a1.prototype.infos = [1, 2];
a1.prototype.getName = function () {
    console.log(JSON.stringify(this.infos), this._name);
    return this._name;
}
a1.prototype.setName = function (name) {
    this._name = name;
    this.infos.push("666");
    return this;
}

function a2(name) {
    this._name = name;
}

function a3() {
}

/* a2/a3 继承 a1 */
a2.prototype = new a1();
a3.prototype = new a1();
/* a2 的构造函数指向自身 */
a2.prototype.constructor = a2;
console.log(a1.prototype, a2.prototype, a3.prototype)
new a2("aha").getName();// [1,2] aha
new a2("aha").setName("vvv").getName(); // [1,2,"666"] vvv
new a2("aha").getName(); // [1,2,"666"] aha。修改infos对象后,会影响到所有的实例对象的infos

//   -----------------  构造继承  -----------------  
function b1(name) {
    this._name = name;
    this.getName = function () {
        console.log(this._name);
        return this._name;
    }
}

function b2(name) {
    // 继承b1
    b1.apply(this, arguments);
    this.setName = function (name) {
        this._name = name;
        return this;
    }
}

new b2("a").getName();
new b2("b").setName("dsa").getName();

//   -----------------  实例继承  -----------------  
function C1(name) {
    this._name = name;
    this.setName = function (name) {
        this._name = name;
        return this;
    }
}

C1.prototype.show = function () {
    console.log(this._name)
}

function C2(name) {
    return new C1(name);
}

new C2("实例继承").show();
/* 继承后C2的实例则是C1,不支持多继承 */
console.log(new C2("a") instanceof C1, new C2("a") instanceof C2)

//   -----------------  拷贝继承  -----------------  
function d1(name) {
    this._name = name;
    this.setName = function (name) {
        this._name = name;
        return this;
    }
}

function d2(name) {
    this.getName = function () {
        console.log(this._name);
        return this._name;
    }
    var _d1 = new d1(name);
    for (let key in _d1) {
        this[key] = _d1[key];
    }
}

new d2("拷贝继承").setName("拷贝继承1").getName();

//   -----------------  组合继承  -----------------  
function e1(name) {
    this._name = name;
    this.setName = function (name) {
        this._name = name;
        return this;
    }
    return this;
}

e1.prototype.show = function () {
    console.log(this._name)
}

function e2(name) {
    // 提供e2的this代替e1的this指向
    // (e1使用的this)是(call提供的e2的this)
    e1.call(this, name);
    this.name1 = "999";
}

// new e2("aaa").show(); // 会报错,因为下列第一点
/*
1. 将e2的原型修改为e1的原型(e2.prototype = new e1()),会导致原型上的声明定义的信息无法使用。
2. 因为e1的原型未做修改(e1.call(this, name);),仅修改了在e1内部的this指向,并未修改到原型。
3. 不使用 e2.prototype = e1.prototype; 因为修改e2会直接影响到e1的原型(此时e1,e2的原型是完全相同的)。
4. 使用 e2.prototype = new e1(); 因为实例化e1后会生成e1自身的__proto__原型来访问到e1的原型对象。
5. 使用 e2.prototype.constructor = e2; 因为 e2.prototype = new e1();后 e2的实例化对象中不存在constructor,在实例化对象中的__proto__会指向new e1()的构造函数(一个构造函数)
    所以需要将e2.prototype.constructor 修改为e2,将e2的constructor指向e2的构造函数。
 */
e2.prototype = new e1();
e2.prototype.constructor = e2;
e2.prototype.show1 = function () {
    console.log("show1", this._name)
}
new e2("组合继承").show();
new e2("组合继承1").setName("组合继承11").show1();

//   -----------------  寄生式组合继承  -----------------  

//   -----------------  es6的extends继承  -----------------  
class g1 {
    _name = "";

    constructor(value) {
        this._name = value
    }

    getName() {
        console.log(this._name);
        return this._name;
    }

    setName(name) {
        this._name = name;
        return this;
    }
}

class g2 extends g1 {
    constructor(value) {
        super(value);
    }
}

new g2("ding").getName();
new g2("ding").setName("啧啧额").getName();

17. 闭包

一个函数有权访问另外一个函数作用域的变量就是闭包。
在一个函数外包访问在这个函数内部定义的局部变量,就需要在一个函数内部再定义一个函数,通过return返回到函数外部,这个过程就是闭包。
简单理解为,在函数内部定义参数不会变化的函数。
应用场景:Ajax、setTimeout、防抖、节流、事件绑定的回调等等
缺陷:由于闭包打破了函数的作用域的限制,导致数据无法清除、销毁,数据量过大可能导致数据溢出。
实例:
<script>
    var c = -1;
    while (++c < 10) {
        (function (c) {
            setTimeout(() => {
                console.log(c);
            }, 100);
        })(c);
    }
</script>

18. 冒泡和捕获、事件流

/*
js中事件冒泡是为了捕获和处理DOM内部传播的事件

事件
    事件就是可以被JavaScript侦测到的行为
事件流
    描述的是页面中接受事件的顺序
事件冒泡
    (个人理解)就是一层一层的向上向外传播的动作
事件捕获
    (个人理解)就是一层一层的向下向内传播的动作

javaScript事件的触发流程(事件流)
    1. 捕获阶段。事件方向为从父元素开始向目标元素传播,从window对象开始传播。
    2. 目标阶段。该事件到达目标元素或者开始该事件的元素。
    3. 冒泡阶段。事件方向为从目标元素向父元素传播,直到window对象结束。
*/
e.stopPropagation(); // 阻止冒泡
e.preventDefault(); // 阻止默认事件

// 添加事件
target.addEventListener(type, listener);
target.addEventListener(type, listener, options);
target.addEventListener(type, listener, useCapture);
/*
type        事件类型(表示监听事件类型的大小写敏感的字符串)
listener    监听事件触发执行的函数(当所监听的事件类型触发时,会接受到一个事件通知对象。
                listener必须是一个是实现了EventListener接口的对象、或者是一个函数)
options     一个关于listener属性的可选参数对象
            {
                // 表示listener会在该类型的事件捕获阶段传播到EventTarget时触发
                capture: Boolean
                // 添加listener后,仅能触发一次
                once: Boolean
                // 设置为true时,表示listener永远不会调用preventDefault()。
                // 如果listener仍然调用这个函数,则触发警告
                passive: Boolean
                // 该AbortSignal的abort()方法被调用时,监听器将会被移除
                signal: AbortSignal
            }
useCapture  Boolean,默认false。true时,沿着DOM树向上冒泡的事件不会触发listener。
                当一个元素嵌套了另外一个元素并且两个元素都对同一事件注册了一个处理函数时,
                所发生的事件冒泡和事件捕获是两种不同的事件传播方式。(事件传播顺序)
 */

19. 事件委托(事件代理)

事件代理是借用 事件捕获和事件冒泡 的机制,委托给目标元素的上级,用来监听事件。
事件代理原理就是DOM元素的事件冒泡。
事件三个阶段
1. 捕获
2. 目标
3. 冒泡 向上传播,向父级传播。就是在此阶段将目标元素的事件绑定到父级来触发

好处
1. 可以节省大量的内存占用,减少事件注册
2. 可以实现新增子DOM时无需再次对其绑定(动态绑定事件)

20. 如何阻止事件冒泡与默认事件

document.body.onclick = function (e) {
    e.stopPropagation(); // 阻止冒泡
    e.preventDefault(); // 阻止默认事件   
}

21. New操作符在创建实例的时候经历了哪几个阶段

var obj = new Obj();[Obj: 构造函数]
1. 创建一个空的javascript对象
2. 将新对象的prototype指向构造函数的prototype
3. 给定参数执行构造函数,并且绑定this为新对象
4. 如果构造函数返回非原始值,则该返回值成为整个new表达式的结果。否则返回新对象为值。
<script>
    function ClassNew(a, b) {
        this.name = false;
        this.a = a;
        this.b = b;
    }

    function newFunc(fn, ...args) {
        // 1. 创建一个空的javascript对象
        var obj = {};
        // 将新对象的prototype指向构造函数的prototype
        obj.__proto__ = fn.prototype;
        // 3. 给定参数执行构造函数,并且绑定this为新对象
        var res = fn.call(obj, ...args);
        // 4. 如果构造函数返回非原始值,则该返回值成为整个new表达式的结果。否则返回新对象为值。
        return res || obj;
    }

    console.log(newFunc(ClassNew, 1, 2));
</script>

22. 异步编程的实现方式

1. 回调函数
优点:简单、容易理解
缺点:不利于维护,代码耦合高
<script>
    function x(callback) {
        // 模拟异步
        setTimeout(function () {
            callback && callback();
        }, 1000);
    }

    function x1() {
        console.log("x1")
    }

    x(x1);
</script>

2. 事件监听,采用事件驱动模式。
优点:可以绑定任意事件、回调
缺点:流程不清晰
<script>
    Object.prototype.on = function (key, fn) {
        this.keys || (this.keys = {});
        this.keys[key] = fn;
        return this;
    }
    Object.prototype.trigger = function (key) {
        this.keys && this.keys[key] && this.keys[key]();
    }

    function on1() {
        on1.trigger("do");
        on1.trigger("do");
        on1.trigger("do");
    }

    function on2() {
        console.log("run do");
    }

    on1.on("do", on2)
    on1();
</script>

3. 发布/订阅
<script>
    // 订阅、发布
    var Subscribe1 = {
        _s: {},
        on(name, callback) {
            if (!this._s[name]) {
                this._s[name] = [];
            }
            var c = -1;
            while (++c < this._s[name].length) {
                var e = this._s[name][c];
                if (e === callback) {
                    return;
                }
            }
            this._s[name].push({
                callback
            });
        },
        emit(name, ...args) {
            var _this = this;
            if (this._s[name]) {
                this._s[name].forEach(function (e) {
                    e.callback.call(_this, ...args);
                })
            }
        },
        off(name) {
            if (this._s[name]) {
                this._s[name] = undefined;
                delete this._s[name];
            }
        }
    }
    Subscribe1.on("on1", function (a) {
        console.log(a);
    })
    Subscribe1.on("on1", function (a) {
        console.log(a, a);
    })
    Subscribe1.emit("on1", "bbbb");
</script>

4. Promise
优点:可使用then、catch方法,链式写法
缺点:编写和理解
<script>
    new Promise(function (success, fail) {
        setTimeout(function () {
            success("emmm");
        }, 1000);
    }).then(function (res) {
        console.log("ok", res);
    }).catch(function (err) {
        console.error(err);
    });
</script>

5. Generator
优点:函数体内外部数据交换、错误处理机制
缺点:流程管理不方便
<script>
    function* Generator1() {
        yield 1;
        yield 2;
        yield 3;
    }

    var s = Generator1();
    console.log(s.next());
    console.log(s.next());
    console.log(s.next());
</script>

6. async
优点:内置执行器、更好的语义、更广的适用性、返回Promise,结构清晰
缺点:错误处理机制
<script>
    async function async1() {
        console.log("async1");
    }

    async1().then(function () {
        console.log("then");
    }).catch(function () {
        console.log("catch");
    })
</script>

23. 深拷贝与浅拷贝的问题

基本数据、引用数据
基本数据:储存在栈内存,值直接储存在变量访问的位置。
    【栈内存:是一块连续的储存区域,主要特点就是先进先出。基本变量则会直接分配空间储存值】
    复制值时:复制的是值
引用数据:储存在堆内存。创建一个引用数据时,会在堆内存中分配一个内存空间,然后在栈内存中储存一个指向此空间的指针(就是栈内存储存的值,
    是堆内存的空间地址)。变量不再访问时,栈内存中的指针被销毁,但堆内存中的对象空间不在自动释放,需要手动调用垃圾回收机制来释放空间。
    【堆内存:是一块非连续的储存区域。主要特点是动态分配和释放空间,需要手动管理内存空间】
    复制值时:复制的是内存地址
深拷贝:复制的是基本数据,会迭代所有的子级属性的基本数据
<script>
    // 1. JSON.parse
    var obj = {
        a: 1,
        b: 2
    }
    var newObj = JSON.parse(JSON.stringify(obj))
    // 2. structuredClone 返回值是原始值的深拷贝,不被序列化的值会抛出异常。
    var newnewObj = structuredClone(obj);
</script>
浅拷贝:复制的是对象所有的属性
<script>
    var obj = {
        a: 1,
        b: 3,
        c: {
            aa: 11
        }
    };
    // 1. es6的...方式
    var newObj = {...obj};
    // 2. Object.assign 将对象所有的可枚举的属性复制到目标对象中,最后返回结果
    var newObj1 = Object.assign({}, obj, newObj);
    // 3. 数组 Array.concat
    var arr1 = [].concat([1, 2]);
</script>

24. js中的异步操作--计时器

setTimeout(function () {
    console.log("1s==1000ms 执行一次");
}, 1000);

setInterval(function () {
    console.log("1s==1000ms 执行多次,直到 clearInterval");
}, 1000);

25. js执行机制--事件循环(Event Loop)【并发模型与事件循环】

Js有一个基于事件循环的并发模型,事件循环负责执行代码、收集和处理事件以及执行队列中的子任务。与其他语言不同例如:Java、C。
- 栈
函数调用形成了一个由若干帧组成的栈。
<script>
    function foo(b) {
        var a = 10;
        return a + b + 11;
    }

    function bar(x) {
        var y = 3;
        return foo(x * y);
    }

    console.log(bar(7));
</script>
当调用bar函数时,第一个帧被创建并压入栈中,栈中包含了bar的参数和局部变量。
当bar调用foo时,第二个帧被创建并压入栈中,放在第一帧之上,帧中包含foo的参数和局部变量。
当foo执行完毕然后返回时,第二个帧就被弹出栈(剩下bar函数的第一个调用帧)。
当bar函数执行完毕并且返回时,第一帧弹出,栈清空。
- 堆
对象被分配到堆中,堆是一个用来表示一大块(通常是未结构化的)内存区域的计算机术语
- 队列
一个js运行时包含了一个待处理消息的消息队列。每一个消息都关联着一个用以处理这个消息的回调函数。
在事件循环的某个时刻,运行时会以最先进入队列的消息开始处理队列中的消息。
    被处理的消息会被移出队列,并作为输入参数来调用与之关联的函数。
    所以调用一个函数总是会为其创建一个新的帧帧。
函数的处理会一直进行到执行栈再次为空为止;然后事件循环将会处理队列中的下一个消息(如果还有的话)。
- 事件循环
// wait 同步等待消息到达
while (queue.waitForMessage()) {
    queue.processNextMessage();
}
执行至完成
每一个消息完整的执行后,其他消息才能继续执行。
当一个函数被执行时,它不会被占用,只有运行完毕后才会去执行其他代码,才能修改这个函数操作的数据。
添加消息
在浏览器中,每当一个事件发生并且有一个事件监听器绑定在该事件上时,一个消息就会被添加进消息队列。
如果没有事件监听器,这个事件将会丢失。
所以当一个带有点击事件处理器的元素被点击时,就会像其他事件一样产生一个类似的消息。
函数setTimeout接受两个参数:待加入队列的消息和一个时间值(可选,默认0)。
这个时间值表示被实际加入到队列的最小延迟时间。
如果队列中没有其他消息并且栈空,在这段延迟时间过去之后,消息会被马上处理。
但是如果有其他消息,setTimeout消息必须等待其他消息处理完。
因此第二个参数仅仅表示最少延时时间,而非确切时间。
<script>
    // setTimeout 10ms后执行,但是当前结果是10ms返回结果是在同步2秒后才展示的。
    // 获取当前的秒数
    var s = new Date().getSeconds();
    setTimeout(function () {
        console.log("执行10ms的计时器");
    }, 10);
    while (true) {
        if (new Date().getSeconds() - s >= 2) {
            console.log("这个是同步两秒后执行");
            break;
        }
    }
</script>

26. 宏任务和微任务(都是异步任务)

宏任务:setTimeout、setInterval、Ajax、DOM事件
1、通常优先级较低
2、会被推迟执行,直到Js引擎处于空闲状态时才会执行
3、可以由各种事件触发,例如鼠标事件、定时器事件等。
微任务:Promise、async/await
1、通常比宏任务具有更高的优先级
2、总是在当前任务执行完毕后立即执行
3、通常是Js引擎本身创建和调度的,如Promise的回调、MutationObserver的回调等
<script>
    console.log(1);
    setTimeout(() => {
        console.log(3);
    })
    Promise.resolve().then(e => {
        console.log(4);
    })
    console.log(2);
    // 结果:1 2 4 3
    // 1、2同步,4、3 Promise的优先级高(微任务),setTimeout的优先级低(宏任务)
</script>

27. forEach与map的区别

forEach 遍历数组,没有返回值
map 遍历数组,映射新元素,返回新数组,不改变当前数组
<script>
    const arr = [1, 2, 3];
    arr.forEach(e => {
        console.log(e);
    });
    const res = arr.map(e => {
        return {
            value: e
        };
    });
    console.log(res);
</script>

28. 什么是伪数组?如何将伪数组转换成真数组?

伪数组:
1. 可以使用索引对数组进行操作。
2. 具有length的长度。
3. 不能使用数组的方法,pop、push等
4. 对象的原型__proto__/prototype不是Array
真数组:
1. 可以按照索引的方式,储存多个数组。
2. 具有length属性,表示数组内数据的长度。
3. 数组的原型__proto__,指向Array(包含 push、pop等函数)。
<script>
    var lis = document.getElementsByTagName("li");
    // 1、创建一个空数组,遍历循环伪数组,将遍历出来的数据添加到空数组中
    var arr = [];
    for (var i = 0; i < lis.length; i++) {
        arr.push(lis[i]);
    }
    console.log(arr);
    // 2、arr.push.apply(arr,伪数组)
    var arr = [];
    arr.push.apply(arr, lis);
    console.log(arr);
    // 3、使用slice:利用Array原型的slice方法,配合apply将slice的this指向伪数组
    var arr = Array.prototype.slice.apply(lis);
    console.log(arr);
    // 4、利用es6提供的Array的from方法(将类数组对象或者可迭代对象转为新的数组实例,
    // Array.from(arrayLike(要转换的类数组对象或者可迭代对象)[,
    //      mapFn(可选,返回指定值)[,
    //      thisArg(执行mapFn时用作this的值)]]))
    var arr = Array.from(lis);
    console.log(arr);
</script>

29 DOM节点的增删改查

// 获取一个 li.name
document.querySelector("li.name");
// 获取所有的 li.names
document.querySelectorAll("li.names");
// 获取所有的li标签
document.getElementsByTagName("li");
// 获取id为id_name的DOM
var id_name = document.getElementById("id_name");
// 获取类名为class_name的所有DOM
document.getElementsByClassName("class_name");
// 创建一个div
var _div = document.createElement("div");
// 添加到body子元素的最后一个元素之后
document.body.append(_div);
// 复制元素 cloneNode(bool) true 包括所有属性、false 仅元素 
_div.cloneNode();
// 添加到父元素的末尾
id_name.parentNode.appendChild();
// 在id_name里面,将_div插入到id2之前
id_name.insertBefore(_div, document.getElementById("id2"));
// 在id_name里面,使用_div替换id2
id_name.parentNode.replaceChild(_div, document.getElementById("id2"));
// 在id_name里面删除子元素id2
id_name.removeChild(document.getElementById("id2"));
// 删除id1的DOM
document.getElementById("id1").remove();
// 设置id2DOM的文本为6666
document.getElementById("id2").innerText = "6666";
// 设置id1的子元素文本为<p>ooo</p>
document.getElementById("id1").innerHTML = "<p>ooo</p>";
// 设置样式
document.getElementById("id1").style.display = "block";
// 设置图片的src
document.getElementsByTagName("img")[0].src = "http//...";
// 设置a标签的href
document.getElementsByTagName("a")[0].href = "http//...";

30. 函数的防抖和节流

console.log("-----------防抖----------");

/**
 * 防抖
 * run[false],n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时(setTimeout计算,使用宏任务)
 * run[true],间隔时间触发,没有到间隔时间触发时则重新计时(time计算,直接执行不使用任务)
 * @param fn 执行的函数
 * @param time 执行间隔时间
 * @param run 是否直接执行 默认 false
 * @returns {(function(...[*]): void)|*}
 */
function debounce(fn, time = 1000, run = false) {
    let timer = null;
    let beginTime = null;
    return function (...args) {
        if (run) {
            if (!beginTime || ((beginTime.getTime() + 1000) <= new Date().getTime())) {
                fn && fn.apply(this, args);
            }
            beginTime = new Date();
        } else {
            if (timer) {
                clearTimeout(timer);
                timer = null;
            }
            let _this = this;
            timer = setTimeout(function () {
                clearTimeout(timer);
                timer = null;
                fn && fn.apply(_this, args);
            }, time);
        }
    }
}

document.getElementById("FangDou").onclick = debounce(function (e) {
    console.log(this, e);
    console.log("防抖");
}, 1000, false);
console.log("----------防抖END----------");
console.log("-----------节流----------");

/**
 * n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效
 * @param fn 执行函数
 * @param time 间隔时间
 * @returns {(function(): void)|*}
 */
function throttled(fn, time) {
    let beginTime = null;
    return function () {
        if (beginTime && ((beginTime + time) < new Date().getTime()) || !beginTime) {
            beginTime = new Date().getTime();
            fn && fn.apply(this, arguments);
        }
    };
}

document.getElementById("JieLiu").onclick = throttled(function (e) {
    console.log(this, e);
    console.log("节流");
}, 1000, true);
console.log("----------节流END----------");

31. null与undefined

// 表示值未初始化,原始类型则是自身
undefined;
// 表示值初始化了,但是是未创建的对象,原始类型 Object
null;
// 都可以表示空

32. addEventListener 监听点击事件与click 事件有什么区别?


<script>
    function clickTap(e) {
        console.log(this, e);
    }
</script>
<div onclick="clickTap(this)">
    <button>click1</button>
    <button>click2</button>
    <button>click3</button>
    <button>click4</button>
</div>
<div id="id1">
    <button>add1</button>
    <button>add2</button>
    <button>add3</button>
    <button>add4</button>
</div>
<script>
    document.getElementById("id1").addEventListener("click", function (e) {
        console.log(this, e);
    });
</script>

addEventListener
onclick
都是处理点击事件的。
1. 上面的代码是实现区别。
2. 多个事件处理器
onclick 属性只能设置一个事件处理函数
addEventListener 可以设置多个事件处理函数,它们会按照添加顺序依次执行
3. 分离关注点
onclick 在html中混合结构(HTML)和行为(JS),违反了分离关注点的原则,使得代码维护性降低
addEventListener 可以将事件处理逻辑放在单独的Js文件中,实现内容、样式、行为的分离
4. 事件捕获、冒泡阶段
onclick 不允许直接控制事件处理是在捕获还是冒泡阶段
addEventListener 第三个参数可以控制事件处理是在捕获true还是冒泡false阶段
5. 溢出监听器
onclick 需要重新分配属性为null或者另外一个函数
addEventListener 可以通过removeEventListener方法明确移除,提供了更好的控制和灵活性

33. 检测对象中的属性是否存在

1. in
可以用来判断一个属性是否属于一个对象,也能检测原型链上的属性
<script>
    var obj = {
        a: 1
    };
    console.log("a" in obj);
</script>

2. Reflect.has
Reflect是ES2015新增的内置对象,提供了与JS对象交互的方法
判断一个对象是否存在某个属性,和 in 操作符的功能一样
<script>
    var obj = {
        a: 1
    };
    console.log(Reflect.has(obj, "ab"));
</script>

3. hasOwnProperty
用来检测一个对象是否含有特定的自身属性,
即是用来判断一个属性是否定义在对象自身中而非继承原型链。
<script>
    var obj = {
        a: 1
    };
    console.log(obj.hasOwnProperty("a"));
</script>

4. Object.prototype.hasOwnProperty()
用法 Object.prototype.hasOwnProperty.call(object,propName);
<script>
    var obj = {
        a: 1,
        hasOwnProperty: "aaaa"
    };
    console.log(Object.prototype.hasOwnProperty.call(obj, "hasOwnProperty"));
</script>

5. Object.hasOwn()
ES13(ES2022),代替Object.prototype.hasOwnProperty.call()
<script>
    var obj = {
        a: 1,
        hasOwn: "hasOwn"
    };
    console.log(Object.hasOwn(obj, "hasOwn"));
</script>

34. 判断对象是否为空


<script>
    var obj = {};
    var obj1 = {a: 1};
</script>
1. JSON.stringify()
将可序列化的对象转为JSON字符串
通过字符串"{}"判断
<script>
    console.log(JSON.stringify(obj) === "{}");
    console.log(JSON.stringify(obj1) === "{}");
</script>

2. Object.keys().length
获取对象的keys,返回数组
通过长度判断
<script>
    console.log(0 === Object.keys(obj).length)
    console.log(0 === Object.keys(obj1).length)
</script>

3. Object.values().length
获取对象的values,返回数组
通过长度判断
<script>
    console.log(0 === Object.values(obj).length)
    console.log(0 === Object.values(obj1).length)
</script>

4. for.in
for.in 以任意语句顺序迭代一个对象中除去Symbol以外的可枚举属性,包括继承的属性
<script>
    function isNullObj(obj) {
        for (const objKey in obj) {
            return false;
        }
        return true;
    }

    console.log(isNullObj(obj));
    console.log(isNullObj(obj1));
</script>

5. {}.hasOwnProperty.call(obj,key)
<script>
    function isNullObj(obj) {
        for (const objKey in obj) {
            if (Object.prototype.hasOwnProperty.call(obj, objKey)) return false;
        }
        return true;
    }

    console.log(isNullObj(obj));
    console.log(isNullObj(obj1));
</script>

6. Object.getOwnPropertyNames()
同 Object.keys() 功能一致,获取一个JSON对象中的所有属性名
<script>
    console.log(Object.getOwnPropertyNames(obj).length === 0);
    console.log(Object.getOwnPropertyNames(obj1).length === 0);
</script>

35. js中遍历对象的方法


<script>
    var obj = {
        a: 1
    };
</script>

1. for in
<script>
    for (var key in obj) {
        if (Object.prototype.hasOwnProperty.call(obj, key)) {
            console.log(key);
        }
    }
</script>

2. Object.keys
<script>
    Object.keys(obj).forEach(function (key) {
        console.log(key);
    });
</script>

3. Object.getOwnPropertyNames()
获取属性,包括不可枚举的属性,不包括Symbol
<script>
    Object.getOwnPropertyNames(obj).forEach(function (key) {
        console.log(key);
    })
</script>

4. Object.getOwnPropertySymbols()
仅获取Symbol属性
<script>
    var obj1 = {
        [Symbol()]: 1,
        a: 223
    }
    Object.getOwnPropertySymbols(obj1).forEach(function (key) {
        console.log(key);
    })
</script>

5. Reflect.ownKeys()
ES6(ES2015) 新增的静态方法,该方法返回对象自身所有属性名组成的数组,包括枚举对象的属性、Symbol属性
<script>
    Reflect.ownKeys(obj).forEach(function (key) {
        console.log(key);
    })
</script>

36. 创建一个对象有哪几种方法

// 1. 字面量方式创建
var obj = {};
console.log(obj);

// 2. 工厂模式
function createObj() {
    return {};
}

console.log(createObj());

// 3. 构造函数
function Obj1() {
    this.name = "4";
}

console.log(new Obj1());

// 4. 原型模式
function Obj2() {
    Obj2.prototype.name = "2";
}

console.log(new Obj2());

// 5. 构造函数和原型模式
function Obj3() {
    this.name = "1";
}

Obj3.prototype.getName = function () {
    console.log(this.name);
}
console.log(new Obj3());

37. window.onload 和DOMContentLoaded 的区别

// 所有资源加载完成,包括 css,js,img等
window.onload = function () {
    console.log("onload")
}
// 仅DOM加载完成,不包括css,js,img等
window.addEventListener("DOMContentLoaded", function () {
    console.log("DOMContentLoaded")
})

/*
区别:
    onload是DOM对象,DOMContentLoaded是html5事件
    onload事件会被css,js,img阻塞
    当加载的脚本内容并不包含立即执行DOM操作时,使用DOMContentLoaded事件是个更好的选择,会比onload执行速度更快
 */

38. js延迟加载的方式

js延迟加载就是页面加载时,加载js时不会阻塞页面。也就是说js在页面加载完毕后加载或者同时加载。

1. defer
表明脚本在执行时不会影响页面的构造。也就是说,脚本会被延迟到整个页面都解析完毕后才执行。
HTML4.01 增加此属性
<script src="https://..." defer="defer"></script>

2. async
不让脚本等待下载和执行,从而异步加载其他内容。不能保证js的执行顺序
<script src="https://..." async="async"></script>

3. 动态创建script标签
<script>
    document.documentElement.append(document.createElement("script"));
</script>

4. JQuery getScript
<script>
    $.getScript("https://", function () {
        console.log("脚本加载");
    });
</script>

5. 让JS最后加载
将script标签放置页面尾部,让js最后引入,来加快页面加载速度

ES6

1.

let,const
let 声明变量,不能变量提升
const 声明常量

2. symbol

独一无二的值,const a = Symbol();

3. 模板字符串

var s = "666";
const a = `a\${s}`; 

4. 结构表达式

var [a, b, c] = [1, 2, 3];
console.log(a, b, c);

var [a, b, c] = [1, , 3];
console.log(a, b, c);

var [a, , b] = [1, 2, 3];
console.log(a, b);

var [a, ...b] = [1, 2, 3];
console.log(a, b);

var {a, b} = {
    a: 1,
    b: 666
};
console.log(a, b);

5. 对象

Map,Set
<script>
    const map = new Map([["a", "b"]]);
    map.set("key", "value");
    console.log(map.get("key"));
    console.log(map);

    const set = new Set([1, 2, 3, 3, 3, 4]);
    set.add(5);
    console.log(set.delete(1));
    console.log(set);
</script>

6. Array.form(可迭代数据) 返回数组

7. Array.prototype

// searchElement 需要查询的值
// fromIndex 开始搜索的索引
// res 返回是否查询到结果
var res = Array.prototype.includes(searchElement, fromIndex);

// thisArg 执行callbackFn的this
var thisArg = {};

/**
 * @param element 当前项
 * @param index 当前索引
 * @param array 数组本身
 */
function callbackFn(element, index, array) {
}

// callbackFn 返回任意值,并储存至新数组
// res 是由callbackFn的返回值组成的新数组
var res = Array.prototype.map(callbackFn, thisArg);

// callbackFn 返回值是 bool,将所有返回true的项储存至新数组
// res 是由所有返回true的项的组成的新数组
var res = Array.prototype.filter(callbackFn, thisArg);

// callbackFn 无返回值
Array.prototype.forEach(callbackFn, thisArg);

// callbackFn 返回bool,返回true的是否停止迭代,将当前项储存
// res 返回储存的数据或者undefined
var res = Array.prototype.find(callbackFn, thisArg);

// callbackFn 返回bool,返回true的时候停止迭代,并且返回true
// res 返回 callbankFn的返回值,查询到一个true则返回true,否则返回false
var res = Array.prototype.some(callbackFn, thisArg);

// callbackFn 返回bool,返回false时停止迭代,并返回false
// res 返回callbackFn的返回值,查询到一个false的时候返回false,否则返回true
var res = Array.prototype.every(callbackFn, thisArg);

8. Object

// 比较两个值是否相同
// 1. 不会对数据类型进行转换
// 2. [都是0,都是-0,都是+0,都是NaN]则是true
Object.is(value1, value2);

// 将一个或者多个源对象中所有可枚举的自由属性复制到目标对象,并且返回修改后的目标对象
Object.assign(target, ...source);

// 返回一个给定对象自身的可枚举的字符串键属性名组成的数组
Object.keys(obj);

// 返回一个给定对象自身的可枚举字符串键属性值组成的数组
Object.values(obj);

// 返回一个数组,由给定对象自身的可枚举键属性的键值对
Object.entries();

// 对象声明简写
var a = 1;
var b = 1;
var c = {
    a,
    b
}

// 对象扩展符
// 1. 拷贝对象 浅拷贝
var a = {
    a: 1,
    b: {
        c: 2
    }
}
var c = {...a}
// 2. 合并对象
var a = {a: 1};
var b = {a: 1, b: 2};
var c = {a: 1, c: 2};
var d = {...a, ...b, ...c};

9. 函数方式

// 1. 参数默认值
function fn(a = 1, b = 2) {
    return a + b;
}

fn();
fn(1);
fn(1, 6);

// 2. 箭头函数。自身无this指向,内部this指向是上级this。没有arguments、prototype,所以不能使用new。
var fn = () => {
    return 1;
}
var fn1 = (a = 2, b = 3) => a * b;

10. class 类

    class ClassName {
    name = undefined;

    constructor(name) {
        this.name = name;
    }
}

// 继承
class ClassName1 extends ClassName {
    getName() {
        console.log(this.name);
    }

    constructor(name) {
        super(name);
    }
}

new ClassName1("wawa").getName();

11. Promise

流程
new Promise(executor);

** thenable ** 
在js中,thenable是具有then函数的对象。
所有的Promise都是thenable,但不是所有的thenable都是Promise。
  1. 在构造函数内生成Promise实例,并且生成一对相应的resolve,reject函数,它们与Promise对象绑定在一起
  2. 通常会封装某些提供基于回调的API的异步操作。回调函数(传给原始回调API的参数)在executor代码中定义,因此它可以访问resolve,reject函数
  3. executor是同步调用的(在构造函数Promise时立即调用),并将resolve,reject函数作为传入参数
  4. executor中的代码有机会执行某些操作。异步任务的最终完成通过resolve,reject函数引起的副作用与Promise实例进行通信。这个副作用让Promise对象变为已解决状态
    • 如果先调用resolve,则传入的值将解决,Promise可能会保持待定状态(如果传入了另一个thenable对象),变为已兑现状态(在传入非thenable值的大多数情况下),或者变为已拒绝状态(在解析值无效的情况下)
    • 如果先调用了reject,则Promise立即变为已拒绝状态
    一旦resolve,reject中的一个被调用,Promise将保持待定状态。只有第一次调用resolve,reject会影响Promise的最终状态,随后对任一函数的调用都不会更改兑现值或拒绝原因,也不能将其最终状态从已兑换转为已拒绝或相反。
    • 如果executor抛出错误,则Promise已拒绝。但是,如果resolve,reject中的一个已经被调用,则会忽略该错误。
    • 解决Promise不一定会导致Promise变为已兑换或者拒绝状态。Promise可能处于待定状态,因为它可能是用另一个thenable对象解决的,但它的最终状态将与已解决的thenable对象一致。
  5. 一旦Promise敲定,它会(异步)调用任何通过then,catch,finally关联的进一步处理程序。最终的兑现值或拒绝原因在调用时作为参数作为输入参数传入兑现和拒绝处理程序。
// 表示异步操作的最终完成、失败以及其结果值
Promise
// Promise是一个代理,有三个状态
// 1. 待定(pending) 初始状态
// 2. 已兑换(fulfilled) 已完成
// 3. 已拒绝(rejected) 已失败

var p = new Promise(function (resolve, reject) {
    // 此函数内部,任意错误都会被Promise拒绝,并忽略返回值
    // 标识Promise为已解决状态(resolved)
    resolve(value);
    // 标识Promise为已拒绝状态(rejected)
    reject(error);
});

/**
 接受两个参数 onfulfilled,onrejected
 onfulfilled 是Promise兑换时的回调函数
 onrejected 是Promise拒绝时的回调函数
 每个then返回一个新生成的Promise对象,这个对象可以链式调用
 即使then缺少返回Promise对象的回调函数,处理程序仍会继续到链的下一个链式调用。
 因此,在最后catch之前,可以安全的省略每个链式调用中处理已拒绝状态的回调函数
 */
// 实例方法 可以链式调用
Promise.prototype.then(onfulfilled, onrejected).then(onfulfilled1, onrejected1)
/**
 为Promise注册一个被拒绝时调用的函数。
 */
Promise.prototype.catch()
/**
 * 注册一个在Promise敲定时调用的函数
 */
Promise.prototype.finally()

// Promise并发
Promise.all()
Promise.allSettled()
Promise.any()
Promise.race()

// 静态方法
// 接受一个Promise的可迭代对象作为参数,并返回一个Promise
// 所有的Promise都成功后,返回的Promise被兑换并且返回所有兑换值的数组。
// 任意拒绝后,则返回的Promise被拒绝,并且带有第一个拒绝的原因
Promise.all()
Promise.all([new Promise(), new Promise()]).then()

// 接受一个Promise的可迭代对象作为参数,并返回一个Promise
// 当所有的Promise敲定后,返回的Promise被兑换并返回所有Promise结果的对象数组
Promise.allSettled()
Promise.allSettled([new Promise(), new Promise()]).then()

// 接受一个Promise的可迭代对象作为参数,返回一个Promise
// 当输入的任意一个Promise被实现时,返回的Promise被兑换并返回兑换值。
// 所有Promise被拒绝后,返回的Promise被拒绝并且带有拒绝原因的数组。
Promise.any()
Promise.any([new Promise(), new Promise()]).then().catch()

// 接受一个Promise的可迭代对象的参数,并返回一个Promise
// 这个返回的Promise会随着第一个Promise的敲定而敲定
Promise.race()
Promise.race([new Promise(), new Promise()]).then().catch()

// 返回一个已拒绝的Promise对象
Promise.reject()
Promise.reject(new Error("手动报错")).catch()

// 返回一个已解决的Promise对象
Promise.resolve()
Promise.resolve("手动成功获取值").then();

12. Proxy

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

语法
<script>
    // target 源,要使用Proxy包装的目标对象(可是原生数组、任意对象、函数、其他代理)
    // handler 行为,一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理的行为
    const p = new Proxy(target, handler)
</script>

方法
<script>
    // target 目标对象
    target
    // thisArg 被调用时的上下文对象
    thisArg
    // argumentsList 被调用时的参数数组
    argumentsList
    // newTarget 最初被调用的构造函数
    newTarget
    // property 带检索其描述的属性名,支持Symbol
    property
    // descriptor 待定义或修改的属性的描述符
    descriptor
    // receiver Proxy或者继承Proxy的对象
    receiver
    // prop 返回属性名称的描述
    prop

    // 用于拦截函数的调用
    handler.apply();
    new Proxy(target, {
        apply: function (target, thisArg, argumentsList) {
        },
    });

    // 用于拦截new操作符
    handler.constructor();
    new Proxy(target, {
        construct: function (target, argumentsList, newTarget) {
        },
    });

    // 用于拦截对象的Object.defineProperty()操作
    handler.defineProperty();
    new Proxy(target, {
        defineProperty(target, property, descriptor) {
        }
    })

    // 用于拦截对象的属性delete操作
    handler.deleteProperty();
    new Proxy(target, {
        deleteProperty(target, property) {
        }
    })

    // 用于拦截对象的读取属性操作
    handler.get();
    new Proxy(target, {
        get(target, property, receiver) {
        }
    })

    // 是Object.getOwnPropertyDescriptor的钩子
    handler.getOwnPropertyDescriptor();
    new Proxy(target, {
        getOwnPropertyDescriptor(target, prop) {
        }
    })

    // 是代理的方法,当读取代理对象的原型时,该方法就会被调用
    handler.getPrototypeOf();
    new Proxy(target, {
        getPrototypeOf(target) {
        }
    })

    // 拦截 in 操作符的代理方法
    handler.has();
    new Proxy(target, {
        has(target, prop) {
        }
    })

    // 拦截对象的Object.isExtensible()方法
    handler.isExtensible();
    new Proxy(target, {
        isExtensible(target) {
        }
    })

    // 用于拦截 Reflect.ownKeys()
    handler.ownKeys();
    new Proxy(target, {
        ownKeys(target) {
        }
    })

    // 用于拦截Object.preventExtensions()
    handler.preventExtensions();
    new Proxy(target, {
        preventExtensions(target) {
        }
    })

    // 用于拦截设置属性值的操作
    handler.set();
    new Proxy(target, {
        set(target, property, newValue, receiver) {
        }
    })

    // 用于拦截Object.setPrototypeOf()
    handler.setPrototypeOf();
    new Proxy(target, {
        setPrototypeOf(target, prototype) {
        }
    })
</script>

13. 模块化

// 导出
const obj = {
    a: 1,
    b: 2
}
export {obj};
export default obj;
// 导入
import obj from "path";
import "path";

// 优点
// 1. 防止命名冲突
// 2. 复用性强

14. 运算符

  1. ...拓展符。数组、对象的展开
console.log(...[1, 2, 3]);
console.log(1, ...[2, 3, 4], 5)
console.log({...{a: 1, b: 2}})
  1. ?.可选链。console.log(({})?.a);console.log(({a: 1})?.a);
  2. ??空值合并操作符。console.log(""??1);console.log(null??2)

Ajax

1. Ajax是什么

一种创建交互式网页应用的技术开发技术。
沟通客服端和服务器,可以在不用刷新整个网页的情况下,与服务器进行异步通讯的技术。
通过XMLHttpRequest可以在不刷新URL的情况下,请求特定的URL从而获取数据。

2. Ajax过程


<script>
    // 1. 创建XMLHttpRequest对象
    var xhr;
    if (window.XMLHttpRequest) {
        // IE7+
        xhr = new XMLHttpRequest();
    } else {
        // IE6/5
        xhr = new ActiveXObject("Microsoft.XMLHTTP");
    }

    // 2. 设置回调函数,根据状态码执行不同的事情
    xhr.onreadystatechange = function (e) {
        switch (xhr.readyState) {
            case 1:
                console.log("5 open已经调用", new Date().getTime());
                break;
            case 2:
                console.log("6 send已经调用,并且header和状态已经可获得", new Date().getTime());
                break;
            case 3:
                console.log("7 下载数据包中,responseText属性已经包含部分数据", new Date().getTime());
                break;
            case 4:
                console.log("8 下载操作已经完成", new Date().getTime());
                break;
        }
    }

    // 请求完成时触发load事件
    xhr.onload = function (e) {
        console.log("请求完成了");
    }
    console.log("1 设置请求请求参数完成", new Date().getTime());

    // 3. 使用open方法与服务器建立连接并且设置请求信息
    xhr.open("post", "https://www.aaaa.com/any/post", true);
    xhr.setRequestHeader("token", "xxxxx");

    console.log("2 调用open方法完成", new Date().getTime());

    // 4. 发送数据
    xhr.send(new FormData());
    console.log("3 调用send方法完成", new Date().getTime());

    // 上诉调用顺序
    // 1 设置请求请求参数完成 1703814771206
    // 5 open已经调用 1703814771207
    // 2 调用open方法完成 1703814771207
    // 3 调用send方法完成 1703814771207
    // POST https:// 404 (Not Found) ** 此处是错误信息 404,由于找不到目标URL报的错 **
    // 6 send已经调用,并且header和状态已经可获得 1703814771334
    // 7 下载数据包中,responseText属性已经包含部分数据 1703814771335
    // 8 下载操作已经完成 1703814771335
    // 请求完成了
</script>

3. 使用Js和DOM进行局部刷新

<!doctype html>
<body>
<h2>以下是请求Ajax得到结果</h2>
<div class="onAjax"></div>
</body>
<script>
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function () {
        if (xhr.readyState === 4 && xhr.status === 200) {
            document.querySelector(".onAjax").innerText = xhr.responseText;
        }
    }
    xhr.open("get", "https://www.aaaa.com/any/post", true);
    xhr.send();
</script>
</html>

4. 区别http请求、ajax请求

都是数据传输和通信的技术
Http: 
   1. 应用层的协议,互联网通信的基础,主要用于客户端与服务器之间的请求和相应交互
   2. http 同步\异步
   3. 请求方式 get\post\put\delete等,header\cookie等
   4. 传输各种数据 text/json/xml/二进制等
   5. 无状态的协议,每次请求和响应之间没有直接的关联
Ajax:
   1. 是一种web开发技术,在浏览器中实现异步数据交换
   2. 异步
   3. ajax是通过http实现的,拥有额外的标记 "X-Requested-With": "XMLHttpRequest"
   4. 传输各种数据
   5. ajax可以保持与服务器的长连接(websocket等技术),可以使服务器主动推送数据到客服端
   
综上所述:
   http是一种底层的协议,而ajax是基于http实现的高级技术.ajax建立在http之上,通过js在客服端提供了动态\交互的web体验.

5. get、post的区别

get 
   1. 主要用于获取数据不产生副作用的操作(例如删除,修改)
   2. 数据明文(相对于POST不太安全)
   3. 可以被浏览器或代理缓存
   4. 只支持URL编码
post
   1. 提交数据,请求服务器处理.
   2. 参数一般在请求体中(可以传输大量的数据,没有长度限制),数据非明文,对于GET来说更安全一些
   3. 基本不会被缓存
   4. 支持多种编码格式,URL/表单(multipart/form-data)/(application/x-www-form-urlencoded)

6. HTTP常见状态码

1** 接受请求正在处理
2** 请求正常处理完毕
3** 需要附加操作已完成请求
4** 服务器无法处理请求
5** 服务器处理请求出错

200 请求成功
204 无内容
206 是对资源某一部分的请求
301 永久性重定向
302 临时性重定向
304 请求资源未修改,不会返回资源,使用缓存
400 客服端错误,服务器无法处理请求
401 身份验证错误
403 没有权限访问
404 找不到资源
500 服务器错误
502 网关异常
503 服务器处理不了请求,通常是服务器停机
504 超时

7. http和https

http 是一种网络协议.明文传输
https 是在http的基础上增加了一层ssl的网络协议,也就是http安全版.加密传输

8. 跨越问题

由于同源策略(协议/域名/端口,任意一项不一致就会出现)的影响,就会出现跨域。
为了保证用户的隐私安全,防止恶意网站通过脚本获取或者篡改其他网站的数据。
解决方案:
   1. 前端代理服务器
   2. CORS。跨源资源共享,基本http头的机制,该机制通过允许服务器标示除了自身外的其它源,使得浏览器允许这些其他源加载自己的资源
   3. JSONP。改变请求方式为JSONP,仅支持GET,与AJAX无关联。(原理,script不受同源策略影响,通过script标签获取数据)
CORS浏览器自动添加,也是最好的方案

9. 网页从输入url到页面加载发送了什么

  1. 用户输入URL
  2. 域名解析(DNS查询)
  3. TCP连接建立(涉及三次握手,其中浏览器和服务器交换SYN(同步),ACK(确认)数据包以确认连接)
  4. HTTP请求发送(建立连接后,浏览器构造并发送一个HTTP请求到服务器)
  5. 服务器处理请求
  6. HTTP响应发送(http版本、状态码等等的信息)
  7. 浏览器接收并解析响应
  8. 渲染页面(HTML,浏览器开始构建DOM,解析CSS并创建渲染树,绘制)
  9. 执行JS
  10. 关闭TCP连接(进行4词挥手过程来关闭TCP连接)

10. 浏览器如何渲染页面

  1. 发送HTTP请求
  2. 接收到HTML文件
  3. 构建DOM树:浏览器解析HTML文件构建DOM树(文件对象模型)。DOM树是表示网页内容的树状结构,多个多级节点组成,每个节点代表HTML中的元素、文本内容、注释
  4. 加载CSS样式(异步微任务):解析CSS样式表(层叠样式表模型)并应用到对应的元素上,实现布局、样式
  5. 构造渲染树:
    • 浏览器根据DOM树和CSS样式表建立渲染树(表示网页视觉呈现的树状结构)
    • 渲染树映射除了隐藏的元素(display:none;<head></head>)外的所有DOM结构
  6. 布局(layout),浏览器根据渲染树进行布局。
  7. 绘制(painting),渲染树转化为屏幕上的实际内容。
  • 重绘Repaint:当页面元素样式的改变不影响元素在文档流中的位置时(不改变layout的情况下,如
    background-color、border-color、visibility),浏览器只会将新样式赋予元素进行重绘
  • 回流Reflow:当改变文档内容、结构、位置时,回流操作触发。
    1. DOM操作(增、删、改等等)
    2. 文本的变化
    3. CSS属性的变化(width、height等等)
    4. 样式表的增、删、改
    5. 窗口变化(浏览器窗口)
    6. 伪类激活等

优化方案:

  1. 样式文件应当在 <head></head>标签中,脚本文件在 body 结束前
  2. 优化选择器。嵌套层减少到最小。下面是选择器执行效率(快-慢)
    • ID #id
    • 类 .class
    • 标签 div
    • 相邻 div+p
    • 子元素 div>span
    • 通用 *
    • 属性 input[type=“text”]
    • 伪类 div:hover
  3. 减少DOM操作
  4. 尽量只对absolute/fixed的元素设置动画
  5. 滚动时禁止hover

11. MVC和MVVM的理解

MVC:
    Model 模型
    View 视图,展示
    Controller 控制器
在MVC中,模型和视图之间没有直接的联系。视图通过控制器来访问模型,而模型的变更通常会触发视图的更新。
这种分离使得代码更加易于维护、拓展,因为每个部分都有明确的责任划分

MVVM:
Model 模型
View 视图,通过数据绑定与ViewModel交互
ViewModel 包含View所需要的所有数据和行为,又对模型进行抽象、封装。
            ViewModel暴露了便于数据绑定的属性和命令,使得视图可以直接绑定到这些属性,而不直接操作Model。
ViewModel数据变化时,View更新。View数据更新时,ViewModel数据更新。

总结:
MVC 强调分层架构和职责分离,通过控制器来协调View和Model的交互。
MVVM 强调数据驱动和声明式编程,通过数据绑定和ViewModel来实现Model和View的解耦。

MVVM通常在前端体现,Vue、Angular、React

12. 单页面应用的理解

一种WEB应用程序开发模式,特点是在用户与应用程序交互的过程中,
    大部分功能都在同一个网页上动态加载和更新,而无需整个页面刷新

首次加载速度慢
SEO问题

贼方便前后端分离

13. 回流(重排)重绘

  • 重绘Repaint:当页面元素样式的改变不影响元素在文档流中的位置时(不改变layout的情况下,如
    background-color、border-color、visibility),浏览器只会将新样式赋予元素进行重绘
  • 回流Reflow:当改变文档内容、结构、位置时,回流操作触发。
    1. DOM操作(增、删、改等等)
    2. 文本的变化
    3. CSS属性的变化(width、height等等)
    4. 样式表的增、删、改
    5. 窗口变化(浏览器窗口)
    6. 伪类激活等

优化方案

  1. 减少频繁操作样式
  2. 减少频繁操作DOM(使用DocumentFragment)
  3. 操作DOM前先将DOM隐藏(display: none;)

14. WEB性能优化

  1. 清理HTML 删无用的空白字符、注释,使用语义化的标签
  2. css文件压缩
  3. 避免使用CSS表达式,可能导致频繁的重绘和回流
/*这种的*/
div {
    width: expression(document.body.clientWidth < 600 ? "300px" : "600px");
}
  1. 利用CSS继承和CSS简写
  2. 减少HTTP请求
  3. 启用缓存
  4. 延迟加载、懒加载
  5. 优化图片、视频、音频等(体积)

Vue

1. 什么是Vue

一款渐进式Js框架

2. Vue响应式

修改数据模型时,视图会更新。

原理:

  • 侦测数据的变化(数据劫持 、数据代理)
  • 收集视图依赖的数据(依赖收集)
  • 数据变化,通知视图并更新(发布订阅模式)

侦测数据变化

1. Object.defineProperty
设置对象属性的setter、getter方法来监听数据的变化,通过getter收集数据依赖,setter方法就是一个观察者,在数据变化时通知订阅者更新视图。
<script>
    /**
     * 此静态方法会直接在一个对象中定义一个新属性,或者修改现有属性,并返回此对象。
     * obj 需要定义属性的对象
     * prop 一个字符串或者Symbol,指定的定义、修改的属性键
     * descriptor 定义、修改的属性描述符
     * @return Object 传入函数的对象,其指定的属性已经被添加、修改
     */
    Object.defineProperty(obj, prop, descriptor);

    // 描述符
    descriptor : {
        /**
         * 该属性的类型不能在数据属性和访问器属性之间更改
         * 1. 能否删除此属性
         * 2. 能否重新定义属性描述
         * (默认 false)
         */
        configurable: Boolean
        /**
         * 与属性关联的值是否可以通过赋值运算符更改
         * 这个value,是否能进行修改
         * (默认 false)
         */
        writable: Boolean
        /**
         * 是否能在对应对象的属性枚举中出现
         * 当前属性是否能在for.in等枚举中出现
         * (默认 false)
         */
        enumerable: Boolean
        /**
         * 与属性相关联的值。可以是任何有效的js值
         * 默认 undefined
         */
        value: any
        /**
         * 用作属性setter的函数。
         * 当属性被调用时触发此函数,this为通过该属性访问的对象(可能不是自身的this),返回值则是该属性的值
         * 默认 undefined
         */
        get: any
        Function()
        /**
         * setter的函数
         * 赋值时调用此参数,一个参数(需要赋值的值),this设置为通过该属性分配的对象
         * 默认 undefined
         */
        set: void Function(value)
    }
</script>
<script>
    var data = {
        a: 1,
        b: 2
    };

    function refView() {
        console.log("刷新View");
    }

    function f(data) {
        if (data && typeof data === "object") {
            Object.keys(data).forEach(e => {
                f1(data, e);
            });
        }

        function f1(obj, name) {
            var value = obj[name];
            f(value);
            Object.defineProperty(obj, name, {
                enumerable: true,
                configurable: true,
                get() {
                    console.log("get", name, value);
                    return value;
                },
                set(data) {
                    console.log("set", name, data, this);
                    if (value !== data) {
                        f(data);
                        refView();
                        value = data;
                    }
                }
            });
        }
    }

    f(data);
    data.a = 111;
    data.b = 222;
    data.b = {
        c: 1
    };
    for (var x in data) {
        console.log(x);
    }
    console.log(data.b.c);
</script>

2. Proxy 代理
<script>
    var data = {
        a: 1,
        b: {
            c: 22
        },
    };

    function refView() {
        console.log("刷新View");
    }

    var p = new Proxy(data, {
        get(target, p, receiver) {
            console.log("get", target, p, receiver)
            if (target[p] && typeof target[p] === "object") {
                return new Proxy(target[p], this);
            }
            return Reflect.get(target, p, receiver);
        },
        set(target, p, newValue, receiver) {
            console.log("set", target, p, newValue, receiver)
            refView();
            return Reflect.set(target, p, newValue, receiver);
        }
    });
    p.a = 22;
    p.b.c = 99;
    console.log(p.b.c);
</script>

3. 虚拟DOM

  • 为什么使用虚拟DOM减少复杂页面的重绘、回流。兼容性好(是Js对象)
  • 虚拟DOM和真实DOM的区别。虚拟DOM不会重绘、回流,频繁修改仅最后一次更新真实DOM,更新差异部分只渲染局部

4. Vue虚拟DOM,diff算法

emm 有些为难我。

5. 数据双向绑定原理

使用数据劫持的方式实现双向绑定。
使用 Object.defineProperty(obj, prop, descriptor); 来进行属性的劫持。
使用方式参考上述文档·Vue响应式·

6. 事件修饰符

  • .prevent 阻止默认事件
  • .stop 阻止事件冒泡
  • .once 事件仅触发一次
  • .capture 事件捕获触发机制,内部元素触发先执行此事件
  • .self 仅此元素点击时才能触发事件
  • .passive 表示事件函数不会调用 e.preventDefault()

7. 生命周期

  1. beforeCreated 组件实例初始化之后立即调用(实例初始化之前,prop解析之后,data()computed()等选项处理之前)
  2. created 在组件实例处理完所有与状态相关的选项后调用(响应式数据、计算属性、方法、侦听器已完成,但挂载未开始)
  3. beforeMount 在组件挂载之前调用(组件已经完成其响应式状态的设置,但还没有完成创建DOM节点)
  4. mounted 在组件挂载之后调用(所有同步子组件都已经被挂载。其自身的DOM树已经创建完成并且插入了父容器中)
  5. beforeUpdate 在组件即将因为一个响应式状态变更而更新其DOM树之前调用(可以在Vue更新DOM之前访问DOM状态)
  6. updated 在组件即将因为一个响应式状态变更而更新之其DOM树之后调用(父组件的更新钩子将在子组件的更新钩子之后调用)
  7. beforeUnmount 在一个组件实例被卸载之前调用(当这个钩子被调用时,组件实例还保存着所有的功能)
  8. unmounted 在一个组件实例被卸载之后调用(其所有子组件已经被卸载,所有相关的响应式作用已经停止)
  • errorCaptured 在捕获后代组件传递的错误时调用(错误来源:组件渲染、事件处理器、生命周期钩子、setup()函数、侦听器、自定义指令钩子、过渡钩子)

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

  1. beforeCreate 父组件
  2. created 父组件
  3. beforeMount 父组件
  4. beforeCreate 子组件
  5. created 子组件
  6. beforeMount 子组件
  7. mounted 子组件
  8. mounted 父组件

9. 异步请求推荐哪个生命周期发送

1. create
2. mounted

10. 缓存组件 Keep-alive

Vue 内置组件,在组件切换过程中保存状态到内存中,防止重新渲染DOM。
包裹动态组件时,会缓存不活动的组件实例,而不是销毁。
<keep-alive>
    <component is="view"></component>
</keep-alive>

11. 组件通信

  1. Props(组件属性)通信 父->子
<!--父组件-->
<template>
    <child name="ok,我是普通字符串"></child>
</template>

<!--子组件-->
<template>{{name}}</template>
<script>
    export default {
        props: {
            name: {
                type: String,
                default: "我是默认空字符串"
            }
        }
    }
</script>
  1. Custom Events(自定义事件) 子->父
<!--父组件-->
<template>
    <child @call="call"></child>
</template>
<script>
    export default {
        methods: {
            call(e) {
                console.log("子组件触发父组件", e);
            }
        }
    }
</script>
<!--子组件-->
<template>
    <button @click="tap">触发父组件事件</button>
</template>
<script>
    export default {
        emits: ["call"],
        methods: {
            tap() {
                this.$emit("call", "参数");
            }
        }
    }
</script>
  1. Vuex(状态管理) 多个组件共享数据 任意通信
import {createStore} from "vuex";

const vuexStore = createStore({
    state() {
        return {
            message: "init"
        };
    },
    getters: {
        message(state) {
            return state.message;
        }
    },
    mutations: {
        updateMessage(state, message) {
            state.message = message;
        }
    }
});
export default vuexStore;

// app.use(VuexStore);
// 此文件需要在main.js中导入
<!--父组件-->
<template>
    <div>
        {{ message }}
        <child></child>
    </div>
</template>
<script>
    import {mapGetters} from 'vuex';
    import Child from "./components/child.vue";

    export default {
        components: {Child},
        computed: mapGetters(["message"]),
    }
</script>
<!--子组件-->
<template>
    <button @click="updateMessage(Math.random())">更新Message</button>
</template>
<script>
    import {mapMutations} from 'vuex';

    export default {
        methods: mapMutations(["updateMessage"])
    }
</script>

  1. Provide / Inject 父->子
<!--父组件-->
<template>
    <div>
        <child></child>
        <button @click="update">修改</button>
    </div>
</template>
<script>
    import Child from "./components/child.vue";

    export default {
        components: {Child},
        // 这对选项需要一起使用,允许一个祖先组件向其他所有子孙后代注入一个依赖,不管层级多深.
        // provide 选项应该是一个对象或者是一个返回对象的函数.该对象包含可注入后代组件的property
        provide() {
            return {
                a: this.m
            }
        },
        data() {
            return {
                m: {
                    a: 1111
                }
            }
        },
        methods: {
            update() {
                this.m.a = Math.random();
            }
        }
    }
</script>

<!--子组件-->
<template>
    a: {{ a.a }}
    b: {{b}}
</template>
<script>
    export default {
        /*
        Provide/Inject 绑定并不是可响应的.
        如果传入的是一个可监听的对象,那么其对象的property还是可响应的.
         */
        // 此信息应该是一个字符串对象,或者一个对象
        // from: property是在可用的注入内容中搜索用的key(字符串或者Symbol)
        // default: property是降级情况下使用的value
        inject: {
            a: {
                from: "a",
            },
            b: {
                from: "b",
                default: 223
            }
        }
    }
</script>
  1. ref 属性
<!--父组件-->
<template>
    <div>
        <child ref="RefChild"></child>
        <button @click="update">修改</button>
    </div>
</template>
<script>
    import Child from "./components/child.vue";

    export default {
        components: {Child},
        methods: {
            update() {
                this.$refs.RefChild.message = Math.random();
            }
        }
    }
</script>
<!--子组件-->
<template>
    <div>
        <p>{{ message }}</p>
    </div>
</template>
<script>
    export default {
        data() {
            return {
                message: "message"
            }
        }
    }
</script>

12. Vue的data为什么是函数不是对象

组件需要复用时,如是一个对象,则data对象会共享属性.
则会导致组件受到其他实例对象数据的污染.

13. $nextTick 作用与原理

作用:
处理需要更新DOM后的DOM数据。
<script>
    export default {
        methods: {
            updateAll() {
                Vue.$nextTick(() => {
                    this.$nextTick(() => {
                        console.log("全局更新DOM完成");
                    })
                });
            },
            update() {
                this.$nextTick(() => {
                    console.log("更新DOM完成");
                })
            }
        }
    }
</script>

原理:
将回调延迟到下次DOM更新循环之后执行。在修改数据之后立即使用它,然后等待DOM更新。
它跟全局的Vue.$nextTick一样,不同的是回调的this自动绑定到调用它的实例上。
属于异步更新。

14. v-for 为什么加 v-bind:key

因为可以在for中快速分辨出是否已经存在项目、需要更新的项目。
绑定key后,可以视作增加了一个索引,可以快速、准确的增删改查

15. computed和watch区别

computed 计算属性
支持缓存,依赖数据发生变化后才会重新计算,同步计算。
<script>
    export default {
        data() {
            return {
                num: 4
            }
        },
        computed: {
            getNum() {
                return this.num * 5;
            }
        }
    }
</script>

watch 监听属性
是对数据变化的监听,变化时会触发回调。
<script>
    export default {
        data() {
            return {
                num: 4,
                number: 1
            }
        },
        watch: {
            // 监听回调
            number(value, oldValue) {
                console.log(value, oldValue);
            },
            num: {
                // 组件加载后立即触发回调
                immediate: true,
                // 深度监听 后代属性变化时,也会触发
                deep: true,
                /**
                 * 监听回调
                 * @param value 新的值
                 * @param oldValue 以前的值
                 */
                handler(value, oldValue) {
                    console.log(value, oldValue);
                }
            }
        }
    }
</script>

16. v-show和v-if

1. 都是条件渲染
2. false时都不会占据页面位置

v-if
是将元素整个删除、添加
切换中有一个局部编译、卸载的过程,会销毁并重建内部的事件以及子组件
原理:通过v-if的条件表达式判断来决定是否生成DOM。

v-show
使用css属性,DOM还存在,仅仅是使用css隐藏、显示
<style>
    div {
        display: none;
    }
</style>

17. v-if和v-for避免一起使用

优先级:v-for优先于v-if

原因:
带来性能上的浪费(每次都会先进行循环再来进行判断)

18. 插槽 slot

slot 会接受来自父组件的模板,在子组件中渲染。
slot是一个元素出口,标识了父元素提供的插槽内容将会在哪里渲染。
<Parent>
    这里是默认的数据
    <p>啊啊啊</p>
    <template v-slot:after="这里是参数">
        我是具名插槽
    </template>
    <template v-slot:[dynamicSlotName]>
        我是动态插槽
    </template>
</Parent>

Parent.vue
<template>
    <div>
        <p>Parent</p>
        <div>
            默认插槽
            <slot></slot>
        </div>
        <div>
            具名插槽
            <slot text="参数" name="after"></slot>
        </div>
    </div>
</template>

19. Vuex 状态管理器

是一个专门为Vue.js应用程序开发的状态管理模式。
采用集中式储存管理应用的所有状态,并以相对应的规则保证状态以一种可以预测的方式发生变化。

核心概念
State 单一状态树,mapState辅助函数
Getters 派生状态,MapGetters辅助函数
Mutations 更改状态的方法,MapMutations辅助函数
Actions 提交的是mutation,并不是直接变更。可以包含任意异步操作。
Modules 解决单一状态树过于复杂时的问题。将store分割成模块module,每个模块都存在state、mutation、action、getter
<script>
    const store = new Vuex.Store({});
</script>

使用方式

文件 A.vue
<template>
    <div>
        <child ref="RefChild"></child>
        {{ getMessage }}
        {{ getValue}}
        <button @click="update">修改</button>
    </div>
</template>
<script>

    import Child from "./components/child.vue";
    import VuexStore from "./VuexStore.js";
    import {mapGetters} from "vuex";

    export default {
        components: {Child},
        computed: {
            ...mapGetters(["getMessage"]),
            ...mapGetters("moduleA", [
                "getValue"
            ])
        },
        methods: {
            update() {
                console.log(VuexStore);
                this.$refs.RefChild.message = Math.random();
                VuexStore.dispatch("getAsyncMessage", this.$refs.RefChild.message);
            }
        }
    }
</script>

文件 b.vue
<template>
    <div>
        <p>{{ message }}</p>
    </div>
</template>
<script>
    export default {
        data() {
            return {
                message: "message"
            }
        }
    }
</script>

文件 VuexStore.js
<script>
    import {createStore} from "vuex";

    const moduleA = {
        namespaced: true,
        state() {
            return {
                testValue: 1
            };
        },
        getters: {
            getValue: state => state.testValue,
        },
        mutations: {
            changeTestValue(state, testValue) {
                state.testValue = testValue;
            }
        },
        actions: {
            changeAsyncTestValue(store, param) {
                setTimeout(function () {
                    store.commit("changeTestValue", param);
                }, 1000);
            }
        }
    };

    const VuexStore = createStore({
        state() {
            return {
                message: "init",
                number: 1
            };
        },
        modules: {
            moduleA
        },
        getters: {
            getNumber: state => state.number,
            getMessage: state => state.message,
            getValue: state => state.number,
        },
        mutations: {
            updateMessage(state, message) {
                state.message = message;
            },
            pushNumber(state) {
                state.number++;
            }
        },
        actions: {
            getAsyncMessage(store, message) {
                setTimeout(function () {
                    store.commit("updateMessage", message);
                }, 1000);
            }
        }
    });
    export default VuexStore;
</script>

20. 解决Vuex页面刷新状态丢失问题

使用 sessionStorage或者 localStorage将副本保存到本地,页面刷新后重新读取

21. Vuex的属性

  1. getters
  2. mutations
  3. actions
  4. state
  5. modules

22. 不使用Vuex会带来一些什么问题

不好维护,可读性降低

23. 路由传参

<!-- 基于 VueRouter@4 -->
<script>
    import {createRouter, createWebHashHistory} from "vue-router";
    import child from "./components/child.vue";
    import page1 from "./components/page1.vue";

    const Routers = createRouter({
        history: createWebHashHistory(),
        routes: [
            {path: '/', component: child},
            {path: "/page1", component: page1}
        ]
    });
    export default Routers;
</script>
1. query传参
* 组件
<router-link :to="{path: '/page1',query: {a: 1,b: 2}}">Page</router-link>
* js
<script>
    Routers.push({
        path: "/page1",
        query: {
            aaa: 111
        }
    })
</script>

2. params传参(Vue-Router4+后,param模式已经不能使用了)

24. 路由跳转方式

1.
<router-link :to="{path: '/page1',query: {a: 1,b: 2}}">Page</router-link>
2.
<script>
    Routers.push({path: "newPath"});
</script>
2.
<script>
    Routers.push({path: "newPath"});
</script>
3.
<script>
    this.$router.push({path: "/page1"})
</script>

25. Vue路由两种模式

  1. hash模式 浏览器上会出现#,window.location.hash
    • 改变hash不会刷新页面
    • 改变hash会生成新的浏览器记录
  2. history模式 使用html5的pushState和replaceState方法,作用是添加、替换浏览器的历史记录中的目录

26. router和route的区别

route
$route是一个跳转的路由对象,每一个路由都会有一个$route对象,是一个局部对象

router
$router是VueRouter的一个对象,通过Vue得到的一个router的实例对象,且是全局对象

27. vue-router原理

路由是URL与UI之间的单向映射关系。
实现路由变化的方式:hash模式,history模式。

通过Vue.use注册插件,根据URL的变化匹配相对应的路由获取组件并渲染,但不跳转页面。

28. 路由守卫

通过跳转和取消的方式守卫路由。
参数或查询的改变并不会触发进入、离开的导航守卫。可以观察{$route}对象来查看路由的变化。
或者使用 beforeRouteUpdate 的组件内守卫。

注入路由方式:
1. 全局
<script>
    const Routers = createRouter({
        history: createWebHashHistory(),
        routes: [
            {path: '/', component: child},
            {path: "/page1", component: page1},
        ]
    });
    /**
     * 注册全局前置守卫
     * @param to 即将进入的目标
     * @param from 当前导航正要离开的路由
     * @param next next的参数跟下列的返回一样(最新版本不推荐使用了)
     * @return 1. false 取消当前的导航,2. 一个路由地址 { name: 'Login' } 进入此路由 3. undefined/true 此导航继续执行
     */
    Routers.beforeEach((to, from, next) => {
        // 1. next方式进入页面
        // next();
        // 2. return 方式进入页面
        return true;
    })
</script>
2. 单个
<script>
    const beforeEnterComponent = (to, from) => {
        console.log(to, from);
    }

    const Routers = createRouter({
        history: createWebHashHistory(),
        routes: [
            /**
             * 注册单个路由的守卫
             * @param beforeEnter 路由独享的守卫,只在进入路由时触发,不会在params/query/hash改变时触发
             */
            {path: '/', component: child, beforeEnter: beforeEnterComponent},
            {path: "/page1", component: page1, beforeEnter: [beforeEnterComponent, beforeEnterComponent]},
        ]
    });
</script>
3. 组件
添加配置路由如下
{path: '/:id', component: child, beforeEnter: beforeEnterComponent}
<template>
    <div>
        <p>{{ message }}</p>
    </div>
</template>
<script>
    export default {
        data() {
            return {
                message: "message"
            }
        },
        created() {
            console.log(this.$route.params);
        },
        /**
         * 在渲染组件的对应路由被验证前触发
         * 不能获取组件实例"this",在守卫执行时,组件实例还未创建
         * @param to
         * @param from
         */
        beforeRouteEnter(to, from) {
            console.log(to, from);
        },
        /**
         * 在当前路由被改变时,但是还组件被复用时调用
         * 例如:路由【/user/:id】 /user/1 -> /user/2 -> /user/3 跳转时触发
         * 由于会渲染同样的当前组件,因此组件实例会被复用,而这个钩子就在这个情况下复用
         * 所以组件已经挂载好了,导航守卫可以访问组件实例 "this"
         * @param to
         * @param from
         */
        beforeRouteUpdate(to, from) {
            console.log(to, from);
        },
        /**
         * 在导航离开时渲染组件的对应路由时触发
         * 可以访问this
         * @param to
         * @param from
         */
        beforeRouteLeave(to, from) {
            console.log(to, from);
        }
    }
</script>

29. 路由懒加载

使用前提:打包构建应用时,JS过大,影响页面加载速度。
解决方法:将不同组件分割成不同的代码块,当路由被访问时才加载对应组件,就会更高效。
使用Vue-Router的动态导入
<script>
    // 默认导入
    import OwnComponent from "./OwnComponent.vue";
    // 1. 动态导入
    const OwnComponent = () => import("./OwnComponent.vue");
    // 2. 或者直接在Router中使用
    const Routers = createRouter({
        history: createWebHashHistory(),
        routes: [
            // 这里就是使用2的方式
            {path: '/', component: () => import("./OwnComponent.vue")},
            {path: '/home/', component: OwnComponent},
        ]
    });
</script>

30. Vue初始页面闪烁问题

因为Vue初始完成之前,div {{}}这些不属于Vue的控制权。
所以通过原生CSS进行控制
<!doctype html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        .vueLoading {
            position: fixed;
            left: 0;
            top: 0;
            bottom: 0;
            right: 0;
            z-index: 1;
            background: antiquewhite;
            display: flex;
            align-items: center;
            justify-content: center;
        }
    </style>
</head>
<body>
<div id="el">
    <div class="vueLoading" :style="{display: 'none'}">正在进行初始化</div>
    这里是Vue控制区域{{6666}}
</div>
</body>
<script src="vue地址"></script>
<script>
    // .....vue代码
</script>
</html>

31. vue-loader的作用、用途

观看VueLoaderV15版本的官网。

VueLoader是一个webpack的loader,允许开发者使用一种单文件组件(SFCs)的格式来写组件。
1. 允许为Vue组件的每个部分使用其它的webpack loader,例如在style的部分使用Sass和在template部分使用Pug
2. 允许在一个 .vue 文件中使用自定义块,并对其运用自定义的loader链
3. 使用webpack loader将style和template中引用的资源当作模块依赖来处理
4. 为每个组件模拟出scoped CSS
5. 在开发过程中使用热重载保持状态
最后说的:就是一个强大的前端工具,来帮开发者开发项目。

其他的

直接访问VueLoader官网

32. 自定义指令


<script>
    export default {
        created() {
        },
        // 在模板中注册自定义组件
        directives: {
            /**
             * 在模板中注册的自定义组件 tedd
             * @param el 实例对象
             * @param binding
             * @param vnode
             * @param prevNode
             */
            tedd(el, binding, vnode, prevNode) {
                console.log(el, binding, vnode, prevNode);
                el.addEventListener("click", function () {
                    this.innerText = Math.random();
                    binding.value && binding.value();
                })
            },
        },
        methods: {
            wa() {
                console.log("wa");
            }
        }
    }
</script>

<template>
    <div>
        <p></p>
        <p v-tedd:oii.aaa.bbb="wa">页面1
        <p></p></p>
    </div>
</template>

33. Vue性能优化

  1. 减少data中的数据,因为每条属性都会增加getter和setter,会收集对应的watcher
  2. v-if v-for 不在同一个组件上使用,for的优先级高于if
  3. v-for中的绑定事件,可以使用·事件代理·代替
  4. 利用keep-alive缓存组件
  5. 组件不会经常重复变更展示、隐藏的情况下使用 v-if,经常变更使用v-show
  6. 多使用v-bind:key
  7. 使用路由懒加载、异步组件
  8. 防抖、节流
  9. 第三方模块按需加载
  10. 长列表,滚动可视化区域才动态加载
  11. 图片懒加载
  12. 压缩代码
  13. 使用CDN
  14. 服务端开启gzip

Vue3

1. Vue3中为什么使用Proxy代替defineProperty

Object.defineProperty
Vue2实现响应式的原理,是通过Object.defineProperty的getter和setter实现。
<script>
    var data = (() => {
        return {
            message: 1,
            a: 111
        };
    })()

    function updateValue(data, key, value) {
        console.log("updateValue", data, key, value);
    }

    function f1(data, key, value) {
        // 此value getter/setter 取值是同一个,所以数据是同一个
        f(value);
        Object.defineProperty(data, key, {
            configurable: true,
            enumerable: true,
            get() {
                return value;
            },
            set(v) {
                if (value !== v) {
                    value = v;
                    f(value);
                    updateValue(data, key, value);
                }
            }
        });
    }

    function f(data) {
        if (data && typeof data === "object") {
            Object.keys(data).forEach(e => {
                f1(data, e, data[e]);
            });
        }
    }

    f(data);
    data.message = {
        b: 3
    };
    console.log(data, data.message)
</script>

Proxy可以监听此对象的所有操作进行监听、代理所有属性
<script>
    function updateValue(data, key, value) {
        console.log("updateValue", data, key, value);
    }

    var data = (() => {
        var data = {
            message: 1,
            a: 111
        };

        function f(data) {
            if (data && typeof data === "object") {
                return new Proxy(data, {
                    get(target, p, receiver) {
                        return target[p];
                    },
                    set(target, p, newValue, receiver) {
                        if (target[p] !== newValue) {
                            target[p] = f(newValue);
                            updateValue(target, p, newValue);
                        }
                    }
                });
            } else {
                return data;
            }
        }

        return f(data);
    })();
    data.message = {
        b: 3
    };
    data.message.b = 566
    data.message.b = 1
    data.message.b = 2
    console.log(data, data.message)
</script>

2. Vue3新特性

  1. 优点
    • 兼容大部分vue2语法
    • 性能提升
    • 更好支持typescript
    • 使用es6的proxy代替了Object.defineProperty
  2. 性能提升原因
    • 静态标记(Vue2对虚拟dom全局对比,Vue3只对比patchFlags的节点)
    • hoistStatic 静态提升(Vue3静态资源提取到渲染函数render之外,作为常量)
  3. 通过Reflect(反射)动态对被代理对象的响应属性进行特性的操作
  4. Composition组合式API(响应式:setup【ref…】)
  5. Vue创建方式(createApp(App).mount(“#app”); )
  6. v-for、v-if优先级不同(v-if>v-for)

3. Vue3响应式实现原理

使用Proxy代理属性等

4. 编译方面的优化

diff算法优化,引入PatchFlag

5. 生命周期(组合式)

  1. beforeCreate/created 被setup方法代替
  2. onBeforeMount
  3. onMounted
  4. onBeforeUpdate
  5. onUpdated
  6. onBeforeUnmount
  7. onUnmounted

6. Vue3对比Vue2的优势

体积更小、性能更好
更好的TS支持
更好的逻辑抽离
更多的新功能

7. Vue3的watch和Vue2的不同

Vue2中可以通过watch对象来监听多个数据的变化
Vue3引入组合式API,通过多个watch函数来监听多个数据的变化

8. Vue3的watchEffect与watch的区别

watch 需要明确监听的属性
watchEffect 根据其中的属性,自动监听变化

TypeScript

1. TS有什么优点

  • 减少低级的语法错误
  • 规范代码
  • 智能提示

缺点

  • 学习成本高
  • 开发成本高

2. 什么是泛型?有什么用

类型参数,在函数、类、接口中不固定某些类型,使用类型参数的方式来实现指定类型

// 接口
// T, K, F 都是类型参数(泛型)
interface Web2023<T, K, F> {
    t: T;
    k: K;
    f: F;
}

const a: Web2023<String, Number, Array<String>> = {
    t: "sss",
    k: 66,
    f: ["aaa"]
}

// 函数
function f<T>(callback: () => T): T {
    return callback();
}

f<String>(() => {
    return "999";
});

// 类
class A<T> {
    t: T;
}

new A<String>();

3. 类型别名type和接口interface区别

  • 类型别名不能被继承或者实现,接口可以被继承或者实现
  • 类型别名可以定义任意类型,包括联合类型、交叉类型、字面量类型、原始类型等。接口只能定义对象类型,包括属性、方式、索引等。
  • 类型别名通常为复杂类型创建别名,以方便使用。接口通常用于定义某个实体的结构,以及实现该结构的对象或者类

4. 装饰器

装饰器就是一种特殊的声明,它能被附加到类声明、方法、访问符、属性、参数上

执行顺序 (同类型,从下往上,从后往前)
  1. 属性装饰器工厂
  2. 属性装饰器
  3. 函数装饰器工厂
  4. 参数装饰器工厂
  5. 参数装饰器
  6. 函数装饰器
  7. 类装饰器工厂
  8. 类装饰器
  • 属性装饰器
declare type PropertyDecorator = (target: any, propertyKey: string | symbol) => void;

function Attr(target: any, propertyKey: string | symbol) {
    console.log("属性装饰器", target, propertyKey);
}

function AttrA(str: string) {
    console.log("属性装饰器工厂", str);
    return function (target: any, propertyKey: string | symbol) {
        console.log("属性装饰器工厂A", target, propertyKey);
    };
}
  • 函数装饰器
declare type MethodDecorator = (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => PropertyDescriptor | void;

function Fun(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    console.log("函数装饰器", target, propertyKey, descriptor);
    const fun = descriptor.value;
    descriptor.value = function () {
        console.log("函数装饰器 desc")
        fun.apply(this, arguments);
    }
}

function FunA() {
    console.log("函数装饰器A", name);
    return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        console.log("函数装饰器A", target, propertyKey, descriptor);
        const fun = descriptor.value;
        descriptor.value = function () {
            console.log("函数装饰器A Desc")
            fun.apply(this, arguments);
        }
    }
}
  • 参数装饰器
declare type ParameterDecorator = (target: any, propertyKey: string | symbol, parameterIndex: number) => void;

function Param(target: any, propertyKey: string | symbol, parameterIndex: number) {
    console.log("参数装饰器", target, propertyKey, parameterIndex);
}

function ParamA(str: number) {
    console.log("参数装饰器A", str)
    return function (target: any, propertyKey: string | symbol, parameterIndex: number) {
        console.log("参数装饰器A", target, propertyKey, parameterIndex);
    }
}
  • 类装饰器
declare type ClassDecorator = <T extends {
    new(...args: any[]): {}
}>(target: T) => T | void;

function Class<T extends {
    new(...args: any[]): {}
}>(constructor: T) {
    console.log("类装饰器", constructor)
    Object.seal(constructor);
    Object.seal(constructor.prototype);
}

function ClassA<T extends {
    new(...args: any[]): {}
}>(constructor: T) {
    console.log("类装饰器A", constructor)
    return class extends constructor {
        // ...
    }
}

function ClassB(str: string) {
    console.log("类装饰器工厂C", str)
    return function (constructor: Function) {
        console.log("类装饰器工厂C", constructor);
        // Object.seal 静态方法密封一个对象,使其阻止其拓展并且使得现有属性不可配置
        // 密封对象有一组固定的属性:不能添加新属性,不能删除现有属性或更改其可枚举性和可配置性,不能重新分配其原型。
        // 不更改现有属性的特性

        // 密封类的构造函数
        Object.seal(constructor);
        // 密封类的构造函数原型
        Object.seal(constructor.prototype);
    }
}

5. 类型体操

就是复杂的类型操作。

6. any优缺点

缺点,不明确类型
优点,可以不明确类型、快速开发

7. 工具类型

  • Partial 将类型T的所有属性变为可选属性。
  • Required 将T的属性变为必选属性
  • Readonly T的属性变为只读
  • Record<K,T> 使用K的属性作为键,T是键的类型,来建立一个新的类型
  • Pick<T,K> 使用K的属性作为键,T中对应的键的类型作为类型,来建立一个新类型
  • Omit<T,K> 在T的属性中,删除K的属性,来建立一个新类型
  • Exclude<T,U> 是否T继承U,true 返回never,false 返回 T。type Exclude<T, U> = T extends U ? never : T;
  • Extract<T,U> 是否T继承U,true 返回T,false 返回 never。type Extract<T, U> = T extends U ? T : never
  • NonNullable 过滤null/undefined类型
  • ReturnType 获取函数类型T的返回值类型

8. 怎么给第三方库编辑类型文件

  • 创建d.td文件
  • 使用declare module 语句定义模块名,模块名需要与库的导出名称一致。
  • 添加类型定义
  • 导出类型
  • 使用类型文件
declare module 'lodash' {
    function utils(...args: any[]): any;

    export {utils};
}
// 使用位置
import {utils} from "lodash";

9. Vue如何使用TS

  • 创建Vue3项目时,直接创建ts模板
  • Vue2 添加 class-component 和 vue-property-decorator 库来使用TS

10. 修饰符 public/private/protected/readonly

  • readonly 只读
  • public 类成员默认可见,可内部外部访问
  • private 类成员只能在类的内部访问,子类也不能访问
  • protected 类成员在类、子类内部访问,不会暴露属性

11. any/never/unknown/undefined/void的区别

  • any 任意类型,不确定类型
  • never 永远没有结果的类型
  • unknown 未知的类型,any的安全类型
  • undefined 是一个值,也是一个类型。(也就是说有一个占位符,void表示没有占位符)
  • void 空类型

12. 内置数据类型

  • any 任意类型
  • number 数字
  • string 字符串
  • boolean 布尔
  • Array/string[] 数组
  • [string,number…] 元组
  • enum 枚举
  • void 标识没得返回值
  • null 表示对象缺失
  • undefined 声明未定义的值
  • never 用于不会出现的值

13. 协变、逆变

协变、逆变是描述类型系统中泛型或函数类型的子类型关系的两个概念。

  • 协变。TypeA和TypeB,如果由A、B子类型能得出A是B的子类型,称之为协变
  • 逆变。TypeA和TypeB,如果得出B是A的子类型,称之为逆变。

14. implements 与 extends 的区别

  • extends 继承,仅一个类
  • implements 实现,可以实现多个接口

其他

SSR、SSG

  1. SSR 服务端渲染
  2. SSG 预先生成静态资源

window.indexedDB 数据库


<script>
    /**
     * indexedDb 数据库
     */
    class Db {
        /**
         * 服务器配置
         * @type {{tables: [{tableName: string,tableKey: string,autoIncrement?: boolean,table: [{id: string,param: IDBIndexParameters,key: string}]}], serverName: string, version: number}}
         */
        serverConfig = {
            serverName: "",
            version: 1,
            tables: []
        };

        /**
         * IndexDB
         * @param config 配置文件
         * @param config.serverName 数据库名称
         */
        constructor(config) {
            this.serverConfig.serverName = config.serverName;
        }

        /**
         * 检测数数据库初始化是否完成
         * @param {boolean} _throw 是否自动抛出异常
         * @return {boolean}
         */
        check(_throw = true) {
            if (_throw && !!this.iDBRequest) throw "数据库初始化未完成";
            return !!this.iDBRequest;
        }

        /**
         * 数据库对象
         * @type {IDBOpenDBRequest}
         */
        dbOpenDBRequest;
        /**
         * 数据库升级执行回调的信息
         * @type {IDBDatabase}
         */
        iDBDatabase;

        /**
         * 数据库打开成功后执行回调的信息
         * @type {IDBDatabase}
         */
        iDBRequest;

        /**
         * 打开数据库
         * @param config 配置文件
         * @param config.version 版本号
         * @param {[{tableName: string,tableKey: string,autoIncrement?: boolean,table: [{id: string,param: IDBIndexParameters,key: string}]}]} config.tables 数据表格式
         * @param {(this:IDBRequest<IDBDatabase>, ev: Event) => void?} config.onSuccess 成功回调
         * @param {(this:IDBRequest<IDBDatabase>, ev: Event) => void?} config.onError 失败回调
         * @param {(this:IDBOpenDBRequest, ev: IDBVersionChangeEvent) => void?} config.onUpgradeneeded  升级回调
         * @return Promise<IDBRequest<IDBDatabase>>
         */
        openServer(config) {
            this.serverConfig.version = config.version;
            this.serverConfig.tables = config.tables;
            return new Promise((success, fail) => {
                const _this = this;
                console.log("正在打开数据库");
                this.dbOpenDBRequest = indexedDB.open(this.serverConfig.serverName, this.serverConfig.version);
                this.dbOpenDBRequest.onupgradeneeded = function (ev) {
                    console.log("数据库已经升级");
                    config.onUpgradeneeded && config.onUpgradeneeded.bind(this, ev);
                    _this.iDBDatabase = this.result;
                    _this.createTables(this.result);
                    console.log("数据库升级完成");
                }
                this.dbOpenDBRequest.onsuccess = function (ev) {
                    console.log("数据库打开成功");
                    _this.iDBRequest = this.result;
                    config.onSuccess && config.onSuccess.bind(this, ev);
                    success(this.result);
                }
                this.dbOpenDBRequest.onerror = function (ev) {
                    console.log("数据库打开失败");
                    config.onError && config.onError.bind(this, ev);
                    fail(this.result);
                }
            });
        }

        /**
         * 创建表
         * @param {IDBDatabase} request
         */
        createTables(request) {
            if (!request) throw "数据库尚未打开";
            console.log("正在创建数据表");
            const tables = this.serverConfig.tables;
            tables.map(e => {
                if (!request.objectStoreNames.contains(e.tableName)) {
                    /** @type {IDBObjectStore}*/
                    const store = request.createObjectStore(e.tableName, {
                        keyPath: e.tableKey,
                        autoIncrement: e.autoIncrement ?? true
                    });
                    e.table.map(ee => {
                        store.createIndex(ee.id, ee.key, ee.param);
                    });
                }
            });
        }

        /**
         * 执行事务
         * @param {string|[string]} tableName 表名称
         * @param {IDBTransactionMode} mode 事务模式
         * @param config 配置信息
         * @param {(this:IDBTransaction, ev: Event) => any?} config.oncomplete 事务全部完成后执行
         * @param {(this:IDBTransaction, ev: Event) => any} config.onerror 事务异常执行
         * @return {IDBTransaction} 事务对象
         */
        runTransaction(tableName, mode = Db.Mode.ReadWrite, config = {}) {
            const transaction = this.iDBRequest.transaction(tableName, mode);
            config && config.onerror && (transaction.onerror = config.onerror);
            config && config.oncomplete && (transaction.oncomplete = config.oncomplete);
            return transaction;
        }

        /**
         * 事务模式
         * @type {{ReadOnly: IDBTransactionMode, VersionChange: IDBTransactionMode, ReadWrite: IDBTransactionMode}}
         */
        static Mode = {
            ReadOnly: "readonly",
            VersionChange: "versionchange",
            ReadWrite: "readwrite"
        }

        /**
         * 插入数据
         * @param {string|[string]} tableName 表名称
         * @param {Object} data 数据
         * @return Promise<IDBRequest<IDBValidKey>>
         */
        insert(tableName, data) {
            console.log("执行插入数据");
            /**
             * @type {Promise<IDBRequest<IDBValidKey>>}
             */
            return new Promise((success, fail) => {
                /**
                 * @type {IDBRequest<IDBValidKey>}
                 */
                const res = this.runTransaction(tableName, Db.Mode.ReadWrite).objectStore(tableName).add(data);
                res.onsuccess = function () {
                    success(this);
                }
                res.onerror = function (ev) {
                    fail(ev);
                }
            });
        }

        /**
         * 查询数据 根据主键
         * @param tableName 表明
         * @param id 主键值
         */
        select(tableName, id) {
            console.log("查看单条数据");
            return new Promise((success, fail) => {
                const res = this.runTransaction(tableName, Db.Mode.ReadWrite).objectStore(tableName).get(id);
                res.onsuccess = function () {
                    success(this);
                }
                res.onerror = function (ev) {
                    fail(ev);
                }
            });
        }

        /**
         * 查看多条数据
         * @param tableName 库名
         * @param {Object|IDBKeyRange?} key 查询数据的key
         * @param {number?} count 长度
         * @return {Promise<unknown>}
         */
        selectAll(tableName, key, count) {
            console.log("查看多条数据");
            return new Promise((success, fail) => {
                const res = this.runTransaction(tableName, Db.Mode.ReadWrite).objectStore(tableName).getAll(key, count);
                res.onsuccess = function () {
                    success(this);
                }
                res.onerror = function (ev) {
                    fail(ev);
                }
            });
        }

        /**
         * 更新数据
         * @param tableName 表名称
         * @param value 值
         * @param {IDBValidKey?} key 条件、主键的key值,在创建 table 时存在keyPath则不需要传参
         * @return {Promise<unknown>}
         */
        update(tableName, value, key) {
            console.log("修改数据");
            console.log(value, key);
            return new Promise((success, fail) => {
                const res = this.runTransaction(tableName, Db.Mode.ReadWrite).objectStore(tableName).put(value, key);
                res.onsuccess = function () {
                    success(this);
                }
                res.onerror = function (ev) {
                    fail(ev);
                }
            });
        }

        /**
         * 删除
         * @param tableName 表名称
         * @param {IDBValidKey | IDBKeyRange} key 删除的条件
         * @return {Promise<IDBRequest<undefined>>}
         */
        remove(tableName, key) {
            console.log("删除数据");
            return new Promise((success, fail) => {
                const res = this.runTransaction(tableName, Db.Mode.ReadWrite).objectStore(tableName).delete(key);
                res.onsuccess = function () {
                    success(this);
                }
                res.onerror = function (ev) {
                    fail(ev);
                }
            });
        }
    }
</script>

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        th {
            font-size: 14px;
            padding: 10px;
        }

        td {
            font-size: 14px;
            padding: 10px;
        }

        button {
            font-size: 12px;
            padding: 7px 20px;
            border-radius: 20px;
            border: 1px solid gainsboro;
            background: transparent;
            margin: 10px;
        }

        button:hover {
            background: #eeeeee;
        }

        button:active {
            background: #d0d0d0;
        }
    </style>
</head>
<body>
<table border width="1000">
    <thead>
    <tr>
        <th>ID</th>
        <th>随机数</th>
        <th>操作</th>
    </tr>
    </thead>
    <tbody>
    <!--    <tr>-->
    <!--        <td>1</td>-->
    <!--        <td>4561.31561561</td>-->
    <!--        <td>-->
    <!--            <button class="delete">删除</button>-->
    <!--            <button class="update">修改</button>-->
    <!--        </td>-->
    <!--    </tr>-->
    </tbody>
</table>
<button id="insert">添加数据</button>
<button id="select">查看数据</button>
</body>
<script>
    const db = new Db({
        serverName: "service"
    });
    db.openServer({
        version: 14,
        tables: [{
            tableName: "names",
            tableKey: "id",
            table: [{
                id: "id",
                key: "id",
                param: {unique: true}
            }, {
                id: "name",
                key: "name",
                param: {}
            }]
        }, {
            tableName: "names3",
            tableKey: "id",
            autoIncrement: true,
            table: [{
                id: "id",
                key: "id",
            }, {
                id: "name",
            }]
        }],
    }).then(() => {
        select();
    });
    document.querySelector("body").addEventListener("click", function (ev) {
        const target = ev.target;
        if (ev.target.className === "update") {
            const id = target.attributes.getNamedItem("data-id").value;
            db.update("names3", {
                id: id - 0,
                name: Math.random()
            }).then(select);
        }
        if (ev.target.className === "delete") {
            const id = target.attributes.getNamedItem("data-id").value;
            db.remove("names3", id - 0).then(select);
        }
    });

    function select() {
        db.selectAll("names3", IDBKeyRange.lowerBound(0)).then(res => {
            console.log(res.result);
            document.querySelector("table tbody").innerHTML = "";
            res.result.map(e => {
                const data = document.createElement("tr");
                data.innerHTML = `<td>\${e.id}</td>
                    <td>\${e.name}</td>
                    <td>
                        <button data-id="\${e.id}" class="delete">删除</button>
                        <button data-id="\${e.id}" class="update">修改</button>
                    </td>`;
                document.querySelector("table tbody").append(data);
            })
        });
    }

    document.getElementById("insert").onclick = function () {
        db.insert("names3", {
            name: Math.random() * 999,
        });
    }

    document.getElementById("select").onclick = function () {
        select();
    }
</script>
</html>
  • 10
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
2023前端面试中,可能会涉及到以下几个高频面试题: 1. 浏览器兼容性问题及解决方案:面试官可能会询问你在开发过程中遇到的浏览器兼容性问题以及你是如何解决的。你可以提到一些常见的兼容性问题,比如不同浏览器对CSS属性的支持不一致或JavaScript API的兼容性问题。解决方案可以包括使用CSS前缀、Polyfill库或检测浏览器特性并提供不同的代码实现。 2. 前后端接口文档和接口测试:你可能会被问到在前端开发中如何与后端协作。你可以提到根据后端提供的接口文档进行开发,使用工具(比如Postman)测试接口的可用性和返回值是否符合预期。同时,你还可以提到与后端沟通以了解前端需要的参数和数据结构。 3. 跨域问题及解决方案:面试官可能会问到前端如何实现跨域。你可以解释浏览器的同源策略以及由此带来的限制。然后提到一些解决方案,如使用JSONP、CORS(跨源资源共享)、代理服务器或反向代理等。 综上所述,2023前端面试可能涉及浏览器兼容性问题及解决方案、前后端接口文档和接口测试、跨域问题及解决方案等。记住在回答面试问题时,要清晰、简洁地说明问题和解决方案。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [2023高频前端面试题总结(附答案)](https://blog.csdn.net/weixin_45102366/article/details/125525247)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [2023高频前端面试题(含答案)](https://blog.csdn.net/weixin_44672169/article/details/116011608)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值