Skip to content

【快手】深入理解前端面试题

快手一面

Vue生命周期

beforeCreate created beforeMount mounted

beforeUpdate updated

beforeDestory destoryed

Vue3移除了beforeCreate created两个声明周期钩子,这是因为setup发生在开始创建组件之前,在beforeCreatecreated之前执行

可以在setup中使用的生命周期函数:onMounted onUpdated onUnmounted onBeforeUpdate这几个,

Vue 生命周期

网络请求一般在什么时候发起,为什么

越早越好,一般是放在createdonMounted或者setup

  • created(vue2) 此时组件内的基本数据已经创建好,组件的模板结构尚未生成
  • mounted(vue2) onMounted(vue3) 组件挂载到DOM树上,可以获取到DOM
  • setup(vue3) 时机要早于beforeCreatedcreated 所以在setup中发起网络请求也可以

setup的执行时机相当于哪个生命周期

setup的执行要早于beforeCreatedcreated,可以认为相当于这两个生命周期

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区别

  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只能监听到valueget 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比又如何?

Vue组件传参方法

  • prop & emit
  • provide & inject
  • 子组件defineExpose 父组件ref获取

Vuex异步操作如何同时修改多个state

ES6特性了解哪些

  • let & const
    • 独立作用域
  • 箭头函数
    • 无this 无arguments对象
  • Promise
    • 类方法、实例方法
  • async await
    • 异步函数 生成器语法糖

let & const的特性

不存在变量提升 暂时性死区

ES6 明确规定,如果区块中存在letconst命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。

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
      }
    })
  }
cookielocalStoragesessionStorage
大小4Kb5MB5MB
兼容H4/H5H5H5
访问任何窗口任何窗口同一窗口
有效期手动设置窗口关闭
存储位置浏览器和服务器浏览器浏览器
与请求一起发送
语法复杂简单简单
  • 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

根据同源策略的限制,在端口,域名,协议这三者一个或者多个不同的情况下,就会出现跨域限制,请求发送到了服务器并且服务器也响应了数据,但是浏览器不会为你展示出来。

但是,<script> 标签访问时,可以跨越这些同源策略限制,但只能使用 GET 方法:

html
<script>
  function jsonCallback(data) {
    console.log(data)
  }
</script>
<script src="https://c.y.qq.com/qzone/fcg-bin/fcg_ucc_getcdinfo_byids_cp.fcg"></script>

<script>标签返回的是一段由函数jsonCallback包裹的数据,这样在页面中就可以拿到跨域的数据了

介绍一下事件循环

主线程 宏任务 微任务 EventTable EventQueue

队列检查 函数执行栈 优先级 常见的宏任务/微任务

事件循环代码运行结果

js
setTimeout(() => {
  console.log('time1')
  Promise.resolve()
    .then(() => {
      console.log('promise1')
      process.nextTick(() => {
        console.log('next tick1')
      })
    })
    .catch((err) => {
      console.log(err)
    })
})

setTimeout(() => {
  console.log('time2')
  new Promise((res) => {
    res()
    console.log('promise2')
    process.nextTick(() => {
      console.log('next tick2')
    })
  }).then(() => {
    console.log('promise3')
  })
})

输出顺序: time1 promise1 next tick1 time2 promise2 next tick2 promise3

介绍一下Flex布局

  • 主轴 交叉轴
  • flex container
    • 应用在flex container上的属性
  • flex item
    • 应用在flex item上的属性
    • flex: 1含义

快手二面

CSS主题切换方案

  • 将颜色变量抽取为CSS Variable
  • 使用:root选择器定义变量
  • 根据根组件不同的class切换颜色
html
<style>
  :root {
    --text-color: #1f1f1f;
    --bg-color: #f5f5f5;
    --primary-color: #1890ff;
  }
  :root.dark {
    --text-color: #f5f5f5;
    --bg-color: #1f1f1f;
    --primary-color: #1890ff;
  }
  .card {
    color: var(--text-color);
    background-color: var(--bg-color);

    padding: 10px;
    max-width: 300px;
    transition: all 0.2s;
  }
  .card h2 {
    color: var(--primary-color);
  }
</style>

<div>
  <button id="dark-mode">Toggle Dark Mode</button>
  <div class="card">
    <h2>跨站脚本攻击(XSS)</h2>
    <p>
      Cross-Site
      Scripting(跨站脚本攻击)简称XSS,是一种代码注入攻击。攻击者通过在目标网站上注入恶意脚本,使之在用户的浏览器上运行。利用这些恶意脚本,攻击者可获取用户的敏感信息如
      Cookie、SessionID 等,进而危害数据安全。 为了和 CSS 区分,这里把攻击的第一个字母改成了
      X,于是叫做 XSS。
    </p>
  </div>
</div>

<script>
  const root = document.querySelector('html')
  const btn = document.querySelector('#dark-mode')
  btn.addEventListener('click', () => {
    root.classList.toggle('dark')
  })
</script>

Proxy与defineProperty实现数据劫持

深入理解Proxy与Reflect

算法: 两数之和-修改版

从数组中找到两数之和为目标值的数字对数 不允许重复使用数字

  • [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

Released under the MIT License.