前端面试2024

HTML篇

<header> : 头部标签
<nav> :       导航标签
<article> :   内容标签
<section> : 定义文档某个区域
<aside> :    侧边栏标签
<footer> :   底部标签

6、网站TDK三大标签SEO优化 
TDK是网站标题(title)、描述(description)、关键词(keywords)这三者的英文首字母缩写,主要用来对当前网页进行总结和概况。

在SEO界,标题、描述、关键词通常也被称为三大标签。

7、标签上title属性与alt属性的区别是什么?
alt是为了在图片未能正常显示时(屏幕阅读器)给予文字说明。且长度必须少于100个英文字符或者用户必须保证替换文字尽可能的短。
title属性为设置该属性的元素提供建议性的信息。使用title属性提供非本质的额外信

CSS

2、rgba和opacity的透明效果有什么不同?
opacity
opacity是一个属性。opacity属性的值,可以被其子元素继承,给父级div设置opacity属性,那么所有子元素都会继承这个属性,并且,该元素及其继承该属性的所有子元素的所有内容透明度都会改变。

rgba(0,0,0,0.5)
rgba是一个属性值。rgba设置的元素,只对该元素的背景色有改变,并且,该元素的后代不会继承该属性。

3、display:none与visibility:hidden的区别?
这两个属性都是让元素隐藏,不可见。两者区别如下:


(1)relative:相对定位,相对于自己本身在正常文档流中的位置进行定位。
(2)absolute:生成绝对定位,相对于最近一级定位不为static的父元素进行定位。
(3)fixed: 生成绝对定位,相对于浏览器窗口或者frame进行定位。(老版本IE不支持)
(4)static:默认值,没有定位,元素出现在正常的文档流中。(很少用)
(5)sticky:生成粘性定位的元素,容器的位置根据正常文档流计算得出。(很少用)

三、HTML&&CSS混合篇


1、Localstorage、sessionStorage、cookie 的区别
共同点:都是保存在浏览器端、且同源的

三者区别:

1、cookie 数据始终在同源的 http 请求中携带(即使不需要),即 cookie 在浏览器和服务器

间来回传递,而 sessionStorage 和 localStorage 不会自动把数据发送给服务器,仅在本地保存。 cookie 数据还有路径(path)的概念,可以限制 cookie 只属于某个路径下

2、存储大小限制也不同,cookie 数据不能超过 4K,同时因为每次 http 请求都会携带 cookie、 所以 cookie 只适合保存很小的数据,如会话标识。sessionStorage 和 localStorage 虽然也有存 储大小的限制,但比 cookie 大得多,可以达到 5M 或更大。

3、数据有效期不同

sessionStorage:仅在当前浏览器窗口关闭之前有效;

localStorage:始终有效,窗口或浏览器关闭也一直保存,因此用作持久数据;

cookie:只在设置的 cookie 过期时间之前有效,即使窗口关闭或浏览器关闭

4、作用域不同

sessionStorage 不在不同的浏览器窗口中共享,即使是同一个页面;

localstorage 在所有同源窗口中都是共享的;

cookie 也是在所有同源窗口中都是共享的

5、web Storage 支持事件通知机制,可以将数据更新的通知发送给监听者。

6、web Storage 的 api 接口使用更方便。

4、img 的 alt 与 title 的异同,还有实现图片懒加载的原理?
异同
alt 是图片加载失败时,显示在网页上的替代文字; title 是鼠标放上面时显示的文字,title

是对图片的描述与进一步说明;

这些都是表面上的区别,alt 是 img 必要的属性,而 title 不是 对于网站 seo 优化来说,title 与 alt 还有最重要的一点: 搜索引擎对图片意思的判断,主 要靠 alt 属性。所以在图片 alt 属性中以简要文字说明,同时包含关键词,也是页面优化的 一部分。条件允许的话,可以在 title 属性里,进一步对图片说明 由于过多的图片会严重影响网页的加载速度,并且移动网络下的流量消耗巨大,所以 说延迟加载几乎是标配了。

原理
图片懒加载的原理很简单,就是我们先设置图片的 data-set 属性(当然也可以是其他任意的, 只要不会发送 http 请求就行了,作用就是为了存取值)值为其图片路径,由于不是 src,所 以不会发送 http 请求。 然后我们计算出页面 scrollTop 的高度和浏览器的高度之和, 如果 图片举例页面顶端的坐标 Y(相对于整个页面,而不是浏览器窗口)小于前两者之和,就说明图片就要显示出来了(合适的时机,当然也可以是 其他情况),这时候我们再将data-set 属性替换为 src 属性即可。

JavaScript


1、JS数据类型


JS数据基础类型有:
String、Number、Boolean、Null、undefined五种基本数据类型,加上新增的两种ES6的类型Symbol、BigInt

JS有三种 复杂类型 (引用数据类型): 
Object(对象)、Array(数组)、function(函数)

箭头函数与普通函数的区别?


(1) 箭头函数比普通函数更加简洁
        如果没有参数 , 就直接写一个空括号即可 , 如果只有一个参数 , 可以省去参数的括号     如果有多个参数 , 用逗号分割 , 如果函数体的返回值只有一句 , 可以省略大括号。
(2) 箭头函数没有自己的this
        箭头函数不会创建自己的this, 所以它没有自己的this, 它只会在自己作用域的上一层继承this。所以箭头函数中this的指向在它在定义时已经确定了, 之后不会改变。
(3) 箭头函数继承来的this指向永远不会改变
(4) call()、apply()、bind()等方法不能改变箭头函数中this的指向  
(5) 箭头函数不能作为构造函数使用
        由于箭头函数时没有自己的this,且this指向外层的执行环境,且不能改变指向,所以不能当做构造函数使用。 
(6) 箭头函数没有自己的arguments对象。在箭头函数中访问arguments实际上获得的是它外层函数的arguments值。 
(7) 箭头函数没有prototype 
(8) 补充:箭头函数的this指向哪⾥?
        箭头函数不同于传统JavaScript中的函数,箭头函数并没有属于⾃⼰的this,它所谓的this是捕获其所在上下⽂的 this 值,作为⾃⼰的 this 值,并且由于没有属于⾃⼰的this,所以是不会被new调⽤的,这个所谓的this也不会被改变。


原型链


前提须知:
(1) prototype:所有的函数都有原型prototype属性,这个属性指向函数的原型对象。
(2) __proto__,这是每个对象(除null外)都会有的属性,叫做__proto__,这个属性会指向该对象的原型。
(3) constructor: 每个原型都有一个constructor属性,指向该关联的构造函数。

原型链:获取对象时,如果这个对象上本身没有这个属性时,它就会去它的原型__proto__上去找,如果还找不到,就去原型的原型上去找...一直直到找到最顶层(Object.prototype)为止,Object.prototype对象也有__proto__属性,值为null
此外,每一个prototype原型上都会有一个constructor属性,指向它关联的构造函数。


闭包的理解?


概念:有权访问另一个函数内部变量的函数。
本质:是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量,利用闭包可以突破作用链域,将函数内部的变量和方法传递到外部。
面试:什么是闭包?
    通俗的来说:闭包是在一个函数内部在定一个函数,然后内部函数访问外部函数的一个变量就会形成闭包,闭包的话会形成一个私有空间,然后避免全局变量的一个污染,然后会持久化存储数据到内存中,但是闭包也有弊端,它会导致内存泄漏

拓展:内存泄漏怎么解决?
首先避免它的使用,其次的话就是变量执行完以后,可以让它赋值为null,最后利用JS的一个垃圾回收机制进行回收

闭包用处:
读取内部函数的变量;
这些变量的值始终会保持在内存中,不会在外层函数调用后被自动清除
闭包优点:
变量会一直在内存中;        
避免全局变量的污染;
私有变量的存在;
闭包缺点:
    变量长期储存在内存中,会增大内存的使用量,使用不当会造成内存泄露

判断闭包的3个特点:
  1.函数嵌套函数;

  2.内部函数一定操作了外部函数的局部变量;

  3.外部函数一定将内部函数返回到外部并保存在一个全局变量中;

判断闭包的执行结果:
  1.外部函数被调用了几次就有几个受保护的局部变量的副本;

  2.来自一个闭包的函数被调用几次,受保护的局部变量就变化几次;

闭包特性:
函数嵌套函数;
内部函数可以直接使用外部函数的局部变量;
变量或参数不会被垃圾回收机制回收;


JS垃圾回收机制


垃圾回收机制(Garbage Collection)简称GC:是JavaScript中使用的内存管理系统的基本组成部分
JavaScript 是在创建变量(对象,字符串等)时自动进行了分配内存,并且在不使用它们时会 “自动” 释放、释放的过程成为垃圾回收
内存在不使用的时候会被垃圾回收器自动回收
 

typeof和instanceof的区别是什么? 


(1)typeof 的返回值是一个字符串,用来说明变量的数据类型;

typeof用于数据类型的判断,返回值有number、string、boolean、function、undefined、object 六个。但是,在其中你会发现,typeof判断null、array、object以及函数的实例(new + 函数)时,它返回的都是object。这就导致在判断这些数据类型的时候得不到真实的数据类型。所以,typeof 存在的弊端——它虽然可以判断基本数据类型(null 除外),但是引用数据类型中,除了function 类型以外,其他的也无法判断。

(2)instanceof的返回值是布尔值,用于判断一个变量是否属于某个对象的实例。instanceof 可以准确地判断复杂引用数据类型,但是不能正确判断基础数据类型。


14、如何使用原生 JavaScript 给一个按钮绑定两个 onclick 事件?
 
<button id="btn">点击</button>
 
<script>
    //通过事件监听  绑定多个事件
    let btn = document.getElementById('btn')
    btn.addEventListener('click', one)
    btn.addEventListener('click', two)
    function one() {
        alert('第一个')
    },
    function two() {
        alert('第二个')
    },
</script>
15、var、let和const的区别?
(1)块级作用域: 块作用域由 { }包括,let和const具有块级作用域,var不存在块级作用域。块级作用域解决了ES5中的两个问题:

内层变量可能覆盖外层变量
用来计数的循环变量泄露为全局变量
(2)变量提升: var存在变量提升,let和const不存在变量提升,即在变量只能在声明之后使用,否在会报错。

(3)给全局添加属性: 浏览器的全局对象是window,Node的全局对象是global。var声明的变量为全局变量,并且会将该变量添加为全局对象的属性,但是let和const不会。

(4)重复声明: var声明变量时,可以重复声明变量,后声明的同名变量会覆盖之前声明的遍历。const和let不允许重复声明变量。

(5)暂时性死区: 在使用let、const命令声明变量之前,该变量都是不可用的。这在语法上,称为暂时性死区。使用var声明的变量不存在暂时性死区。

(6)初始值设置: 在变量声明时,var 和 let 可以不用设置初始值。而const声明变量必须设置初始值。

(7)指针指向: let和const都是ES6新增的用于创建变量的语法。 let创建的变量是可以更改指针指向(可以重新赋值)。但const声明的变量是不允许改变指针的指向。



讲解js的call、apply和bind区别?


首先,call apply bind三个方法都可以用来改变函数的this指向,具体区别如下:

call( ) 是接收一个及其以上的参数,第一个参数表示this要指向的对象,其余参数表示调用函数需要传入的参数,返回调用函数的返回结果,属于立即执行函数;
apply( ) 是接收两个参数,第一个参数表示this要指向的对象,第二参数表示调用函数需要传入的参数所组成的数组,返回调用函数的返回结果,属于立即执行函数;
bind( ) 是接收一个及其以上的参数,和call()一致,但是其返回是一个函数,而不是调用函数的返回结果
call、apply、bind相同点:都是改变this的指向,传入的第一个参数都是绑定this的指向,在非严格模式中,如果第一个参数是nul或者undefined,会把全局对象(浏览器是window)作为this的值,要注意的是,在严格模式中,null 就是 null,undefined 就是 undefined
call和apply唯一的区别是:call传入的是参数列表,apply传入的是数组,也可以是类数组
bind和call、apply的区别: bind返回的是一个改变了this指向的函数,便于稍后调用,不像call和apply会立即调用;bind和call很像,传入的也是参数列表,但是可以多次传入,不需要像call,一次传入
值得注意:当 bind 返回的函数 使用new作为构造函数时,绑定的 this 值会失效,this指向实例对象,但传入的参数依然生效 (new调用的优先级 > bind调用)

栈溢出及解决方法

缓冲区溢出是由于C语言系列设有内置检查机制来确保复制到缓冲区的数据不得大于缓冲区的大小,因此当这个数据足够大的时候,将会溢出缓冲区的范围。
栈溢出就是缓冲区溢出的一种。 由于缓冲区溢出而使得有用的存储单元被改写, 往往会引发不可预料的后果。程序在运行过程中,为了临时存取数据的需要,一般都要分配一些内存空间,通常称这些空间为缓冲区。如果向缓冲区中写入超过其本身长度的数据,以致于缓冲区无法容纳,就会造成缓冲区以外的存储单元被改写,这种现象就称为缓冲区溢出。缓冲区长度一般与用户自己定义的缓冲变量的类型有关。

由于缓冲区溢出而使得有用的存储单元被改写,往往会引发不可预料的后果。向这些单元写入任意的数据,一般只会导致程序崩溃之类的事故,对这种情况我们也至多说这个程序有Bug。但如果向这些单元写入的是精心准备好的数据,就可能使得程序流程被劫持,致使不希望的代码被执行,落入攻击者的掌控之中,这就不仅仅是bug,而是漏洞(exploit)了。

栈溢出的解决方法

减少栈空间的需求,不要定义占用内存较多的auto变量,应该将此类变量修改成指针,从堆空间分配内存。
函数参数中不要传递大型结构/联合/对象,应该使用引用或指针作为函数参数。
减少函数调用层次,慎用递归函数,例如A->B->C->A环式调用。

webpack


1、谈谈你对Webpack的理解

Webpack是一个模块打包工具,可以使用它管理项目中的模块依赖,并编译输出模块所需的静态文件。
它可以很好地管理、打包开发中所用到的HTML,CSS,JavaScript和静态文件(图片,字体)等,让开发更高效。
对于不同类型的依赖,Webpack有对应的模块加载器,而且会分析模块间的依赖关系,最后合并生成优化的静态资源。
2、Webpack的基本功能有哪些?

代码转换:TypeScript 编译成 JavaScript、SCSS 编译成 CSS 等等
文件优化:压缩 JavaScript、CSS、HTML 代码,压缩合并图片等
代码分割:提取多个页面的公共代码、提取首屏不需要执行部分的代码让其异步加载
模块合并:在采用模块化的项目有很多模块和文件,需要构建功能把模块分类合并成一个文件
自动刷新:监听本地源代码的变化,自动构建,刷新浏览器
代码校验:在代码被提交到仓库前需要检测代码是否符合规范,以及单元测试是否通过
自动发布:更新完代码后,自动构建出线上发布代码并传输给发布系统。
3、Webpack构建过程?

从entry里配置的module开始递归解析entry依赖的所有module
每找到一个module,就会根据配置的loader去找对应的转换规则
对module进行转换后,再解析出当前module依赖的module
这些模块会以entry为单位分组,一个entry和其所有依赖的module被分到一个组Chunk
最后Webpack会把所有Chunk转换成文件输出在整个流程中Webpack会在恰当的时机执行plugin里定义的逻辑
4、有哪些常见的Loader?

optimize-css-assets-plugin:压缩css;
file-loader:把文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件 (处理图片和字体)
url-loader:与 file-loader 类似,区别是用户可以设置一个阈值,大于阈值会交给 file-loader 处理,小于阈值时返回文件 base64 形式编码 (处理图片和字体)
css-loader:加载 CSS,支持模块化、压缩、文件导入等特性
style-loader:把 CSS 代码注入到 JavaScript 中,通过 DOM 操作去加载 CSS
json-loader: 加载 JSON 文件(默认包含)
ts-loader: babel-loader:把 ES6 转换成 ES5
ts-loader: 将 TypeScript 转换成 JavaScript
less-loader:将less代码转换成CSS
eslint-loader:通过 ESLint 检查 JavaScript 代码
vue-loader:加载 Vue单文件组件
5、有哪些常见的Plugin?

html-webpack-plugin:自动创建一个html文件,并把打包好的js插入到html中
uglifyjs-webpack-plugin:不支持 ES6 压缩 ( Webpack4 以前)
mini-css-extract-plugin: 在每一次打包之前,删除整个输出文件夹下的内容;
clean-webpack-plugin: 抽离css代码放到一个单独的文件中;
copy-webpack-plugin: 拷贝文件
webpack-bundle-analyzer: 可视化 Webpack 输出文件的体积 (业务组件、依赖第三方模块)
optimize-css-assets-plugin:压缩css;
6、那你再说一说Loader和Plugin的区别?

Loader 本质就是一个函数,在该函数中对接收到的内容进行转换,返回转换后的结果。 因为 Webpack 只认识 JavaScript,所以 Loader 就成了翻译官,对其他类型的资源进行转译的预处理工作。
Plugin 就是插件,基于事件流框架 Tapable,插件可以扩展 Webpack 的功能,在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。
Loader 在 module.rules 中配置,作为模块的解析规则,类型为数组。每一项都是一个 Object,内部包含了 test(类型文件)、loader、options (参数)等属性。
Plugin 在 plugins 中单独配置,类型为数组,每一项是一个 Plugin 的实例,参数都通过构造函数传入。
7、如何优化 Webpack 的构建速度?

(1)使用高版本的 Webpack 和 Node.js

(2)压缩代码

通过 uglifyjs-webpack-plugin 压缩JS代码
通过 mini-css-extract-plugin 提取 chunk 中的 CSS 代码到单独文件,通过 css-loader 的 minimize 选项开启 cssnano 压缩 CSS。
(3)多线程/多进程构建:thread-loader, HappyPack

(4)压缩图片: image-webpack-loader

(5)缩小打包作用域

exclude/include (确定 loader 规则范围)
resolve.modules 指明第三方模块的绝对路径 (减少不必要的查找)
resolve.mainFields 只采用 main 字段作为入口文件描述字段 (减少搜索步骤,需要考虑到所有运行时依赖的第三方模块的入口文件描述字段)
resolve.extensions 尽可能减少后缀尝试的可能性
noParse 对完全不需要解析的库进行忽略 (不去解析但仍会打包到 bundle 中,注意被忽略掉的文件里不应该包含 import、require、define 等模块化语句)
ignorePlugin (完全排除模块)
8、说一下 Webpack 的热更新原理吧?

Webpack 的热更新又称热替换(Hot Module Replacement),缩写为 HMR。 这个机制可以做到不用刷新浏览器而将新变更的模块替换掉旧的模块。
HMR的核心就是客户端从服务端拉去更新后的文件,准确地说是 chunk diff (chunk 需要更新的部分),实际上 WDS 与浏览器之间维护了一个 Websocket,当本地资源发生变化时,WDS 会向浏览器推送更新,并带上构建时的 hash,让客户端与上一次资源进行对比。客户端对比出差异后会向 WDS 发起 Ajax 请求来获取更改内容(文件列表、hash),这样客户端就可以再借助这些信息继续向 WDS 发起 jsonp 请求获取该chunk的增量更新。
后续的部分(拿到增量更新之后如何处理?哪些状态该保留?哪些又需要更新?)由 HotModulePlugin 来完成,提供了相关 API 以供开发者针对自身场景进行处理,像react-hot-loader 和 vue-loader 都是借助这些 API 实现 HMR。
9、什么是bundel?什么是chunk?什么是model?

bundel:是webpack打包后的一个文件;
chunk:代码块,一个chunk 可能有很多的模块组成,用于合并和分割代码;
model:模块,在webpck中,一切都是模块,一个文件就是一个模块,她从入口开始查找webpck依赖的所有模块
10、webpack和grunt以及gulp有什么不同?

grunt和gulp是基于任务处理的工具,我们需要把我们要做的事分配成各种各样的任务,grunt和gulp会自动执行各种分配的任务,像流水线一样,把资源放上去通过不同的插件进行加工,他的插件非常丰富,能够为我们打造各种工作流;
webpack是模块化打包工具,把所有文件都当作模块进行处理,也就是说webpack和grunt和gulp是两种完全不一样的东西;
18、 const定义的对象属性是否可以改变?
情况一:const定义的变量存在块级作用域,且不存在变量提升,一般用于定义常量,定义的时候必须初始化。

答:不可以,const定义的如果是基本数据类型(string,number,boolean,null,undifined,symbol),定义后就不可再修改,如果修改,会报错。

情况二:那么如果是const定义的对象呢?是否可以修改对象中的属性?
答案:可以

原因:对象是引用类型的,const定义的对象t中保存的是指向对象t的指针,这里的“不变”指的是指向对象的指针不变,而修改对象中的属性并不会让指向对象的指针发生变化,所以用const定义对象,对象的属性是可以改变的。

19、栈溢出及解决方法?
栈溢出(stack Overflow)

缓冲区溢出是由于C语言系列设有内置检查机制来确保复制到缓冲区的数据不得大于缓冲区的大小,因此当这个数据足够大的时候,将会溢出缓冲区的范围。
栈溢出就是缓冲区溢出的一种。 由于缓冲区溢出而使得有用的存储单元被改写, 往往会引发不可预料的后果。程序在运行过程中,为了临时存取数据的需要,一般都要分配一些内存空间,通常称这些空间为缓冲区。如果向缓冲区中写入超过其本身长度的数据,以致于缓冲区无法容纳,就会造成缓冲区以外的存储单元被改写,这种现象就称为缓冲区溢出。缓冲区长度一般与用户自己定义的缓冲变量的类型有关。

由于缓冲区溢出而使得有用的存储单元被改写,往往会引发不可预料的后果。向这些单元写入任意的数据,一般只会导致程序崩溃之类的事故,对这种情况我们也至多说这个程序有Bug。但如果向这些单元写入的是精心准备好的数据,就可能使得程序流程被劫持,致使不希望的代码被执行,落入攻击者的掌控之中,这就不仅仅是bug,而是漏洞(exploit)了。

栈溢出的解决方法

减少栈空间的需求,不要定义占用内存较多的auto变量,应该将此类变量修改成指针,从堆空间分配内存。
函数参数中不要传递大型结构/联合/对象,应该使用引用或指针作为函数参数。
减少函数调用层次,慎用递归函数,例如A->B->C->A环式调用。
20、JS如何实现多线程?
什么是JavaScript的多线程?
JavaScript本身是单线程的,但是可以通过实现多线程来提高性能和用户体验。多线程允许JavaScript在等待用户交互或网络请求时,执行其他任务,从而提高页面加载速度和响应速度。

JavaScript中有哪些实现多线程的方式?
JavaScript有多种实现多线程的方式,包括Web Workers、SharedArrayBuffer、WebAssembly等。其中,Web Workers允许在后台线程中运行JavaScript代码,而SharedArrayBuffer和BufferSource API则允许在多个线程之间共享数据。

如何使用Web Workers实现多线程?
使用Web Workers实现多线程需要创建一个新的worker线程,并将需要执行的代码作为字符串传递给worker。worker线程可以访问全局对象messageChannel的postMessage方法来发送消息,主线程可以使用onmessage方法来接收消息并执行相应的操作。

如何保证多线程安全?
多线程环境下的安全问题主要包括数据竞争和死锁等。为了解决这些问题,需要使用同步机制,如使用Promise、async/await等异步编程方式,或者使用事件循环、共享内存等机制来保证数据的一致性和安全性。

描述一个实际的多线程应用场景。
在实际应用中,多线程可以用于提高页面加载速度和响应速度,例如在电商网站中,可以使用Web Workers在后台线程中加载和处理商品图片,从而提高页面加载速度和用户体验。同时,多个并发请求也可以使用Web Workers并行处理,提高系统性能和响应速度。

21、浅拷贝和深拷贝区别概念常见情况?
浅拷贝:在栈内存中重新开辟一块内存空间,并将拷贝对象储存在栈内存中的数据存放到其中。
深拷贝:基于浅拷贝的过程如果栈内存里面存储的是一个地址,那么其在堆内存空间中的数据也会被重新拷贝一个并由栈空间新创建的地址指向。


1、基本类没有问题
因为,基本类型赋值时,赋的是数据(所以,不存在深拷贝和浅拷贝的问题)。

 例如1:
    var x = 100;
    var y = x; //此时x和y都是100;
   如果要改变y的值,x的值不会改变。

2、引用类型有问题
因为,引用类型赋值时,赋的值地址(就是引用类型变量在内存中保存的内容)
 例如2:
var arr1 = new Array(12,23,34)
var arr2 = arr1;  //这就是一个最简单的浅拷贝
如果要改变arr2所引用的数据:arr2[0]=100时,那么arr1[0]的值也是100。
原因就是 arr1和arr2引用了同一块内存区域(以上的第二点中有体现)。
这是最简单的浅拷贝,因为,只是把arr1的地址拷贝的一份给了arr2,并没有把arr1的数据拷贝一份。所以,拷贝的深度不够。

一、常见的 “浅” 拷贝方法:

除了上面我们演示的对于赋值操作,下面将介绍一些开发中可能会用到,当然也可以会被面试官问到的实现深浅拷贝的方法。

1. Object.assign()
方法解释:方法用于将所有可枚举属性的值从一个或多个源对象分配到目标对象,它将返回目标对象可以实现一个浅拷贝的效果。
参数一:目标对象
参数二:源对象
 

var obj1 = {
            a: 1,
            b: 2,
            c: ['c', 't', 'r']
        }
var obj2 = Object.assign({}, obj1);
obj2.c[1] = 5;
obj2.b = 3
console.log(obj1); // {a:1,b:2,c:["c", 5, "r"]}
console.log(obj2); // {a:1,b:3,c:["c", 5, "r"]}
console.log(obj1.c); // ["c", 5, "r"]
console.log(obj2.c); // ["c", 5, "r"]
注意:可见Object.assign()方法对于一维数据是深拷贝效果,但是对于多维数据是浅拷贝效果。Object.assign是一个浅拷贝,它只是在根属性(对象的第一层级)创建了一个新的对象,但是对于属性的值是仍是对象的话依然是浅拷贝,

2. slice()

方法解释:数组进行截取,如果不传参数,会使用默认值,得到一个与原数组元素相同的新数组。
参数一:截取的起始位置
参数二:截取的结束位置
 

var a = [1, [1, 2], 3, 4];
var b = a.slice();
a[0] = 99
b[1][0] = 2;
console.log(a); // [99,[2,2],3,4]
console.log(b); // [1,[2,2],3,4]
注意:可见slice()方法也只是对一维数据进行深拷贝,但是对于多维的数据还是浅拷贝效果。

3. concat()方法

方法解释:数组的拼接(将多个数组或元素拼接形成一个新的数组),不改变原数组,如果不传参数,会使用默认值,得到一个与原数组元素相同的新数组 (复制数组)。
 

var a = [1, 2, [3, 4]]
var c = [];
var b = c.concat(a);
a[0] = 99
b[2][1] = 88
console.log(a); // [99,2,[3,88]]
console.log(b); // [1,2,[3,88]]
注意:可见concat()方法也只对一维数据具有深拷贝效果,对于多维的数据任然只是浅拷贝

4. ES6拓展运算符

var a = [1, 2, [3, 4]]
var b = [...a];
a[2][1] = 88
b[1] = 99
console.log(a); // [1,2,[3,88]]
console.log(b); // [1,99,[3,88]]
注意: 可见ES6的展开运算符对于一维数据是深拷贝效果,但是对于多维数据任然是浅拷贝效果。

二、实现 “深” 拷贝常见方法:

1. JSON.parse(JSON.stringify(obj))

JSON.stringify()是目前前端开发过程中最常用的深拷贝方式,原理是把一个对象序列化成为一个JSON字符串,将对象的内容转换成字符串的形式再保存在磁盘上,再用JSON.parse()反序列化将JSON字符串变成一个新的对象

JSON.stringfy() 将对象序列化成json对象
JSON.parse() 反序列化——将json对象反序列化成js对象
function deepCopy(obj1){
    let _obj = JSON.stringify(obj1);
    let obj2 = JSON.parse(_obj);
    return obj2;
}
var a = [1, [1, 2], 3, 4];
var b = deepCopy(a);
b[1][0] = 2;
console.log(a); // 1,1,2,3,4
console.log(b); // 1,2,2,3,4
注意:它会抛弃对象的constructor,深拷贝之后,不管这个对象原来的构造函数是什么,在深拷贝之后都会变成Object类型,这种方法能正确处理的对象只有 Number, String, Boolean, Array, 扁平对象,
也就是说,只有可以转成JSON格式的对象才可以这样用,像function没办法转成JSON。

2. 使用第三方库实现对象的深拷贝,比如:lodash、jQuery

import lodash from 'lodash'
var objects = [1,{ 'a': 1 }, { 'b': 2 }]; 
var deep = lodash.cloneDeep(objects);
deep[0] = 2;
deep[1].a = 2;
console.log(objects); // [1,{ 'a': 1 }, { 'b': 2 }]
console.log(deep); //[2,{ 'a': 2 }, { 'b': 2 }]
3. 递归

这里简单封装了一个deepClone的函数,for in遍历传入参数的值,如果值是引用类型则再次调用deepClone函数,并且传入第一次调用deepClone参数的值作为第二次调用deepClone的参数,如果不是引用类型就直接复制

var obj1 = {
    a:{
        b:1
    }
};
function deepClone(obj) {
    var cloneObj = {}; //在堆内存中新建一个对象
    for(var key in obj){ //遍历参数的键
       if(typeof obj[key] ==='object'){ 
          cloneObj[key] = deepClone(obj[key]) //值是对象就再次调用函数
       }else{
           cloneObj[key] = obj[key] //基本类型直接复制值
       }
    }
    return cloneObj 
}
var obj2 = deepClone(obj1);
obj1.a.b = 2;
console.log(obj2); //{a:{b:1}}
但是还有很多问题

首先这个deepClone函数并不能复制不可枚举的属性以及Symbol类型
这里只是针对Object引用类型的值做的循环迭代,而对于Array,Date,RegExp,Error,Function引用类型无法正确拷贝
对象循环引用成环了的情况
22、promise和async,await的区别? 
Promise和async/await是JavaScript中处理异步操作的两种方式。

1. Promise是一种用于处理异步操作的对象。它可以表示一个异步操作的最终完成或失败,并返回相应的结果或错误信息。Promise有三种状态:pending(进行中)、fulfilled(已完成)和rejected(已拒绝)。通过调用Promise的then()方法可以注册回调函数来处理异步操作的结果。

2. async/await是ES8引入的一种更加简洁的处理异步操作的方式。async函数是一个返回Promise对象的函数,其中可以使用await关键字来等待一个Promise对象的解决。await关键字可以暂停async函数的执行,直到Promise对象解决为止,并返回解决后的结果。

区别:

- 语法上,Promise使用then()和catch()方法来处理异步操作的结果,而async/await使用async函数和await关键字来等待异步操作的结果。
- 可读性上,async/await更加直观和易于理解,代码结构更加清晰,而Promise则需要通过链式调用then()方法来处理多个异步操作。
- 错误处理上,Promise使用catch()方法来捕获错误,而async/await可以使用try-catch语句来捕获错误。
详细解答:

JavaScript的异步机制包括以下几个步骤:
(1)所有同步任务都在主线程上执行,行成一个执行栈
(2)主线程之外,还存在一个任务队列,只要异步任务有了结果,就会在任务队列中放置一个事件
(3)一旦执行栈中的所有同步任务执行完毕,系统就会读取任务队列,看看里面还有哪些事件,哪些对应的异步任务,于是异步任务结束等待状态,进入执行栈,开始执行
(4)主线程不断的重复上面的第三步
promise的用法
Promise,简单来说就是一个容器,里面保存着某个未来才会结束的时间(通常是一个异步操作的结果)

基本语法
 let obj = new Promise((resolve,reject) => {
        //...
        resolve('success')
    });
    
 obj.then(result => {
        console.log(result); //success
 });
promise共有三个状态

pending(执行中)、resolve(成功)、rejected(失败)

链式调用
Promise 链式调用是一种编程模式,允许在异步操作之间顺序执行多个操作。在每个操作中,可以使用 `.then()` 方法返回一个新的 Promise,从而在异步流程中继续执行下一个操作。这样可以避免回调函数地狱,提高代码的可读性和可维护性。

链式调用的基本步骤包括:

创建一个新的 Promise 对象,并调用 `resolve` 或 `reject` 来变更其状态。
在 `then` 或 `catch` 方法中,处理成功或失败的状态。
在 `then` 方法中,可以使用 `return` 关键字返回一个新的 Promise 对象,或者直接返回普通值。
继续在下一个 `then` 方法中处理返回的 Promise 对象,或者直接处理返回的普通值。
例如,以下代码展示了如何使用 `.then()` 和 `.catch()` 方法进行链式调用:

let promise1 = new Promise((resolve, reject) => {
    resolve('new promise111111');
});
 
promise1.then(res => {
    console.log(res); // 输出: 'new promise111111'
    return '链式调用的方式';
  }).then(value => {
        console.log(value); // 输出: '链式调用的方式'
});
在这个例子中,`promise1` 被成功地 resolve 并返回了一个值,然后 `then` 方法被调用,返回了一个新的 Promise 对象,并返回了 `'链式调用的方式'`。这个新的 Promise 对象又被继续 `.then` 处理,最终返回了 `'链式调用的方式'`。

需要注意的是,每次 `.then` 方法调用都会返回一个新的 Promise 对象,因此链式调用的结果取决于最后 `.then` 方法中返回的值或新的 Promise 对象。

错误捕获
Promise.prototype.catch用于指定Promise状态变为rejected时的回调函数,可以认为是.then的简写形势,返回值跟.then一样

let obj = new Promise((resolve,reject) => {
    reject('error');
});
 
obj.catch(result => {
    console.log(result);
})
async、await的用法
特点简洁:异步编程的最高境界就是不关心它是否是异步。async、await很好的解决了这一点,将异步强行转换为同步处理。
async/await与promise不存在谁代替谁的说法,因为async/await是寄生于Promise,Generater的语法糖。

用法
async用于申明一个function是异步的,而await可以认为是async wait的简写,等待一个异步方法执行完成。
规则:
1 async和await是配对使用的,await存在于async的内部。否则会报错
2 await表示在这里等待一个promise返回,再接下来执行
3 await后面跟着的应该是一个promise对象,(也可以不是,如果不是接下来也没什么意义了…)

写法
async function demo() {
    let a= await sleep(100); //上一个await执行之后才会执行下一句
    let b= await sleep(a+ 100);
    let c= await sleep(b+ 100);
    return c; // console.log(c);
}
 
demo().then(result => {
    console.log(result);
});
错误捕获
如果是reject状态,可以用try-catch捕捉

let obj = new Promise((resolve,reject) => {
    setTimeout(() => {
        reject('error');
    },1000);
});
 
async function demo(item) {
    try {
        let result = await obj;
    } catch(e) {
        console.log(e);
    }
}
 
demo();
两者区别
1、promise是ES6,async/await是ES7
2、async/await相对于promise来讲,写法更加优雅
3、reject状态:
    (1)promise错误可以通过catch来捕捉,建议尾部捕获错误,
    (2)async/await既可以用.then又可以用try-catch捕捉

Vue2

优点
 

vue框架的优点:

1、响应式数据绑定:通过使用Vue的数据绑定语法,可以实现数据的自动更新。

2、组件化开发:Vue允许将页面划分为独立的组件,提高了代码的可维护性和复用性。

3、虚拟DOM:Vue使用虚拟DOM来跟踪页面上的变化,并高效地更新实际的DOM。

4、指令系统:Vue提供了丰富的内置指令,用于处理常见的DOM操作和逻辑控制。 生态系统:Vue拥有庞大的生态系统,包括插件、工具和第三方库,可以满足各种开发需求。


​1、什么是前端构建工具?比如(Vue2的webpack,Vue3的Vite)
①首先要明白:浏览器它只认识html, css, js
企业级项目里都可能会具备哪些功能?
1. typescript:·如果遇到ts文件我们需要使用tsc将typescript代码转换为js代码
2. React/Vue:安装react-compiler / vue-complier,将我们写的jsx文件或者.vue文件转换为render函数
3. less/postcss/component-style:我们又需要安装less-loader, sass-loader等一系列编译工具4. 语法降级:babel --->将es的新语法转换旧版浏览器可以接受的语法
5. 体积优化:uglifyjs --->将我们的代码进行压缩变成体积更小性能更高的文件
②前因后果:
因为稍微改一点点东西,非常麻烦!
将App.tsx --->tsc --->· App.jsx --->React-complier --->js文件
但是有一个东西能够帮你把tsc,react-compiler, less, babel,uglifyjs全部集成到一起,我们只需要关心我们写的代码就好了
我们写的代码变化-->有人帮我们自动去tsc, react-compiler,less, babel, uglifyjs全部挨个走一遍-->js
这个东西就叫做 前端构建工具
③前端构建工具的工作:
打包: 将我们写的浏览器不认识的代码交给构建工具进行编译处理的过程就叫做打包,打包完成以后会给我们一个浏览器可以认识的文件。
一个构建工具他到底承担了哪些脏活累活:
1.模块化开发支持:支持直接从node_modules里引入代码+多种模块化支持
2.处理代码兼容性:比始babel语法降级,less,ts 语法转换(**不是构建工具做的,构建工具将这些语法对应的处理工具集成进来自动化处理)
3.提高项目性能:压缩文件,**代码分割*
4.优化开发体验:
(1)构建工具会帮你自动监听文件的变化,当文件变化以后自动帮你调用对应的集成工具进行重新打包,然后再浏览器重新运行(整个过程叫做热更新, hot replacement)
(2)开发服务器:跨域的问题,用react-cli create-react-element-vue-cli·解决跨域的问题,
④构建工具总结: 
        构建工具它让我们可以不用每次都关心我们的代码在浏览器如何运行,我们只需要首次给构建工具提供一个配置文件(这个配置文件也不是必须的,如果你不给他他会有默认的帮你去处理),有了这个集成的配置文件以后,我们就可以在下次需要更新的时候调用一次对应的命令就好了,如果我们再结合热更新,我们就更加不需要管任何东西,这就是构建工具去做的东西。
构建工具它让我们不用关心生产的代码,也不用去关心代码如何在浏览器运行,只需要关心我们的开发怎么写的爽怎么写就好了

​3、Vuex的理解及使用场景
Vuex 是一个专为 Vue 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。

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

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

2.Getter:在 store 中定义“getter”(可以认为是 store 的计算属性),

就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来, 且只有当它的依赖值发生了改变才会被重新计算

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

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

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

vue 的生命周期 八个阶段


1.beforeCreate

在实例创建之间执行,数据是未加载状态。

创建一个Vue实例,此时实例上只有一些生命周期函数和默认的事件

此时data computed watch methods上的方法和数据均不能访问

2.created

在实例创建、数据加载后,能初始化数据,DOM渲染之前执行。

可以对data数据进行操作,可进行一些请求,请求不易过多,避免白屏时间太长。

3.beforeMount

虚拟DOM已创建完成,在数据渲染前最后一次更改数据。el未挂载。

判断el的挂载方式

判断是否有template设置

将template进行渲染保存到内存当中,还未挂载在页面上

4.mounted

页面、数据渲染完成。el挂载完毕。可以访问DOM节点。

将内存中的模版挂载到页面上

此时可以操作页面上的DOM节点

此时组件从创建阶段进入运行阶段

5.beforeUpdate

重新渲染之前触发。不会造成重渲染。

页面显示的数据是旧的,此时data里面的数据是最新,页面数据和data数据暂未同步6.updated

数据已经更新完成,DOM也重新render完成,更改数据会陷入死循环。

根据data里的最新数据渲染出最新的DOM树,然后将最新的DOM挂载到页面

此时data和页面数据一致,都是最新的

7.beforeDestroy

实例销毁前执行,实例仍然完全可用。

此时组件从运行阶段进入到销毁阶段

组件上的data和methods以及过滤器等都出于可用状态,销毁还未执行

8.Destroyed 

实例销毁后执行,这时候只剩下DOM空壳。

组件已经被完全销毁,组件中所有的数据、方法、指令、过滤器等,都已不可用

简述Vue每个周期具体适合哪些场景


beforeCreate: 在new一个vue实例后,只有一些默认的生命周期钩子和默认事件,其他的东西都还没创建。在beforeCreate生命周期执行的时候,data和methods中的数据都还没有初始化。不能在这个阶段使用data中的数据和methods中的方法
created: data 和 methods都已经被初始化好了,如果要调用 methods 中的方法,或者操作 data 中的数据,最早可以在这个阶段中操作
beforeMount: 执行到这个钩子的时候,在内存中已经编译好了模板了,但是还没有挂载到页面中,此时,页面还是旧的
mounted: 执行到这个钩子的时候,就表示Vue实例已经初始化完成了。此时组件脱离了创建阶段,进入到了运行阶段。如果我们想要通过插件操作页面上的DOM节点,最早可以在和这个阶段中进行
beforeUpdate: 当执行这个钩子时,页面中的显示的数据还是旧的,data中的数据是更新后的, 页面还没有和最新的数据保持同步
updated: 页面显示的数据和data中的数据已经保持同步了,都是最新的
beforeDestory: Vue实例从运行阶段进入到了销毁阶段,这个时候上所有的 data 和 methods , 指令, 过滤器 ……都是处于可用状态。还没有真正被销毁
destroyed: 这个时候上所有的 data 和 methods , 指令, 过滤器 ……都是处于不可用状态。组件已经被销毁了。
6、简述MVVM 和MVC的原理以及区别?
MVVM视图模型双向绑定,是Model-View-ViewModel的缩写

1、MVVM的优点:

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

Vue首屏加载优化

路由懒加载:在 Vue 的路由配置中,通过设置components属性为一个函数来实现懒加载

图片优化:对首屏中的图片进行优化,包括压缩图片大小、选择合适的图片格式(如 WebP)、使用懒加载或占位符等技术。同时,可以考虑使用图片CDN 来加速图片的加载。

代码压缩:对 JavaScript、CSS 和 HTML 代码进行压缩和合并,减少文件大小和网络传输量。可以使用工具如 Webpack 进行构建和优化。
CDN加速:将静态资源(如 JavaScript、CSS 和图片)部署到内容分发网络(CDN)上,利用 CDN 的全球分布节点来加速资源的加载。


什么是MVC

        MVC是应用最广泛的软件架构之一,一般MVC分为:Model(模型),View(视图),Controller(控制器)。 这主要是基于分层的目的,让彼此的职责分开.View一般用过Controller来和Model进行联系。Controller是Model和View的协调者,View和Model不直接联系。基本都是单向联系。M和V指的意思和MVVM中的M和V意思一样。C即Controller指的是页面业务逻辑。MVC是单向通信。也就是View跟Model,必须通过Controller来承上启下。

Model(模型)表示应用程序核心(如数据库)。

View(视图)显示效果(HTML页面)。

Controller(控制器)处理输入(业务逻辑)。

MVC 模式同时提供了对 HTML、CSS 和 JavaScript 的完全控制。

Model(模型)是应用程序中用于处理应用程序数据逻辑的部分。(通常模型对象负责在数据库中存取数据)

View(视图)是应用程序中处理数据显示的部分。(通常视图是依据模型数据创建的)

Controller(控制器)是应用程序中处理用户交互的部分。(通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据)

优点:

1、低耦合

2、重用性高

3、生命周期成本低

4、部署快

5、可维护性高

6、有利软件工程化管理
3、MVC与MVVM的区别?

        MVC和MVVM的区别并不是VM完全取代了C,ViewModel存在目的在于抽离Controller中展示的业务逻辑,而不是替代Controller,其它视图操作业务等还是应该放在Controller中实现。也就是说MVVM实现的是业务逻辑组件的重用。

MVC中Controller演变成MVVM中的ViewModel
MVVM通过数据来显示视图层而不是节点操作
MVVM主要解决了MVC中大量的dom操作使页面渲染性能降低,加载速度变慢,影响用户体验


vue常见指令


v-model 多用于表单元素实现双向数据绑定
v-bind:简写为冒号:“:”,动态绑定一些元素的属性,类型可以是:字符串、对象或数组。
v-on:click 给标签绑定函数,可以缩写为:“@”,例如绑定一个点击函数 函数必须写在methods里面
v-for 格式: v-for="字段名 in(of) 数组json" 循环数组或json,记得加上key
v-show 显示内容
v-if 指令:取值为true/false,控制元素是否需要被渲染
v-else 指令:和v-if指令搭配使用,没有对应的值。当v-if的值false,v-else才会被渲染出来
v-else-if 必须和v-if连用
v-text 解析文本
v-html 解析html标签 (一般常见的解决后台的富文本内容)
v-bind:class 三种绑定方法
对象型 "{red:isred}"
三元型 " isred?"red":"blue"
数组型 " [{red:"isred" },{blue:"isblue"}] "
v-once 进入页面时 只渲染一次 不在进行渲染
v-cloak 防止闪烁
v-pre 把标签内部的元素原位输出
8、vue中的data为什么是一个函数?起到什么作用? 
在Vue组件中,data选项必须是一个函数,而不能直接是一个对象。这是因为Vue组件可以同时存在多个实例,如果直接使用对象形式的data选项,那么所有的实例将会共享同一个data对象,这样就会造成数据互相干扰的问题。
因此,将data选项设置为函数可以让每个实例都拥有自己独立的data对象。当组件被创建多次时,每个实例都会调用该函数并返回一个新的data对象,从而保证了数据的隔离性。
另外,data选项作为一个函数还具有一个重要的特性,就是它可以接收一个参数,这个参数是组件实例本身。这个特性在一些场景下非常有用,例如在定义组件时需要使用组件实例的一些属性或方法来计算初始数据。
因此,为了避免数据共享和保证数据隔离性,以及方便使用组件实例的属性和方法,Vue组件中的data选项必须是一个函数。
以下是一个简单的Vue组件示例,其中data被定义为一个函数:

<template>
  <div>
    <p>{{ message }}</p>
    <button @click="increment">{{ count }}</button>
  </div>
</template>
 
<script>
export default {
  data() {
    return {
      message: 'Hello, Vue!',
      count: 0
    }
  },
  methods: {
    increment() {
      this.count++
    }
  }
}
</script>
在这个例子中,data函数返回一个包含message和count两个属性的对象。每次创建组件实例时,Vue都会调用该函数返回一个新的数据对象,确保每个组件实例都有它自己的数据对象。

vue中hash和history的区别 


Vue-router的路由分为hash和history模式

1、hash方式

hash方式是指url中存在 # 的一种方式,是vueRouter的默认模式,
当#后面的url地址发生变化时,浏览器不会向服务器发送请求,故不会刷新页面
当#后面的url地址发生变化时,会触发hashChange(hash模式得核心实现原理)事件,从而,我们可以通过监听hashChange事件来知道路由发生变化,从而进一步去更新我们的页面
只可修改hash部分,
当浏览器刷新时,浏览器只会向服务器去请求# 前面的域名服务器下根目录下的index.html文件
hash模式会创建hashHistory对象,hashHistory对象有两个方法,push() 和 replace()
HashHistory.push()会将新的路由添加到浏览器访问的历史的栈顶,而HasHistory.replace()会替换到当前栈顶的路由
2、history模式

history模式得路由和域名之间直接通过/连接,无#符分隔,就是普通url形式
history模式当发生路由跳转时,通过HTML5的history.pushState()方法或者history.replaceState() 方法改变地址栏地址,并将地址的改变记录到浏览器访问栈中。(这里有一点需要注意,它只改变了浏览器地址栏中的地址,但并不会像服务器去发送请求)
当浏览器前进,后台,或者调用back(),forward(), go()等方法时,会触发popstate事件。故,我们可以通过监听popstate事件来获取最新的路由地址,从而更新页面
通过pushstate() 修改的url可以是与当前url同源的任意url。
需要和服务器配合使用,否则容易出现页面404的情况
总结如下:

hash模式带#号比较丑,history模式比较优雅;
pushState设置的新的URL可以是与当前URL同源的任意URL;而hash只可修改#后面的部分,故只可设置与当前同文档的URL;
pushState设置的新URL可以与当前URL一模一样,这样也会把记录添加到栈中;而hash设置的新值必须与原来不一样才会触发记录添加到栈中;
pushState通过stateObject可以添加任意类型的数据到记录中;而hash只可添加短字符串;
pushState可额外设置title属性供后续使用;
hash兼容IE8以上,history兼容IE10以上;
history模式需要后端配合将所有访问都指向index.html,否则用户刷新页面,会导致404错误。

nextTick的实现


nextTick是Vue提供的一个全局API,是在下次DOM更新循环结束之后执行延迟回调,在修改数据之后使用$nextTick,则可以在回调中获取更新后的DOM。
Vue在更新DOM时是异步执行的。只要侦听到数据变化,Vue将开启1个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个watcher被多次触发,只会被推入到队列中-次。这种在缓冲时去除重复数据对于避免不必要的计算和DOM操作是非常重要的。nextTick方法会在队列中加入一个回调函数,确保该函数在前面的dom操作完成后才调用
比如,我在干什么的时候就会使用nextTick,传一个回调函数进去,在里面执行dom操作即可。简单了解nextTick的实现,它会在callbacks里面加入我们传入的函数,然后用timerFunc异步方式调用它们,首选的异步方式会是Promise。这让我明白了为什么可以在nextTick中看到dom操作结果。
实现原理:在下次 DOM 更新循环结束之后执行延迟回调,在修改数据之后立即使用 nextTick 来获取更新后的 DOM。 nextTick主要使用了宏任务和微任务。 根据执行环境分别尝试采用Promise、MutationObserver、setImmediate,如果以上都不行则采用setTimeout定义了一个异步方法,多次调用nextTick会将方法存入队列中,通过这个异步方法清空当前队列。


Vue3篇


1、Vue2.0和Vue3.0的区别?
前言

vue3.0和2.0的区别:1、vue3.0对响应式系统进行了彻底重写替代了vue2.0的object.defineproperty方法;2、vue3.0使用了更多的静态分析和编译优化,打包后的文件更小;3、vue3.0相较于2.0有更快的速度和更低的内存占用;4、vue3.0的composition api相较于vue2.0的options api更加灵活和易于维护等等区别。

Vue.js是一款流行的用于构建用户界面的开源JavaScript框架。Vue.js库的最新版本是Vue3.0,它相较于之前的版本(如Vue2.0)有许多新的特性和改进。本文将重点介绍Vue3.0和2.0之间的主要区别。

1. 响应式系统重写:

Vue3.0对响应式系统进行了彻底重写,采用了Proxy代理对象替代了Vue2.0的Object.defineProperty方法。这带来了更好的性能和响应式能力。Proxy在监听对象变化方面要比Object.defineProperty更加强大和灵活。

2. 更小的体积:

Vue3.0使用了更多的静态分析和编译优化,使打包后的文件更小。有助于减少首次加载的时间和渲染的延迟。

3. 更好的性能:

Vue3.0在性能方面进行了一系列的优化。通过减少虚拟DOM更新的次数和更高效的渲染,Vue3.0相较于2.0有更快的速度和更低的内存占用。

4. Composition API:

Vue3.0引入了Composition API,这是一个全新的API风格,可以更好地组织和重用组件逻辑。相较于Vue2.0的Options API,Composition API更加灵活和易于维护。

5. TypeScript支持:

Vue3.0对TypeScript的支持更加完善。通过对TypeScript的类型检查和提供更好的类型声明,使得开发者在使用Vue3.0时可以获得更好的开发体验和代码质量。

6. 更强大的工具链:

Vue3.0在开发者工具方面进行了改进,提供了更强大的调试和性能分析工具。这使得开发者可以更方便地调试和优化应用程序。

需要注意的是,由于Vue3.0引入了一些新的特性和改变,因此在迁移过程中可能需要对现有的Vue2.0代码进行一定的改动。但Vue团队提供了一些迁移指南和帮助文档,可以帮助开发者轻松地升级到Vue3.0。

综上所述,Vue3.0相较于Vue2.0带来了许多新的特性和改进,包括响应式系统重写、更小的体积、更好的性能、Composition API、TypeScript支持以及更强大的工具链。这些改进使得Vue3.0更加强大、灵活和易于使用。开发者可以根据自己的需求选择合适的版本,并根据需要进行相应的迁移工作。

详解
Vue2和vue3的初始化就存在着⼀定区别,⽐如vue3.0可以在安装脚⼿架同时提前安装好⼀些项⽬开发必备的插件,并且3.0提供了可视化创建脚⼿架,可以更加⽅便的对插件和依赖进⾏管理和配置,同时两个版本的⽬录结构也是有些许差别的。(比如:Vue3相对于Vue2,打包工具Vite替代了webpack;TS替代了JS,pinia替代了vuex;Element-plus替代了Element等等)
在开发过程中两个版本的使⽤⽅法虽然在表⾯上没有太⼤的⼀个区别,但是在他的底层⽅⾯去看的话区别还是很⼤的,其中就包括渲染⽅式,数据监听,双向绑定,⽣命周期,vue3更精准变更通知,这⾥着重说⼀下关于双向绑定的更新,
vue2 的双向数据绑定是利⽤ES5的⼀个 API ,Object.definePropert()对数据进⾏劫持 结合发布订阅模式的⽅式来实现的。
vue3 中使⽤了 ES6 的 ProxyAPI 对数据代理,通过 reactive() 函数给每⼀个对象都包⼀层 Proxy,通过 Proxy 监听属性的变化,从⽽实现对数据的监控。
2、Vue3带来了什么改变? 
性能的提升

打包大小减少41%

初次渲染快55%, 更新渲染快133%

内存减少54%

源码的升级

使用Proxy代替defineProperty实现响应式

重写虚拟DOM的实现和Tree-Shaking

拥抱TypeScript

Vue3可以更好的支持TypeScript

新的特性

Composition API(组合API)
(1)setup配置

(2)ref与reactive

(3)watch与watchEffect

(4)provide与inject

新的内置组件
(1)Fragment

(2)Teleport

(3)Suspense

其他改变
(1)新的生命周期钩子

(2)data 选项应始终被声明为一个函数

(3)移除keyCode支持作为 v-on 的修饰符

3、生命周期(vue2和vue3的生命周期对比)有哪些?
vue2.x的生命周期

vue3.0的生命周期

Vue3.0中可以继续使用Vue2.x中的生命周期钩子,但有有两个被更名:

beforeDestroy改名为 beforeUnmount
destroyed改名为 unmounted
Vue3.0也提供了 Composition API 形式的生命周期钩子,与Vue2.x中钩子对应关系如下:

beforeCreate===>setup()
created=======>setup()
beforeMount ===>onBeforeMount
mounted=======>onMounted
beforeUpdate===>onBeforeUpdate
updated =======>onUpdated
beforeUnmount ==>onBeforeUnmount
unmounted =====>onUnmounted
4、Vue3.0中的响应式原理是什么?vue2的响应式原理是什么?
vue2.x的响应式

实现原理:
对象类型:通过Object.defineProperty()对属性的读取、修改进行拦截(数据劫持)。
数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)。
                                   Object.defineProperty(data, 'count', {
                                                                  get () {}, 
                                                                   set() {}
                                    })

存在问题:
新增属性、删除属性, 界面不会更新。
直接通过下标修改数组, 界面不会自动更新。
Vue3.0的响应式

实现原理:
通过Proxy(代理): 拦截对象中任意属性的变化, 包括:属性值的读写、属性的添加、属性的删除等。
通过Reflect(反射): 对源对象的属性进行操作。
MDN文档中描述的Proxy与Reflect:
Proxy:Proxy - JavaScript | MDN
Reflect:Reflect - JavaScript | MDN
new Proxy(data, {
    // 拦截读取属性值
    get (target, prop) {
        return Reflect.get(target, prop)
    },
    // 拦截设置属性值或添加新属性
    set (target, prop, value) {
        return Reflect.set(target, prop, value)
    },
    // 拦截删除属性
    deleteProperty (target, prop) {
        return Reflect.deleteProperty(target, prop)
    }
})

proxy.name = 'tom'   

vue3响应式数据的判断
isRef: 检查一个值是否为一个 ref 对象
isReactive: 检查一个对象是否是由 reactive 创建的响应式代理
isReadonly: 检查一个对象是否是由 readonly 创建的只读代理
isProxy: 检查一个对象是否是由 reactive 或者 readonly 方法创建的代理
5、vue3的常用 Composition API有哪些?
1.拉开序幕的setup

理解:Vue3.0中一个新的配置项,值为一个函数。
setup是所有Composition API(组合API)“ 表演的舞台 ”。
组件中所用到的:数据、方法等等,均要配置在setup中。
setup函数的两种返回值:
   若返回一个对象,则对象中的属性、方法, 在模板中均可以直接使用。(重关注!)
   若返回一个渲染函数:则可以自定义渲染内容。(了解)
     5.setup的几个注意点

 setup执行的时机
 在beforeCreate之前执行一次,this是undefined。
setup的参数
props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性。
context:上下文对象
attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于 this.$attrs。
slots: 收到的插槽内容, 相当于 this.$slots。
emit: 分发自定义事件的函数, 相当于 this.$emit。
尽量不要与Vue2.x配置混用
Vue2.x配置(data、methos、computed...)中可以访问到setup中的属性、方法。

但在setup中不能访问到Vue2.x配置(data、methos、computed...)。

如果有重名, setup优先。

setup不能是一个async函数,因为返回值不再是return的对象, 而是promise, 模板看不到return对象中的属性。(后期也可以返回一个Promise实例,但需要Suspense和异步组件的配合)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值