早餐奶
1. 自我介绍
2. 说说async、await的设计和实现
设计:
-
async是用来声明一个异步函数,该函数返回一个promise对象;await用来等待一个promise完成,并获取其结果;
-
当函数执行的时候,一旦遇到awiait就会先返回,等到触发的异步操作完成,再接着执行函数体内后的语句; 实现: async/await语法糖就是使用Generator函数+自动执行器来运作的;
3. 深拷贝需要注意哪些问题?
性能问题: 深拷贝操作需要遍历对象及其所有的嵌套对象,可能会导致性能问题,尤其是大型对象;基本类型和特殊类型的处理:- 要处理基本类型(字符串,数字,布尔值等)和特殊类型(函数,正则表达式和日期对象等)。
- 基本类型,直接返回其值即可,特殊类型,可以选择直接引用原始对象,而不进行复制;
- 处理循环引用: 对象属性之间存在相互引用的情况,导致递归复制陷入无限循环,使用一个额外的数据结构(map活weakmap)来存储已经复制的对象;
-
实现: 浅拷贝: object.assign(); slice();JSON.stringify()和JSON.parse();
递归实现深拷贝
-
应用场景: 深拷贝:
需要创建一个完全独立的副本时,防止对原对象得修改; 在对象状态管理中,需要创建对象的副本以记录历史状态、实现撤销和重做等操作。浅拷贝:
当只需要复制对象的引用,而不需要创建对象的副本时。
4. 判断数组的方法有哪些?手写一个instanceof方法
1. Array.isArray() //返回一个布尔值 true是,false否
2. 构造函数属性constructor //arr.constructor==Array; true是,false否
3. Object.getPrototypeOf()方法返回指定对象(arr)原型,与array对比
//Object.getPrototypeOf(arr) == Array.prototype;true是,false否
4. instanceof运算符 // arr1 instanceof Array; true是,false否
function myInstanceof(obj, constructor) {
//获取对象原型
let proto = obj._proto_;
//获取对比数据原型
let prototype = constructor.prototype;
while(proto) {
if(proto === prototype) {
return true; //相同原型,返回true
}
proto = proto._proto_; //继续查找上一层
}
return false //原型链结束都没找到,返回false
}
//示例
console.log(myInstanceof([1,2,3]), Array)
5、如何借鉴React diff算法的思想,实现各种情况树节点的更新
diff思想:
用于更新虚拟DOM树的一种算法;通过比较虚拟DOM树的差异,
然后对有差异的部分进行更新,从而提高性能;
原理:
1.逐层比较:从根节点开始逐层比较新旧虚拟DOM树的节点,根节点不同,整个树更新,
相同,继续比较子节点;
2.列表比较:使用Key属性来确定哪些节点需要更新,删除或添加;
节点被移动新位置,采取尽量复用,而不是重新创建;
3.不同类型的节点;节点类型不同,直接删除旧节点,创建新节点替换;
4.属性比较:逐个比较属性的值,属性值不同,更新该属性;
5.子节点比较:递归调用自身,继续比较子节点的差异;
// 通过递归比对新旧两颗虚拟DOM树的差异,核心思想:相同类型的节点,更新需要变化的属性和递归处理子节点;节点类型不同直接替换整个节点;
function updateTreeNode(newNode, oldNode) {
// 如果旧节点不存在,直接创建新节点
if (!oldNode) {
createNode(newNode);
return;
}
// 如果新节点不存在,直接删除旧节点
if (!newNode) {
removeNode(oldNode);
return;
}
// 如果两个节点相同,则更新属性
if (newNode.type === oldNode.type) {
updateNodeAttributes(oldNode, newNode.attributes);
// 递归比对子节点
const oldChildren = oldNode.children || [];
const newChildren = newNode.children || [];
oldChildren.forEach((child, index) => {
updateTreeNode(newChildren[index], child);
});
} else {
// 节点类型不同,直接替换旧节点
replaceNode(oldNode, newNode);
}
}
6. 怎么让中间页携带上cookie?
疑似:1: `withCredentials为true;` 2: `后端设置Header(请求头)`
7. 说说跨域问题
```js
跨域: 指浏览器不能执行其他网站的脚本。是由浏览器的同源策略造成的,
是浏览器对javascript实施的安全限制;
同源指的是: 域名,协议,端口相同
解决方案:
1. JSONP: 在HTML标签里,比如script,img
2. jQuery Ajax;
3. CORS,通过服务端设置Access-Control-Allow-Origin请求头,带cookie,前后都设置
4. nginx代理跨域
5. postMessage跨域
6. nodejs中间件代理跨域
7. vue框架 通过wabpack设置prosy设置
```
8. 讲讲webpack的整个工作流程
9. 有没有用过webpack的loader解决过一些具体的场景问题?
loader用于对模块得“源代码”进行转换,在import或“加载”模块时3预处理文件;
style-loader: 将css添加到DOM的内联样式标签style里;
css-loader: 允许将css文件通过require的方式引入,并返回css代码;
less-loader: 处理less;
sass-laoder: 处理sass;
babel-loader: 用babel来转换ES6到ES5
10. ES5怎么实现继承?讲讲对原型链的理解
11. require和import的区别?
1.加载时机不同
import是在编译时加载,必须放在文件的开头;
require是在运行时加载,可以放在代码的任何位置;
2.所属规范不同
import是ES6(ECMAScript 2015)引入的关键字,属于ES模块化语法规范;
require是CommonJS规范的一部分,主要用于Node。js环境
3.本质
require是赋值过程;
import是解构过程;
12. 有没有什么想问我的?
什么是事件代理
事件代理: 指利用JavaScript的事件冒泡机制,将子元素事件委托到父元素来处理,
而不是每个子元素单独绑定事件监听器;
工作原理:
1.事件冒泡: 但某个子元素触发事件(点击,键盘等)时,事件会从目标逐层向上传递到它的父元素,直至根元素(通常是document).
2.父元素处理子元素事件: 利用事件冒泡机制,可以在父元素上监听事件,通过事件对象event.target获取具体触发事件的子元素,然后根据需要进行处理;
优点: 可以提高性能
Object.freeze 的作用
是javaScript用于冻结对象的方法,冻结后的对象变的不可变,不能添加、删除或修改其属性,
优化性能;(避免vue初始化时候,做一些无用的操作,从而提高性能)
MVC和MVVM
MVVM是一种设计思想; 即 Model-View-ViewModel的简写,即模型-视图-视图模型。
View 和 Model 之间并没有直接的联系,而是通过ViewModel进行交互;
Model 和 ViewModel 之间的交互是双向的,
因此View 数据的变化会同步到Model中,而Model 数据的变化也会立即反应到View 上。
将模型中的数据转化成视图可直接使用的形式;简化了视图和模型直接的交互逻辑。
关注业务逻辑,不需要手动操作DOM,
MVC即Model-View-Controller。
MVVM主要解决了MVC中大量的dom操作是页面渲染性能降低,加载速度变慢,影响用户体验
Flex布局,Gird布局对比
flex: 是一维布局系统,适合做局部布局;
一次只能处理一个维度上的元素布局,一行或者一列;
gird:是二维布局系统,通常用于整个要页面的规划;
可同时在行和列上进行布局;
性能优化
HTML
1.避免HTML中书写CSS代码,因为这样难以维护;
2.使用语义化标签,减少css代码,增加可读性和SEO;
3.减少标签的使用,DOM解析式一个大量遍历的过程,减少不必要的标签,能降低遍历次数;
CSS
1.优化选择器路径:使用。c{},而不是 .a .b .c{};
2.选择器合并:共同的属性内容提取出来,压缩空间和资源开销;
3.精准样式,使用padding-left:10px 而不是padding:0 0 0 10px;
4.雪碧图
5.为0值去单位:添加兼容性;
6.压缩文件大小,减少资源下载负担;
JS
1.尽可能合并JS代码:提取公共方法,进行面对对象设计;
2.尽可能少地创建DOM,而是在HTML和CSS中使用display: none 来隐藏;
3.压缩文件大小,减少资源下载负担;
VUE
vue数据双向绑定
简单理解
采用数据劫持结合发布者订阅者模式的方式;
通过Object.defindProperty()劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,
触发相应的监听回调。
加深
- 采用数据劫持结合发布-订阅模式实现的;
- 通过Object.defindProperty()来实现数据劫持,对于每一个数据属性,vue会将它转化为一个getter和setter,从而拦截对数据的访问和修改操作。
发布-订阅模式
- 视图和数据之间的关联是通过一个叫Watcher的类来实现的。Watcher作为发布者和订阅者之间的桥梁,每一个Watcher订阅了某个数据属性,当该属性发生变化时,Watcher就会被通知触发相应的视图更新;
实现步骤:
- Dep(依赖收集器):每个数据属性都有一个对应的 Dep 对象,用来收集所有依赖于该属性的 Watcher。
- Watcher:当视图模板被解析时,Vue 会为每一个依赖数据的地方创建一个 Watcher,并将其添加到对应数据属性的 Dep 中。
- 通知更新:当数据属性发生变化时,对应的 Dep 会通知所有相关的 Watcher,进而触发视图更新。
vue 封装的数组方法,手写一下
vue data 属性值发生变化,会不会立即更新
vue watch 监听到属性值发生变化,是怎么处理的
- 依赖追踪:Vue使用ES6的
Proxy对象(或在Vue 2中使用Object.defineProperty)来劫持(或“拦截”)你对数据的访问和修改操作。这使得Vue能够跟踪每个属性的依赖关系。 - 派发更新:当你修改一个响应式属性时,Vue会通知依赖该属性的观察者(watcher)。这通常是通过调用观察者的回调函数来实现的。
- 执行回调:一旦通知被发送,Vue就会执行与该属性相关联的观察者的回调函数。回调函数可以接收新旧值作为参数,允许你执行一些逻辑,比如更新DOM、计算新值等。
mixin 的原理
extends 的原理
自定义指令的原理和用法
什么是高阶函数,什么是高阶组件
v-if v-for 为什么不能连用
- 性能问题
- 同时使用时,Vue会在每次循环渲染时重新计算v-if指令的值。会导致额外的性能开销,尤其处理大量数据时;此外v-for的优先级高于v-if(在vue3中,v-if 具有比 v-for 更高的优先级。)Vue 会首先为每一个数组或对象元素创建 DOM 元素,然后再根据 v-if 的条件来销毁其中一部分。这实际上是一种资源浪费,因为很多被创建的 DOM 元素可能最终不会被渲染到页面上。
- 逻辑复杂性
- 会使代码逻辑变得复杂,增加代码的阅读难度和维护成本;
- 渲染问题
- 同时使用 v-if 和 v-for 可能会导致意外的渲染结果。
v-for 事件代理
手写 v-for
provide inject 的原理
vue parent 和 children 之间的通信
listeners 的作用
手写 vue router
手写 vuex
vuex 是怎么全局注册的
- 首先安装Vuex
npm install vuex --save - 创建一个Vuex Store。咋项目src目录下新建一个store文件夹,然后在该文件夹中个创建一个index.js文件。
- 在store/index.js文件中定义store并导出。
import Vue from 'vue'; import Vuex from 'vuex'; Vue.use(Vuex); const store = new Vuex.Store({ // state, mutations, actions, getters 等 }); export default store; - 在主文件main.js中引入store并注册到vue实例。
import Vue from 'vue'; import App from './App.vue'; import store from './store'; new Vue({ store, render: h => h(App), }).$mount('#app');
redux 和 vuex 的区别
composition api 和 vue2 的区别
-
虚拟dom
虚拟DOM:就是用JS来模拟DOM结构。
-
优点:
- 性能优化: 减少对真是DOM的操作次数,提高渲染效率;
- 跨平台:可以渲染到不同的平台,例如浏览器、移动端等;
- 简化开发:开发者可以使用声明式的方式操作虚拟DOM,无需关心底层DOM操作细节;
虚拟 DOM 是 Vue.js 渲染机制的核心,它通过比较新旧虚拟 DOM 的差异,最终只更新真实 DOM 中需要改变的部分,从而优化性能。