# 【快手】深入理解前端面试题 ## 快手一面 ### Vue生命周期 `beforeCreate created beforeMount mounted` `beforeUpdate updated` `beforeDestory destoryed` Vue3移除了`beforeCreate` `created`两个声明周期钩子,这是因为setup发生在开始创建组件之前,在`beforeCreate`和`created`之前执行 可以在setup中使用的生命周期函数:`onMounted` `onUpdated` `onUnmounted` `onBeforeUpdate`这几个, ![Vue 生命周期](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a299bd2229fb45d68a1cdd8731c94449~tplv-k3u1fbpfcp-zoom-1.image) ### 网络请求一般在什么时候发起,为什么 越早越好,一般是放在`created`或`onMounted`或者`setup`中 - `created`(vue2) 此时组件内的基本数据已经创建好,组件的模板结构尚未生成 - `mounted`(vue2) `onMounted`(vue3) 组件挂载到DOM树上,可以获取到DOM - `setup`(vue3) 时机要早于`beforeCreated`和`created` 所以在setup中发起网络请求也可以 ### setup的执行时机相当于哪个生命周期 `setup`的执行要早于`beforeCreated`和`created`,可以认为相当于这两个生命周期 ### Vue2和Vue3的响应式原理 #### Vue2响应式原理 全部使用`Object.defineProperty()`中的set与get函数 #### Vue3响应式原理 `ref`使用的是`Object.defineProperty()`,而`reactive`使用的是`Proxy` `Proxy`可以直接深度代理一个对象,通过设置handler中的捕获器可以对对象创建一个代理,将各种行为监听并且同步到对象本身上 ```js const obj = { name: 'Ziu', age: 18 } const proxy = new Proxy(obj, { set: function (target, key, newVal) { console.log(`监听: ${key} 设置 ${newVal}`) target[key] = newVal }, get: function (target, key) { console.log(`监听: ${key} 获取`) return target[key] } }) ``` ### Proxy相比于defineProperty有何优势 [defineProperty 和 Proxy区别](https://segmentfault.com/a/1190000041084082) 1. **监听数据的角度** 1. `defineproperty`只能监听某个属性而不能监听整个对象。 2. `proxy`不用设置具体属性,直接监听整个对象。 3. `defineproperty`监听需要知道是哪个对象的哪个属性,而`proxy`只需要知道哪个对象就可以了。也就是会省去`for in`循环提高了效率。 2. **监听对原对象的影响** 1. 因为`defineproperty`是通过在原对象身上新增或修改属性增加描述符的方式实现的监听效果,一定会修改原数据。 2. 而`proxy`只是原对象的代理,`proxy`会返回一个代理对象不会在原对象上进行改动,对原数据无污染。 3. **实现对数组的监听** 1. 因为数组 `length` 的特殊性 `(length 的描述符configurable 和 enumerable 为 false,并且妄图修改 configurable 为 True 的话 js 会直接报错:VM305:1 Uncaught TypeError: Cannot redefine property: length)` 2. `defineproperty`无法监听数组长度变化, `Vue`只能通过重写数组方法的方式变现达成监听的效果,光重写数组方法还是不能解决修改数组下标时监听的问题,只能再使用自定义的`$set`的方式 3. 而`proxy`因为自身特性,是创建新的代理对象而不是在原数据身上监听属性,对代理对象进行操作时,所有的操作都会被捕捉,包括数组的方法和`length`操作,再不需要重写数组方法和自定义`set`函数了。(代码示例在下方) **4. 监听的范围** 1. `defineproperty`只能监听到`value`的 `get set` 变化。 2. `proxy`可以监听除 `[[getOwnPropertyNames]]` 以外所有`JS`的对象操作。监听的范围更大更全面。 #### Proxy优势 - Proxy可以直接监听整个对象而非属性。 - Proxy可以直接监听数组的变化。 - Proxy有13中拦截方法,如`ownKeys、deleteProperty、has` 等是 `Object.defineProperty` 不具备的。 - Proxy返回的是一个新对象,我们可以只操作新的对象达到目的,而`Object.defineProperty`只能遍历对象属性直接修改; - Proxy做为新标准将受到浏览器产商重点持续的性能优化,也就是传说中的新标准的性能红利。 #### Object.defineProperty优势 兼容性好,支持 IE9,而 Proxy 的存在浏览器兼容性问题,而且无法用 polyfill 磨平。 #### Object.defineProperty劣势 - `Object.defineProperty` 只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。 - `Object.defineProperty`不能监听数组。是通过重写数据的那7个可以改变数据的方法来对数组进行监听的。 - `Object.defineProperty` 也不能对 `es6` 新产生的 `Map`,`Set` 这些数据结构做出监听。 - `Object.defineProperty`也不能监听新增和删除操作,通过 `Vue.set()`和 `Vue.delete`来实现响应式的。 ### Vue3数据双向绑定原理 ### ref与reactive区别与适用场景 - ref接受的数据类型:基本类型(string number bigint boolean undefined symbol null),引用类型 - 把参数加工成一个响应式对象,全称为reference对象(简称为ref对象) - 如果参数是基本类型,那么形成响应式依赖于`Object.defineProperty()`的get()和set() - 如果ref的参数是引用类型,底层ref会借助reactive的Proxy生成代理对象,从外部依然是通过`.value`获取 - reactive只接受引用类型(Object Array) - 它的响应式是更深层次的,底层是将数据包装成一个Proxy - 参数必须是对象或者数组,如果要让对象的某个元素实现响应式时比较麻烦。需要使用toRefs - 必须只使用那些不会改变引用地址的操作,否则会丢失响应性,如解构、重新赋值 ```js const obj1 = ref({ name: 'Ziu', age: 18 }) console.log(obj1) // RefImpl console.log(obj1.value) // Proxy const obj2 = reactive({ name: 'Kobe', age: 19 }) console.log(obj2) // Proxy ``` ### Vue3功能上相比于Vue2有哪些优点 - Composition API - 生命周期钩子 - `ref() reactive()` - hook复用代码 - 不再需要根标签 (Fragments) - 性能提升 diff算法 - Proxy与defineProperty [Vue3 对比 Vue2.x 差异性、注意点、整体梳理,与React hook比又如何?](https://juejin.cn/post/6892295955844956167) ### Vue组件传参方法 - prop & emit - provide & inject - 子组件defineExpose 父组件ref获取 ### Vuex异步操作如何同时修改多个state ### ES6特性了解哪些 - let & const - 独立作用域 - 箭头函数 - 无this 无arguments对象 - Promise - 类方法、实例方法 - async await - 异步函数 生成器语法糖 ### let & const的特性 不存在变量提升 暂时性死区 > ES6 明确规定,如果区块中存在`let`和`const`命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。 ```js var tmp = 123; if (true) { tmp = 'abc'; let tmp; } console.log('tmp =', tmp); ``` ### Promise介绍一下 - 三种状态 `pending fulfilled rejected` - 类方法 `.resolve .reject .all .race .any` - 实例方法 `.then .catch .finally .allSettled` - `.then`方法两个入参 一个是上一个Promise对象resolved的回调 另一个是rejected的回调,返回一个新的Promise对象 - `.then`传入的回调函数会被加入微任务队列 - `.catch`本质上是只有错误处理函数的`.then` ### Promise.all .race .any功能及区别 传入的都是一个可迭代对象(iterable),迭代器,类似Array - Promise.all() 所有都resolved 将结果按顺序放入数组中返回 如果某个rejected则直接rejected,原因为第一个rejected的原因 - Promise.race() 一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝 - Promise.any() any方法会等到一个fulfilled状态,才会决定新的Promise状态 如果所有Promise都是reject的,那么也会等到所有Promise都变成rejected状态 ### Promise看代码写结果 ```js function testPromise() { return new Promise((resolve, reject) => { console.log('1'); resolve(2); reject(3); console.log('4'); }).then(res => { console.log('res = ', res); }, err => { console.log('err =', err); }); } testPromise(); ``` 输出结果:`1 4 res = 2` 执行`resolve`之后,当前Promise的状态变为`fulfilled`不再改变,即使后面继续调用了`reject`,后续代码继续执行输出`'4'`,随后执行微任务中的`.then`,输出`res = 2` ### 手写Promise.all() ```js function selfPromiseAll(iterable) { return new Promise((resolve, reject) => { const promises = Array.from(iterable) // 将可迭代对象转为数组 const result = [] // 保存结果 let count = 0 // 记录是否所有promise都执行完毕 // 并发执行每一个promise for (let i = 0; i < promises.length; i++) { Promise.resolve(promises[i]) .then((res) => { result[i] = res // 将结果按顺序存入result count++ // 保证每一个Promise都执行完毕后再resolve if (count === promises.length) { resolve(result) } }) .catch((err) => reject(err)) // 只要有reject 外部Promise直接reject } }) } ``` ### Cookie localStorage SessionStorage区别及使用场景 | | cookie | localStorage | sessionStorage | | --- | --- | --- | --- | | 大小 | 4Kb | 5MB | 5MB | | 兼容 | H4/H5 | H5 | H5 | | 访问 | 任何窗口 | 任何窗口 | 同一窗口 | | 有效期 | 手动设置 | 无 | 窗口关闭 | | 存储位置 | 浏览器和服务器 | 浏览器 | 浏览器 | | 与请求一起发送 | 是 | 否 | 否 | | 语法 | 复杂 | 简单 | 简单 | - Cookie是由`?key1=value1;key2=value2`组成的,可以通过`encodeURIComponent()`来保证它不包含任何逗号、分号或空格(cookie值中禁止使用这些值). - Cookie一般的字段有`path domain max-age expires secure` - 不同的host之间的localStorage、sessionStorage对象是隔离的 ### localStorage常用方法 ```js localStorage.setItem('key', value) localStorage.getItem('key') localStorage.removeItem('key') localStorage.clear() ``` ### 跨域解决方法 - JSONP - CORS - 代理服务器 #### JSONP 根据同源策略的限制,在**端口,域名,协议**这三者**一个或者多个不同的情况下**,就会出现跨域限制,请求发送到了服务器并且服务器也响应了数据,但是浏览器不会为你展示出来。 但是,` ``` ` ``` ### Proxy与defineProperty实现数据劫持 [深入理解Proxy与Reflect](https://juejin.cn/post/7179634726309724219) ### 算法: 两数之和-修改版 从数组中找到两数之和为目标值的数字对数 不允许重复使用数字 - `[1, 2, 3, 4, 4], 5` 结果为 `2` - `[1, 1, 2, 3, 4, 4], 5` 结果为 `3` ```js /** * 从数组中找到两数之和为目标值的数字对数 不允许重复使用数字 * @param {number[]} nums 数组 * @param {number} target 目标值 * @returns {number} 次数 */ function twoSum(nums, target) { // 边界情况 if (nums.length < 2) return 0 let count = 0 const map = new Map() for (let i = 0; i < nums.length; i++) { const num = nums[i] const diff = target - num const value = map.get(diff) // undefined | number // 如果找到了与diff相等的num if (value !== undefined && value > 0) { count++ map.set(diff, value - 1) // 剩余数字个数-1 } else { // 未找到 则将num设为键 值为出现次数 const count = map.get(num) === undefined ? 1 : map.get(num) + 1 map.set(num, count) } } return count } console.log(twoSum([1, 2, 3, 4, 4], 5)) // 2 console.log(twoSum([1, 1, 2, 3, 4, 4], 5)) // 3 ```