前端面试
作者:高天阳
邮箱:13683265113@163.com
笔试
1. 利用html css 编写样式,div垂直body居中、div内的text垂直居中,div高度等于body宽度的一半
分析:本题考点分为div的垂直居中、文本的垂直居中、body的宽度获取、margin溢出的处理
答:
2. 下面代码输出结果是什么
分析:此题考点为JS的数据类型及隐式转换
数据类型
首先,先来了解两个概念:栈 stack 和 堆 heap
堆、栈
栈(stack):先进后出,栈会自动分配内存空间,会自动释放,存放基本类型,简单的数据段,占据固定大小的空间, 使用的是一级缓存,他们通常都是被调用时处于存储空间中,调用完立即释放。
基本类型:String,Number,Boolean,Null,Undefined,Symbol(es6)
它们是直接按值存放的,可以直接访问。
堆(heap):队列优先,先进先出,动态分配的内存,大小不定也不会自动释放,存放引用类型,指那些可能由多个值构成的对象,保存在堆内存中, 包含引用类型的变量,实际上保存的不是变量本身,而是指向该对象的指针。使用的是二级缓存,生命周期由虚拟机的垃圾回收算法来决定, 一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。
引用类型:Function,Array,Object
当我们需要访问引用数据类型的值时,首先从栈中获得该对象的地址指针,然后再从堆内存中取得所需的数据。
堆、栈的区别
栈:所有在方法中定义的变量都是放在栈内存中,随着方法的执行结束,这个方法的内存栈也自然销毁。
优点:存取速度比堆快,仅次于直接位于CPU中的寄存器,数据可以共享;
缺点:存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。
堆:堆内存中的对象不会随方法的结束而销毁,即使方法结束后,这个对象还可能被另一个引用变量所引用(参数传递)。 创建对象是为了反复利用,这个对象将被保存到运行时数据区。
栈和堆的溢出
栈:可以递归调用方法,这样随着栈深度的增加,JVM维持着一条长长的方法调用轨迹,直到内存不够分配,产生栈溢出。
堆:循环创建对象,通俗点就是不断的new 一个对象。
下面来看看传值和传址的区别
其实这两者区别就是基本类型和引用类型的区别,话不多说看栗子
因为a是数组,是引用类型,赋给b的时候传的是栈中的地址,不是堆内存中的对象,c仅仅是从a堆内存中获取的一个数据值,并保存在栈中, 所以b修改的时候,会根据地址回到a堆中修改,c则直接在栈中修改,并且不能指向a堆内存中。
深浅拷贝
关于引用类型,深浅拷贝在前端面试中经常被问到,和大家分享一下,先来说说浅拷贝
浅拷贝:也就是只复制了第一层属性,复制对象是基本类型
在复制基本类型时,直接使用等号完成,在复制引用类型时,循环遍历对象,对每个属性或值使用等号完成。
下面看个栗子
在这个栗子中,color2复制color1,因为数组中的每一项都是基本类型(string),假如数组中的某一项保存的是一个对象, 或者是一个数组,又或者说对象的某一个属性还是一个对象(也就是引用类型的某个属性还是引用类型),此时浅拷贝就没用了,那该怎么办?
我们先来看一个引用类型属性还是引用类型的栗子(有点绕口.....)
在上面这个小栗子中,hobby属性是一个数组。a对象中的name属性值为字符串,hobby为数组。a拷贝到b,两个属性均能顺利拷贝。 给b对象新增一个number类型的属性age时,b能够正常修改,而a中无定义。说明 子对象 b 的 age 并没有关联到 父对象 a 中,所以为undefined。
但是,若修改的属性为对象或数组时,那么父子对象之间就会发生关联。其在内存的状态,见下图
下面继续来说我们的拷贝,现在该深拷贝出场了......
深拷贝:对属性中所有引用类型的值,遍历到是基本类型的值为止,利用递归来实现深拷贝。
来看一个栗子
对于深拷贝,我们先判断它是否为引用类型,如果不是,直接返回如果是,循环遍历该对象的属性, 如果某个属性还是引用类型,则针对该属性再次调用deepClone函数。
所以,现在就可以解释 var a = function(){}; var b = function(){}; a==b
为false了。
变量a实际保存的是指向堆内存中对象的一个指针,而b保存的是指向堆内存中另一个对象的一个指针;虽然这两个对象的值是一样的, 但它们是独立的2个对象,占了2份内存空间;所以 a==b 为 false。
如果 var a = {}; var b = a; 这时变量b复制了变量a保存的指针,它们都指向堆内存中同一个对象;所以 a==b 为 true。
{} == {} and [] == [] 同理不做赘述
隐式类型转换
ToPrimitive
在发生转换的时候,js其实都是会将操作对象转化为原始的对象,这也是最为诟病的地方,因为js很难直接抛出错误, 她会用一套自己的方法去理解我们的错误,并做相应的调整,哪怕这些错误我们是无意识的。所以我们要知道她的转换方式, 才能做到知己知彼,对代码的控制更为精准。
流程如下:
input为原始值,直接返回;
不是原始值,调用该对象的valueOf()方法,如果结果是原始值,返回原始值;
调用valueOf()不是原始值,调用此对象的toString()方法,如果结果为原始值,返回原始值;
如果返回的不是原始值,抛出异常TypeError。
其中PreferredType控制线调取valueOf()还是toString()。
ps: Date类型按照String去调用。
ok,通过这个隐式装箱,我们得到了操作数的原始值。接下来,我们根据不同情况,看看发生了什么呢~
数学运算
想必大家用过以下做法去完成类型转换吧
这种类似的数学运算会做类型转换,*,/和-操作符都是数字运算专用的。 当这些运算符与字符串一起使用时,会强制转换字符串为数字类型的值。但是‘+’尤为致命,为啥捏?
当'+'作为双目运算符时,如a+b。
它的运行如下:
计算两个操作数的原始值: prima = ToPrimitive(a), prima = ToPrimitive(b);
如果原始值有String,全部转换为String,返回String相加后的结果;
如果原始值没有String,全部转换为Number, 返回Number相加后的结果;
当'+'作为单目运算符时, 例如 +a.
流程是这样的:
将a转换为Number,Number(a);
举个栗子:
[] + []
{} + [] 与 [] + {}
PS: [].valueOf 为[], 但在ES6中JS会优先调用[Symbol ToPrimitive]来转换为原始类型。
比较运算
首先,比较运算分为2种, 一种为严格比较(===),只有类型相等,值也一致时才会为true,否则为false, 另一种为抽象相等也叫宽松相等(==),先将运算数转化为相同类型,再做比较,具体过程见 Abstract Equality Comparison Algorithm。
这个算法大致说了这么几个情况,x == y
xy都为Null或undefined,return true;
x或y为NaN, return false;
如果x和y为String,Number,Boolean并且类型不一致,都转为Number再进行比较
如果存在Object,转换为原始值,比较
回到开头的题目
[] == false
{} == false
[]
ToPrimitive
value
toNumber
toString
toBoolean
NaN
NaN
"NaN"
false
Infinity
Infinity
"Infinity"
false
[]
0
""
true
[1]
1
"1"
true
null
0
"null"
false
undefined
NaN
"undefined"
false
{}
NaN
"[object Object]"
true
function()
NaN
"function"
true
ToNumber
参数
结果
undefined
NaN
null
+0
Boolean
true被转换为1,false被转换为+0
Number
无需转换
String
由字符串解析为数字,例如"324"被转换为324
ToString
参数
结果
undefined
"undefined"
null
"null"
Boolean
"true"或"false"
Number
数字作为字符串,比如"1.765"
String
无需转换
答:4、6
基本类型练习: 运算符(+,-,*,/,%)操作时,转换类型 ”+“ 号运算符:
总结:
当加号运算符时,String和其他类型时,其他类型都会转为 String;其他情况,都转化为Number类型, 注: undefined 转化为Number是 为’NaN‘, 任何Number与NaN相加都为NaN。 其他运算符时, 基本类型都转换为 Number,String类型的带有字符的比如: '1a' ,'a1' 转化为 NaN 与undefined 一样。
tips:
(1)NaN 不与 任何值相等 包括自身,所以判断一个值 是否为 NaN, 即用 "!==" 即可。 (2)转换为 Boolean类型为 false 的有:null,0,'',undefined,NaN,false (3)number() 与 parseInt() 都可以将对象转化为Number类型,Number函数要比parseInt函数严格很多。基本上,只要有一个字符无法转成数值,整个字符串就会被转为NaN。
Object类型练习: 当object与基本类型运算时:
当对 obj,obj1 用Number()和String()换转时
总结:
Number类型会先调用valueOf(), String类型会先调用toString(), 如果结果是原始值, 则返回原始值,否则继续用toString 或 valueOf(),继续计算,如果结果还不是原始值,则抛出一个类型错误;
为什么 {} + [] = 0 ? 因为 javascript在运行时, 将 第一次{} 认为是空的代码块,所以就相当于 +[] = 0. 还有 {} +5 = 5, 同理。
3. 利用宏任务,微任务的知识点判断程序输出
分析:
4. bind函数实现
分析:
5. trottle函数实现
分析:
6. 给定一个不含重复数字的数组arr,指定个数n,目标和sum,判断是否含有由n个不同数字相加得到sum的情况
分析:
参考资料
Last updated
Was this helpful?