面试 五

时间:2024-04-09 20:54:23

一、设计模式

1、工厂模式

工程模式:在javascript中,工程模式的表现形式就是 一调用即可返回新对象的函数。

// 工厂模式
function person (name, age) {
	return { name, age}
}
const person1 = person("tom", 18)
const person2 = person("jock", 20)

// 构造函数
function Food(name, color) {
	this.name = name;
	this.color = color
}
const food1 = new Food("西兰花", "黄绿色")

2、单例模式

单例模式:在使用这个模式时,单例对象整个系统需要保证 只有一个 存在。
单例方法:

// 自己实现
// 1、定义类
class SingleTon {
	// 2、添加私有属性(static)
	static #instance
	// 3、添加静态方法
	static getInstance() {
		// 4、判断并返回对象
		if(this.#instance) {
			this.#instance = new SingleTon()
		}
		// 此处返回的永远是这个单例对象
		return this.#instance
	}	
}
const s1 = SingleTon.getInstance()
const s2 = singleTon.getInstance()
console.log(s1 === s2)

单例的思想:vue2中的 use 方法;vue3中的 use 方法

3、观察者模式

观察者模式:在对象之间定义一个一对多的依赖,当一个对象状态改变的时候,所有依赖的对象都会自动收到通知。
在这里插入图片描述

4、发布订阅模式

发布订阅者模式和观察者模式类似,区别是:一个有中间商(发布订阅模式)一个没中间商(观察者模式)。
对比如下:
在这里插入图片描述
应用场景:
Vue中的EventBus
1、vue2:直接使用实例方法($on, $emit, $off, $once)
手写EventBus:

<body>
<button id="on">注册事件</button>
<button id="emit">触发事件事件</button>
<button id="off">移除事件</button>
<button id="once">一次性事件注册</button>
<button id="onceEmit">一次性事件触发</button>
<script>
  class EventBus {
    #handlers = {}
    $on(event, callback) {
      if (this.#handlers[event] === undefined ) this.#handlers[event] = []
      this.#handlers[event].push(callback)
    }
    $emit(event, ...arg) {
      let events = this.#handlers[event] || []
      events.forEach((callback) => {
        // console.log(arg)
        callback(...arg)
      })
    }
    $off(event) {
      this.#handlers[event] = undefined
    }
    $once(event, callback) {
      this.$on(event, (...arg) => {
        callback(...arg)
        this.$off(event)
      })
    }
  }
  let bus = new EventBus()

document.getElementById("on").addEventListener("click", function () {
  bus.$on("event1", () => {console.log("event1的回调")})
  bus.$on("event2", (name, age) => {console.log("event2的回调1",name, age)})
  bus.$on("event2", (name, age) => {console.log("event2的回调2",name,age)})
})
document.getElementById("emit").addEventListener("click", function () {
  bus.$emit("event1")
  // bus.$emit("event2", "jock", 20)
  bus.$emit("event2", "tom", 18)
})
document.getElementById("off").addEventListener("click", function () {
  bus.$off("event2")
})
document.getElementById("once").addEventListener("click", function () {
  bus.$once("once-on", (name, age) => {console.log("once-on的回调", name, age)})
})
document.getElementById("onceEmit").addEventListener("click", function () {
  bus.$emit("once-on", "jock", 20)
})
</script>
</body>

5、原型模式

原型模式:是创建原型模式的一种,其特点在于 复制 一个已经存在的实例来返回新的实例,而不是新建实例。
应用:
1、Object.create:将对象作为原型,创建新对象
2、vue2中的数组的方法:
1)数组变更方法(push、pop、shift、unshift、splice、sort、reverse)

6、代理模式

代理模式是为一个对象提供一个代用品或占位符,以便控制对它的访问。
应用场景:缓存代理
需求:第一次查询的数据通过接口获取,重复查询通过缓存获取(根据省份查询下属城市)。
具体逻辑:查询到结果以后,将省份和城市列表,按照键值对的形式存到本地对象中,后续查询时,判断对象中是否有相关数据,没有就走接口取数据,有就直接取本地数据。
在这里插入图片描述

7、迭代器模式

可以让用户透过特定的接口巡防容器中的每一个元素而不用了解底层的实现(遍历)。
场景:
1、for in 和 for of
2、迭代协议

二、vite和webpack的区别

构建速度:
webpack构建速度较慢,因为它要分析整个依赖,进行多次文件的扫描和转义;
vite利用ES模块的特性构建正在编译的文件而不是整个项目,使得构建速度比较快(改哪里就构建哪里)。
开发模式:
webpack使用HMR(热模块替换)实现快速开发模式,配置复杂;
vite也支持HMR,但是不需单独配置(默认支持该功能)。
配置复杂度:
webpack配置比较复杂,比如处理不同资源需要配置不同的loader;
vite鼓励0配置,也支持自定义配置去适用于复杂的项目;
插件生态:
webpack历史丰富,拥有庞大的插件生态系统,适用于各种不同需求;
vite相对来说,插件较少。
编译方式:
webpack使用多种加载器和插件处理不同类型的资源;
vite利用es模块的原生,支持使用原生的浏览器来导入和处理模块,不需要大规模的编译和打包。
使用场景:
webpack适用于复杂的大型项目,特别是需要大量自定义配置和复杂的一些构建的项目;
vite适用于中小型项目,或者是快速开发原型跟小型应用的场景。
打包原理:
webpack将所有的资源打包成一个或多个大bundle文件;
vite是保持开发时的模块结构,是浏览器进行原生的导入,在生产环境中进行代码的分割和优化。
综上,webpack复杂灵活,适用于复杂的一些场景,有庞大的插件生态,缺点是构建速度慢,配置复杂,开发体验没有vite流畅;
vite构建速度快,0配置启动,原生的es模块支持,适用于小型项目跟快速的原型开发,缺点是插件生态跟webpack相比比较少,不太适合一些复杂的大型项目

三、vue脚手架打包构建过程及优化

webpack构建流程:
1、初始化参数:从配置文件和 Shell语句中读取与合并参数,得出最终的参数
2、开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译
3、确定入口:根据配置中的 entry 找出所有的入口文件
4、编译模块:从入口文件出发,调用所有配置的Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理
5、完成模块编译:在经过第四步使上oader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系
6、输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会
7、输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统,在以上过程中,webpack 会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用
Webpack 提供的 API 改变 Webpack 的运行结果
优化:
因为webpack构建流程其中有个步骤是所有的模块进行编译处理,那么我们可以在以下几点上做优化处理,既然它要查找文件,那么我们就让它查找的范围缩小从而提升查找效率,可以用alias,extensions等配置缩小范围,第二点减少需要解析的文件,使用noparse配置告诉webpack排除忽略指定文件,不对他们进行解析,第三点避免重复编译第三方库,可以把第三方库单独打包到一个文件中,它不会跟着业务文件一起重新打包,也能提高webpack的构建速度,起到优化作用。那么webpack在对代码进行压缩打包时,如果有多个js文件需要被压缩,她会一个个进行压缩,可以使用thread loader插件来开启多个子进程,采用并行的方式对多个js文件进行压缩打包。

四、vue2和vue3的区别

1.双向数据绑定原理不同
2.是否支持碎片(根标签)
3.API类型不同
4.定义数据变量和方法不同
5.生命周期钩子函数不同
6.父子传参不同
7.指令与插槽不同
8.diff算法不同
9.setup
10.vuex 和pinia的区别

五、继承

1 原型链继承
一是字面量重写原型会中断关系,使用引用类型的原型,并且子类型还无法给超类型传递参数。
2 借用构造函数(类式继承)
借用构造函数虽然解决字称两料问题录准没有原型,则复用无从谈起。所以我们需要原型链+借用构造函数的模式,这种模式称为组合继承。
3 组合式继承
组合式继承是比较常用的一种继承方法,其背后的思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又保证每个实例都有它自己的属性。
4.组合式寄生

ES6的class extends

六、ts中的接口 interface

在TypeScript中,接口是一种强大的工具,用于定义对象的形状和行为。通过使用接口,我们可以提供更好的类型检查、模块化和代码复用。接口可以定义对象的属性和方法,可以包含可选属性和只读属性,还可以通过继承和实现创建更复杂的接口和类之间的关系
在这里插入图片描述
接口属性:
可选属性(通过?字符标识)
只读属性

interface Role {
  readonly 0: string;
  readonly 1: string;
}

多余属性检查

七、c3、h5的新特性

八、命名空间和module的区别

九、node

十、监听窗口变化

window.addEventListener('resize', () => {})

十一、computed的缓存机制,可以用来做双向数据绑定么

1.Computed也是响应式的
Computed是响应式的,读取Computed会触发get,设置Computed会触发set

2.Computed如何控制缓存
计算属性是有缓存的,比如某个计算属性C,它依赖data中的A,如果没有缓存的话,每次读取C时,C都回去读取A,从而触发A的get。多次触发A的get有时候是一个非常消耗性能的操作。所以Computed必须要有缓存。

computed里面控制缓存最重要的一点就是脏数据标记为dirty, dirty是watcher的一个属性。

当dirty为true时,读取computed会重新计算
当dirty为false时,读取computed会使用缓存

3. 依赖的data发生变化,computed是如何更新的
页面P依赖计算属性C, 计算属性C又依赖data里面的A, computed更新步骤如下:

由于C依赖了A, A可以收集到C的watcher
当A发生变化时,会将watcher的脏数据标记位dirty设置为true
并且A会收集到页面P的watcher,A通知P进行更新,从而页面P重新读取计算属性C, 由于此时dirty为true,此时的计算属性会重新计算。
computed更新完毕,重新将脏数据标记位dirty设置为false,如果其依赖的A不发生改变,那下次再进入就会读取缓存。
4.绑定表单元素

//方法一:用“Vuex 的思维”去解决这个问题。给 <input> 中绑定 value,然后侦听 input 或者 change 事件,在事件回调中调用一个方法。
<input :value="message" @input="updateMessage">
  
computed: {
  message() {
    return this.msg + '%';
  }
},
methods: {
  updateMessage (e) {
    this.msg = e.target.value;
  }
}
//------------------------------------------
//使用带有 setter 的双向绑定计算属性:
<input v-model="message">

computed: {
  message: {
    get () {
      return this.msg + '%';
    },
    set (value) {
      this.msg = value;
    }
  }
}

十二、computed和watch的区别

1、computed是计算属性,watch是监听,监听data中的数据变化
2、computed支持缓存,当其以来的属性的值发生变化时,计算属性会重新计算,反之,则使用缓存中的属性值;watch不支持缓存,当对应属性发生变化的时候,响应执行。
3、computed不支持异步,有异步操作时无法监听数据变化;watch支持异步操作。
4、computed第一次加载时就监听;watch默认第一次加载时不监听,有两个配置,immediate(默认为false)和deep(默认为false),immediate为true时,第一次加载时监听,deep为true时,开启深度监听,用来监听对象子属性的变化
5、computed中的函数必须有return,watch不用
6、computed:一个属性受多个属性影响,如购物车
watch:一个数据影响多条数据,如搜索数据

十三、路由钩子函数

1.全局前置守卫

router.beforeEach

const router = createRouter({ ... })

router.beforeEach((to, from, next) => {
  // ...
  // 返回 false 以取消导航
  return false
})

2.全局解析守卫

router.beforeResolve:这和 router.beforeEach 类似,因为它在每次导航时都会触发,不同的是,解析守卫刚好会在导航被确认之前、所有组件内守卫和异步路由组件被解析之后调用。

router.beforeResolve(async to => {
  if (to.meta.requiresCamera) {
    try {
      await askForCameraPermission()
    } catch (error) {
      if (error instanceof NotAllowedError) {
        // ... 处理错误,然后取消导航
        return false
      } else {
        // 意料之外的错误,取消导航并把错误传给全局处理器
        throw error
      }
    }
  }
})

3.全局后置钩子

router.afterEach:和守卫不同的是,这些钩子不会接受 next 函数也不会改变导航本身:

router.afterEach((to, from) => {
  sendToAnalytics(to.fullPath)
})

4.路由独享的守卫

beforeEnter:可以直接在路由配置上定义 beforeEnter 守卫:

const routes = [
  {
    path: '/users/:id',
    component: UserDetails,
    beforeEnter: (to, from) => {
      // reject the navigation
      return false
    },
  },
]

5.组件内的守卫

beforeRouteEnter
beforeRouteUpdate
beforeRouteLeave
在路由组件内直接定义路由导航守卫(传递给路由配置的):

const UserDetails = {
  template: `...`,
  beforeRouteEnter(to, from) {
    // 在渲染该组件的对应路由被验证前调用
    // 不能获取组件实例 `this` !
    // 因为当守卫执行时,组件实例还没被创建!
  },
  beforeRouteUpdate(to, from) {
    // 在当前路由改变,但是该组件被复用时调用
    // 举例来说,对于一个带有动态参数的路径 `/users/:id`,在 `/users/1` 和 `/users/2` 之间跳转的时候,
    // 由于会渲染同样的 `UserDetails` 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
    // 因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例 `this`
  },
  beforeRouteLeave(to, from) {
    // 在导航离开渲染该组件的对应路由时调用
    // 与 `beforeRouteUpdate` 一样,它可以访问组件实例 `this`
  },
}

十四、插槽怎么定义

插槽分为三种:
默认插槽 具名插槽 作用域插槽

1.默认插槽
【定义:默认插槽是将父组件的结构和数据插入子组件中,默认插槽只有一个插入位置,要插入的html结构和data数据必须在父组件中,不过css可以在子组件中】
【简述:将父组件的自定义html和data插入子组件的对应位置】
【特点:父组件决定结构和数据】

2.具名插槽
【定义:具名插槽和默认插槽类似,只是默认插槽只有一个插入位置,具名插槽可以有多个插入位置】
【简述:将多个父组件的自定义html和data插入子组件的多个位置】
【特点:父组件决定结构和数据】

3.作用域插槽
【定义:作用域插槽的data数据固定写在子组件中,数据的html结构根据父组件传入的html结构来决定】
【简述:根据父组件中不同的html结构解析data中的数据】
【特点:子组件决定数据,父组件决定结构】

十五、display、opacity和visibility的区别

1.空间占据

display:none隐藏后不占据额外空间,它会产生回流和重绘,而visibility:hidden和opacity:0元素虽然隐藏了,但它们仍然占据着空间,它们俩只会引起页面重绘。

2.子元素继承

display:none不会被子元素继承,但是父元素都不在了,子元素自然也就不会显示了
visibility:hidden 会被子元素继承,可以通过设置子元素visibility:visible 使子元素显示出来
opacity: 0 也会被子元素继承,但是不能通过设置子元素opacity: 0使其重新显示

3.事件绑定

display:none 的元素都已经不再页面存在了,因此肯定也无法触发它上面绑定的事件;
visibility:hidden 元素上绑定的事件也无法触发;
opacity: 0元素上面绑定的事件是可以触发的。

4.过渡动画

transition对于display肯定是无效的
transition对于visibility也是无效的;
transition对于opacity是有效

十六、两个盒子怎么居中对齐

十七、盒子塌陷

盒子塌陷是本应该在父盒子内部的元素跑到了外部。

盒子高度塌陷原因?

①父元素没有设置高度(height);
②且其子元素脱离了文档流,导致内部没有元素可以撑起当前父元素:
a. 内部子元素使用了浮动导致内部元素脱离了文档流,且脱离文档流的元素无法撑起当前父元素。
b. 内部子元素使用了绝对定位或者固定定位脱离文档流。

如何清除塌陷
1、将盒子大小写死,给每个盒子设定固定的width和height,直到合适为止。

优点:简单方便;
缺点:非自适应,浏览器的窗口大小直接影响用户体验。

2、给外部的父盒子也添加浮动,让其也脱离标准文档流。

优点:方便;
缺点:对页面的布局不是很友好,不易维护。

3、给父盒子添加overflow:auto;或者overflow:hidden;

优点:浏览器支持好,简单;
缺点:当子元素有定位属性时overflow:auto;有可能出现滚动条,影响美观。overflow:hidden;可能会带来内容不可见的问题。

4、父盒子里最下方引入空的div并清除浮动块

优点:所有浏览器都支持,并且容器溢出不会被裁剪;
缺点:引入了不必要的冗余元素 。

5、为外部盒子添加after伪类,设置clear属性清除浮动(主流)

优点:不会造成代码冗余,剩余代码性能优化,推荐使用。

父盒子::after {
    content: "";
    /* 清除两边的浮动 */
    clear: both;
    /* 也可以使用display:table; */
    display: block;
    /* 兼容IE浏览器 */
    zoom: 1;
}

十八、bfc

官方定义:BFC(Block Formatting Context)块格式化上下文, 是Web页面的可视CSS渲染的一部分,是块盒子的布局过程发生的区域,也是浮动元素与其他元素交互的区域。
通俗来说就是:BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。 我们经常使用到BFC,只不过不知道它是BFC而已。
如何创建一个BFC
常用的方式有以下几种:

浮动元素(元素的float不是 none,指定float为left或者right就可以创建BFC)
绝对定位元素(元素的 position 为 absolute 或 fixed)
display:inline-block,display:table-cell,display:flex,display:inline-flex
overflow指定除了visible的值

BFC的布局规则
内部的Box会在垂直方向一个接着一个地放置。
Box垂直方向上的距离由margin决定。属于同一个BFC的两个相邻的Box的margin会发生重叠。
每个盒子的左外边框紧挨着包含块的左边框,即使浮动元素也是如此。
BFC的区域不会与float box重叠。
BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素,反之亦然。
计算BFC的高度时,浮动子元素也参与计算。

BFC可以解决哪些问题?
解决浮动元素令父元素高度坍塌的问题
外边距垂直方向重合的问题
非浮动元素被浮动元素覆盖

十九、登录流程

二十、菜单权限

二十一、动画和过渡

参考:https://www.arryblog.com/guide/css3/css-transition-animation.html#%E4%B8%80%E3%80%81transition-%E8%BF%87%E6%B8%A1%E5%8A%A8%E7%94%BB
transition 与 animation 的区别
1、transition 是过渡,是样式值的变化的过程,只有开始和结束;animation 其实也叫关键帧,通过和 @keyframe 结合可以设置中间帧的一个状态
2、animation 配合 @keyframe 可以不触发事件就触发这个过程,而 transition 需要通过 hover 或者 js 事件来配合触发
3、animation 可以设置很多的属性,比如循环次数,动画结束的状态等等,transition 只能触发一次;
4、animation 可以结合 keyframe 设置每一帧,但是 transition 只有两帧(开始和结束);

二十二、防抖和节流

https://www.bilibili.com/video/BV1dv4y117mY/?spm_id_from=333.337.search-card.all.click&vd_source=0c7220c5a1319b81d321a134289baf22
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二十三、循环对象的方法

for in
Object.keys 返回一个数组
.Object.getOwnPropertyNames(obj)方法返回在给定对象中直接找到的所有属性(即key 值) 返回类型是数组
Object.entries()方法返回一个给定对象自身可枚举属性的键值对数组