大三在校生的首次求职之路

2,255 阅读5分钟

前言

身为一名大三前端练习生的我,抱着对金三银四期望,开始在boss上疯狂的投简历。只为求一个前端实习生的岗位。终于在我一个星期不懈的努力下,约到了杭州一个中厂的面试,我暂且把该公司称为 A。下面我就是分享一下我的首次面试经历吧。

流程

1. 自我介绍

A 公司的人事约的早上十点开始,由于是我的第一次面试,所以我还是非常紧张的。那天我在寝室忐忑的等到了十点,面试也正式开始了。首先面试官进行了自我介绍。接下来该我了。我巴拉巴拉的说了我的基本信息、技术栈、项目经历等。就这样,自我介绍在我结结巴巴的介绍中结束了。

2. 项目经历

在做完自我介绍后,面试官开始对着我的简历问我自己做的项目的问题,一些比较基础的问题我在这里就略过了。给大家分享一个比较经典的问题。

2.1 你项目中的markdown是怎么使用的?

由于我在自己写的项目中用到了markdowm文档,所以面试官顺势就问了这个问题。

我的回答: 我现在我的项目中安装了用于做markdown的包,然后在项目中引入,在将该包的函数调用将markdown转化为html结构展示在页面上。(但我忘了那个函数叫啥。。。)。

补充:具体用法如下:

// html
<a-textarea v-model:value="writeContent" placeholder="输入文章内容. . ." class="input" />
    <div class="markdown-body show content" v-html="showContent">
// js
import { marked } from 'marked'
const showContent = computed(() => {
      return marked(data.writeContent, { sanitize: true })
    })

3. 关于Vue的问题

由于我学的框架Vue,所以面试官也毫不留情的对我vue的知识进行炮轰。

3.1 说说 v-if 和 v-show 的区别

我的回答: v-if在渲染时,v-if的dom的不会被加载,v-show的dom结构会被加载,然后再用 css属性 display: none 对该dom结构隐藏。

补充:v-if切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件;v-show只是简单的基于css切换。(果然就是比我说得好。)

3.2 Vue的双向数据绑定的实现

我的回答:(额,当时说的实在太结巴了, 所以我们就直接来看补充吧。)

补充:

  1. 实现一个监听器​​Observer​​ ,对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者;
  2. 实现一个解析器​​Compile​​,对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数
  3. 实现一个发布订阅模型​​Watcher​​+​​Dep​​,作为连接Observer和Compile的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图。

具体代码实现(简洁版):


let _Vue = null
class Store {
  constructor (options) {
    // state
    this.vm = new _Vue({
      data: {
        state: options.state
      }
    })
    // getters
    let getters = options.getters || {}
    this.getters = {}
    Object.keys(getters).forEach(getterName => {
      Object.defineProperty(this.getters, getterName, {
        get: () => {
          return getters[getterName](this.state)
        }
      })
    })
    // mutations
    let mutations = options.mutations || {}
    this.mutations = {}
    Object.keys(mutations).forEach(mutationName => {
      this.mutations[mutationName] = (arg) => {
        mutations[mutationName](this.state, arg)
      }
    })
    // actions
    let actions = options.actions || {}
    this.actions = {}
    Object.keys(actions).forEach(actionName => {
      this.actions[actionName] = (arg) => {
        actions[actionName](this, arg)
      }
    })
  }
  dispatch(method, arg) {
    this.actions[method](arg)
  }
  commit = (method, arg) => {
    // console.log(this);
    this.mutations[method](arg)
  }
  get state() {
    return this.vm.state
  }
}
let install = function(Vue) {
  _Vue = Vue
  Vue.mixin({
    beforeCreate() {
      if (this.$options && this.$options.store) {  // this.$options读取到根组件
        this.$store = this.$options.store
      } else { // 子组件
        this.$store = this.$parent && this.$parent.$store
      }
    }
  })
}
let Vuex = {
  Store,
  install
}

export default Vuex

3.3 VueRouter的使用方法

我的回答: 首先在vue项目安装一个 VueRouter包,然后我们创建一个router文件专门配置项目的路由。然后再将 routes 数组抛出,在选择路由模式,然后在被Vue实例对象use一下就可以用了。

补充:我们还要new一下VueRouter 创建一个路由实例,传入 routes、mode、base在将这个实例抛出给 Vue实例use。

3.4 接口请求可以写在Vue项目那些生命周期

我的回答:可以在钩子函数 created、beforeMount、mounted 中进行调用,因为在这三个钩子函数中,data 已经创建,可以将服务端端返回的数据进行赋值。

补充: 在实际应用中也可以放在 beforeCreate中, 因为接口请求是异步操作,执行需要时间。

3.5 怎么做移动端的适配

我的回答:(当时第一时间想到的是媒体查询)在css对该容器进行媒体查询,根据不同的页面宽度来调节容器的大小。

补充:

  1. 媒体查询 + rem
  2. rem + flexbox(插件)
  3. vm、vh、%

4. 关于JS的问题

在问完Vue的问题之后,面试官开始了对 JS的炮轰。

4.1 数组加值的方法

我的回答: 前后加值: unshift、 push、 中间加值:splice(), 如果数组是排好序的也可以用for循环。

补充: 无

4.2 阶乘实现的方法

我的回答:我们可以采用for循环,或者递归的方法。

补充: 代码实现:

let jiehcheng = (n) =>{
    if( n === 2) {
    return 2 
    }
    return n * jiehcheng(n-1)
}

4.3 计算字符串a 在 b中出现了几次

我的回答: (当时我没想起来滑动窗口,所以回到的不是很好,我们直接来看补充吧。)

补充:

let appear = (a, b) => {
  let len = a.length
  let conut =0
  for(let i=0; i <b.length -len; i++) {
    if(a === b.slice(i, i+len)) {
      conut ++;
    }
  }
  return conut
}

4.4 如何实现给字符串加上前缀。

我的回答; 写一个方法,在方式中接收一个字符串为参数, 再将这个字符串添加前缀并返回。(显然这个方法是很笨的,也不太行)

补充:

function newString(string) {
  return new String(string) + 'qianzhui..'
}
let string =  newString('adda')

4.5 你做过榜单, 怎么实现榜单更新。

我的回答: 如果榜单的数据发生变化,我们可以先在前端做好榜单的排序,返回给数据库,然后再将数据请求回来重新渲染。

补充: 如果前端点击事件导致榜单数据发生改变,我们先让页面数据发生变化,再向后端发送数据已经更新的请求去更新数据库。

4.6 1. 面对十万条数据,你怎么渲染

我的回答: (当时想到的就是懒加载)我们使用懒加载去加载一步的数据,在页面发生滚动时,我们再根据滚动的距离去加载其他的数据。(回答不够全面)

补充:

// 分页渲染(使用定时器的方式实现)
 const renderList = async(parent) => {
     const list =await getList()
     const page = 0
     const limit = 200
     const totalPage = Math.ceil(list.length / limit)
     const render = (page) => {
         if(page >= totalPage) return
         setTimeout(() => {
             for(let i =page * limit; i < page * limit +limit; i++) {
                const item = list[i]
                const div = document.createElement('div')
                div.className = 'item'
                div.innerHTML = `<img src="${item.src}" /> <span>${item.text}</span>`
                parent.appendChild(div)
             }
             render(page + 1)
         },0)
     }
     render(page)
 }
// 文档碎片 + 分页渲染
const renderList = async(parent) => {
    console.time('渲染时间');
    const list =await getList()
    const page = 0
    const limit = 200
    const totalPage = Math.ceil(list.length / limit)
    const render = (page) => {
        if(page >= totalPage) return
        requestAnimationFrame(() => { //减少重排
            //创建一个文档碎片
            const fragment = document.createDocumentFragment()
            for(let i =page * limit; i < page * limit +limit; i++) {
               const item = list[i]
               const div = document.createElement('div')
               div.className = 'item'
               div.innerHTML = `<img src="${item.src}" /> <span>${item.text}</span>`
               fragment.appendChild(div) // 不会触发重排
            }
            parent.appendChild(fragment)
            render(page + 1)
        },0)
    }
    render(page)
   console.timeEnd('渲染时间')
}

5. 关于ES6的问题

5.1 介绍一下Promise和 async/await。

我的回答:Promise 是异步编程的一种解决方案,是一个构造函数,自身有all、reject、resolve方法,原型上有then、catch等方法。Async/Await 代码看起来简洁一些,使得异步代码看起来像同步代码,async函数一定会返回一个promise对象。如果一个async函数的返回值看起来不是promise,那么它将会被隐式地包装在一个promise中。

补充: Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。一旦状态改变,就不会再变,任何时候都可以得到这个结果。async/await的实现使用了Generator 函数。

5.2 解构怎么使用

我的回答:

  1. 数组的使用[...array]
  2. 对象的使用{key, key , ...other}

5.3  你有用过Decorator嘛

我的回答:(因为第一时间没有反应出Decorator是什么,所以我就说没用过)

补充: 装饰器(Decorator)用来增强 JavaScript 类(class)的功能。装饰者模式就是一种在不改变原类和使用继承的情况下,动态地扩展对象功能的设计理论。

优点:

  • 代码可读性变强了,装饰器命名相当于一个注释
  • 在不改变原有代码情况下,对原来功能进行扩展

6. 其他问题

6.1 你怎么做项目的分支管理

我的回答: 因为我们写的项目比较小,所以一般只会使用一个分支,所以不会做分支的管理。(到这里已经感觉GG了。。。)

补充: 分支管理指的是从当前主分支(master)中创建分支(branch),然后每个人负责在自己的分支上进行开发、提交,最后所有功能都开发完成之后,再合并到主分支(master)上。

6.2 Myqsl 和mongodb的区别

我的回答:Mysql对数据库进行增删改查是使用sql语句,mongodb对数据库进行增删改查是使用js语句映射。

补充: 1、MySQL是关系型数据库,而mongodb是非关系型数据库;

2、MySQL中支持多种引擎,不同引擎有不同的存储方式,而mongodb以类JSON的文档的格式存储;

3、MySQL使用传统SQL语句进行查询,而mongodb有自己的查询方式(类似JavaScript的函数);

4、MySQL占用空间小,支持join,而mongodb占用空间大,不支持join。

总结

整个面试过程持续了半小时左右,我自己对我的评价是面的有些糟糕。因为总结下来,A公司的面试官问的问题还是比较简单的,可惜自己语言组织能力不够好,再加上第一次面试的紧张情绪,让我发挥的有些糟糕。革命尚未成功,同志还需努力呀。