一面
1. 描述一个项目中大概负责的功能点以及实现的效果?
3. 文件上传进度跟踪 如何实现?
4. css 选择器优先级?
5. 正方形会随着浏览器窗口的拖拽调整大小比例 浏览器宽度缩小 大小比例会自动缩小 如何做 vw?
<style>
#square {
background-color: #333;
/* 初始大小设置,可能会被JavaScript覆盖 */
width: 100vw;
height: 100vw;
max-width: 100%; /* 防止正方形过大超出视口宽度 */
max-height: 100vh; /* 防止正方形过大超出视口高度 */
}
</style>
<div id="square"></div>
<script>
function resizeSquare() {
const square = document.getElementById("square");
const sideLength = Math.min(window.innerWidth, window.innerHeight); // 取较小的值以保持正方形
square.style.width = `${sideLength}px`;
square.style.height = `${sideLength}px`;
}
// 初始设置大小
resizeSquare();
// 监听窗口大小变化
window.addEventListener("resize", resizeSquare);
</script>
6. 实现两个边框 回字形?
7. 基本数据类型有哪些?
8. js 的基本数据类型 存储方式有哪些?
9. 为什么复杂数据类型(引入数据类型)存放在数据堆中?
- 复杂数据类型的大小是动态的 在运行过程中也会发生变化(增删数组或对象的元素) 堆内存允许动态分配或重新分配内存 而栈内存通常分配固定大小的空间
- 实现数据共享 将复杂数据类型存放在堆中 并通过栈中的引用来访问他们 可以实现多个变量指向同一个复杂数据对象 节省内存 简化数据传递
- 栈的效率和限制 由于栈的大小相对有限 将复杂且大小可变的数据移动到堆中 可以避免栈溢出
- 内存由运行时环境(如 JS 引擎的垃圾回收机制)管理 它能够自动处理内存的分配和回收 (复杂数据类型的生命周期往往不如简单数据类型那样容易预测)
10. 判断数组的方法?
11. v-if 和 v-show 的区别?
12. v-for 中 key 值的作用?
13. 在 vue 组件中 会设置不同的样式 怎么保证各个组件中样式不会相互污染呢?
14. scoped 防止组件之间样式的相互污染 这个原理是什么?
scoped 通过在编译时为组件内的每个元素添加唯一标识属性 data-v-xxxx,并修改 CSS 选择器使其包含这一属性 例如.class 变为[ data-v-xxx ].class 从而实现样式局部作用域 防止组件间样式的相互污染
15. vue 中初始的化没有监听某个数据 后面想再次监听 应该怎么做
16. 如果没有在 data 中进行声明这个对象 后面直接给他赋值 这个时候 watch 会监听到这个数据吗?
结果:如果你在组件的生命周期内动态添加了一个未在 data 选项中声明的属性,Vue 默认不会追踪这个属性的变化,因此 watch 也不会自动监听到这个新属性的改变。
17. 为什么不会监听到?
原因:因为 Vue 在组件初始化时会对 data 对象进行响应式处理,即通过 Object.defineProperty 方法将数据转换成响应式属性,这样就可以追踪数据的变化并触发视图的更新。对于在 data 之外动态添加的属性,由于它们在组件初始化时不存在,所以不会经过这一响应式处理过程。
this.$set(this, "newProperty", value); Object.assign(this.$data, { newProperty: value }); // 使用 vm.$data 直接修改:
18. 事件循环
// 写出下方代码打印顺序 react
console.log("start");
setTimeout(() => {
console.log("s1");
}, 0);
new Promise((resolve) => {
console.log("p1");
resolve();
}).then((v) => {
console.log("t1");
setTimeout(() => {
console.log("s2");
}, 0);
new Promise((resolve) => {
console.log("p2");
resolve();
}).then((v) => {
console.log("t2");
});
console.log("t3");
setTimeout(() => {
console.log("s3");
}, 0);
});
console.log("end");
16. TS 中 type 和 interface 的区别?
17. 地图添加 marker 称为撒点 渲染多个撒点 页面加载比较慢 性能优化有什么办法优化?(继续理解)
延迟加载:当用户滚动或拖动地图的时候 不会立即加载所有的撒点 只加载当前视口内的撒点
// 假设你已经有了一个百度地图实例变量map var map = new BMap.Map("mapContainer"); var markersData = [ /* 你的撒点数据数组 */ ]; // 初始化地图(这里只是示例代码,你需要根据实际情况设置中心点、缩放级别等) map.centerAndZoom(new BMap.Point(116.404, 39.915), 11); // 标记集群(可选) var markerClusterer = new BMapLib.MarkerClusterer(map, []); // 初始化时传入空数组,稍后再添加标记 // 加载当前视口内的撒点 function loadMarkersInView() { var bounds = map.getBounds(); // 获取当前地图视口的边界框 var markersToShow = []; // 存储要显示的标记 // 遍历撒点数据,判断每个点是否在边界框内 for (var i = 0; i < markersData.length; i++) { var point = new BMap.Point(markersData[i].lng, markersData[i].lat); // 假设你的数据包含lng和lat属性 if (bounds.containsPoint(point)) { var marker = new BMap.Marker(point); // 创建标记 // 设置标记的样式、事件等(可选) // ... markersToShow.push(marker); // 如果使用了标记集群,则添加到集群中 // markerClusterer.addMarker(marker); map.addOverlay(marker); // 将标记添加到地图上 } } // 如果使用了标记集群,则不需要单独添加标记到地图上 // 注意:如果你使用了标记集群,请确保不要在事件处理函数外调用markerClusterer.addMarker(),而是在这里统一添加 } // 监听地图移动结束事件 map.addEventListener("moveend", loadMarkersInView); // 初始加载视口内的撒点 loadMarkersInView();
使用标记群体:如果标记比较密集的时候 将多个标记组合成一个集群标记 当用户放大视图的时候 集群会分解标记
webWorker:因为标记处理非常耗时 使用 webWorker 在后台线程中处理数据 避免页面卡顿
// worker.js 创建webWork脚本 self.onmessage = function(event) { // 假设我们接收到一个包含标记数据的数组 var markersData = event.data.markersData; // 在这里处理你的标记数据 // 例如,你可以进行过滤、转换或其他计算 // 假设我们处理完数据后,将结果发送回主线程 var processedData = /* 处理后的数据 */; // 发送数据回主线程 self.postMessage(processedData); }; // main.js // 创建一个新的 Worker 对象 var worker = new Worker('worker.js'); // 当 Worker 发送消息时 worker.onmessage = function(event) { // 接收到的数据 var processedData = event.data; // 在这里,你可以使用处理后的数据来更新你的地图标记 // 例如,清除现有标记并添加新标记 // 假设你有一个函数来更新地图标记 updateMapMarkers(processedData); }; // 假设这是你的原始标记数据 var markersData = /* 你的标记数据 */; // 发送数据到 Worker worker.postMessage({ markersData: markersData }); // 更新地图标记的函数(需要你自己实现) function updateMapMarkers(data) { // ... 实现更新地图标记的代码 ... }
18. 为什么选择前端?
二面
3. vue 里面数据展示和处理 他是怎么进行监听的?
响应式数据处理
- 数据劫持:
- Vue 使用 Object.defineProperty()方法来实现数据劫持。当 Vue 实例被创建时,它会递归地遍历 data 对象的所有属性,并使用 Object.defineProperty()将这些属性转化为 getter 和 setter。
- Getter 用于在访问属性时,Vue 可以检查该属性是否已经被监听,如果没有,则将其添加到监听列表中。
- Setter 则用于在修改属性时,Vue 能够捕获到这种变化,并执行相应的操作,如更新视图。
- 发布订阅模式
- Vue 采用发布订阅模式来处理数据的变化和视图的更新。当数据发生变化时,Vue 会触发一个事件,通知所有订阅了该数据的监听器。
- Vue 使用一个事件队列来维护所有的订阅者和事件处理函数。当数据发生变化时,Vue 会将变化事件添加到事件队列中,并逐个执行对应的处理函数。
- 计算属性
- Vue 的计算属性是一种基于它们的依赖进行缓存的属性。只有当它的相关依赖发生改变时才会重新求值。这使得计算属性非常适合进行复杂的数据处理或计算。
- 当计算属性的依赖发生变化时,Vue 会重新计算该属性,并触发相应的视图更新
- 侦听器 Watcher
- Vue 的侦听器(watchers)是一种更通用的方式来观察和响应 Vue 实例上的数据变化。你可以使用 watch 选项或 watch 方法来观察特定的数据变化,并执行相应的操作。
- Proxy vue3
- Proxy 可以代理整个对象,而不仅限于属性的劫持,同时也可以监控数组的变化。这使得 Vue 3 在处理数据变化时更加高效和灵活。
4. definePProperty 如果没有 proxy 如何实现数组和对象的监听?
对象:this.$set() 数组:原型链上的方法
5. vue 里面对 dom 事件大概是怎样的处理过程?
通过 v-on 或 @ 指令将 DOM 事件绑定到组件方法上,当事件被触发时,Vue 会自动调用相应的方法并执行其中的逻辑。如果方法内修改了组件的数据,Vue 的响应式系统会确保视图和数据保持同步更新。此外,Vue 还提供了事件修饰符来修改事件监听器的行为,并支持自定义事件用于组件间通信。
6. vue 中监听事件 这个事件怎么映射到浏览器原始的 dom 上的过程?
- 事件绑定:通过 v-on 指令(或简写为 @)在 Vue 组件的模板中绑定 DOM 事件到 Vue 实例的方法上。
- 编译阶段:Vue 的编译器(Compiler)在构建虚拟 DOM(Virtual DOM)的过程中,会识别这些事件绑定,并将它们转换为可以在浏览器 DOM 上实际监听的事件监听器。
- 添加事件监听器:当 Vue 实例挂载(mount)到真实的 DOM 元素上时,Vue 会遍历虚拟 DOM 中的事件绑定,并在对应的真实 DOM 元素上添加事件监听器。这些监听器会调用 Vue 实例中定义的方法。
- 事件触发:当用户在浏览器上触发 DOM 事件(如点击、输入等)时,浏览器会调用这些事件监听器。
- vue 方法执行:事件监听器被触发后,会调用 Vue 实例中对应的方法。这些方法可以访问 Vue 实例的数据、方法和其他属性,执行相应的逻辑。
- 响应式更新:如果 Vue 方法中修改了 Vue 实例的数据,Vue 的响应式系统会检测到这些变化,并自动更新视图,确保视图和数据保持一致。
7. vue-router 前端的路由都有哪些个事件发射?
- hashChange 事件(Hash 模式特有):
- 当使用 Hash 模式的前端路由时,路由的变化是通过监听 URL 中#(hash)部分的变化来实现的。因此,当 hash 值发生变化时,会触发 hashChange 事件。
- 可以通过监听 window 对象的 hashChange 事件来捕捉 hash 值的变化,并执行相应的操作。
- popstate 事件(History 模式特有):
- 当使用 History 模式的前端路由时,路由的变化是通过调用 history.pushState 或 history.replaceState 方法并监听 popstate 事件来实现的。
- popstate 事件在用户点击浏览器的前进或后退按钮时触发,或者在 JavaScript 代码中调用 history.back()、history.forward()或 history.go()方法时触发。
- 可以通过监听 window 对象的 popstate 事件来捕捉路由的变化,并执行相应的操作。
- vue-router 的导航守卫
- 全局前置守卫(beforeEach)、全局后置守卫(afterEach)、路由独享的守卫(beforeEnter)和组件内的守卫(beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave)等导航守卫。
- 这些守卫可以在路由跳转的不同阶段执行代码,如检查用户登录状态、获取数据等
8. 上传文件 如何读取文件?是直接读取全部的内容然后分片上传?
通过 slice 先切分文件 hash...
9. 断点上传的主要实现原理?
通过将大文件分割成多个小文件块进行分段传输,并在传输过程中记录已上传的分片信息。在上传中断时,客户端可以发送恢复上传请求,服务器根据已上传的分片信息继续接收剩余的分片,并最终将所有分片合并成完整的文件。同时,还需要实现错误处理和重试机制、校验和完整性验证以及进度显示等功能来提高上传的可靠性和效率。
- 文件分片:将大文件分割成多个较小的文件块(通常是固定大小的块)通过 JavaScript 的 Blob 对象或 File API 来实现,个文件块都有一个唯一的标识符,用于记录文件块的传输状态
- 上传请求:客户端发起上传请求,并将文件分片按顺序上传到服务器。、
- 上传状态记录:服务器端需要记录上传的状态,包括已接收的分片、分片的顺序和完整文件的大小等信息。这可以通过数据库、文件系统或其他存储机制来实现。
- 中断处理:如果上传过程中发生中断(例如网络中断、用户主动中止等),客户端可以记录已上传的分片信息,以便在恢复上传时使用。
- 恢复上传:
- 当上传中断后再次开始上传时,客户端可以发送恢复上传请求,并将已上传的分片信息发送给服务器
- 服务器接收到恢复上传请求后,根据已上传的分片信息,判断哪些分片已经上传,然后继续接收剩余的分片
- 文件合并:
- 当所有分片都上传完成后,服务器将所有分片按顺序组合成完整的文件。这通常涉及读取每个分片的内容,并将其写入一个新的文件中
- 校验和完整性验证:
- 使用校验和(如 MD5 或 SHA-1)来验证文件的完整性。在上传过程中,客户端可以计算每个分片的校验和,并在服务器端进行验证。如果校验和不匹配,说明文件在传输过程中可能发生了损坏或修改。
- 进度显示:在客户端显示上传进度。这可以通过监听上传过程中的事件(如进度事件)来实现,并将进度信息展示给用户。
10. this 指向有哪些?
11. 如果是严格模式(('use strict'))下 this 指向问题?
全局上下文中的 this:严格模式下全局上下文中的 this 不再是全局对象(window)。而是 undefined(原因:如果你在不属于任何对象的函数(即全局函数)中使用了 this,并且这个函数在严格模式下运行,那么 this 将是 undefined)
// 全局 'use strict'; function globalFunction() { console.log(this); // undefined } globalFunction(); // new 'use strict'; function Constructor() { console.log(this instanceof Constructor); // true } new Constructor(); // 普通函数 'use strict'; function ordinaryFunction() { console.log(this); // undefined } ordinaryFunction(); // 函数作为对象的方法调用 'use strict'; var obj = { method: function() { console.log(this === obj); // true } }; obj.method();
构造函数和 new 操作符:this 仍然指向新创建的对象实例
函数作为普通函数调用:若函数内部的代码试图将 this 绑定到全局对象,严格模式会阻止这种行为,并使 this 的值为 undefined。
函数作为对象的方法调用:this 仍然指向调用该函数的对象
12. apply bind call 的区别?
13. 如果对一个函数执行了 bind 操作 改变为 this1 对这个返回的值又执行了一次 bind 更改为 bind2 返回了一个新的函数 那么最终 bind 会指向什么?
在 JavaScript 中,当你对一个函数使用.bind()方法时,你会创建一个新的函数,这个新函数在被调用时会有指定的 this 值,以及可选的预先设定的参数序列。如果你对已经绑定过的函数再次使用.bind(),每次都会返回一个新的函数,并且这个新函数的 this 值会是你最近一次.bind()调用时指定的值。
function originalFunction() { console.log(this.value); } var obj1 = { value: 'this1' }; var obj2 = { value: 'this2' }; // 第一次 bind,将 this 绑定到 obj1 var boundFunction1 = originalFunction.bind(obj1); // 第二次 bind,将 this 绑定到 obj2(注意:这里不会改变 boundFunction1 的 this) var boundFunction2 = boundFunction1.bind(obj2); // 调用 boundFunction1,this 指向 obj1 boundFunction1(); // 输出 "this1" // 调用 boundFunction2,this 指向 obj2(因为我们在第二次 bind 时指定了) boundFunction2(); // 输出 "this2"
14. 小程序里面的 this 和前端 web 里面的 this 有什么不同?
- 小程序中的 this 和 Web 前端中的 this 主要区别在于上下文环境和作用域规则:小程序里的 this 通常指向当前页面或组件实例,便于访问数据和方法,在 Page 对象的方法或生命周期函数内部(如 onLoad、onShow、onReady 等),this 指向当前的 Page 对象,可以访问页面定义的 data、methods、properties 等。在自定义组件中,this 指向组件实例,可以访问组件的属性、方法等。
- Web 前端中的 this 行为更依赖于 JavaScript 的执行上下文,如在对象方法中指向对象,在事件处理器或构造函数中可能指向不同的实体
15. node 在文件处理里面 在读文件和写文件时 可能会直接 read 也可能是 readStream 流式的读取 那么流式的和直接读有什么区别? 这个流式的是个什么概念?
- 直接读取
- 过程:Node.js 会一次性将整个文件内容读入内存,然后你可以对该内容执行各种操作。
- 缺点:只适用于小文件 不适用于多文件和大文件,因为文件内容过大或过多 可能会占用大量内存,甚至超过 Node.js 进程可以分配的内存限制 导致内存不足
- 流式读取
- 使用场景:处理大文件或需要同时处理多个文件
- 过程:文件内容被分成小块(称为“数据块”或“块”),并逐个从文件中读取到内存中。然后,你可以对每个数据块执行所需的操作,并在处理完一个数据块后释放该数据块占用的内存,以便为下一个数据块腾出空间
- 优势:
- 允许你以较小的内存占用处理大文件。由于你一次只处理文件的一个小部分,因此你不需要将整个文件内容加载到内存中。
- 流式读取还允许你同时处理多个文件,因为你可以为每个文件创建一个单独的流,并在需要时释放每个流占用的内存。
const fs = require("fs"); const readableStream = fs.createReadStream("example.txt"); readableStream.on("data", (chunk) => { console.log(chunk.toString()); // 处理数据块 }); readableStream.on("end", () => { console.log("文件读取完毕"); }); readableStream.on("error", (err) => { console.error(`读取文件时出错: ${err.message}`); });
16. http 协议 握手和加密的过程有了解吗?
tcp 连接+TLs 加密
17. 怎么验证密钥的有效性?
在整个过程中,如果任何一个步骤失败(如证书验证失败、密钥交换过程中出现问题等),TLS 握手过程就会失败,并且断开所有的连接,从而确保 TLS 中对称密钥的有效性。这个过程综合了密码学中的多种技术,如非对称加密、哈希函数、数字签名等,以确保通信的安全性和密钥的有效性。
握手开始:客户端向服务器发送支持的 TLS 版本、加密套件列表和随机数等。
服务器响应:服务器从客户端提供的加密套件列表中选择一个,并发送自己的公钥证书、一个随机数以及选择的加密套件给客户端。
客户端验证:客户端验证服务器发送的公钥证书的有效性,这通常包括检查证书链、证书颁发机构(CA)的有效性以及证书是否未被撤销等。
密钥交换:一旦服务器证书验证通过,客户端会生成一个对称密钥(即预主密钥),并使用服务器的公钥对这个密钥进行加密,然后发送给服务器。只有服务器能够使用其私钥解密这个密钥。
生成会话密钥:服务器解密出对称密钥后,双方使用之前交换的随机数和这个对称密钥,通过预定的密钥派生函数(KDF)生成会话密钥(即对称密钥)。
加密通信:在握手过程完成后,客户端和服务器使用生成的会话密钥对通信内容进行加密和解密,确保通信内容的安全性和机密性。
webPack pagin 和 loader 有什么区别? Loaders 专注于文件的转换,而 Plugins 则关注于构建过程的扩展和自定义。 Loaders 处理文件级别上的转换,而 Plugins 则在更宏观的层面上影响整个构建过程。
- Loaders(加载器)
- Loaders 主要用于转换文件内容,使 Webpack 能够理解和处理不同类型的资源。例如,它们可以将 Sass 编译为 CSS,或将 TypeScript 编译为 JavaScript,甚至处理图片和字体文件。
- Loaders 是在 Webpack 解析模块依赖树的过程中被执行的,也就是说,当 Webpack 遇到一个需要加载的文件时,它会检查是否有适用的 loader 来处理该文件类型。
- Loaders 可以串联使用(链式加载),前一个 loader 的输出可以作为下一个 loader 的输入。
- Plugins(插件)
- Plugins 则更加灵活,它们可以扩展 Webpack 的功能,包括但不限于优化输出、实时编译、热模块替换、文件生成、环境变量注入等。
- Plugins 在整个 Webpack 的构建周期中都有机会介入,通过监听 Webpack 的特定事件或钩子(hooks)来触发自己的功能。
- Plugins 可以访问 Webpack 的内部 API,这意味着它们可以修改和控制 Webpack 的构建流程,甚至改变最终的输出结果。
有些过一些 webpack 插件之类的东西吗? 比如说 ac 创客 概念之类的东西?
webPack 的整个文件处理 怎么跟这些 loader 进行交互的?
- 模块解析: 当 Webpack 开始构建过程时,从一个或多个入口点(entry points)开始解析。每个入口点是一个模块,Webpack 会递归地查找并解析这个模块所依赖的所有其他模块。
- Loader 调用: 在解析过程中,每当遇到一个非 JavaScript 或 JSON 文件时,Webpack 会检查配置文件(通常是 webpack.config.js)中是否定义了相应的 Loader 来处理该类型的文件。如果找到匹配的 Loader,Webpack 将调用这个 Loader 并传递文件内容给它。
- Loader 执行顺序: 如果配置了多个 Loader,它们会按照从右到左、从下到上的顺序执行。及最后一个定义的 Loader 最先运行,而第一个定义的 Loader 最后运行。确保文件内容在被最终处理之前能被多个 Loader 依次转换。
- Loader 处理文件: Loader 收到文件内容后,可以对其进行任何必要的转换。
- babel-loader 可以将 ES6 代码转换为浏览器可识别的 ES5 代码
- css-loader 可以处理 CSS 文件中的导入语句
- file-loader 或 url-loader 可以处理图片和字体文件,将其嵌入到输出文件中或生成单独的文件。
- Loader 返回结果: Loader 完成转换后会将结果返回给 Webpack。这个返回的内容会被视为一个新的模块,然后 Webpack 继续解析这个新模块的依赖关系。
- 循环处理: Webpack 会继续解析和处理新生成的模块,直到所有的依赖都被解析和转换完成。这可能涉及到多次调用 Loader 来处理不同类型的文件。
- 打包输出: 一旦所有模块及其依赖都被处理完毕,Webpack 会将它们打包成一个或多个输出文件(chunks),这些文件可以是 JavaScript、CSS 或其他类型的资源,具体取决于配置。