分享一个中型公司面试原题,卷起来~
The following article is from 前端早茶 Author 广东靓仔
本文适合最近在考虑新机会的的小伙伴阅读,简历指导、模拟面试可关注「前端之神」找我
一、前言
这套题目是某位群友亲身经历过的,感谢小伙伴share~
二、原题
讲讲事件循环
所谓单线程,无非就是同步队列和异步队列,js代码是自上向下执行的,在主线程中立即执行的就是同步任务,比如简单的逻辑操作及函数,而异步任务不会立马立马执行,会挪步放到到异步队列中,比如ajax、promise、事件、计时器等等。
也就是先执行同步,主线程结束后再按照异步的顺序再次执行。
事件循环意思就是:等待主线程中任务全部完成后,再回来把异步队列中任务放到主程序中运行,这样反复的循环
在事件循环中,每进行一次循环操作称为tick,tick 的任务处理模型是比较复杂的,里边有两个词:分别是 Macro Task (宏任务)和 Micro Task(微任务)。
事件委托
sass和less的区别
sass和less都是css的预编译处理语言,他们引入了mixins,参数,嵌套规则,运算,颜色,名字空间,作用域,JavaScript赋值等 加快了css开发效率,
sass的安装需要Ruby环境的,是在服务端上处理的,而Less是需要引入less.js来处理Less代码输出css到浏览器,也可以在开发环节使用Less,然后编译成css文件,直接放到项目中。
Less是@,而Scss是$
Sass提供4中输出选项:nested, compact, compressed 和 expanded.
scss引用的外部文件命名必须以开头, 文件名如果以下划线开头的话,Sass会认为该文件是一个引用文件,不会将其编译为css文件.
Sass有工具库Compass
Less有UI组件库Bootstrap
http的状态码、http缓存
状态码分为5类:
1** | 信息,服务器收到请求,需要请求者继续执行操作 |
2** | 成功,操作被成功接收并处理 |
3** | 重定向,需要进一步的操作以完成请求 |
4** | 客户端错误,请求包含语法错误或无法完成请求 |
5** | 服务器错误,服务器在处理请求的过程中发生了错误 |
301:
302:
304:
305:被请求的资源必须通过指定的代理才能被访问。
400:
403:服务器已经理解请求,但是拒绝执行它。
404:请求失败,请求所希望得到的资源未被在服务器上发现。
500:
服务器遇到了一个未曾预料的状况,导致了它无法完成对请求的处理。一般来说,这个问题都会在服务器端的源代码出现错误时出现。
http缓存请求相应头:
http缓存方案
不推荐缓存html文件,这样每次html加载渲染都可以感知文件变化,反正文件没变还是使用本地缓存,文件名都变了说明修改过,重新请求缓存就好了。
了解了上面不同浏览器行为对http缓存的不同影响,理解强缓存与协商性缓存就很容易了。
不发起http请求,直接使用本地缓存,比如浏览器地址栏回车,使用浏览器的刷新按钮,在Expires或max-age生效的情况下,触发的都是强缓存。
在使用本地缓存前,先与服务器协商,核对缓存文件是否为最新。比如设置了cache-control=no-cache,不管你做任何操作,都会发起请求,这一类就是协商性缓存了。
css盒模型
标准模型和IE模型
/* 标准模型 */
box-sizing:content-box;
/*IE模型*/
box-sizing:border-box;
BFC(block formatting context)块级格式化上下文
(1). 内部的box会在垂直方向上,一个接一个地放
(2). 每个元素的margin box的左边,与包含块border box的左边相接触(对于从做往右的格式化,否则相反)
(3). box垂直方向的距离由margin决定,属于同一个bfc的两个相邻box的margin会发生重叠
(4). bfc的区域不会与浮动区域的box重叠
(5). bfc是一个页面上的独立的容器,外面的元素不会影响bfc里的元素,反过来,里面的也不会影响外面的
(6). 计算bfc高度的时候,浮动元素也会参与计算
1. 脱离文档流(float不为none时)
2.position为absolute或fixed
3. display 为inline-block、table-cell、table-caption、flex、inline-flex。
4. overflow不为visible
5. 根元素。
1. 自适应两栏布局
2. 清除内部浮动
3. 防止垂直margin重叠
小程序分包机制
微信小程序采用的是类似离线包加载方案,用户第一次打开时会先下载好所有代码,然后再加载页面;当用户再次进入时,会直接使用已下载的代码,省去了代码下载的过程,打开速度更快。
第一次打开小程序时白屏时间很长,因为要先下载好所有的代码,代码越多,白屏时间越长
离线包和M页的一种结合机制,即把代码划分成主包+N个分包
目录
├── app.js
├── app.json
├── app.wxss
├── packageA
│ └── pages
│ ├── cat
│ └── dog
├── packageB
│ └── pages
│ ├── apple
│ └── banana
├── pages
│ ├── index
│ └── logs
└── utils
app.json
subpackages 字段声明项目分包结构:
{
"pages":[
"pages/index",
"pages/logs"
],
"subpackages": [
{
"root": "packageA",
"pages": [
"pages/cat",
"pages/dog"
]
}, {
"root": "packageB",
"name": "pack2",
"pages": [
"pages/apple",
"pages/banana"
]
}
]
}
subpackages 中,每个分包的配置有以下几项:
字段类型说明rootString分包根目录nameString分包别名,分包预下载时可以使用pagesStringArray分包页面路径,相对与分包根目录independentBoolean分包是否是独立分包
打包原则
app(主包)也可以有自己的 pages(即最外层的 pages 字段)
subpackage 的根目录不能是另外一个 subpackage 内的子目录
tabBar 页面必须在 app(主包)内
引用原则
packageA 无法 import packageB 的 template,但可以 require app、自己 package 内的 template
packageA 无法使用 packageB 的资源,但可以使用 app、自己 package 内的资源
js的基本数据类型和引用类型
说说js缓存
缓存的好处:
本地存储
sessionStorage
localStorage
cookie
cookie和storage的区别
jquery的ajax实现原理
1、ajax和jsonp这两种技术在调用方式上“看起来”很像,目的也一样,都是请求一个url,然后把服务器返回的数据进行处理,因此jquery和ext等框架都把jsonp作为ajax的一种形式进行了封装;
axios的实现原理
// 拦截器构造函数
function InterceptorManager() {
this.handlers = [];
}
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
this.handlers.push({
fulfilled: fulfilled,
rejected: rejected
});
return this.handlers.length - 1;
};
// 请求拦截和响应拦截均是拦截器的实例化
function Axios(instanceConfig) {
this.defaults = instanceConfig;
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}
// 用数组存储执行链
var chain = [dispatchRequest, undefined];
var promise = Promise.resolve(config);
// 将请求拦截成对推入执行链的头部
// 知道这个原理以后,我们就知道在设置多个请求拦截时,会按照设置时的顺序,倒序处理
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
chain.unshift(interceptor.fulfilled, interceptor.rejected);
});
// 将响应拦截成对推入执行链的尾部,执行时按照设置时的顺序,正序处理
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
chain.push(interceptor.fulfilled, interceptor.rejected);
});
// 依次成对执行,并用新的promise代替旧的promise,最后返回最新的promise
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}
return promise;
// 取消请求
function CancelToken(executor) {
if (typeof executor !== 'function') {
throw new TypeError('executor must be a function.');
}
// 在 CancelToken 上定义一个 pending 状态的 promise ,将 resolve 回调赋值给外部变量 resolvePromise
var resolvePromise;
this.promise = new Promise(function promiseExecutor(resolve) {
resolvePromise = resolve;
});
var token = this;
// 立即执行 传入的 executor函数,将真实的 cancel 方法通过参数传递出去。
// 一旦调用就执行 resolvePromise 即前面的 promise 的 resolve,就更改promise的状态为 resolve。
// 那么xhr中定义的 CancelToken.promise.then方法就会执行, 从而xhr内部会取消请求
executor(function cancel(message) {
// 判断请求是否已经取消过,避免多次执行
if (token.reason) {
return;
}
token.reason = new Cancel(message);
resolvePromise(token.reason);
});
}
CancelToken.source = function source() {
// source 方法就是返回了一个 CancelToken 实例,与直接使用 new CancelToken 是一样的操作
var cancel;
var token = new CancelToken(function executor(c) {
cancel = c;
});
// 返回创建的 CancelToken 实例以及取消方法
return {
token: token,
cancel: cancel
};
};
package.json版本命名的几种方式
~和^
npm install --save xxx, 会优先考虑使用 ^而不是~
以版本号x.y.z为例
x:主版本号, 当你做了不兼容的API修改
y:次版本号, 当你做了向下兼容的功能性问题
z:修订号, 当你做了向下兼容的问题修复
~x.y.z, 会更新到y最新的版本,
^x.y.z, 会更新到x的最新版本
Tips: 通常会使用package-lock.json来解决发布版本不一致问题
浏览器的重绘与回流
重绘不一定需要重排(比如颜色的改变)
重排(回流)必然导致重绘(比如改变网页位置)
重排(Reflow)(回流):
当渲染树的一部分必须更新并且节点的尺寸发生了变化,浏览器会使渲染树中受到影响的部分失效,并重新构造渲染树。
是在一个元素的外观被改变所触发的浏览器行为,浏览器会根据元素的新属性重新绘制,使元素呈现新的外观。比如改变某个元素的背景色、文字颜色、边框颜色等等
1. 将多次改变样式属性的操作,合成一次操作。
2. 将需要多次重排的元素,嗯,position属性。设为absolute。或fixed。使其脱离文档流,这样,他的变化就不会影响到其他的元素。
3. 在内存中多次操作节点。完成后再添加到文档中去。
4. 如果对一个元素进行复杂的操作,可以将display属性设为none嗯使其隐藏。操作完后再显示。
5. 在需要经常获取那些引起浏览器重排的属性值时,要缓存到变量。
用css隐藏dom的几种方式
方式一:display:none
.hide {
display:none;
}
方式二:visibility:hidden
.hidden{
visibility:hidden
}
.transparent {
opacity:0;
}
display: flex几个常用的属性
display:flex;/*flex块级,inline-flex:行内快*/
6个属性
flex-direction
justify-content:space-around;
/**
*center:水平居中,
*flex-start:靠左;
*flex-end:靠右;
*space-between:两边的向两边靠,中间等分;
*space-around:完美的平均分配
**/
align-items:
align-items:stretch;
/**
*center:垂直居中、
*flex-start:至顶、
*flex-end:至底、
*space-between、
*space-around
**/
flex-direction:
flex-direction: row;
/**
*column从上向下的排列,
*column-reverse、
*row:从左到右,
*row-reverse:从右向左
**/
flex-wrap:
flex-wrap:wrap;
/**
*wrap多行显示(父容器不够显示的时候,从上到下)、
*nowrap(当容器不够宽的时候,子元素会平分父容器的宽或者高)、
*wrap-reverse:从下向上
**/
/*flex-flow是flex-direction、flex-wrap的缩写*/
vue父子组件的生命周期过程
加载渲染过程:父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted。
子组件更新过程:父beforeUpdate->子beforeUpdate->子updated->父updated
父组件更新过程:父beforeUpdate->父updated
销毁过程:父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
vue的双向绑定
vue2.x版本的数据双向绑定主要通过Object.defineProperty()方法来进行数据劫持以及发布者-订阅模式来实现的。
1.实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。
2.实现一个订阅者Watcher,可以收到属性的变化通知并执行相应的函数,从而更新视图。
3.实现一个解析器Compile,可以扫描和解析每个节点的相关指令,并根据初始化模板数据以及初始化相应的订阅器。
vue3.x采用数据劫持结合发布者-订阅者模式的方式,通过new Proxy()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
小结:Vue 3.0与Vue 2.0的区别仅是数据劫持的方式由Object.defineProperty更改为Proxy代理,其他代码不变。
虚拟dom性能好在哪里
缺点:
父子组件的通信
说说vue虚拟dom的算法
核心:
渲染函数:渲染函数是用来生成Virtual DOM的。Vue推荐使用模板来构建我们的应用界面,在底层实现中Vue会将模板编译成渲染函数,当然我们也可以不写模板,直接写渲染函数,以获得更好的控制。
VNode 虚拟节点:它可以代表一个真实的 dom 节点。通过 createElement 方法能将 VNode 渲染成 dom 节点。简单地说,vnode可以理解成节点描述对象,它描述了应该怎样去创建真实的DOM节点。
patch(也叫做patching算法):虚拟DOM最核心的部分,它可以将vnode渲染成真实的DOM,这个过程是对比新旧虚拟节点之间有哪些不同,然后根据对比结果找出需要更新的的节点进行更新。这点我们从单词含义就可以看出, patch本身就有补丁、修补的意思,其实际作用是在现有DOM上进行修改来实现更新视图的目的。Vue的Virtual DOM Patching算法是基于Snabbdom的实现,并在些基础上作了很多的调整和改进。
diff 算法包括几个步骤:
虚拟节点有哪些属性
children: 值可能是undefined(是undefined表示没有子元素),也可能使数组
data: {}
elm: undefined。elm是undefined说明这个节点还没有在DOM树上
key: undefined
sel: “div”。选择器
text:文字描述
patch函数
import {
init,
classModule,
propsModule,
styleModule,
eventListenersModule,
h,
} from "snabbdom";
// 创建patch函数
let patch = init([classModule, propsModule, styleModule, eventListenersModule]);
// 创建一个虚拟节点,但是它还没有在DOM树上。要想把它放在DOM树上,需要patch函数
let vnode1 = h('a', {props: {href: 'http://www.baidu.com',target:'_blank'}}, '百度一下')
let container = document.getElementById('container');
// 让虚拟结点上树,patch函数只能让一个虚拟结点上树。如果vnode2和vnode3要上树,需要把这个注释掉
patch(container, vnode1);
let vnode2 = h('div',{class:{"box":true}},'我是一个盒子');
let vnode3 = h('ul',{},[
h('li','苹果'), // 第二个参数可以没有
h('li','香蕉'), // 这里已经调用了h函数
h('li',[
h('p',{},'桔子'),
h('p',{},'哈哈')
]),
h('li','西瓜'),
h('li',h('span','火龙果')) // 如果children只有一个子元素,第三个参数可以不要数组
]);
vue-router两种模式的区别
hash模式
原理
window.onhashchange = function(event){
console.log(event.oldURL, event.newURL);
let hash = location.hash.slice(1);
document.body.style.color = hash;
}
history模式
原理
window.history.pushState(stateObject, title, URL)
window.history.replaceState(stateObject, title, URL)
pushState() 和 replaceState()。通过这两个 API
$router与$route的区别
vue-router的history模式在后端需要做什么配置
因为我们的应用是个单页客户端应用,所以呢,我们要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html
页面,这个页面就是我们app 依赖的页面。
nginx
location / {
try_files $uri $uri/ /index.html;
}
Apache
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]
</IfModule>
原生 Node.js
const http = require('http')
const fs = require('fs')
const httpPort = 80
http.createServer((req, res) => {
fs.readFile('index.htm', 'utf-8', (err, content) => {
if (err) {
console.log('We cannot open "index.htm" file.')
}
res.writeHead(200, {
'Content-Type': 'text/html; charset=utf-8'
})
res.end(content)
})
}).listen(httpPort, () => {
console.log('Server listening on: http://localhost:%s', httpPort)
})
三、最后
关注我,一起携手进阶