阿里子公司面试题都这么底层的吗?最近的秋招提前期陆陆续续开始了,最近面试了一家阿里旗下的医疗子公司。经历了一面和二面两次
前言
最近的秋招提前期陆陆续续开始了,最近面试了一家阿里旗下的医疗子公司。经历了一面和二面两次发现面试官问的问题对于对于初入职场的实习生还是十分务实且重要的,问的问题也比较常见,这里给大家分享一下了。
Vue2与vue3中响应式原理分别是什么?
只要你说你的技术栈中有Vue,那么这题你回答不上来,面试的话大概率是会被提前结束了。
要回答这个问题,我们先需要清楚Vue2与Vue3的响应式的实现分别是通过Object.defineProprty
与Proxy
来进行的数据劫持的,那他们有什么区别呢?
- Vue 2 的响应式机制依赖于
Object.defineProperty
,它通过遍历对象属性并设置 getter 和 setter 来实现数据的响应式。这种方法在处理对象属性时有一定的局限性,尤其是在数组操作和新增属性方面。
并且由于Object.defineProperty
是只能监视单个对象或者属性,因此我们在绑定响应式时需要循坏对象中的属性来进行响应式的绑定,这也是为什么Vue2中的数据放在data内部来进行响应式的绑定,而后续添加的属性则不会具有响应式的能力。并且既讲到了响应式原理,那么一定要把数据的双向绑定实现一起说了。
vue数据的双向绑定 = 发布订阅者模式 + 数据响应式
vue双向绑定的工作流程
- 初始化响应式数据: Vue 在初始化时会将数据对象转换成响应式的。在 Vue 2 中,这是通过递归遍历对象属性并使用
Object.defineProperty
来完成的;而在 Vue 3 中,则是通过Proxy
来实现的。 - 收集依赖: 当数据首次被读取时,Vue 会记录哪些渲染函数依赖于这些数据。这通常发生在组件的渲染过程中。
- 数据变化: 当响应式数据发生变化时setter函数被启动,Vue 会通知所有依赖这些数据的渲染函数。
- 重新渲染: 依赖数据变化的渲染函数会被重新执行,从而触发视图的更新。
如果你阅读过Vue2的源码你就会发现:vue2使用Watcher监视vue实例对象,然后将其传入Watcher,同时内部使用Observer中walk循环调用来对defineProperty对数据进行劫持,将Watcher中的updata数据修改方法方法传入Dep依赖中,最后在修改数据是,会在defineproperty中的setter内部嗲用此方法数据进行修改并通知热更新。
双向绑定原理:
// Observer 类负责将数据转换为响应式的
function Observer(value) {
this.value = value;
this.walk(this.value);
}
Observer.prototype.walk = function (obj) {
const keys = Object.keys(obj);
keys.forEach(key => {
this.convert(key, obj[key]);
});
};
Observer.prototype.convert = function (key, val) {
defineReactive(this.value, key, val);
};
// defineReactive 函数用于定义响应式属性
function defineReactive(obj, key, val) {
const dep = new Dep();
Object.defineProperty(obj, key, {
get() {
const target = Dep.target;
if (target) {
dep.depend();
}
return val;
},
set(newVal) {
if (newVal === val) return;
val = newVal;
dep.notify();
}
});
}
// Dep 类用于管理观察者
function Dep() {
this.subs = [];
}
Dep.target = null;
Dep.prototype = {
depend() {
if (Dep.target) {
this.addSub(Dep.target);
}
},
addSub(sub) {
this.subs.push(sub);
},
notify() {
this.subs.forEach(sub => sub.update());
}
};
// Watcher 类用于监视数据变化
class Watcher {
constructor(vm, expr, cb) {
this.vm = vm;
this.expr = expr;
this.cb = cb;
this.value = this.get();
observe(vm.$data);
}
get() {
Dep.target = this;
const value = this.vm.$data[this.expr];
return value;
}
update() {
const newValue = this.vm.$data[this.expr];
if (newValue !== this.value) {
this.value = newValue;
this.cb(newValue);
}
}
}
// Vue 类的简化版本
class Vue {
constructor(options) {
this.$options = options;
this.$data = options.data;
// observe(this.$data);
this.$watchers = [];
}
}
function observe(value) {
if (!value || typeof value !== 'object') {
return;
}
return new Observer(value);
}
// 示例
const vm = new Vue({
data: {
message: 'Hello Vue!'
}
});
const watcher = new Watcher(vm, 'message', newValue => {
console.log('Message changed:', newValue);
});
console.log(vm.$data.message) //调用一次来触发getter
vm.$data.message = 'Hello Vue 2!'; // 触发更新,打印:Message changed: Hello Vue 2!
-
Vue 3 使用了
Proxy
来实现更强大的响应式机制。通过Proxy
,Vue 3 可以更高效地处理对象和数组的变化,提供更好的性能和灵活性。 -
首次修改未被访问过的属性:
- 在 Vue 3 中,即使一个属性从未被访问过,当首次修改这个属性时,
Proxy
的set
方法仍然会被调用。 - 这意味着即使属性从未被访问过,也可以触发响应式更新。
- 在 Vue 3 中,即使一个属性从未被访问过,当首次修改这个属性时,
-
响应式更新:
- 当首次修改未被访问过的属性时,虽然没有依赖被收集,但由于
Proxy
的特性,仍然会调用trigger
函数来尝试通知依赖者。 - 由于没有依赖者存在,这个尝试不会导致任何更新发生。但下次当这个属性被访问时,依赖收集机制会正常工作。
- 当首次修改未被访问过的属性时,虽然没有依赖被收集,但由于
这里响应式以reactive
来为例解析vue3响应式:
let activeEffect;
const targetMap = new WeakMap();
// 跟踪依赖
function track(target, key) {
if (!activeEffect) return;
let depsMap = targetMap.get(target);
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()));
}
let dep = depsMap.get(key);
if (!dep) {
depsMap.set(key, (dep = new Set()));
}
dep.add(activeEffect);
}
// 触发更新
function trigger(target, key) {
const depsMap = targetMap.get(target);
if (!depsMap) return;
const effects = depsMap.get(key);
if (effects) {
effects.forEach(effectFn => effectFn());
}
}
// 创建一个副作用函数,它会自动追踪依赖并在数据变化时重新执行。
function reactive(target) {
return new Proxy(target, {
get(target, key, receiver) {
track(target, key);
console.log('---------')
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
Reflect.set(target, key, value, receiver);
console.log('!!!!!!!!!!!!')
trigger(target, key);
return true;
}
});
}
// 使用 cleanup 函数来清理不再需要的依赖关系。
function effect(fn, options = {}) {
const effectFn = () => {
activeEffect = effectFn;
cleanup(effectFn);
const res = fn();
cleanup(effectFn);
activeEffect = null;
return res;
};
effectFn.options = options;
effectFn.deps = [];
effectFn();
return effectFn;
}
function cleanup(effectFn) {
for (const dep of effectFn.deps) {
dep.delete(effectFn);
}
effectFn.deps.length = 0;
}
const state = reactive({
message: 'Hello Vue!'
});
effect(() => {
console.log(`Message is ${state.message}`);
});
setTimeout(() => {
state.message = 'Hello Vue 3!';
}, 1000);
谈谈你对React的理解
面试问到这个,我们要在知道React的特点的前提下,再去讲解一些Vue的对应特点。
-
声明式编程:
- React 采用声明式编程模型,开发者描述 UI 应该是什么样子,而不是描述如何改变 UI。
- 通过 JSX 语法糖,React 使得 HTML 和 JavaScript 的混合变得自然。
-
组件化:
- React 强调组件化开发,每个组件负责 UI 的一小部分,并且可以复用。
- 组件之间通过 props 和 state 进行通信。
-
虚拟 DOM:
- React 使用虚拟 DOM 来提高性能。
- 通过比较前后两次渲染得到的虚拟 DOM 树,React 只会更新必要的部分,减少浏览器重排和重绘的成本。
-
单向数据流:
- React 推崇单向数据流,数据从父组件向下传递到子组件,状态管理清晰。
- 这种模式有助于避免复杂的数据流问题,并使调试更容易。
-
Hooks:
- React Hooks 是 React 16.8 版本引入的新特性,允许在不编写类组件的情况下使用 state 和其他 React 特性。
- Hooks 如
useState
,useEffect
,useContext
等简化了函数组件的状态管理和副作用逻辑。
React中Hooks式开发对于模板类开发的优点
这个我都问奇怪了,这么大的范围,我就也只说了一些,这里给大家找了一些区别和优点:
1. 复用状态逻辑
- Hooks 允许你从不同的函数组件中复用状态逻辑,这使得代码更加模块化并且易于维护。
- Class Components 要实现复用逻辑通常需要使用高阶组件 (HOCs) 或者 Render Props,这些方法往往不如直接使用 Hooks 来得直观和简洁。
2. 更简单的状态管理
- Hooks 如
useState
和useReducer
提供了一种简单的方式来管理组件内部的状态。 - Class Components 中的状态管理通常涉及到
this.state
和this.setState
的使用,这可能会导致一些常见的错误,例如忘记绑定this
的上下文。
3. 更少的样板代码
- Hooks 减少了在函数组件中设置状态和生命周期逻辑所需的样板代码量。
- Class Components 需要定义类、构造函数、绑定方法等,这增加了额外的样板代码。
4. 更简单的生命周期管理
- Hooks 如
useEffect
提供了一种简洁的方式来处理副作用逻辑,比如数据获取、订阅或手动更改 DOM。 - Class Components 需要在多个生命周期方法(如
componentDidMount
,componentDidUpdate
,componentWillUnmount
)中分散副作用逻辑。
5. 更好的可读性和可测试性
- Hooks 使得函数组件的结构更加清晰,逻辑更加集中,这有助于提高代码的可读性和可测试性。
- Class Components 的生命周期方法和状态逻辑可能会分散在整个类中,这可能让测试变得更加困难。
6. 更简单的错误处理
- Hooks 如
useErrorBoundary
可以帮助处理错误边界,使得错误处理更加直观。 - Class Components 中的错误边界需要实现
getDerivedStateFromError
和componentDidCatch
方法,这可能会让错误处理逻辑变得复杂。
知道useEffect的作用吗?那你了解useEffect的实现原理吗?
这里我就只讲述了一下useEffect的使用,没有讲原理,但是还是把它可能对比成vue中的一些声明周期函数来多说一些。
useEffect
允许你指定一个函数,在组件渲染后执行。这个函数可以执行一些副作用操作,如数据获取、订阅服务等。当组件卸载时,useEffect
还可以指定一个清除副作用的函数。
useEffect
接受一个可选的依赖数组作为第二个参数。- 如果指定了依赖数组,那么副作用函数只会在依赖项改变时重新执行。
- 如果依赖数组为空 (
[]
),那么副作用函数只会在组件挂载和卸载时执行一次。
其实useEffect的实现原理与vue3中的Effect实现还是十分相像的:
let currentHook = null;
let hooks = [];
function useState(initialState) {
const index = hooks.length;
if (currentHook === null) {
hooks.push({ state: initialState, listeners: [] });
} else {
hooks[index].state = hooks[index].state;
}
currentHook = hooks[index];
const setState = (newState) => {
currentHook.state = newState;
hooks[index].listeners.forEach(listener => listener());
};
return [currentHook.state, setState];
}
function useEffect(callback, dependencies) {
const index = hooks.length;
if (currentHook === null) {
hooks.push({ cleanup: null, dependencies: dependencies || [], callback });
} else {
hooks[index].callback = callback;
hooks[index].dependencies = dependencies || [];
}
currentHook = hooks[index];
const cleanup = () => {
if (currentHook.cleanup) {
currentHook.cleanup();
}
};
if (dependencies) {
const prevDeps = currentHook.dependencies;
const nextDeps = dependencies;
const equal = prevDeps.length === nextDeps.length &&
prevDeps.every((dep, index) => Object.is(dep, nextDeps[index]));
if (!equal) {
cleanup();
currentHook.callback();
currentHook.cleanup = callback();
currentHook.dependencies = dependencies;
}
} else {
cleanup();
currentHook.callback();
currentHook.cleanup = callback();
}
}
跨域的方法和原理是什么
之所以存在跨域问题是因为浏览器有着同源策略 同源 = 协议 + 主机 + 端口 三者都相同。两个源不同,称之为跨源或跨域 我们有三种常用的方法规避这个限制。
- JSONP
JSONP是服务器与客户端跨源通信的常用方法。最大特点就是简单适用,老式浏览器全部支持,服务器改造非常小。
它的基本思想是,网页通过添加一个<script>
元素,向服务器请求JSON数据,这种做法不受同源政策限制;服务器收到请求后,将数据放在一个指定名字的回调函数里传回来。
请注意,由于这是一种 JSONP 请求,它只能从同源策略允许的服务器进行。此外,JSONP 只支持 GET 请求。
- WebSocket
WebSocket是一种通信协议,使用ws://
(非加密)和wss://
(加密)作为协议前缀。该协议不实行同源政策,只要服务器支持,就可以通过它进行跨源通信。
GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw== Sec-WebSocket-Protocol: chat, superchat Sec-WebSocket-Version: 13 Origin: http://example.com
上面代码中,有一个字段是Origin
,表示该请求的请求源(origin),即发自哪个域名。
正是因为有了Origin
这个字段,所以WebSocket才没有实行同源政策。因为服务器可以根据这个字段,判断是否许可本次通信。如果该域名在白名单内,服务器就会做出如下回应。
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat
- CORS
简单请求CORS请求:浏览器会在请求头中自动加入一个 Origin
头,表示请求的来源。服务器可以通过响应头中的 Access-Control-Allow-Origin
来决定是否允许此来源的请求。
由于CORS跨区内容庞大,这里推荐大家阅读浏览器同源政策及其规避方法 - 阮一峰的网络日志 (ruanyifeng.com)
type 和 interface的区别
type
和 interface
都用于定义类型,但它们之间存在一些关键差异。
type
(类型别名)
- 用途: 主要用于创建类型别名,可以为简单的类型或者复杂的类型联合或交集创建新的名称。
- 简单数据类型:
type
可以用来定义基本类型、元组类型、枚举类型等。 - 不可扩展性:
type
定义的类型不能被扩展,也就是说你不能使用extends
关键字来扩展一个由type
定义的类型
interface
(接口)
- 用途: 主要用于描述对象的结构,也可以用于类的实现。
- 可扩展性:
interface
支持继承,你可以通过extends
关键字来扩展一个接口。 interface
支持继承
泛型是一种编程技术,它允许您编写可以处理多种类型的数据的函数或类,同时保持类型安全。通过使用泛型,您可以定义灵活且可重用的组件,这些组件可以在运行时处理不同类型的参数。
泛型函数
在 TypeScript 中,您可以定义一个泛型函数,该函数接受一个类型参数 T
。这个类型参数代表了一个未知的具体类型,但是它必须在整个函数内部保持一致。
function identity<T>(arg: T): T {\
return arg;
}
泛型类
泛型也可以应用于类
class GenericNumber<T> {
value: T;
constructor(value: T) {
this.value = value;
}
}
let myNum = new GenericNumber<number>(123); // myNum 是 GenericNumber<number> 类型
三数之和
最后一题是一道的leetcode————三数之和:
- 如何确定每个数的过程
- 第一个数O(n) 排序 (想同的元素在一起)
相同的元素跳过
- 双指针 左边比右边的小
< target lfet++
> target right--
- 排序 nums 重复的数字
function threeSum(nums){
nums.sort((a,b) => a - b); //从小到大排序
const result=[]
for(let i=0;i<nums.length-2;i++){
if(i>0 && nums[i]===nums[i-1]) continue; //去重
let left=i+1,right=nums.length-1;
while(left<right){ //O(n^2)
const sum=nums[i]+nums[left]+nums[right];
if(sum<0){
left++;
}else if(sum>0){
right--;
}else{
result.push([nums[i],nums[left],nums[right]]);
while(left<right && nums[left]===nums[left+1]) left++;
while(left<right && nums[right]===nums[right-1]) right--;
}
}
}
return result
}
在接受完这家公司面试的考验后,更加认识到了大厂对底层要求的严格了。但是还是这家阿里旗下的医疗公司的Offer还是拿下了。
转载自:https://juejin.cn/post/7402987272504852532