把一些面试题目分享一下,偏基础底层。仅供参考,如有错误或者改进,可以在评论或者博客获取联系方式,谢谢指正。
HTML+CSS篇
.aaa{
font-size:20px;
background:red;
}
.bbb{
font-size:20px;
background:yellow;
float:left;
}
.ccc{
font-size:40px;
background:blue;
float:left;
}
.ddd{
font-size:20px;
background:orange;
}
<body>
<div class="aaa">
aaa
<div class="bbb">bbb</div>
<div class="ccc">ccc</div>
<div class="ddd">ddd</div>
</div>
</body>
复制代码
画出HTML图
aaa
被浮动影响所覆盖,但是bbb
属于aaa
的子元素,只会产生float文字围绕的效果,所以bbb
和ccc
会向左靠,aaa
和ddd
并列的原因是因为ccc
的font-size:40px
,占据了第二行ddd
的位置,并且有浮动属性,不会让后面的文字往前靠,所以才会产生这种效果。
页面元素垂直水平居中有哪几种方式
// 绝对定位
.box{
width: 200px;
height: 200px;
margin: auto;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
// css3+绝对定位
.box{
position:absolute;
top:50%;
left:50%;
transform: translate(-50%,-50%);
}
// flex
.box{
display: flex;
justify-content: center; /* 水平居中 */
align-items: center; /* 垂直居中 */
}
// table
.box{
display:table-cell;
text-align:center;
vertical-align:middle;
}
复制代码
平常用到的就这些了
CSS优化提高性能的方法有哪些
这个有很多优化的地方,比如资源加载方面,渲染性能,解析及回流方面都有所涉及, 移除空的元素选择器
- css资源托管CDN,避免并发加载上限,依赖加载,不让页面加载延后资源的时候进行重绘。
- js文件内尽量少修改首屏css样式,引发重绘与回流。尽量去创建图层避免多次回流,减少回流的次数。
- 用translate替代top改变
- 用opacity替代visibility
- 不滥用浮动、web字体
- 不滥用无效属性,比如使用了display:inline;就不要再设置width、height等属性
- 值为0时不需要任何单位
- 尽量少用标签选择器,因为浏览器是从最后的选择开始往前做匹配的
- 使用css特性样式的时候加上浏览器前缀
- 不要一条一条地修改 DOM 的样式,预先定义好 class,然后修改 DOM 的 className 把 DOM 隐藏后修改,比如:先把 DOM 给 display:none (有一次 Reflow),然后你修改100次,然后再把它显示出来
- 不要把 DOM 结点的属性值放在一个循环里当成循环里的变量
- 尽量不要使用 table 布局,可能很小的一个小改动会造成整个 table 的重新布局
- 新建图层实现动画效果
- 启用 GPU 硬件加速
max-height:0px 和 height:200px !important; 谁生效
min和max无视!important; 取前者
JS篇
怎么去手写实现一个new方法 以及new到底做了什么
这个问题有一半公司都问到了,可能是考基础和作用域链吧
function _new(fun) {
return function() {
let obj = {
__proto__: fun.prototype
}
fun.apply(obj, arguments)
return obj
}
}
复制代码
new运算符怎么从构造器中得到一个对象
在《JavaScript设计模式和开发实践一书》
中,我们可以这么去理解new运算的过程
var objectFactory = function(){
var obj = new Object();//创建一个Object
var Constructor = [].shift.call(arguments);//获取指定函数对象
obj.__proto__ = Constructor.prototype;//Object.prototype是个错误的指向,所以改变指向的原型
var ret = Constructor.apply(obj, arguments);//借用外部传入的构造器给obj设置属性
return typeof ret === 'object'? ret : obj;//兼容,因为某些浏览器没有暴露__proto__属性。
}
//测试
function Person(name){
this.name = name;
}
Person.prototype.getName = function(){
return this.name;
}
var a = objectFactory(Person,'seven');
console.log(a.name);//seven
console.log(a.getName());//seven
console.log(Object.getPrototypeOf( a ) === Person.prototype);//true
复制代码
以下代码运行结果
var myName1 = function(){
this.name = 'seven';
return {
name:'juejin'
}
}
var myName2 = function(){
this.name = 'seven';
return 'juejin'
}
var myName3 = function(){
return 'juejin'
}
var obj = new myName1();
console.log(obj.name);//juejin
var str = new myName2();
console.log(str.name);//seven
var und = new myName3();
console.log(und.name);//undefined
复制代码
题不难,关键在new需要注意的一个问题:
如果构造器显示的返回了一个object类型的对象,那么此次运算结果会最终返回这个对象,而不是我们之前期待的this
当引用一个变量的时候,引擎做了那些事情
例如:
var obj = { name: 'seven'};
var A = function(){};
A.prototype = obj();
var B = function(){};
B.prototype = new A();
var b = new B();
console.log(b.name);//seven
复制代码
- 首先引擎根据LHS查询去getter遍历
b
的所有属性,没有找到 - 查找
name
属性的请求被委托给了对象b
的构造器的原型,b.__proto__
被指向B.prototype
- 而
B.prototype
又被指向new A()
创建出来的对象,在该对象中仍然没有找到 - 然后再往上委托给
A.prototype
,而它被设置成obj
- 在
obj
中getter到name
属性,返回该值 - 题外:如果最后obj也没有找到name属性,请求将会传递给obj的构造器原型:Object.prototype,而null作为对象原型链的终点。显然没有,返回undefined
怎么手写实现 Object.create()方法
Object.create方法克隆一个对象,把__proto__
改变指向,prototype
被new
之后才会产生__proto__
所以得出方法:
function create(target){
var F = function(){};
F.prototype = target;
return new F()
}
var obj = {name : 'seven'};
var copy = create(obj);
console.log(copy.name); //seven
复制代码
手动实现bind
简化版
Function.prototype.myBind = function(context){
var _this = this;//保存指向
return function(){
return _this.apply(context,arguments);//改变this执行对象
}
}
复制代码
当然,为了两次参数结合,还需要复杂些
Function.prototype.myBind = function(){
var _this = this;
var context = [].shift.call(arguments);//获取目标对象
var args = [].slice.call(arguments);//获取剩余参数
return function(){
return _this.apply(context,[].concat.call(args,[].slice.call(arguments)));
//context改变指向,而后面是为了整合两次传入的参数
}
}
var obj = { name: 'seven' }
var fun = function(a,b,c){
console.log(this.name)
console.log(a,b,c)
}.bind(obj,1,2)
fun(3);
//seven
//1,2,3
复制代码
JavaScript中的原型、原型链是什么?
每一个函数都有prototype属性,它指向一个对象,即原型对象。其他对象可以通过它实现属性继承,而且任何一个对象都可以成为原型。
每个对象都有__proto__
属性,它是实现原型链的的关键,而prototype则是原型链的组成,任何被实例出的属性都会通过它去调用父类原型的方法,父类如果没有方法就会再去通过父类的父类的prototype
里找,直到Object.prototype
,null
作为对象原型链的终点,没有找到,返回undefined
。
function fn () {
this.name = 'seven'
}
fn.prototype.getName = function(){
console.log(this.name)
}
var fn2 = new fn();
fn2.getName();//'seven';
fn2.__proto__ === fn.prototype;// true
复制代码
作用域和执行上下文的区别
作用域是函数定义时确定的,函数内的变量根据变量去一层一层网上查找。 上下文是函数调用时确定的,主要是this的值,简单来说,谁调用它就指向谁。
为什么typeof null 是object
因为在二进制里,对象的前三位000,而null的二进制表示全0,自然前三位也是0,所以执行typeof时会返回“object”,这是ES5遗留的一个bug。
闭包是什么,作用和缺陷
由于垃圾回收机制,所有局部作用域的变量在退出函数后,这些局部变量没有标示失去引用,都将被自动回收销毁。
闭包能够访问其他函数内的变量,也可以封装变量,把一些不需要暴露在全局的变量封装成“私有变量”。
缺陷其实几乎没有,很多人以为会造成内存泄漏,那只是使用不当才会造成的。因为使用闭包的同时容易形成循环引用,如果闭包的作用域中保存着一些DOM节点,才有可能造成内存泄露,由于IE的BOM和DOM中的对象是使用C++以COM对象的方式实现的,而COM对象的垃圾回收机制采用的是引用计数策略,如果两个对象间造成了循环引用,那这两个对象都无法被回收。就得手动设置为null
,然后下次垃圾收集器会删除它们。
代码输出结果
function fn1(){
for(var i=0;i<4;i++){
var tc = setTimeout(function(i){
console.log(i)
clearTimeout(tc)
},10,i)
}
}
fn1();
复制代码
看到这题的时候,第一时间在想clearTimeout(tc)
是什么时候去执行的。如果没有这句的时候。输出肯定是0,1,2,3
,但是问题要从var tc
来看,tc
被重复赋值了,而clearTimeout
在异步函数里,只清除了申明的最后一次。 所以最后输出0,1,2
function fn2(){
for(var i=0;i<4;i++){
var tc = setInterval(function(i,tc){
console.log(i)
clearTimeout(tc)
},10,i,tc)
}
}
fn2()
复制代码
这个跟上一题也有相似之处,不过tc被传进去了,i=0
的时候,设置了定时器a
执行,当i=1
的时候定义了定时器b
,a
已经执行了一次,再所以会把上一次的值给清除掉,导致每次都执行了上一次的定时器才clearInterval
关闭掉,最后一次是因为i=4
的时候才能关掉i=3
的定时器,但是因为条件限制所以没关闭掉。
所以fn2会输出0,1,2,3(无限循环)......
科里化
有如下一个函数
var fn = function(a,b,c){
return a+b+c;
}
复制代码
拟写一个函数满足curry(fn)(1)(2)(3) 返回值为6;
function curry(fn) {
if(typeof fn !== 'function') throw new Error("首个参数必须为函数");
var len = fn.length;
var arg = [].prototype.slice.call(arguments, 1);//保存参数
return function() {
arg = arg.concat([].prototype.slice.call(arguments));//参数合并
if(arg.length < len) { //不满足a,b,c 3个形参的长度,继续调用
return arguments.callee;
}else{ //满足条件,执行语句
return fn.apply(null,arg);
}
}
}
curry(fn)(1)(2)(3);//6
复制代码
简单一点的例子 科里化函数的特点参数复用、提前返回、延迟计算/运行。需要理解闭包的概念
var adder = function () {
var _value = 0;
return function () {
if (arguments.length === 0) { // 假如没有参数返回,就返回值,终止循环调用
return _value
}
_value+=parseFloat([].slice.call(arguments))
return arguments.callee;// 继续调用
}
};
var arr = adder();
arr(1)(2)(3)(); //6
复制代码
HTML5的离线储存怎么使用,工作原理能不能介绍下
在html属性里加入
<html manifest = "cache.manifest">
复制代码
配置文件
CACHE MANIFEST //必须写
版本号
#v1.0.0
#需要缓存的文件
CACHE:
/reset.css
/app.js
#不需要缓存的文件
NETWORK:
/login.html
/data/*
#无法访问页面
FALLBACK:
/404.html
复制代码
基于新建的cache.manifest文件的缓存机制,通过这个文件上的解析清单离线存储资源。当网络在处于离线状态时浏览器自动取出离线存储的数据进行页面解析后展示。
如何判断一个对象是不是函数
var fn = function () {}
// typeof JQ源码
typeof fn === 'function' && typeof obj.nodeType !== "number";
// toString
Object.propotype.toString.call(fn) === "[object Function]"
// 通过原型
fn instanceof Function
// 通过构造函数
fn.constructor === Function
复制代码
列几条JavaScript的基本规范
- 使用全等===/全不等!==来比对数据类型
- 当命名对象、函数和实例时使用驼峰命名规则
- 不要使用全局变量申明,必须加var,避免污染全局命名空间
- 当命名对象、函数和实例时使用驼峰命名规则
还一堆....
pop()-push()-shift()-unshift()功能,forEach()-map()-reduce()有什么区别
pop(): 截取数组的最后一个元素,返回该值
push(): 给数组的最后添加一个元素,返回新长度
shift(): 截取数组的首个元素,返回
unshift():给数组的首个位置添加一个元素,返回新长度
reduce(): 不改变原数组,接受一个函数作为累加器,数组长度为0时不执行,适合计算大批数据
map(): 不影响原数组,返回新的数组
forEach(): 改变原数组做处理
复制代码
["1","2","3"].map(parseInt) 输出什么;
因为map会有2个参数,第一个是item对象,第二个是索引,而parseInt误将第二个参数认为是解析的基数,导致输出[1,NaN,Nan]
["1","2","3"].map(parseInt)
等同于
["1","2","3"].map(function(item,index){
return parseInt(item,index)
})
复制代码
浏览器和其他考点
一个页面,从输入URL到页面加载完成发生了什么(越详细越好)
我回答的不够仔细,以下是针对收集的资料整合之后的
- 输入URL,浏览器开启一个线程来处理这个请求,对URL进行协议判断,如果是http就按照web方式处理
- 在浏览器缓存、系统缓存、路由器缓存里查找,有就直接返回。
- 通过DNS解析拿到真实的IP地址。
- 向真实的IP地址服务器发起TCP连接,与浏览器建立tcp三次握手。
- 进行HTTP协议会话,浏览器发送报头(请求报头)
- 服务器处理请求将结果(资源文件)返回至浏览器
- 浏览器下载html文档,同时设置缓存;
- 浏览器启用HTML Parse解析器对 HTML 文件进行解析, 通过词法分析的过程,将内容分析成不同的token,然后根据HTML的文档,从上到下依次nextToken进行解析
token
,并获取下一个token
的位置,所以我们的DOM tree
是通过词法分析token
一步一步添加的。而类似于link
,script src
引用web资源的地址由浏览器发送请求css和js相关资源,将请求回来的js资源利用V8内核引擎执行js代码。请求回来的CSS资源则会生成相应的CSSOM
,前面的DOM tree
生成完毕后浏览器并不会直接渲染出来,而是会等待CSSOM(css tree)
生成后进行合并,再渲染出来。形成绘制树Render Tree
。 - 最后计算每个结点在页面中的位置,这一个过程称为
layout
其过程是在一个连续的二维平面上进行的,接下来将这些结果栅格化,映射到屏幕的离散二维平面上,这一过程称为 paint; 现代浏览器为提升性能,将页面划分多个layer
,各自进行paint
然后组合成一个页面呈现在用户眼前
线程和进程的区别
线程是最小的执行单元,进程是最小的资源管理单元。 一个线程只能属于一个进程,而一个进程可以有多个线程,至少有一个线程
请描述你对HTTP协议的理解
HTTP是一个属于面向应用层的超文本传输协议,基于TCP/IP通信协议来传递数据,制定服务器与客户端之间数据传输的通信规则。 HTTP有1.0和1.1、2.0版本,保持着无状态、无连接的协议。 HTTP请求由三部分组成:请求行、消息报头、请求正文。 HTTP响应也是由三个部分组成,分别是:状态行、消息报头、响应正文。 剩余的我也不知道该说什么了
请描述你对跨域的理解(同源限制)是什么,解决办法
浏览器的同源策略导致了跨域用于隔离潜在恶意文件的重要安全机制,限制必须协议、域名、端口一致
- 既然前端有限制,就把请求代理转移到后端。反向代理,需要服务端做大量修改,不建议;
- 由于浏览器不会阻止动态文本加载,所以JSONP方式应运而生
- CORS,虽然比代理简单一些,但是不会自动处理响应头(cookie等...),只需要服务端设置
Access-Control-Allow-Origin
和Access-Control-Allow-Methods
等属性进行设置。 - iframe 嵌套通讯,通过postMessage和message事件通信,用的较少
- WebSocket,也需要服务器做相关设置,像Node的
socket.io
请说明sessionStorage、localStorage、cookie的区别
- local:本地存储,窗口通用,生命周期是永久,除非用户手动清除。
- session:会话储存,限定本次窗口,关闭后消失。
- 本地存储Storage的大小限制5MB,不参与通信,遵守同源限制。
- cookie:每个domain最多只能有20条cookie,每个cookie长度不能超过4KB。否则会被截掉。每次请求都会自动带上。
什么是cookie隔离
cookie在访问对应域名下的资源时都会通过HTTP请求发送到服务器。但是访问静态资源根本不需要cookie,否则非常浪费流量,可以使用不同的domain(域名)来存储资源。因为cookie有跨域限制不能跨域提交请求,使用其他域名的请求头中就不会带有cookie数据,降低请求头的大小和请求时间,也减少了Web Server对cookie的处理分析环节,提高了webserver的http请求的解析速度,从而提高响应速度。
其他
问的最多的就是开发部署流程还有框架实现原理,笔者主Vue,建议去看vue源码实现,有render、update、patch、vnode等相关的解读,vue3.0明年就要见面了。开发部署流程每个公司有其自己的规范,这里就不提了。
一些细节的东西可能不懂或者有描述不对的地方,欢迎指正。