🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
[TOC] ## vue 的 computed / watch 的区别 从其用途来看: 计算属性用于 HTML 模板中,用于代替模板表达式`{{ }}}`中的一些复杂逻辑; 侦听属性是一种通用的用于观察和响应 Vue 实例上的数据变动的方式; 其书写方式如下: ```js var vm = new Vue({ el: '#example', data: { message: 'Hello', firstName: 'Foo', }, computed: { // 计算属性的 getter reversedMessage: function () { // `this` 指向 vm 实例 return this.message.split('').reverse().join('') } }, watch: { // 监听的属性和其发生变化后触发的回调 firstName: function (val) { this.fullName = val + ' ' + this.lastName } } }) ``` 就应用场景而言,计算属性适合用在模板渲染中,某个值是依赖了其它的响应式对象甚至是计算属性计算而来;而侦听属性适用于观测某个值的变化去完成一段复杂的业务逻辑。 从其特性来看: 这里主要考虑页面重新渲染时两者的区别,当页面重新渲染(不是刷新)的时候,计算属性不会变化,直接读取缓存使用,只有在相关依赖发生改变时才会重新求值。而侦听属性在页面重新渲染时即使值不变化也会执行。 >什么才算页面重新渲染? [从源码的角度来分析](https://ustbhuangyi.github.io/vue-analysis/prepare/flow.html): ......好吧我讲不出来让面试官给我讲讲吧 ## webpack 相比 rollup 有哪些优点、缺点? 目前 Vue、React 等一些有名的 JS 库都是采用 rollup 来进行打包的,比较流行的说法是 rollup 更适合构建 JavaScript 库而 webpack 更适合构建前端应用。 - 如果你确实需要代码分割特性,或者你有很多静态资源需要处理,再或者你构建的项目需要引入很多 CommonJS 模块的依赖,那么 Webpack 是个很不错的选择。 - 如果你的代码是基于 ES2015 模块,而且希望你写的代码能够被其他人直接使用,你需要的打包工具可能是 Rollup。 回到这个题目:webpack 相比 rollup 有以下的优点 - 支持热模块更新 - 代码分割功能强大,利于处理 CommonJS 模块的依赖 - 扩展性强、插件机制完善,利于处理多种不同格式的静态资源 有以下缺点: - 构建速度较慢 - 配置较复杂 关于为何 rollup 构建速度更快可以参考其 [中文文档](https://www.rollupjs.com/guide/zh#tree-shaking) ## 为什么实际应用中 Last-Modified 和 etag 都会使用到? 主要是因为如果文件内容被修改了但是实际上并没有变化,比如删掉一个字符 a 又填上去,该文件的 Last-Modified 会发生变化,但是 Etag 不会变。 下面贴两段介绍吧: ☝ Last-Modified / If-Modified-Since:`Last-Modified`表示本地文件最后修改日期,`If-Modified-Since`会将上次从服务器获取的`Last-Modified`的值发送给服务器,询问服务器在该日期后资源是否有更新,有更新的话就会将新的资源发送回来。 但是如果(服务器)在本地打开缓存文件(或者删了个字符 a 后又填上去),就会造成`Last-Modified`被修改,所以在 HTTP / 1.1 出现了`ETag`。 ☝ Etag / If-None-Match:`ETag`类似于文件指纹,`If-None-Match`会将当前`ETag`发送给服务器,询问该资源`ETag`是否变动,有变动的话就将新的资源发送回来。并且`ETag`优先级比`Last-Modified`高。 由于 etag 要使用少数的字符表示一个不定大小的文件(如 Etag: "58c4e2a1-f7"),所以 etag 是有重合的风险的,如果网站的信息特别重要,连很小的概率如百万分之一都不允许,那么就不要使用 etag 了。使用 etag 的代价是增加了服务器的计算负担,特别是当文件比较大时。 ## node 的基本特点?异步 I/O 的优势?如何读取大文件? - 基本特点 - 异步 I/O:Node 在底层构建了很多异步 I/O 的 API,从文件读取到网络请求,极大地提升了程序性能 - 事件与回调函数:配合异步 I/O,将事件点暴露给业务逻辑。这种事件的编程方式具有轻量级、松耦合、只关注事务点等优势 - 单线程:单线程的最大好处是不用像多线程编程那样处处在意状态的同步问题,没有死锁的存在,也没有线程上下文交换所带来的性能上的开销。但是它也有以下弱点(child process 模块出现后都得到了解决或缓解): - 无法利用多核 CPU - 错误会引起整个应用退出,应用的健壮性值得考验 - 大量计算占用 CPU 导致无法继续调用异步 I/O - 跨平台:借助 libuv 实现跨平台运行 - 异步 I/O 的优势:Node 面向网络并且擅长并行 I/O,能够有效地组织起更多的硬件资源,在 I/O 密集型场景表现还是不错的 - 利用 stream 模块分批次地读取大文件 ```js // 复制文件 const fs = require('fs') const path = require('path') const fileName1 = path.resolve(__dirname, 'data.txt') const fileName2 = path.resolve(__dirname, 'data-bak.txt') const readStream = fs.createReadStream(fileName1) const writeStream = fs.createWriteStream(fileName2) readStream.pipe(writeStream) // 可读流调用 pipe 方法让数据通过管道从可读流流入可写流 readStream.on('data', chunk => { console.log(chunk) console.log(chunk.toString()) // 可以看到文件是分批次地读取的 }) readStream.on('end', () => { console.log('copy done') }) ``` ## 如果动态给 Vue 实例添加属性,如何监听到新增属性的变化? 使用 Vue 提供的 API `Vue.set( target, propertyName/index, value )` 向响应式对象中添加一个属性,并确保这个新属性同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新属性,因为 Vue 无法探测普通的新增属性 (比如`this.myObject.newProperty = 'hi'`) 注意这里的响应式对象(target)是 data 选项中已存在的对象。 可以参考这篇文章:[https://www.jianshu.com/p/71b1807b1815](https://www.jianshu.com/p/71b1807b1815) ## 列举几个在 web 中实现长连接的技术方案 什么是长连接?长连接就是让 HTTP 协议所需要使用的 TCP 连接保持不断开的状态,因为对每个请求都频繁地建立和断开 TCP 连接会造成很大的时间开销。 - 响应头 keep-alive:自 HTTP/1.1 起,默认使用长连接,即自动添加该响应头 - 轮询 - websocket [https://www.cnblogs.com/huchong/p/8595644.html](https://www.cnblogs.com/huchong/p/8595644.html) ## 不同子域如何共享一段 js 脚本? `a.test.com`和`b.test.com`应该算不同子域吧?这个问题考察的是跨域技巧吗?只需要给页面添加`document.domain = 'test.com'`表示二级域名都相同就可以实现跨域。 ## vuex 的设计思想?与 redux 的区别? 在 Vuex 中,store 被直接注入到了所有的组件实例中,因此可以比较灵活的使用: * 使用 dispatch 和 commit 提交更新 * 通过 mapState 或者直接通过 this.$store 来读取数据 在 Redux 中,我们每一个组件都需要显式地用 connect 把需要的 props 和 dispatch 连接起来。 从实现原理上来说,最大的区别是两点: * Redux 使用的是不可变数据,而 Vuex 的数据是可变的。Redux 每次都是用新的 state 替换旧的 state,而 Vuex 是直接修改 * Redux 在检测数据变化的时候,是通过 diff 的方式比较差异的,而 Vuex 其实和 Vue 的原理一样,是通过 getter/setter 来比较的(如果看 Vuex 源码会知道,其实他内部直接创建一个 Vue 实例用来跟踪数据变化) 而这两点的区别,其实也是因为 React 和 Vue 的设计理念上的区别。React 更偏向于构建稳定大型的应用,非常的科班化。相比之下,Vue 更偏向于简单迅速的解决问题,更灵活,不那么严格遵循条条框框。因此也会给人一种大型项目用 React,小型项目用 Vue 的感觉。(这是别人的总结哦:[https://juejin.im/post/5b8b56e3f265da434c1f5f76#heading-0](https://juejin.im/post/5b8b56e3f265da434c1f5f76#heading-0)) ## 设计一个秒杀系统需要注意什么问题? 也可以这么说:假设一万人抢一个东西,前端需要注意些什么? 其实这个问题更适合完全做后端的同学来答... 因为我也没做过秒杀系统所以就 copy 以下别人的回答吧:[https://juejin.im/post/595b5a8a5188250da1276a95](https://juejin.im/post/595b5a8a5188250da1276a95) 前端可能的状态: - 秒杀开始前,秒杀按钮灰掉为“未开始”,不可点击。 - 秒杀进行中,秒杀按钮可以点击下单。 - 秒杀结束后,秒杀按钮灰掉为“已结束”,不可点击。 具体的需求: 1、秒杀产品的介绍,详情,参数等等,全部静态化,切勿通过后台 API 查询更新,减轻后端的压力。 2、用户点击“下单”后,按钮置灰,禁止用户重复提交请求,限制用户在 60 秒之内只能提交一次请求。 3、下单的 URL 在活动开始前不可露出或者生效,否则容易被使用工具绕过浏览器提前下单。导致活动还未开始,已经开始下单这个大黑洞。正确的做法是活动开始前,通过更新 JS 文件露出下单的 URL(这个怎么做到?)。 4、下单过程中,涉及到订单参数的修改全部关掉,比如,购买的金额,产品的份额等等,降低订单服务的压力。 ## vue 3.0 使用 proxy 重构的原因? 参考链接:[https://segmentfault.com/a/1190000015483195](https://segmentfault.com/a/1190000015483195) 首先罗列`Object.defineProperty()`的缺点: 1、`Object.defineProperty()`不会监测到数组引用不变的操作(比如`push/pop`等); 2、`Object.defineProperty()`只能监测到对象的属性的改变, 即如果有深度嵌套的对象则需要再次给之绑定`Object.defineProperty()`; 关于`Proxy`的优点 1、可以劫持数组的改变; 2、`defineProperty`是对属性的劫持,`Proxy`是对对象的劫持; 为了加深理解,我们来举几个例子: 先来看看为什么说`Object.defineProperty()`不能监测到数组变化: ```js const data = { list: [] } Object.keys(data).forEach(function (key) { let value = data[key] Object.defineProperty(data, key, { enumerable: true, configurable: true, get () { return value }, set (newValue) { console.log('set value') value = newValue return true } }) }) // 所谓对数组的监控,即能检测到数组元素的增加和删除 data.list.push(1) // nothing happen console.log(data.list) data.list = [2, 3] // set value console.log(data.list) ``` 当监控数组数据对象的时候,实质上就是监控数组的地址,地址不变也就不会被监测到,所以我们向 list 里 push 元素的时候并没有触发打印;当我们直接替换 list 对象的时候就触发了打印。所以这就是`Object.defineProperty`在数组监控方面的不足。 我们总是说 Vue 使用 hack 方法弥补了这个缺陷,那么看下其大致思路: ```js let arrayMethod = Object.create(Array.prototype); ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].forEach(function (method) { Object.defineProperty(arrayMethod, method, { enumerable: true, configurable: true, value: function () { let args = [...arguments] Array.prototype[method].apply(this, args); console.log(`operation: ${method}`) dep.notify(); } }) }); [Array Object].__proto__ = arrayMethod; ``` 其核心思想就是`覆写`数组对象中的方法,在调用数组方法的同时能触发回调。同时这样做的好处是,仅仅是数据中的数组对象的原型被修改掉了,并不会影响到全局的数组对象。 最后用 proxy 来试下其是否能检测到数组元素变化: ```js const data = { list: [1, 2, 3, 4] } const proxyArr = new Proxy (data.list, { get: function (target, key, receiver) { return Reflect.get(target, key, receiver) }, set: function (target, key, value, receiver) { console.log(target, key, value, receiver) return Reflect.set(target, key, value, receiver) } }) proxyArr.push(2) // [ 1, 2, 3, 4 ] '4' 2 [ 1, 2, 3, 4 ] // [ 1, 2, 3, 4, 2 ] 'length' 5 [ 1, 2, 3, 4, 2 ] proxyArr.shift() /* [ 1, 2, 3, 4 ] '4' 2 [ 1, 2, 3, 4 ] [ 1, 2, 3, 4, 2 ] 'length' 5 [ 1, 2, 3, 4, 2 ] [ 1, 2, 3, 4, 2 ] '0' 2 [ 1, 2, 3, 4, 2 ] [ 2, 2, 3, 4, 2 ] '1' 3 [ 2, 2, 3, 4, 2 ] [ 2, 3, 3, 4, 2 ] '2' 4 [ 2, 3, 3, 4, 2 ] [ 2, 3, 4, 4, 2 ] '3' 2 [ 2, 3, 4, 4, 2 ] [ 2, 3, 4, 2, <1 empty item> ] 'length' 4 [ 2, 3, 4, 2, <1 empty item> ] */ ``` 可以看到确实可以监测到数组元素变化。 ## 页面出现长时间白屏,怎么办? [https://juejin.im/post/5bee7dd4e51d451f5b54cbb4](https://juejin.im/post/5bee7dd4e51d451f5b54cbb4) ## babel 的转译原理? 详细的可以阅读这篇文章:[JavaScript 深入理解 Babel 原理及其使用](https://www.jianshu.com/p/e9b94b2d52e2) babel 是一个转译器,相对于编译器 compiler,叫转译器 transpiler 更准确,因为它只是把同种语言的高版本规则翻译成低版本规则,而不像编译器那样,输出的是另一种更低级的语言代码。 但是和编译器类似,babel 的转译过程也分为三个阶段:`parsing`、`transforming`、`generating`,以 ES6 代码转译为 ES5 代码为例,babel 转译的具体过程如下: >[success]ES6 代码输入 → babylon 进行解析 → 得到 AST → plugin 用 babel-traverse 对 AST 树进行遍历转译 → 得到新的 AST 树 → 用 babel-generator 通过 AST 树生成 ES5 代码 此外,还要注意很重要的一点就是,babel 只是转译新标准引入的语法,比如 ES6 的箭头函数转译成 ES5 的函数;而新标准引入的新的原生对象,部分原生对象新增的原型方法,新增的 API 等(如 Proxy、Set 等),这些 babel 是不会转译的。需要用户自行引入 polyfill 来解决。