小厂面试总结
成都某小厂前端面试经验,走进去就给了一套题让做来看看,题目很简单但是坑比较多吧,考察的都是基础内容。。。。
笔试部分
1、5+4+3+2+’1‘ = ?
这里考察的是操作符 ‘+’ 在进行运算的时候的符号类型转换规则。
- NaN + NaN = NaN
- Infinity + Infinity = Infinity
- -Infinity + -Infinity = -Infinity
- Infinity + -Infinity = NaN
- +0 + +0 = +0
- -0 + -0 = -0
- +0 + -0 = +0
- String + String = 将两个字符串拼接
- String + 其他 = 将其他操作数转为字符串,再与字符串进行拼接。
因此,这道题的答案是141,将number类型相加再与字符串进行拼接
2、0.1 + 0.2 > ? < ? = 0.3
这道题考的是浮点数的精度问题,看见这道题的时候就想起了上学期学计算机基础的时候算二进制加减法的一道典型题,回来翻了一下书
由于js中不论小数还是整数,都只有一个number类型表示,因此number类型只能为双精度
双精度小数的格式: 符号位(1)+ 阶码(11)+ 尾数(52)
0.1 的二进制表示为0.00011000110001100011000110001…
0.2的二进制表示为0.001100110011001100110011001100…
由此可以发现这两个小数的二进制表示都是一个无限循环的小数,因此在进行二进制加法运算时,结果也是一个无限小数,因此计算机系统会对结果进行一个舍入操作,导致最后的结果近似于0.30000000000000004
因此本题答案应该是 0.1 + 0.2 > 0.3
3、box-sizing:border-box的理解和作用
这道题考察的是css盒子模型的内容,首先盒子模型的概念是:盒子模型= 外边距(margin)+border(边框)+ 内边距(padding)+content(内容)
一般浏览器默认盒子模型为标准盒子模型(box-sizing:content-box),即宽高会加上padding和border的值,需要手动计算调整content的值,以免超出给定的宽高。
当设置了box-sizing:border-box属性后,padding和border的值就不会影响宽高,将padding和border的值都算在content中,盒子会自动根据padding和border的值调整content的值,不需要手动调整。
3、[‘1’,‘2’,‘3’].map(parseInt) 的结果是什么?
本题考查的是数组的map()方法和parseInt函数:对数组中每一项给定函数,返回每次函数调用的结果组成的数组。
因此,就是调用parseInt(‘1’,0),parseInt(‘2’,1),parseInt(‘3’,2)函数的过程。
现在了解一下parseInt函数。
概念:以第二个参数为基数来解析第一个参数字符串,通常用来做十进制向上取整
特点:接收两个参数parseInt(string,radix)
string:字母(大小写均可)、数组、特殊字符(不可放在开头,特殊字符及特殊字符后面的内容不做解析)的任意字符串,如 ‘2’、‘2w’、‘2!’
radix:解析字符串的基数,基数规则如下:
- 区间介于[2,36]
- 参数为0时根据十进制解析
- 不传入基数参数时,默认的基数规则为:
- 如果 string 以 “0x” 开头,parseInt() 会把 string 的其余部分解析为十六进制的整数;parseInt(“0xf”) //15
- 如果 string 以 0 开头,其后的字符解析为八进制或十六进制的数字;parseInt(“08”) //8
- 如果 string 以 1 ~ 9 的数字开头,parseInt() 将把它解析为十进制的整数;parseInt(“88.99f”) //88
- 只有字符串中的第一个数字会被返回。parseInt(“10.33”) //返回10;
- 开头和结尾的空格是允许的。parseInt(" 69 10 ") //返回69
- 如果字符串的第一个字符不能被转换为数字,返回 NaN。parseInt(“f”) //返回NaN 而parseInt(“f”,16) //返回15
根据上面的规则分析一下本题:parseInt(‘1’,0),基数参数为0,按十进制解析,因此返回数字1
parseInt(‘2’,1),基数参数为1,不在基数参数的范围内,因此结果为NaNcy
parseInt(‘3’,2),基数参数为2,默认按二进制解析,二进制应为0、1组成,因此无法解析,返回结果为NaN
因此本题答案为[1,NaN,NaN];(这道题是真没想到这方面,一切归结于基础太弱!)
既然都到这里了,就总结一下数组的遍历方法,警示下一次面试!!
方法 | 用法 |
---|---|
every() | 对数组中的每一项运行给定函数,如果该函数对每一项都返回true,则返回true |
filter() | 对数组中的每一项运行给定函数,返回函数会返回true的项组成的函数 |
forEach() | 对数组中的每一项运行给定函数,无返回值 |
map() | 对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组 |
some() | 对数组中的每一项运行给定函数,如果该函数对任一项返回true,则返回true |
呜呜呜,js基础真的太薄弱了,在这里深刻检讨反思
4、await/async 的理解和用法
其实这个考点给的是一个代码题,让回答代码的执行输出顺序,但是代码内容记不住了,只能以这种方式总结了
async
是异步的意思,await
则可以理解为async wait
。所以可以理解async
就是用来声明一个异步方法,而await
是用来等待异步方法执行
async
放在函数前面,表明该函数是一个异步函数,表示该函数的执行不会阻塞后面代码的执行,await
用于等待一个异步方法执行完成
异步函数返回一个promise,当函数内部执行到await语句时,如果语句返回的是一个promise对象,则等待promise对象的状态变为resolve后再继续往下执行,并阻塞函数内部后面的代码,若不是一个promise,则返回值。
因此,async/await的作用是将异步逻辑,转化为同步的顺序来书写,是为了优化.then链而开发出来的
啊啊啊突然想起来这道题的坑了,返回的是一个promise对象而不是值本身!!!!当时脑袋抽风了没想起来。。。。
5、var、let、const作用域范围
这应该是一道代码题,考察了var范围和立即执行函数的考点,记忆中代码是这样的:
var name = Lily
(function(){
if(typeof name === undefined){
var name = Tom
console.log('Hello' + name);
}else{
console.log('GoodBye' + Lily)
}
})()
es6中新增了let和const关键字,那与var的区别在哪里呢?下面整理了一下:
- 作用域区别
let和const具有块级作用域,var不存在块级作用域,var声明的变量是一个全局变量,可以跨块访问但不能跨函数访问function testVar(){ var test = 'aaaaa' } testVar(); console.log(test)//此时输出报错,因为var不能跨函数访问
- 变量提升
JavaScript中存在变量提升机制,即允许变量在声明之前使用,例如:console.log(a) var a = 'a';
上述代码能成功执行,就是因为变量提升机制的存在。var关键字存在变量提升,let和const不存在变量提升
3. 全局属性
浏览器的全局对象是window。var声明的变量为全局变量,并且会将变量添加为全局对象的属性,但是let和const不会var a = 'a'; console.log(window.a);
- 重复声明
var声明变量时可以重复声明,后声明的变量会覆盖之前声明的变量,而const和let不允许重复声明变量。
因此在开发中尽量避免使用var关键字声明变量,容易造成全局变量污染。- 变量初始值设置
var和let关键字声明变量时都可以不赋初始值,而const声明的变量必须设置初始值。
因为const声明的变量可以理解为一个常量,也就是说const声明的变量不能改变指针的指向,但是可以改变指针指向地址的值,因此对于一个引用类型的变量例如对象来说,不能更改对象名,但是可以修改对象内的属性(我是这样理解的)
总结到这个地方之后,我们可以清楚的知道var
定义的变量是一个全局变量,那答案是不是走else
判断的分支呢?当然不是,我们看见,判断是放在了一个立即执行函数里面,让我们来看看什么是立即执行函数。
立即执行函数表达式是一个在定义时就会立即执行的JavaScript函数,立即函数的写法为:
(function(){})()
显而易见,题目中给的函数是一个立即执行函数,立即执行函数与普通函数相比除了无需调用执行的区别以外还有哪些区别呢?
这就是这道题的精华所在,普通函数中的变量不能从外部访问,但是普通函数内部可以访问外部的变量,立即执行函数与普通函数在这点上类似。区别在于函数定义作用域,一个函数可以使用它所被定义的作用域中的所有变量,因此当一个被定义在全局的普通函数,可以使用全局中定义的变量。
而对于一个立即执行函数来说,它的作用域已经被限制在一个块里了,(function(){})()
,因此只能访问当前的块作用域中定义的所有变量,自然不能访问全局的变量。
因此,这道题的答案应该是执行第一个分支,因为在立即执行函数中不能拿到全局中定义的全局变量,这里的坑也是在var
关键字定义的变量是全局变量上,容易造成混淆。
6、如何实现数组去重?
这应该是一道很简单的算法题,我首先想到的肯定是用set
实现数组去重,但是想想直接调api好像有点不太聪明,就多写了几种解决方案:
方法一:用set去重
var array = [1,1,1,2,3,3,4,4,5,6,7,7,8];
let newArray = [...new Set(array)]
console.log(newArray);
方法二:用filter实现去重
let array = [1,1,1,2,3,3,4,4,5,6,7,7,8];
let newArray;
newArray = array.filter((item,index)=>{
return array.indexOf(item) === index;
})
console.log(newArray);
方法三:利用for循环嵌套indexOf实现
let array = [1,1,1,2,3,3,4,4,5,6,7,7,8];
let newArray = [];
for(let i = 0; i < array.length; i++){
if(newArray.indexOf(array[i]) === -1){
newArray.push(array[i])
}
}
console.log(newArray);
方法四:嵌套两层for循环依次将数组的每一个元素依次与其他元素做比较,发现重复元素使用splice删除。
for(let i = 0; i < array.length-1; i++){
for(let j = i+1; j < array.length; j++){
if(array[i] === array[j])
{
array.splice(j,1);
j--;
}
}
}
方法五:借助新数组 通过 indexOf 方法判断当前元素在数组中的索引,如果与循环的下标相等则添加到新数组中
for(let i = 0; i < array.length; i++){
if(array.indexOf(array[i]) === i){
newArray.push(array[i]);
}
}
console.log(newArray);
方法六:使用双重for循环,若内外层元素相同且内外侧下标不一致,则数组重复,删除重复项
for(let i = 0; i < array.length; i++){
for(let j = 0; j < array.length; j++){
if(array[i] === array[j] && i !== j){
array.splice(j,1);
}
}
}
console.log(array);
方法七:利用includes实现去重
for(let i = 0; i < array.length; i++){
if(!newArray.includes(array[i])){
newArray.push(array[i])
}
}
console.log(newArray);
到这里,就是笔试的全部内容了呜呜呜呜,做这套题的唯一想法就是后悔来之前没有再好好看看JavaScript,从现在开始精读红宝书了。后面继续更新面试的时候面试官提的一些比较经典的问题。