【快手】深入理解前端面试题
快手一面
Vue生命周期
beforeCreate created beforeMount mounted
beforeUpdate updated
beforeDestory destoryed
Vue3移除了beforeCreate
created
两个声明周期钩子,这是因为setup发生在开始创建组件之前,在beforeCreate
和created
之前执行
可以在setup中使用的生命周期函数:onMounted
onUpdated
onUnmounted
onBeforeUpdate
这几个,
网络请求一般在什么时候发起,为什么
越早越好,一般是放在created
或onMounted
或者setup
中
created
(vue2) 此时组件内的基本数据已经创建好,组件的模板结构尚未生成mounted
(vue2)onMounted
(vue3) 组件挂载到DOM树上,可以获取到DOMsetup
(vue3) 时机要早于beforeCreated
和created
所以在setup中发起网络请求也可以
setup的执行时机相当于哪个生命周期
setup
的执行要早于beforeCreated
和created
,可以认为相当于这两个生命周期
Vue2和Vue3的响应式原理
Vue2响应式原理
全部使用Object.defineProperty()
中的set与get函数
Vue3响应式原理
ref
使用的是Object.defineProperty()
,而reactive
使用的是Proxy
Proxy
可以直接深度代理一个对象,通过设置handler中的捕获器可以对对象创建一个代理,将各种行为监听并且同步到对象本身上
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
不用设置具体属性,直接监听整个对象。defineproperty
监听需要知道是哪个对象的哪个属性,而proxy
只需要知道哪个对象就可以了。也就是会省去for in
循环提高了效率。
监听对原对象的影响
- 因为
defineproperty
是通过在原对象身上新增或修改属性增加描述符的方式实现的监听效果,一定会修改原数据。 - 而
proxy
只是原对象的代理,proxy
会返回一个代理对象不会在原对象上进行改动,对原数据无污染。
- 因为
实现对数组的监听
- 因为数组
length
的特殊性(length 的描述符configurable 和 enumerable 为 false,并且妄图修改 configurable 为 True 的话 js 会直接报错:VM305:1 Uncaught TypeError: Cannot redefine property: length)
defineproperty
无法监听数组长度变化,Vue
只能通过重写数组方法的方式变现达成监听的效果,光重写数组方法还是不能解决修改数组下标时监听的问题,只能再使用自定义的$set
的方式- 而
proxy
因为自身特性,是创建新的代理对象而不是在原数据身上监听属性,对代理对象进行操作时,所有的操作都会被捕捉,包括数组的方法和length
操作,再不需要重写数组方法和自定义set
函数了。(代码示例在下方)
4. 监听的范围
defineproperty
只能监听到value
的get set
变化。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
- 必须只使用那些不会改变引用地址的操作,否则会丢失响应性,如解构、重新赋值
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 明确规定,如果区块中存在
let
和const
命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。
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看代码写结果
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()
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常用方法
localStorage.setItem('key', value)
localStorage.getItem('key')
localStorage.removeItem('key')
localStorage.clear()
跨域解决方法
- JSONP
- CORS
- 代理服务器
JSONP
根据同源策略的限制,在端口,域名,协议这三者一个或者多个不同的情况下,就会出现跨域限制,请求发送到了服务器并且服务器也响应了数据,但是浏览器不会为你展示出来。
但是,<script>
标签访问时,可以跨越这些同源策略限制,但只能使用 GET
方法:
<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
队列检查 函数执行栈 优先级 常见的宏任务/微任务
事件循环代码运行结果
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
切换颜色
<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实现数据劫持
算法: 两数之和-修改版
从数组中找到两数之和为目标值的数字对数 不允许重复使用数字
[1, 2, 3, 4, 4], 5
结果为2
[1, 1, 2, 3, 4, 4], 5
结果为3
/**
* 从数组中找到两数之和为目标值的数字对数 不允许重复使用数字
* @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