2023前端面试题汇总(最新)-持续更新中
目录
-
1. 防抖和节流
-
2. js闭包
-
3. Vue框架知识点总结
-
- 3.1 为什么数据属性被视为函数?(常见面试问题)
-
- 3.2 MVC模式与MVVM模式有何不同?
-
- 3.3 v-model的工作原理是什么?
-
- 3.4 数据属性为何设计为函数?(常见面试问题)
-
- 3.5 v-if与v-show有什么区别?
-
- 3.6 v-for循环中必须指定key的原因是什么?
-
- 3.7 打包后dist目录过大该如何优化?
-
- 3.8 computed属性与watch有什么区别?
-
- 3.9 组件间如何实现数据传递?
- 父组件向子组件传输数据的方式有哪些?
- 子组件如何将状态反馈给父组件?
- 全局事件总线机制的功能及其实现原理
- 3.9 组件间如何实现数据传递?
-
*4. Vue原型机制与 prototype链的作用
- 4.1 深拷贝 与 浅拷贝
- 4.2 $nextTick的作用
- 4.3 Vue生命周期
-
4. ES6面试题
- 4.1 区别在于变量声明的方式不同
- 4.2 解构赋值允许将数组或对象的元素逐个分配给变量
- 4.3 利用es6新增的功能巧妙实现快速去重
- 4.4 Promise面试题 分析以下代码在运行时会返回什么值
- 4.5 跨域问题的解决方案包括:
- 4.5.1 webpack插件(插件功能)
- 4.5.2 JSONP (需后端支持)
- 4.5.3 webpack插件(提供额外功能)
- 4.5.4 CORS (后端配置方法)
-
第四个章节·第六节 git命令(在日常工作中非常实用)
- 第四个章节·第六节·核心内容
- 第四个章节·第六节·详细信息
- 第四个章节·第六节·操作方法
- 暂存区管理:临时存储修改后的代码
- 版本恢复:恢复到之前的工作状态
- 第四个章节·第六节·核心内容
-
探讨HTTP GET和POST请求之间的差异
-
解析JavaScript中cookie、localStorage与sessionStorage的区别
-
对比异步编程中的async与await机制及其异同
-
详细阐述setTimeout函数时间参数设置为零时的运行机制及其可能导致的错误原因
-
求解如何在JavaScript中获取数组的最大值
-
探讨如何在JavaScript中获取数组的最小值
-
数组去重
-
利用filter方法结合indexOf索引实现去重
-
通过sort排序和splice方法实现去重操作
-
最简洁的方式是使用新Set对象构造函数来去重
-
通过indexOf查找并push至新数组实现数据去重
-
基于对象属性名唯一性原则实现数组去重
-
5.4. 数组求和
-
- 5.5 生成从0 到 指定数字的数组
-
-
5.6 JavaScript 数据类型分类
-
5.7 JavaScript 变量的提升作用
-
5.8 this 关键字的作用机制
-
5.9 map 和 forEach 的功能特点对比
-
6.0 ES6 新增的主要特性总结
-
6.1 JavaScript 数组方法的全面解析
-
6.2 项目性能优化的最佳实践方案
-
6.3 遍历数组元素的高效方法
-
6.4 forEach 迭代与 map 方法的区别分析
-
6.5 forEach 迭代与 for 循环的使用场景对比
-
6.6 Vue2 到 Vue3 主要功能的变化总结
-
6.7 call、apply、bind 函数及其使用场景区分
1. 防抖和节流
防抖机制(debounce):在单位时间周期内,在连续触发事件时仅执行最近的一次操作。例如点击按钮,在2秒钟的时间间隔后调用函数;如果再次点击按钮在1.5秒钟时触发,则会重新计算下一个2秒钟的时间窗口来调用该函数。
应用场景:搜索框、输入框、手机邮箱验证等
思路:利用定时器,每次触发先清除定时器()
底层代码实现:
const box = document.querySeleter(".box")
let i = 1
function mouseMove() {
box.innerHTML = i++
}
function debounce(fn, t) {
let timer
// return 返回一个匿名函数
return function() {
if(timer) clearTimeout(timer)
timer = setTimeOut(function() {
fn()
}, t)
}
}
box.addEventListener('mousemove', debounce(mouseMove, 500))
节流(throttle) :单位时间内,频繁触发事件,只执行一次
频繁发生的场景包括鼠标经过页面、页面缩放操作以及滚动操作(包括scroll事件)和下拉刷新动作。
const box = document.querySeleter(".box")
let i = 1
function mouseMove() {
box.innerHTML = i++
}
function throttle(fn, t) {
let timer
// return 返回一个匿名函数
return function() {
if(!timer) {
timer = setTimeOut(function() {
fn()
// 清空定时器 此处使用timer=null清除定时器是因为写在了定时器里面,setTimeout中是无法清除定时器的,因为定时器还在运作
timer = null
}, t)
}
}
}
box.addEventListener('mousemove', throttle(mouseMove, 500))
2. js闭包
什么是闭包:闭包就是能够读取其他函数内部变量的函数
function fn0 () {
const aaa = 0
return function() {
return aaa
console.log('打印', aaa)
}
}
闭包存在意义:
可以延长变量的生命周期4可以创建私有的环境
闭包好处:
可以读取其他函数的内部变量
将变量始终保存在内存中
可以封装对象的私有属性和方法
坏处:
消耗内存、使用不当会造成内存溢出问题
3. Vue相关总结
3.1 vue中的data为什么是一个函数?(面试常问)
Vue 中的 data 必须是个函数,因为当 data 是函数时,
组件实例化的时候这个函数将会被调用,返回一个对象,
计算机会给这个对象分配一个内存地址,实例化几次就分配几个内存地址,
他们的地址都不一样,所以每个组件中的数据不会相互干扰,改变其中一个组件的状态,其它组件不变。
简单来说,就是为了保证组件的独立性和可复用性,如果 data 是个函数的话,
每复用一次组件就会返回新的 data,类似于给每个组件实例创 建一个私有的数据空间,
保护各自的数据互不影响
3.2 MVC 和 MVVM的区别
MVC:M(model数据)、V(view视图),C(controlle控制器)
缺点是前后端无法独立开发,必须等后端接口做好了才可以往下走;
前端没有自己的数据中心,太过依赖后台
MVVM:M(model数据)、V(view视图)、VM(viewModel控制数据的改变和控制视图)
html部分相当于View层,可以看到这里的View通过通过模板语法来声明式的将数据渲染进DOM元素,
当ViewModel对Model进行更新时,通过数据绑定更新到View。
Vue实例中的data相当于Model层,而ViewModel层的核心是Vue中的双向数据绑定,
即Model变化时VIew可以实时更新,View变化也能让Model发生变化
MVVM与MVC最大的区别就是:它实现了View和Model的自动同步,
也就是当Model的属性改变时,我们不用再自己手动操作Dom元素,来改变View的显示,
而是改变属性后该属性对应View层显示会自动改变
3.3 v-model 原理
是采用数据劫持结合发布者-订阅者模式的方式,
通过Object.defineProperty()来劫持各个属性的setter,getter,
在数据变动时发布消息给订阅者,触发相应的监听回调从而达到数据和视图同步。
3.4 vue中的data为什么是一个函数?(面试常问)
实际上就是一个闭包,因为vue是单页面应用,是由很多组件构成,每一个组件中都有一个data,
所以通过闭包给每一个组件创建了一个私有的作用域,这样就不会相互影响。
3.5 v-if 和 v-show的区别
v-if是通过添加和删除元素来进行显示或者隐藏
v-show是通过操作DOM修改display样式来修改元素的显示和隐藏
如果需要频繁的进行元素的显示和隐藏使用v-show性能更好
3.6 v-for中为什么要有key
key 可以提高虚拟DOM的更新效率。
在Vue中,默认情况下采用in-place re-use策略,在DOM操作时若无键(key)将导致…的情况发生
key 只能是字符串或者number,其他类型不可以
1. 虚拟DOM中key的作用:
key是虚拟DOM对象的标识,当数据发生变化时,
Vue会根据【新数据】生成【新的虚拟DOM】,
随后Vue进行【新的虚拟DOM】与【旧的虚拟DOM】差异比较比较规则如下:
2. 比较规则:
1)旧虚拟DOM找到了与新虚拟DOM相同的key:
若虚拟DOM中内容没变,直接使用之前的真实DOM
若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM
3. 用index作为key可能会引发的问题:
1)若对数据进行:逆序添加、逆序删除等破坏顺序的操作,会产生没有必要的真实DOM更新==>界面效果没问题,但效率低
2)如果结构中还包含输入类的DOM,会产生错误的DOM更新 ==> 界面有问题
3.7 打包后 dist 目录过大,解决办法?
- 在dist打包过程中生成的.map文件可被移除;同时,在vue.config.js中将生产源地图设置为false。
- 组价与路由采用惰性加载及按需引入等方式实现资源优化。
- 对文档与图片实施压缩操作是必要的步骤;首先,在项目中安装并激活 compression-webpack-plugin 包ages插件以实现高效的分段式构建过程;然后,在构建时应用 minisize 策略以进一步优化代码量;当构建出的任务超出预设大小限制时(通常以字节为单位),系统会自动触发压缩措施。
3.8 computed 和 watch 的区别
- Computed是一种计算属性,在变化发生时触发相应的回调操作。
- Computed中的函数必须包含返回值定义域的行为特征;而watch不具备强制性的返回值定义域要求。
- Computed在首次加载时就会执行一次;而watch在首次加载时不进行响应。
- 当computed所依赖的属性未发生变化时,则会被缓存中的数据所覆盖;反之,则会重新执行当前函数。
3.9 vue组件之间的数据传递
3.9.1 父 传 子
在子组件中为每个实例注册自定义属性后,可以通过props属性接收相应的数据

3.9.2 子 传 父
1)第一种方式:通过父组件给子组件传递函数类型的props实现:子组件给父组件传递数据
父组件:


子组件:

第二种方式:借助父组件为子组件注册一个自定义事件来完成:子组件向父组件发送信息。


子组件

第三种方案:通过将父组件与子组件关联起来,并在其中绑定一个自定义事件来完成;采用Ref机制来处理


子组件

3.9.3 全局事件总线:可以实现任意组件间的数据传递
main.js:将全局事件bus,挂载到Vue的原型上,这样所有的组件都可以使用

兄弟组件A:

兄弟组件B:

4. 消息订阅与发布
一种组件间的通信方式,适用于任意组件间通信。
使用步骤:
- 通过npm安装PubSub库: 使用npm命令行指令安装PubSub库。
- 导入: 可以从'pubsub-js'库导入PubSub。
- 消息接收: A组件想接收数据,则可在该组件中进行订阅操作,并将消息回调触发于该组件内部。
mounted() {
this.pid = punsub.subscribe('xxx', (data)=>{
......
})
}
通过pubsub.publish(‘xxx’, 数据)实现数据的发布
4.0 vue原型 和 原型链
不管是普通函数还是构造函数,都有一个属性prototype,用来指向这个函数的原型对象。
同时原型对象也有一个constructor属性,用来指向这个原型对象的构造函数。
原型对象的作用:用来存储实例化对象的公有属性和方法
例如
function Person (name, age) {
this.name = name
this.age = age
this.say = function () {
console.log('我会说话')
}
}
let person1 = new Person('小明', 20)
console.log(person1.name) // 小明
console.log(person1.age) // 20
console.log(person1.say()) // 我会说话
基于之前的情况:创建实例person1就可以利用Person类的构造函数访问其内部属性及方法
总结
访问一个属性时,默认先在当前对象中查找;若未找到,则会转向其 prototype(即 proto),继续沿着 prototype chain 查找直至到达 Object.prototype(即 proto)。每个函数都具有 prototype 属性;而所有函数及其 prototype 的 proto 都指向 Object.prototype。通过 prototype chain 最终到达的目标是 Object.prototype(其 proto 为 null)。
具体来说,则是建议您参考下方提供的文章链接地址(文章内容叙述得极其详尽且易于理解):
4.1 深拷贝 与 浅拷贝
首先,在浅拷贝中只针对引用类型的变量进行操作即可实现单层数据的复制功能。具体来说,在浅拷贝中需要生成一个新的对象来存储原始对象的值信息,并且这种操作仅适用于单层的数据结构。在此过程中需要考虑两种类型的变量:基本数据类型和引用数据类型。在创建浅拷贝时需要考虑两种类型的变量:基本数据类型(如整数、字符串等)可以直接通过赋值实现值层面的复制;而引用型变量则需要通过生成一个新的指针来指向内存地址的位置(即复制该内存地址),这种机制确保了当原始对象发生更改时不会影响到被复制的对象的数据内容
常见方法:
1、复制对象:通过调用Object.assign方法或利用展开运算符{...obj}创建新对象
2、复制数组:调用Array.prototype.concat方法并使用简写形式[…]arr执行元素拼接
弊端:如果拷贝的数据复杂,此时修改拷贝对象,会影响原数据
例如:
const obj = {
name: '你好',
age: 18,
family: {
son: '大儿子'
}
}
const newObj = {}
Object.assign(newObj, obj)
newObj.family.son = '小儿子'
console.log(obj) // 此时打印出来的原数据obj里面的family对象值被修改
浅拷贝代码实现:
function clone(target) {
let cloneTarget = {};
for (const key in target) {
cloneTarget[key] = target[key];
}
return cloneTarget;
};
深拷贝
与其相对应的是创建一个新的指针,并会从头开始生成一个全新的内存区域;该新的指针将会指向这个全新的内存区域。这些对象彼此之间没有相互影响
实现深拷贝的方法
- 最常用的
JSON.parse(JSON.stringify())
生成一个新的空对象实例,并遍历源对象的所有属性;然后将这些属性逐一复制到目标新对象中;最后生成并返回新的克隆体实例。
由于要拷贝的对象可能嵌套层次较深的原因,在这种情况下建议采用一种更为高效的策略即递归调用的方式来完成这一操作,并对上文提到的代码进行改进以提高其运行效率
如果是原始类型,无需拷贝,可以直接返回
2. 如果是引用类型,可以创建一个新的对象,遍历需要克隆的对象,将需要克隆对象执行深拷贝后依次添加到新对象上
module.exports = function clone(target) {
if (typeof target === 'object') {
// 此处要先判断是否是数组,因为数组也属于对象
let cloneTarget = Array.isArray(target) ? [] : {};
for (const key in target) { // key 是属性名,target[key]是属性值
cloneTarget[key] = clone(target[key]); // cloneTarget[key] 是创建属性名
}
return cloneTarget;
} else {
return target;
}
};
详细可参考:https://www.jianshu.com/p/a447d7325736
其中,在 lodash 库中提供了名为 cloneDeep 的深度复制功能模块。
let obj = [{'a' : 1}, {'b': 2}]
let deepObj = _.cloneDeep(obj)
console.log(obj)
4.2 $nextTick的作用
首先我们需要明确这一点:Vue的响应式机制并非是在数据变化时导致DOM随之发生即时变化的现象而是按照特定的策略来进行DOM更新
作用:$nextTick是在下次DOM更新循环结束后触发延迟回调,在修改数据后用于访问更新后的DOM,在下次DOM更新循环结束后触发延迟回调。
说白了就是:** 当数据更新了,在DOM更新后,自动执行该函数**
什么时候用?
- 在Vue生命周期钩子中的created()阶段必须执行特定的DOM操作吗?原因在于,在此阶段运行钩子时,并未触发任何渲染动作。
- 当数据发生更新时,请确保所有基于新DOM的操作都将在Vue.nextTick()回调处执行。
想了解更详细的🉑️参考:https://www.jianshu.com/p/8efa5cba7d07
4.3 Vue生命周期
可点击参考文档详细了解
4. ES6面试题
4.1 var let const 区别
使用var关键字进行变量声明时,可能会存在变量提升问题;可能存在变量覆盖问题,在这种情况下,默认会基于最后一次赋值结果执行操作;不支持块级作用域
const的作用是定义一个常量。在JavaScript中使用const时,默认情况下需要先对其进行赋值才能引用该常量。这种数据类型的定义属于块级作用域,在这种情况下也不会发生变量提升或覆盖现象。值得注意的是,在某些情况下(例如涉及数组或对象),针对数组或对象中的元素进行修改并不会被视为对常量本身进行任何改动(即不被视为常量的修改),因此不会导致错误信息出现)。
let:具有块级独立的作用域特征;
没有发生变量提升或覆盖的情况;
同一作用域内不能重复声明let;
虽然不同作用域允许重复声明但不会产生错误。
4.2 解构赋值
let a = 1; let b = 2; 如果在不声明第三个变量的前提下,使a=2, b=1?
答案:[a, b] = [b, a]
4.3 如何利用es6快速的去重?
let arr = [23, 12, 13, 33, 22, 12, 21]
let item = [...new Set(arr)]
4.4 Promise 面试题 以下代码的执行结果是?
const promise = new Promise((resolve, reject) => {
console.log(1)
resolve()
console.log(2)
})
promise.then(() => {
console.log(3)
})
console.log(4)
答案:1,2,4,3
本研究主要关注于 promise 的运行机制及其内部操作流程。具体而言,在 new Promise 执行时其构造函数采用同步方式运行,在 promise 执行的一刻任务 1 和 2 立即触发并被立即执行完毕。然而 .then 方法采用异步机制运行因此只有在完成任务 1 和 2 后才会将输出结果 4 返回给调用方随后才会返回结果 3 给最终调用方完成整个流程的关键步骤
4.5 跨域的解决方法
跨域:若协议参数、DNS名称或端口数值中任一属性与预期值存在差异,则会发生跨域行为。该安全机制通过严格的参数匹配来保障服务的一致性。指协议参数、DNS名称或端口数值的一致性即为同源特性。出于提高系统安全性考虑,在未获得明确授权的情况下,默认禁止不同来源客户端脚本进行资源交互以避免潜在的安全风险。
解决办法:
4.5.1. webpack 里的proxy
devServer: {
proxy: { //配置跨域
'/api': {
target: 'http://121.121.67.254:8185/', //这里后台的地址模拟的;应该填写你们真实的后台接口
changOrigin: true, //用于控制请求头中的post值,默认开启
pathRewrite: {
/* 重写路径,当我们在浏览器中看到请求的地址为:http://localhost:8080/api/core/getData/userInfo 时
实际上访问的地址是:http://121.121.67.254:8185/core/getData/userInfo,因为重写了 /api
*/
'^/api': ''
}
},
}
}
4.5.2. jsonp (需要后端支持 )
方案1 *:通配符,全部允许,存在安全隐患(不推荐)。
一旦启用本方法,表示任何域名皆可直接跨域请求:
1 server {
2 ...
3 location / {
4 # 允许 所有头部 所有域 所有方法
5 add_header 'Access-Control-Allow-Origin' '*';
6 add_header 'Access-Control-Allow-Headers' '*';
7 add_header 'Access-Control-Allow-Methods' '*';
8 # OPTIONS 直接返回204
9 if ($request_method = 'OPTIONS') {
10 return 204;
11 }
12 }
13 ...
14 }
方案2:多域名配置(推荐)
配置多个域名在map中 只有配置过的允许跨域:
map $http_origin $corsHost {
2 default 0;
3 "~https://zzzmh.cn" https://zzzmh.cn;
4 "~https://chrome.zzzmh.cn" https://chrome.zzzmh.cn;
5 "~https://bz.zzzmh.cn" https://bz.zzzmh.cn;
6 }
7 server {
8 ...
9 location / {
10 # 允许 所有头部 所有$corsHost域 所有方法
11 add_header 'Access-Control-Allow-Origin' $corsHost;
12 add_header 'Access-Control-Allow-Headers' '*';
13 add_header 'Access-Control-Allow-Methods' '*';
14 # OPTIONS 直接返回204
15 if ($request_method = 'OPTIONS') {
16 return 204;
17 }
18 }
19 ...
20 }
4.5.3. webpack plugin (插件)
通过安装该中间键实现前后端服务的无缝连接
中间件
let webpack = require('webpack')
let middle = require('webpack-dev-middleware')
let compiler = webpack(require('./webpack.config.js'))
app.use(middle(compiler))
4.5.4. cors (后端解决)
var allowCrossDomain = function(req,res,next) {
// 请求源
res.header("Access-Control-Allow-Origin", "*")
// 请求头 token
res.header("Access-Control-Allow-Headers", "*")
// 请求方法 get post put del
res.header("Access-Control-Allow-Methods", "*")
next();
}
app.use(allowCrossDomain )
4.6 git命令(实际工作中常用)
4.6.1 基本
git init 初始化git仓库 (mac中Command+Shift+. 可以显示隐藏文件)
2. git status 查看文件状态
3. git add 文件列表 追踪文件
4. git commit -m 提交信息 向仓库中提交代码
5. git log 查看提交记录
4.6.2 分支明细
(1)主分支(master):第一次向 git 仓库中提交更新记录时自动产生的一个分支。
(2)开发分支(develop):作为开发的分支,基于 master 分支创建。
(3)功能分支(feature):作为开发具体功能的分支,基于开发分支创建
4.6.3 分支命令
(1)git branch 查看分支
(2)git branch 分支名称 创建分支
(3)git checkout 分支名称 切换分支
(4)git merge 来源分支 合并分支 (备注:必须在master分支上才能合并develop分支)
(5)git branch -d 分支名称 删除分支(分支被合并后才允许删除)(-D 强制删除)
4.6.4 暂时保存更改的代码
(1)存储临时改动:git stash
(2)恢复改动:git stash pop
4.6.5 代码回退
git reset --soft HEAD~n
4.7 get与post请求有什么区别
get是从服务器上获取数据,post是向服务器传送数据。
POST比GET安全,因为数据在地址栏上不可见。
get方式提交的数据最多只能有1024字节,而post则没有此限制。
GET使用URL或Cookie传参。而POST将数据放在request BODY中。
GET与POST都有自己的语义,不能随便混用。
根据研究表明,在网络环境下良好的条件下,在发送频率上的一次性和二次性之间存在基本无差别。然而,在网络环境下较差的情形下,则明显体现出二次发送的数据传输控制协议(TCP)在确认数据完整性方面具有显著优势。采用POST方法发送数据时会提交两次请求;GET方法仅提交一次请求。
4.8 cookie、localStorage、sessionStorage的区别
共同点: 都是保存在浏览器端、且同源的
不同点:
无论是否必要,cookie数据始终通过同源的http请求进行传输。其中其路径则决定了cookie只能在浏览器与服务器之间传输。而 cookie数据还有路径(path)的概念,则是限制cookie仅适用于某个特定路径范围之外的技术细节。值得注意的是,在这种情况下 sessionStorage 和 localStorage 并不会主动将数据发送给服务器,在本地存储的情况下也不会被同步更新。
存储大小限制也不同:
cookie数据不能超过4K,sessionStorage和localStorage可以达到5M
sessionStorage:仅在当前浏览器窗口关闭之前有效;
JavaScript中的localStorage始终可靠地记录信息,并且即使在窗口或浏览器关闭后仍能保存数据内容。作为本地存储机制的一种形式,在开发中常被用作持久化的数据存储方案。
cookie 仅在指定设置的时间范围内存在;即便窗口被关闭或浏览器退出。
作用域不同
sessionStorage:不在不同的浏览器窗口中共享,即使是同一个页面;
localstorage:该存储机制支持跨浏览器的一致性;无论浏览器是否处于关闭状态,在此存储机制中数据均得以保存
cookie: 同源窗口中的cookie同样具有共享性.这表明只要浏览器未关闭, cookie的数据依然能够被保存
4.9 async 和 await 的区别
区别:
通过async关键字可以定义异步函数,并获取或返回与该函数相关的Promise对象。其作用体现在通过functionName.then()方法完成后续操作。
await 后面跟的是任意表达式,一般使用promise的表达式
异步内部实现时会存在返回值,在这种情况下会触发promise.resolve();当出现异常时会触发promise.reject()并由catch捕获处理结果。
等待直到 promise 对象完成其执行过程,在 Promise 完成时获取 resolve 的值后继续执行后续代码。需要注意的是,在某些情况下(例如,在某些情况下),随后的表达式 可能会触发 Promise.reject()(例如,在某些情况下),因此建议将 await 放置在 try-catch 语句中以处理潜在的错误。
优点:async和await 属于es7语法。编写方便,提高程序效率,避免了回调地狱
补充:promise和async和await的区别
promise es6语法,promise中包含catch,async需要自己定义catch
promise 提供的方法会多一些,all、race等方法,aync中是没有的。
5.0 setTimeout 时间为0, 以及误差的原因
setTimeout,如果时间为0,则会立即插入队列,不是立即执行,等待前面的代码执行完毕。
5.1 求数组的最大值?
function getMaxArryNum(arr) {
return Math.max(...arr)
}
getMaxArryNum([1,2,3,4,5,6])
5.2 求数组的最小值?
const getMinArryNum= (arr) => {
return Math.min(...arr)
}
getMinArryNum([1,2,3,4,5,6])
5.3 数组去重
5.3.1 利用filter 、indexof
const removeEqual = (arr) => {
const result = arr.filter((item, index, self) => {
return self.indexof(item) === index
})
return result
}
removeEqual([1, 2, 2, 3, 4, 5, 5, 6, 7, 7])
在第一次循环中,在传入元素1时,index(1)返回的结果是0;而由于此时参数index(1)的实际值也是0,则满足条件。
在第二次循环中,在传入元素2时,index(2)返回的结果是1;而参数index(2)的实际值同样是1,则满足条件。
在第三次循环中,在传入元素2时(此时参数index(2)的实际值已经变为2),index(2)返回的结果仍然是0;这使得判断结果不满足条件而被跳过。
综上所述:
该方法允许对数组进行过滤操作。
其中x表示要过滤的元素;
index是与x一同传递的原始索引值;
self则代表整个数组本身。
创建一个包含从0开始一直到指定数字的所有整数的数组。
5.3.2 sort 、splice 实现去重
在排序前先对数组进行排序,在排序后依次执行循环:若当前项与前一项相等时,则移除当前项并令i递减;然后继续比较后续元素。
var arr = [1, 2, 3, 2, 4, 1];
arr.sort();
for (var i = 0; i < arr.length; i++) {
if (arr[i] === arr[i+1]) {
arr.splice(i,1);
i--;
}
}
console.log(arr); // [1, 2, 3, 4]
5.3.3 最短的方法,使用new Set([…])
var arr = [1, 2, 3, 2, 4, 1];
var newArr = new Set(arr);
console.log(newArr); // [1, 2, 3, 4];
5.3.4 indexOf 、push 实现
最基本的方法是:首先生成一个新的空数组用于存储结果;然后依次检查原始数组中的每个元素;对于每一个元素,在新生成的空数组中查找是否存在该元素;如果不存在,则将其添加到新数组中;如果已经存在,则直接跳过当前元素。
var arr = [1, 2, 3, 2, 4, 1];
var newArr = [];
for (var i = 0; i < arr.length; i++) {
if (newArr.indexOf(arr[i]) === -1) {
newArr.push(arr[i]);
}
}
console.log(newArr); // [1, 2, 3, 4]
5.3.5 对象属性名唯一性实现 数组去重
基于对象属性不可重复的特点,在创建新对象时将数组中的各项依次设为该对象的各项属性;然后调用Object.keys()方法获取所有字段名称所组成的数组;需要注意的是,在此示例中所有字段名称均为字符串类型,在实际操作中需将其转换为数值型。
var arr = [1, 2, 3, 2, 4, 1];
var obj = {};
var res = [];
arr.forEach(n => obj[n] = 1); // 把每一项添加为对象的属性,重复的属性不会再次添加,而是修改已存在的属性的值
res = Object.keys(obj).map(n => +n); // 得到包含字符串属性名的数组并把每一项转换成数字
console.log(res); // [1, 2, 3, 4]
5.4. 数组求和
const arrSum = (arr) => {
const temp = arr.reduce((pre, now) => {
return pre+now
},0)
return temp
}
arrSum([1,2,3,4])
5.5 生成从0 到 指定数字的数组
const getArr = (startNum, endNum) => {
let arr = []
for(var i=startNum; i<=endNum; i++){
arr.push(i)
}
return arr
}
getArr(0,4)
5.6 js的数据类型
js 数据类型分为基本数据类型和复杂数据类型
基本数据类型:Boolean、Number、String、Null、Undefined
复杂数据类型: Object、Array、Function、Date
5.7 js的变量提升
在js中,变量和函数的声明会被提升到最顶部执行
函数提升高于变量的提升
函数内部如果用var声明了相同名称的外部变量,函数将不再向上寻找
匿名函数不会提升
5.8 this指向
this总是指向函数的直接调用者。
如果有new关键字,this指向new出来的对象
在事件中,this指向触发这个事件的对象
5.9 map和forEach的区别
forEach方法,是最基本的方法,遍历和循环。默认有3个参数:分别是遍历的每一个元素item,遍历的索引index,遍历的数组array
map方法,和foreach一致,不同的是会返回一个新的数组,所以callback需要有return返回值,如果没有会返回undefined
6.0 箭头函数和普通函数的区别?
函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象
不可以当作构造函数,也就是说不可以使用new命令,否则会报错
不可以使用arguments对象,该对象在函数体内不存在,如果要用可以使用Rest参数代替
不可以使用yield命令,因此箭头函数不能用作Generator函数
6.1 es6新增
新增模版字符串
箭头函数
增加let、const来声明变量
for-of用来遍历数据-例如数组中的值
解构赋值
新增简单数据类型Symbol,独一无二的,不会与其他属性名冲突
将Promise对象纳入规范,提供了原生的Promise对象
6.2 数组方法汇总
map 循环遍历数组、返回一个新的数组
forEach 循环遍历数组,不改变原数组
push/pop 在数组的末尾添加/删除元素 改变原数组
unshift/ shift 在数组的头部添加/删除元素,改变原数组
join 把数组转化为字符串
some 有一项返回为true,则整体为true
every 有一项返回为true,则整体为false
filter 数组过滤
slice(start, end) 数组截取,包括开头,不包括截取,返回一个新的数组
splice(start, number, value) 删除数组元素,改变原数组
indexof/lastindexof: 查找数组项,返回对应的下标
concat:数组的拼接,不影响原数组,浅拷贝
sort:数组排序 改变原数组
reverse: 数组反转,改变原数组
6.3 项目性能优化方案
动态加载组件 <component :is="componA" />
减少http请求
减少DNS查询
使用CDN
避免重定向
图片懒加载
路由懒加载
减少DOM元素操作
使用外部js和css
压缩js、css、字体、图片等
使用iconfont字体图标、雪碧图等
避免图片的src为空
把样式表放在link中
把js放在页面的底部
6.4 forEach 和 map的区别
- Map函数必须通过return语句来生成并返回一个数组集合;而forEach函数不具备这种功能。
- Map集合具备良好的链式操作能力。
- Map函数允许使用break关键字跳出循环过程;
然而,在forEach函数中无法使用该关键字。
当使用Map时它不影响原始的数据存储结构;
对于forEach来说如果修改的是基本数据类型则不会影响原始数据存储结构。
5.
综合比较发现Map算法在执行效率上明显优于forEach方法。
6.5 forEach 和 for 循环的区别
- for循环是通过索引循环遍历每一个元素;而forEach是通过JS底层程序来实现循环遍历数组元素
- for循环可以通过break终止循环;而forEach不可以,会报错
- for循环可以通过控制循环变量的数值来控制循环次数;而forEach不行
- for循环可以在循环体外调用循环变量;而forEach不行
- for循环的执行效率比forEach高
==补充:==既然for比forEach效率高,为何不用for
原因: for循环只能通过索引下标数值来操作数组元素,而forEach可以设置参数,操作上更加遍历。
实际工作中,可自行根据需要选择
6.6 Vue2 和 Vue3 的区别
api 类型有所差异
在双向数据绑定的实现机制上存在差异。具体而言,在Vue 2中主要采用Object.defineProperty方法对属性进行劫持,并结合发布者订阅者的模式进行处理;而Vue 3则利用了ES6引入的Proxy特性。具体来说,在Vue 3中是通过Proxy特性对数据进行劫持,并触发响应。对比可以看出,在Vue 2中虽然实现了双向绑定功能但在检测下标变化方面存在不足;而Vue 3中的Proxy特性不仅能够劫持整个对象还能够返回一个新对象供后续操作使用
-
存储数据
Vue组件2通过将数据存储到data属性中来实现功能。
Vue组件3则依赖于钩子函数setup()来完成初始化任务,在组件实例化时自动调用。 -
生命周期钩子不同
Vue2--------------vue3
beforeCreate -> setup()
created -> setup()
beforeMount -> onBeforeMount
mounted -> onMounted
beforeUpdate -> onBeforeUpdate
updated -> onUpdated
beforeDestroy -> onBeforeUnmount
destroyed -> onUnmounted
activated -> onActivated
deactivated -> onDeactivated
-
处理文件上的引用
在vue2中生成的新实例对象的所有内容都会被处理的对象所管理;不管是否需要用到这些内容,它们都会被处理一遍以确保完整性和一致性。
从vue3.0开始支持按需引入的方法导入ES模块组件如keep-alive和v-model指令等。 -
项目启动方式
vue2 是 npm run dev
vue3 是 npm run serve -
语法上的区别
取消v-model,采用modelValue
<input v-model="value" />
<input modelValue="value" />
弃用全局API new Vue 采用 createAPP
const app = Vue.creatApp()
不能使用vue.nextTick()或this.$nextTick(),改用如下:
import { nextTick } from 'vue'
nextTick(() => {
// something
})
6.7 call、apply、bind使用和区别
该方法的主要作用是实现特定功能。其语法定义为fun.call(thisArg, arg1, arg2,…),其中thisArg参数用于fun函数运行时指定的this值;arg1、arg2等参数则传递给fun函数;该方法返回fun函数调用后的返回值
- 调用函数
- 改变this指向
const obj = {name: 'red'}
function fn(x, y) {
console.log(this)
console.log(x+y) // 3
}
fn.call() // 此时this指向为window
fn.call(obj, 1, 2) //此时this指向为obj
apply的作用:
语法说明如下:fun.apply()接收两个参数。
其中thisArg表示fun函数运行时指定的那个this值;argsArray则表示传递给fun执行操作的具体参数。
- 调用函数
- 改变this指向
const obj = {name: 'red'}
function fn(x, y) {
console.log(this)
console.log(x+y) // 3
}
fn.apply() // 此时this指向为window
fn.apply(obj, [1, 2]) //此时this指向为obj
该方法主要用于处理数组相关的问题。例如,在编程中可以通过调用Math.max()函数来获取一个数组中的最大值。
在代码实现上,
max = Math.max(...)
与
const max = Math.max.apply(Math, [1,2,3])
均可实现相同功能。
bind purpose:
在JavaScript中将一个函数与某个对象关联起来的作用。
语法:
fun.bind(thisArg, arg1, arg2,…)
参数定义:
thisArg: 该实例上被绑定到fun函数中的this变量赋值;
arg1、arg2等: 被传递给被绑定的fun函数作为参数;
返回结果:
是一个新Function实例副本(或说是引用),其绑定关系已经被复制过来,并且该新对象对该实例进行了this指针引用;
注意:
此操作不会导致任何实际调用(invocation),但会修改绑定对象(target)所使用的$this指针。
const obj = {name: 'tiger'}
function fn() {
console.log(this)
}
const fun = fn.bind(obj)
console.log(fun) // 打印出来是fn()函数,但是this指向obj
fun()
总结:
相同点:
三者都可以改变this的指向
不同点:
call和apply 可以调用函数
call和apply,传递的参数不同,call是以arg1,arg2形式传递,apply是以数组形式传递[argsArr]
bind不会调用函数
