春招已经接近尾声,想必诸多学子也已收获满意的offer。而笔者在与一面阔别大概半个月之久,又收到小米的二面。相对于其他面试,小米更侧重的是你用最简练的语言能够最详细地表述你的想法,最后惯例得两道手撕代码题。
略
「2.1 vuex是什么?怎么使⽤?哪种功能场景使⽤它?」
modules:项⽬特别复杂的时候,可以让每⼀个模块拥有⾃⼰的 state、mutation、action、getters,使得结构⾮常清晰,⽅便管理
「2.2 关于响应式数据绑定,双向绑定机制:Object.defineProperty()」
vue实现数据双向绑定主要是:采⽤数据劫持结合发布者-订阅者模式的⽅式,通过Object.defineProperty()
来劫持各个属性的setter
,getter
,在数据变动时发布消息给订阅者,触发相应监听回调。当把⼀个普通Javascript
对象传给Vue实例来作为它的data
选项时,Vue
将遍历它的属性,⽤Object.defineProperty()
将它们转为getter/setter
。⽤户看不到getter/setter
,但是在内部它们让Vue
追踪依赖,在属性被访问和修改时通知变化。
vue
的数据双向绑定将MVVM
作为数据绑定的⼊⼝,整合Observer
,Compile
和Watcher
三者,通过Observer
来监听⾃⼰的model
的数据变化,通过Compile
来解析编译模板指令(vue
中是⽤来解析{{}}
),最终利⽤watcher
搭起observer
和Compile
之间的通信桥梁,达到数据变化—>视图更新;视图交互变化(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网站中常见攻击手法和原理」
xss
):恶意攻击者通过往web
页面中插入恶意html
代码,当用户浏览该页面时,嵌入web
里面的html
代码会被执行,从而达到恶意攻击用户的特殊目的。sql
注入:sql
注入就是把sql
命令插入到web
表单进行提交,或输入域名,或页面请求的查询字符串,最终达到欺骗服务器执行恶意sql
命令的目的。具体而言,就是利用现有应用程序,将恶意的sql
命令注入到后台数据库引擎中进行执行。cookie
攻击:通过js很容易访问到当前网站的cookie
,你可以打开任何网站,然后在浏览器地址栏输入javascript:alert(doucment.cookie)
,立刻可以看到当前站点的cookie,攻击者可以利用这个特性取得用户的关键信息。假设这个网站仅依赖cookie进行用户身份验证,那么攻击者就可以假冒你的身份来做一些事情。现在多数浏览器都支持在cookie
上打上HttpOnly
的标记,但凡有这个标记的cookie
就无法通过js
来获取,如果能够在关键cookie
上打上标记,就可增强cookie
的安全性。HTTP Headers
攻击:凡是用浏览器查看任何web网站,无论你的web网站采用何种技术和框架,都用到了http
协议。http
协议在Response header
和content
之间,有一个空行,即两组CRLF(0x0D 0A)
字符这个空行标志着headers
的结束和content
的开始。攻击者利用这一点,只要攻击者有办法将任意字符注入到headers
中,这种攻击就可以发生。webshell
。「Vue中diff原理」
要知道渲染真实DOM的开销是很大的,比如有时候我们修改了某个数据,如果直接渲染到真实dom上会引起整个dom树的重绘和重排。有没有可能我们只更新我们修改的那一小块dom而不要更新整个dom呢?diff算法能够帮助我们
「diff算法包括一下几个步骤:」
JavaScript
对象结构表示DOM
树的结构;然后用这个树构建一个真正的DOM
树,插到文档当中diff
),记录两棵树差异DOM
树上(patch
),视图就更新了diff算法是通过「同层的树节点」进行比较而非对树进行逐层搜索遍历的方式,所以时间复杂度只有O(n),是一种相当高效的算法 逐个遍历newVdom的节点,找到它在oldVdom中的位置,如果找到了就移动对应的DOM元素,如果没找到说明是新增节点,则新建一个节点插入。遍历完成之后如果oldVdom中还有没处理过的节点,则说明这些节点在newVdom中被删除了,删除它们即可。
「vue模板编译原理」
模板转换成视图的过程整个过程:
Virtual DOM
的。Vue
推荐使用模板来构建我们的应用界面,在底层实现中Vue
会将模板编译成渲染函数,当然我们也可以不写模板,直接写渲染函数,以获得更好的控制。VNode
虚拟节点:它可以代表一个真实的dom
节点。通过createElement
方法能将VNode
渲染成dom
节点。简单地说,vnode
可以理解成节点描述对象,它描述了应该怎样去创建真实的DOM
节点。patch
(也叫做patching
算法):虚拟DOM
最核心的部分,它可以将vnode
渲染成真实的DOM
,这个过程是对比新旧虚拟节点之间有哪些不同,然后根据对比结果找出需要更新的的节点进行更新。这点我们从单词含义就可以看出,patch
本身就有补丁、修补的意思,其实际作用是在现有DOM
上进行修改来实现更新视图的目的。Vue
的Virtual DOM Patching
算法是基于Snabbdom
的实现,并在些基础上作了很多的调整和改进。「介绍下你了解Webpack多少知识」
基本概念:
Loader和Plugin的区别:
module.rules
中配置,也就是说他作为模块的解析规则而存在。类型为数组,每一项都是一个Object
,里面描述了对于什么类型的文件(test
),使用什么加载(loader
)和使用的参数(options
)。plugins
中单独配置。类型为数组,每一项是一个plugin
的实例,参数都通过构造函数传入。在我的个人理解中,plugin更像是对loader的补充,两者进行相辅相成,loader大多是固定的配置,而plugin能够处理更加灵活的设置。
核心作用:
webpack
的Plugin
机制,我们在实现模块化打包和编译兼容的基础上,可以进一步实现诸如按需加载,代码压缩等一系列功能,帮助我们进一步提高自动化程度,工程效率以及打包输出的质量。用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);
题目描述:
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遍历过的键值存储在数组中,再下一次进行遍历时发现一样的值,直接跳过进行比较。
https://mp.weixin.qq.com/s/RmcMWzkAiOrOOyxtPSZDUg
https://juejin.cn/post/6844903877771264013
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8