1. webpack与浏览器
1.兼容性和优化总结
- 兼容性:
不同浏览器的默认样式存在差异,可以使用 Normalize.css
抹平这些差异。
- 优化:
请求优化:尽量减少请求,开启Gzi压缩合并cssjs文件(需要下载这个包 compression-webpack-plugin,并在config文件配置)。
页面优化:减少DOM访问,减少重绘和重排、节流防抖、路由懒加载、使用字体矢量图标、按需引入组件。
图片懒加载vue-lazyload 或者elementui自带lazy属性。npm run build --report
后生成分析报告
使用cdn,图片和css文件都交给cdn服务器,浏览器访问index.html时从cdn服务器返回。
不使用iframe gif ,用css代替js动画,小图标使用base64格式,html中不能有空的src herf会阻塞下载进程,减少全局变量。
使用link标签 代替@import引入css文件,因为link可以并行下载css文件不会停止对当前文档处理。
2.LocalStorage sessionStorage cookie区别
共同:都是保存在浏览器、而且同源
区别:
存放地址不同:cookie在http路径中携带,而其他两个是存在浏览器中的,不会发给后端。
大小不同:cookie只能存4K 其他两个5M。
有效期不同:sesstionStorage在浏览器关闭之前有效,cookie在其设置的有效期内有效,localStorage长期有效。
作用域不同:sessionStorage在不同浏览器窗口不能共享,即使是同一个页面;localstotage、cookie在同源窗口是共享的。
3.浏览器渲染过程
解析HTML解构,构建DOM树,并请求css js img。
css文件下载完成构建css树
然后dom树和css树合成rendertree(渲染树)
布局:计算出每个节点在屏幕的位置,通过显卡把页面显示到屏幕上。
4.一个页面从输入URL到页面加载完成都发生了什么?
1.浏览器查找域名对应的ip地址(先从浏览器缓存找、系统缓存、路由器缓存、DNS缓存)
2.浏览器向web服务器发送一个http请求 三次握手
3.服务器301重定向,浏览器跟踪重定向地址并请求
4.服务器处理请求结果,返回http相应
5.浏览器渲染dom树,发送请求获取cssimgjs等资源,最后渲染页面。
2.vue相关
1.优势:
轻量级框架、简单易学、组件化开发、双向绑定,数据结构分离、虚拟dom、diff算法,单页面应用跳转路由不会刷新页面,第三方ui组件库多。
2.mvvm原理:
vue.js是采用数据劫持结合发布者-订阅模式的方法,通过Object.defineProperty()劫持每个属性的getter和setter,在数据变化时发布消息给订阅者,触发相应的监听回调。
- 需要observe的数据对象进行递归遍历,包括子属性的对象,都加上getter和setter,这样某个变化就会触发setter,监听到数据变化。
- compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面试图,并将每个指定对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据变化收到通知更新视图。
- watcher订阅者是observe和compile之间通信的桥梁,主要做:
在自身实例化时往属性订阅器(dep)添加自身
自身有个updata()方法
属性变动dep.notice()通知时,调用自身update()方法,并触发compolie中绑定的回调。
3.生命周期
创建vue实例newVue()初始化-> beforeCreate() -> 数据侦听、配置事件/侦听器 -> created() ->watcher、computed()、metheds、事件/侦听器的回调函数都准备完毕 -> beforeMount() -> 相关的 render
函数首次被调用,虚拟dom完成 -> mounted() -> 实力挂载完成dom渲染完成。
在数据发生改变后 -> beforeUpdate() -> 虚拟 DOM 重新渲染和更新完毕 -> updated()。
activated()被 keep-alive 缓存的组件激活时调用,deactivated()被 keep-alive 缓存的组件失活时调用。
beforeDestroy()-> 实力销毁 -> destroyed -> 所有指令解绑,事件监听器移除,子实例被销毁
errorCaptured() 后代组件的错误时被调用。
4.为什么避免v-for和v-if连用
v-for比v-if有更高的优先级,会造成不必要的浪费,解决方法:可以用计算属性,或者把v-if放在父元素上,子元素标签里写v-for
5.computed和watch的区别
coputed:依赖于data()中的值,而且只有依赖的数据发生改变才会重新计算。而且需要返回值,支持缓存,不支持异步操作。定义的计算属性不需要写在data()中。适用于:一个数据受多个数据影响。
watch:支持异步操作 也依赖data()中的某个数据。适用于:一个数据影响多个数据
6.vuex
五大核心:
state:存放集中状态。
mutations:同步修改state,类似事件
action:异步提交mutations
getter:state的计算属性,第一个属性必须时state
module:分割模块 每个模块有自己的四大属性。需要开启命名空间namespaced: true
第一步在src目录下的store文件夹里创建read子文件夹创建store.js文件,如下
const state = {
bigPages: '',
}
const getters = {
getBigPages(state) {
return state.bigPages //getter一定要把state作为参数传进去 而且有返回值
}
}
const mutations = {
setBigPages(state, str) { //mutations第一个参数是state,第二个参数是传入的值
state.bigPages = str
}
}
const actions = {
changeBigPages(context, str) { // 异步action第一个参数是context,第二个是传入的值
context.commit('setBigPages', str) // 通过context的commit方法改变值
}
}
export default {
namespaced: true,//命名空间
state,
getters,
mutations,
actions,
}
第二步:在store文件夹下创建store.js
import Vue from 'vue' //引入vue
import Vuex from 'vuex' // 引入vuex
import read from './read/store' // 引入模块化vuex
Vue.use(Vuex) //使用vuex
const state = {
requestUrl: '/api',
token: localStorage.getItem('ACCESS_TOKEN'),
}
const getters = {
getRequestUrl() {
return state.requestUrl
},
getToken(state) {
return state.token
}
}
const mutations = {
setRequestUrl(state, str) {
state.requestUrl= str
},
}
const actions = {
changeRequestUrl(context,str){
context.commit('setRequestUrl',str)
}
}
const store = new Vuex.Store({
state,
getters,
mutations,
actions,
modules: {
read //这里一定要引入模块
},
})
export default store
第三步:入口文件一定要引入store,在vue中注入store。这样全局this.$store可以调用
第四步:取值和使用
- state
方法一:this.$store.read.bigPages
方法二:从vuex引入mapStates,在计算属性中加上路径读取
import {mapState} from 'vuex'
computed:{
...mapState({
bigPages(state) {
return state.read.bigPages
},
cart(state){
return state.cart.cart;
}
})
}
- mutations
方法一:this.$store.commit('read/setBigPages','参数')
方法二:辅助函数mapMutations
import {mapMutations} from 'vuex'
methods:{
...mapMutations({
setBigPages:'read/setBigPages',
})
}
mapActions 和 mapGetter方法一样。第一种方法调用action,需要this.$store.dispatch()
7.vue-router及相关
更新视图但不刷新页面是vue路由的核心之一
1.跳转方式
编程式:
this.$router.push({path:'',query:{key:value}})
this.$router.push({name:'',params:{key:value}}) 页面刷新params获取不到了
this.$router.replace()
this.$router.go(n) 向前或向后跳转n个页面,前后由n是正数还是负数决定。
声明式导航:
<router-link :to={path:'',quert:{key:value}}></router-link>
2.$route和$router的区别
$route是当前路由信息对象 存放着path params hash query fullPath name matched等信息。
$router是VueRouter的实例,是全局路由对象 push replace go等方法都在里面。
3. 路由拦截器
全局前置:beforeEach
全局解析:beforeResolve
全局后置:afterEach
路由独享守卫:beforeEnter
组件内守卫:beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave
流程:
- 导航被激活
- 在失活的组件里调用beforeRouteLeave守卫
- 调用全局beforeEach守卫
- 重用的组件里调用beforeRouteUpdate守卫
- 调用独享守卫beforeEnter
- 解析异步路由组件
- 在激活的组件里调用beforeRouteEnter
- 调用全局的beforeResolve全局解析首位
- 导航被确认
- 调用全局后置守卫afterEach
- 触发DOM更新
- 调用beforeRouteEnter守卫中传给next回调函数,创建好的组件实例会作为回调函数的参数传入。
8.Vue3性能提升表现在哪些方面?
1、编译阶段优化
① diff算法优化
vue3
在diff
算法中相比vue2
增加了静态标记
,其作用是为了会发生变化的地方添加一个flag标记
,下次发生变化的时候直接
找该地方进行比较。
② 静态提升
Vue3中对不参与更新
的元素,会做静态提升,只会被创建一次
,在渲染时直接复用。免去了重复的创建操作,优化内存。
没做静态提升之前,未参与更新的元素也在render函数
内部,会重复创建阶段
。
做了静态提升后,未参与更新的元素,被放置在render 函数外
,每次渲染的时候只要取出
即可。同时该元素会被打上静态标记值为-1
,特殊标志是负整数
表示永远不会用于 Diff
。
③ 事件监听缓存
默认情况下绑定事件行为会被视为动态绑定(没开启事件监听器缓存
),所以每次
都会去追踪它的变化。开启事件侦听器缓存
后,没有了静态标记。也就是说下次diff算法
的时候直接使用
。
④ SSR优化
当静态内容大到一定量级时候,会用createStaticVNode
方法在客户端去生成一个static node
,这些静态node
,会被直接innerHtml
,就不需要创建对象,然后根据对象渲染。
2、源码体积
相比Vue2
,Vue3
整体体积变小
了,除了移出一些不常用的API
,最重要的是Tree shanking
。
任何一个函数,如ref、reavtived、computed
等,仅仅在用到
的时候才打包
,没用到
的模块都被摇掉
,打包的整体体积变小
。
3、响应式系统
vue2
中采用 defineProperty
来劫持整个对象,然后进行深度遍历所有属性,给每个属性
添加getter和setter
,实现响应式。
vue3
采用proxy
重写了响应式系统,因为proxy
可以对整个对象进行监听
,所以不需要深度遍历。
- 可以监听动态属性的添加
- 可以监听到数组的索引和数组length属性
- 可以监听删除属性
9. setup
最初版本setup表现形式为选项,两个参数props和context,context中又有emit expose attrs slots等属性。
<scrtpt setup>
是语法糖,简化了组合式 API
的写法,并且运行性能更好。使用 script setup
语法糖的特点:
- 属性和方法无需返回,可以直接使用。
- 引入
组件
的时候,会自动注册
,无需通过components
手动注册。 - 使用
defineProps
接收父组件传递的值。 useAttrs
获取属性,useSlots
获取插槽,defineEmits
获取自定义事件。- 默认
不会对外暴露
任何属性,如果有需要可使用defineExpose
。
10.vue项目首次加载慢,如何解决?
Vue和React项目中的优化_import-rewriter插件里的bundlesimportrewriter-优快云博客
11.spa的理解
spa:单页面应用 single page web application
整个应用只有一个完整的页面;
点击页面中的链接不会刷新页面,只会做页面的局部更新;
数据都要通过ajax请求获取,并在前端异步展现
12.watch与watchEffect区别
watch
和 watchEffect
都是监听器,watchEffect
是一个副作用函数。它们之间的区别有:
-
watch
:既要指明监视的数据源,也要指明监视的回调。 -
而
watchEffect
可以自动监听数据源作为依赖。不用指明监视哪个数据,监视的回调中用到哪个数据,那就监视哪个数据。 -
watch
可以访问改变之前和之后
的值,watchEffect
只能获取改变后
的值。 -
watch
运行的时候不会立即执行
,值改变后才会执行,而watchEffect
运行后可立即执行
。这一点可以通过watch
的配置项immediate
改变。 -
watchEffect
有点像computed
:- 但
computed
注重的计算出来的值(回调函数的返回值), 所以必须要写返回值。 - 而
watcheffect
注重的是过程(回调函数的函数体),所以不用写返回值。
- 但
-
watch
与vue2.x
中watch
配置功能一致,但也有两个小坑- 监视
reactive
定义的响应式数据时,oldValue
无法正确获取,强制开启
了深度监视(deep配置失效) - 监视
reactive
定义的响应式数据中某个属性
时(()=>obj.name),deep配置有效
。
- 监视
3.JS 面试题
1 .赋值,浅拷贝与深拷贝
赋值:没有创建新对象,仅仅是拷贝了原对象的指针。
浅拷贝:是创建一个新对象,这个对象仅对原对象的属性进行拷贝,属性值是基本类型时,拷贝的是原数据,属性值是引用类型时,拷贝的是指针。因此,如果原对象的属性有引用类型数据,无论修改新对象还是原对象的引用类型数据,另一个都会随之改变。
深拷贝:也是创建一个新对象,但不仅对原对象的属性进行拷贝,还在堆内存中开辟一个新的地址用来存储新对象,所以新对象和原对象没有关联,修改其中一个另一个不会变化。
方法:
浅拷贝:Object.assign() Array.concat Array.slice() 展开运算符[..., arr]
深拷贝:JSON.parse(JSON.stringfy(data)), 缺点undefined类型和function类型不会被拷贝、不能拷贝循环引用对象。自己手写递归遍历,缺点可能失误写错或者在网上找。lodash里方法cloneDeep()。
2. 防抖与节流
防抖:n秒内重复点击,重新计时,只执行最后一次。
输入框掉接口搜索、 校验onchange事件、窗口大小改变。
function debounce(fn, delay) {
let timer = null;
return function () {
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, arguments);
}, delay);
};
}
节流:n秒内重复点击,只有一次生效。
页面滚动事件、懒加载、防止表单重复提交。
function throttle(fn, delay) {
let valid = true;
return function () {
if (valid) {
valid = false;
setTimeout(() => {
fn.apply(this, arguments);
valid = true;
}, delay);
}
};
}
3. Proxy和Reflect
这俩都可以对 对象或函数进行拦截并执行一些额外操作,比如getter setter函数中打印输出或其他复杂操作。那为什么两个要一起用 用一个不行吗?
原因就是这个proxy中getter setter的第三个参数 reciver(除了get set 其实还有很多方法是两者皆有的比如has deleteProperty ownKeys等等)
let parent = {
get value() {
return "parent";
},
};
let proxy = new Proxy(parent, {
get(target, key, receiver) {
// 这里receiver应该是proxy的 但是变成了child,Proxy局限性,用Reflect解决。
console.log(receiver === proxy);
return target[key];
},
});
let child = { name: "Jerry" };
// 设置 child 继承 代理对象 proxy
Object.setPrototypeOf(child, proxy);
child.value; // false
从这里看出其实receiver就是类似this 上下文,get函数的receiver是Proxy 或者继承 Proxy 的对象。就有了局限性,要解除这个局限性,就要用Reflect 映射:
let parent = {
name: "Tom",
get value() {
return this.name;
},
};
let proxy = new Proxy(parent, {
get(target, key, receiver) {
// 相当于 return target[key].call(receiver)
return Reflect.get(target, key, receiver);
},
});
let child = { name: "小Tom" };
// 设置 child 继承 代理对象 proxy
Object.setPrototypeOf(child, proxy);
console.log(child.value); // 小Tom
注意上面代码中的// 相当于 return target[key].call(receiver)
其实就是this指向问题,如果target
对象中指定了getter
,receiver
则为getter
调用时的this
值。
4.Object.keys不能访问到,obj.b可以访问到?
Object.keys()只能遍历出 除 symbol 类型外的可枚举属性。除此之外 还有values() entirs()等都访问不到symbol类型的数据。
5.事件循环机制
Javascript 有一个 main thread 主线程和 call-stack 调用栈(执行栈),所有的任务都会被放到调用栈等待主线程执行。
- 同步任务、异步任务
JavaScript 单线程中的任务分为同步任务和异步任务。同步任务会在调用栈中按照顺序排队等待主线程执行,异步任务则会在异步有了结果后将注册的回调函数添加到任务队列(消息队列)中等待主线程空闲的时候,也就是栈内被清空的时候,被读取到栈中等待主线程执行。任务队列是先进先出的数据结构。
- Event Loop
调用栈中的同步任务都执行完毕,栈内被清空了,就代表主线程空闲了,这个时候就会去任务队列中按照顺序读取一个任务放入到栈中执行。每次栈内被清空,都会去读取任务队列有没有任务,有就读取执行,一直循环读取-执行的操作,就形成了事件循环。
- 宏任务(macro-task)、微任务(micro-task)
除了广义的同步任务和异步任务,JavaScript 单线程中的任务
可以细分为宏任务和微任务。
macro-task包括:script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering。
micro-task包括:process.nextTick, Promises中的then catch, setState()、Object.observe, MutationObserver。
console.log(1);
setTimeout(function() {
console.log(2);
})
var promise = new Promise(function(resolve, reject) {
console.log(3);
resolve();
})
promise.then(function() {
console.log(4);
})
console.log(5);
//1
//3
//5
//4
//2
- 上面的示例中,第一次事件循环,整段代码作为宏任务进入主线程执行。
- 遇到了 setTimeout ,就会等到过了指定的时间后将回调函数放入到宏任务的任务队列中。
- 遇到 Promise,将 then 函数放入到微任务的任务队列中。
- 整个事件循环完成之后,会去检测微任务的任务队列中是否存在任务,存在就执行。
- 第一次的循环结果打印为: 1,3,5,4。
- 接着再到宏任务的任务队列中按顺序取出一个宏任务到栈中让主线程执行,那么在这次循环中的宏任务就是 setTimeout 注册的回调函数,执行完这个回调函数,发现在这次循环中并不存在微任务,就准备进行下一次事件循环。
- 检测到宏任务队列中已经没有了要执行的任务,那么就结束事件循环。
- 最终的结果就是 1,3,5,4,2。
重点:
- 执行全局Script同步代码,这些同步代码有一些是同步语句,有一些是异步语句(比如setTimeout等);
- 全局Script代码执行完毕后,
执行栈
Stack会清空; - 从
微队列
中取出位于队首的回调任务,放入执行栈
Stack中执行,执行完后微队列
长度减1; - 继续循环取出位于
微队列
的任务,放入执行栈
Stack中执行,以此类推,直到直到把微任务
执行完毕。注意,如果在执行微任务
的过程中,又产生了微任务
,那么会加入到微队列
的末尾,也会在这个周期被调用执行; 微队列
中的所有微任务
都执行完毕,此时微队列
为空队列,执行栈
Stack也为空;- 取出
宏队列
中的任务,放入执行栈
Stack中执行; - 执行完毕后,
执行栈
Stack为空; - 重复第3-7个步骤
例:
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0)
async1();
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
console.log('script end');
/*
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
*/
6. JS中的new
new
运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。
new
关键字会进行如下的操作:
- 创建一个空的简单JavaScript对象(即
{}
); - 为这个空对象新创建的对象添加隐式原型__proto__,将该属性链接至构造函数的原型对象 ;
- 将步骤1新创建的对象作为
this
的上下文 ; - 如果该函数没有返回对象,则返回
this
。
7.原型链
每个实例对象(object)都有一个私有属性隐式原型(称之为 __proto__ )指向它的构造函数的原型对象显示原型(prototype)。该原型对象也有一个自己的原型对象(__proto__),层层向上直到一个对象的原型对象为 null
。
8.promise
<script>
function test(){
return new Promise((res,rej)=>{
setTimeout(()=>{
console.log(1);
res()
},1000)
})
}
function test2(){
return new Promise((res,rej)=>{
setTimeout(()=>{
console.log(5);
res()
},2000)
})
}
async function test1(){
console.log(2);
await test();
await test2();
console.log(3);
}
test1().then(()=>{console.log(4)})
</script>
//2 1 5 3 4
注意:await后面要跟 promise 才能按自己写得顺序执行。
new Promise接受两个回调 一个是resovle 一个是rejected,可以利用使promise在某种if情况下进入resolve(),或reject()。因为promise有三种状态,pending、fulfilled、rejected。resolve()方法对应着fulfilled状态,reject()方法对应rejected状态。
promise还可以链式调用.then() .catch() .all() .race()他们都返回一个promise对象,所以()里写箭头函数保证this指向。
在promise中调用resolve()方法就会进入.then()第一个回调, 调用reject()方法就会进入.then()的第二个回调,否则就进入.cathc()
扩展: .all() .race()的使用方法
.all()的用法
let wake = (time) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(`${time / 1000}秒后醒来`)
}, time)
})
}
let p1 = wake(3000)
let p2 = wake(2000)
Promise.all([p1, p2]).then((result) => {
console.log(result) // [ '3秒后醒来', '2秒后醒来' ]
}).catch((error) => {
console.log(error)
})
.all()里面p1计时器3秒比p2的2秒长,但是先执行。所以可以用来执行特定顺序的异步。.all()后面接.then()可以看到then返回的是每个异步函数执行回调后的结果组成的数组。所以all()括号里的函数都执行。
那么问题来了,既然是都执行,.all()之后的.then()返回的是所有resolve()的结果。那如果一个进入reject状态 一个进入resolve状态的情况呢?
答案是:只要有一个进入reject()方法里,.all.catch()就会执行并返回那个reject()的方法。
.rece()的用法
顾名思义,Promse.race就是赛跑的意思,意思就是说,Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success')
},1000)
})
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('failed')
}, 500)
})
Promise.race([p1, p2]).then((result) => {
console.log(result)
}).catch((error) => {
console.log(error) // 打开的是 'failed'
})
9.js数据类型及判断
基本数据类型:number、string、boolean、null、undefined
引用数据类型:function object array
typeof可以判断除了null、array的所有类型,判断null 返回object,像判断null可以用===
instanceof可以判断new过的数据类型,不能判断基本数据类型。
constructor 返回某变量的构造函数,但是null undefined不能正常返回,而且构造函数可以改的所以也不是很准。
最好的方法:Object.prototype.toString.call(data) 返回 [object type] 准确的。
扩展:判断数组 Array.isArray() 判断null 变量===null 判断对象constructor
10.array数组方法
length
join() 将数组转成字符串,返回一个字符串。
delete 删除。
shift 删除第一个元素,返回删除的元素长度减1.
pop 删除最后一个,返回删除的元素长度加1.
unshift 数组头加一个或多个
push 数组尾加一个或多个
concat 连接两个数组
slice 剪切一部分
sort 排序
splice 插入 删除 替换,接受三个参数,第一个是index,第二个是删除几个,第三个是要替换添加的数组
every() 判断每一个
Array.from 将伪数组转换成真数组(伪数组只有长度没有poppush方法)
Array.of 将一系列值转成数组
11.字符串方法
substr()
substring()
split() 将字符串转数组
concat 连接字符串
indexof 返回一个字符在字符串中的索引值
slice 剪切
match 匹配正则的字符
12.??和?.
空值合并操作符?? 这个和 || 很像,但是它不会屏蔽掉false和 0
只有当左侧为null和undefined时,才会返回右侧的数
可选链操作符( ?.
)
?. 可选链运算符,检查每个级别,如果碰到的是 undefined 或 null 属性,直接返回 undefined,不会继续往下检查
??= 逻辑空赋值运算符 (x ??= y) 仅在 x 是 nullish (null 或 undefined) 时对其赋值
13.dom事件类型和冒泡
DOM事件类型就是:事件捕捉、事件冒泡
捕捉:比如点击事件,会从根元素开始触发,向内传播,一直到目标元素。祖先元素---> 目标的父元素--->目标元素。
冒泡:点击事件从目标元素传递到目标父元素,在传递到目标祖先元素。
先捕捉在冒泡。
event.stopPropagition()阻止冒泡
event.preventDefault()阻止捕捉。
事件委托就是运用了冒泡机制。这样就不用给每个子元素绑定事件,给他们的父元素绑定一次事件就可以。
14.闭包
就是在一个函数内部可以访问,另一个函数内部的变量
function f(){
var a = 1;
var b = 2;
function add (){
return a+b
}
return
}
15.this指向
以函数调用this,指向window
以方法调用,指向的是调用方法的对象。
以构造函数调用,指向就是新创建的对象。
使用call apply 调用,指向的是对象
箭头函数调用,指向的是外层函数,没如果没有外层函数,就是window
补充:改变this只指向,apply call bind的异同
const obj = {
a:1,
b:2,
}
function add (name) {
console.log(this.a,this.b)
console.log(name)
}
// apply 第一个参数是原this的obj,第二个参数是数组
add.apply(obj, [1,2,3])
// call 第二个参数可选,有的话接受参数列表。
add.call(obj, 1,2,3)
// bind 与call类似 但是有一个函数返回值
const func2 = add.bind(obj)
func2(1,2,3)
- 三者都可以改变函数的this对象指向,都是用来改变函数执行时的上下文,可借助它们实现继承;
- 三者第一个参数都是this要指向的对象,如果如果没有这个参数或参数为undefined或null,则默认指向全局window
- 三者都可以传参,但是apply是数组,而call是参数列表,且apply和call是一次性传入参数,而bind可以分为多次传入
- call()和apply()唯一区别是参数不一样,call()是apply()的语法糖;
- bind是返回绑定this之后的函数,一个新函数,而apply、call 则是立即执行调用
16.ES6总结
let const 块级作用域、没有变量提升,暂时性死区、不能重复声明。
模板字符串 `${}`
函数参数可以设置默认值,占位符_ , 箭头函数(this指向父级,不能用arguments对象,不能new,不能使用Generator函数)
对象的简写,Object.keys()
for of循环数组
import导入模块和export 输出模块
promise
解构赋值
展开运算符
async await
17.函数式编程
面向函数编程(Functional Programming,简称FP)是一种编程范式,与面向对象编程(OOP)和面向过程编程(PO)并列。其核心思想是将计算过程分解成可复用的函数,并强调函数的纯粹性和无副作用。
在函数式编程中,函数被视为第一等公民,这意味着函数可以像其他数据类型一样被赋值给变量、作为参数传递给其他函数或作为函数的返回值。这种特性使得函数式编程在处理复杂逻辑时更加灵活和模块化。
纯函数:
接收相同的参数,返回的结果永远相同。且函数内部不会产生副作用(函数在执行过程中产生了外部可观察变化,如http请求、获取dom、Date.now()或者Math.random()、修改了外部数据等)
let a = 1;
function func() {
a = 'b';
};
func();
console.log(a); // b,修改了外部变量,不是纯函数。
function add (a, b) {
return a + b // 相同的ab结果相同,且不会产生副作用,是纯函数。
}
优点:结果可预测,可赋值复用,容易维护,放心调用。
高阶函数:
接收一个或多个函数,或 返回一个函数作为结果
例:常用的map forEach filter等。
事件监听、闭包常与高阶函数一起使用,但本身不是高阶函数。
优点:代码更加简洁和可读,减少了重复代码的出现,函数组合使用。
函数柯里化:
接收一个原始函数作为参数,返回一个接收并处理剩余参数的函数。
// 简单使用
function res(a,b,c) {
return a + b + c
}
console.log(res(10,20,30))
// 上面等于下方柯里化写法
function curryFn(a) {
return function(b) {
return function(c) {
return a + b + c
}
}
}
const curryRes = curryFn(10)(30)
console.log(curryRes(20)) // 60
// 高阶函数使用
function curry(fn) {
const len = fn.length
return function f1(...args) {
if (args.length >= len) {
return fn(...args)
} else {
return function f2(...moreArgs) {
const newArgs = [...args, ...moreArgs]
return f1(...newArgs)
}
}
};
}
// 柯里化后的函数
const curriedMultiply = curry(res)
// 使用柯里化后的函数
const temp = curriedMultiply(2)
console.log(temp(3)(4)) // 9
优点:
复用,定义函数时设置一些通用的参数,然后生成一个新的函数,我们在调用这个新的函数时只需要提供剩下的参数。这样可以减少重复代码,提高代码复用性。
延迟执行,柯里化的函数在接收到足够的参数之前不会执行,而是返回一个新的函数,等待剩余的参数。这意味着我们可以在任何需要的时候提供参数,然后在最后一刻计算结果。我们可以把参数的收集和最后的计算分开,先收集参数,在需要结果的时候进行计算。
组合,我们可以创建一系列的小函数,每个函数只做一件事,然后通过柯里化和函数组合来创建更复杂的函数。
函数式编程缺点:性能不如面向对象,也更占用内存。
面向对象 | 函数式 | |
核心理念 | 类,对象。强调“做什么,对象有哪些属性” | 纯粹性和无副作用,预测性。强调“谁做和结果” |
代码组织 | 状态和方法封装到类 | 通过函数接收状态,状态与方法是分离的 |
状态管理 | 类内部修改状态,有副作用 | 通过函数返回状态,强调无副作用、不可变 |
复用 | 类的继承extends implement | 通过各个函数组合在一起,链式调用 |
18.垃圾回收机制
内存管理:
分配内存 =》 使用内存 =》清理内存
内存回收机制:
标记清除:(目前使用)
- 垃圾收集器在运行时会给内存中的所有变量都加上一个标记
- 然后从各个根对象开始遍历,把还在被上下文变量引用的变量标记去掉标记
- 清理所有带有标牌机的变量,销毁并回收它们所占用的内存空间
- 最后垃圾回收程序做一次内存清理
引用计数:
- 当变量进行声明并赋值后,值的引用数为1。
- 当同一个值被赋值给另一个变量时,引用数+1
- 当保存该值引用的变量被其它值覆盖时,引用数-1
- 当该值的引用数为0时,表示无法再访问该值了,此时就可以放心地将其清除并回收内存。
造成内存泄漏的常见原因有:
- 过多的缓存。及时清理过多的缓存。
- 滥用闭包。尽量避免使用大量的闭包。
- 定时器或回调太多。与节点或数据相关联的计时器不再需要时,DOM节点对象可以清除,整个回调函数也不再需要。可是,计时器回调函数仍然没有被回收(计时器停止才会被回收)。当不需要setTimeout或setInterval时,定时器没有被清除,定时器的糊掉函数以及其内部依赖的变量都不能被回收,会造成内存泄漏。解决方法:在定时器完成工作时,需要手动清除定时器。
- 太多无效的DOM引用。DOM删除了,但是节点的引用还在,导致GC无法实现对其所占内存的回收。解决方法:给删除的DOM节点引用设置为null。
- 滥用全局变量全局变量是根据定义无法被垃圾回收机制进行收集的,因此需要特别注意临时存储和处理大量信息的全局变量。如果必须使用全局变量来存储数据,请确保将其指定为null或在完成后重新分配它。解决方法:使用严格模式。
- 从外到内执行appendChild此时即使调用removeChild也无法进行释放内存。解决方法:从内到外appendChild。
- 反复重写同一个数据会造成内存大量占用,但是IE浏览器关闭后会被释放。
- 注意程序逻辑,避免编写『死循环』之类的代码。
- DOM对象和JS对象相互引用。
19.打破循环
在for中:continue跳出这次循环,break跳过这个for循环执行下面的代码,return直接退出所在的函数、方法。
forEach:接受一个同步的回调函数(异步函数和promise作为回调会发生预料之外的结果),break和continue会报错(因为break只能跳出循环而不能跳出函数),return会跳出本次循环。打断foreach方法:抛出错误,throw(new Error())
20.Set、WeakSet、Map、WeakMap区别
Set:
是由一堆无序的、相关联的,且不重复的内存结构组成的组合
- 集合中的元素无序且唯一
- 集合中的元素可以是任何类型,无论是原始值还是对象引用
const mapSet = new Set()
mySet.add(1)
mySet.delete(1)
mySet.has(1)
mySet.clear()
mySet.size()
mySet.keys() // 返回键名的遍历器
mySet.values() // 返回键值的遍历器
mySet.entries() // 返回键值对的遍历器
WeakSet:
- WeakSet 中的元素只能是对象,不能是其他类型的值
- WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,也就是说,如果该对象不在被其他变量引用,那么垃圾回收机制就会自动回收该对象所占用内存,所以只要 WeakSet 成员对象在外部消失,它们在 WeakSet 里面的引用就会自动消失。
- WeakSet 不可遍历。
Map:
- Map默认情况下不包含任何键,所有键都是自己添加进去的。不同于Object原型链上有一写默认的键。
- Map的键可以时任何类型数据,就连函数都可以。
- Map的键值对个数可以轻易通过size属性获取,Object需要手动计算。
- Map在频繁增删键值对的场景下性能比Object更好。
const map = new Map()
map.set(1, '2234')
map.get('name') // 不存在返回undefined
map.has(1) // 判断是否存在
map.delete(1) // 删除
map.clear() // 清空
WeakMap:
弱映射,只能将对象作为键名,且对键名是弱引用,也就是说,一旦不再需要,WeakMap里面的键名对象和所对应的键值对会自动消失,不需要手动删除引用。
Map和WeakMap区别:
- Map的键可以是任意类型,WeakMap只接受对象作为键,不接受其它类型的值作为键
- Map的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键;WeakMap的键是弱引用,键所指向的对象是可以被垃圾回收,此时键是无效的。
- Map可以被遍历,WeakMap不能被遍历
21.迭代器对象转为数组,比如Map类型keys values
const map1 = new Map();
map1.set("0", "foo");
map1.set(1, "bar");
const iterator1 = map1.values();
console.log([...iterator1]);
// ["foo", "bar"]"
4.css
1.移动端1px被渲染成2px的问题。
全局解决:在<meta>标签中viewport属性设置,initial-scale=0.5
局部:使用transform:scale(0.5)
2. display:none visible:hidden区别
都能隐藏元素。
前者:让元素从渲染树中消失,不占空间。非继承性子孙节点直接消失。导致重排重绘。
后者:存在渲染树中,占据空间。继承性,子孙节点可以通过visiblity:visible显示。导致重绘不会重排。
3. BFC 块级格式化上下文 block format context
作用:可以包含住浮动元素、不被浮动元素遮挡、阻止父子margin折叠。
创建规则:根元素、浮动元素(float不为none)、绝对定位absolu fixed、display flex inline-block inline-flex等、overflow不为visible的元素。
4.flex布局
主要就是
justify-content:主轴对齐方式.
align-items:交叉轴对齐方式.
flex-wrap:项目都排在一条线(又称”轴线”)上。flex-wrap属性定义,如果一条轴线排不下,如何换行.
5.使一个盒子水平垂直居中
1.父盒子position relative;子盒子absolute,top left各50%,margin-top left自己的一半px。
2.父盒子position relative;子盒子absolute ;margin:auto;topleftrightbottom都是0.
3.父盒子display:table-cell;vertical-align:middle;text-align:center;子盒子:display:inline-block.
4.父盒子:display:flex;justify-content:center;align-items:center;
5.直接计算父子之间的距离,在子盒子margin-top left。
6.父盒子:position relative;子盒子absolute,top left各50%;tansform:translate(-50%,-50%)
6.css盒模型
box-sizing:border-box;怪异盒子模型;width=设置的宽+padding+border
box-sizing:content-box;标准盒模型;标准盒模型下width就是设置的值
7.块级元素 行内元素 css权重
块级元素:div ul li dl dt dd p h1-h6 form
行内:a span b img input select button
css权重:
!important最大
内联:1000
id选择器:100
类、伪类、属性:10
标签 伪元素:1
5.HTTP
1.什么情况下只能用get请求,不能用post?
单页面应用 vue react,刷新的时候会发出一次get请求,为了防止白屏需要nginx配置。
jsonP解决跨域。
2.react自定义hook与普通函数(如utils)的区别?
hook只能在react组件中或hook组件中使用,不然会报错。
useEffect useState等只能在hook、react组件中使用,不能在普通函数中使用。
自定义Hooks
需要以use
开头,普通函数则没有这个限制。
3. 常见http状态码
200: 成功
301:请求资源的 URL 已永久更改。在响应中给出了新的 URL。
304:这是用于缓存的目的。它告诉客户端响应还没有被修改,因此客户端可以继续使用相同的缓存版本的响应。
401: 虽然 HTTP 标准指定了"unauthorized",但从语义上来说,这个响应意味着"unauthenticated"。也就是说,客户端必须对自身进行身份验证才能获得请求的响应。
403:客户端没有访问内容的权限。
404:服务器找不到请求的资源。
502:此错误响应表明服务器作为网关需要得到一个处理这个请求的响应,但是得到一个错误的响应。
503:服务器没有准备好处理请求。常见原因是服务器因维护或重载而停机。
504:当服务器充当网关且无法及时获得响应时,会给出此错误响应。
4. Tcp三次握手,四次挥手
Tcp :(Transmission Control Protocol)传输控制协议。
三次握手:

5. get和post区别
长度:get受限于url最大2-5k,post没有大小限制。
安全:get暴露在url中,且保存在历史中,有缓存,不安全,post较为安全。
形式:get在url中,post是requst body的形式。get是获取数据,post是发送数据。
6.Http缓存
强缓存:
cache-control: 代替Expires的设置有效时间的方法。cache-control: max-age=N,N就是需要缓存的秒数。从第一次请求资源的时候开始,往后N秒内,资源若再次请求,则直接从磁盘(或内存中读取),不与服务器做任何交互。
协商缓存:
last-modified:
-
首先需要在服务器端读出文件修改时间,
-
将读出来的修改时间赋给响应头的
last-modified
字段。 -
最后设置
Cache-control:no-cache
ETag: 设置一个缓存过期时间戳
6.React相关
1.react函数编程与vue3的区别?
1. 核心思想
尤雨溪说vue是灵活易用的渐进式框架,进行数据拦截/代理,它对侦测数据的变化更敏感更准确。
react从一开始就是UI库,推崇函数式编程(纯组件),数据不可变以及单向数据流。
2. 原理上
vue是响应式状态管理、更新单个状态dom节点:vue会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树;
而react是手动setState,会自顶向下全diff,更新整个组件与子组件。
Vue
是对数据进行劫持/代理,它对监测数据的变化更加精准,动了多少数据就触发多少更新,更新粒度很小,而React
推崇函数式,这是没办法感知数据变化的,就是说不知道什么时候应该刷新,而且即便是手动setState
触发更新,它也也不知道哪些组件需要刷新,而是渲染整个DOM
,说白了就是无脑刷新嘛,这样就导致性能不好,所以后面只能不断通过其他办法来避免不必要的刷新,或者优化无脑刷新的性能。当然Vue
也不是那么完美,它实现精准刷新也是有代价的,就是需要给每个组件配置监视器,管理依赖收集和派发更新,这同样是有消耗的。
https://segmentfault.com/a/1190000043837947
3. diff算法不同
react:使用Fiber算法,由于是单向链表速度比vue2慢,同时将abcd改为abdc移动的元素比vue2多。React会逐层对比两棵DOM树(tree component element三层),找出需要更新的节点,然后只更新那些节点。这种算法的时间复杂度为O(n)。
vue2: 采用双端比对的方式优化了 Diff 算法。有“就地复用”原则。
Vue3: 也采用了双端对比的方式优化了 Diff 算法,还构造了最长递增子序列,最大程度降低了 DOM 操作。新增静态标签,对预测可能会变化的dom元素,加上flag,下次更新时直接对比。
因为是面试题,所以回答的时候简单的说一下大致区别,若详细了解具体源码:
认识&对比 React、Vue2、Vue3 三者的 diff 算法_19.vue2和vue3和react的diff算法区别?-优快云博客
补充:
在面试过程中还可以说一下实际体验上的不同,比如:template插值表达式和jsx语法,框架和库对ts不同的支持,生命周期区别,props声明与直接props,vue插槽和reactprops,路由和跨组件通信,最后react还可以使用类组件的写法。
7.TypeScript
1.命名空间与模块区别
书写方式:namespace 和 es6
作用域: 命名空间下export的才能被外面访问,模块导出后全局可以访问。
依赖项:命名空间不能声明依赖项,模块可以声明依赖项。
性能:命名空间就像是一个js对象,模块可以按需加载。
2.类型别名和接口区别
类型别名鼠标放上去显示的是具体类型,接口是接口名。
别名不能使用接口的extends和implement。
别名可以定义基本数据类型、联合类型、元组,任一类型,接口不行。
别名不能重名,接口重名就是扩展(增加)类型
3.TS是什么,增加了什么
是
- 以JS为基础构建的语言
- 一个js的超集
- 可以在任何支持js的平台中运行
- 扩展了js并添加了类型
增加了:
- 类型
- 添加ES不具备的新特性
- 支持ES的新特性
- 丰富的配置选项
- 强大的开发工具
4.常用高级类型与基础类型
高级类型:
交叉类型 &
联合类型 |
类型别名 type
类型约束: T extends U ,判断T是否可以赋值给U
映射类型:P in T ,遍历T的所有类型
类型索引:keyof, 返回类型上已知的公共属性名
索引访问:T[K],返回T对应属性的类型
条件类型:T extends U ? X :Y
只读:Readonly
可选:Partial
必选:Required
摘除:Extract<T,U> 提取T中可以赋值给U的类型,T U的交集。
排除:Exclude<T,U> 从T中剔除可以赋值给U的类型。T有 U没有的。
获取函数返回值类型:ReturnType<() => string>。常与typeof搭配
构建对象类型:Record<T,K>
基础类型:
- boolean(布尔类型)
- number(数字类型)
- string(字符串类型)
- array(数组类型)
- tuple(元组类型)
- enum(枚举类型)
- any(任意类型)
- null 和 undefined 类型
- void 类型
- never 类型
- object 对象类型
5.项目中识别不到的类型
使用declare声明:
下载库、框架支持的类型包