2021 前端面试(杭州)

webpack loader 是做什么的
loader

webpack将一切文件视为模块,但是webpack原生是只能解析js文件,如果想将其他文件也打包的话,就会用到loader。 所以Loader的作用是让webpack拥有了加载和解析非JavaScript文件的能力。

常见loader

style-loader:将CSS添加到DOM的内联样式标签style里

css-loader:允许将CSS文件通过require的方式引入,并返回CSS代码

less-loader:处理less

sass-loader:处理sass

postcss-loader:用postcss来处理CSS

file-loader:分发文件到output目录并返回相对路径

url-loader 和 file-loader 类似,但是当文件小于设定的limit时可以返回一个Data Url

html-minify-loader:压缩HTML文件

babel-loader:把ES6文件转换成ES5文件

webpack中的plugin是做什么的
plugin

loader 用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。包括:打包优化,资源管理,注入环境变量。

常见插件

html-webpack-plugin:会在打包结束之后自动创建一个index.html, 并将打包好的JS自动引入到这个文件中

webpack-dev-server:启动一个本地临时的服务器,可以设置端口,支持热更新

clean-webpack-plugin:每次打包之前先将指定的文件夹清空再生成新的文件

copy-webpack-plugin:打包相关的文档(如word等)。文档内容是固定不变的, 只需要将对应的文件拷贝到打包目录中即可

commons-chunk-plugin:提取公共代码

define-plugin:定义环境变量

webpack是做什么的
为什么需要webpack
1.开发的时候需要一个开发环境,要是我们修改一下代码保存之后浏览器就自动展现最新的代码那就好了(热更新服务)
2.本地写代码的时候,要是调后端的接口不跨域就好了(代理服务)
3.为了跟上时代,要是能用上什么ES678N等等新东西就好了(翻译服务)
4.项目要上线了,要是能一键压缩代码啊图片什么的就好了(压缩打包服务)
5.我们平时的静态资源都是放到CDN上的,要是能自动帮我把这些搞好的静态资源怼到CDN去就好了(自动上传服务)

如果与输入相关的需求,找entry(比如多页面就有多个入口)

如果与输出相关的需求,找output(比如你需要定义输出文件的路径、名字等等)

如果与模块寻址相关的需求,找resolve(比如定义别名alias)

如果与转译相关的需求,找loader(比如处理sass处理es678N)

如果与构建流程相关的需求,找plugin(比如我需要在打包完成后,将打包好的文件复制到某个目录,然后提交到git上)

webpack是如何打包的,如何处理js,css,sass,less这些文件的
git merge 和git rebase有什么区别
git merge master

合并master的记录到分支,合并之后的所有commit会按提交时间从新到旧排列
用git log --graph查看,会有边线
保持了所有commit的连贯性

git rebase master

当前分支的head会移动到master的结尾,会变成一个新的commit
graph是一条直线
commit记录会被修改

什么时候用 rebase,什么时候用 merge?

•如果你的分支要跟别人共享,则不建议用 rebase,因为 rebase 会创建不一致的提交历史。

•如果你想保留完整的提交历史,推荐使用 merge,merge 保留历史 而 rebase 会重写历史。

•rebase 还可以用来压缩、简化历史,通过 git rebase -i 可以在分支合并到主干前,整理自己分支的提交历史,把很多细碎的 commit 整理成一条详细的 commit。

•rebase 一次只处理一个冲突,merge 则一次处理全部冲突。处理冲突 rebase 更方便,但如果有很多冲突的话,撤销一个 rebase 会比 merge 更复杂,merge 只需要撤销一次。

一键换肤功能的实现
sass语法,混入,函数
函数可以访问任何全局定义的变量,也可以像mixin一样接受参数。函数可能包含多个语句,必须调用@return来设置函数的返回值。

es6新特性
const let
模板字面量:${}
解构
for of
拓展运算符
箭头函数
类class
Promise
Promise属性和方法
类的静态属性,静态方法,公用方法
Promise.all Promise.any的原理
Async await
Js事件执行机制(宏任务,微任务)
浏览器的渲染机制,并结合浏览器的渲染机制阐述一下事件循环机制
浏览器渲染的主要工作步骤:下载资源,执行js,进行布局,绘制合成(光栅,合成)
解析HTML文本,生成Document Object Model,即DOM
从网络或本地缓存中加载css,JavaScript资源
当HTML解析器找到script标签后,会暂停HTML解析优先加载解析执行js代码(若不想中断HTML解析可在script标签上添加async或defer,使浏览器异步加载执行js)
async/defer的区别:
defer:有多个设置了defer的script标签存在,则会按照顺序执行 所有的script,defer脚本会在文档渲染完毕后,DOMContentLoaded事件调用前执行
async:async的设置,会使得script脚本异步的加载并在允许的情况下执行,async的执行,并不会按着script在页面中的顺序来执行,而是谁先加载完谁执行且DOMContentLoaded事件的触发并不受async脚本加载的影响
defer:如果你的脚本代码依赖于页面中的DOM元素(文档是否渲染完毕),或者被其他脚本文件依赖
async:如果你的脚本并不关心页面中的DOM元素(文档是否渲染完毕),并且也不会产生其他脚本需要的数据

主线程解析CSS样式,并把CSS样式一一对应到DOM节点上,注意,此时CSS页面还没有生效,只是样式和节点绑定了关系
CSS根据DOM节点,会生成类似于DOM结构的一个布局树,仅包含了页面上可见内容的信息,如果有 display: none 等,则该元素不属于布局树。如果有p::before {content:“123”} 等伪类的存在,就算它不在DOM中,也会包含在布局树中。
至此浏览器知道了:文档的结构,每个DOM元素的样式,页面的几何形状以及绘制的顺序。把这些东西换转为屏幕上像素我们称之为 光栅化。在现代浏览器中执行这一行为的过程,称为 合成(Compositing),就是把页面各个部分分成若干层,分别进行栅格化,然后合成器线程的单独线程中进行合成,一个层可以称之为一个 layer。
层分好了并确定了顺序之后,主线程就把这个信息提交给合成线程,然后合成器线程把每个图层栅格化,发送给栅格线程,栅格线程把它们存储在GPU内存内。
最终,合成线程将栅格化的块合成帧,传递给浏览器进程,显示在屏幕上。
数组平整化(多维数组降维)
利用toString(): 这种方法无法正确处理空数组,且返回的数组中数字是字符串形式

function flatten1(arr) {
return arr.toString().split(’,’)
}
利用Array.prototype.reduce()累加器: 注意需要加初始值(空数组),否则当arr为空时会报错

function flatten2(arr) {
return arr.reduce((a, b) => {
return [].concat(Array.isArray(a) && a ? flatten2(a) : a, Array.isArray(b))
},[])
}
遍历递归数组

const res = [] //存储结果
function flatten3(arr) {
for (const i = 0; i < arr.length; i++) {
if (Array.isArray(arr[i])) {
flatten3(arr[i])
} else {
res.push(arr[i])
}
}
}
利用正则表达式:先传换成JSON字符串->去掉所有中括号->拼接回两端中括号->转回数组

function flatten4(arr) {
return JSON.parse("[" + JSON.stringify(arr).replace(/([],)|[[]]*/g,’’) + “]”)
}
ES6数组新增方法flat/flatMap

//使用 Infinity,可展开任意深度的嵌套数组
const arr4 = [1, 2, [3, 4, [5, 6, [7, 8, [9, 10]]]]];
arr4.flat(Infinity);
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
深拷贝和浅拷贝
深拷贝的实现(JSON序列化的缺点,函数拷贝)
箭头函数和普通函数的区别(箭头函数能new吗,能作为构造函数吗)
Vue子组件会在父组件什么周期内渲染
Vue不同页面复用相同组件保证组件重新刷新
Vue的数据双向绑定,数据驱动
前端工程化
前端工程化是使用软件工程的技术和方法来进行前端的开发流程、技术、工具、经验等规范化、标准化,其主要目的为了提高效率和降低成本,即提高开发过程中的开发效率,减少不必要的重复工作时间

如何做"前端工程化"?

模块化

JS的模块化
用++Webpack + Babel++将所有模块打包成一个文件同步加载,也可以搭乘多个chunk异步加载;
用++System+Babel++主要是分模块异步加载;
用浏览器的

css的模块化
资源的模块化
组件化

规范化

自动化

图标合并
持续集成
自动化构建
自动化部署
自动化测试

婚礼纪
如何实现垂直居中,水平垂直居中
垂直居中

设置line-height
CSS3的flex布局
a. align-items: center;
b. flex-direction: column; justify-content: center;

使用绝对定位和transform
vertical-align:middle
该属性定义行内元素的基线相对于该元素所在行的基线的垂直对齐

vertical-align:middle 有什么使用条件或者说配合他使用的语句
vertical-align:middle有两种用法

在表单元格中,这个属性会设置单元格框中的单元格内容的对齐方式。

该属性定义行内元素的基线相对于该元素所在行的基线的垂直对齐。

要让class="box"的div在class="wrapper"的div里面垂直居中,我可以在class="wrapper"的div里面加一个div空标签,把它的高度设为100%,宽度设置为0,再给它一个vertical-align:middle样式,同样的给class="box"的div一个vertical-align:middle样式,那么box就可以在div里面垂直居中了。

CSS垂直居中
vertical-align的初始值是多少 vertical-align默认是baseline,即基线对齐

flex中,align-items有几个备选属性
flex-start | flex-end | center | baseline | stretch

flex-start:交叉轴的起点对齐。

flex-end:交叉轴的终点对齐。

center:交叉轴的中点对齐。

baseline: 项目的第一行文字的基线对齐。

stretch(默认值):如果项目未设置高度或设为auto,将占满整个容器的高度。

纯css画一个三角形
宽高为0,设置border宽度,其中一边边框有颜色,其他皆为透明的

  .triangle {
    width: 0px;
    height: 0px;
    border-width: 50px;
    border-style: solid;
    border-color: red transparent transparent transparent
  }

纯css实现一个九宫格布局
如果将一个父元素的visibility设置为hidden,子元素的visibility设置为show,那么子元素显示吗
可见

visibility:hidden,display:none,opcity:0之间的区别
浏览器的重绘和回流
当render tree 中的一部分(或者全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建。这就称为回流,每个页面至少需要一次回流,那就是页面第一次加载的时候。

当render tree 中的一些元素需要更新属性时,而这些属性只影响元素的外观,风格,而不会影响布局时,就叫做重绘。

回流一定会重绘,但是重绘不一定回流

发生重绘和回流的情况

页面第一次加载的时候
改变字体,改变元素尺寸(如果只改变字体颜色,元素背景颜色那么只触发重绘)
改变元素里面内容的时候(比如在input里面输入文字)
添加/删除可见DOM元素(注意:如果元素本身就display:none的元素不会发生重排;visibility:hidden的元素显示或隐藏不影响重排)
fiexd定位的元素,在拖动滚动条的时候会一直发生回流。
调整窗口大小的时候
计算offertWidth 和 offsetHeight属性的时候
如何减少回流和重绘

使用tranform代替top
使用visibility 代替 display:none(前者只会重绘,后者会回流)
避免使用table布局(可能一个很小的改动会造成整个table的重新布局)
尽可能在dom树末端改变class(回流是不可避免的,但可以减少其影响。尽可能在DOM树末端改变class,可以限制回流的范围,使其影响尽可能少的节点)
避免设置多层内联样式(比如 div > span > a,因为浏览器要去递归寻找,比较复杂。)
将动画应用到position属性为absolute或者fixed的元素上(避免影响其他元素布局,这样只是重绘,而不是回流,同时控制动画可以选择requestAnimationFrame)
避免使用CSS表达式
避免频繁的操作dom(你可以js创建一个元素,完成所有dom操作在添加到页面当中)
避免频繁的读取会引发回流/重绘的属性(如果要多次使用可以保存起来 const width = element.offertWidth)
BFC以及如何触发一个BFC
BFC:一个元素触发BFC以后,这个元素可以被看做是一个独立的容器。容器里面的元素不会在布局上影响到外部的元素

触发BFC:

根元素()
浮动元素(float不为none)
绝对定位元素
overflow不为visible的元素
contain的值为layout,content,paint的元素
BFC的特性以及应用

避免外边距重叠(margin塌陷问题)
清除浮动
阻止元素被浮动元素覆盖
单行文字溢出隐藏
overflow: hidden;溢出隐藏

white-space: nowrap;文字不能转行

text-overflow:ellipsis;隐藏的部分用…表示

padding-top,padding-bottom是基于什么设定的百分比的值
js如何判断一个数组
Array.isArray

instanceof

const arr = [1, 2, 3, 4]
console.log(arr instanceof Array)
构造函数

const arr = [1, 2, 3, 4]
console.log(arr.constructor === Array)
原型链

const arr = [1, 2, 3, 4]
console.log(Object.prototype.toString.call(arr) === ‘[object Array]’)
instanceof操作符如何使用,instanceof操作符执行时是如何操作的,或者说如何模拟一个instanceof操作符
instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上

即用于判断一个引用类型是否属于某构造函数;还可以在继承关系中用来判断一个实例是否属于它的父类型。

与typeof的区别

、null 都会返回object

为了弥补这一点,instanceof 从原型的角度,来判断某引用属于哪个构造函数,从而判定它的数据类型。

new 关键字执行时是如何操作的,或者说new的时候都做了哪些事情
以构造器的prototype属性为原型,创建新对象;

将this(也就是上一句中的新对象)和调用参数传给构造器,执行;

如果构造器没有手动返回对象,则返回第一步创建的对象

promise如何捕获异常(promise.catch())
try catch拿到的是什么错误,或者说能捕捉到什么异常
try catch能捕捉同步异常还是异步异常
call,apply,bind有什么区别
js中浮点精度丢失是什么原因,怎么解决
原因:由于计算机的二进制实现和位数限制,有些数无法有限表示。JS 遵循 IEEE 754 规范,采用双精度存储(double precision),占用 64 bit。由于计算机是用二进制来存储和处理数字,不能精确表示浮点数,而js中没有相应的封装类来处理浮点数运算,直接计算会导致运算精度丢失。尾数位最大是 52 位,因此 JS 中能精准表示的最大整数是 Math.pow(2, 53)。

解决方法: 基本的思路就是通过将浮点数转换成整数进行计算,然后再将整数的小数点位调整,转回正确的浮点数结果。

//乘法
Number.prototype.mul = function(arg) {
var m = 0,
s1 = this.toString(),
s2 = arg.toString();
try {
m += s1.split(".")[1].length
} catch(e) {}
try {
m += s2.split(".")[1].length
} catch(e) {}
return Number(s1.replace(".", “”)) * Number(s2.replace(".", “”)) / Math.pow(10, m)
}

//加法
Number.prototype.add = function(arg) {
var r1, r2, m;
try {
r1 = this.toString().split(".")[1].length
} catch(e) {
r1 = 0
}
try {
r2 = arg.toString().split(".")[1].length
} catch(e) {
r2 = 0
}
m = Math.pow(10, Math.max(r1, r2))
return( this.mul(m) + Number(arg).mul(m) ).div(m);
}

//减法
Number.prototype.sub = function(arg) {
return this.add(-arg);
}

//除法
Number.prototype.div = function(arg) {
var t1 = 0,
t2 = 0,
r1, r2;
try {
t1 = this.toString().split(".")[1].length
} catch(e) {}
try {
t2 = arg.toString().split(".")[1].length
} catch(e) {}
r1 = Number(this.toString().replace(".", “”))
r2 = Number(arg.toString().replace(".", “”))
return(r1 / r2) * Math.pow(10, t2 - t1);
}

//四舍五入保留两位 n 数
Number.prototype.toFixed = function(n){
var times = Math.pow(10, n)
var des = this * times + 0.5
des = parseInt(des, 10) / times
return des + ‘’;

}
JSON.stritify实现一个深拷贝会有什么弊端
如果obj里面有时间对象,则JSON.stringify后再JSON.parse的结果,时间将只是字符串的形式。而不是时间对象;

如果obj里有RegExp、Error对象,则序列化的结果将只得到空对象;

如果obj里有函数,undefined,则序列化的结果会把函数或 undefined丢失;

如果obj里有NaN、Infinity和-Infinity,则序列化的结果会变成null

JSON.stringify()只能序列化对象的可枚举的自有属性,例如 如果obj中的对象是有构造函数生成的, 则使用JSON.parse(JSON.stringify(obj))深拷贝后,会丢弃对象的constructor;

如果对象中存在循环引用的情况也无法正确实现深拷贝;

//实现深拷贝函数
function deepClone(data) {
const type = this.judgeType(data);
let obj = null;
if (type == ‘array’) {
obj = [];
for (let i = 0; i < data.length; i++) {
obj.push(this.deepClone(data[i]));
}
} else if (type == ‘object’) {
obj = {}
for (let key in data) {
if (data.hasOwnProperty(key)) {
obj[key] = this.deepClone(data[key]);
}
}
} else {
return data;
}
return obj;
}

function judgeType(obj) {
// tostring会返回对应不同的标签的构造函数
const toString = Object.prototype.toString;
const map = {
‘[object Boolean]’: ‘boolean’,
‘[object Number]’: ‘number’,
‘[object String]’: ‘string’,
‘[object Function]’: ‘function’,
‘[object Array]’: ‘array’,
‘[object Date]’: ‘date’,
‘[object RegExp]’: ‘regExp’,
‘[object Undefined]’: ‘undefined’,
‘[object Null]’: ‘null’,
‘[object Object]’: ‘object’,
};
if (obj instanceof Element) {
return ‘element’;
}
return map[toString.call(obj)];
}
function和箭头函数有什么区别
vue中如何进行兄弟组件间的通信
观察者模式和订阅发布模式有什么区别
观察者模式
定义:观察者模式定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知,并自动更新。

发布-订阅模式
定义:发布者的消息发送者不会将消息直接发送给订阅者,这意味着发布者和订阅者不知道彼此的存在。在发布者和订阅者之间存在第三个组件,称为消息代理或调度中心或中间件,它维持着发布者和订阅者之间的联系,过滤所有发布者传入的消息并相应地分发它们给订阅者。

TCP和UDP有什么区别
如何保证cookie的安全性
柯里化
定义:柯里化是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术

// 普通的add函数
function add(x, y) {
return x + y
}

// Currying后
function curryingAdd(x) {
return function (y) {
return x + y
}
}

add(1, 2) // 3
curryingAdd(1)(2) // 3
柯里化的好处:

参数复用
提前确认
var on = function(element, event, handler) {
if (document.addEventListener) {
if (element && event && handler) {
element.addEventListener(event, handler, false);
}
} else {
if (element && event && handler) {
element.attachEvent(‘on’ + event, handler);
}
}
}

var on = (function() {
if (document.addEventListener) {
return function(element, event, handler) {
if (element && event && handler) {
element.addEventListener(event, handler, false);
}
};
} else {
return function(element, event, handler) {
if (element && event && handler) {
element.attachEvent(‘on’ + event, handler);
}
};
}
})();

//换一种写法可能比较好理解一点,上面就是把isSupport这个参数给先确定下来了
var on = function(isSupport, element, event, handler) {
isSupport = isSupport || document.addEventListener;
if (isSupport) {
return element.addEventListener(event, handler, false);
} else {
return element.attachEvent(‘on’ + event, handler);
}
}
在项目中,封装一些dom操作可以说再常见不过,上面第一种写法也是比较常见,但是我们看看第二种写法,它相对一第一种写法就是自执行然后返回一个新的函数,这样其实就是提前确定了会走哪一个方法,避免每次都进行判断。
延迟运行
Function.prototype.bind = function (context) {
var _this = this
var args = Array.prototype.slice.call(arguments, 1)

 return function() {
     return _this.apply(context, args)
 }

}
js中经常使用的bind,实现的机制就是Currying.
柯里化封装

// 支持多参数传递
function progressCurrying(fn, args) {

var _this = this
var len = fn.length;
var args = args || [];

return function() {
    var _args = Array.prototype.slice.call(arguments);
    Array.prototype.push.apply(args, _args);

    // 如果参数个数小于最初的fn.length,则递归调用,继续收集参数
    if (_args.length < len) {
        return progressCurrying.call(_this, fn, _args);
    }

    // 参数收集完毕,则执行fn
    return fn.apply(this, _args);
}

}
柯里化经典面试题

// 实现一个add方法,使计算结果能够满足如下预期:
add(1)(2)(3) = 6;
add(1, 2, 3)(4) = 10;
add(1)(2)(3)(4)(5) = 15;

function add() {
// 第一次执行时,定义一个数组专门用来存储所有的参数
var _args = Array.prototype.slice.call(arguments);

// 在内部声明一个函数,利用闭包的特性保存_args并收集所有的参数值
var _adder = function() {
    _args.push(...arguments);
    return _adder;
};

// 利用toString隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
_adder.toString = function () {
    return _args.reduce(function (a, b) {
        return a + b;
    });
}
return _adder;

}

add(1)(2)(3) // 6
add(1, 2, 3)(4) // 10
add(1)(2)(3)(4)(5) // 15
add(2, 6)(1) // 9

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值