热乎的前端面试题(昨天)🏷️
1.Vue 响应式原理?
- •
vue2
:
- • 在
Vue2
中注意使用Object.defineProperty()
方法来实现响应式,它为对象中的每一个属性都定义了一个getter
和setter
,当数据发生变化时,会触发相应的更新操作,从而让视图也随之更新。 - • 在
Vue2
中,数据对象通过new Vue
中的data
对象进行实例化,在实例化的过程中,Vue
会遍历这个数据对象使用Object.defineProperty()
把他们转化为getter/setter
,当getter/setter
接收到访问或修改时,会通过对应的Watcher
及所依赖的Watcher
进行更新。
- •
vue3
:
- • 在 vue3 中主要使用了
Reactive API
和Proxy
来实现响应式 - •
reactive()
函数会讲一个普通对象转换成响应式对象,这个函数内部使用了ES6
的proxy
对象,讲的对象的getter/setter
, 并使用依赖收集,派发更新等机制。 - • 同时增加了
ref
和computed
这两个响应式API
,让我们可更灵活的管理组件内的数据状态。
2.Vue是如何监听数组的变化的?
- • Vue 通过把数组的原生方法重写了
- 1. 获取原生数组的原型方法,因为拦截后才能对数组添加拦截操作
- 2. 对数组的原型方法使用
Object.defineProperty()
做一些拦截操作 - 3. 把需要被拦截的数组类型的数据原型指向改造后的原型方法
const arrayProto = Array.prototype // 获取Array的原型 function def (obj, key) { Object.defineProperty(obj, key, { enumerable: true, configurable: true, value: function(...args) { console.log(key); // 控制台输出 push console.log(args); // 控制台输出 [Array(2), 7, "hello!"] // 获取原生的方法 let original = arrayProto[key]; // 将开发者的参数传给原生的方法,保证数组按照开发者的想法被改变 const result = original.apply(this, args); // do something 比如通知Vue视图进行更新 console.log('我的数据被改变了,视图该更新啦'); this.text = 'hello Vue'; return result; } }); } // 新的原型 let obj = { push() {} } // 重写赋值 def(obj, 'push'); let arr = [0]; // 原型的指向重写 arr.__proto__ = obj; // 执行push arr.push([1, 2], 7, 'hello!'); console.log(arr);
3.vue 为什么不能检测数组变动?
- • 通常情况下,vue 通过对每一个键设置
getter/setter
来实现响应式,没有对数组的每个键设置响应式,而是直接对值递归设置响应式。主要考虑到性能问题。
4.canvas 生成图片模糊问题?
- • 没有使用整数坐标:可使用
Math.floor
将坐标强制转换为整数。 - • 线条宽度设置的太小,可将
lineWidth
设置为整数值
5.vue 的数据劫持?
- • Vue2:
- • 使用 Object.defineProperty() 的 getter/setter
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Vue2数据劫持Demo</title> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app"> <!-- 在这个demo中,我们首先创建了一个Vue实例,然后在mounted钩子函数中,遍历data对象的所有属性,并使用defineReactive方法来对每个属性进行数据劫持。在defineReactive方法中,我们使用Object.defineProperty方法来重新定义属性的set和get方法。在set方法中,我们实现了自己的逻辑,例如在属性变化时强制更新视图。 接下来,我们在页面中展示了data中的一个属性message,并提供了一个按钮,点击按钮时会修改message属性的值。当message属性发生变化时,会触发我们自定义的set方法,从而实现了数据的响应式更新。 需要注意的是,在这个demo中我们使用了Vue的forceUpdate方法来强制更新视图。这个方法会强制vue 重新渲染视图,从而显示最新的数据。虽然这个方法可以解决数据更新不及时的问题,但是它会导致性能上的一些损失。因此,在实际开发中,我们应该尽量减少使用 forceUpdate方法,而是通过合理的设计数据结构来避免这个问题。 --> <p>{{ message }}</p> <button @click="changeMessage">Change Message</button> </div> <script> var vm = new Vue({ el: "#app", data: { message: "Hello world!", }, mounted: function () { var self = this; Object.keys(this.$data).forEach(function (key) { self.defineReactive(self.$data, key, self.$data[key]); }); }, methods: { defineReactive: function (obj, key, val) { var self = this; Object.defineProperty(obj, key, { get: function () { console.log("get " + key + ": " + val); return val; }, set: function (newVal) { console.log("set " + key + ": " + newVal); val = newVal; self.$forceUpdate(); // 强制更新视图 }, }); }, changeMessage: function () { this.message = "Hello Vue!"; }, }, }); </script> </body> </html>
- • Vue3:
- • 使用 proxy 来实现数据劫持
- 1. proxy 可直接监听数组的修改
- 2. proxy 可直接监听属性的新增和删除
- 3. 在实现深层次监听的时候,不是一次性递归所有的属性为其添加监听,而是只有在数据对象属性被访问的时候才会添加
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Vue3数据劫持Demo</title> <script src="https://unpkg.com/vue@next"></script> </head> <body> <div id="app"> <!-- 在这个demo中,我们首先创建了一个data对象,其中包含一个属性message。然后,我们使用Proxy方法创建了一个名为reactiveData的代理对象,并在set方法中添加了自定义的逻辑。在这个demo中,我们只是简单地在控制台中输出了一些调试信息,但是在实际开发中,我们可以在set方法中添加任何我们需要执行的逻辑,例如更新视图或向服务器发送请求等。 接下来,我们使用Vue.createApp方法创建了一个Vue实例,并将reactiveData对象作为data选项传递给它。这样,我们就实现了对data对象中属性的监听。在页面中展示了data中的一个属性message,并提供了一个按钮,点击按钮时会修改message属性的值。当message属性发生变化时,会触发我们自定义的set方法,从而实现了数据的响应式更新。 需要注意的是,在这个demo中我们没有使用$forceUpdate方法来强制更新视图。这是因为Vue3中的数据响应式系统已经得到了优化,并且可以更好地处理数据更新的问题,因此我们不再需要手动强制更新视图。 --> <p>{{ message }}</p> <button @click="changeMessage">Change Message</button> </div> <script> const data = { message: "Hello world!", }; const reactiveData = new Proxy(data, { set(target, key, value) { console.log(`set \${key}: \${value}`); target[key] = value; return true; }, }); const app = Vue.createApp({ data() { return reactiveData; }, methods: { changeMessage() { this.message = "Hello Vue!"; }, }, }); app.mount("#app"); </script> </body> </html>
6.给定一个对象数组,如何根据另一个对象数组对另一个去重?
- • 可使用 filter 方法结合 map 和 includes 方法来实现根据另一个对象数组去重操作
let originalArr = [{name: 'John', age: 26}, {name: 'Mary', age: 22}, {name: 'Peter', age: 28}]; let filterArr = [{name: 'John', age: 26}, {name: 'Bob', age: 25}]; let filteredOriginalArr = originalArr.filter((obj) => { return !filterArr.map((item) => item.name).includes(obj.name); }); console.log(filteredOriginalArr); // Output: [{name: 'Mary', age: 22}, {name: 'Peter', age: 28}] // originalArr 表示要被筛选的对象数组 // filterArr 则是用来进行筛选的参照数组 // filter 方法将会按条件对 originalArr 进行筛选,并返回结果存储在 filteredOriginalArr 中。
特殊字符描述
•问题标注 Q:(question)
•答案标注 R:(result)
•注意事项标准:A:(attention matters)
•详情描述标注:D:(detail info)
•总结标注:S:(summary)
•分析标注:Ana:(analysis)
•提示标注:T:(tips)