2024面试题

Vue 2 和 Vue 3 是 Vue.js 框架的两个版本,它们之间有一些重要的区别。以下是一些主要的区别:

1. 响应式能力:在 Vue 2 中,Vue 实例的响应式数据是通过 Object.defineProperty 实现的。而在 Vue 3 中,响应性系统进行了重大更新,使用了 Proxy 代理对象来实现,性能更好。

2.性能优化:Vue 3 在性能方面有了一些重大改进。 Vue 3 中引入了虚拟 DOM 的新算法,即“树懒化”(Tree Shaking),使得渲染更加快速和高效。

3. 组合式 API:Vue 3 引入了组合式 API,使得组件的逻辑更加模块化和容易复用。相比于 Vue 2 的选项 API,组合式 API 更加灵活和强大。

4. TypeScript 支持:Vue 3 更加友好地支持 TypeScript,使得在开发过程中更容易捕获潜在的错误,并提供了更好的类型推断和代码补全。

5. 其他改进:Vue 3 还包括了一些其他的改进,如 Teleport、Suspense、Fragments 等,使得开发者在构建复杂应用时能够更加高效地处理各种情形。

总的来说,Vue 3 在性能、开发体验和功能方面都有很大的提升,但由于一些插件和库还没有完全兼容 Vue 3,因此在迁移项目时需要谨慎考虑。

vue3中ref和reactive的区别:

vue3中使用reactive定义一个变量,重新赋值会失去响应式吗?

会失去响应式,因为vue的响应式是通过对象的属性变化来实现的,当给一个变量重新赋值时,实际上是新建了一个对象,而上一个对象就被丢弃了,新对象是没有经过reactive处理的,自然就不具备响应式。

解决方法:

1.使用ref代替reactive.

const data = ref({
    name: '',
    age: ''
})

2.定义对象时包裹一层:如

const data = reactive(
    obj: {
        name: '',
        age: ''
       }
})

3.使用Object.assign()拷贝对象,但有些情况不适用。

什么是pinia?

pinia是vue专属的最新状态管理库,是vue状态管理工具的替代品

1.提供了更简单的api,去掉了mutation.

2.提供了符合组合式风格的api(和vue3语法统一)

3.去掉了modules的概念,每一个store都是一个独立的模块。

4.搭配typescript一起使用提供可靠的类型推断

em和rem的区别:

rem(root element)是参照html的font-size,em(element)是参照父元素的font-size

以rem/em为单位比px更方便,屏幕尺寸发生变化时只需要更改html/body基数即可,不需要再重新给每个标签写不同的font-size

在使用media query做响应式布局时(以bootstrap为例):

      a.首先要做的是确定rem的基数(即html的font-size),一般都是以10px为基数(方便计算),这个可以根据自己的习惯来.

      b.确定html基准百分数(rem参照下html的字体大小采取百分数形式),百分数是自己设定的基数除以默认字体大小(16px)的值.比如以10px为基数时,基准百分数就是: 10px/16px=62.5%;即设置html的font-size为62.5%;

     c.当html的基准百分数设置之后,元素使用rem为单位时就是以基数(a中设置的基数值)为基础单位,换算方式为: 10px=1rem;比如要设置一个p标签的字体大小为20px,用rem表示就是2rem(公式: 以rem为单位的值=想要设置的字体大小(20px)/基数值(10px)=2rem).

2.使用em为单位,系统处理时(将em转化为px)只会看父元素(本文以body为例)的font-size

    此时只需要设置一个基数即可,然后接下来使用时只需要用想要的字体大小除以设置的基数值即为以em为单位的值;如: 设置em基数为10px,要设置一个标签的字体大小为15px,则em=想要设置的值(15px)/基数(10px)=1.5em

渲染机制

vue性能优化:

一、编码阶段

尽量减少 data 中的数据,data 中的数据都会增加 getter 和 setter,会收集对应的 watcher
v-if 和 v-for 不能连用, v-for的优先级比v-if高,一起使用会造成性能浪费
如果需要使用 v-for 给每项元素绑定事件时使用事件代理
SPA 页面采用 keep-alive 缓存组件
在更多的情况下,使用 v-if 替代 v-show
key 保证唯一
使用路由懒加载、异步组件
防抖、节流
第三方模块按需导入
长列表滚动到可视区域动态加载
图片懒加载

二、SEO 优化
预渲染
服务端渲染 SSR
三、打包优化
压缩代码
Tree Shaking/Scope Hoisting
使用 cdn 加载第三方模块
多线程打包 happypack
splitChunks 抽离公共文件
sourceMap 优化
四、用户体验
骨架屏
PWA
还可以使用缓存(客户端缓存、服务端缓存)优化、服务端开启 gzip 压缩等。

vue 中的 spa 应用如何优化首屏加载速度?
优化首屏加载可以从这几个方面开始:
请求优化: CDN 将第三方的类库放到 CDN 上,能够大幅度减少生产环境中的项目体积,另外 CDN 能够实时地根据网络流量和各节点的连接、负载状况以及到用户的距离和响应时间等综合信息将用户的请求重新导向离用户最近的服务节点上。

**缓存:**将长时间不会改变的第三方类库或者静态资源设置为强缓存,将 max-age 设置为一个非常长的时间,再将访问路径加上哈希达到哈希值变了以后保证获取到最新资源,好的缓存策略有助于减轻服务器的压力,并且显著的提升用户的体验

**gzip:**开启 gzip 压缩,通常开启 gzip 压缩能够有效的缩小传输资源的大小。

**http2:**如果系统首屏同一时间需要加载的静态资源非常多,但是浏览器对同域名的 tcp 连接数量是有限制的(chrome 为 6 个)超过规定数量的 tcp 连接,则必须要等到之前的请求收到响应后才能继续发送,而 http2 则可以在多个 tcp 连接中并发多个请求没有限制,在一些网络较差的环境开启 http2 性能提升尤为明显。

**懒加载:**当 url 匹配到相应的路径时,通过 import 动态加载页面组件,这样首屏的代码量会大幅减少,webpack 会把动态加载的页面组件分离成单独的一个 chunk.js 文件

**预渲染:**由于浏览器在渲染出页面之前,需要先加载和解析相应的 html、css 和 js 文件,为此会有一段白屏的时间,可以添加 loading,或者骨架屏幕尽可能的减少白屏对用户的影响体积优化

合理使用第三方库: 对于一些第三方 ui 框架、类库,尽量使用按需加载,减少打包体积

使用可视化工具分析打包后的模块体积:webpack-bundle- analyzer 这个插件在每次打包后能够更加直观的分析打包后模块的体积,再对其中比较大的模块进行优化

提高代码使用率: 利用代码分割,将脚本中无需立即调用的代码在代码构建时转变为异步加载的过程

封装: 构建良好的项目架构,按照项目需求就行全局组件,插件,过滤器,指令,utils 等做一些公共封装,可以有效减少我们的代码量,而且更容易维护资源优化

图片懒加载: 使用图片懒加载可以优化同一时间减少 http 请求开销,避免显示图片导致的画面抖动,提高用户体验

使用 svg 图标: 相对于用一张图片来表示图标,svg 拥有更好的图片质量,体积更小,并且不需要开启额外的 http 请求

压缩图片: 可以使用 image-webpack-loader,在用户肉眼分辨不清的情况下一定程度上压缩图片

微信小程序性能优化:

  • 启动小程序方面:
    • 分包加载
    • 控制代码包大小
  • 页面及页面层级方面
    • 合理使用setData调用,减少setData次数和数据量
  • 数据通信方面:精简代码,降低WXML结构和JS代码的复杂性。
    • js中提高数据更新速度
    • wxml 中提高数据更新速度

vue3怎么实现异步组件:

git merge 和git rebase的区别:

都是合并代码的命令,merge合并结果能够体现出时间线,但是rebase会打乱时间线

rebase会删除原有分支的记录看起来简洁,merge会保留原分支的记录看起来不太简洁

所以具体怎么使用这两个命令看项目需要

怎么理解强缓存和协商缓存:

强缓存定义:也叫彻底缓存就是浏览器直接读取缓存,不发出任何请求,性能提升最大

协商缓存定义:协商缓存就是浏览器向服务器发送一个请求,服务器会检查该资源是否有更新,如果有更新,就返回最新的资源,状态码200,如果没有更新,状态码304,不返回资源,浏览器从缓存中读取资源。

token:

基于Token的身份验证的过程如下:

1.用户通过用户名和密码发送请求。

2.程序验证。

3.程序返回一个签名的token 给客户端。

4.客户端储存token,并且每次用于每次发送请求。

5.服务端验证token并返回数据。

优点:
因为json的通用性,所以JWT是可以进行跨语言支持的,像JAVA,JavaScript,NodeJS,PHP等很多语言都可以使用。
JWT可以在自身存储一些其他业务逻辑所必要的非敏感信息(头部(header),载荷(payload),签证(signature)三部分组成)
便于传输,jwt的构成非常简单,就是一个字符串,字节占用很小,所以它是。
非常便于传输的,它不需要在服务器保存会话信息,所以易于应用的扩展。

token失效的解决方案:

1.返回登录界面,重新登录。

2.使用定时器,每隔一段时间自动刷新token,增加性能损耗不推荐

3.后端每次响应都响应一个token过期时间,前端进行判断,在token过期前进行刷新,这种方式存在太多不可控因素,如客户端系统时间被修改、长时间没请求导致token过期却未刷新,无法做到无感刷新,并且也存在并发问题

2.双token的方式进行无感刷新,登录后客户端收到两个token(access_token,refresh_token),其中access_token用来鉴定身份,而refresh_token用来刷新access_token;这就要求了refresh_token要比access_token有效时间要长,并且refresh_token不能用来鉴定身份

流程:

  1. 首先,登录得到了两个token,并将其存起来
  2. 当access_token过期时,自动发送refresh_token到刷新token的请求路径请求token刷新
  3. 得到新的token之后,将请求重新发送,实现用户无感刷新token
    //判空
    
    let isEmpty = function(obj) {
        return obj == null || obj == "undefined" || obj == "null" || new String(obj).trim() == '';
    };
    
    // 封装axios
    
    const my_axios = axios.create({
        baseURL: '/app',
        timeout: 15000,
        withCredentials: true
    });
    
    // 定义请求拦截器,将请求带上token
    
    my_axios.interceptors.request.use(
            req => {
            	//判断当前是否存在tokenBo(tokenBo即两个token组成的对象),存在则带上token
            	//isEmpty函数在上方的工具函数中
                if (!isEmpty(sessionStorage.getItem("tokenBo"))) {
                	//通过请求路径中是否含有refershToken来判断当前是一般请求还是token刷新请求;从而在请求头中带上不同的token
                    if (-req.url.indexOf("refreshToken") == 1) {
                        req.headers['accessToken'] = JSON.parse(sessionStorage.getItem("tokenBo")).accessToken;
                    } else {
                        req.headers['refreshToken'] = JSON.parse(sessionStorage.getItem("tokenBo")).refreshToken;
                    }
                }
                return req;
            },
            err => {
                return Promise.reject(err)
            }
        )
    
    // 响应拦截,进行token的无感刷新
    // 这是最难的一步,我们需要考虑到几个问题,第一:要实现无感,那么用户的请求就不能被舍弃,而是需要
    // 得到新的token后帮他再执行一次;第二,当同时出现多个请求时,可能会导致多次刷新token的情况,所
    // 以需要用一个标志量来标志是否正在刷新token,并使用一个数据对请求进行存储
    
    //标志当前是否正在刷洗token
    let isNotRefreshing = true;
    //请求队列
    let requests = [];
    my_axios.interceptors.response.use(
        async res => {
        	//我们可以定义一个标准响应体,比如:{code=10415,msg='token已过期',data:null},当收到token过期的响应就要进行token刷新了
            if (res.data.code == 10415) {
            	//首先拿到响应的配置参数,这和请求的配置参数是一样的,包括了url、data等信息,待会需要使用这个config来进行重发
                const config = res.config;
                //如果当前不处于刷新阶段就进行刷新操作
                if (isNotRefreshing) {
                    isNotRefreshing = false;
                    //返回刷新token的回调的返回值,本来考虑到由于请求是异步的,所以return会先执行,导致返回一个undefined,那么就需要使用async+await,但实际上没有加也成功了
                    return my_axios.get("/admin/refreshToken")
                        .then(res => {
                        	//如果token无效或token仍然过期,就只能重新登录了
                            if (res.code == 10422 || res.code == 10415) {
                                sessionStorage.removeItem("tokenBo");
                                sessionStorage.removeItem("currentAdmin");
                                location.href = '/login';
                            } else if (res.code == 10200) {
                            	//刷新成功之后,将新的token存起来
                                sessionStorage.setItem("tokenBo", JSON.stringify(res.data))
                                //执行requests队列中的请求,(requests中存的不是请求参数,而是请求的Promise函数,这里直接拿来执行就好)
                                requests.forEach(run => run())
                                //将请求队列置空
                                requests = []
                                //重新执行当前未执行成功的请求并返回
                                return my_axios(config);
                            }
                        })
                        .catch(() => {
                            sessionStorage.removeItem("tokenBo");
                            sessionStorage.removeItem("currentAdmin");
                            location.href = '/';
                        })
                        .finally(() => {
                            isNotRefreshing = true;
                        })
                } else {
                	//如果当前已经是处于刷新token的状态,就将请求置于请求队列中,这个队列会在刷新token的回调中执行,由于new关键子存在声明提升,所以不用顾虑会有请求没有处理完的情况,这段添加请求的程序一定会在刷新token的回调执行之前执行的
                    return new Promise(resolve => {
                    	//这里加入的是一个promise的解析函数,将响应的config配置对应解析的请求函数存到requests中,等到刷新token回调后再执行
                        requests.push(() => {
                            resolve(my_axios(config));
                        })
                    })
                }
            } else {
                if (res.data.code == 10200) {
                    return res.data;
                } else {
                    if (res.data.code == 10409) {
                        sessionStorage.removeItem("tokenBo");
                        sessionStorage.removeItem("currentAdmin");
                        location.href = "/#/login"
                    }
                    Message.error(res.data.message);
                    return res.data;
                }
            }
    
        },
        err => {
            if (err && err.response && err.response.status) {
                switch (err.response.status) {
                    case 404:
                        Message.error("页面未找到");
                        break;
                    case 401:
                        Message.error('没有权限访问')
                        break;
                    case 500:
                        Message.error("系统维护中")
                        break;
                    case 505:
                        Message.error("网络错误")
                }
            }
        }
    )
    

nextTick的底层原理:

是什么:在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM

为什么:由于Vue DOM更新是异步执行的,即修改数据时,视图不会立即更新,
vue里面有一个watcher,用于观察数据的变化,然后更新dom,vue里面并不是每次数据改变都会触发更新dom,而是将这些操作都缓存在一个队列,在一个事件循环结束之后,刷新队列,统一执行dom更新操作
为了确保得到更新后的DOM,所以设置了 Vue.nextTick()方法。

原理:Vue.nextTick()的实现原理主要是利用浏览器的异步任务队列机制,将回调函数推入到一个队列中,在下一个事件循环周期(MacroTask)中执行这个队列中的所有回调函数。具体来说,当用户使用 Vue.nextTick()执行回调函数时,Vue.js 会按照以下步骤进行处理:

首先,Vue.js 会将回调函数推入到一个队列中。这个队列称为“异步更新队列”(Async Queue),它是 Vue.js 用于收集在同一事件循环周期内需要执行的所有异步任务的容器。

接着,Vue.js 会判断当前是否存在一个微任务(MicroTask)队列。如果存在,则将异步更新队列合并到微任务队列中;否则,创建一个新的微任务队列,并将异步更新队列添加到其中。

接着,Vue.js 会将当前执行上下文捕获并保存下来。这个上下文包含了当前执行 Vue.nextTick()方法的组件实例、数据变化等信息。

最后,Vue.js 会将一个微任务添加到微任务队列中。这个微任务的作用是在下一个事件循环周期中执行异步更新队列中的所有回调函数,并且在执行之前恢复上下文,确保回调函数能够正确地访问到相关数据。

在实现上,nextTick 方法会根据当前环境选择不同的底层实现。在现代浏览器中,它使用了 MutationObserver 和 Promise 等技术实现异步任务调度;在旧版浏览器中,则使用了 setTimeout 来模拟异步任务

nextTick的本质上是返回一个promise 当数据发生变化的时候,vue不回立刻更新dom而是开启一个队列把更新函数放入队列中,然后刷新队列,这里是使用了promise就是当同步任务完成后才刷新队列然后执行里面的更新函数。nextTick就是获取刚才的promise,然后用then方法执行nextTick的回调函数。就是在dom更新完成后才会调用nextTick的回调函数。

数据埋点的原理:

说简单点就是,用户进行的操作,能通过特定的标识符记录下来,这些标识符汇总可以得到一个数据池,根据这些数据池,就可以监控用户的大概操作

普通函数和箭头函数的区别:

  1. 语法形式:普通函数使用function关键字定义,而箭头函数使用箭头(=>)符号来定义。

  2. this指向:普通函数中的this指向调用该函数的对象或者undefined(在严格模式下),而箭头函数中的this指向定义时所在的上下文,与调用位置无关。

  3. arguments对象:普通函数内部可以使用arguments对象来获取传入的参数列表,而箭头函数没有自己的arguments对象,它会继承外层作用域的arguments对象。

  4. 构造函数:普通函数可以作为构造函数使用,通过new关键字创建实例对象,而箭头函数不能被用作构造函数。

  5. 返回值:普通函数中可以使用return语句来返回值,而箭头函数可以省略return关键字,自动返回表达式的值。

  6. 简洁性:箭头函数通常比普通函数更简洁,尤其是在只有一个参数和单行表达式的情况下,可以省略括号和大括号。

  7. 其他区别:箭头函数不具有 prototype 原型对象。箭头函数不具有 super。
    箭头函数不具有 new.target

多维数组转一维数组的方法:

  1. 迭代法:使用两层循环遍历多维数组,将每个元素逐个添加到一维数组中。具体步骤如下:

    • 创建一个空的一维数组。
    • 使用外层循环遍历多维数组的每一行。
    • 使用内层循环遍历当前行的每个元素,并将其添加到一维数组中。
    • 返回生成的一维数组。
  2. 递归法:通过递归的方式将多维数组转换为一维数组。具体步骤如下:

    • 创建一个空的一维数组。
    • 遍历多维数组的每个元素,如果当前元素是一个子数组,则递归调用该方法将子数组转换为一维数组,并将返回的一维数组添加到结果数组中。
    • 如果当前元素不是子数组,则直接将其添加到结果数组中。
    • 返回生成的一维数组。
  3. 使用es6的flat()方法,flat()默认是拉平一层,但是要想拉平多层且我们不知道该数组的是几维数组时,我们可以用Infinity作为参数,可以实现多维数组转换为一维数组
    let arr = [1,2[3,4,[5]],6,7];
    console.log(arr.flat(infinity))

websocket介绍及使用:

webSocket是HTML5下不同于http的一种新协议(本质上也是基于tcp协议),实现了浏览器和服务器的全双工通信,是一个持久化的协议

WebSocket是一种双向通信协议。在建立连接后,WebSocket服务器端和客户端都能主动向对方发送或接收数据,就像Socket一样;

WebSocket需要像TCP一样,先建立连接,连接成功后才能相互通信。

原理:

websocket为应用层协议,其定义在TCP/IP协议栈之上通过握手机制,在客户端和服务器之间建立了一个类似TCP的长连接,从而方便他们之间的双向通信

底层协议:

socket请求失败的原因:

Socket 错误指的是在使用 Socket 进行网络通信时,由于各种原因而导致的错误。Socket 是一种网络编程接口,它允许应用程序通过 TCP/IP 协议或 UDP 协议进行网络通信。在进行网络通信时,可能会出现各种各样的错误,例如连接超时、连接被重置、无法连接等等,这些错误都被称为 Socket 错误。

Socket 错误通常会由操作系统或网络设备等方面引起,可能包括以下一些原因:

连接超时:在建立连接时,等待对方响应的时间超过了指定的时间,导致连接失败。
连接被重置:连接成功建立后,由于网络设备或对方主机等原因,连接被异常关闭,导致连接失败。
网络不可达:在进行网络通信时,发现网络不可达,无法进行通信。
连接被拒绝:对方主机拒绝连接请求,导致连接失败。
地址已在使用中:在进行网络通信时,要求使用的地址已经被其他应用程序占用,导致连接失败。

小程序的唯一标识,以及登陆流程:

小程序的唯一标识appid,

名词释义:

code:临时登录凭证,一次性且时效5分钟;
appid:小程序唯一标识;
appsecret:当前小程序的app secret 可以与code ,appId去换取session_key;
session_key:会话密钥,微信是不会把有效期告知开发者的,它会根据用户使用小程序的行为对session_key 进行续期。如果用户频繁使用,则有效期更长;
openid:当前用户在我们微信小程序中的唯一标识,永远不变。
共28位。一个用户在一个应用中会有一个唯一的openid

unionid:共29位。只要是同一个微信开放平台帐号下的移动应用、网站应用和公众帐号(包括小程序),用户的unionid是唯一的。换句话说,同一用户,对应同一个微信开放平台下的不同应用,unionid是相同的。

登录流程时序解读
1:前端调用wx.login()获取用户临时登录凭证code。
2:前端通过wx.request()方法发送code 到后端,后端发送appid、appsecret、code到微信后台接口,换取 openid、session_key等,然后整合为自定义登录态返回给前端,前端把自定义登录态存储到本地。
3:后续要验证用户身份时,前端只需要在请求头上携带自定义登录态发送给后端,后端通过自定义登录态查询对应的openid和session_key,然后给前端返回相应的业务数据。

小程序跳转公众号:

  1. 直接放置公众号二维码:在小程序中放置一个公众号二维码,用户可以通过长按识别来跳转到公众号。这种方法是最简单的。1
  1. 使用公众号组件:在小程序中使用`<official-account></official-account>`标签来引导用户关注公众号。官方文档提供了关于如何使用这个组件的详细信息。

  2. 使用webview内嵌的公众号链接:在满足一定条件下,可以在小程序中使用webview来嵌入公众号页面,用户点击后可以直接跳转到公众号。这种方式需要对webview进行一些配置。

小程序打包体积过大:

小程序分包的原则:

1.分包前,小程序中所有的页面和资源都被打包到一起,导致整个项目体积过大,影响小程序的首次启动的时间。因此,要将小程序分包,以提高首次启动速度。

2.分包可以按功能进行划分,即将功能相似的页面和资源分到同一个包中,这样可以更好地组织和管理代码。

3.在分包的时候,应该考虑代码的复用性。将公共的、可复用的代码抽离出来,放到一个独立的包中,这样可以避免重复的代码占用额外的空间和带宽。

4.分包也可以按业务进行划分,即按照不同的业务模块进行分包,这样可以更好地实现业务模块的解耦,方便后续的维护和扩展。

5.在分包的时候,应该考虑资源的加载顺序。按照加载顺序来合理地划分资源,可以避免不必要的等待和浪费。

6.分包还可以按重要性和优先级进行划分,即将重要的、使用频率高的页面和资源放在一个独立的包中,这样可以更快地加载和响应用户的请求。

综上所述,小程序分包的目的是为了提高加载速度、提高代码复用性、方便管理和扩展、减少等待和浪费、更快地响应用户请求。因此,在进行分包的时候,应该根据实际情况综合考虑上述原则,进行合理的划分和组织。

try...catch只能捕捉到同步执行代码块中的错误,不能异步捕获代码错误,因为它本身就是一个同步代码块

事件循环

JS执行是单线程的,它是基于事件循环的

  1. 所有同步任务都在主线程上执行,形成一个执行栈。
  2. 主线程之外,会存在一个任务队列,只要异步任务有了结果,就在任务队列中放置一个事件。
  3. 当执行栈中的所有同步任务执行完后,就会读取任务队列。那些对应的异步任务,会结束等待状态,进入执行栈。
  4. 主线程不断重复第三步。

宏任务和微任务有哪些

小程序底层原理:

小程序是双线程通信方式(为了管控安全,避免操作DOM),分为渲染层和逻辑层,其中 WXML 模板和 WXSS 样式工作在渲染层,JS 脚本工作在逻辑层。

小程序的渲染层和逻辑层分别由2个线程管理:渲染层的界面使用了WebView 进行渲染;逻辑层采用JsCore线程运行JS脚本。一个小程序存在多个界面,所以渲染层存在多个WebView线程,这两个线程的通信会经由微信客户端Native做中转,逻辑层发送网络请求也经由Native转发

虚拟 DOM 机制 virtual Dom

用JS对象模拟DOM树 -> 比较两个DOM树 -> 比较两个DOM树的差异 -> 把差异应用到真正的DOM树上

1.在渲染层把WXML转化成对应的JS对象

2.在逻辑层发生数据变更的时候,通过宿主环境提供的setData方法把数据从逻辑层传递到Native,再转发到渲染层

3.经过对比前后差异,把差异应用在原来的DOM树上,更新界面

原型和原型链

知识总结:什么是原型、原型对象、原型链?原型链如何继承?如何访问原型对象上的属性?属性的访问规则是什么?如何设置原型对象的属性?_原型和原型链-CSDN博客

webpack的构建流程

Webpack是一个现代化的前端构建工具,它主要用于将多个模块打包成一个或多个静态资源文件。下面是Webpack的构建流程:

1. 解析配置:Webpack首先会读取并解析项目中的配置文件,通常是webpack.config.js文件,该文件包含了Webpack的各种配置选项。

2. 解析入口:Webpack会根据配置文件中的entry字段,找到项目的入口文件。入口文件是Webpack构建的起点,Webpack会从入口文件开始递归解析依赖。

3. 解析模块:Webpack会根据入口文件及其依赖关系,逐个解析模块。在解析过程中,Webpack会根据配置文件中的rules字段,使用相应的loader对模块进行转换。例如,可以使用Babel-loader将ES6代码转换为ES5代码。

4. 构建依赖图:Webpack会根据模块之间的依赖关系,构建一个依赖图。该图描述了模块之间的依赖关系,以及每个模块被转换后的代码。

5. 生成输出:根据配置文件中的output字段,Webpack会将构建后的代码生成一个或多个静态资源文件。可以通过配置output字段的path和filename选项来指定输出文件的路径和名称。

6. 优化和压缩:Webpack还提供了一些优化功能,例如代码压缩、文件合并、模块分离等。可以通过配置文件中的optimization字段来启用这些优化功能。

7. 输出结果:Webpack会将构建后的静态资源文件输出到指定的路径。同时,Webpack还会生成一个manifest文件,该文件记录了每个模块的映射关系,用于在浏览器端正确加载模块。

说说轮播图的思路

redux

Redux 是一个用于状态管理的 JavaScript 库,通过单一状态树和纯函数的方式,提供了可预测且易于理解的状态管理解决方案,使应用的状态变得可控

http缓存有哪些

http缓存_http缓存有哪些-CSDN博客

vue2和vue3的tree shaking的区别

虚拟dom的理解

本质上就是普通的js对象,用于描述视图的界面结构,在vue中,每个组件都有一个render函数,每个render函数都返回一个虚拟dom树,这也就意味着每个组件都对应着一棵虚拟dom树。

为什么需要虚拟dom?
在vue中,组件创建时会调用render函数,数据更新时会重新调用render函数。如果在渲染时,直接使用真实DOM,由于真实DOM的创建、更新、插入等操作会带来大量的性能损耗,从而就会极大的降低渲染效率。因此,vue在渲染时,为解决渲染效率的问题使用虚拟dom来替代真实dom。
3.虚拟dom是如何转换为真实dom的?
在一个组件实例首次被渲染时,它先生成虚拟dom树,然后根据虚拟dom树创建真实dom,并把真实dom挂载到页面中合适的位置,此时,每个虚拟dom便会对应一个真实的dom。
如果一个组件受响应式数据变化的影响,需要重新渲染时,会重新调用render函数,创建出一个新的虚拟dom树,用新树和旧树对比,通过对比,vue会找到最小更新量,然后更新必要的虚拟dom节点,最后,这些更新过的虚拟节点,会去修改它们对应的真实dom这样一来,就保证了对真实dom达到最小的改动。

 

单点登录

单点登录原理及实现方式-CSDN博客

vuex

Vuex是实现组件全局状态(数据)管理的一种机制,可以方便实现组件数据之间的共享;Vuex集中管理共享的数据,易于开发和后期维护;能够高效的实现组件之间的数据共享,提高开发效率;存储在Vuex的数据是响应式的,能够实时保持页面和数据的同步;
Vuex重要核心属性包括:state,mutations,action,getters,modules.

state
Vuex 使用单一状态树,即每个应用将仅仅包含一个store 实例,但单一状态树和模块化并不冲突。存放的数据状态,不可以直接修改里面的数据。

mutations
mutations定义的方法动态修改Vuex 的 store 中的状态或数据。

action
actions可以理解为通过将mutations里面处里数据的方法变成可异步的处理数据的方法,简单的说就是异步操作数据。view 层通过 store.dispath 来分发 action。

getters
类似vue的计算属性,主要用来过滤一些数据。

modules
项目特别复杂的时候,可以让每一个模块拥有自己的state、mutation、action、getters,使得结构非常清晰,方便管理。

vue的生命周期

Vue的生命周期:就是Vue从创建到销毁的整个过程,可以分为四个阶段、八种状态。

四个阶段:

1.Vue的初始化阶段:beforeCreate(Vue初始化之前调用,此时还没有data和methods属性)和created(Vue初始化之后调用,在此之前会初始化数据监测、数据代理。Vue实例data和methods属性,可以访问但是此时还没有编译模板)

***在初始化和挂载之间Vue开始解析模板,生成虚拟的DOM(内存中),但是页面还不能显示解析好的内容(判断有没有el,进行创建或者使用template)

2.Vue的实例挂载阶段:beforeMount(挂载前,模板编译完成,但是此时el还没有挂载,页面显示的是未经Vue编译的DOM结构,但是data和methods可见,所有对DOM的操作无效)和{在两个钩子之间将内存中的虚拟DOM转为真实DOM插入页面}         mounted(挂载完成,此时el挂载完成,模板加载完成,页面显示的是经Vue编译的DOM结构,此时对DOM操作奏效(不建议),此时初始化过程结束,在此进行开启定时器,发送网络请求(操作后台data的时候)、订阅消息、开启自定义事件等操作)

3.Vue的数据更新阶段:beforeUpdate(数据更新是运行,此时的数据已经是最新数据,但是界面还是旧数据)和{在两个钩子之间根据新数据生成新的虚拟DOM,与旧的虚拟DOM进行对比最终完成页面更新}        Update(数据更新完成,此时data数据和界面数据都是最新数据。并且完成页面的渲染)

4.Vue的销毁阶段:beforeDestroy(销毁前收尾工作,取消订阅消息、关闭定时器、解绑自定义事件)和destroyed(实例销毁完成,此时与视图已经解绑)

https为什么比http安全及两者的区别

HTTP在通信中:

  • 通信使用明文,不对内容进行加密,容易被窃听
  • 不验证通信方的身份,内容可能被伪装分子窃取

HTTPS则解决了上述问题:(通过对称加密,非对称加密,摘要算法,数字签名等手段实现安全性)

  • 内容加密,建立一个信息安全通道,来保证数据传输的安全
  • 身份认证,确认网站的真实性
  • 数据完整性,防止内容被第三方冒充或者篡改     

axios封装以及原理

vue的自定义指令

图片懒加载和预加载

超详细的图片预加载和懒加载教程-CSDN博客

深拷贝与浅拷贝

防抖和节流

用递归实现阶乘比如传入5,就返回5*4*3*2*1的结果

function jie(n){
  if(n == 1){//跳出条件
     return 1;
  }
  return n*jie(n-1); //5*jie(4)
}
//调用:计算5的阶乘
var result = jie(5);
console.log(result);

异步任务分为哪些

异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有等主线程任务执行完毕,"任务队列"开始通知主线程,请求执行任务,该任务才会进入主线程执行,包含宏任务(就是将当前任务放在下一个任务列最顶部)和微任务(将当前任务放入下一个任务列的底部)

主要的异步任务有

Events:javascript各种事件的执行都是异步任务
setTimeout、setInterval 定时器 特别的如果将setTimeout()的第二个参数设为0,就表示当前代码执行完(执行栈清空)以后,立即执行(0毫秒间隔)指定的回调函
queueMicrotask 执行微任务
XMLHttpRequest(也就是 Ajax)
requestAnimationFrame 类似于定时器,但是是以每一帧为单位
fetch Fetch API 提供了一个 JavaScript 接口,用于访问和操纵 HTTP 管道的一些具体部分
MutationObserver 创建并返回一个新的 MutationObserver 它会在指定的DOM发生变化时被调用。
Promise
async function

怎样理解回流和重绘

  • 回流:在布局之后对元素的大小、位置进行改变
  • 重绘:在第一次绘制完页面后,重新绘制的过程
  • 回流触发时机:    

       (1)DOM结构发生改变 (添加新的节点或者移除节点)

       (2)布局发生改变(修改了width、height、padding、font-size等值)

       (3)窗口大小发生改变

       (4)调用getComputedStyle方法获取尺寸、位置信息  

       (5)内容发生变化,比如文本变化或图片被另一个不同尺寸的图片所代替

       (6)页面一开始渲染的时候

       重绘触发时机:

       (1)修改网页内容的样式时,比如文字颜色,背景颜色,边框颜色,文本方向的修改等。

       (2)触发回流一定会触发重绘

  • 如何避免回流?

  • 1.  修改样式时尽量一次性修改

    比如通过cssText修改,比如通过添加class修改;

    2.尽量避免频繁的操作DOM

    我们可以在一个DocumentFragment或者父元素中将要操作的DOM操作完成,再一次性的操作;

    3.尽量避免通过getComputedStyle获取尺寸、位置等信息;

    4.对某些元素使用position的absolute或者fixed

    并不是不会引起回流,而是开销相对较小,不会对其他元素造成影响。

    5.避免设置多项内联样式

    6.避免使用css的JavaScript表达式

promise介绍

promise是异步问题的一种解决方案(但是它本身不是异步的),解决了回掉地狱的问题,它本质上是一个对象,用于获取异步的一些消息。
promise有三种状态: pending(等待态),fulfiled(成功态),rejected(失败态);状态一旦改变,就不会再变。创造promise实例后,它会立即执行。

数组去重的方法

1.filter+indexof

var arr = [1, 2, 3,4 ,5,6, 4, 3, 8, 1]
    function newArrFn (arr) {
      // 利用indexOf检测元素在数组中第一次出现的位置是否和元素现在的位置相等,
      // 如果相等,说明数组中没有重复的
      return arr.filter((i,index)=>{
            arr.indexOf(i) === index
        })
    }
    console.log(newArrFn(arr));

2.for循环+indexof(或者includes)

var arr = [1, 2, 3,4 ,5,6, 4, 3, 8, 1]
    // 数组去重:
    // 方法2: for + indexof
    function newArrFn (arr) {
      let newArr = []
      for(let i = 0;i<arr.length;i++){
        newArr.indexOf(arr[i]) === -1 ? newArr.push(arr[i]) : newArr
      };
      return newArr
    }
    console.log(newArrFn(arr));

3.sort 排序(首先利用 sort 方法进行排序。进行循环,如果原数组的第 i 项和新数组的 i - 1 项不一致,就push进去。)

var arr = [1, 2, 3,4 ,5,6, 4, 3, 8, 1]
    // 数组去重:
    // 方法3: for + sort
    function newArrFn (arr) {
      arr = arr.sort()
      let newArr = []
      for(let i = 0;i<arr.length;i++){
        arr[i] === arr[i-1] ? newArr : newArr.push(arr[i])
      };
      return newArr
    }
    console.log(newArrFn(arr));

4.Set(ES6中新增了数据类型Set,Set的一个最大的特点就是数据不重复Set函数可以接受一个数组(或类数组对象)作为参数来初始化,利用该特性也能做到给数组去重。)

var arr = [1, 2, 3,4 ,5,6, 4, 3, 8, 1]
    // 数组去重:
    // 方法4: set
    function newArrFn (arr) {
      // .new Set方法,返回是一个类数组,需要结合 ...运算符,转成真实数组
      return ([...new Set(arr)])
    }
    console.log(newArrFn(arr));

5.reduce

var arr = [1, 2, 3,4 ,5,6, 4, 3, 8, 1]
    // 数组去重:
    // 方法12 :reduce
    function newArrFn (arr) {
      let newArr = []
      return  arr.reduce((prev, next,index, arr) => {
        // 如果包含,就返回原数据,不包含,就把新数据追加进去 
        return newArr.includes(next) ? newArr :  newArr.push(next)
      }, 0)
    }
    console.log(newArrFn(arr));

如何实现一个new

要手动实现一个 new 操作符,首先要知道 new 操作符的含义以及在使用的过程中干了什么事情,即构造函数的内部原理,简单的分为以下的四个步骤:
        ·创建一个新对象;
        ·链接到原型(将构造函数的 prototype 赋值给新对象的 __proto__);
        ·绑定this(构造函数中的this指向新对象并且调用构造函数);
        ·返回新对象;

const objectFactory = function(){
    // 创建一个空对象
    // var obj = {}; 和下面的是同样的效果
    var obj = new Object();
    // 获得一个构造函数
    // Constructor = [].shift.apply(arguments);
    Constructor = [].shift.call(arguments);
    // 将构造函数的原型对象,作为新对象的原型对象
    obj.__proto__ = Constructor.prototype;
    // 绑定this,让this指向新的对象
    // var ret = Constructor.apply(obj,arguments)
    // var ret = Constructor.call(obj,arguments[0],arguments[1],arguments[2])
    var ret = Constructor.call(obj,...arguments)
    // 确保 使用new返回的是一个 object 对像
    return typeof ret === "object" ? ret : obj;
}

上面我们通过创建一个对象,然后由 arguments 伪数组我们就可以知道参数包含的构造函数以及我们调用时传入的参数,然后就是对构造函数和相关参数的获取,但是 arguments 是伪数组,只拥有简单的下标访问和循环功能,并没有拥有数组的一些方法,所以我们就是用 call 和 apply 两种 方式让 arguments 伪数组拥有数组的 shift()方法, 后面又使用了call 和 apply 两种不同的绑定this的方式,call和apply的区别就是后面接的参数个数不一样。call可以接收多个参数,但是apply只能接收一个参数。但是效果是一样的,最后我们来测试一下我们的代码的正确性

function Person (name,age,sex){
    this.name = name;
    this.age = age;
    this.sex = sex;
}
 
// 通过手写new ,就是objectFactory()方法创建的实例
const obj1 = objectFactory(Person,"cmm",18,"man")
console.log(obj1)
//Person { name: 'cmm', age: 18, sex: 'man' }
 
// 通过new创建的构造实例
const obj2 = new Person("cll",18,"woman");
console.log(obj2)
//Person { name: 'cll', age: 18, sex: 'woman' }

跨域是什么,怎么解决,jsonp怎么做的

数组合并有哪些方法

1.ES6 解构 不改原数组值,生成新的数组。  

[...arr, ...array]

2.遍历添加 

遍历方法:forEach、map、filter、every、for、for in、for of等。

添加方法:push(后追加)、unshift(前追加)等。

arr值改变成追加后的样子,array值不改变

array.forEach(item => {
    arr.push(item)
})

3.concat 不改原数组值,生成新的数组。

arr.concat(array)

4.join & split 

原数组值不改变。 

默认会把数组中的数字类型转成字符串类型。

(arr.join(',') + ',' + array.join(',')).split(',')

5.解构添加

arr.push(...array)
arr.unshift(...array)

6.splice解构 原数组值不变,返回空数组。 

arr.splice(arr.length, '', ...array)

7.apply/call

arr.push.call(arr, ...array)
arr.unshift.call(arr, ...array)

arr.push.apply(arr, array)
arr.unshift.apply(arr, array)

请求参数如何防篡改

Web安全 【基础】 请求防篡改和唯一请求(重放攻击)_请求重放-CSDN博客

localStorage如何跨域获取

localStorage是浏览器提供的一种本地存储机制,它只能在同源的情况下被访问。如果要在不同域名的页面中访问同一个localStorage,可以使用以下两种方式:

使用postMessage跨域通信在A域名的页面中,可以使用postMessage方法将localStorage的值发送到B域名的页面中,B域名的页面中再通过监听message事件获取localStorage的值。示例代码:在A域名的页面中

var value = localStorage.getItem('key');
window.parent.postMessage(value, 'http://B.com');

B域名的页面中

window.addEventListener('message', function(event) {
  if (event.origin === 'http://A.com') {
    var value = event.data;
    localStorage.setItem('key', value);
  }
});

  • 25
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值