文章目录
- A公司
- B公司
- C公司
- D公司
- E公司
- vue2的mixins用过吗?
- vue2的extends用过吗?
- vue2生命周期
- vue2父子组件生命周期执行顺序
- 自定义指令生命周期
- vue2父子组件通信方式
- vue2数据响应式原理
- git 本次提交误操作,具体撤销操作
- vue3的 ref 和 reactive 的使用有什么区别
- vue3中在ref中定义的变量被reactive引用,还需要用 .value获取吗?
- pinia有什么优点吗?vue3中如何使用pinia?
- keep-alive了解吗?它有哪些生命周期钩子?actived什么时候会触发?
- 首页加载慢,有什么解决办法吗?性能指标
- axios拦截的原理
- http如何与后端连接?
- 封装一个npm包步骤?
- 前端项目自动化部署
- 后端返回100000条数据如何处理?
- F公司
- vue2父子组件加载渲染的顺序
- vue3的ref和reactive的区别
- 讲讲vue3响应式
- vue3 v-if 和 v-for 的优先级
- v-for 循环中的key的作用
- vue3有什么优势
- ts和js的区别
- 循环遍历数组的哪些方法返回一个新数组?
- 深浅拷贝的实现方式
- 箭头函数和普通函数的区别
- forEach 和 map 的区别
- computed和watch的区别
- $nextTick的原理
- CSS垂直居中的实现方法
- display:none 和 visibilily: hidden 的区别
- 二面
- vue2、vue3的区别
- vue父子组件加载、更新、销毁的过程
- v-for 中 key 的作用
- vue2 diff 算法 ,vue3 diff算法
- new Vue() 初始化都做了什么事情?
- ts中的联合类型有4种类型,如何用Exclude 从联合类型中去除指定的类?
- js 函数重载
- 深拷贝实现方法?有循环嵌套的话怎么办?
- 什么是闭包?
- 浏览器的垃圾回收机制
- 原型链
- 浏览器缓存?强缓存和协商缓存执行时机
- css布局都知道哪些?三栏等高布局如何实现?
- line-height 和 height 设置一样的值想垂直居中,有没有居中实现不了的时候?
- 三面
- vue2、vue3的区别
- router全局路由前置和后置守卫分别做什么事情?
- es6新属性有哪些?
- Set 和 Map的区别、使用场景
- for...of 用法
- 箭头函数和普通函数的区别
- 改变this指向的方法有哪些?
- apply和call的区别
- js实现异步的方式有哪些?
- promise 和 async await 的区别
- promise.all 和 promise.race 的区别
- 基于promise写一个原生ajax请求
- 实现图片先模糊再显示高清图
- 移动端 1px 问题如何解决
- 如何进行移动端适配
- css 样式在客户端上不同机型不兼容,如何处理?
- G公司
- 后台系统如何实现页面权限控制?给不同角色分派不同的页面权限
- 页面按钮权限如何管理?
- uniapp如何使用分包,减少主包体积??还知道其他减少主包体积的方法吗?
- 写一个方法 使localStorage 和 sessionStorage实现序列化和反序列化
- 系统埋点
- spa单页面首次加载慢优化方法
- vue3相比vue2的升级
- 前端项目部署CI/CD有了解吗?
- 混合h5开发和客户端如何交互的?
- 看你封装了图片上传组件,使用 Canvas 压缩图片并上传阿里云 OSS,如何封装的?
- 看你二次封装富文本编辑器,二次封装都做了什么处理?用的什么富文本编辑器?
- axios配置过程
- vue项目的webpack如何配置压缩代码体积的?uniapp内部是如何实现代码压缩体积的?
- 有一个数组,比如 let arr = [1,2,3],修改 arr[1] = 4,数据更新了,但是dom还没更新,如何解决?
- $set内部实现原理
- js基本数据类型和引用类型分别有哪些?区别是什么?
- js事件循环机制
- 了解闭包、内存泄漏、垃圾回收机制吗?
- 闭包的使用场景
- 如何防止内存泄漏?
- 防抖、节流使用场景
- vue父子组件加载过程
- created和mounted生命周期的区别
- v-for 中 key 的作用
- vue2 diff 算法
- new Vue() 初始化都做了什么事情?
- computed和watch有什么区别?
- vue的$nextTick解决了什么问题?内部是如何实现的?
- 为什么vue中的data是一个 function?
- 平时通过什么途径了解新技术?
- 有了新技术如何向团队推进,用到公司项目中?
- 另外服务端渲染了解下
- H公司
通过这次面试,感觉面试官不再逮着那些死记硬背的八股文问题问了,更多的是问实际工作里遇到的场景,让你说怎么解决。然后我把面试里遇到的题目整理出来,放在这篇文章里。不过现在只写了题目,等我有空了,就把答案也补上,希望能帮到也在准备前端面试的小伙伴~
A公司
ts用的怎么样?都用过哪些功能?泛型用过没?
项目中只用过一些基础的,联合类型、接口、数组的类型、函数的类型、类型断言、type类型别名、接口继承
泛型:是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
也就是说,泛型是允许同一个函数接受不同类型参数
的一种模版,与any相比,使用泛型来创建可服用的组件要更好,因为泛型会保留参数类型(PS:泛型是整个TS的重点,也是难点,请多多注意~)
ts保姆级教程,别再说你不会ts了
一篇让你完全够用的TS指南
手写链式调用
链式调用在JavaScript语言中很常见,如jQuery、Promise等,都是使用的链式调用,当我们在调用同一对象多次其属性或方法的时候,我们需要多次书写对象进行 .或()
操作。
链式调用是通过在对象的方法中返回对象自身(this)来实现的。可使多个方法调用连续写在一起,形成链式调用。
- javascript的链式编程的核心就是
return this
- 链式调用的关键在于
每个方法都返回一个对象
,这样就可以继续调用下一个方法,无论这个对象是通过原型继承还是直接创建的。
JavaScript的链式调用
javascript中的链式操作
function Person(){
this.age=20;//默认值为20;
}
//通过原型给 Person构造函数上添加一个设置年龄的方法
Person.prototype.setAge=function(num){
this.age=num;
return this
}
//通过原型给 Person构造函数上添加一个获取年龄的方法
Person.prototype.getAge=function(){
return this.age;
}
// 使用链式调用
console.log(new Person().setAge(30).getAge()); //30
// promise中的链式调用
new Promise((resolve, reject) => {
resolve();
})
.then(() => {
throw new Error('Something failed');
})
.then(() => {
console.log('Do this whatever happened before');
})
.catch(() => {
console.log('Do that');
})
// vue3中实现链式调用
import { ref, computed } from 'vue';
const createCalculator = () => {
const value = ref(0);
const add = (num) => {
value.value += num;
return calculator; // 返回 calculator 对象以实现链式调用
};
const subtract = (num) => {
value.value -= num;
return calculator; // 返回 calculator 对象以实现链式调用
};
const getResult = computed(() => { //使用 computed 创建了一个计算属性 getResult,并通过 result.value 访问计算结果。
return value.value;
});
const calculator = {
add,
subtract,
getResult
};
return calculator;
};
const calc = createCalculator();
const result = calc.add(5).subtract(3).add(10).getResult; // 链式调用
console.log(result.value); // 输出 12
数组里有两个一样的数字,如何找到?
// 排序法
// 先对数组进行排序,然后检查相邻的元素是否相等。时间复杂度取决于排序算法的性能,一般为 O(n log n)。
function testArr(arr: any[]) {
let result = [] as number[]
arr.sort()
for (let i = 0; i < arr.length; i++) {
if (arr[i] === arr[i + 1]) {
result.push(arr[i])
}
}
return Array.from(new Set(result))
}
onMounted(() => {
let array = [1, 2, 5, 4, 7, 9, 2, 4, 4]
const result = testArr(array)
console.log(result) // 输出 [2,4]
})
// 使用对象来实现
function testArr(arr: any[]) {
let obj = {}
let newArr = [] as any[]
for (let i = 0; i < arr.length; i++) {
const num = arr[i]
if (obj[num]) {
newArr.push(num)
} else {
obj[num] = true
}
}
return Array.from(new Set(newArr))
}
onMounted(() => {
let array = [1, 2, 5, 4, 7, 9, 2, 4, 4]
const result = testArr(array)
console.log(result) // 输出 [2,4]
})
// 集合唯一法
// 使用集合(Set)来存储已经出现过的数字,遍历数组时,判断当前数字是否已经存在于集合中。时间复杂度为 O(n)。
function testArr(arr: any[]) {
let newArr = [] as any[]
let set = new Set()
for (let num of arr) {
if (set.has(num) && !newArr.includes(num)) {
newArr.push(num)
} else {
set.add(num)
}
}
console.log(set, 'set')
return newArr
}
onMounted(() => {
let array = [1, 2, 5, 4, 7, 9, 2, 4, 4]
const result = testArr(array)
console.log(result)
})
var、let、const的区别
- 作用域:使用
var
声明的变量属于全局变量
或者函数作用域
,即在声明它的函数内部有效,而使用let
、const
声明的变量属于块级作用域
,即在声明它的语句块内部有效(例如,if、for、while、try 等语句块)。这意味着使用 var 声明的变量在函数外部也可以访问,而使用 let 声明的变量只能在语句块内部访问。 - 变量提升: 使用
var
声明的变量会被提升
到函数作用域的顶部
,也就是说,即使在变量声明之前使用了变量,也不会报错,只是返回undefined
。而使用let
、const
声明的变量不会被提升,如果在声明之前使用变量,会抛出 ReferenceError。 - 暂时性死区:在使用
let、const
命令声明变量之前,该变量都是不可用的。这在语法上,称为暂时性死区
。使用var
声明的变量不存在暂时性死区。(a=1;let a; 这种就会报错) - 重复声明:在
同一个作用域
中,使用var
声明同名的变量不会报错,而是会覆盖
原来的变量。而使用let
、const
声明同名的变量会报错
。 - 给全局添加属性:浏览器的全局对象是 window,Node 的全局对象是 global。
var
声明的变量为全局变量,并且会将该变量添加为全局对象的属性,但是let
和const
不会。 - 初始值设置: 在变量声明时,
var
和let
可以不用设置初始值。而const
声明变量是一个只读变量
,必须设置初始值。 - 指针指向:
let
和const
都是 ES6 新增的用于创建变量的语法。let
创建的变量是可以更改指针指向(可以重新赋值)。但const
声明的变量是不允许改变指针的指向。
可参考: 前端面试题:JS中的let和var的区别
让我写一段代码,我用的 let 定义的变量,问为什么不用 const 定义呢?
在let和const之间,建议优先使用const,尤其是在全局环境,不应该设置变量,只应设置常量。
const优于let有几个原因。
- 一是阅读代码的人立刻会意识到不应该修改这个值。
- 二是防止了无意间修改变量值所导致的错误。
- 三是 JavaScript 编译器会对const进行优化,所以多使用const,有利于提高程序的运行效率,
手写promise和async await
promise
function testPromise(ready: boolean) {
return new Promise((resolve, reject) => {
if (ready) {
resolve("正确");
} else {
reject("捕获错误");
}
});
}
// 方法调用
testPromise(true)
.then((res) => {
console.log(res, "res");
})
.catch((err) => {
console.log(err, "err");
});
async await
function testAsync(str: string) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(str);
}, 1000);
});
}
async function testAwt() {
const result = await testAsync("hello world");
console.log(result); //1秒后出现hello world
console.log("阻塞"); //1秒后出现阻塞
}
onMounted(() => {
testAwt();
console.log("同步代码");// 同步代码立即执行
});
// 执行结果:同步代码 => hello world => 阻塞
promise 和 async await,两者有什么区别
解决问题的角度
:Promise 解决了多个回调函数嵌套
的时候会造成回调地狱
问题,不利于代码维护。async/await
解决 Promise的多个then
的链式调用问题。(Promise 解决的是异步编码风格
的问题,而不是一些其他的问题)语法
:Promise
使用链式调用
的方式处理异步操作,通过then
和catch
方法来注册回调函数。
而async/await
使用类似同步代码的方式来处理异步操作,使代码可读性更高。使用async
声明一个 function 是异步函数,然后通过await
关键字等待一个异步方法执行完成,并且会阻塞
当前函数体内后面的代码,等await等待的 promise对象执行完毕后,再执行阻塞的代码。规定await只能出现在async函数内。async 函数返回的是一个 Promise 对象。错误处理
:在Promise
中,使用catch
方法来捕获和处理错误。而在async/await
中,可以使用try/catch
语句来捕获异步函数中的错误。可读性
:相对于 Promise 的链式调用,async/await 更接近传统的同步编程风格,简化异步代码的编写和理解。异步操作的顺序控制
:使用Promise
时,你可以使用.then
方法将多个异步操作串联起来,或者使用Promise.all
来等待多个异步操作都完成。而使用async/await
,则可以使用await
关键字按照顺序依次执行异步操作。
promise.all 和 promise.race 的区别
Promise.all
Promise.all
可将多个Promise
实例包装成一个新的Promise
实例。同时,成功和失败的返回值是不同的,成功
返回的是一个结果数组
;失败
返回的是第一个
被reject失败状态的值
,而且并不影响其他Promise实例正常的reject拒绝操作。Promise.all
传入数组中的多个 Promise 实例状态都变成fulfilled
,返回新的Promise
实例状态才会变成fulfilled
,只要数组中有一个 Promise 实例 被rejected
,返回新的Promise
实例 就变成rejected
。Promise.all
传入的是数组
,返回的也是数组,并会进行映射
,传入的Promise对象返回的值是按照顺序在数组中排列的,注意他们的执行顺序并不是按照传入顺序的,除非可迭代对象为空。Promise.all
获得的成功结果数组的数据顺序和 Promise.all 接收到的数组顺序
是一致的,这样当遇到发送多个请求并根据顺序获取和使用数据的场景,就可以使用Promise.all
来解决。(用于并发请求)- 注意,如果作为参数的 Promise 实例,自己定义了
catch
方法,那么它一旦被rejected
,并不会触发Promise.all()的catch
方法。
const p1 = new Promise((resolve, reject) => {
resolve('hello');
})
.then(result => result)
.catch(e => e);
const p2 = new Promise((resolve, reject) => {
throw new Error('报错了');
})
.then(result => result)
.catch(e => e);
Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// ["hello", Error: 报错了]
如果p2没有自己的catch方法,就会调用Promise.all()的catch方法。
const p1 = new Promise((resolve, reject) => {
resolve('hello');
})
.then(result => result);
const p2 = new Promise((resolve, reject) => {
throw new Error('报错了');
})
.then(result => result);
Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// Error: 报错了
promise.race
-
Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
-
字面意思 “
赛跑
”,就是说 promise.race([a1,a2,a3]) 里面哪个结果获得的快,就返回哪个结果,不管结果本身是成功还是失败。 -
Promise.race() 不会对resolve(解决)或reject(拒绝)的 Promise 区别对待。无论是解决还是拒绝,只要第一个落定的 Promise,Promise.race() 就会包装其解决值或拒绝理由并返回新Promise。
-
当做一件事,超过多少长时间就不做了,可用
promise.race
来解决;比如可以设置图片请求超时。例子在下文
Promise.race([testPromise(true), testAwt()]);
async function runAsync(x: number) {
const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000))
return p
}
Promise.race([runAsync(1), runAsync(2), runAsync(3)])
.then(res => {
console.log('result', res)
})
.catch(err => {
console.log('err', err)
})
// 1 => result 1 => 2 => 3
// then 只会捕获第一个成功的方法,其他的函数虽然还会继续执行,但是不被then捕获了
//请求某个图片资源
function requestImg(){
var p = new Promise(function(resolve, reject){
var img = new Image();
img.onload = function(){
resolve(img);
}
//img.src = "https://b-gold-cdn.xitu.io/v3/static/img/logo.a7995ad.svg"; 正确的
img.src = "https://b-gold-cdn.xitu.io/v3/static/img/logo.a7995ad.svg1";
});
return p;
}
//延时函数,用于给请求计时
function timeout(){
var p = new Promise(function(resolve, reject){
setTimeout(function(){
reject('图片请求超时');
}, 5000);
});
return p;
}
Promise
.race([requestImg(), timeout()])
.then(function(results){
console.log(results);
})
.catch(function(reason){
console.log(reason);
});
async 如何以同步的方式实现异步的?
考察js的事件循环、微任务和宏任务。
通过使用async/await,我们可以在主函数中按照顺序执行异步操作,就像是同步代码一样。这样做可以使代码更易读、更易维护,
B公司
http和https的区别
主要区别在于 安全性
和 数据传输方式
上,https比http更加安全,适用于保护网站用户的隐私和安全,如银行网站、电子商务网站。
HTTPS 是身披 SSL 外壳的HTTP。
HTTP + SSL + 证书 + 报文完整性 = HTTPS
安全性
:http
协议传输的数据都是明文传输
,因此使用 http协议传输 的数据可以被任何抓包工具截取并查看。而https
协议是由SSL+http协议
构建的可进行加密传输、身份认证的网络协议,更为安全。数据传输方式
:http
协议的端口号是80,https
协议的端口号是443。证书
:https 协议需要到CA申请证书,一般免费证书较少,因而需要一定费用。网络速度
: http协议比https协议快,因为https协议需要进行加密和解密的过程。SEO优化
:搜索引擎更倾向于把https网站排在更前面的位置,因为https更安全。
项目中既有http请求又有https请求的时候如何处理?
建一个单独的文件,分别存储好http和https相对应的域名,封装两套 request 请求。
说下你知道的前端数据存储方式?
浏览器的本地存储主要分为Cookie
、WebStorage
和IndexedDB
, 其中WebStorage
又可以分为localStorage
和sessionStorage
。
- cookies:小型文本文件,由服务器发送到用户的浏览器以存储在用户的计算机上。可以用于存储少量数据,但受限于大小和安全性。
- localStorage:存储持久数据,浏览器关闭后数据不丢失除非主动删除数据。
- sessionStorage: 数据在当前浏览器窗口关闭后自动删除。
- IndexedDB:适用于需要处理大量结构化数据、需要离线访问和更复杂查询的情况,例如离线Web应用程序、数据同步等。IndexedDB 提供了更大的存储容量和更强大的查询能力,但也需要更多的编程工作
可参考:第2篇: 能不能说一说浏览器的本地存储?各自优劣如何?
前端数据库的话你常用哪些方法?
增删改查(add,delete)
什么是跨域,跨域的解决方案?
协议、域名、端口号有一个不一样就会导致跨域。
同步和异步的区别
同步任务
:立即执行的任务;在主线程
上排队执行,形成一个执行栈
;只有前一个任务执行完毕,才能继续执行下一个任务。异步任务
:不进入主线程,而进入“任务队列
”的任务;只有等主线程任务全部执行完毕。“任务队列”的任务才会进入主线程执行。
js是单线程,他是如何处理异步的?
通过JS的事件循环
这里面试我的面试官想考察 浏览器内核是多线程的。遇到异步任务内部是如何处理的? 我的回答是:
浏览器内核是多线程的,分为GUI渲染线程、JS引擎线程、事件触发线程、定时器线程、异步http请求线程。遇到一个异步任务,会先交给其他辅助线程来处理,执行完毕后放入任务队列中等待执行;当执行栈中任务都处理完毕,就从任务队列中先取微任务执行,然后再取宏任务执行。
具体可查看这篇文章前一部分:浏览器的进程和线程
js在头部和尾部的区别
JS的加载、解析与执行会阻塞文档的解析,也就是说,在构建 DOM 时,HTML 解析器若遇到了 JavaScript,那么它会暂停文档的解析,将控制权移交给 JS 引擎,等 JS 引擎运行完毕,浏览器再从中断的地方恢复继续解析文档。
也就是说,如果想要首屏渲染的越快,就越不应该在首屏就加载 JS 文件,这也是建议将 script 标签放在 body 标签底部的原因。当然在当下,并不是说 script 标签必须放在底部,因为你可以给 script
标签添加 defer
或者 async
属性。
defer与async的区别
- defer要等到整个页面在内存中正常渲染结束(DOM 结构完全生成,以及其他脚本执行完成),才会执行。
- async一旦下载完,渲染引擎就会中断渲染,执行这个脚本以后,再继续渲染。
- 一句话,
defer
是“渲染完再执行
”,async
是“下载完就执行
”。 - 另外,如果有多个defer脚本,会按照它们在页面出现的顺序加载,而多个async脚本是不能保证加载顺序的。(因为只要该模块加载完成,就执行该模块,不确定模块什么时候能加载完)
Promise.all 和 Promise.race的使用
这个是面试官提了他们的使用场景,多个异步任务的时候,问我怎么处理,我回答了这两个。
答案同A公司
网络不好的时候,页面渲染慢有什么优化方法?
骨架屏和懒加载
vue中父子组件传值的方式
vue中的8种常规通讯方案:
- 通过 props 传递
- 通过 $emit 触发自定义事件
- 使用 ref
- EventBus(事件中心)
- $parent 或 $root
- attrs 和 listeners
- provide 和 inject
- Vuex
组件间通信的分类可以分成以下:
- 父子关系的组件数据传递选择
props
与$emit
进行传递,也可以选择ref
。- 兄弟关系的组件数据传递可选择
$bus
,其次可以选择$parent
进行传递。- 祖先与后代组件数据传递可选择
attrs
和listeners
,或者provide
和inject
。- 复杂关系的组件数据传递可通过
Vuex
存放共享的变量。
watch 和 computed 的区别
-
computed :基于其依赖的
响应式数据
(data,props)进行计算得出结果的属性。并且computed
的值有缓存
,只有他依赖的属性值发生改变,下一次获取computed
的值时才会重新计算computed
的值。必须同步。 -
watch :更多的是
观察
作用,无缓存
性;类似于某些数据的监听回调,每当监听的数据变化时都会执行回调进行后续操作。支持同步/异步。
运用场景:
- 当需要进行数值计算,并依赖于已有的
响应式数据
进行计算得出结果的场景,应该使用computed
,因为可以利用computed
的缓存特性,避免每次获取值时都要重新计算。- 当需要在数据变化时
执行异步
或者开销较大
的操作时,应使用watch
。
C公司
先问了我最近做的项目,负责做哪些模块?担任什么角色
对组件化的理解?
因为我用的 vue 技术栈,所以回答了对 vue 组件化的理解。
- 将复杂的系统分解为相互独立、可替换和可复用的模块。这些模块被称为组件。
- 组件可以独立开发、测试和部署,从而提高了代码的可维护性和可复用性。
- 组件分类有
页面组件
、业务组件
、通用组件
。- vue中常见的组件化技术有:属性props,自定义事件,插槽等,他们主要用于组件通信、扩展等。
- 组件应该是高内聚、低耦合的。
- 遵循单向数据流原则。
JS数组常用的操作方法有哪些?
对uniapp的理解
因为看我有小程序项目,所以问了下。
uni-app 是一个使用
Vue.js
开发所有前端应用的框架,开发者编写一套代码,可发布到iOS、Android、Web(响应式)、以及各种小程序(微信/支付宝/百度/头条/飞书/QQ/快手/钉钉/淘宝)、快应用等多个平台。(一套代码编到十几个平台)
vue3生命周期,微信小程序生命周期
vue3生命周期:
- 基本上就是在 Vue2 生命周期钩子函数名基础上加了
on
setup
代替了两个钩子函数beforeCreate
和created
- beforeDestory 和 destoryed 更名为
onBeforeUnmount
和onUnmounted
微信小程序生命周期:
应用生命周期(App.vue中的)
函数名 | 说明 |
---|---|
onLaunch | 当uni-app 初始化完成时触发(全局只触发一次) |
onShow | 当 uni-app 启动,或从后台进入前台显示 |
onHide | 当 uni-app 从前台进入后台 |
onError | 当 uni-app 报错时触发 |
页面生命周期
函数名 | 说明 |
---|---|
onInit | 监听页面初始化,其参数同 onLoad 参数,为上个页面传递的数据,参数类型为 Object(用于页面传参),触发时机早于 onLoad |
onLoad | 监听页面加载,该钩子被调用时,响应式数据、计算属性、方法、侦听器、props、slots 已设置完成,其参数为上个页面传递的数据,参数类型为 Object(用于页面传参) |
onShow | 监听页面显示,页面每次出现在屏幕上都触发,包括从下级页面点返回露出当前页面 |
onReady | 监听页面初次渲染完成,此时组件已挂载完成,DOM 树($el)已可用,注意如果渲染速度快,会在页面进入动画完成前触发 |
onHide | 监听页面隐藏 |
onUnload | 监听页面卸载 |
vue中父子组件传值的方式
答案同B公司
前端常用数据结构有哪些?
只回答了数组 和 对象。回来百度了下:
数组
(Array):是一种线性表数据结构,用于存储一组有序的元素。在JavaScript中,数组可以动态增长,并且可以容纳不同类型的元素。对象
(Object):是一种复合数据类型,用于存储键值对。对象可以通过字符串键来访问其属性。在JavaScript中,对象是一种非常灵活的数据结构,用于表示复杂的数据关联。栈
(Stack):是一种遵循后进先出
原则的线性数据结构。在前端开发中,栈常被用于处理函数调用、表达式求值等场景。队列
(Queue):是一种遵循先进先出
原则的线性数据结构。在前端开发中,队列通常用于事件处理、消息传递等。链表
(Linked List):是一种由一系列节点组成的数据结构,每个节点都包含下一个节点的指针。链表在前端开发中可能被用于实现各种数据结构或算法。哈希表
:通过将键(key)映射到值(value)的方式来实现高效的数据存储和检索。通常被用于一些需要快速查找和存储键值对的场景,例如管理状态、缓存数据等。JavaScript中的对象可以被视为一种哈希表的实现,因为它们基本上就是键值对的集合。树
(Tree):是一种层级数据结构,由节点和边组成。在前端开发中,树可能被用于构建DOM树或者实现树形结构的数据。图
(Graph):是一种由节点和边组成的非线性数据结构。在前端开发中,可能会用到图来表示各种复杂的关系网络。
这些数据结构在前端开发中经常被使用,了解它们的特性和适用场景对于有效地处理和组织前端数据至关重要。
谈谈你对栈和堆的理解,栈和堆上存放的是什么数据?所有引用类型都存放在堆上吗?
栈(Stack)
存储方式
: 栈是一种线性结构,采用先进后出
的存储方式,是静态分配
的内存区域。数据类型
: 在栈上主要存放局部变量
、函数调用信息
和执行上下文、以及函数参数
等数据。大小限制
: 栈的大小通常有限的、固定的,由系统预先分配好,因此栈的内存空间相对较小。自动管理
: 栈的内存管理是由操作系统自动进行的,当函数执行结束或者变量超出作用域时,栈上的数据会被自动释放
。
堆(Heap)
存储方式
: 堆是一种非线性结构,其存储方式是动态分配
的。数据类型
: 在堆上主要存放动态分配的对象
、引用类型
以及通过new关键字创建的对象
。大小限制
: 堆的大小比栈要大得多,并且大小不固定;可以动态地分配和释放内存。手动管理
: 在很多编程语言中,如C++,需要手动管理堆上的内存;而在像Java和JavaScript这样的语言中,堆内存通常由垃圾回收器
自动管理。
数据存储位置
基本类型
: 基本类型的数据通常存储在栈
上。 例如整数、浮点数。指针等引用类型
: 引用类型的数据(对象、数组、链表等)的实际数据存储在堆
上,而变量本身存储在栈上,变量中存储的是指向堆内存中实际数据的引用。 引用数据类型存储在堆内存中,因为引用数据类型占据空间大
、大小不固定
。
注意
:
- 并非所有引用类型都存放在堆上,例如一些小的对象、数组可以直接存放在栈上,一旦占用的内存超了,就会改放在堆上。这些细节取决于具体的编程语言和其底层实现。
闭包中的变量
并不保存中栈内存中,而是保存在堆内存
中。这是因为v8引擎垃圾回收机制是在堆内存上进行的。- 看网上有人说常用的V8引擎,没有栈内存,只有堆内存的概念。堆内存里根据数据的生命周期又进行了内存分片。这个还需要继续考证,目前还没有精力,问题先留在这里,以后再更新…
栈内存和堆内存的优缺点
- 在JS中,基本数据类型变量大小固定,并且操作简单容易,所以把它们放入栈中存储。
- 引用类型变量大小不固定,所以把它们分配给堆中,让他们申请空间的时候自己确定大小,这样把它们分开存储
能够使得程序运行起来占用的内存最小
。- 栈内存由于它的特点,所以它的系统效率较高。堆内存需要分配空间和地址,还要把地址存到栈中,所以效率低于栈。
主要做抖音小程序,不熟悉的话你会如何进展呢?
看官方文档
D公司
基本都是问了项目基础建设和工程化的问题,这次面试让我学到很多,知道了自己欠缺的地方,也给自己制定了学习任务。
git commit 时候不符合 公司代码提交规范如何在项目中配置?
- 安装
commitlint cli
在提交代码前对git commit提交信息进行校验 - 在根目录手动创建
commitlint.config.js
文件,自定义配置校验规则 - 安装 Git Hook 工具
husky
,它继承了Git下所有的钩子(pre-commit、pre-push 等),在触发钩子的时候,husky可以阻止不合法的commit,push等等 - 如果使用vue-cli创建的项目中可以不安装husky,在package.json文件中直接通过
gitHooks
字段直接添加git钩子
(commit-msg、pre-commit等钩子)即可。
前端项目如何部署到gitLab?docker了解吗?
比如跨项目都公用的组件,如何处理?
封装一个npm包
webpack工程化用less写样式,内部是如何转化的(loader)
less-loader => css-loader => style-loader
less-loader
将 less 文件转换为 CSS文件;- 再由
css-loader
解析CSS文件,并处理依赖关系,如@import和url();最终返回 CSS 代码。- 再由
style-loader
把 CSS 文件,整合在 html 文件的<head>
标签的<style>
中。就是说将模块导出的内容作为样式并添加到 DOM 中
css-loader 如何转化成单独的文件?
有利于缓存,使用mini-css-extract-plugin
,将CSS提取到单独的文件中,支持按需加载。
mini-css-extract-plugin
的作用:这个插件用于将 CSS 抽取到单独的文件中,并压缩它们。它可以在生产环境中使用。
全局自定义指令
localStorage 和 sessionStorage的使用场景,分别存储什么数据?
localStorage :适合存储长期使用的数据,跨时间和标签页共享
- 利用localStorage的较大容量和持久特性,可以利用
localStorage
存储一些内容稳定的资源,比如官网的logo
,存储Base64
格式的图片资源,因此利用localStorage。
sessionStorage:适合存储临时性的数据,只在当前会话有效
- 可以用它对表单信息进行维护,将表单信息存储在里面,可以保证页面即使刷新也不会让之前的表单信息丢失。
- 可以用它存储本次浏览记录。如果关闭页面后不需要这些记录,用
sessionStorage
就再合适不过了。事实上微博就采取了这样的存储方式。
vue3搭建项目流程,需要考虑什么?
* 架子:选用合适的初始化脚手架(`vue-cli2.0`或者`vue-cli3.0`)
* 请求:数据`axios`请求的配置
* 登录:登录注册系统
* 路由:路由管理页面
* 数据:`vuex`全局数据管理
* 权限:权限管理系统
* 埋点:埋点系统
* 插件:第三方插件的选取以及引入方式
* 错误:错误页面
* 入口:前端资源直接当静态资源,或者服务端模板拉取
* `SEO`:如果考虑`SEO`建议采用`SSR`方案
* 组件:基础组件/业务组件
* 样式:样式预处理起,公共样式抽取
* 方法:公共方法抽离
前端域名写死不安全,改起来也不方便,如何处理?
axios请求拦截器 和 响应拦截器 要做的事情有哪些?底层原理是如何实现的?
请求拦截器
- 请求拦截器可以在每个请求里加上token,做了统一处理后维护起来也方便。
// 请求拦截器
axios.interceptors.request.use(
config => {
// 每次发送请求之前判断是否存在token
// 如果存在,则统一在http请求的header都加上token,这样后台根据token判断你的登录情况,此处token一般是用户完成登录后储存到localstorage里的
token && (config.headers.Authorization = token)
return config
},
error => {
return Promise.error(error)
})
响应拦截器(可以在接收到响应后先做一层操作)
响应数据处理:对返回的数据进行处理,例如解析、转换为特定格式等。
错误处理:检查响应状态码,根据不同的状态码进行相应的错误处理,例如重定向、错误提示等。
统一异常处理:处理请求过程中可能发生的异常,例如网络错误、超时等。
// 响应拦截器
axios.interceptors.response.use(response => {
// 如果返回的状态码为200,说明接口请求成功,可以正常拿到数据
// 否则的话抛出错误
if (response.status === 200) {
if (response.data.code === 511) {
// 未授权调取授权接口
} else if (response.data.code === 510) {
// 未登录跳转登录页
} else {
return Promise.resolve(response)
}
} else {
return Promise.reject(response)
}
}, error => {
// 我们可以在这里对异常状态作统一处理
if (error.response.status) {
// 处理请求失败的情况
// 对不同返回码对相应处理
return Promise.reject(error.response)
}
})
Axios 的底层原理是基于浏览器提供的 XMLHttpRequest
对象或者基于 Node.js 提供的 http
模块,通过封装这些底层 API,提供了更方便的 HTTP 请求和响应处理接口。
Axios 使用 Promise
来处理异步请求,可以通过链式调用
的方式编写代码,使得代码更加简洁和易读。
学习一个新技术如何进行的?
想要学习一门新技术,最好的方式是先了解这门技术是如何诞生的,以及它所解决的问题是什么。了解了这些后,才能抓住这门技术的本质。
E公司
各种方方面面的八股文。问的也不深,浅浅的一层。
vue2的mixins用过吗?
vue2的extends用过吗?
vue2生命周期
vue2父子组件生命周期执行顺序
自定义指令生命周期
vue2父子组件通信方式
答案同B公司
vue2数据响应式原理
git 本次提交误操作,具体撤销操作
1. 撤销上一个提交
git reset HEAD^ --soft
这会将 HEAD(当前分支的最新提交)移动到前一个提交,并将上一个提交从历史记录中删除。
注意
:需要小心使用此命令,因为会影响整个项目历史。
关于–soft:
–soft 选项表示撤销最近一次提交,但不删除之前的更改。也就是说,该操作将会取消最近一次提交并将变更回滚到暂存区(Index),但是工作目录中的文件还保持原有状态,没有被清除或者修改。
2. 撤销多个提交
git reset HEAD~n --soft // 将 HEAD 指向要保留的提交之前的提交( n是要保留的提交数 )
git stash // 暂存要丢弃的提交
如果要撤销最近的两个提交,可以执行以下命令;这将撤销最近的两个提交,但保留更改在工作目录中。
git reset HEAD~2 --soft
在撤销提交后,你可以重新提交更改或者修改需要更改的内容。
git push -f // 强制推上去
注意
:
- 此时如果用
git push
会报错,因为我们本地库HEAD指向的版本比远程库的要旧。 - 在执行此操作之前,请确保和协作者进行充分的沟通,以避免对其他人的工作产生影响。
3. 回滚提交
git revert <commit-hash> //<commit-hash>是要回滚的提交的哈希值
4. git reset 和 git revert 的使用场景
git reset
: 如果想恢复到之前某个提交的版本,且那个版本之后提交的版本我们都不要了,就可以用这种方法。
git revert
: 如果我们想撤销之前的某一版本,但是又想保留该目标版本后面的版本,记录下这整个版本变动流程,就可以用这种方法
vue3的 ref 和 reactive 的使用有什么区别
vue3中在ref中定义的变量被reactive引用,还需要用 .value获取吗?
const myName = ref<string>('我是铁锤')
const myState = reactive({
name: myName,
age: 18
})
console.log(myState.name, 'ref中定义的变量被reactive引用,不需要用 .value获取')
上面代码中,在 reactive
对象中引用 ref
定义的 myName
时,不需要使用 .value
获取,是因为 Vue3 在内部自动解包
了 ref 对象;这是 Vue3 设计的一个便利之处。
pinia有什么优点吗?vue3中如何使用pinia?
keep-alive了解吗?它有哪些生命周期钩子?actived什么时候会触发?
首页加载慢,有什么解决办法吗?性能指标
axios拦截的原理
Axios 是一个基于 promise
网络请求库,作用于node.js 和浏览器中。 在服务端它使用原生 node.js http
模块, 而在客户端 (浏览端) 则使用 XMLHttpRequests
。
它拥有三大功能:
- 适配器
web端使用XMLHttpRequest
Node端根据当前协议会使用http
模块与https
模块- 拦截器
通过Promise
链式调用,在真正请求之前与之后添加钩子函数- 抵御CSRF安全问题
内部设置xsrfName字段可以提供给开发者传递token
拦截原理:
- axios支持自定义配置,支持get等api,其实最终都是调用原型上的
Axios.prototype.request
方法,这个方法会对传入的参数进行混合。- axios对于浏览器基于
Promise
创建了XmlHttpReques
对象,成功失败返回的是Promise
,所以我们可以调用.then
方法接收返回值。- 对于请求拦截、响应拦截,当使用拦截的
.use()
的时候传入了成功和失败的回调方法
,在axios调用request()
方法的时候,会创建个数组,chain=[dispatchRequest,undefined]
请求拦截器方法是被unshift
到dispatchRequest前面,响应拦截器是被push
到undifined后面,然后按照顺序循环调用每次取出两个,正好对应传入的成功回调和失败回调,这个数组保证了,请求拦截和响应拦截的顺序请求拦截--->发送请求api--->响应拦截
。其中dispatchRequest
是api请求,放在中间;undefined只起一个占位的作用。- 不管我们调用get,post,delete,head等方法并且传入参数,最后都通过
merge
处理传给了request,所以最后执行的都是request
方法;这也是说Axios.prototype.request
是核心方法的原因;- 对于取消请求在创建axios的时候传入了取消的对象cancelToken,这个对象创建了一个promise并向外暴露了一个取消方法,当调用这个取消方法的时候,会把这个promise状态设置为成功,当成功的时候,会在xhr文件里调用 取消请求的 request.abort()方法。
通过下面文章可阅读axios拦截源码:
axios原理分析,让你知道拦截器的实现,取消请求,自定义配置。
我终于明白了Axios拦截器的原理
http如何与后端连接?
tcp三次握手之后,发送http请求
封装一个npm包步骤?
前端项目自动化部署
后端返回100000条数据如何处理?
虚拟滚动
webWorker 多线程处理
F公司
也是八股文。
vue2父子组件加载渲染的顺序
父组件 beforeCreate
父组件 created
父组件 beforeMount
子组件 beforeCreate
子组件 created
子组件 beforeMount
子组件 mountd
父组件 mountd
vue3的ref和reactive的区别
- 首先Vue3提供
ref
和reactive
两个API来实现响应式。 - Vue3 区分
ref
和reactive
的原因就是Proxy
无法对原始值
做代理,所以需要一层对象
作为包裹。
ref 响应式原理
- ref 内部封装一个
RefImpl
类,并设置 get/set 方法,当通过.value
调用时就会触发劫持,从而实现响应式。- ref 返回的是一个包装过的响应式引用,所以需要通过
.value
来访问内部的值。- 当接收的是对象或数组时候,内部依然是用
reactive
去实现响应式。
具体的可以直接去看vue3源码 /vue3/packages/reactivity/src/ref.ts
reactive 响应式原理
reactive
函数利用Proxy
对象实现了对普通对象
的代理,并通过拦截对象的访问和修改操作,实现了数据的响应式更新。- 在代理对象中,当
访问
对象属性时,会触发get
处理函数。在这个函数中,会收集当前属性的依赖,并返回当前属性的值。这里的依赖是指在模板中引用了该属性的地方,Vue 3 会自动跟踪这些依赖。- 在代理对象中,当
修改
对象属性时,会触发set
处理函数。在这个函数中,会更新属性的值,并通知所有依赖该属性的地方进行更新。这里的更新是指重新计算引用该属性的部分内容,并将结果显示在页面上。- 使用
Proxy
拦截数据的访问和修改操作,再使用Reflect
完成原本的操作(get
、set
)
具体的可以直接去看vue3源码 /vue3/packages/reactivity/src/ref.ts
讲讲vue3响应式
vue3 v-if 和 v-for 的优先级
v-for 循环中的key的作用
key 的作用主要是为了更高效的更新虚拟 DOM,因为它可以非常精确的找到相同节点,diff 操作可以更高效。
如果数据项的顺序发生了改变,Vue 不会移动 DOM 元素来匹配数据项的顺序,而是简单“就地复用
”此处的每个元素。
vue3有什么优势
ts和js的区别
TypeScript是JavaScript的超集,它向其添加了强类型特征、类、接口等面向对象编程的概念。本质上,TypeScript可以看作是一个为JavaScript提供类型检查和更严格语法的扩展版本,有以下几个区别、特点以及优缺点:
1)语言类型不同
TypeScript 是强类型语言,JavaScript是弱类型语言;强类型语言
是指在编译期间
进行类型检查,弱类型语言
则是在运行期间
进行类型检查
2) 类型检查
TypeScript与JavaScript最大的区别在于它允许类型注解和类型检查。使用类型注解可以明确地指定变量、函数的类型,而类型检查可以在代码编写过程中及时发现错误,增强了代码的可维护性
和健壮性
。
3) 面向对象编程
TypeScript支持类
、接口
、继承
、泛型
等面向对象编程的概念,这些概念在JavaScript中并不完整或者根本不存在。
4) 更严格的语法
TypeScript对于JavaScript语法进行了扩展,包括对ES6/ES7+的新特性的支持,还有很多自己独有的语言特性,例如元组,枚举,交叉类型,并集类型等。
5) 执行效率
由于TypeScript需要在 编译阶段 进行类型检查和转换,相比较于JavaScript,不可避免地会增加一定的编译时间,同时需要额外的编译步骤。
6) 学习成本
相比于JavaScript,TypeScript拥有更多的语法和概念,需要花费更多的时间来学习。
总的来说,TypeScript相对于JavaScript,优点在于:强类型
、面向对象编程
、更严格的语法
、更好的可维护性
;缺点在于:编译时间较长
、学习成本比较高
。另外,TypeScript也并不适用于所有场景,具体应该根据具体的项目需求和团队技术水平进行选择。
循环遍历数组的哪些方法返回一个新数组?
map 、filter、reduce
深浅拷贝的实现方式
箭头函数和普通函数的区别
forEach 和 map 的区别
返回值:
- forEach() 方法没有返回值(或者说返回 undefined),它只是对数组的每个元素执行指定的回调函数。
- map() 方法返回一个新的数组,该数组包含对原数组每个元素执行回调函数后的结果。
原数组的改变:
- forEach() 方法不会改变原始数组。如果在 forEach() 的回调函数中修改了数组的元素,那么这些修改只是针对当前循环的元素有效,不会改变原数组
- map() 方法不会改变原始数组,而是返回一个新的数组,该数组由原数组经过回调函数操作后的结果组成。
支持链式调用:
forEach不支持链式调用,而map支持链式调用,可以继续对返回的新数组进行操作。
const array3 = [1, 2, 3, 4, 5];
const doubledSum = array3
.map((element) => element * 2)
.reduce((accumulator, currentValue) => accumulator + currentValue, 0);
console.log(doubledSum); // 输出: 30
computed和watch的区别
$nextTick的原理
CSS垂直居中的实现方法
display:none 和 visibilily: hidden 的区别
二面
vue2、vue3的区别
vue父子组件加载、更新、销毁的过程
v-for 中 key 的作用
vue2 diff 算法 ,vue3 diff算法
new Vue() 初始化都做了什么事情?
ts中的联合类型有4种类型,如何用Exclude 从联合类型中去除指定的类?
Exclude (意思是排除) ts中可以排除 联合类型 中一部分的内容。它是操作联合类型
的。
使用示例:
type myTypes = 'name' | 'age' | 'height'
type someMyTypes = Exclude<myTypes, 'name'>
const myTypes1: someMyTypes = 'age'
const myTypes2: someMyTypes = 'name' //报错 不能将类型'name'分配给 'age' | 'height'
其实现原理是:
type Exclude<T, U> = T extends U ? never : T
传入两个泛型
- 这里用 myTypes 也就是 ‘name’ | ‘age’ | ‘height’ 去代表 T
- 用 name 属性去代表第二个泛型 U
- T extends U 就判断是否’name’ | ‘age’ | ‘height’ 有 name, 有name就返回never,就代表将其排除
可参考:
Typescript的联合类型、Partial、Pick、Exclude、Omit介绍
TS学习
js 函数重载
JavaScript 不支持函数重载的传统形式,即在同一个作用域中定义多个同名函数但参数不同的情况。在 JavaScript 中,后定义的函数会覆盖之前定义的同名函数。
然而,可以通过一些技巧来模拟函数重载的效果。下面是两种常见的实现方式:
- 使用参数判断:在函数内部使用条件语句根据传入的参数类型或数量执行不同的逻辑。例如:
// 这种方式需要在函数内部进行参数类型或数量的判断,并根据不同的情况执行相应的逻辑。
function example(arg1, arg2) {
if (typeof arg1 === 'string' && typeof arg2 === 'number') {
// 执行某些操作
} else if (typeof arg1 === 'number' && typeof arg2 === 'string') {
// 执行其他操作
}
// ...
}
- 使用对象参数:可以将函数的参数组织为一个对象,根据对象的属性来执行不同的逻辑。例如:
function example(options) {
if (options.type === 'string') {
// 执行某些操作
} else if (options.type === 'number') {
// 执行其他操作
}
// ...
}
// 调用函数时传入不同的选项
example({ type: 'string' });
example({ type: 'number' });
深拷贝实现方法?有循环嵌套的话怎么办?
什么是闭包?
- 函数的嵌套
- 内部函数引用外部函数的局部变量,延长外部函数的变量生命周期
闭包的坏处:
函数内部可以访问函数外部的变量,容易造成内存泄露,因为闭包中的局部变量永远不会被回收。
解决这个问题的办法就是在不使用这些变量时,及时把不需要的局部变量全部删除
浏览器的垃圾回收机制
原型链
当访问某个对象的属性时,会先在这个对象本身属性上查找,如果没有找到,就去它的原型(
_proto_
)去找,即它的构造函数的prototype查找,如果没有找到,就到原型的原型去找(构造函数的prototype._proto_
)。如果直到最顶层的Object.prototype
还是找不到,是null,则返回undefined
。这样一层一层的查找就会形成一个链式结构,这就是原型链。
浏览器缓存?强缓存和协商缓存执行时机
css布局都知道哪些?三栏等高布局如何实现?
flex布局、宫格布局、两栏布局、三栏布局、浮动布局
line-height 和 height 设置一样的值想垂直居中,有没有居中实现不了的时候?
在移动端中使用 line-height=容器高度height 实现文字垂直居中时,在安卓手机会发现文字偏上的问题。
使用 flex
方案 或者 padding
方案较为简单与合理。
三面
vue2、vue3的区别
router全局路由前置和后置守卫分别做什么事情?
es6新属性有哪些?
Set 和 Map的区别、使用场景
- Map 是键值对,Set 是值的集合,当然键和值可以是
任何的值
。- Map 可以通过
get
方法获取值,而 Set 不能,因为它只有值。- 都能通过迭代器进行
for...of
遍历。Set
的值是唯一的可以做数组去重
,Map
由于没有格式限制,可以做数据存储
。- Map 以键值对的形式存储,key=value组成pair,是一组映射关系。Set只有值,并且Set中的元素不可以重复且自动排序。
for…of 用法
- for of 用于遍历可迭代对象,如数组、字符串、Set、Map等;不会遍历原型链。
- for of 遍历获取的是对象的键值,没有索引。
- 普通对象用 for of 遍历会报错。如果是类数组对象的话可以用Array.from() 转成数组再遍历。
箭头函数和普通函数的区别
改变this指向的方法有哪些?
- 使用 ES6 的箭头函数
- 在函数内部使用 _this = this
- new 实例化一个对象
- 使用 apply、call、bind
apply和call的区别
js实现异步的方式有哪些?
定时器setTimeout (setInterval),回调函数,Promise.then,async await
promise 和 async await 的区别
promise.all 和 promise.race 的区别
基于promise写一个原生ajax请求
使用 Promise 来实现 AJAX 请求,可以利用 XMLHttpRequest
对象并返回一个 Promise
对象。以下是一个使用 Promise 实现 AJAX 的示例:
function ajaxRequest(method, url, data) {
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
//当请求状态为 4(表示请求完成)时,如果状态码为 200,则调用 resolve 方法并传递响应数据作为参数;
//否则,调用 reject 方法并传递状态文本作为参数。
if (xhr.readyState === 4) {
if (xhr.status === 200) {
resolve(xhr.responseText);
} else {
reject(xhr.statusText);
}
}
};
xhr.open(method, url, true); //使用 open() 方法打开与指定 URL 的连接。第三个参数为 true 表示异步请求。
if (method === 'POST') {
xhr.setRequestHeader('Content-type', 'application/json');
}
xhr.send(JSON.stringify(data));
});
}
var data = {
name: 'John',
age: 25
};
ajaxRequest('POST', 'http://example.com/api', data)
.then(function(response) {
console.log(response);
})
.catch(function(error) {
console.error(error);
});
实现图片先模糊再显示高清图
两个图片链接??
web前端图片加载优化,从图片模糊到清晰的实现过程
移动端 1px 问题如何解决
通过 伪元素
+ transform
的手段
CSS 移动端 1px(线条/边框) 解决方案
如何进行移动端适配
vue中使用amfe-flexible和postcss-pxtorem结合实现移动端适配方案
css 样式在客户端上不同机型不兼容,如何处理?
同该公司二面问题
css在ios和安卓上的兼容问题(持续更新)
G公司
后台系统如何实现页面权限控制?给不同角色分派不同的页面权限
页面按钮权限如何管理?
全局自定义指令
if (!hasPermission) {
el.parentNode.removeChild(el);
}
uniapp如何使用分包,减少主包体积??还知道其他减少主包体积的方法吗?
如何使用分包?
uniapp通过在pages.json
中配置 subpackages
字段声明项目分包结构:
{
"pages":[
"pages/index",
"pages/logs"
],
"subpackages": [
{
"root": "packageA",
"pages": [
"pages/cat",
"pages/dog"
]
}, {
"root": "packageB",
"name": "pack2",
"pages": [
"pages/apple",
"pages/banana"
]
}
]
}
对小程序进行分包的好处主要有以下两点:
- 可以优化小程序首次启动的下载时间
- 在多团队共同开发时可以更好的解耦协作
微信小程序每个分包的大小是2M,总体积一共不能超过20M。
减少主包体积的方法
1) 合理使用分包加载。按照功能划分分包。
2)避免非必要的全局自定义组件和插件。 当主包里存在一些JS文件/插件只会被分包使用(而主包自己不使用)时,我们建议把这些JS文件/插件从主包中拆分出去,放到对应的分包里,从而优化主包的加载速度。
3)控制代码包内的资源文件。比如图片等静态资源放到服务器上,小程序引用服务器url文件地址(图片较多的话会节约大量的体积)。项目仅保留了底部导航的icon。
4)及时清理无用代码和资源。建议使用微信开发者工具提供的「代码静态依赖分析」,不定期地分析代码包的文件构成和依赖关系,以此优化代码包大小和内容。
5)在微信开发者工具「详情」-「本地设置」中开启「上传代码时自动压缩脚本文件」的设置
6)Hbuilder运行时勾选压缩代码;开发者工具里勾选上代码时压缩样式,js,html
写一个方法 使localStorage 和 sessionStorage实现序列化和反序列化
- localStorage 和 sessionStorage只能存储
字符串
。非字符串数据在存储之前会自动转换为字符串。注意这种转换不能在获取数据时撤销。 对象
和数组
必须转换为string
进行存储。否则会导致保存的数据异常[object Object]
;JSON.parse()
和JSON.stringify()
方法可以将数组、对象等值类型转换为字符串类型,从而存储到 Storage 中;
封装序列化、反序列化方法:
var defaultSerializer = {
serialize: function (item) {
return JSON.stringify(item);
},
deserialize: function (data) {
return data && JSON.parse(data);
}
};
使用:
const user = { "username": "name" };
// user 存储时,先使用 JSON 序列化,否则保存的是[object Object]
localStorage.setItem("user", defaultSerializer.serialize(user));
sessionStorage.setItem("user", defaultSerializer.serialize(user))
// 获取到的数据为string,使用时反序列化数据
const userLocal = defaultSerializer.deserialize(localStorage.getItem("user"));
const userSession = defaultSerializer.deserialize(sessionStorage.getItem("user"))
系统埋点
系统埋点一般就是在某个步骤,上传数据到服务端,服务端给存起来。比如用户登陆就把用户id传过去,用户点击购买按钮就也上传一次,用户付款就上传一次,本质上还是调接口
spa单页面首次加载慢优化方法
重点:关于性能优化,定位问题比解决问题更困难。
指标:测量FP、FCP、LCP、TTI、CLS等衡量指标。本地用 lighthouse, 线上用 Web Vitals。
工具:webpagetest.org
优化方法:
- 路由懒加载
- UI框架按需加载
- 图片资源的压缩
- 组件重复打包(提取页面公共资源)/ 使用externals和CDN
- 开启GZip压缩
- Nginx 开启 gzip
- 使用SSR
路由懒加载
以函数
的形式加载路由,这样就可以把各自的路由文件分别打包,只有在解析给定的路由时,才会加载路由组件。
静态资源本地缓存
后端返回资源问题:
- 采用HTTP缓存,设置Cache-Control,Last-Modified,Etag等响应头
- 采用Service Worker离线缓存
- 前端合理利用localStorage
图片资源的压缩
- 图片压缩网站
- 使用image-webpack-loader
组件重复打包(提取页面公共资源、去重和分离)
假设A.js文件是一个常用的库,现在有多个路由使用了A.js文件,这就造成了重复下载。
解决方案:基础包分离
- 使用 html-webpack-externals-plugin,将
基础包
通过CDN
引入,不打入 bundle 中。 - 使用
SplitChunksPlugin
进行(公共脚本、基础包、页面公共文件)分离(Webpack4内置) ,替代了 CommonsChunkPlugin 插件。 - webpack 3 引入通过CommonsChunkPlugin 配置方法
minChunks: 3
minChunks为3表示会把使用3次及以上的包抽离出来,放进公共依赖文件,避免了重复加载组件
- webpack内部完成分包依赖的是
SplitChunksPlugin
来实现的,可以在官方文档中看见过去是使用CommonsChunkPlugin来实现的分包,从v4版本后就换成了SplitChunksPlugin。所以自动分包的策略实际上是对配置文件webpack.config.js中optimization.splitChunks
配置项的修改。可参考 分包王!🧨优化项目体积减少20%! - SplitChunksPlugin主要作用是提取公共代码,防止代码被重复打包,拆分过大的js文件,合并零散的js文件
- 注意:有时资源拆分的过细也不好,可能会造成浏览器 http 请求的增多
原理如图
开启GZip压缩
拆完包之后,我们再用gzip做一下压缩 安装 compression-webpack-plugin
在 vue.config.js 中引入并修改 webpack 配置
const CompressionPlugin = require('compression-webpack-plugin')
configureWebpack: (config) => {
if (process.env.NODE_ENV === 'production') {
// 为生产环境修改配置...
config.mode = 'production'
return {
plugins: [new CompressionPlugin({
test: /\.js$|\.html$|\.css/, //匹配文件名
threshold: 10240, //对超过10k的数据进行压缩
deleteOriginalAssets: false //是否删除原文件
})]
}
}
Nginx 开启 gzip
nginx安装目录 -> conf -> nginx.conf
nginx.conf文件添加如下gzip配置
http {
gzip on;
gzip_static on;
gzip_buffers 4 16k;
gzip_comp_level 5;
gzip_types text/plain application/javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg
image/gif image/png;
}
可参考:
vue打包优化
SPA首屏加载速度慢的怎么解决?
vue3相比vue2的升级
从框架层面:
- 响应式系统的重新配置,使用
Proxy
代替Object.defineProperty
- 静态元素提升
- 虚拟节点静态标记
- 打包体积优化
从API层面:
- 新增组合API,更好的逻辑重用和代码组织
- 支持多个根节点
从其它层面:
- typeScript支持
- v-if和v-for的优先级
- 生命周期变化
- ssr渲染性能提升
- 自定义指令生命周期名称不同
前端项目部署CI/CD有了解吗?
混合h5开发和客户端如何交互的?
方式一:使用常规UA的方式。
- 判断是安卓端还是ios端
var u = navigator.userAgent
var isAndroid = u.indexOf('Android') > -1 || u.indexOf('Linux') > -1
var isIOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/)
- 给客户端(安卓,ios)传参,并调用其方法
try {
if (isIOS) {
window.webkit.messageHandlers.closeWebview.postMessage({})
} else if (isAndroid) {
window.android.closeWebview()
}
} catch (error) {
console.log(error)
}
看你封装了图片上传组件,使用 Canvas 压缩图片并上传阿里云 OSS,如何封装的?
看你二次封装富文本编辑器,二次封装都做了什么处理?用的什么富文本编辑器?
axios配置过程
vue项目的webpack如何配置压缩代码体积的?uniapp内部是如何实现代码压缩体积的?
webpack压缩代码体积方式
- JS代码压缩(terser-webpack-plugin)
- CSS代码压缩(css-minimizer-webpack-plugin)
- Html文件代码压缩(HtmlwebpackPlugin)
- 文件大小压缩,减少http传输过程中宽带的损耗(compression-webpack-plugin)
- 图片压缩
- Tree Shaking
- 代码分离(splitChunksPlugin)
- 内联 chunk
关于webpack对前端性能的优化,可以通过文件体积大小入手,其次还可通过分包的形式、减少http请求次数等方式,实现对前端性能的优化
关于 Tree Shaking
Tree shaking 的作用:消除无用的 JS 代码,减少代码体积
。
举个例子
// util.js
export function targetType(target) {
return Object.prototype.toString.call(target).slice(8, -1).toLowerCase();
}
export function deepClone(target) {
return JSON.parse(JSON.stringify(target));
}
项目中只使用了 targetType 方法,但未使用 deepClone 方法,项目打包后,deepClone 方法不会被打包到项目里
tree-shaking 原理:
依赖于ES6的模块特性,ES6模块依赖关系是确定的,和运行时的状态无关,可以进行可靠的静态分析,这就是 tree-shaking 的基础
静态分析就是不需要执行代码,就可以从字面量上对代码进行分析。ES6之前的模块化,比如 CommonJS 是动态加载,只有执行后才知道引用的什么模块,就不能通过静态分析去做优化,正是基于这个基础上,才使得 tree-shaking 成为可能
Tree shaking 并不是万能的
并不是说所有无用的代码都可以被消除,还是上面的代码,换个写法 tree-shaking 就失效了。
// util.js
export default {
targetType(target) {
return Object.prototype.toString.call(target).slice(8, -1).toLowerCase();
},
deepClone(target) {
return JSON.parse(JSON.stringify(target));
}
};
// 引入并使用
import util from '../util';
util.targetType(null)
同样的,项目中只使用了 targetType 方法,未使用 deepClone 方法,项目打包后,deepClone 方法还是被打包到项目里
在 dist 文件中搜索 deepClone 方法:
究其原因,export default 导出的是一个对象,无法通过静态分析判断出一个对象的哪些变量未被使用,所以 tree-shaking 只对使用 export 导出的变量生效。
这也是函数式编程越来越火的原因,因为可以很好利用 tree-shaking 精简项目的体积,也是 vue3 全面拥抱了函数式编程的原因之一
有一个数组,比如 let arr = [1,2,3],修改 arr[1] = 4,数据更新了,但是dom还没更新,如何解决?
回答:this.$set
我们知道Vue2 是不可以通过数组下标对响应式数组进行设置和读取的,这样并不会触发视图的更新。这是因为Vue无法侦测到对数组元素的直接修改。
有两种方式实现:
1)使用this.$set
方法:Vue提供了this.$set
来更新数组。例如,this.$set(arr, 1, 4)
,这样就会触发视图的更新。
2)使用数组的变异方法:Vue重写了一些数组的变异方法,如 push()、pop()、shift()、unshift()、splice()、sort() 和 reverse(),这些方法会触发视图的更新。所以,可以使用arr.splice(1,1,4)
方法来修改数组,而不会导致视图不更新。
$set内部实现原理
- 如果目标是
数组
,使用Vue中数组的splice()
变异方法来更新指定位置的元素并触发响应式更新。- 如果目标对象已经包含了指定的属性,即为响应式,直接赋值。
- 如果目标对象没有指定的属性,即新添加的属性不是响应式,Vue会通过
defineProperty
方法进行响应式
处理,并在新的属性上设置getter
和setter
,以便在属性被访问或修改时触发响应式更新。
js基本数据类型和引用类型分别有哪些?区别是什么?
基本数据类型:
String、Number、Boolean、Undefined、Null、Symbol(符号,es6 新增,表示独一无二的值)和 BigInt(大整数,es10 新增)
引用数据类型:
包含Object、Array、Date、 function 等
区别:
基本类型
: 基本类型的数据通常存储在栈
上。占据空间小
、大小固定
。 例如整数、浮点数。指针等引用类型
: 引用类型的数据(对象、数组、链表等)的实际数据存储在堆
上,而变量本身存储在栈上,变量中存储的是指向堆内存中实际数据的引用。 引用数据类型存储在堆内存中,因为引用数据类型占据空间大
、大小不固定
。
js事件循环机制
了解闭包、内存泄漏、垃圾回收机制吗?
闭包的使用场景
如何防止内存泄漏?
防抖、节流使用场景
vue父子组件加载过程
created和mounted生命周期的区别
v-for 中 key 的作用
vue2 diff 算法
new Vue() 初始化都做了什么事情?
- new Vue的时候调用会调用
Vue.prototype._init
方法
定义 $set
、$get
、$delete
、$watch
等方法
定义 $on
、$once
、$off
、$emit
等事件
定义 _update
、$forceUpdate
、$destroy
生命周期
-
调用
$mount
进行页面的挂载 -
挂载的时候主要是通过
mountComponent
方法 -
定义
updateComponent
更新函数 -
执行
render
生成虚拟DOM -
_update将虚拟DOM生成真实DOM结构,并且渲染到页面中
computed和watch有什么区别?
vue的$nextTick解决了什么问题?内部是如何实现的?
为什么vue中的data是一个 function?
因为组件是用来复用的,且JS里对象是引用关系,如果组件中data是一个对象,那么这样作用域没有隔离,子组件中的data属性值会相互影响,如果组件中data选项是一个函数,那么每个实例可以维护一份被返回对象的独立拷贝,组件实例之间的data属性值不会互相影响;而 new Vue 的实例,是不会被复用的,因此不存在引用对象的问题。
平时通过什么途径了解新技术?
技术博客、微信公众号、买的课程、书本、github优秀项目
有了新技术如何向团队推进,用到公司项目中?
- 先自己学习新技术,达到入门级以上水平。
- 看看新技术能否引入项目中,本地新拉一个分支,用新技术写一下。
- 分析现有项目的难点、痛点,看新技术能否解决这些问题以及新技术能带来多大收益。
- 组织团队内分享,普及新技术以及新技术带来的好处。
另外服务端渲染了解下
H公司
router路由的使用,路由导航守卫有哪些?
全局路由守卫、路由独享的守卫、组件内的守卫
路由中的meta字段用过没,它的使用场景?
将任意信息附加到路由上,如过渡名称
、谁可以访问路由
等。这些事情可以通过接收属性对象的meta
属性来实现,并且它可以在路由地址
和导航守卫
上都被访问到。定义路由的时候你可以这样配置 meta 字段:
const routes = [
{
path: '/posts',
component: PostsLayout,
children: [
{
path: 'new',
component: PostsNew,
// 只有经过身份验证的用户才能创建帖子
meta: { requiresAuth: true },
},
{
path: ':id',
component: PostsDetail
// 任何人都可以阅读文章
meta: { requiresAuth: false },
}
]
}
]
- 一个路由匹配到的所有路由记录会暴露为
$route
对象(还有在导航守卫中的路由对象)的$route.matched
数组。我们需要遍历
这个数组来检查路由记录中的meta
字段;
export function getRoute(route: RouteLocationNormalized): RouteLocationNormalized {
if (!route) return route
const { matched, ...opt } = route
return {
...opt,
matched: (matched
? matched.map((item) => ({
meta: item.meta,
name: item.name,
path: item.path
}))
: undefined) as RouteRecordNormalized[]
}
- 但是 Vue Router 还为你提供了一个
$route.meta
方法,它是一个非递归合并所有 meta 字段(从父字段到子字段)的方法。这意味着你可以简单地写
router.beforeEach((to, from) => {
// 而不是去检查每条路由记录
// to.matched.some(record => record.meta.requiresAuth)
if (to.meta.requiresAuth && !auth.isLoggedIn()) {
// 此路由需要授权,请检查是否已登录
// 如果没有,则重定向到登录页面
return {
path: '/login',
// 保存我们所在的位置,以便以后再来
query: { redirect: to.fullPath },
}
}
})
谈谈对this对象的理解
apply、call、bind的使用区别
var year = 2023
function getDate (month, day) {
return this.year + '-' + month + '-' + day
}
let obj = { year: 1998 }
getDate.call(null, 3, 8) // 2023-3-8
getDate.call(obj, 3, 8) // 1998-3-8
getDate.apply(obj, [6, 8]) // 1998-6-8
getDate.bind(obj,3,8)() // 1998-3-8
// 或者
let boundGetDate = getDate.bind(obj)
boundGetDate(3,8) // 1998-3-8
// 可以看出
// call和apply区别是 参数的方式, apply使用数组传递参数,call是按顺序传递参数。
// bind 传参也是按顺序传递参数,但是bind()方法后还有个(),说明bind方法返回的是一个函数,并不执行,需要手动去调用才执行
相同点:
- 都可以改变函数内部 this 的指向。
- 三者第一个参数都是 this 要指向的对象,也就是想指定的上下文。
- 都可以利用
后续参数
传参。
区别:
- 后续参数的传递:apply使用
数组
传递参数;call和bind按顺序传递
参数。 - apply、call都是对函数的直接调用;bind方法返回的仍是一个
函数
(this指向已经变化的函数),需要手动调用。## proto 和 prototype 的区别
使用 apply、call 和 bind 要注意以下几点:
1)只能在
函数
上使用,对于其他类型的值调用这些方法会抛出 TypeError 异常;
2)改变函数执行上下文可能会造成一些意外的后果,例如访问未定义变量、调用非法函数等;
3)使用 apply、call 和 bind 应该避免滥用,只在确有必要的情况下使用。过多使用这些方法可能会导致代码难以理解和维护,甚至降低程序性能。
通常来说,apply 和 call 常用于快速处理函数参数为数组或固定长度时,而 bind 则更适合用于需要重复使用同一个上下文的场景,例如事件处理器等。
promise的用法
- Promise 是异步操作的一种解决方案,一般用来解决层层嵌套的回调函数(回调地狱)的问题。Promise 解决的是
异步编码风格
的问题,而不是一些其他的问题。 - Promise 是一个
构造函数
,接受一个函数
作为参数,返回一个Promise
实例,因此可以对返回值进行.then()
调用。 - 每一个
.then()/.catch()
方法返回的是一个新生成的Promise
对象,这个对象可被用作链式调用
。即then()
方法后面再调用另一个then()
方法。例子见下文。 - Promise 本身是同步的立即执行函数,当执行
resolve/reject
的时候,此时是异步操作,会先执行then/catch
等,当主线程执行完成后,才会去调用 resolve/reject 中存放的 方法执行。 - Promise对象的错误具有“
冒泡
”性质,会一直向后传递,直到被捕获为止。例子见下文。
一个promise实例有三种状态: pending
、fulfilled
、rejected
。分别代表进行中、已成功和已失败。初始值是pending,实例的状态只能由 pending 转变 fulfilled
或者rejected
。状态一旦改变,就不会再改变了。
当Promise启动后,
满足成功的条件时我们让状态从等待变成成功
满足失败的条件时我们让状态从等待变成失败
应用场景:
1、解决回调地狱 (通过.then .catch方法,处理ajax返回的结果,用链式调用的方式解决回调地狱)
2、将异步操作队列化,按照期望的顺序执行,返回符合预期的结果(.then(1).then(2).then(3) 顺序执行)
手写实现Promise使用:
//拿ajax的例子来做一下Promise封装
function myAjax(url) {
return new Promise((resolve, reject) => {
$.ajax({
url: url,
success: function(data) {
//成功则返回data数据
resolve(data)
},
error: function(err) {
//失败则返回错误信息err
reject(err)
}
});
})
}
/*来来来,看好了,
封装好ajax之后我们就可以去进行请求数据了*/
let res1 = myAjax('xxxx/api/xxxx');
let res2 = res1.then(myAjax('xxxx/api/xxxx'));
let res3 = res2.then(myAjax('xxxx/api/xxxxx'));
当要写有顺序的异步事件时,需要串行时,可这样写:
then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。
let promise =new Promise((resolve, reject) =>{
ajax('first').success(function(res){
resolve(res)
})
})
promise.then(res =>{
return new Promise((resolve, reject) =>{
ajax('second').success(function(res){
resolve(res)
})
})
}).then(res =>{
return new Promise((resolve, reject) =>{
ajax('third').success(function(res){
resolve(res)
})
})
}).then(res =>{
})
Promise对象的错误具有“冒泡
”性质。
getJSON('/post/1.json').then(function(post) {
return getJSON(post.commentURL);
}).then(function(comments) {
// some code
}).catch(function(error) {
// 处理前面三个Promise产生的错误
});
缺点:
- 无法取消Promise,一旦新建它就会立即执行,无法中途取消。
- 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。
- 当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
可参考:(前端必会)理解异步和Promise的使用
js promise看这篇就够了
promise.catch 换成 try catch 捕获错误可以吗?
不可以。
- try/catch 只能捕获 async/await 中的异步操作的错误,而不能直接捕获普通的 Promise 错误。
catch()
方法是.then(null, rejection)
或.then(undefined, rejection)
的别名,本质是catch 原本就是 then 方法的语法糖。用于指定发生错误时的回调函数。- Promise对象的错误具有“
冒泡
”性质,会一直向后传递,直到被catch捕获为止。 - 一般来说,使用
catch
方法代替then()
第二个参数。
promise.all 和 promise.race 的区别
答案同A公司
ES6新增的方法有哪些?
proto 和 prototype 的关系
- 区别
prototype
的维度是函数,
而__proto__
的维度是对象
。__proto__
是每个对象都有的属性,我们通常把它称为"隐式原型
",把prototype
称为"显式原型
"。- 另外,函数也是一个对象,所以它既有prototype属性又有__proto__属性。
- 联系
_proto_
就是我们原型链中的连接点
;- 当访问某个对象的属性时,会先在这个对象本身属性上查找,如果没有找到,就去它的原型(
_proto_
)去找,即它的构造函数的prototype查找,如果没有找到,就到原型的原型去找(构造函数的prototype._proto_
)。如果直到最顶层的Object.prototype
还是找不到,是null,则返回undefined
。这样一层一层的查找就会形成一个链式结构,这就是原型链
。
- 其它
另外,通过重写prototype
还可以实现原型链继承。
数组去重的方法
手写js去重
var array=['12','32','89','12','12','78','12','32'];
// 最简单数组去重法
//新建一个空的结果数组,for 循环原数组,判断结果数组是否存在当前元素,
//如果有相同的值则跳过,不相同则push进数组。
function unique1(array){
var n = []; //一个新的临时数组
for(var i = 0; i < array.length; i++){ //遍历当前数组
if (n.indexOf(array[i]) == -1)
n.push(array[i]);
}
return n;
}
arr=unique1(array);
// 速度最快, 占空间最多(空间换时间)
function unique2(array){
var n = {}, r = [], type;
for (var i = 0; i < array.length; i++) {
type = typeof array[i];
if (!n[array[i]]) {
n[array[i]] = [type];
r.push(array[i]);
} else if (n[array[i]].indexOf(type) < 0) {
n[array[i]].push(type);
r.push(array[i]);
}
}
return r;
}
//数组下标判断法
function unique3(array){
var n = [array[0]]; //结果数组
for(var i = 1; i < array.length; i++) { //从第二项开始遍历
if (array.indexOf(array[i]) == i)
n.push(array[i]);
}
return n;
}
es6 Set()
es6方法数组去重,第一种方法
let arr = [1,2,3,4,2,1,2,3,4,12,3,1,2,31,1]
let s = [...new Set(arr)];
es6方法数组去重,第二种方法
function dedupe(array) {
return Array.from(new Set(array)); //Array.from()能把set结构转换为数组
}
使用 filter 方法
// 参数self表示(调用 filter 方法的数组本身)
// self.indexOf(value) 可以得到当前元素在数组中第一次出现的索引,当它与当前索引 index 相等时,表示当前元素是第一次出现,保留;
// 否则,表示当前元素已经在之前出现过,进行过滤。
//filter 方法,最终返回一个新的数组,新数组中只包含满足条件的元素,即去除了重复的元素。
const arr = [1, 2, 3, 3, 4, 4, 5];
const uniqueArr = arr.filter((value, index, self) => {
return self.indexOf(value) === index;
});
console.log(uniqueArr); // [1, 2, 3, 4, 5]
使用 reduce 方法
// prev 参数表示累加器(初始值为一个空数组),cur 参数表示当前元素。
// 通过判断当前元素 cur 是否已经存在于累加器 prev 中,如果不存在,则将其添加到累加器中。
// 这里利用了 includes 方法来检查元素是否已经存在于数组中。
const arr = [1, 2, 3, 3, 4, 4, 5];
const uniqueArr = arr.reduce((prev, cur) => {
if (!prev.includes(cur)) {
prev.push(cur);
}
return prev;
}, []);
console.log(uniqueArr); // [1, 2, 3, 4, 5]
使用 map 方法
// 创建一个空的 Map 对象 map,用于存储已经出现过的元素。然后,通过 forEach 方法遍历原数组 arr 的每个元素 item。
// 在遍历过程中,使用 map.has(item) 来检查 Map 中是否已经存在该元素。如果不存在,则说明该元素是第一次出现,将其作为键存储到 Map 中,并将其添加到新数组 uniqueArr 中
const arr = [1, 2, 3, 3, 4, 4, 5];
const map = new Map();
const uniqueArr = [];
arr.forEach(item => {
if (!map.has(item)) {
map.set(item, true);
uniqueArr.push(item);
}
});
console.log(uniqueArr); // [1, 2, 3, 4, 5]
Proxy和Reflect的使用
什么是Proxy?
- Proxy 是ES6中的方法,并不是所有的浏览器都支持(比如IE11)。
- Proxy 用于创建一个 目标对象 的代理,在对 目标对象 的操作之前提供了拦截,可以对外界的操作进行 过滤 和 改写。这样我们可以不直接操作目标对象,而是通过操作对象的
代理对象
来间接操作对象。- Proxy 直接代理整个目标对象,并且返回一个新的
Proxy
对象。- Proxy 支持的拦截操作一共 13 种。get、set、has、deleteProperty、getPrototypeOf、setPrototypeOf、apply等
var proxy = new Proxy(target, handler);
//new Proxy()表示生成一个Proxy实例,target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。
var obj = new Proxy({}, {
get: function (target, propKey, receiver) {
console.log(`getting ${propKey}!`);
return Reflect.get(target, propKey, receiver);
},
set: function (target, propKey, value, receiver) {
console.log(`setting ${propKey}!`);
return Reflect.set(target, propKey, value, receiver);
}
});
Reflect
- Reflect对象与Proxy对象一样,也是 ES6 为了
操作对象
而提供的新 API。Reflect对象的设计目的有这样几个。- Proxy 的一些拦截方法要求返回
true/false
来表示操作是否成功,比如set
、deleteProperty
等方法,这也和Reflect
对象的静态方法相对应。- 现阶段,某些方法同时在
Object
和Reflect
对象上部署,未来的新方法将只部署在Reflect
对象上。也就是说,从Reflect
对象上可以拿到语言内部
的方法。- 让Object操作都变成
函数
行为。某些Object操作是命令式,比如name in obj
和delete obj[name]
,而Reflect.has(obj, name)
和Reflect.deleteProperty(obj, name)
让它们变成了函数行为。- 另外,使用
Reflect
可以修正 Proxy 的this
指向问题;因为 目标对象内部的this
关键字会指向 Proxy 的代理对象
,而不是目标对象。。
vue2跳转路由传参和获取参数的方法,query和params传参的区别
使用
不带参数
this.$router.push('/home')
this.$router.push({name:'home'})
this.$router.push({path:'/home'})
query传参
this.$router.push({path:'/home',query: {id:'1'}})
this.$router.push({name:'/home',query: {id:'1'}})
// html 取参 $route.query.id
// script 取参 this.$route.query.id
params传参
this.$router.push({name:'home',params: {id:'1'}}) // 只能用 name
// 路由配置 path: "/home/:id" 或者 path: "/home:id" ,
// 不配置path ,第一次可请求,刷新页面id会消失
// 配置path,刷新页面id会保留
// html 取参 $route.params.id
// script 取参 this.$route.params.id
传参区别和总结
- query 传参配置的是
path
,而 params 传参配置的是name
,且在params
中配置path
无效。- query传参 参数会显示在地址栏中,页面 url后面会拼接参数,类似?id=1, 非重要性的可以这样传, 密码之类还是用
params
。- params使用动态传参,如果在router的
path
里面加上/:id
,地址栏会显示url/id,如果router里面不加,地址栏不会显示参数,刷新参数会消失,可以传比较重要只能使用一次的参数。