hr小姐姐说一共有1轮笔试 + 3轮技术面 + 1轮hr面,面试地点在中关村天使大厦,岗位是1-3年前端,时间是2019年8月
笔试
笔试分为多选 简答 判断 手写代码四部分,下面只写了印象比较深的几道。
多选
1、position为relative的元素可以使用top和left进行定位吗
答:可以。
我自己没见过这种写法,就没敢选,然后错。
2、以下哪个是加密算法
答:RES、DES。
md5不算加密算法。
简答
这部分题目是给出代码,让你写输出
1、
setTimeout(() => {console.log(1)})
const promise = new Promise(resolve => {
setTimeout(() => {console.log(2)})
resolve()
})
promise.then(() => {console.log(3)})
答:312。
考察macro/micro task
2020.5.29更新
这两天去面试又栽在这个题的plus版上了,看题
async function f1() {
print(2)
await print(3);
await print(3.5);
print(4)
}
new Promise((res, rej) => {
setTimeout(() => {
print(8)
})
print(7)
res()
}).then(() => {
print(9)
})
f1()
// 答案在文末
我看 macro/micro的时候也没见说 async是哪种,查看 mdn,他说 "如果 await 后面的值不是一个 Promise,await 会把该值转换为已正常处理的 Promise,然后等待其处理结果。"也就是说题目中的 f1
里面的代码转换成 promise
版本就是这样⬇
async function f1() {
print(2)
Promise.resolve(print(3)).then(() => {
Promise.resolve(print(3.5)).then(() => {
print(4)
})
})
}
对了 ie不支持async/await语法,不愧是你 ie
------------2021.3.23更新--------
最近看到一篇讲 eventLooooooop
的好文,里面第三节 microTask执行时机讲的真的很好,强烈推荐,下面是我总结的他的总结
1、每个eventloop 有三部分:task,microTask, 可能存在UI render
2、microTask中注册的microTask会直接添加到当前microTask队列
3、microTask的执行时机为‘尽可能早’,只要JavaScript执行栈为空就会执行,一轮eventloop可能不止一轮microTask执行
4、从不同task源来的task可能会被放进不同的task队列, 通过区分队列的优先级来保证更好的交互体验(比如鼠标和键盘事件的task队列优先级就会比较高)
------------2021.3.23更新完--------
2、
for(var i = 1; i < 3; i++) {
setTimeout(() => {console.log(i)})
}
答:3 3
考察异步,这个题简直是必考题
变种:
for(let i = 1; i< 3; i++) {
setTimeout(() => {console.log(i)})
}
答:1 2
用let的话就会每轮循环都是一个崭新的i
3、
function A() {
this.a = 'hi'
console.log(this.a)
}
A.prototype.a = 'hello'
const a = new A()
console.log(a.a)
答:hi hi
考察原型链,A.prototype.a = 'hello'
,修改的是a原型上的a属性,与a本身的a属性无瓜。
浏览器运行截图
4、
[] == false
答:true
考察类型转换,双等运算两边先转换为Number
5、
[1,2,3].push(4)
答:4
考察常用函数返回值, 数组的push和unshift都返回最新数组的长度
判断
判断就5道题,挺简单的,没啥印象
手写代码
手写一个节流函数,这个网上一搜一大把就不说了
一面
笔试写了大概30-40分钟,一面的面试官就来了,看答题情况的时候顺便要求介绍一下自己,然后针对题目做了一些讲解,然后开始问问题。
1、再手写一个防抖,我写了一个第一次触发事件不会调用回调的,面试官又问如果希望首次也会调用怎么写,代码如下
var debounce = function(fn, delayTime, immediate) {
var timeId;
return function() {
var context = this, args = arguments;
if(immediate) {
var callNow = !timeId;
if(callNow) {
fn.apply(context, args);
}
}
timeId && clearTimeout(timeId);
timeId = setTimeout(function() {
fn.apply(context, args);
}, delayTime)
}
}
然后还聊了一下时间戳和定时器的方式实现节流的不同,需要注意箭头函数是不可以使用arguments对象的,所以返回的函数必须要写成return function() {}
2、有什么实现深拷贝的方法吗
我一开始以为他说api,就回答JSON.parse(JSON.stringfy())和MessageChannel,他问有什么问题吗。我说不能解决复制函数和环的问题。他又问那你能自己实现一个吗,继续手写代码
function isObject(obj) {
return obj !== null && typeof obj === 'object'
}
function cloneDeep(obj) {
let result = {}
const keys = Object.keys(obj);
for(let i = 0, len = keys.length; i < len; i++) {
if(isObject(obj[keys[i]])){
result[keys[i]] = cloneDeep(obj[keys[i]])
} else {
result[keys[i]] = obj[keys[i]]
}
}
return resultset
}
写完之后他又问我应该如何判断一个变量是数组,答Array.isArray()和Object.prototype.toString.call(arr) === '[object Array]',回来反思发现可能是写深拷贝的时候忘记了数组的情况,然后他才问的判断数组。
2020.2.5更新
深拷贝写的是个p,正确写法参照解决循环引用和相同引用的js深拷贝实现
3、如何用css画一个三角形
答:heigh: 0; width: 0; border: 100px, solid, transparent; border-bottom: 100px, solid, yellow;
4、怎么实现垂直居中
答:position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); 还有flex;
5、简单说一下前端优化策略
答:减少请求,他:具体应该怎么减少,我:比如图片懒加载,配置svg-sprite-loader打包一张svg图面,然后就是减少dom操作,减少浏览器回流重绘次数,减少作用域链的查找,减少对象的深度查找。他:还有吗。我:暂时想不起其它了
优化涉及的东西太多了,以后再单独总结吧。
6、new一个对象的时候发生了什么
这个问题是讲解笔试简答第三题时候问的
正确答案:1.创建一个空对象; 2.设置创建对象的__proto__属性的值为构造函数的prototype属性的值; 3.将第一步创建的空对象作为this的上下文,执行构造函数代码; 如果构造函数没有返回对象,那么返回this
7、看你简历上写最近在看vue源码,那你知道nextTick咋实现的吗
答:2.6的版本是promise,mutationObserver,setTimeout,setImmediate
至此面试官说一面差不多就到这里,算法啥的留给二面吧,他给我的一面评价是知识广度不够(因为笔试错了比较多),但是感觉人比较有灵性,可以进入二面,然后就去叫下一个boss了。
二面
二面面试官看起来比前一个要严厉好多,以为要问一些算法题,结果"一面反馈基础不够扎实,那我就再问一点" "GG"
1、import和require的区别
答:import输出引用,require输出拷贝。他:还有吗。 我:不知道了。他:还有require是运行时加载,import是编译时输出接口。
2、说一下浏览器的事件传播机制
答:不知道
正确答案: 事件传播分为三个阶段:捕获,目标对象,冒泡。其中捕获是事件对象从window派发到目标对象父级的过程;目标阶段是事件对象派发到目标元素时的阶段,如果事件类型指示不冒泡,那事件传播在此阶段终止;冒泡和捕获相反,是以目标对象父级到window的过程。
3、手写一个bind
答:不知道。
平时用的都是call和apply来改this,bind只用过一两次,手写call也看过,但是我连bind的参数是啥都没印象,写个锤子。
正确答案:
///使用call
Function.prototype.cvBind = function() {
var self = this
var context = [].shift.call(arguments)
var args = Array.from(arguments)
return function() {
return self.call(context, ...args)
}
}
// 不用call
Function.prototype.cvBind = function() {
var context = [].shift.call(arguments)
context.fn = this
var args = []
var argument = [].slice.call(arguments, 0)
for(var i = 0, len = argument.length; i < len; i++) {
args.push('argument[' + i + ']')
}
return function() {
var result = eval('context.fn(' + args + ')')
delete context.fn
return result
}
}
// 测试
var obj = {
a: 'local',
log: function(x, y) {
console.log(this.a, x, y)
}
}
var a = 'window'
obj.log('arg1', 'arg2')
var func = obj.log.cvBind(window, 'arg1', 'arg2')
func()
需要注意不用call的版本需要拷贝一次arguments,不然return的函数中args数组里都是undefined,上面的代码不考虑参数是引用类型变量。
2019.10.29更新
因为return的是一个function(){}
而不是箭头函数,所以存在自己的arguments而不能使用闭包中的arguments,这里拷贝一遍是可以的可以,但是也可以返回一个箭头函数来直接使用父作用域中的arguments
2020.5.29更新
原文中 bind的写法依然有一个问题,就是使用这样的 bind进行柯里化的时候,不能正确拼接多个参数。
var add = (a, b) => a + b
add.bind(null, 1).bind(null, 2)()
mdn官方实现方法如下
var slice = Array.prototype.slice;
Function.prototype.bind = function() {
var thatFunc = this, thatArg = arguments[0];
var args = slice.call(arguments, 1);
if (typeof thatFunc !== 'function') {
throw new TypeError('Function.prototype.bind - ' + 'what is trying to be bound is not callable');
}
return function(){
var funcArgs = args.concat(slice.call(arguments))
return thatFunc.apply(thatArg, funcArgs);
};
};
4、写一个继承
答:不知道
正确答案:
4.1类式继承,通过构造函数实现继承
//父类
function Parent(name) {
this.name = name || 'parent'
}
Parent.prototype.say = function() {
return this.name
}
//子类
function Child() {}
4.1.1 父类对象继承
Child.prototype = new Parent('child')
var child = new Child()
child.say()
这种继承方式,子类继承父类自身属性和父类原型上的属性,但是缺点在于初始化父类对象指给子类原型时,并不能确定父类构造函数的初始化参数。
4.1.2 改造子类构造函数
function Child() {
Parent.apply(this, arguments)
}
弟中弟方法,只能继承父类自身方法
4.1.3 共享原型
Child.prototype = Parent.prototype
弟中弟中弟,共享一个原型,子类修改会影响父类(然而面试的时候脑子里浮现的就是这种)
4.1.4 临时构造函数
function inherit(Child, Parent) {
var F = function() {}
F.prototype = Parent.protoType
Child.protoype = new F()
}
利用一个空函数F()充当子类父类之间的代理,既可以实现父类原型属性的继承,也可以在子类原型上随意拓展
使用Object.create()
可以达到相同效果
Child.prototype = Object.create(Parent.prototype)
4.1.5 关于protptype.constructor
整理资料的时候,发现有些在继承后又写了一句Child.prototype.constructor = Child
,有些就没有。首先这个constructor时创建实例对象的构造函数的引用,然后就是这句话到底有用没用,下面是ctrl cv自MDN的两个例子以及结论
示例1:
function Parent() {};
function CreatedConstructor() {}
CreatedConstructor.prototype = Object.create(Parent.prototype);
CreatedConstructor.prototype.create = function create() {
return new this.constructor();
}
new CreatedConstructor().create().create(); // error undefined is not a function since constructor === Parent
在上面的示例中,将显示异常,因为构造函数链接到Parent。为了避免它,只需分配将要使用的必要构造函数。
function Parent() {};
function CreatedConstructor() {}
CreatedConstructor.prototype = Object.create(Parent.prototype);
CreatedConstructor.prototype.constructor = CreatedConstructor; // set right constructor for further using
CreatedConstructor.prototype.create = function create() {
return new this.constructor();
}
new CreatedConstructor().create().create(); // it's pretty fine
示例2:
function ParentWithStatic() {}
ParentWithStatic.startPosition = { x: 0, y:0 };
ParentWithStatic.getStartPosition = function getStartPosition() {
return this.startPosition;
}
function Child(x, y) {
this.position = {
x: x,
y: y
};
}
Child.prototype = Object.create(ParentWithStatic.prototype);
Child.prototype.constructor = Child;
Child.prototype.getOffsetByInitialPosition = function getOffsetByInitialPosition() {
var position = this.position;
var startPosition = this.constructor.getStartPosition(); // error undefined is not a function, since the constructor is Child
return {
offsetX: startPosition.x - position.x,
offsetY: startPosition.y - position.y
}
};
对于此示例,就需要保持父构造函数继续正常工作。
结论:手动设置或更新构造函数可能会导致不同且有时令人困惑的后果。为了防止它,只需在每个特定情况下定义构造函数的角色。在大多数情况下,不使用构造函数,并且不需要重新分配构造函数。
4.2 通过复制属性实现继承
浅拷贝和4.1.3的共享原型没区别,深拷贝继承之后修改父类,子类不会改变。都有问题不过也是一种思路,顺带一提。
5、跨域有哪些解决方案
答:iframe, jsonp, cors。他:用过jsonp吗。答:没有,用的都是cors。他:那说一下cors是怎么解决跨域问题的。我:不知道。他:那请求头里有哪些相关的字段。我:(我知道你真的很给机会了但是对不起我是个菜鸡我真的)不知道。他:用过nginx吗。我:没有。
正确答案:点这里
balabala一些客套话,问我有什么问题,我说没有,然后结束。
路漫漫其修远兮
认识到差距也更有前进的动力,继续加油
=============================================
答案:7 2 3 9 3.5 4 8
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。