【前端面经】热乎的小米面经总结

409次阅读  |  发布于3年以前

写在前面

春招已经接近尾声,想必诸多学子也已收获满意的offer。而笔者在与一面阔别大概半个月之久,又收到小米的二面。相对于其他面试,小米更侧重的是你用最简练的语言能够最详细地表述你的想法,最后惯例得两道手撕代码题。

1 自我介绍、项目介绍

2 常规基础题

「2.1 vuex是什么?怎么使⽤?哪种功能场景使⽤它?」

modules:项⽬特别复杂的时候,可以让每⼀个模块拥有⾃⼰的 state、mutation、action、getters,使得结构⾮常清晰,⽅便管理

「2.2 关于响应式数据绑定,双向绑定机制:Object.defineProperty()」

vue实现数据双向绑定主要是:采⽤数据劫持结合发布者-订阅者模式的⽅式,通过Object.defineProperty()来劫持各个属性的settergetter,在数据变动时发布消息给订阅者,触发相应监听回调。当把⼀个普通Javascript对象传给Vue实例来作为它的data选项时,Vue将遍历它的属性,⽤Object.defineProperty()将它们转为getter/setter。⽤户看不到getter/setter,但是在内部它们让Vue追踪依赖,在属性被访问和修改时通知变化。

vue的数据双向绑定将MVVM作为数据绑定的⼊⼝,整合ObserverCompileWatcher三者,通过Observer来监听⾃⼰的model的数据变化,通过Compile来解析编译模板指令(vue中是⽤来解析{{}}),最终利⽤watcher搭起observerCompile之间的通信桥梁,达到数据变化—>视图更新;视图交互变化(input)—>数据model变更双向绑定效果。

「数据劫持」:Vue内部使⽤了Object.defineProperty()来实现双向绑定,通过这个函数可以监听到set和get的事件。

var data = { name: 'yck' }
observe(data)
let name = data.name // -> get value
data.name = 'yyy' // -> change value
function observe(obj) {
    // 判断类型
    if (!obj || typeof obj !== 'object') {
     return
    }
    //Object.keys(obj)将对象转为数组
    Object.keys(obj).forEach(key => {
     defineReactive(obj, key, obj[key])
    })
}
function defineReactive(obj, key, val) {
    // 递归⼦属性
    observe(val)
    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter() {
            console.log('get value')
            return val
        },
        set: function reactiveSetter(newVal) {
            console.log('change value')
            val = newVal
        }
    })
}

「Proxy 与 Object.defineProperty 对⽐」

Object.defineProperty 虽然已经能够实现双向绑定了,但是他还是有缺陷的 .

「web网站中常见攻击手法和原理」

「Vue中diff原理」

要知道渲染真实DOM的开销是很大的,比如有时候我们修改了某个数据,如果直接渲染到真实dom上会引起整个dom树的重绘和重排。有没有可能我们只更新我们修改的那一小块dom而不要更新整个dom呢?diff算法能够帮助我们

「diff算法包括一下几个步骤:」

diff算法是通过「同层的树节点」进行比较而非对树进行逐层搜索遍历的方式,所以时间复杂度只有O(n),是一种相当高效的算法 逐个遍历newVdom的节点,找到它在oldVdom中的位置,如果找到了就移动对应的DOM元素,如果没找到说明是新增节点,则新建一个节点插入。遍历完成之后如果oldVdom中还有没处理过的节点,则说明这些节点在newVdom中被删除了,删除它们即可。

「vue模板编译原理」

模板转换成视图的过程整个过程:

「介绍下你了解Webpack多少知识」

基本概念:

Loader和Plugin的区别:

在我的个人理解中,plugin更像是对loader的补充,两者进行相辅相成,loader大多是固定的配置,而plugin能够处理更加灵活的设置。

核心作用:

3 手撕代码题

3.1 千分位格式化数字

用js实现如下功能,将给定的数字转化成千分位的格式,如把12345678转化成12,345,678

这题目相对是比较简单了,能够用来解决的问题的方法也有很多,最简单的可以用正则化进行处理。

let num = 12345678;
let str = num.toString();
let newStr = str.replace(/(\d)(?=(?:\d{3})+$)/g,"$1,");

思路:将数字转换为字符串(toString())再打散成数组(split),如果直接数字转换为数组,就是一整个放进去了,不能单独取到每一位。然后通过循环,逐个倒着把数组中的元素插入到新数组的开头(unshift),第三次或三的倍数次,插入逗号,最后把新数组拼接成一个字符串。

let num = 12345678;
function Thousands(num){
  //将数字转换为字符串后进行切分为数组
  let numArr = num.toString().split("");
  let arr = [];
  let count = 0;//用于计数
  for(let i = numArr.length-1;i>=0;i--){
    count++;
    //从numArr末尾取出数字后插入arr中,其实就是对齐进行倒序
    arr.unshift(numArr[i]);
    //当count每到三位数字,则进行追加逗号。i!=0即取到第1位的时候,前面不用加逗号。
    if(!(count%3)&&i!==0) arr.unshift(",");
  }
  //将数组拼接为字符串
  return arr.join("");
}
Thousands(num);

缺点:一位一位的加进去,性能差,且还要先转换成字符串再转换成数组。

思路:不先转为数组,直接获取字符串的每一个字符进行拼接。

let num = 12345678;
function Thousands(num){
  //将数字转换为字符串
  let str = num.toString();
  let res = "";//用于接收拼接后的新字符串
  let count = 0;//用于计数
  for(let i = str.length-1;i>=0;i--){
    count++;
    //从numArr末尾取出数字后插入arr中,其实就是对齐进行倒序
    res = str.charAt(i) + res;
    //当count每到三位数字,则进行追加逗号。i!=0即取到第1位的时候,前面不用加逗号。
    if(!(count%3)&&i!==0) res = ',' + res;
  }
  //将数组拼接为字符串
  return res;
}
Thousands(num);

缺点:依旧需要进行一一分割拼接。

思路:每次取末三位子字符串放到一个新的空字符串里并拼接上之前的末三位,原本数组不断截掉后三位直到长度小于三个,最后把剥完的原数组拼接上新的不断被填充的数组。

let num=123345678;
function Thousands(num){
    //将数字转换为字符串
    let str = num.toString();
    let res = "";//用于接收拼接后的新字符串
    while(str.length>3){
        res = "," + str.slice(-3) + res;
        str = str.slice(0,str.length-3)
    }
    if(str) return str + res;
};
Thousands(num);

3.2 比较两个对象的属性和值是否相同

题目描述:

obj1 = {name:"wenbo",age:12,score:[120,121,113]};
obj2 = {age:12,name:"wenbo",score:[120,121,113]};

思路:对两个对象进行遍历取值进行比较

function fun(obj1,obj2){
  //判断obj1、obj2是否为Object类型
  let o1 = obj1 instanceof Object;
  let o2 = obj2 instanceof Object;
  //如果两者有不是对象类型的,既可以直接进行等值比较
  if(!o1 || !o2) return obj1 === obj2;
  //如果两个是对象类型,且两者的键值对个数不同
  if(Object.keys(obj1).length!==Object.keys(obj2).length) return false;
  //当以上情况均不是,则进行遍历比较
  for(let key in obj1){
    //需要判断两个对象的此key对应的值是否为对象类型
    let flag1 = obj1[key] instanceof Object;
    let flag2 = obj2[key] instanceof Object;
    if(flag1 && flag2){
      fun(obj1[key],obj2[key])
    }else if(obj1[key] !== obj2[key]){
      return false;
    }
  }
  return true;
}
let obj1 = {name:"wenbo",age:12,score:[120,121,113]};
let obj2 = {age:12,name:"wenbo",score:[120,121,113]};
fun(obj1,obj2);

亦或:

function fun(obj1,obj2){
  //判断obj1、obj2是否为Object类型
  let o1 = obj1 instanceof Object;
  let o2 = obj2 instanceof Object;
  //如果两者有不是对象类型的,既可以直接进行等值比较
  if(!o1 || !o2) return obj1 === obj2;
  //如果两个是对象类型,且两者的键值对个数不同
  if(Object.keys(obj1).length!==Object.keys(obj2).length) return false;
  //取对象obj1和obj2的属性名
  let obj1Props = Object.getOwnPropertyNames(obj1);
  //循环取出属性名,再判断属性值是否一致
  for (let i = 0; i < obj1Props.length; i++) {
    let propName = obj1Props[i];
    //需要判断两个对象的此key对应的值是否为对象类型
    let flag1 = obj1[propName] instanceof Object;
    let flag2 = obj2[propName] instanceof Object;
    if(flag1 && flag2){
      fun(obj1[propName],obj2[propName])
    }else if(obj1[propName] !== obj2[propName]){
      return false;
    }
  }
  return true;
}
let obj1 = {name:"wenbo",age:12,score:[120,121,113]};
let obj2 = {age:12,name:"wenbo",score:[120,121,113]};
console.log(fun(obj1,obj2));;

当对象遍历过程中,遇到对象的属性时Object类型,且指向的是该对象,那么需要考虑的是以上代码还能运行成功吗?如:

let obj1 = {name:"wenbo",age:12,score:[120,121,113]};
obj1.temp = obj1;
let obj2 = {age:12,name:"wenbo",score:[120,121,113};
obj2.temp = obj1;

思路:新建一个数组,将obj1遍历过的键值存储在数组中,再下一次进行遍历时发现一样的值,直接跳过进行比较。

参考文章

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8