在掘金上看到了一名大佬发了一篇很细致的口试纪录文章-
《一年半履历,百度、有赞、阿里口试总结》,为了查漏补缺,抽空就细致做了下。(
预计只要我这么无聊了哈哈哈)
有给出的或许有些不圆满的答案,也全力给出/圆满了(可以有错,人人自行分辨)。有些很难题的题目(比方完成Promise
),附带相干链接(懒癌患者福利)。
总的来说,将这些题目分成了“Javascript”、“CSS”、“浏览器/协定”、“算法”和“Web工程化”5个部份举行回复和代码完成。
末了,迎接来我的博客和我扯犊子:godbmw.com。直接戳本篇原文的地点:刷《一年半履历,百度、有赞、阿里口试总结》·手记
1. Javascript相干
1.1 回文字符串
题目:完成一个函数,推断是不是是回文字符串
原文的思绪是将字符串转化成数组=>反转数组=>拼接成字符串。这类做法充分运用了js的BIF,但机能有所消耗:
function run(input) {
if (typeof input !== 'string') return false;
return input.split('').reverse().join('') === input;
}
实在平常思绪也很简朴就可以完成,机能更高,然则没有运用js的特征:
// 回文字符串
const palindrome = (str) => {
// 范例推断
if(typeof str !== 'string') {
return false;
}
let len = str.length;
for(let i = 0; i < len / 2; ++i){
if(str[i] !== str[len - i - 1]){
return false;
}
}
return true;
}
1.2 完成Storage
题目:完成Storage,使得该对象为单例,并对localStorage举行封装设置值setItem(key,value)和getItem(key)
题目重点是单例形式,须要注重的是借助localStorage
,不是让自身手动完成!
谢谢@whiteyork_和@chenzesam的提示:箭头函数没有prototype
function Storage(){}
Storage.getInstance = (function(){
var instance = null
return function(){
if(!instance){
instance = new Storage()
}
return instance
}
})()
Storage.prototype.setItem = function(key, value) {
return localStorage.setItem(key, value)
}
Storage.prototype.getItem = function(key){
return localStorage.getItem(key)
}
// 测试代码:Chrome环境
let a = Storage.getInstance()
let b = Storage.getInstance()
console.log(a === b)
a.setItem("key", 1)
console.log(b.getItem("key"))
1.3 JS事宜流
题目:说说事宜流吧
事宜流分为冒泡和捕捉。
事宜冒泡:子元素的触发事宜会一向向父节点通报,一向到根结点住手。此历程当中,可以在每一个节点捕捉到相干事宜。可以经由历程stopPropagation
要领停止冒泡。
事宜捕捉:和“事宜冒泡”相反,从根节点最先实行,一向向子节点通报,直到目的节点。印象中只要少数浏览器的老旧版本才是这类事宜流,可以疏忽。这里说的确切有题目,更正下:addEventLister
给出了第三个参数同时支撑冒泡与捕捉。
谢谢@junior-yang的提示
1.4 完成函数继承
题目:如今有一个函数A和函数B,请你完成B继承A。而且申明他们优瑕玷。
要领一:绑定组织函数
长处:可以完成多继承
瑕玷:不能继承父类原型要领/属性
function Animal(){
this.species = "动物";
}
function Cat(){
Animal.apply(this, arguments); // 父对象的组织函数绑定到子节点上
}
var cat = new Cat()
console.log(cat.species) // 输出:动物
要领二:原型链继承
长处:可以继承父类原型和实例要领/属性,而且可以捕捉父类的原型链修正
瑕玷:没法完成多继承,会糟蹋一些内存(Cat.prototype.constructor = Cat
)。除此之外,须要注重应当将Cat.prototype.constructor
从新指向自身。
js中交流原型链,均须要修复prototype.constructor
指向题目。
function Animal(){
this.species = "动物";
}
Animal.prototype.func = function(){
console.log("heel")
}
function Cat(){}
Cat.prototype = new Animal()
Cat.prototype.constructor = Cat
var cat = new Cat()
console.log(cat.func, cat.species)
要领3:连系上面2种要领
function Animal(){
this.species = "动物";
}
Animal.prototype.func = function(){
console.log("heel")
}
function Cat(){
Animal.apply(this, arguments)
}
Cat.prototype = new Animal()
Cat.prototype.constructor = Cat;
var cat = new Cat()
console.log(cat.func, cat.species)
1.5 ES5对象 vs ES6对象
题目:es6 class 的new实例和es5的new实例有什么区别?
在ES6
中(和ES5
比拟),class
的new
实例有以下特性:
-
class
的组织参数必需是new
来挪用,不可以将其作为平常函数实行 -
es6
的class
不存在变量提拔 - 最主要的是:
es6
内部要领不可以罗列。es5的prototype
上的要领可以罗列。
为此我做了以下测试代码举行考证:
console.log(ES5Class()) // es5:可以直接作为函数运转
// console.log(new ES6Class()) // 会报错:不存在变量提拔
function ES5Class(){
console.log("hello")
}
ES5Class.prototype.func = function(){ console.log("Hello world") }
class ES6Class{
constructor(){}
func(){
console.log("Hello world")
}
}
let es5 = new ES5Class()
let es6 = new ES6Class()
console.log("ES5 :")
for(let _ in es5){
console.log(_)
}
// es6:不可罗列
console.log("ES6 :")
for(let _ in es6){
console.log(_)
}
这篇《JavaScript竖立对象—从es5到es6》对这个题目的深切诠释很好,引荐寓目!
1.6 完成MVVM
题目:请简朴完成双向数据绑定mvvm
vuejs是运用Object.defineProperty
来完成的MVVM,采纳的是定阅宣布形式。每一个data
中都有set和get属性,这类点对点的效力,比Angular
完成MVVM的体式格局的效力更高。
<body>
<input type="text">
<script>
const input = document.querySelector('input')
const obj = {}
Object.defineProperty(obj, 'data', {
enumerable: false, // 不可罗列
configurable: false, // 不可删除
set(value){
input.value = value
_value = value
// console.log(input.value)
},
get(){
return _value
}
})
obj.data = '123'
input.onchange = e => {
obj.data = e.target.value
}
</script>
</body>
1.7 完成Promise
这是一名大佬完成的Promise
版本:过了Promie/A+
规范的测试!!!网上能搜到的基础都是从这篇文章变形而来或许直接照搬!!!原文地点,直接戳:理会Promise内部构造,一步一步完成一个完全的、能经由历程一切Test case的Promise类
下面附上一种近乎圆满的完成:可以没法和其他Promise
库的完成无缝对接。然则,上面的原文完成了悉数的,迎接Mark!
function MyPromise(executor){
var that = this
this.status = 'pending' // 当前状况
this.data = undefined
this.onResolvedCallback = [] // Promise resolve时的回调函数集,由于在Promise完毕之前有可以有多个回调添加到它上面
this.onRejectedCallback = [] // Promise reject时的回调函数集,由于在Promise完毕之前有可以有多个回调添加到它上面
// 变动状况 => 绑定数据 => 实行回调函数集
function resolve(value){
if(that.status === 'pending'){
that.status = 'resolved'
that.data = value
for(var i = 0; i < that.onResolvedCallback.length; ++i){
that.onResolvedCallback[i](value)
}
}
}
function reject(reason){
if(that.status === 'pending'){
that.status = 'rejected'
that.data = reason
for(var i = 0; i < that.onResolvedCallback.length; ++i){
that.onRejectedCallback[i](reason)
}
}
}
try{
executor(resolve, reject) // resolve, reject两个函数可以在外部传入的函数(executor)中挪用
} catch(e) { // 斟酌到实行历程可以有错
reject(e)
}
}
// 规范是没有catch要领的,完成了then,就完成了catch
// then/catch 均要返回一个新的Promise实例
MyPromise.prototype.then = function(onResolved, onRejected){
var that = this
var promise2
// 值穿透
onResolved = typeof onResolved === 'function' ? onResolved : function(v){ return v }
onRejected = typeof onRejected === 'function' ? onRejected : function(r){ return r }
if(that.status === 'resolved'){
return promise2 = new MyPromise(function(resolve, reject){
try{
var x = onResolved(that.data)
if(x instanceof MyPromise){ // 如果onResolved的返回值是一个Promise对象,直接取它的结果做为promise2的结果
x.then(resolve, reject)
}
resolve(x) // 不然,以它的返回值做为promise2的结果
} catch(e) {
reject(e) // 如果失足,以捕捉到的毛病做为promise2的结果
}
})
}
if(that.status === 'rejected'){
return promise2 = new MyPromise(function(resolve, reject){
try{
var x = onRejected(that.data)
if(x instanceof MyPromise){
x.then(resolve, reject)
}
} catch(e) {
reject(e)
}
})
}
if(that.status === 'pending'){
return promise2 = new MyPromise(function(resolve, reject){
self.onResolvedCallback.push(function(reason){
try{
var x = onResolved(that.data)
if(x instanceof MyPromise){
x.then(resolve, reject)
}
} catch(e) {
reject(e)
}
})
self.onRejectedCallback.push(function(value){
try{
var x = onRejected(that.data)
if(x instanceof MyPromise){
x.then(resolve, reject)
}
} catch(e) {
reject(e)
}
})
})
}
}
MyPromise.prototype.catch = function(onRejected){
return this.then(null, onRejected)
}
// 以下是简朴的测试样例:
new MyPromise(resolve => resolve(8)).then(value => {
console.log(value)
})
1.8 Event Loop
题目:说一下JS的EventLoop
实在阮一峰先生这篇《JavaScript 运转机制详解:再谈Event Loop》已讲的很清楚了(手动赞)!
这里简朴总结下:
- JS是单线程的,其上面的一切使命都是在两个处所实行:实行栈和使命行列。前者是寄存同步使命;后者是异步使命有结果后,就在个中放入一个事宜。
- 当实行栈的使命都实行完了(栈空),js会读取使命行列,并将可以实行的使命从使命行列丢到实行栈中实行。
- 这个历程是轮回举行,所以称作
Loop
。
2. CSS相干
2.1 程度垂直居中
题目: 两种以上体式格局完成已知或许未知宽度的垂直程度居中
第一种要领就是运用CSS3
的translate
举行偏移定位,注重:两个参数的百分比都是针对元素自身盘算的。
.wrap {
position: relative;
width: 100vw;
height: 100vh;
}
.box {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
第二种要领是运用CSS3
的flex
规划,父元素diplay属性设置为flex
,而且定义元素在两条轴线的规划体式格局均为center
.wrap {
display: flex;
justify-content: center;
align-items: center;
width: 100vw;
height: 100vh;
}
.wrap .box {
width: 100px;
height: 100px;
}
第三种要领是运用margin负值来举行元素偏移,长处是浏览器兼容好,瑕玷是不够天真(要自行盘算margin的值):
.wrap {
position: relative;
width: 100vw;
height: 100vh;
}
.box {
position: absolute;
top: 50%;
left: 50%;
width: 100px;
height: 100px;
margin: -50px 0 0 -50px;
}
2.2 “点击”转变款式
题目:完成结果,点击容器内的图标,图标边框变成border 1px solid red,点击空白处重置。
运用event.target
可以推断是不是是指定元素自身(推断“空白处”),除此之外,注重制止冒泡(题目指清楚明了“容器内”)。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
#app {
min-width: 100vw;
min-height: 100vh;
}
#app .icon{
display: inline-block;
cursor: pointer;
}
</style>
</head>
<body>
<div id="app">
<span class="icon">123456</span>
</div>
<script>
const app = document.querySelector("#app")
const icon = document.querySelector(".icon")
app.addEventListener("click", e => {
if(e.target === icon){
return;
}
// 非空白处才去除 border
icon.style.border = "none";
})
icon.addEventListener("click", e => {
// 制止冒泡
e.stopPropagation()
// 变动款式
icon.style.border = "1px solid red";
})
</script>
</body>
</html>
3. 浏览器/协定相干
3.1 缓存机制
题目:说一下浏览器的缓存机制。
浏览器缓存分为强缓存和协商缓存。缓存的作用是进步客户端速率、节约收集流量、下降服务器压力。
强缓存:浏览器要求资本,如果header中的Cache-Control
和Expires
没有逾期,直接从缓存(当地)读取资本,不须要再向服务器要求资本。
协商缓存:浏览器要求的资本如果是逾期的,那末会向服务器发送要求,header中带有Etag
字段。服务器再举行推断,如果ETag婚配,则返回给客户端300系列状况码,客户端继承运用当地缓存;不然,客户端会从新猎取数据资本。
关于历程当中细致的字段,可以参考这篇《http协商缓存VS强缓存》
3.2 从URL到页面天生
题目:输入URL到看到页面发作的全历程,越细致越好
- DNS剖析
- 竖立TCP衔接(3次握手)
- 发送HTTP要求,从服务器下载相干内容
- 浏览器构建DOM树和CSS树,然后天生衬着树。这个一个渐进式历程,引擎会力图最快将内容显现给用户。
- 在第四步的历程当中,
<script>
的位置和加载体式格局会影响响应速率。 - 搞定了,封闭TCP衔接(4次握手)
3.3 TCP握手
题目:诠释TCP竖立的时刻的3次握手和封闭时刻的4次握手
看这题的时刻,我也是倏忽懵(手动捂脸)。引荐翻一下盘算机收集的相干书本,关于FIN
、ACK
等字段的解说很赞!
3.4 CSS和JS位置
题目:CSS和JS的位置会影响页面效力,为何?
先说CSS。CSS的位置不会影响加载速率,然则CSS平常放在<head>
标签中。前面有说DOM树和CSS树配合天生衬着树,CSS位置太靠后的话,在CSS加载之前,可以会涌现闪屏、款式杂沓、白屏等状况。
再说JS。JS是壅塞加载,默许的<script>
标签会加载而且马上实行剧本,如果剧本很庞杂或许收集不好,会涌现良久的白屏。所以,JS标签平常放到<body>
标签末了。
如今,也可认为<script>
标签设置async
或许defer
属性。前者是js剧本的加载和实行将与后续文档的加载和衬着同步实行。后者是js剧本的加载将与后续文档的加载和衬着同步实行,当一切元素剖析完,再实行js剧本。
4. 算法相干
4.1 数组全分列
题目:如今有一个数组[1,2,3,4],请完成算法,取得这个数组的全分列的数组,如[2,1,3,4],[2,1,4,3]。。。。你这个算法的时候庞杂度是多少
完成思绪:从“最先元素”起,每一个元素都和最先元素举行交流;不停减少局限,末了输出这类分列。暴力法的时候庞杂度是 $O(N_N)$,递归完成的时候庞杂度是 $O(N!)$
怎样去重?去重的全分列就是从第一个数字起每一个数离别与它背面非反复涌现的数字交流。关于有反复元素的数组,比方:[1, 2, 2]
,应当剔除反复的状况。每次只须要搜检arr[start, i)
中是不是是有和arr[i]
雷同的元素,有的话,申明之前已输出过了,不须要斟酌。
代码完成:
const swap = (arr, i, j) => {
let tmp = arr[i]
arr[i] = arr[j]
arr[j] = tmp
}
const permutation = arr => {
const _permutation = (arr, start) => {
if(start === arr.length){
console.log(arr)
return
}
for(let i = start; i < arr.length; ++i){
// 全分列:去重操纵
if(arr.slice(start, i).indexOf(arr[i]) !== -1){
continue
}
swap(arr, i, start) // 和最先元素举行交流
_permutation(arr, start + 1)
swap(arr, i, start) // 恢复数组
}
return
}
return _permutation(arr, 0)
}
permutation([1, 2, 2])
console.log("**********")
permutation([1, 2, 3, 4])
4.2 背包题目
题目:我如今有一个背包,容量为m,然后有n个货色,重量离别为w1,w2,w3…wn,每一个货色的价值是v1,v2,v3…vn,w和v没有任何关系,要求背包能装下的最大价值。
这个还在进修中,背包题目博大精深。。。
4.3 图的连通重量
题目:我如今有一个canvas,上面随机布着一些黑块,请完成要领,盘算canvas上有多少个黑块。
这一题可以转化成图的联通重量题目。经由历程getImageData
取得像素数组,从头至尾遍历一遍,就可以够推断每一个像素是不是是黑色。同时,预备一个width * height
大小的二维数组,这个数组的每一个元素是1/0
。如果是黑色,二维数组对应元素就置1;不然置0。
然后题目就被转换成了图的连通重量题目。可以经由历程深度优先遍历或许并查集来完成。之前我用C++完成了,这里不再冗赘:
5. Web工程化
5.1 Dialog组件思绪
题目:如今要你完成一个Dialog组件,说说你设想的思绪?它应当有什么功用?
- 可以指定宽度、高度和位置
- 须要一个遮盖层,遮住底层内容
- 由头部、尾部和正文组成
- 须要监听事宜和自定义事宜,非单向数据流:比方点击组件右上角,修正父组件的
visible
属性,封闭组件。
关于工程化组件封装,可以去尝尝ElementUI。这个是ElementUI的Dialog组件:Element-Dialog
5.2 React的Diff算法和假造DOM
题目: react 的假造dom是怎样完成的
原答案写的挺好滴,这里直接贴了。
起首说说为何要运用Virturl DOM,由于操纵实在DOM的消耗的机能价值太高,所以react内部运用js完成了一套dom构造。
在每次操纵在和实在dom之前,运用完成好的diff算法,对假造dom举行比较,递归找出有变化的dom节点,然后对其举行更新操纵。
为了完成假造DOM,我们须要把每一种节点范例笼统成对象,每一种节点范例有自身的属性,也就是prop,每次举行diff的时刻,react会先比较该节点范例:
如果节点范例不一样,那末react会直接删除该节点,然后直接竖立新的节点插进去到个中;
如果节点范例一样,那末会比较prop是不是有更新,如果有prop不一样,那末react会剖断该节点有更新,那末重衬着该节点,然后在对其子节点举行比较,一层一层往下,直到没有子节点。
参考链接:React源码之Diff算法
末了,迎接来我的博客和我扯犊子:godbmw.com。直接戳本篇原文的地点:刷《一年半履历,百度、有赞、阿里口试总结》·手记