当前位置 : 主页 > 网络编程 > PHP >

面试又造火箭了?我该怎么办!?

来源:互联网 收集:自由互联 发布时间:2023-10-08
小伙伴们是否在面试中因为造火箭的问题而苦恼? 我不想抱怨什么,因为我就是那种让面试者造火箭的面试官:) 本文将从底层逻辑分析引起这种现状的原因并提出诚恳建议。 视频教

小伙伴们是否在面试中因为造火箭的问题而苦恼?

我不想抱怨什么,因为我就是那种让面试者造火箭的面试官:)

本文将从底层逻辑分析引起这种现状的原因并提出诚恳建议。

面试又造火箭了?我该怎么办!?_数据

视频教程

村长特地录制了配套视频,手把手带领大家撸出自己的​​mini vue​​:

从手写Vue到面试策略分析

欢迎各位小伙伴三连+关注,您的鼓励是我坚持下去的最大动力❤️

为什么面试会造火箭

因为市场供需关系变了,以前是小甜甜,现在是牛夫人。尤其初中级前端不像6、7年前那会儿随便找工作。竞争对手多了,用人单位就更挑剔,给同样的钱,当然选更有能力、有干劲的员工。同时面试官们发现大家简历千篇一律:熟练使用vue框架和全家桶,熟练使用​​element-ui​​​、​​iView​​​等组件库,熟练使用​​axios​​​获取服务端数据等等。此时如果不提高面试难度,很难区分面试者能力高下。面试又造火箭了?我该怎么办!?_数据驱动_02

遇到造火箭问题怎么办

比如面试官问:

  • 为什么需要数据响应式?​​vue​​中是怎么实现的?
  • 为什么需要虚拟​​dom​​?​​diff​​过程是怎样的?

小伙伴们选择去找答案,背下来,你这样解决不了问题,因为背的答案是死的,经不住推敲,稍微追问几下就露馅了。

我认为大家应该借此契机好好学习一下源码,不仅能找到问题答案,加深对API理解,还能学到很多算法、设计模式和工程化知识,对提高编程水平很有帮助。

源码学起来很困难怎么办

很多小伙伴也想通过阅读源码学习,但源码通常很庞大繁杂,很容易劝退。建议大家从一个​​mini​​版的实现入手。先打下一个很好的基础,掌握之后再去看源码就会简单不少。

造个火箭试试

我就以​​Vue​​​为例,写一个​​mini​​版,然后我们再考虑那些造火箭问题。

Vue的设计理念

开始之前我们先看一下Vue的设计理念,这样后面写起来会更容易理解:

易理解、友好、性能好、易维护、可测试面试又造火箭了?我该怎么办!?_数据_03

代码中感受一下

没有使用​​Vue​​,01-no-vue.html:

<div id="app"></div>

<script>
// 需求:
// 1.有个title标题,想要显示在h3标签中
// 2.2秒后title会变化
const title = '我就是个标题'
const h3 = document.createElement('h3')
h3.textContent = title
app.appendChild(h3)
setTimeout(() {
h3.textContent = '我还是那个标题,但我变了'
}, 2000);
</script>

特点是:

  • 用户要直接接触dom
  • dom操作也是业务一部分
  • 用户心智负担更重,开发效率底下

使用​​Vue​​,02-with-vue.html:

<div id="app">
<h3>{{title}}</h3>
</div>

<script src="http://unpkg.com/vue"></script>
<script>
// 需求:
// 1.有个title标题,想要显示在h3标签中
// 2.2秒后title会变化
new Vue({
data() {
return {
title: '我就是个标题'
}
},
mounted() {
setTimeout(() {
this.title = '我还是那个标题,但我变了'
}, 2000);
},
}).$mount('#app')
</script>

重要变化是我们的app以数据驱动,能避免DOM操作,那么我们的目标就很明确了:

  • 要能知道数据发生变化
  • 变化之后能执行视图更新

造个轮子试试

基本结构:​​Vue​​​构造函数和​​$mount​​方法

<div id="app"></div>

<script>
function Vue(options) {}
Vue.prototype.$mount = function() {}
</script>

利用​​defineProperty​​​实现数据响应式,监控​​data​​中数据变化

function Vue(options) {
// 响应式
this.$options = options
this.$data = options.data()
observe(this.$data)
}
Vue.prototype.$mount = function () {}
// 遍历obj所有key做响应式处理
function observe(obj) {
Object.keys(obj).forEach(key {
defineReactive(obj, key, obj[key])
})
}
// 所谓响应式就是拦截对象属性访问
function defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {
get() { return val },
set(newVal) { val = newVal }
}
})
}

挂载:准备一个更新函数,负责视图初始化和后续更新

<script>
Vue.prototype.$mount = function (sel) {
// 创建更新函数
this.update = function () {
const child = this.$options.render.call(this)
const parent = document.querySelector(sel)

if (!this.isMounted) {
// init
parent.appendChild(child)

this.isMounted = true
if (this.$options.mounted) {
this.$options.mounted.call(this)
}
} else {
// update
parent.innerHTML = ''
parent.appendChild(child)
}
}

this.update()
}
function observe(obj) {}
function defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {
get() {},
set(newVal) {
if (newVal !== val) {
// 触发更新
app.update();
}
}
})
}
</script>
<script>
const app = new Vue({
// 添加render函数负责渲染dom
render() {
const h3 = document.createElement('h3')
h3.textContent = this.$data.title
return h3
}
})
</script>

问题:每次更新都是全量更新视图

基于​​vnode​​实现,避免全量更新

<script>
Vue.prototype.$mount = function (sel) {
this.update = function () {
// 执行render获取vnode
const vnode = this.$options.render.call(this, this.createElement)

if (!this.isMounted) {
// init patch:传入parent是dom
const parent = document.querySelector(sel)
this.patch(parent, vnode)
} else {
// update patch:传入两个vnode做diff
this.patch(this._vnode, vnode)
}

this._vnode = vnode
}

this.update()
}

// 加一个vnode生成函数
Vue.prototype.createElement = function (tag, props, children) {
return { tag, props, children }
}

// patch用于初始化或更新时转换vnode为dom
Vue.prototype.patch = function (n1, n2) {
if (n1.nodeType) {
// init
const child = this.createElm(n2)
n1.appendChild(child)
n2.$el = child
} else {
// update
}

}

// 递归创建元素
Vue.prototype.createElm = function (vnode) {
const {tag, props, children} = vnode
const el = document.createElement(tag)
// 创建children
if (Array.isArray(children)) {
// element
children.forEach(child el.appendChild(createElm(children)))
} else {
// text
el.textContent = children
}
vnode.$el = el
return el
}
</script>
<script>
const app = new Vue({
// render返回vnode
render(h) {
return h('h3', null, this.$data.title)
}
})
app.$mount('#app')
</script>

更新逻辑:主要看双方children类型,针对性做dom操作,此处仅解决了测试用例中的文本情况

Vue.prototype.patch = function (n1, n2) {  if (n1.nodeType) {} else {    // 获取待操作dom    const el = n2.$el = n1.$el    // children更新    if (n1.tag === n2.tag) { // 是否相同节点,节点复用      if (typeof n1.children === 'string') {        if (typeof n2.children === 'string') {          // text update          if (n1.children !== n2.children) {            el.textContent = n2.children          }        } else {          // replace text with elements        }      } else {        if (typeof n2.children === 'string') {          // replace elements with text        } else {          // update children        }      }    } else {      // replace    }  }

再来思考回答策略

可使用四段体:介绍概念,说必要性,源码如何实现,实践中如何使用。例如:

  • 为什么需要数据响应式?怎么执行?
    介绍概念:数据响应式是MVVM这类框架中侦测数据变化的机制,三大框架中各不相同(知道就发挥一下)
    必要性:MVVM最重要的任务就是实现数据驱动,要实现数据驱动就必须要知道数据何时发生变化,从而做出响应,这就需要一套数据响应式机制。
    源码实现:vue 2.x中主要利用defineProperty,vue 3.x中主要利用Proxy(不知道就不提)。以vue 2.x为例通过遍历对象属性,定义get/set,做属性拦截,将来数据变化,就可以感知,并调用更新函数使视图更新。
    结合实践:实践中,我们传入组件的属性props,方法methods,数据data,都会在Vue初始化的时候统一做响应式处理,因此当它们发生变化,视图就会重新渲染,得以更新。也有些特殊情况,比如有新属性添加或删除,需要使用Vue.set/delete这样的API。

当然,小伙伴还要继续学习不少细节,比如:

  • 怎么通知视图更新(依赖收集,异步更新等知识点)
  • 视图是怎么更新的(虚拟dom和patch)
  • vue 2.x中响应式有啥问题(效率、额外api、数组处理等)
  • 为什么需要Vue.set/delete这样的API

你看全是和响应式这个点引申出来的,如果回答得当,基本妥妥的。

范例源码

关注公众号「​​村长学前端​​」自取

视频教程

村长特地录制了配套视频,手把手带领大家撸出自己的​​mini vue​​:

从手写Vue到面试策略分析



上一篇:造就 Rust 的无名英雄
下一篇:没有了
网友评论