面试题总结
开篇小啰嗦
2019年是互联网最难受的一年。但是同时也是最好的一年。但是市场无论怎么变,自己如果能时刻保持学习,便可以以不变应万变。下面整理一下最新鲜的面试题。你肯定看上去就很熟悉。但是又觉得没那么熟悉,下面就一起复盘一下。(大神略过)
答案部分并不是全部详细的回答,毕竟面试的时候不可能给你太长时间去细讲,主要是写一下核心的死里以及主要代码。如果要想仔细了解,查一下资料,每个答案都有很多的详细答案。
下面的题目是各种公众号,朋友面试,整理出来的问题,答案是自己整理的,难免有纰漏。望指正
CSS类
1.盒模型
在一个文档中,每个元素都被表示为一个矩形的盒子,不同的浏览器显示不同的盒子模型,
盒模型是有两种标准的,
一个是标准模型
标准和模型中,宽高就是你自己的内容宽高
标准盒模型中,总width = 你设置的width+2margin+2padding+2border
一个块的总宽度= width + margin(左右) + padding(左右) + border(左右)
一个是IE模型(怪异盒模型)。
IE模型中盒模型的宽高是内容(content)+填充(padding)+边框(border)的总宽高。
在IE的盒子模型中,width表示content+padding+border这三部分的宽度。
一个块的总宽度= width + margin(左右)(即width已经包含了padding和border值)
两个盒子展示图
css如何设置两种模型
这里用到了CSS3 的属性 box-sizing
/* 标准模型 */
box-sizing:content-box;
/*IE模型*/
box-sizing:border-box;
比如说两个盒子并列排布,宽各为50%,但是此时有个新需求,盒子加上边框,用标准的你就会发现问题,会掉下来。如果用怪异的。因为border是计算在width里面的,不会撑开,完美解决。
2.不设宽高水平垂直居中方法
第一种:transform:translate(-50%,-50%)
.parent{
position:relative
}
.son{
position:absolute;
top:50%;
left:50%;
transform:translate(-50%,-50%);
}
第二种:弹性盒模型
.parent{
display:flex;
justify-content:center;定义项目在主轴(水平方向)上居中对齐
align-items:center;定义项目在交叉轴(垂直方向)上居中对齐
}
.son{
里面随便写。宽高爱写不写。都是居中的
}
3.三栏布局
三栏布局,顾名思义就是两边固定,中间自适应。三栏布局在实际的开发十分常见,比如淘宝网的首页,就是个典型的三栏布局:即左边商品导航和右边导航固定宽度,中间的主要内容随浏览器宽度自适应。
第一种: 浮动布局
.left{
float: left;
width: 300px;
height: 100px;
background: #631D9F;
}
.right{
float: right;
width: 300px;
height: 100px;
background: red;
}
现在两侧的样式写好了,那么我们来写中间的样式,
.center{
margin-left: 300px;
margin-right: 300px;
background-color: #4990E2;
}
清除一下浮动
.main::after{
content:'';
display: block;
clear: both;
}
第二种:Position布局
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>position</title>
<style type="text/css">
*{
margin: 0;
padding: 0;
}
</head>
<body>
<article class="main">
<div class="left">左</div>
<div class="center">中
<h2>绝对定位</h2>
</div>
<div class="right">右</div>
</article>
</body>
</html>
<script>
.left{
position: absolute;
left: 0;
width: 300px;
background-color: red;
}
.center{
position: absolute;
left: 300px;
right: 300px;
background-color: blue;
}
.right{
position: absolute;
right: 0;
width: 300px;
background-color: #3A2CAC;
}
</script>
第三种: flex弹性盒布局
.main {
display: flex;
justify-content:center;
align-items:center;
}
.left{
width: 400px;
background-color: red;
}
.center{
flex:1
background-color: blue;
word-break: break-word;
}
.right{
background-color: red;
width: 400px;
}
4.选择器权重计算方式
| 选择器 | 栗子 |
|---|---|
| ID | #id |
| class | .class |
| 标签 | p |
| 属性 | [type='text'] |
| 伪类 | :hover |
| 伪元素 | ::first-line |
| 相邻选择器、子代选择器 | > + |
- 内联样式,如: style="...",权值为1000。
- ID选择器,如:#content,权值为0100。
- 类,伪类、属性选择器,如.content,权值为0010。
- 类型选择器、伪元素选择器,如div p,权值为0001。
- 通配符、子选择器、相邻选择器等。如* > +,权值为0000。
- 继承的样式没有权值
5.清除浮动的方法
浮动产生的原因:
当一个内层元素是浮动的时候,如果没有关闭浮动,其父元素也就不会再包含这个浮动的内层元素,因为此时浮动元素已经脱离了文档流。也就是为什么外层不能被撑开了
一、:after的3行代码
原理:利用:after和:before在元素内插入两个元素块(其实就是在节点本身构造出两个存在于Render Tree的节点),从而达到清除浮动的效果。
.box:after{
clear: both;
content: '';
display: block;
}
二、添加新的节点,使用clear:both方法
原理:在父节点中插入元素块,清除浮动
<div class="box">
<div class="d1">1</div>
<div class="d2">2</div>
<div class="clc"></div>
</div>
<style>
.clc{
clear: both;
}
</style>
三、在父节点上设置一个新类名,然后在类名css中设置
原理:这样也可以清除浮动,其原理是使用overflow属性来清除浮动,overflow有三个值:auto| hidden | visible,
我们可以使用 hidden和 auto来清除浮动,
但绝不可以使用 visible 清除浮动,使用这个值达不到效果
<div class="box over-flow">
<div class="d1">1</div>
<div class="d2">2</div>
</div>
<style>
.over-flow{
overflow: auto;
}
</style>
6.flex
flex是css3的弹性盒模型。在解决一个布局的时候,非常方便,
flex弹性盒主要是要区分什么是偶写在父元素,什么时候写在子元素。
分不清,父子元素写这些属性,就会很难用。或各种不生效
这一块就是各种属性多用。都尝试一遍,就知道属性怎么使用了。
7.什么是BFC、可以解决哪些问题
BFC 全称为 块格式化上下文 (Block Formatting Context) 。
????
没有深入研究的话谁一看都一脸懵逼,啥意思。
MDN讲那么多定义,最后还是不知道干嘛的
仅仅知道它是一种功能,针对于 HTML文档 起作用。
既然这样,就直接上例子。少说定义,就知道干嘛的了
-
形成 BFC 的条件
1、浮动元素,float 除 none 以外的值;
2、绝对定位元素,position(absolute,fixed);
3、display 为以下其中之一的值 inline-blocks,table-cells,table-captions;
4、overflow 除了 visible 以外的值(hidden,auto,scroll)。
-
bfc的特性(功能)
(1).使 BFC 内部浮动元素不会到处乱跑;
父元素包含子元素,子元素如果浮动了,就脱离了文档流, 那么父元素就会高度塌陷。无法包含子元素,这时候把父元素形成一个块级别格式化一下,就可以了。父元素 ovelflow:hidden 就是这个原理
(2).和浮动元素产生边界
一半情况下,两个元素,其中一个元素浮动。另一个元素,想要跟浮动元素,产生边距,需要设置margin-left:浮动元素宽度+margin值 但是把非浮动元素块级格式化一下,就不用这么麻烦了
(3)上代码
/*如果没有块级格式化产生边距20px*/
<body>
<div class="e"></div>
<div class="m"></div>
</body>
<style>
.e{
border:1px solid #0f0;
width:100px;
height:100px;
float:left;
}
.m{
border:1px solid #f00;
width:100px;
height:100px;
margin-left:120px; 这块要设置成120px才能形成
}
</style>
/*如果有块级格式化产生边距20px*/
<body>
<div class="e"></div>
<div class="m"></div>
</body>
<style>
.e{
border:1px solid #0f0;
width:100px;
height:100px;
margin-right:20px; 自己设置边距就可以
float:left;
}
.m{
border:1px solid #f00;
width:100px;
height:100px;
overflow:hidden; 把自己变成块级格式化元素
}
</style>
8.position属性
absolute: 生成绝对定位的元素,相对于 static 定位以外的第一个父元素进行定位。
元素的位置通过 "left", "top", "right" 以及 "bottom" 属性进行规定。
fixed:生成绝对定位的元素,相对于浏览器窗口进行定位。
元素的位置通过 "left", "top", "right" 以及 "bottom" 属性进行规定。
relative:生成相对定位的元素,相对于其正常位置进行定位。
因此,"left:20" 会向元素的 LEFT 位置添加 20 像素。
static:默认值。没有定位,元素出现在正常的流中(忽略 top, bottom, left, right 或者 z-index 声明)。
手写题
手写题是每个公司都会有考试。,范围也比较固定,如果能大概准备一下,八九不离十就这几道题,当然基础选择题就忽略了
防抖和节流
防抖是将多次执行变为最后一次执行,节流是将多次执行变为在规定时间内只执行一次 我们在操作的js的时候,有很多根据窗口尺寸,或者滚动加载事件来执行方法的。但是这种操作会及其频繁的占用浏览器性能
所以我们设置一下,避免频繁调用方法,增加浏览器的负担
(1)防抖
防抖是在单位时间内。只要触发事件,调用方法的时间就会重新计算延迟。只有单位时间不再操作。才会调用一次方法。
比如 持续触发scroll事件时,并不执行handle函数,当1000毫秒内没有触发scroll事件时,才会延时触发scroll事件。
简单的防抖函数实现
function debounce(fn, delay) {
let timer
return function() {
let context = this
let args = arguments
// 防抖事件,只要定时器存在。还一直频繁操作事件,我就一直给你清空,直到你停止
if (timer !== null) {
clearTimeout(timer)
timer = setTimeout(()=> {
fn.apply(context, args)
},delay)
}
}
}
function handle() {
console.log(90909201902190)
}
window.addEventListener('scroll', debounce(handle, 1000))
应用场景: 两个条件:
1,如果客户连续的操作会导致频繁的事件回调(可能引起页面卡顿).
2,客户只关心"最后一次"操作(也可以理解为停止连续操作后)所返回的结果.
例如:
输入搜索联想,用户在不断输入值时,用防抖来节约请求资源。
按钮点击:收藏,点赞,心标等
(2) 节流
当用户操作持续出发事件的时候,保证一定时间内,只执行一次方法
// 定时器版本的节流
function throttle(fn, delay) {
let timer
return function () {
let args = arguments
let context = this
// 如果频繁操作事件,定时器还在,什么都不执行,如果定时器不在了,设置一个定时器。
if(!timer) {
timer = setTimeout(() => {
fn.apply(context,args)
timer = null
},delay)
}
}
}
(3) 总结
函数防抖:将几次操作合并为一此操作进行。原理是维护一个计时器,规定在delay时间后触发函数,但是在delay时间内再次触发的话,就会取消之前的计时器而重新设置。这样一来,只有最后一次操作能被触发。
函数节流:使得一定时间内只触发一次函数。原理是通过判断是否到达一定时间来触发函数。
区别: 函数节流不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数,而函数防抖只是在最后一次事件后才触发一次函数。 比如在页面的无限加载场景下,我们需要用户在滚动页面时,每隔一段时间发一次 Ajax 请求,而不是在用户停下滚动页面操作时才去请求数据。这样的场景,就适合用节流技术来实现。
数组去重、数组扁平化
一、ES6 set去重
function unique (arr) {
return Array.from(new Set(arr))
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
//[1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {}, {}]
二、利用indexOf去重
function unique(arr) {
var array = [];
for (var i = 0; i < arr.length; i++) {
if (array .indexOf(arr[i]) === -1) {
array .push(arr[i])
}
}
return array;
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
三、利用filter
function unique(arr) {
return arr.filter(function(item, index, arr) {
//indexOf总是返回第一个元素的位置,后续的重复元素位置与indexOf返回的位置不相等,因此被filter滤掉了。
return arr.indexOf(item, 0) === index;
});
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
//[1, "true", true, 15, false, undefined, null, "NaN", 0, "a", {…}, {…}]
手写call、apply、bind
一、手写实现call
/*强调一下,一般来说,对象调用了那个方法,这个方法的this就是指向这个对象的*/
/* 其实call方法就是在方法(Function)的原型上添加了一个方法(call)。
这个添加的方法(call)。有个参数(随便都行)就是你要指向的那个对象(foo1)。
方法里面的this(bar)就是你要改变指向的方法。原理就是:
在这个call方法里面把要改变方向的方法(bar)复制一份给被指向的对象(foo1),
然后被指向的对象调用这个方法。这不就是把bar方法的this指向foo1对象了啊
*/
// 手动实现一个call
Function.prototype.call2 = function(context = window) {
context.fn = this
// 打印一下this,就是 方法bar
// context是传入的对象,this是指需要改变指向的那个函数,在目标对象上复制一个需要改变指向的方法
let args = [...arguments].slice(1) // //解析参数,第一个参数不要。因为是指向的对象。从第二个参数开始才是需要的
let result = context.fn(...args)// //目标对象上新复制的函数,执行一下,记得把处理的参数放进去。
delete context.fn // // //执行完。把添加的方法删了。要不然平白无故就会多出一个函数。上面已经改变
return result // 把结果返回啊
}
var foo1 = {
value:5
}
function bar(name,age) {
console.log(name)
console.log(age)
console.log(this.value)
}
bar.call2(foo1,'back',12)
一、手写实现apply
/*apply和call的唯一区别就是传的参数不同。call是一个个传进去,apply是传一个数组进去。所以两个的原理写法是差不多的。区别就是处理参数的时候不同*/
Function.prototype.apply1 = function(context=window) {
context.fn = this
let result
// 如果第二个传参了,apply 要求第二个参数是数组。就把数组一个个拓展开。
if (arguments[1]) {
result = context.fn(...arguments[1])
} else {
result = context.fn()
}
delete context.fn
return result
}
const bar = {
m:1,
c:2,
}
function test() {
console.log(this.m)
}
test.apply1(bar)
三、手写实现bind
bind和apply的区别在于,bind是返回一个绑定好的函数,apply是直接调用.
就是返回一个函数,里面执行了apply上述的操作而已.
不过有一个需要判断的点,因为返回新的函数,要考虑到使用new去调用,
并且new的优先级比较高,所以需要判断new的调用,
/*
1.绑定this指向
2.返回一个绑定后的函数(高阶函数原理)
3.如果绑定的函数被new执行 ,当前函数的this就是当前的实例
new出来的结果可以找到原有类的原型
*/
Function.prototype.myBind = function (context, ...args) {
// 传件来的需要改变指向的函数
const fn = this
// 传的参数是单个还是数组
args = args ? args : []
// 返回一个新的函数
return function newFn(...newFnArgs) {
// 传件来的函数是是当前返回函数的实例。并且new出来的也要可以传参
if (this instanceof newFn) {
return new fn(...args, ...newFnArgs)
}
return fn.apply(context, [...args,...newFnArgs])
}
}
sleep函数
实现promise
eventEmitter(emit,on,off,once)
实现new
先说说new的作用
new可以使得构造函数产生一个实例对象,并且实例可以继承构造函数的属性和方法
同时,new出来的实例,也可以访问到构造函数定义在原型上的方法和属性
比如:
function Test(name) {
this.name = name
}
Test.prototype.sayName = function () {
console.log(this.name)
}
const t = new Test('yck')
console.log(t.name) // 'yck'
t.sayName() // 'yck'
构造函数尽量不要返回值。因为返回原始值不会生效,返回对象会导致 new 操作符没有作用。 如下:
function Test(name) {
this.name = name
console.log(this) // Test { name: 'yck' }
return { age: 26 }
}
const t = new Test('yck')
console.log(t) // { age: 26 }
console.log(t.name) // 'undefined'
实现一下new
先看一下new的几个作用
-
new 操作符会返回一个对象,所以我们需要在内部创建一个对象
-
这个对象,可以访问挂载在构造函数上的属性和方法
-
这个对象可以访问到构造函数原型上的属性,所以需要将对象与构造函数链接起来
-
返回原始值需要忽略,返回对象需要正常处理
function NewCopy(fn,...args) {
// 创建一个对象
let obj = {}
//因为 obj 对象需要访问到构造函数原型链上的属性,所以我们通过 setPrototypeOf 将两者联系起来。这段代码等同于 obj.__proto__ = Con.prototype
Object.setPrototypeOf(obj, fn.prototype)
// 将 obj 绑定到构造函数上,并且传入剩余的参数
let result = fn.apply(obj, args)
// 判断构造函数返回值是否为对象,如果为对象就使用构造函数返回的值,否则使用 obj
return result instanceof Object ? result : obj
}
实验一波
function Test(name, age) {
this.name = name
this.age = age
}
Test.prototype.sayName = function () {
console.log(this.name)
}
const a = create(Test, 'yck', 26)
console.log(a.name) // 'yck'
console.log(a.age) // 26
a.sayName() // 'yck'
归根结底new其实就是做了这个四部操作
(1) 创建一个新对象;
(2) 将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象) ;
(3) 执行构造函数中的代码(为这个新对象添加属性) ;
(4) 返回新对象。
懒加载的原理,大概实现思路
图片懒加载技术主要通过监听图片资源容器是否出现在视口区域内,来决定图片资源是否被加载。 那么实现图片懒加载技术的核心就是如何判断元素处于视口区域之内。
以前
以前的懒加载核心技术原理就是通过js提供的elemenr.getBoundingClientRect()
- 给图片容器自定义属性,不赋值真实图片连接,
- 利用scroll事件, Element.getBoundingClientRect() 方法判断目标容器是否给跟可视区域交叉了
- 判断交叉了 就 把真实链接赋值给图片容器,显示内容
注意使用的时候 scroll事件记得使用节流,避免一直触发操作事件,影响性能
现在
Web为开发者提供了 IntersectionObserver 接口它 可以异步监听目标元素与其祖先或视窗的交叉状态, 注意这个接口是异步的,它不随着目标元素的滚动同步触发,所以它并不会影响页面的滚动性能。
var io = new IntersectionObserver(callback, options)
// 用来指定交叉比例,决定什么时候触发回调函数,是一个数组,默认是[0]。
// 我们指定了交叉比例为0,0.5,1,当观察元素img0%、50%、100%时候就会触发回调函数
const options = {
root: null,
threshold: [0, 0.5, 1]
}
var io = new IntersectionObserver(callback, options)
io.observe(document.querySelector('img'))
实现demo
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div class="e"></div>
<div id="m">ssss</div>
<div class="e"></div>
<div class="m"></div>
<div class="e"></div>
<div class="m"></div>
<div class="e"></div>
<div class="m"></div>
<div class="e"></div>
<div class="m"></div>
<div class="e"></div>
<div class="m"></div>
<div class="e"></div>
<div class="m"></div>
<img src="https://img.alicdn.com/tps/i3/T1QYOyXqRaXXaY1rfd-32-32.gif" alt="" class="img" data-src="https://gtd.alicdn.com/sns_logo/i6/TB1fVJJQFXXXXcyXpXXSutbFXXX.jpg_240x240xz.jpg">
<img src="https://img.alicdn.com/tps/i3/T1QYOyXqRaXXaY1rfd-32-32.gif" alt="" class="img" data-src="https://gtd.alicdn.com/sns_logo/i6/TB1fVJJQFXXXXcyXpXXSutbFXXX.jpg_240x240xz.jpg">
<img src="https://img.alicdn.com/tps/i3/T1QYOyXqRaXXaY1rfd-32-32.gif" alt="" class="img" data-src="https://gtd.alicdn.com/sns_logo/i6/TB1fVJJQFXXXXcyXpXXSutbFXXX.jpg_240x240xz.jpg">
<img src="https://img.alicdn.com/tps/i3/T1QYOyXqRaXXaY1rfd-32-32.gif" alt="" class="img" data-src="https://gtd.alicdn.com/sns_logo/i6/TB1fVJJQFXXXXcyXpXXSutbFXXX.jpg_240x240xz.jpg">
<img src="https://img.alicdn.com/tps/i3/T1QYOyXqRaXXaY1rfd-32-32.gif" alt="" class="img" data-src="https://gtd.alicdn.com/sns_logo/i6/TB1fVJJQFXXXXcyXpXXSutbFXXX.jpg_240x240xz.jpg">
<img src="https://img.alicdn.com/tps/i3/T1QYOyXqRaXXaY1rfd-32-32.gif" alt="" class="img" data-src="https://gtd.alicdn.com/sns_logo/i6/TB1fVJJQFXXXXcyXpXXSutbFXXX.jpg_240x240xz.jpg">
<img src="https://img.alicdn.com/tps/i3/T1QYOyXqRaXXaY1rfd-32-32.gif" alt="" class="img" data-src="https://gtd.alicdn.com/sns_logo/i6/TB1fVJJQFXXXXcyXpXXSutbFXXX.jpg_240x240xz.jpg">
<img src="https://img.alicdn.com/tps/i3/T1QYOyXqRaXXaY1rfd-32-32.gif" alt="" class="img" data-src="https://gtd.alicdn.com/sns_logo/i6/TB1fVJJQFXXXXcyXpXXSutbFXXX.jpg_240x240xz.jpg">
<img src="https://img.alicdn.com/tps/i3/T1QYOyXqRaXXaY1rfd-32-32.gif" alt="" class="img" data-src="https://gtd.alicdn.com/sns_logo/i6/TB1fVJJQFXXXXcyXpXXSutbFXXX.jpg_240x240xz.jpg">
<img src="https://img.alicdn.com/tps/i3/T1QYOyXqRaXXaY1rfd-32-32.gif" alt="" class="img" data-src="https://gtd.alicdn.com/sns_logo/i6/TB1fVJJQFXXXXcyXpXXSutbFXXX.jpg_240x240xz.jpg">
<img src="https://img.alicdn.com/tps/i3/T1QYOyXqRaXXaY1rfd-32-32.gif" alt="" class="img" data-src="https://gtd.alicdn.com/sns_logo/i6/TB1fVJJQFXXXXcyXpXXSutbFXXX.jpg_240x240xz.jpg">
<img src="https://img.alicdn.com/tps/i3/T1QYOyXqRaXXaY1rfd-32-32.gif" alt="" class="img" data-src="https://gtd.alicdn.com/sns_logo/i6/TB1fVJJQFXXXXcyXpXXSutbFXXX.jpg_240x240xz.jpg">
</body>
</html>
<style>
.img{
display: block;
width: 200px;
height:300px;
border:1px solid #00f;
}
</style>
<script>
const io = new IntersectionObserver(callback)
let imgs = document.querySelectorAll('[data-src]') // 将图片的真实url设置为data-src src属性为占位图 元素可见时候替换src
console.log(imgs)
function callback(entries){
entries.forEach((item) => { // 遍历entries数组
console.log(item)
if(item.isIntersecting){ // 当前元素可见
item.target.src = item.target.dataset.src // 替换src
io.unobserve(item.target) // 停止观察当前元素 避免不可见时候再次调用callback函数
}
})
}
imgs.forEach((item)=>{ // io.observe接受一个DOM元素,添加多个监听 使用forEach
io.observe(item)
})
</script>
函数currying
一、柯里化的概念
在数学和计算机科学中,柯里化是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。
举例来说,一个接收3个参数的普通函数,在进行柯里化后, 柯里化版本的函数接收一个参数并返回接收下一个参数的函数, 该函数返回一个接收第三个参数的函数。 最后一个函数在接收第三个参数后, 将之前接收到的三个参数应用于原普通函数中,并返回最终结果。
// 举个例子
// 数学和计算科学中的柯里化:
//一个接收三个参数的普通函数
function sum(a,b,c) {
console.log(a+b+c)
}
//用于将普通函数转化为柯里化版本的工具函数
function curry(fn) {
//...内部实现省略,返回一个新函数
}
//获取一个柯里化后的函数
let _sum = curry(sum);
//返回一个接收第二个参数的函数
let A = _sum(1);
//返回一个接收第三个参数的函数
let B = A(2);
//接收到最后一个参数,将之前所有的参数应用到原函数中,并运行
B(3) // print : 6
这里大概就是讲一下什么是函数柯里化的概念,下面大概讲一下柯里化的应用
二、柯里化的用途
柯里化本质上是降低通用性,提高适用性。来看一个例子:
我们开发工作中会遇到很多的校验工作,封装一个函数,传入正则验证参数,传入待验证参数进行验证,
function checkByRegExp(regExp,string) {
return regExp.test(string);
}
checkByRegExp(/^1\d{10}$/, '18642838455'); // 校验电话号码
checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, 'test@163.com'); // 校验邮箱
如果验证少没问题,如果很多的话,就不行了,很繁琐。向下面这样
checkByRegExp(/^1\d{10}$/, '18642838455'); // 校验电话号码
checkByRegExp(/^1\d{10}$/, '13109840560'); // 校验电话号码
checkByRegExp(/^1\d{10}$/, '13204061212'); // 校验电话号码
checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, 'test@163.com'); // 校验邮箱
checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, 'test@qq.com'); // 校验邮箱
checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, 'test@gmail.com'); //
极度繁琐,第一个验证参数,很长,很多,很不优雅
我们常用的工具库loadsh就有这个功能,如果使用柯里化以后
//进行柯里化
let _check = curry(checkByRegExp);
//生成工具函数,验证电话号码
let checkCellPhone = _check(/^1\d{10}$/);
//生成工具函数,验证邮箱
let checkEmail = _check(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/);
checkCellPhone('18642838455'); // 校验电话号码
checkCellPhone('13109840560'); // 校验电话号码
checkCellPhone('13204061212'); // 校验电话号码
checkEmail('test@163.com'); // 校验邮箱
checkEmail('test@qq.com'); // 校验邮箱
checkEmail('test@gmail.com'); // 校验邮箱
就会很简洁优雅,
其实就是可以理解为:参数复用
ES6
es6 不用说了。面试必问。必问,而且你回答一下还不行,还会针对你的问题进行深层次问,大都会给一个业务场景,让你说。但是只要基础说好了。业务按着功能讲一下思路就好了
let、const、var区别
var 声明的变量可以挂载在window上,const let不行
var存在变量提升,const let没有
let const 存在块级作用域,var不存在
同一作用域下let和const不能声明同名变量,而var可以
const 声明的变量必须有值,不能为空,let可以
let,const 存在暂时性死区
箭头函数与普通函数的区别
箭头函数已经是老生长谈了,所谓的箭头函数,写法很多种,在这里不做详细解释了,
- 箭头函数是匿名函数,不能作为构造函数,不能使用new
- 箭头函数不绑定arguments,取而代之用rest参数...解决
let B = (b)=>{
console.log(arguments);
}
B(2,92,32,32); // Uncaught ReferenceError: arguments is not defined
let C = (...c) => {
console.log(c);
}
C(3,82,32,11323); // [3, 82, 32, 11323]
- 箭头函数不绑定this,会捕获其所在的上下文的this值,作为自己的this值
- 箭头函数没有原型属性
var a = ()=>{
return 1;
}
function b(){
return 2;
}
console.log(a.prototype); // undefined
console.log(b.prototype); // {constructor: ƒ}
5.总结一下,箭头函数,任何方法没有办法修改指向的
promise、async await、Generator的区别
我们知道JavaScript是单线程语言,如果没有异步编程非得卡死。 这仨个具体的详细用法最好百度查找相关资料。这里对其详细用法不做阐述。 因为每一个的用法,一篇长文章都不一定可以解释完。。。。
异步编程以前通用的是回调函数来解决,少量回调还好,如果很多层的话,解读要疯掉,很不直观,
promise
所以promise可以解决回调的地狱,将原来的用 回调函数 的 异步编程 方法转成用relsove和reject触发事件, 用 then 和 catch 捕获成功或者失败的状态执行相应代码的异步编程的方法
Generator
Generator是ES6的实现,最大的特点就是可以交出函数的执行权, 普通函数的写法,但是不会返回执行结果,需要手动执行next来执行结果,这样如果很多的话,你手动也不太能分清楚谁先执行,谁后执行
async await
这个方案就很厉害了,是Generator的语法糖,整合了里面的功能,不需要手动执行阻塞通行,执行执行完方法,自动执行接下来的方法,而且只需要一行代码。
在 await 的部分等待返回, 返回后自动执行下一步。而且相较于Promise,async 的优越性就是把每次异步返回的结果从 then 中拿到最外层的方法中,不需要链式调用,只要用同步的写法就可以了。
ES6的继承与ES5相比有什么不同
继承的目的是方法或者属性公用,不要重复创建使用,
核心是ES5是通过原型或者构造函数进行继承的,ES6是通过class关键字进行继承的
ES5继承
es5继承有很多方式,最常用的就是原型继承
function Parent(name) {
this.name = name;
this.n = 2222;
this.color = ["red", "blue"];
}
Parent.prototype.sayName = function() {
console.log('父类定义的一堆逻辑')
}
function child(name) {
console.log('子类方法')
}
child.prototype = new Parent(); // 核心地方
var s1 = new child();
s1.sayName(); //也可以调用父函数的方法
console.log(s1.color); //也可以调用父函数的属性 ["red","color"]
console.log(s1.n);// 222
// 此时可以继承父函数的属性
实质上就是将子类的原型设置为父类的实例。
ES6继承
ES6继承是通过class丶extends 关键字来实现继承 Parent是父类,child 是子类 通过 class 新建子类 extends继承父类的方式实现继承,方式比ES5简单的多。
class Parent {
constructor(name) {
this.name = name;
this.color = ["red", "blue"];
}
sayName() {
alert(this.name);
}
}
class child extends Parent {
constructor(name, age) {
// 相当于SuperType.call(this, name);
super(name);
this.age = age;
}
}
var s1 = new child('ren', 19);
console.log(s1);
ES5 :子类的原型设置为父类的实例。(es5继承有太多种了,需要那个搜一下,不用全部记住)
ES6:先创造父类的实例对象 this,子类调用super方法,然后再用子类的构造函数修改this
js模块化(commonjs/AMD/CMD/ES6)
一、CommonJS
CommonJS主要用在Node开发上,每个文件就是一个模块 CommonJS通过require()引入模块依赖,require函数可以引入Node的内置模块、自定义模块和npm等第三方模块。
require函数在加载模块是同步的
所以不太适用于浏览器端,否则会造成一个数据无法require,可能会假死。
二、AMD和CMD
区别于CommonJS,AMD规范的被依赖模块是异步加载的,适用于浏览器端模块化开发
AMD 推崇依赖前置,CMD 推崇依赖就近。
三、ES6模块化
ES6 模块化采用静态编译,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。
commonjs在导出时都是值拷贝,就算导出的值变了,导入的值也不会改变,所以如果想更新值,必须重新导入一次。但是ES6模块采用实时绑定的方式,导入导出的值都指向同一个内存地址,所以导入值会跟随导出值变化
从输入URL到呈现页面过程
大致可以分为如下7步:
输入网址;
发送到DNS服务器,并获取域名对应的web服务器对应的ip地址;
与web服务器建立TCP连接;
浏览器向web服务器发送http请求;
web服务器响应请求,并返回指定url的数据(或错误信息,或重定向的新的url地址);
浏览器下载web服务器返回的数据及解析html源文件;
生成DOM树,解析css和js,渲染页面,直至显示完成;
浏览器的缓存,强缓存、协商缓存
我们在开发的时候,会经常遇到304缓存信息,这就涉及到浏览器的缓存问题了
浏览器分为强缓存和协商缓存:
1)浏览器在加载资源时,先根据这个资源的一些http header判断它是否命中强缓存,强缓存如果命中,浏览器直接从自己的缓存中读取资源,不会发请求到服务器。比如某个css文件,如果浏览器在加载它所在的网页时,这个css文件的缓存配置命中了强缓存,浏览器就直接从缓存中加载这个css,连请求都不会发送到网页所在服务器;
2)当强缓存没有命中的时候,浏览器一定会发送一个请求到服务器,通过服务器端依据资源的另外一些http header验证这个资源是否命中协商缓存,如果协商缓存命中,服务器会将这个请求返回,但是不会返回这个资源的数据,而是告诉客户端可以直接从缓存中加载这个资源,于是浏览器就又会从自己的缓存中去加载这个资源;
3)强缓存与协商缓存的共同点是:如果命中,都是从客户端缓存中加载资源,而不是从服务器加载资源数据;区别是:强缓存不发请求到服务器,协商缓存会发请求到服务器。
4)当协商缓存也没有命中的时候,浏览器直接从服务器加载资源数据。
强缓存
强缓存是利用Expires或者Cache-Control这两个http response header实现的,它们都用来表示资源在客户端缓存的有效期。
这两个header可以只启用一个,也可以同时启用,当response header中,Expires和Cache-Control同时存在时,Cache-Control优先级高于Expires:
弱缓存
弱缓存【ETag、If-None-Match】两个字段实现的
浏览器第一次跟服务器请求一个资源,服务器在返回这个资源的同时,在respone的header加上ETag的header,这个header是服务器根据当前请求的资源生成的一个唯一标识
Response Headers
date: Tue, 02 Jul 2019 09:47:48 GMT
ETag: "17df-adasdascae242vsd"
浏览器再次跟服务器请求这个资源时,在request的header上加上If-None-Match的header,这个header的值就是上一次请求时返回的ETag的值
Response Headers
date: Tue, 02 Jul 2019 09:47:48 GMT
If-None-Match: "17df-adasdascae242vsd"
服务器再次收到资源请求时,根据浏览器传过来If-None-Match和资源生成一个新的ETag,如果这两个值相同就说明资源没有变化,否则就是有变化;如果没有变化则返回304 Not Modified,但是不会返回资源内容;如果有变化,就正常返回资源内容
如果资源已经被浏览器缓存下来,在缓存失效之前,再次请求时,默认会先检查是否命中强缓存,如果强缓存命中则直接读取缓存,如果强缓存没有命中则发请求到服务器检查是否命中协商缓存,如果协商缓存命中,则告诉浏览器还是可以从缓存读取,否则才从服务器返回最新的资源。这是默认的处理方式,这个方式可能被浏览器的行为改变:
1)当ctrl+f5强制刷新网页时,直接从服务器加载,跳过强缓存和协商缓存;
2)当f5刷新网页时,跳过强缓存,但是会检查协商缓存
HTTP2
Https加密原理
https协议就是http+ssl协议
1.交换秘钥流程
2.client请求服务端(指定SSL版本和加密组件)
3.server返回CA证书+公钥
4.client用机构公钥认证server返回的CA证书上的签名是否正确
5.client生成一个密钥R,用公钥对密钥R加密发送给server
6.server用服务器的私钥解密获取密钥R
7.后续通信都是采用密钥R进行加密进行信息传递
https 怎么劫持
中间人截取客户端发送给服务器的请求,然后伪装成客户端与服务器进行通信;将服务器返回给客户端的内容发送给客户端,伪装成服务器与客户端进行通信。 通过这样的手段,便可以获取客户端和服务器之间通信的所有内容。 使用中间人攻击手段,必须要让客户端信任中间人的证书,如果客户端不信任,则这种攻击手段也无法发挥作用
三次握手与四次挥手
三次握手
1.客户端发送位码为syn=1,随机产生seq number=1234567的数据包到服务器,服务器由SYN=1知道客户端要求建立联机(客户端:我要连接你)
2.服务器收到请求后要确认联机信息,向A发送ack number=(客户端的seq+1),syn=1,ack=1,随机产生seq=7654321的包(服务器:好的,你来连吧)
- 客户端收到后检查ack number是否正确,即第一次发送的seq number+1,以及位码ack是否为1,若正确,客户端会再发送ack number=(服务器的seq+1),ack=1,服务器收到后确认seq值与ack=1则连接建立成功。(客户端:好的,我来了)
为什么三次握手呢?
因为两次不安全。四次就浪费
四次挥手
(1)第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。
(2)第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号), Server进入CLOSE_WAIT状态。
(3)第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。
(4)第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手。
跨域有哪几种(JSONP/CORS)
一、jsonp
jsopn是因为script标签不受同源策略限制。 但是只能get 还不安全,可能被拦截,发送病毒
二、cors
cors跨域后端, 只能携带一个请求头或者* , 如果用* 就不能携带cookie(浏览器保证安全)
三、proxy代理
vue或者react框架的代理转发,原理是利用node的express模拟服务器请求目标服务器数据,服务器不存在跨域问题。
四、nginx配置
后端护着运维配置,因为服务器请求服务器不存在跨域,所以nginx代理服务器请求数据
web安全问题
这个不是特别大的重点,并不是所有的公司都会询问。但是一般只要问了,总要说点什么吧
https和http的区别
HTTP 的URL 以http:// 开头,而HTTPS 的URL 以https:// 开头
HTTP 是不安全的,而 HTTPS 是安全的
HTTP 标准端口是80 ,而 HTTPS 的标准端口是443
在OSI 网络模型中,HTTP工作于应用层,而HTTPS 的安全传输机制工作在传输层
HTTP 无法加密,而HTTPS 对传输的数据进行加密
HTTP无需证书,而HTTPS 需要CA机构wosign的颁发的SSL证书
什么是Http协议无状态协议?怎么解决Http协议无状态协议? 无状态协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息
也就是说,当客户端一次HTTP请求完成以后,客户端再发送一次HTTP请求,HTTP并不知道当前客户端是一个”老用户“。
可以使用Cookie来解决无状态的问题,Cookie就相当于一个通行证,第一次访问的时候给客户端发送一个Cookie,当客户端再次来的时候,拿着Cookie(通行证),那么服务器就知道这个是”老用户“。
什么是xss,如何预防
XSS,即 Cross Site Script,中译是跨站脚本攻击;其原本缩写是 CSS,但为了和层叠样式表(Cascading Style Sheet)有所区分,因而在安全领域叫做 XSS。
XSS 攻击是指攻击者在网站上注入恶意的客户端代码,通过恶意脚本对客户端网页进行篡改,从而在用户浏览网页时,对用户浏览器进行控制或者获取用户隐私数据的一种攻击方式。
攻击者对客户端网页注入的恶意脚本一般包括 JavaScript,有时也会包含 HTML 和 Flash。有很多种方式进行 XSS 攻击,
但它们的共同点为:将一些隐私数据像 cookie、session 发送给攻击者,
将受害者重定向到一个由攻击者控制的网站,在受害者的机器上进行一些恶意操作。
-
反射型(非持久型) 攻击者会给用一个恶意链接。使用户跳转到黑客准备的页面,请求服务器。服务器会把恶意代码返回。控制用户的电脑
-
持久型
攻击者会在某个论坛等发表评论。同时把自己的恶意代码也提交到目标服务器。当其他用户点击评论的时候,会从服务器上下载恶意代码、中毒。
防范
-
使用 httpOnly 头指定类型,这样js脚本就无法获取到cookie的信息
-
过滤或移除特殊的 html 标签
什么是csrf,如何预防
CSRF,即 Cross Site Request Forgery,中译是 跨站请求伪造,是一种劫持受信任用户向服务器发送非预期请求的攻击方式。
通常情况下,CSRF 攻击是攻击者借助受害者的 Cookie 骗取服务器的信任,可以在受害者毫不知情的情况下以受害者名义伪造请求发送给受攻击服务器,从而在并未授权的情况下执行在权限保护之下的操作。
防范
- 验证码
验证码被认为是对抗 CSRF 攻击最简洁而有效的防御方法。
从上述示例中可以看出,CSRF 攻击往往是在用户不知情的情况下构造了网络请求。而验证码会强制用户必须与应用进行交互,才能完成最终请求。因为通常情况下,验证码能够很好地遏制 CSRF 攻击。
但验证码并不是万能的,因为出于用户考虑,不能给网站所有的操作都加上验证码。因此,验证码只能作为防御 CSRF 的一种辅助手段,而不能作为最主要的解决方案。
- 检查referer字段
根据 HTTP 协议,在 HTTP 头中有一个字段叫 Referer,它记录了该 HTTP 请求的来源地址。通过 Referer Check,可以检查请求是否来自合法的"源"。
框架(vue)
目前市场上,主流就是vue和react框架。这两个都不会的话,就很难混下去,这次就主要是vue的问题,重点问的,无非就是响应式原理,虚拟dom,nextTick的原理,通信。。。。
watch与computed的区别
vue生命周期及对应的行为
(1)beforeCreate中拿不到任何数据,它在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。
(2)created中已经可以拿到data中的数据了,但是dom还没有挂载。会判断有无el,如果没有el则停止后面的模板挂载。 在实例创建完成后被立即调用。在这一步,实例已完成以下的配置:数据观测 (data observer),属性和方法的运算,watch/event 事件回调。
(3)beforeMount 和 created 拿到的数据相同 在挂载开始之前被调用:相关的 render 函数首次被调用。
(4)mounted中el被创建dom已经更新,vue实例对象中有template参数选项,则将其作为模板编译成render函数,编译优先级render函数选项 > template选项
使用场景:常用于获取VNode信息和操作,ajax请求
(5)由于beforeUpdate更新之前,和updated更新之后
(6)beforeDestroyed 和 destroyed 销毁之前 销毁之后
vue父子组件生命周期执行顺序
父beforeCreate->
父created->
父beforeMount->
子beforeCreate->
子created->
子beforeMount->
子mounted->
父mounted。
组件间通讯方法
父子组件 props
非父子组件 eventBus
vuex
路由传参
vue.nextTick实现原理
vue的响应式原理
响应式原理归根结底就是 数据劫持-发布订阅两个模块。
数据劫持分为劫持对象和数组。。。
发布订阅就是数据收集依赖并渲染。。。
虚拟dom的基本原理以及diff算法大概思路。(还会拓展)
如果频繁操作dom,浏览器解析起来很费劲。性能就会很差。
虚拟dom本质就是一个js对象。通过把节点拆解成一个个的属性。进行保存,这样新老dom节点对比。就相当的快。
vue的核心方法就是patch方法,就是把新老虚拟dom传进去。进行对比。diff算法。
前端diff算法一般都是同级对比。这样时间复杂度就是0(n).不会嵌套对比。提高性能
vue的diff算法有个核心方法patch。先判断是不是属性一样,如果不一样。直接就替换,不走diff,一样再走diff
diff算法 头头对比。尾尾对比。尾头对比。头尾对比 这是前四种
还有一种就是新老虚拟dom全部不一样。
如何设计一个组件,单独设计过什么简单的组件
组件的基本原则
- 可复用的模块,完成既定功能
- 有明确的接口规定
- 可以独立发布
设计过简单的全局表格组件,弹框组件,Message组件,
路由守卫怎么用
一、全局路由守卫
router.beforeEach(async (to, from, next) => {} // 进入到某个路由之前
router.afterEach(async (to, from, next) => {} // 进入到某个路由之后
to:进入到哪个路由去,
from:从哪个路由离开,
next:函数,决定是否展示你要看到的路由页面。
router.beforeEach((to,from,next)=>{
if(to.path == '/login' || to.path == '/register'){
next();
}else{
console.log('您还没有登录,请先登录');
next('/login');
}
})
二、组件路由守卫
// 跟methods: {}等同级别书写,组件路由守卫是写在每个单独的vue文件里面的路由守卫
beforeRouteEnter (to, from, next) {
// 注意,在路由进入之前,组件实例还未渲染,所以无法获取this实例,只能通过vm来访问组件实例
next(vm => {})
}
beforeRouteUpdate (to, from, next) {
// 同一页面,刷新不同数据时调用,
}
beforeRouteLeave (to, from, next) {
// 离开当前路由页面时调用
}
三、路由独享守卫
// 只单独给某个路由独享的配置
export default new VueRouter({
routes: [
{
path: '/',
name: 'home',
component: 'Home',
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
不同权限显示不同的菜单,怎么实现
1.路由设置两个对象,一个普通普通路由(没有任何权限),一个动态路由,
2.路由守卫,监听所有的路由走向,根据接口返回的路由name,遍历动态路由的所有信息
3.一旦信息能匹配上,就使用addRouter动态添加异步路由。实现不同菜单权限展示
webpack
webpack必然会问,但是不会太深究,可是最基本的还是要掌握的,比如什么提高性能了,打包分割,loader作用,之类的
用过哪些loader和plugin
webpack的loader和plugin有上万个,全部记住并且会使用是不可能的事情,所以就说说自己常用的几个
style-loader 将css添加到DOM的内联样式标签style里
css-loader 允许将css文件通过require的方式引入,并返回css代码
less-loader 处理less
sass-loader 处理sass
postcss-loader 用postcss来处理CSS
autoprefixer-loader 处理CSS3属性前缀,已被弃用,建议直接使用postcss
file-loader 分发文件到output目录并返回相对路径
url-loader 和file-loader类似,但是当文件小于设定的limit时可以返回一个Data Url
babel-loader 用babel来转换ES6文件到ES5
loader的执行顺序为什么是后写的先执行
先下后上,先右后左
webpack配置优化
webpack代码分割怎么实现
每次面试都会问,你是怎么进行代码分割的,现在终于可以说出来了。
其实代码分割跟webpack 并没有实质联系。只是webpack 现在内置的插件可以帮我们进行代码分割,所有就绑在一块了。
webpack的代码分割就是自带的属性optimization中的splitChunks各种属性配置
Code Splitting 的核心是把很大的文件,分离成更小的块,让浏览器进行并行加载。
假如打包一个2m的文件。用户加载就是2m的资源,如果是两个1m的,浏览器可以并行加载,速度就会很快
一般分割有三种:
手动进行分割:例如项目如果用到lodash,则把lodash单独打包成一个文件。
同步导入的代码:使用 Webpack 配置进行代码分割。
异步导入的代码:通过模块中的内联函数调用来分割代码。
1.手动分割
module.exports = { entry: { main: './src/index.js', lodash: 'lodash' }
本质就是多入口打包,
它输出了两个模块,也能在一定程度上进行代码分割,不过这种分割是十分脆弱的,如果两个模块共同引用了第三个模块,那么第三个模块会被同时打包进这两个入口文件中,而不是分离出来。(别用这个)
2.同步分割
有时候的代码是同步方法,不存在异步方法的话,使用这个,但是谁的代码里面没有异步的啊,这个用的也不是很多
module.exports = {
// 其它配置
optimization: {
splitChunks: {
chunks: 'initial'
}
}
}
3.异步分割
如果我们只需要针对异步代码进行代码分割的话,我们只需要进行异步导入,Webpack会自动帮我们进行代码分割,异步代码分割它的配置如下:
module.exports = {
// 其它配置
optimization: {
splitChunks: {
chunks: 'async'
}
}
}
最后讲解一下splitChunks属性的用法
module.exports = {
// 其它配置项
optimization: {
splitChunks: {
chunks: 'async', // 异步还是同步分割代码
minSize: 30000, // 如果模块大于30k就开始分割。否则就不分割
minChunks: 1, // 改模块必须引用一次以上才分割
maxAsyncRequests: 5, // 默认就行了
maxInitialRequests: 3,// 默认就行了
automaticNameDelimiter: '~',// 默认就行了
name: true,
cacheGroups: {
vendors: { //分组,如果模块满足在module包里面,就打包成vender.js形式
test: /[\\/]node_modules[\\/]/,
priority: -10// 值越大。越服从谁,比如一个loadsh的包,符合第一个组,也符合默认,就看priority的值,越大就打包到哪个组
},
default: { //分组,如果模块不在module包里面,打包成default.js形式
minChunks: 2,
priority: -20,
reuseExistingChunk: true // 如果一个模块已经被打包了,在遇到的时候,就忽略掉,直接使用以前的包
}
}
}
}
};
sourceMap怎么使用
1.什么是sourcemap, 当你文件里面的代码出错了,如果不配置sourceMap,会把报错的信息体现在,压缩完的代码里,这样就很不好,找不到错在哪里了。 但是配置以后,会提示你错在哪个文件夹的哪一行,方便快速找到错误,映射错误文件
2.在module.exports里面直接加上devtools:'sourceMap',可以开启这个功能,如果配置了sourcemap.打包的速度会变慢的。
3.使用sourcemap以后,你会发现,打包好的文件里面,有个.js.map的映射文件
4.官方文档 配置 里面, 有个选项 devtool.里面有很详细的使用方法,
(1)sourceMap.打包出一个xx.js.map的文件
(2)inline-source-map,会把map的文件取消,转换成一个base64的行字符串加在打包的js文件里面.
(3)inline-cheap-source-map,上面的两个会把哪一行,那一列错的位置告诉我们,但是这个会把那一列去到,提高性能。
(4)cheap-module-eval-source-map,开发使用这个最好,全面,速度还快一点 开发环境
(5)cheap-module-source-map,生产使用这个比较好,mode:producton 生产环境
webpack打包优化(happypack、dll)
plugin与loader的区别
-
loader 用于加载某些资源文件。
因为 webpack 只能理解 JavaScript 和 JSON 文件,对于其他资源例如 css,图片,或者其他的语法集,比如 jsx, coffee,是没有办法加载的。 这就需要对应的loader将资源转化,加载进来。从字面意思也能看出,loader是用于加载的,它作用于一个个文件上。
-
plugin 用于扩展webpack的功能。
它直接作用于 webpack,扩展了它的功能。当然loader也是变相的扩展了 webpack ,但是它只专注于转化文件(transform)这一个领域。而plugin的功能更加的丰富,而不仅局限于资源的加载。
webpack执行的过程
(1)初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数;
(2)开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译;
(3)确定入口:根据配置中的 entry 找出所有的入口文件;
(4)编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理;
(5)完成模块编译:在经过第4步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系;再根据plugin的插件进行响应的配置修改
(6)输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会;
(7)输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统。
如何编写一个plugin,大概思路是什么
plugin的大概方式。plugin是一个类,class定义的,所以在wenpack使用的时候都是需要new的, 在文件目录下自己定义一个类,apply,里面传递一个参数,compiler。还有hooks 的钩子,在官网上有响应的api,
tree-shaking作用,如何才能生效
开发中有这种问题,我在一个模块里面建立了是几个方法,在其他模块里面引入了其中一个方法,打包的时候我们会发现模块里面的方法全部都在打包文件里面,这样没有必要,
tree shaking 翻译成汉语的意思是 摇树的意思,意思是把我不需要引入的模块去掉或者吧不需要模块里面的其他方法去掉
webpack里面写入就能使用
optimization: {
usedExports: true
},
但是会有一些问题,
例如import './style.css' 时,会起到副作用,css整个不引入
这个时候在package.json里面设置,过滤掉所有的css。不被摇掉,polyfill也不被摇掉
"sideEffects": [ ".css", "@babel/polyfill" ],
框架不用脚手架,怎么用webpack搭建一个基础脚手架
----------------分割线(最新,待编辑写答案)------------------
1.V8工作原理
要深入理解 V8 的工作原理,我们需要搞清楚一些概念和原理
编译器(Compiler)、解释器(Interpreter)、抽象语法树(AST)、字节码(Bytecode)、即时编译器(JIT) 等概念
- 编译器和解释器
之所以存在编译器和解释器,是因为机器不能直接理解我们所写的代码,所以在执行程序之前,需要将我们所 写的代码“翻译”成机器能读懂的机器语言。按语言的执行流程,可以把语言划分为编译型语言和解释型语言。
编译型语言在程序执行之前,需要经过编译器的编译过程,并且编译之后会直接保留机器能读懂的二进制文件,这样每次运行程序时,都可以直接运行该二进制文件,而不需要再次重新编译了。比如 C/C++、GO 等都是编译型语言。
在编译型语言的编译过程中,编译器首先会依次对源代码进行词法分析、语法分析,生成抽象语法树(AST),然后是优化代码,最后再生成处理器能够理解的机器码。如果编译成功,将会生成一个可执行的文件。但如果编译过程发生了语法或者其他的错误,那么编译器就会抛出异常,最后的二进制文件也不会生成成功。
解释型语言编写的程序,在每次运行时都需要通过解释器对程序进行动态解释和执行。比如 Python、JavaScript 等都属于解释型语言。
在解释型语言的解释过程中,同样解释器也会对源代码进行词法分析、语法分析,并生成抽象语法树(AST),不过它会再基于抽象语法树生成字节码,最后再根据字节码来执行程序、输出结果。
- 生成抽象语法树(AST)
高级语言是开发者可以理解的语言,但是让编译器或者解释器来理解就非常困难了。对于编译器或者解释器来说,它们可以理解的就是 AST 了。所以无论你使用的是解释型语言还是编译型语言,在编译过程中,它们都会生成一个 AST。这和渲染引擎将 HTML 格式文件转换为计算机可以理解的 DOM 树的情况类似。
AST 的结构和代码的结构非常相似(平铺的代码转换成树状结构),其实你也可以把 AST 看成代码的结构化的表示,编译器或者解释器后续的工作都需要依赖于 AST,而不是源代码。
- 生成字节码(Bytecode)
有了AST以后,解释器出场了,它会根据 AST 生成字节码,并解释执行字节码。
字节码就是介于 AST 和机器码之间的一种代码。但是与特定类型的机器码无关,字节码需要通过解释器将其转换为机器码后才能执行。
V8一开始是没有字节码的,直接就把AST生成机器码,机器直接就识别执行了,但是,自从浏览器在手机上普及以后,机器码占得内存很大,V8需要消耗大量的内存存放转换过来的机器码,所以重构了一波,先转换成字节码。字节码内存比机器码小很多。(如图看到大小对比)
- 即时编译(JIT) 当解释器把AST抓换成字节码以后,还要执行字节码,在执行过程中,发现某一段字节码多次执行,就会被判断成为热点代码 此时编译器会把这段热点代码转换成机器码(效率更高),当再次执行这段代码,就直接执行机器码,提高效率,字节码配合解释器和编译器这种方法就叫 即时编译(JIT)
整体流程
