-
-
- 一、HTML
- 二、CSS
- 三、JavaScript
-
- JS 执行机制
- JS的基本数据类型
- JS有哪些内置对象
- JS的几条基本规范
- JS引用方法
- 合并数组
- 补充get和post请求在缓存方面的区别
- 请描述一下本地存储 `cookies`、 `sessionStorage`和`localstorage`区别
- call,apply,bind的区别?
- 闭包
- JS作用域及作用域链
- this
- 函数作用域
- 事件循环
- 图片的预加载和懒加载
- mouseover和mouseenter的区别
- 解决异步回调地狱
- typeof vs instanceof
- ['1', '2', '3'].map(parseInt) what & why?
- 什么是防抖和节流?有什么区别?如何实现?
- setTimeout、Promise、Async/Await 的区别?
- Async/Await 如何通过同步的方式实现异步?
- JS事件循环,微任务和宏任务
- Promise 构造函数是同步执行还是异步执行,那么 then 方法呢?
- JS 异步解决方案的发展历程以及优缺点
- 情人节福利题,如何实现一个 new
- 四、Vue
- 五、ES6
- 六、Git
- 七、Webpack
- 八、网络协议
- 九、性能优化
- 十、算法
- 十一、其他
-
一、HTML
让一个div垂直居中的方式
.parent {
display: flex;
justify-content: center; //水平居中
align-items: center; //垂直居中
}
详情请戳:https://blog.csdn.net/a1056244734/article/details/106759185
h5新标签
有<section>、<header>、<footer>、<aside>、<nav>、<video>、<audio>、<canvas>等...
详细瞅这里:https://blog.csdn.net/a1056244734/article/details/106865698
谈谈iframe的优缺点
iframe是一种框架,也是一种很常见的网页嵌入方式
iframe的优点:
- iframe能够原封不动的把嵌入的网页展现出来。
- 如果有多个网页引用iframe,那么你只需要修改iframe的内容,就可以实现调用的每一个页面内容的更改,方便快捷。
- 网页如果为了统一风格,头部和版本都是一样的,就可以写成一个页面,用iframe来嵌套,可以增加代码的可重用。
- 如果遇到加载缓慢的第三方内容如图标和广告,这些问题可以由iframe来解决。
iframe的缺点:
- 会产生很多页面,不容易管理。
- iframe框架结构有时会让人感到迷惑,如果框架个数多的话,可能会出现上下、左右滚动条,会分散访问者的注意力,用户体验度差。
- 代码复杂,无法被一些搜索引擎索引到,这一点很关键,现在的搜索引擎爬虫还不能很好的处理iframe中的内容,所以使用iframe会不利于搜索引擎优化。
- 很多的移动设备(PDA 手机)无法完全显示框架,设备兼容性差。
- iframe框架页面会增加服务器的http请求,对于大型网站是不可取的。
- 浏览器的后退按钮无效(只能针对实现当前光标所在页面的前进与后退,无法实现frameset整个页面的前进与后退)
分析了这么多,现在基本上都是用Ajax来代替iframe,所以iframe已经渐渐的退出了前端开发。
二、CSS
3.Bootstrap响应式布局原理
简言之:百分比布局+媒体查询
Bootstrap响应式布局是利用其栅格系统,对于不同的屏幕采用不同的类属性。在开发中可以只写一套代码在手机平板,PC端都能使用,而不用考虑使用媒体查询(针对不同的设备分别写不同的代码)。Bootstrap的官方解释:Bootstrap提供了一套响应式、移动设备优先的流式栅格系统,随着屏幕或视口(viewport)尺寸的增加,系统会自动分为做多12列。
栅格系统用于通过一系列的行(row)与列(column)的组合来创建页面布局。
使用Bootstrap响应式布局
首先需要在head中引入meta标签,添加viewpirt属性,content中宽度等于设备宽度, initial-scale:页面首次被显示可见区域的缩放级别,取值1则页面按实际尺寸显示,无任何缩放;
maximum-scale:允许用户缩放到的最小比例;
user-scalable:用户是否可以手动缩放。代码如下:
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<link rel="stylesheet" type="text/css" href="/stylesheets/bootstrap.min.css">
CSS 盒模型(Box Model),box-sizing
盒模型又被分为 IE盒模型 和 W3C标准盒模型 两种类型。
在 IE8+ 的浏览器中要使用哪个盒模型可以由 box-sizing 控制。
box-sizing: content-box //W3C标准盒模型,默认
box-sizing: border-box //IE盒模型
这两者的区别在于:
- IE盒模型( box-sizing: border-box )的盒子大小为 content + padding + border。
- W3C标准盒模型( box-sizing:content-box )的盒子大小为 content, 不包括 padding 和 border,后两者另算大小。
详情请戳:CSS 盒模型(Box Model),box-sizing
rem与em的区别
rem是根据根的font-size变化,而em是根据父级的font-size变化
rem:相对于根元素html的font-size
html {
font-size: 20px;
}
div {
font-size: 2rem;
}
//相当于
div {
font-size: 40px;
}
em:相对于父元素计算
p {
font-size: 20px;
}
// p为span父元素
p span {
font-size: 2rem;
}
//相当于
p span {
font-size: 40px; // 20*2=40
}
CSS选择器
css常用选择器
通配符选择器:*
ID选择器:#myid
类选择器:.myclassname
元素选择器:div、span、p、a 等
相邻选择器(h1+p)
子选择器(ul < li)
后代选择器:p span、div a 等
伪类选择器:a:hover、 li:nth-child 等
属性选择器:a[rel="external"]、input[type="text"]
[class*="col-"]
选择所有类名中含有"col-"的元素
[class^="col-"]
选择所有类名中以"col-"开头的元素
[class$="-col"]
选择所有类名中以"-col"结尾的元素
css选择器权重
!important --> 行内样式 --> #id --> .class --> 元素和伪元素 --> * --> 继承 --> 默认
position定位属性详解
值 | 描述 |
---|---|
absolute | 生成绝对定位的元素,相对于static定位以外的第一个父元素(最近的已定位的祖先元素)进行定位。元素的位置通过left, right, top, bottom进行规定 |
fixed | 生成绝对定位的元素,相对于浏览器窗口进行定位。元素的位置通过left, right, top, bottom进行规定 |
relative | 生成相对定位的元素,相对于其正常位置定位。元素的位置通过left, right, top, bottom进行规定 |
static | 默认值,忽略 top, bottom, left, right和z-index |
inherit | 从父元素继承该属性的值 |
link与@import区别与选择
<style type="text/css">
@import url(CSS文件路径地址);
</style>
<link href="CSSurl路径" rel="stylesheet" type="text/css">
- link功能较多,可以定义 RSS,定义 Rel 等作用,而@import只能用于加载 css;
- 当解析到link时,页面会同步加载所引的 css,而@import所引用的 css 会等到页面加载完才被加载;
- @import需要 IE5 以上才能使用;
- link可以使用 js 动态引入,@import不行
用 css 或 js 实现多行文本溢出省略效果,考虑兼容性
/*单行*/
.single {
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
word-break: break-all;
}
/*多行*/
.mutiple {
display: -webkit-box; /*重点,不能用block等其他,将对象作为弹性伸缩盒子模型显示*/
-webkit-box-orient: vertical; /*从上到下垂直排列子元素(设置伸缩盒子的子元素排列方式)*/
-webkit-line-clamp: 3; /*行数,超出三行隐藏且多余的用省略号表示...*/
line-clamp: 3;
overflow: hidden;
max-width: 100%;
}
/*多行考虑兼容性*/
p.compatible {
position: relative;
line-height: 20px;
max-height: 40px;
overflow: hidden;
}
p.compatible::after {
content: "...";
position: absolute;
bottom: 0;
right: 0;
padding-left: 40px;
background: -webkit-linear-gradient(left, transparent, #fff 55%);
background: -o-linear-gradient(right, transparent, #fff 55%);
background: -moz-linear-gradient(right, transparent, #fff 55%);
background: linear-gradient(to right, transparent, #fff 55%);
}
详情戳:https://blog.csdn.net/a1056244734/article/details/106772336
三、JavaScript
JS 执行机制
代码提升(为了编译)
- 变量提升
- 函数提升(优先级最高)
编译代码
V8 编译 JS 代码的过程
- 生成抽象语法树(AST)和执行上下文
- 第一阶段是分词(tokenize),又称为词法分析
- 第二阶段是解析(parse),又称为语法分析
- 生成字节码
字节码就是介于 AST 和机器码之间的一种代码。但是与特定类型的机器码无关,字节码需要通过解释器将其转换为机器码后才能执行。 - 执行代码
高级语言编译器步骤:
- 输入源程序字符流
- 词法分析
- 语法分析
- 语义分析
- 中间代码生成
- 机器无关代码优化
- 代码生成
- 机器相关代码优化
- 目标代码生成
执行代码
- 执行全局代码时,创建全局上下文
- 调用函数时,创建函数上下文
- 使用 eval 函数时,创建 eval 上下文
- 执行局部代码时,创建局部上下文
JS的基本数据类型
Undefined
、Null
、Boolean
、Number
、String
、ES6新增:Symbol
JS有哪些内置对象
Object是JavaScript中所有对象的父对象
数据封装对象:Object
、Array
、Boolean
、Number
和String
其他对象:Function
、Arguments
、Math
、Date
、RegExp
、Error
JS的几条基本规范
1、不要在同一行声明多个变量
2、请使用===/!==来比较true/false或者数值
3、使用对象字面量[1,2]替代new Array这种形式
4、不要使用全局变量
5、Switch语句必须带有default分支
6、函数不应该有时候有返回值,有时候没有返回值
7、for循环必须使用大括号
8、if语句必须使用大括号
9、for-in循环中的变量 应该使用var关键字明确限定作用域,从而避免作用域污染
JS引用方法
行内引入
<body>
<input type="button" onclick="alert('行内引入')" value="按钮"/>
<button onclick="alert(123)">点击我</button>
</body>
内部引入
<!DOCTYPE html>
<html>
<head></head>
<body>
<script>
window.onload = function () {
alert("js 内部引入!")
}
</script>
</body>
</html>
外部引入
<body>
<div></div>
<script type="text/javascript" src="./js/index.js"></script>
</body>
注意
1,不推荐行内引入或者HTML中插入<script>,因为浏览器解析顺序缘故,如果解析到死循环之类的JS代码,会卡住页面
2,建议在onload事件之后,即等HTML、CSS渲染完毕再执行代码
合并数组
// 合并a,b两个数组,两种方法
const a = [1,2,3]; const b = [4,5,6];
const c = a.concat(b)
a.push(...b) // a会被改变
详情请戳:https://blog.csdn.net/a1056244734/article/details/106941027
补充get和post请求在缓存方面的区别
-
get请求类似于查找的过程,用户获取数据,可以不用每次都与数据库连接,所以可以使用缓存。
-
post不同,post做的一般是修改和删除的工作,所以必须与数据库交互,所以不能使用缓存。因此get请求适合于请求缓存。
请描述一下本地存储 cookies
、 sessionStorage
和localstorage
区别
相同点:都用于客户端(浏览器)存储
不同点:
1. 存储大小:
- cookies
: 4K左右,
- sessionStorage
和localstorage
5M或更大
2. 数据与服务器之间的交互方式:
- cookie的数据会自动传递到服务器,服务端也可以写cookie到客户端
- sessionStorage
和localstorage
不会自动把数据发给服务器,仅在客户端本地保存
3. 有效时间:
- cookies
的有效期是服务端配置的一个过期时间,在此之前一直有效,即使窗口或浏览器关闭
- sessionStorage
数据在当前浏览器窗口关闭后自动删除
- localstorage
存储持久数据,浏览器关闭后数据不丢失除非主动删除数据
更多详情请戳:浏览器本地存储cookie、localStorage 与 sessionStorage
call,apply,bind的区别?
闭包
闭包
就是能够读取其他函数内部变量的函数。
在javascript中,只有函数(A)内部的子函数(B)才能读取局部变量(A中的),所以闭包可以理解成“定义在一个函数内部的函数”。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。
闭包的特征
- 函数内再嵌套函数
- 内部函数可以引用外层的参数和变量
- 参数和变量不会被垃圾回收制回收
闭包用途
- 保护:保护私有变量不受外界干扰,与外界没有必然关系,可以读取另一个函数内部的变量;
- 保存:形成一个不销毁的私有栈内存,让这些变量的值始终保持在内存中;
闭包的好处
能够实现封装和缓存等
闭包的坏处
消耗内存、不正当使用会造成内存溢出的问题
使用闭包的注意点
1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。
解决方法是,在退出函数之前,将不使用的局部变量全部删除。
2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
闭包大厂面试题
描述:下面代码能否实现点击某个按钮,body的背景色改为按钮对应的颜色,若不能,如何改进(腾讯)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0">
<style>
* {
margin: 0;
padding: 0;
}
html,
body {
height: 100%;
overflow: hidden;
}
button {
padding: 5px 10px;
cursor: pointer;
}
</style>
</head>
<body>
<!---->
<button value="red">红</button>
<button value="green">绿</button>
<button value="blue">蓝</button>
<script>
var body = document.querySelector('body'),
buttons = document.querySelectorAll('button'),
arr = ['red', 'green', 'blue']
for (var i = 0; i < buttons.length; i++) {
buttons[i].onclick = function () {
body.style.background = arr[i]
}
}
</script>
</body>
</html>
答案当然是不能,因为通过var
定义的变量,在for循环中的i
是全局的,变量提升、3次循环过后,i=3
,因为点击每个都相当于点击最后一个。
优化
- 给元素对象添加自定义属性
for (var i = 0; i < buttons.length; i++) {
var item = buttons[i]
//=>在循环的时候,把每一个按钮的索引赋值给当前按钮(元素对象)的myIndex自定义属性
item.myIndex = i
item.onclick = function () {
// this => 当前点击的这个按钮
body.style.background = arr[this.myIndex]
}
}
- 利用闭包
// 都是利用闭包的机制去解决的
for (var i = 0; i < buttons.length; i++) {
buttons[i].onclick = (function (i) {
return function anonymous() {
body.style.background = arr[i]
}
})(i)
}
- 把
var
换为let
如果代码块中出现了 let/const/function 则当前代码块会产生一个 块级上下文(词法/块级作用域) => 私有的上下文
let的方法和闭包的方法原理类似,都是每一轮循环产生一个私有的作用域,(LET块级作用域),保存住当前循环i的值,以供后期调用。
var body = document.querySelector('body'),
buttons = document.querySelectorAll('button'),
arr = ['red', 'green', 'blue']
for (let i = 0; i < buttons.length; i++) {
// 私有的块级上下文
// 循环几次会产生几个块级上下文
buttons[i].onclick = function () {
body.style.background = arr[i]
}
}
后期真实项目中推荐大家使用自定义属性方法。不会产生那么多不销毁的私有作用域。
详情戳:闭包详解
JS作用域及作用域链
作用域:在JavaScript中,作用域分为全局作用域
和函数作用域
全局作用域:代码在程序的任何地方都能被访问,window 对象的内置属性都拥有全局作用域
函数作用域:在固定的代码片段才能被访问
this
this 是和执行上下文绑定的。
执行上下文:
- 全局执行上下文:全局执行上下文中的 this 也是指向 window 对象。
- 函数执行上下文:使用对象来调用其内部的一个方法,该方法的 this 是指向对象本身的。
- eval 执行上下文:执行 eval 环境内部的上两个情况。
根据优先级最高的来决定 this
最终指向哪里。
- 箭头函数(
() => {}
) - 关键字new调用(
new Person()
) - 显式绑定(
bind/apply/call
) - 隐式绑定(
obj.foo()
) - 默认绑定(
foo()
)
几点寻找this规律:
- 给元素的某个事件绑定方法,当事件触发方法执行的时候,方法中的
this
是当前操作的元素(隐式绑定) - 方法执行,看方法前面是否有点(.),如果有点,点前面是谁
this
就是谁(隐式绑定),没有点this
就是window
(在严格模式下("use strict
")没有点this
是undefined
,也成为默认绑定) - 在构造函数模式执行中,函数体中的
this
是当前类的实例 - call/bind/apply可以改变
this
的指向(显式绑定) - 箭头函数会无视以上所有的规则,因为箭头函数没有自己的执行上下文,this的值就是函数创建时候所在的词法作用域(lexical scope)中的this(外层函数的 this),而和调用方式无关。
eg1:
var fullName = 'language'
var obj = {
fullName: 'javascript',
prop: {
getFullName: function () {
return this.fullName
}
}
}
console.log(obj.prop.getFullName())
var test = obj.prop.getFullName
console.log(test())
输出:javascript
language
此题符合第二条规律,方法执行时obj.prop.getFullName()
的getFullName()
前方有点,this
为obj.prop
,输出obj.prop.fullName
即javascript
;test()
前面没有点,this
为window,输出window.fullName
即language
;
详情戳:https://blog.csdn.net/a1056244734/article/details/107181337
函数作用域
全局作用域
对象在代码中的任何地方都能访问,其生命周期伴随着页面的生命周期。
函数作用域
函数内部定义的变量或者函数,并且定义的变量或者函数只能在函数内部被访问。函数执行结束之后,函数内部定义的变量会被销毁。
局部作用域
使用一对大括号包裹的一段代码,比如函数、判断语句、循环语句,甚至单独的一个{}都可以被看作是一个块级作用域。
例子:
作用域有上下级关系,上下级关系的确定就看函数是在哪个作用域下创建的。
如上,fn作用域下创建了bar函数,那么“fn作用域”就是“bar作用域”的上级。
作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。
变量取值:到创建 这个变量 的函数的作用域中取值
作用域链
一般情况下,变量取值到 创建 这个变量 的函数的作用域中取值。
但是如果在当前作用域中没有查到值,就会向上级作用域去查,直到查到全局作用域,这么一个查找过程形成的链条就叫做作用域链。
var a = 0,
b = 0
function A(a) {
A = function (b) {
console.log(a + b++)
}
console.log(a++)
}
A(1)
A(2)
输出:1
4
图解:
注意点:函数找上级作用域时,有一个准则:它的亲爹妈是谁,上级作用域就是谁,即它在哪儿创建的,上级作用域就是谁。
更多作用域易错面试题请戳:JavaScript中函数作用域相关易错面试题
原型&原型链
原型和原型链的概念
其实每个 JS 对象都有 __proto__
属性,这个属性指向了原型。
原型也是一个对象,并且这个对象中包含了很多函数,对于 obj
来说,可以通过 __proto__
找到一个原型对象,在该对象中定义了很多函数让我们来使用。
原型链:
Object
是所有对象的爸爸,所有对象都可以通过__proto__
找到它Function
是所有函数的爸爸,所有函数都可以通过__proto__
找到它- 函数的
prototype
是一个对象 - 对象的
__proto__
属性指向原型,__proto__
将对象和原型连接起来组成了原型链
原型和原型链的关系
instance.constructor.prototype = instance.__proto__
原型和原型链的特点
JavaScript对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变
当我们需要一个属性的时,Javascript引擎会先看当前对象中是否有这个属性, 如果没有的就会查找他的Prototype对象是否有这个属性,如此递推下去,一直检索到 Object 内建对象
事件循环
主要宏任务:整段脚本script
setTimeout
setInterval
Promise主体
其他宏任务:setImmediate
I/O
UI rendering
主要微任务:promise.then catch finally
process.nextTick(Node使用)
MutationObserver(浏览器使用)
MessageChannel
执行顺序
- 执行同步代码,这属于宏任务
- 执行栈为空,查询是否有微任务需要执行
- 必要的话渲染 UI
- 然后开始下一轮 Event loop,执行宏任务中的异步代码
图片的预加载和懒加载
预加载:提前加载图片,当用户需要查看时可直接从本地缓存中渲染
懒加载:懒加载的主要目的是作为服务器前端的优化,减少请求数或延迟请求数
两种技术的本质:两者的行为是相反的,一个是提前加载,一个是迟缓甚至不加载。预加载则会增加服务器前端压力,懒加载对服务器有一定的缓解压力作用。
mouseover和mouseenter的区别
mouseover:当鼠标移入元素或其子元素都会触发事件,所以有一个重复触发,冒泡的过程。对应的移除事件是mouseout
mouseenter:当鼠标移入元素本身(不包含元素的子元素)会触发事件,也就是不会冒泡,对应的移除事件是mouseleave
解决异步回调地狱
promise、generator、async/await
typeof vs instanceof
instanceof
运算符用来检测 constructor.prototype
是否存在于参数 object
的原型链上。
typeof
操作符返回一个字符串,表示未经计算的操作数的类型。
var ClassFirst = function () {
};
var ClassSecond = function () {
};
var instance = new ClassFirst();
typeof instance; // 'object'
typeof instance == 'ClassFirst'; // false
instance instanceof Object; // true
instance instanceof ClassFirst; // true
instance instanceof ClassSecond; // false
typeof
对于原始类型来说,除了null
都可以显示正确的类型typeof
对于对象来说,除了函数都会显示'object'
如果变量可能未定义,使用typeof
比较好。
typeof undefinedVariable // "undefined"
undefinedVariable instanceof Object // throws an exception
如果变量可能为null
,使用instanceof
比较好。
var myNullVar = null;
typeof myNullVar; // "object"
myNullVar instanceof Object; // false
详细请戳:https://blog.csdn.net/a1056244734/article/details/107231545
[‘1’, ‘2’, ‘3’].map(parseInt) what & why?
输出:[1, NaN, NaN]
。
- 首先让我们回顾一下,map函数的第一个参数callback:
var new_array = arr.map(function callback(currentValue[, index[, array]]) {
// Return element for new_array }[, thisArg])
这个callback一共可以接收三个参数,其中第一个参数代表当前被处理的元素,而第二个参数代表该元素的索引。
-
而parseInt则是用来解析字符串的,使字符串成为指定基数的整数。
parseInt(string, radix)
接收两个参数,第一个表示被处理的值(字符串),第二个表示为解析时的基数。 -
了解这两个函数后,我们可以模拟一下运行情况
- parseInt(‘1’, 0) //radix为0时,且string参数不以“0x”和“0”开头时,按照10为基数处理。这个时候返回1
- parseInt(‘2’, 1) //基数为1(1进制)表示的数中,最大值小于2,所以无法解析,返回NaN
- parseInt(‘3’, 2) //基数为2(2进制)表示的数中,最大值小于3,所以无法解析,返回NaN
map函数返回的是一个数组,所以最后结果为[1, NaN, NaN]
- 最后附上MDN上对于这两个函数的链接,具体参数大家可以到里面看
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/parseInt
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/map
什么是防抖和节流?有什么区别?如何实现?
- 防抖
触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间
思路:
每次触发事件时都取消之前的延时调用方法
function debounce(fn) {
let timeout = null; // 创建一个标记用来存放定时器的返回值
return function () {
clearTimeout(timeout); // 每当用户输入的时候把前一个 setTimeout clear 掉
timeout = setTimeout(() => {
// 然后又创建一个新的 setTimeout, 这样就能保证输入字符后的 interval 间隔内如果还有字符输入的话,就不会执行 fn 函数
fn.apply(this, arguments);
}, 500);
};
}
function sayHi() {
console.log('防抖成功');
}
var inp = document.getElementById('inp');
inp.addEventListener('input', debounce(sayHi)); // 防抖
- 节流
高频事件触发,但在n秒内只会执行一次,所以节流会稀释函数的执行频率
- 思路:
每次触发事件时都判断当前是否有等待执行的延时函数
function throttle(fn) {
let canRun = true; // 通过闭包保存一个标记
return function () {
if (!canRun) return; // 在函数开头判断标记是否为true,不为true则return
canRun = false; // 立即设置为false
setTimeout(() => {
// 将外部传入的函数的执行放在setTimeout中
fn.apply(this, arguments);
// 最后在setTimeout执行完毕后再把标记设置为true(关键)表示可以执行下一次循环了。当定时器没有执行的时候标记永远是false,在开头被return掉
canRun = true;
}, 500);
};
}
function sayHi(e) {
console.log(e.target.innerWidth, e.target.innerHeight);
}
window.addEventListener('resize', throttle(sayHi));
setTimeout、Promise、Async/Await 的区别?
这题主要是考察这三者在事件循环中的区别,事件循环中分为宏任务队列和微任务队列。
- 其中settimeout的回调函数放到宏任务队列里,等到执行栈清空以后执行;
- promise.then里的回调函数会放到相应宏任务的微任务队列里,等宏任务里面的同步代码执行完再执行;
- async函数表示函数里面可能会有异步方法,await后面跟一个表达式,async方法执行时,遇到await会立即执行表达式,然后把表达式后面的代码放到微任务队列里,让出执行栈让同步代码先执行。
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0)
async1();
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
console.log('script end');
输出:script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
详情请戳:JavaScript执行机制,任务队列、宏任务和微任务
Async/Await 如何通过同步的方式实现异步?
https://juejin.im/post/5d2c814c6fb9a07ecd3d8e43
JS事件循环,微任务和宏任务
async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2')
}
console.log('script start')
setTimeout(function () {
console.log('setTimeout')
}, 0)
async1()
new Promise(function (resolve) {
console.log('promise1')
resolve()
}).then(function () {
console.log('promise2')
})
console.log('script end')
结果:script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
考察重点:
- 主要宏任务:
整段脚本script
setTimeout
setInterval
、I/O
UI交互事件
postMessage
MessageChannel
setImmediate(Node.js 环境)
- 主要微任务:
promise.then catch finally
process.nextTick(Node使用)
MutationObserver(浏览器使用)
- 宏任务和微任务执行流程:
- 整段脚本script作为宏任务开始执行
- 遇到微任务将其推入微任务队列,宏任务推入宏任务队列
- 宏任务执行完毕,检查有没有可执行的微任务
- 发现有可执行的微任务,将所有微任务执行完毕
- 当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染
- 渲染完毕后,JS线程继续接管,开始新的宏任务,反复如此直到所有任务执行完毕
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
等价于
async function async1() {
console.log('async1 start');
Promise.resolve(async2()).then(() => {
console.log('async1 end');
})
}
详情戳:几道面试题理清JavaScript执行机制(Event Loop),任务队列、宏任务和微任务
Promise 构造函数是同步执行还是异步执行,那么 then 方法呢?
看过 Event Loop 基础原理的就明白,Promise构造函数是同步执行,而 .then .catch .啥啥的是异步(还有process.nextTick等等,大家可以查),
而且放到了微队列中,async/await 中,await 前面的是同步,await 后面的是异步,写法上是这样,但是其实是 语法糖,最后还会转为 Promise.then的形式
JS 异步解决方案的发展历程以及优缺点
JS 异步已经告一段落了,这里来一波小总结
- 回调函数(callback)
setTimeout(() => {
// callback 函数体
}, 1000)
缺点:回调地狱,不能用 try catch 捕获错误,不能 return
回调地狱的根本问题在于:
- 缺乏顺序性: 回调地狱导致的调试困难,和大脑的思维方式不符
- 嵌套函数存在耦合性,一旦有所改动,就会牵一发而动全身,即(控制反转)
- 嵌套函数过多的多话,很难处理错误
ajax('XXX1', () => {
// callback 函数体
ajax('XXX2', () => {
// callback 函数体
ajax('XXX3', () => {
// callback 函数体
})
})
})
优点:解决了同步的问题(只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。)
- Promise
Promise就是为了解决callback的问题而产生的。
Promise 实现了链式调用,也就是说每次 then 后返回的都是一个全新 Promise,如果我们在 then 中 return ,return 的结果会被 Promise.resolve() 包装
优点:解决了回调地狱的问题
ajax('XXX1')
.then(res => {
// 操作逻辑
return ajax('XXX2')
}).then(res => {
// 操作逻辑
return ajax('XXX3')
}).then(res => {
// 操作逻辑
})
缺点:无法取消 Promise ,错误需要通过回调函数来捕获
- Generator
特点:可以控制函数的执行,可以配合 co 函数库使用
function *fetch() {
yield ajax('XXX1', () => {
})
yield ajax('XXX2', () => {
})
yield ajax('XXX3', () => {
})
}
let it = fetch()
let result1 = it.next()
let result2 = it.next()
let result3 = it.next()
- Async/await
async、await 是异步的终极解决方案
优点是:代码清晰,不用像 Promise 写一大堆 then 链,处理了回调地狱的问题
缺点:await 将异步代码改造成同步代码,如果多个异步操作没有依赖性而使用 await 会导致性能上的降低。
async function test() {
// 以下代码没有依赖性的话,完全可以使用 Promise.all 的方式
// 如果有依赖性的话,其实就是解决回调地狱的例子了
await fetch('XXX1')
await fetch('XXX2')
await fetch('XXX3')
}
下面来看一个使用 await 的例子:
let a = 0
let b = async () => {
a = a + await 10
console.log('2', a) // -> '2' 10
}
b()
a++
console.log('1', a) // -> '1' 1
结果:'1' 1
'2' 10
对于以上代码你可能会有疑惑,让我来解释下原因
- 首先函数 b 先执行,在执行到
await 10
之前变量 a 还是 0,因为await
内部实现了generator
,generator 会保留堆栈中东西,所以这时候 a = 0 被保存了下来 - 因为
await
是异步操作,后来的表达式不返回 Promise 的话,就会包装成Promise.reslove(返回值)
,然后会去执行函数外的同步代码 - 同步代码执行完毕后开始执行异步代码,将保存下来的值拿出来使用,这时候
a = 0 + 10
上述解释中提到了 await 内部实现了 generator,其实 await 就是 generator 加上 Promise的语法糖,且内部实现了自动执行 generator。如果你熟悉 co 的话,其实自己就可以实现这样的语法糖。
情人节福利题,如何实现一个 new
function _new(fn, ...arg) {
const obj = Object.create(fn.prototype);
const ret = fn.apply(obj, arg);
return ret instanceof Object ? ret : obj;
}
四、Vue
vue生命周期
什么是Vue生命周期?
Vue 实例从创建到销毁的过程,就是生命周期。也就是从开始创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、卸载等一系列过程,我们称这是 Vue 的生命周期
Vue生命周期的作用是什么?
它的生命周期中有多个事件钩子,让我们在控制整个Vue实例的过程时更容易形成好的逻辑
Vue生命周期总共有几个阶段?
它可以总共分为8个阶段:创建前/后, 载入前/后,更新前/后,销毁前/销毁后
第一次页面加载会触发哪几个钩子?
第一次页面加载时会触发 beforeCreate, created, beforeMount, mounted 这几个钩子
DOM渲染在哪个周期中就已经完成?
DOM 渲染在 mounted 中就已经完成了
每个生命周期适合哪些场景?
简化版本如下:
生命周期钩子的一些使用方法:
- beforecreate : 可以在这加个loading事件,在加载实例时触发
- created : 初始化完成时的事件写在这里,如在这结束loading事件,异步请求也适宜在这里调用
- mounted : 挂载元素,获取到DOM节点
- updated : 如果对数据统一处理,在这里写上相应函数
- beforeDestroy : 可以做一个确认停止事件的确认框
- nextTick : 更新数据后立即操作dom
v-show与v-if区别
v-show是css切换,类似于display: none;
display: block;
切换。
v-if是完整的销毁和重新创建
使用 频繁切换时用v-show,运行时较少改变时用v-if
v-if=‘false’ v-if是条件渲染,当false的时候不会渲染
Vue开发中常用的指令有哪些
v-model :一般用在表达输入,很轻松的实现表单控件和数据的双向绑定
v-html: 更新元素的 innerHTML
v-show 与 v-if: 条件渲染, 注意二者区别
使用了v-if的时候,如果值为false,那么页面将不会有这个html标签生成
v-show则是不管值为true还是false,html元素都会存在,只是CSS中的display显示或隐藏
v-on : click: 可以简写为@click,@绑定一个事件。如果事件触发了,就可以指定事件的处理函数
v-for:基于源数据多次渲染元素或模板块
v-bind: 当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM
语法:v-bind:title="msg" 简写::title="msg"
绑定class的数组用法
//对象方法
v-bind:class="{'orange': isRipe, 'green': isNotRipe}"
// 数组方法
v-bind:class="[class1, class2]"
// 行内
v-bind:style="{color: color, fontSize: fontSize+'px' }"
路由跳转方式
1,<router-link to='/home/' + item.name>{
{
item.name}}</router-link> router-link标签会渲染为<a>标签,咋填template中的跳转都是这种;
2,另一种是编程是导航 也就是通过js跳转 比如 router.push('/home')
Vue全家桶
Vue全家桶:Vue + Vue-router + Vuex+axios
Vue-router:路由
Vuex:状态管理
axios:网络请求
Vue中的MVVM思想
Vue.js是一套构建用户界面的MVVM框架,model-view-viewModel,他的设计思想就是关注Model的变化,通过viewModel自动去更新DOM的状态,也就是Vue的一大特点:数据驱动。
详情请戳:https://blog.csdn.net/a1056244734/article/details/106938104
Vuex的五个核心属性
Vuex是什么?
VueX 是一个专门为 Vue.js 应用设计的状态管理架构,统一管理和维护各个vue组件的可变化状态(你可以理解成 vue 组件里的某些 data )。
vuex 就是一个仓库,仓库里放了很多对象。其中 state 就是数据源存放地,对应于一般 vue 对象里面的 data
state 里面存放的数据是响应式的,vue 组件从 store 读取数据,若是 store 中的数据发生改变,依赖这相数据的组件也会发生更新
它通过 mapState 把全局的 state 和 getters 映射到当前组件的 computed 计算属性
Vue有五个核心概念,state, getters, mutations, actions, modules。
总结
state => Vuex 使用单一状态树,即每个应用将仅仅包含一个store 实例,但单一状态树和模块化并不冲突。存放的数据状态,不可以直接修改里面的数据
getters => 从基本数据派生的数据
mutations => 提交更改数据的方法,同步!
actions => 像一个装饰器,包裹mutations,使之可以异步。
modules => 模块化Vuex
总结
vuex 一般用于中大型 web 单页应用中对应用的状态进行管理,对于一些组件间关系较为简单的小型应用,使用 vuex 的必要性不是很大,因为完全可以用组件 prop 属性或者事件来完成父子组件之间的通信,vuex 更多地用于解决跨组件通信以及作为数据中心集中式存储数据
vue双向绑定(v-model)的原理
vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的。
vue 项目中主要使用 v-model 指令在表单 input、textarea、select 等元素上创建双向数据绑定,我们知道 v-model 本质上不过是语法糖,v-model 在内部为不同的输入元素使用不同的属性并抛出不同的事件:
- text 和 textarea 元素使用 value 属性和 input 事件;
- checkbox 和 radio 使用 checked 属性和 change 事件;
- select 字段将 value 作为 prop 并将 change 作为事件;
以 input 表单元素为例:
<input v-model='name'>
相当于
<input v-bind:value="name" v-on:input="name = $event.target.value">
如果在自定义组件中,v-model 默认会利用名为 value 的 prop 和名为 input 的事件,如下所示:
父组件:
<ModelChild v-model="message"></ModelChild>
子组件:
<div>{
{value}}</div>
props:{
value: String
},
methods: {
test1(){
this.$emit('input', '小红')
},
},
Vue 请求数据方式
Vue 请求数据方式有:vue-resource
、axios
、fetchJsonp
三种。其中,vue-resource
是 Vue
官方提供的插件,axios
与 fetchJsonp
是第三方插件。
一、vue-resource 请求数据,目前已废弃
npm 安装 vue-resource
npm install vue-resource --save
说明:使用 --save
是为了在 package.json
中引用,表示在生产环境中使用。如果要将代码打包发送他人、上传到github,或者要发布代码时,package.json
就是安装所需要的包。那么,当在开发环境中时,使用 --save-dev
;当在生产环境中,使用 --save
。
在 main.js 即入口文件中引入 vue-resource;
import VueResource from 'vue-resource';
引入后,需使用;
Vue.use(VueResource);
在代码中使用:
{
// GET /someUrl
this.$http.get('/someUrl').then(response => {
// get body data
this.someData = response.body;
}, response => {
// error callback
});
}
注意:vue-resource
的请求都是继承promise
的。promise
是属于异步请求;.then
箭头函数里的this代表的是上下文。如果想获取外面的数据,请在在函数外声明,var that = this
;将外层的this先储存到that中再引入函数内部。
vue-resource 参考文档:https://github.com/pagekit/vue-resource/blob/develop/docs/http.md
二、 axios 请求数据
axios 参考文档:https://github.com/axios/axios
三、fetchJsonp 请求数据
参考文档: https://github.com/camsong/fetch-jsonp
computed和watch有什么区别?
computed:
1. computed是计算属性,也就是计算值,它更多用于计算值的场景
2. computed具有缓存性,computed的值在getter执行后是会缓存的,只有在它依赖的属性值改变之后,下一次获取computed的值时才会重新调用对应的getter来计算
3. computed适用于计算比较消耗性能的计算场景
watch:
1. 更多的是「观察」的作用,类似于某些数据的监听回调,用于观察props $emit或者本组件的值,当数据变化时来执行回调进行后续操作
2. 无缓存性,页面重新渲染时值不变化也会执行
小结:
1. 当我们要进行数值计算,而且依赖于其他数据,那么把这个数据设计为computed
2. 如果你需要在某个数据变化时做一些事情,使用watch来观察这个数据变化
写 React / Vue 项目时为什么要在列表组件中写 key,其作用是什么?(滴滴、饿了么)
key是为Vue中的vnode标记的唯一id,通过这个key,我们的diff操作可以 更准确、更快速
准确:
如果不加key,那么vue会选择复用节点(Vue的就地更新策略),导致之前节点的状态被保留下来,会产生一系列的bug
快速:
利用key的唯一性生成map对象来获取对应节点,比遍历方式更快。
组件中的data为什么是函数?
为什么组件中的data必须是一个函数,然后return一个对象,而new Vue实例里,data可以直接是一个对象?
// data
data() {
return {
message: "子组件",
childName:this.name
}
}
// new Vue
new Vue({
el: '#app',
router,
template: '<App/>',
components: {
App}
})
因为组件是用来复用的,JS里对象是引用关系,这样作用域没有隔离,而new Vue的实例,是不会被复用的,因此不存在引用对象问题。
Class 与 Style 如何动态绑定?
Class 可以通过对象语法和数组语法进行动态绑定:
对象语法
<div v-bind:class="{ active: isActive, 'disable': disabled }"></div>
data: {
isActive: true,
disabled: false
}
数组语法
<div v-bind:class="[isActive ? activeClass : '', errorClass]"></div>
data: {
activeClass: 'active',
errorClass: 'text-danger'
}
Style 也可以通过对象语法和数组语法进行动态绑定:
对象语法
<div v-bind:style="{
color: activeColor, fontSize: fontSize + 'px' }"></div>
data: {
activeColor: 'red',
fontSize: 30
}
数组语法
<div v-bind:style="[styleColor, styleSize]"></div>
data: {
styleColor: {
color: 'red'
},
styleSize:{
fontSize:'23px'
}
}
vue的单项数据流
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解
额外的,每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。子组件想修改时,只能通过 $emit 派发一个自定义事件,父组件接收到后,由父组件修改
有两种常见的试图改变一个 prop 的情形 :
- 这个 prop 用来传递一个初始值;这个子组件接下来希望将其作为一个本地的 prop 数据来使用
在这种情况下,最好定义一个本地的 data 属性并将这个 prop 用作其初始值:
props: ['initialCounter'],
data: function () {
return {
counter: this.initialCounter
}
}
- 这个 prop 以一种原始的值传入且需要进行转换
在这种情况下,最好使用这个 prop 的值来定义一个计算属性
props: ['size'],
computed: {
normalizedSize: function () {
return this.size.trim().toLowerCase()
}
}
keep-alive
keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,避免重新渲染 ,用法也很简单:
<keep-alive>
<component>
<!-- 该组件将被缓存! -->
</component>
</keep-alive>
props
- include - 字符串或正则表达,只有匹配的组件会被缓存
- exclude - 字符串或正则表达式,任何匹配的组件都不会被缓存
注意:其中 exclude 的优先级比 include 高
// 组件 a
export default {
name: 'a',
data () {
return {
}
}
}
<keep-alive include="a">
<component>
<!-- name 为 a 的组件将被缓存! -->
</component>
</keep-alive>可以保留它的状态或避免重新渲染
<keep-alive exclude="a">
<component>
<!-- 除了 name 为 a 的组件都将被缓存! -->
</component>
</keep-alive>可以保留它的状态或避免重新渲染
钩子函数
- activated:当组件被激活时触发
- deactivated:当组件被移除时触发
nextTick()
在下次DOM更新循环结束之后执行延迟回调。在修改数据之后,立即使用的这个回调函数,获取更新后的DOM。
一、示例
先来一个示例了解下关于Vue中的DOM更新以及nextTick的作用。
模板
<div class="app">
<div ref="msgDiv">{
{
msg}}</div>
<div v-if="msg1">Message got outside $nextTick: {
{
msg1}}</div>
<div v-if="msg2">Message got inside $nextTick: {
{
msg2}}</div>
<div v-if="msg3">Message got outside $nextTick: {
{
msg3}}</div>
<button @click="changeMsg">
Change the Message
</button>
</div>
Vue实例
new Vue({
el: '.app',
data: {
msg: 'Hello Vue.',
msg1: '',
msg2: '',
msg3: ''
},
methods: {
changeMsg() {
this.msg = "Hello world."
this.msg1 = this.$refs.msgDiv.innerHTML
this.$nextTick(() => {
this.msg2 = this.$refs.msgDiv.innerHTML
})
this.msg3 = this.$refs.msgDiv.innerHTML
}
}
})
点击前
点击后
从图中可以得知:msg1和msg3显示的内容还是变换之前的,而msg2显示的内容是变换之后的。其根本原因是因为Vue中DOM更新是异步的。
二、应用场景
下面了解下nextTick的
主要应用的场景及原因。
- 在Vue生命周期的
created()
钩子函数进行的DOM操作一定要放在Vue.nextTick()
的回调函数中
在created()
钩子函数执行的时候DOM 其实并未进行任何渲染,而此时进行DOM操作无异于徒劳,所以此处一定要将DOM操作的js代码放进Vue.nextTick()
的回调函数中。与之对应的就是mounted()
钩子函数,因为该钩子函数执行时所有的DOM挂载和渲染都已完成,此时在该钩子函数中进行任何DOM操作都不会有问题 。
- 在数据变化后要执行的某个操作,而这个操作需要使用随数据改变而改变的DOM结构的时候,这个操作都应该放进Vue.nextTick()的回调函数中。
具体原因在Vue的官方文档中详细解释:
Vue 异步执行 DOM 更新。只要观察到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作上非常重要。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部尝试对异步队列使用原生的 Promise.then 和MessageChannel,如果执行环境不支持,会采用 setTimeout(fn, 0)代替。
例如,当你设置vm.someData = ‘new value’,该组件不会立即重新渲染。当刷新队列时,组件会在事件循环队列清空时的下一个“tick”更新。多数情况我们不需要关心这个过程,但是如果你想在 DOM 状态更新后做点什么,这就可能会有些棘手。虽然 Vue.js 通常鼓励开发人员沿着“数据驱动”的方式思考,避免直接接触 DOM,但是有时我们确实要这么做。为了在数据变化之后等待 Vue 完成更新 DOM ,可以在数据变化之后立即使用Vue.nextTick(callback) 。这样回调函数在 DOM 更新完成后就会调用。
Vue插槽
详细请戳:Vue插槽详解
单个插槽
当子组件模板只有一个没有属性的插槽时,
父组件传入的整个内容片段将插入到插槽所在的 DOM 位置,
并替换掉插槽标签本身
命名插槽
solt元素可以用一个特殊的特性name来进一步配置如何分发内容。
多个插槽可以有不同的名字。 这样可以将父组件模板中 slot 位置,
和子组件 slot 元素产生关联,便于插槽内容对应传递
作用域插槽
可以访问组件内部数据的可复用插槽(reusable slot)
在父级中,具有特殊特性 slot-scope 的<template> 元素必须存在,
表示它是作用域插槽的模板。slot-scope 的值将被用作一个临时变量名,
此变量接收从子组件传递过来的 prop 对象
你有对 Vue 项目进行哪些优化?
代码层面的优化
- v-if 和 v-show 区分使用场景
- computed 和 watch 区分使用场景
- v-for 遍历必须为 item 添加 key,且避免同时使用 v-if
- 长列表性能优化
- 事件的销毁
- 图片资源懒加载
- 路由懒加载
- 第三方插件的按需引入
- 优化无限列表性能
- 服务端渲染 SSR or 预渲染
Webpack 层面的优化
- Webpack 对图片进行压缩
- 减少 ES6 转为 ES5 的冗余代码
- 提取公共代码
- 模板预编译
- 提取组件的 CSS
- 优化 SourceMap
- 构建结果输出分析
- Vue 项目的编译优化
基础的 Web 技术的优化
- 开启 gzip 压缩
- 浏览器缓存
- CDN 的使用
- 使用 Chrome Performance 查找性能瓶颈
五、ES6
var、let、const之间的区别
let
和const
不存在变量提升机制
创建变量的六种方式中:var/function
有变量提升,而let/const/class/import
都不存在这个机制var
允许重复声明,而let
不允许重复声明- let能解决typeof检测时出现的暂时性死区问题(let比var更严谨)
- let创建的全局变量没有给window设置对应的属性
- let会产生块级作用域
其中let和const的区别
- const声明之后必须赋值,否则会报错
- const定义不可变的量,改变了就会报错
详情戳:JavaScript中let和var区别详解
解构赋值
1. 数组解构赋值:
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。
let arr = ['this is a string', 2, 3];
//传统方式
let a = arr[0],
b = arr[1],
c = arr[2];
//解构赋值,是不是简洁很多?
let [a, b, c] = arr
console.log(a);//this is a string
console.log(b);//2
console.log(c);//3
2 对象解构赋值
解构不仅可以用于数组,还可以用于对象。
let {
foo, bar } = {
foo: 'aaa', bar: 'bbb' }
foo // "aaa"
bar // "bbb"
let obj = {
name: 'chris',
sex: 'male',
age: 26,
son: {
sonname: '大熊',
sonsex: 'male',
sonage: 1
}
}
const {
name, sex, age, son} = obj
console.log(name + ' ' + sex + ' ' + age) //chris male 26
console.log(son) // { sonname: '大熊', sonsex: 'male', sonage: 1 }
扩展运算符
扩展运算符(spread运算符)用三个点号(…)表示,功能是把数组或类数组对象展开成一系列用逗号隔开的值。
let foo = function(a, b, c) {
console.log(a);
console.log(b);
console.log(c);
}
let arr = [1, 2, 3];
//传统写法
foo(arr[0], arr[1], arr[2]);
//使用扩展运算符
foo(...arr);
//1
//2
//3
rest运算符
rest运算符也是三个点号,不过其功能与扩展运算符恰好相反,把逗号隔开的值序列组合成一个数组。
//主要用于不定参数,所以ES6开始可以不再使用arguments对象
var bar = function(...args) {
for (let el of args) {
console.log(el);
}
}
bar(1, 2, 3, 4);
//1
//2
//3
//4
bar = function(a, ...args) {
console.log(a);
console.log(args);
}
bar(1, 2, 3, 4);
//1
//[ 2, 3, 4 ]
详情请戳:ES6中解构、扩展运算符和rest运算符
使用箭头函数应注意什么?
- 用了箭头函数,this就不是指向window,而是父级(指向是可变的)
- 不能够使用arguments对象
- 不能用作构造函数,这就是说不能够使用new命令,否则会抛出一个错误
- 不可以使用yield命令,因此箭头函数不能用作 Generator 函数
Set、Map、weakSet、weakMap的区别?
应用场景Set用于数据重组,Map用于数据储存
Set:
- 成员不能重复
- 只有键值没有键名,类似数组
- 可以遍历,方法有add, delete, has, clear,加入值的时候,不会发生类型转换。
weakSet:
- 成员都是对象
2 成员都是弱引用,随时可以消失。 可以用来保存DOM节点,不容易造成内存泄漏 - 不能遍历,方法有add, delete,has
Map:
- 类似于对象,也是键值对的集合,但是“键”的范围不限于字符串。
- 可以遍历,Map 结构原生提供三个遍历器生成函数和一个遍历方法:keys,values, entries, forEach。
weakMap:
- 只接受对象作为健名(null除外),不接受其他类型的值作为健名
- 健名所指向的对象,不计入垃圾回收机制
- 不能遍历,方法同get,set,has,delete
promise对象的用法,手写一个promise
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。
const promise = new Promise(function(resolve, reject) {
if (/* 异步操作成功 */) {
resolve(value)
} else {
reject(error)
}
})
promise.then(function (value) {
// success
}, function (error) {
/// failure
})
ES5/ES6 的继承除了写法以外还有什么区别?
class Super {
}
class Sub extends Super {
}
const sub = new Sub();
Sub.__proto__ === Super;
子类可以直接通过 proto 寻址到父类。
function Super() {
}
function Sub() {
}
Sub.prototype = new Super();
Sub.prototype.constructor = Sub;
var sub = new Sub();
Sub.__proto__ === Function.prototype;
而通过 ES5 的方式,Sub.__proto__ === Function.prototype
六、Git
git常用的命令
- 从远程库克隆到本地:git clone 网站上的仓库地址
- 新增文件,放到本地缓存:git add .
- 提交文件:git commit –m或者git commit –a
- 查看工作区状况:git status -s
- 临时保存和恢复修改,可跨分支:git stash
- 拉取合并远程分支的操作:git fetch/git merge或者git pull
- 查看提交记录命令:git reflog
更多戳:git基本指令使用,git status,git stash,git merge
七、Webpack
webpack打包原理
WebPack可以看做是模块打包机:它做的事情是,分析你的项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(Less、Sass、TypeScript
等),并将其转换和打包为合适的格式供浏览器使用。
当 Webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。
Webpack的工作方式是:把你的项目当做一个整体,通过一个给定的主文件(如:index.js),Webpack将从这个文件开始找到你的项目的所有依赖文件,使用loaders处理它们,最后打包为一个(或多个)浏览器可识别的JavaScript文件。
webpack的优点
- 引入
happypack
,实现webpack多线程打包,显著提高本地打包速度。 - 引入
webpack DllReferencePlugin
,提前打包公共代码(polyfill和vue全家桶),提高构建速度。 - 支持less、sass预编译,支持postcss配置,自动引入polyfill(可删除)。
- 手动搭建webpack脚手架,脱离官方vue-cli,可根据实际需求自定义调整webpack配置。
- 一经运行,即可同时运行客户端和服务端,并针对开发环境(dev)和生产环境(prod)做区分。dev环境使用
webpack devServer
中的express插槽,prod环境使用express static
映射前端静态文件。
webpack的缺点
webpack的缺点是只能用于采用模块化开发的项目
八、网络协议
网络分层
目前网络分层可分为两种:OSI 模型和 TCP/IP 模型
OSI模型
应用层(Application)
表示层(Presentation)
会话层(Session)
传输层(Transport)
网络层(Network)
数据链路层(Data Link)
物理层(Physical)
TCP/IP模型
应用层(Application)
传输层(Host-to-Host Transport)
互联网层(Internet)
网络接口层(Network Interface)
HTTP/HTTPS比较
1、https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用
2、http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议
3、http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443
4、http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。
HTTP的状态码
状态码 | 类别 | 描述 |
---|---|---|
1xx | Informational(信息状态码) | 接受请求正在处理 |
2xx | Success(成功状态码) | 请求正常处理完毕 |
3xx | Redirection(重定向状态码) | 需要附加操作已完成请求 |
4xx | Client Error(客户端错误状态码) | 服务器无法处理请求 |
5xx | Server Error(服务器错误状态码) | 服务器处理请求出错 |
- 常用的HTTP状态码列表
状态码 | 状态码英文名称 | 中文描述 |
---|---|---|
200 | OK | 请求成功 。一般用于GET与POST请求 |
204 | No Content | 无内容。服务器成功处理,但未返回内容 。在未更新网页的情况下,可确保浏览器继续显示当前文档 |
301 | Moved Permanently | 永久性重定向 。请求的资源已被永久的移动到新URI,返回信息会包括新的URI,浏览器会自动定向到新URI。今后任何新的请求都应使用新的URI代替 |
302 | Found | 临时性重定向 。与301类似。但资源只是临时被移动。客户端应继续使用原有URI |
303 | See Other | 查看其它地址 。与302类似。使用GET请求查看 |
304 | Not Modified | 未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。客户端通常会缓存访问过的资源,通过提供一个头信息指出客户端希望只返回在指定日期之后修改的资源 |
400 | Bad Request | 客户端请求报文中存在语法错误,服务器无法理解 。浏览器会像200 OK一样对待该状态吗 |
401 | Unauthorized | 请求要求用户的身份认证 ,通过HTTP认证(BASIC认证,DIGEST认证)的认证信息,若之前已进行过一次请求,则表示用户认证失败 |
403 | Forbidden | 服务器理解请求客户端的请求,但是拒绝执行此请求 |
404 | Not Found | 服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置"您所请求的资源无法找到"的个性页面。也可以在服务器拒绝请求且不想说明理由时使用 |
500 | Internal Server Error | 服务器内部错误,无法完成请求 ,也可能是web应用存在bug或某些临时故障 |
503 | Service Unavailable | 由于超载或系统维护,服务器暂时的无法处理客户端的请求 。延时的长度可包含在服务器的Retry-After头信息中 |
get请求传参长度的误区
误区:我们经常说get请求参数的大小存在限制,而post请求的参数大小是无限制的
实际上HTTP 协议从未规定 GET/POST 的请求长度限制是多少。对get请求参数的限制是来源与浏览器或web服务器,浏览器或web服务器限制了url的长度。为了明确这个概念,我们必须再次强调下面几点:
- HTTP 协议 未规定 GET 和POST的长度限制
- GET的最大长度显示是因为 浏览器和 web服务器限制了 URI的长度
- 不同的浏览器和WEB服务器,限制的最大长度不一样
- 要支持IE,则最大长度为2083byte,若只支持Chrome,则最大长度 8182byte
为什么用axios,不用ajax?
为什么要用axios?
axios 是一个基于Promise 用于浏览器和 nodejs 的 HTTP 客户端,它本身具有以下特征:
- 从浏览器中创建 XMLHttpRequest
- 从 node.js 发出 http 请求
- 支持 Promise API
- 拦截请求和响应
- 转换请求和响应数据
- 取消请求
- 自动转换JSON数据
- 客户端支持防止CSRF/XSRF
1. axios:
- 从 node.js 创建 http 请求
- 支持 Promise API
- 客户端支持防止CSRF
- 提供了一些并发请求的接口(重要,方便了很多的操作)
2. jQuery ajax:
- 本身是针对MVC的编程,不符合现在前端MVVM
- 基于原生的XHR开发,XHR本身的架构不清晰,已经有了fetch的替代方案
- JQuery整个项目太大,单纯使用ajax却要引入整个JQuery非常的不合理(采取个性化打包的方案又不能享受CDN服务)
简单讲解http2 的多路复用
在 HTTP/1 中,每次请求都会建立一次HTTP连接,也就是我们常说的3次握手4次挥手,这个过程在一次请求过程中占用了相当长的时间,即使开启了 Keep-Alive ,解决了多次连接的问题,但是依然有两个效率上的问题:
- 第一个:串行的文件传输。当请求a文件时,b文件只能等待,等待a连接到服务器、服务器处理文件、服务器返回文件,这三个步骤。我们假设这三步用时都是1秒,那么a文件用时为3秒,b文件传输完成用时为6秒,依此类推。(注:此项计算有一个前提条件,就是浏览器和服务器是单通道传输)
- 第二个:连接数过多。我们假设Apache设置了最大并发数为300,因为浏览器限制,浏览器发起的最大请求数为6,也就是服务器能承载的最高并发为50,当第51个人访问时,就需要等待前面某个请求处理完成。
HTTP/2的多路复用就是为了解决上述的两个性能问题。
在 HTTP/2 中,有两个非常重要的概念,分别是帧(frame)和流(stream)。
帧代表着最小的数据单位,每个帧会标识出该帧属于哪个流,流也就是多个帧组成的数据流。
多路复用,就是在一个 TCP 连接中可以存在多条流。换句话说,也就是可以发送多个请求,对端可以通过帧中的标识知道属于哪个请求。通过这个技术,可以避免 HTTP 旧版本中的队头阻塞问题,极大的提高传输性能。
九、性能优化
如何优化页面,加快页面的加载速度
(1)优化图片格式和大小;
(2)服务端开启gzip;
(3)使用浏览器缓存;
(4)减少重定向请求;
(5)使用CDN存储静态资源;
(6)减少DNS查询次数;
(7)压缩css和js内容;
(9) 后端语法优化;
(10)换个好点的服务器
HTML优化
1、避免 HTML 中书写 CSS 代码,因为这样难以维护。
2、使用 Viewport 加速页面的渲染。
3、使用语义化标签,减少 CSS 代码,增加可读性和 SEO。
4、减少标签的使用,DOM 解析是一个大量遍历的过程,减少不必要的标签,能降低遍历的次数。
5、避免 src、href 等的值为空,因为即时它们为空,浏览器也会发起 HTTP 请求。
6、减少 DNS 查询的次数
CSS优化
1、优化选择器路径:使用 .c {
} 而不是 .a .b .c {
}。
2、选择器合并:共同的属性内容提起出来,压缩空间和资源开销。
3、精准样式:使用 padding-left: 10px 而不是 padding: 0 0 0 10px。
4、雪碧图:将小的图标合并到一张图中,这样所有的图片只需要请求一次。
5、避免通配符:.a .b * {
} 这样的选择器,根据从右到左的解析顺序在解析过程中遇到通配符 * {
} 会遍历整个 DOM,性能大大损耗。
6、少用 float:float 在渲染时计算量比较大,可以使用 flex 布局。
7、为 0 值去单位:增加兼容性。
8、压缩文件大小,减少资源下载负担。
JavaScript优化
1、尽可能把 <script> 标签放在 body 之后,避免 JS 的执行卡住 DOM 的渲染,最大程度保证页面尽快地展示出来
2、尽可能合并 JS 代码:提取公共方法,进行面向对象设计等……
3、CSS 能做的事情,尽量不用 JS 来做,毕竟 JS 的解析执行比较粗暴,而 CSS 效率更高。
4、尽可能逐条操作 DOM,并预定好 CSS 样式,从而减少 reflow 或者 repaint 的次数。
5、尽可能少地创建 DOM,而是在 HTML 和 CSS 中使用 display: none 来隐藏,按需显示。
6、压缩文件大小,减少资源下载负担。
评测你写的前端代码性能和效率?
- Chtome DevTools 的
Performance
:排查应用性能的最佳工具。 - Chtome DevTools 的
Audits
:对页面性能进行检测,根据测试结果进行优化。 - 第三方工具:
Yslow
。
十、算法
编写一个程序将数组扁平化去并除其中重复部分数据,最终得到一个升序且不重复的数组
已知如下数组:
var arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10];
公司:携程
解法一:
const result = Array.from(new Set(arr1.flat(Infinity))).sort((a, b) => {
return a - b
})
console.log(result)
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
知识点:
Array.prototype.flat()
用于将嵌套的数组“拉平”,变成一维的数组。该方法返回一个新数组,对原数据没有影响。
[1, 2, [3, 4]].flat()
// [1, 2, 3, 4]
https://es6.ruanyifeng.com/#docs/array#数组实例的-flat,flatMap
new Set(arr)
可用于数组去重
数组去重的几个方法
解法2:
function flatten(arr) {
while (arr.some(item => Array.isArray(item))) {
arr = [].concat(...arr);
}
return arr;
}
Array.from(new Set(flatten(arr))).sort((a, b) => a - b)
解法3:
Array.from(new Set(arr.toString().split(","))).sort((a,b)=>{
return a-b}).map(Number)
十一、其他
Chrome 浏览器进程
在资源不足的设备上,将服务合并到浏览器进程中
浏览器主进程
- 负责浏览器界面显示
- 各个页面的管理,创建以及销毁
- 将渲染进程的结果绘制到用户界面上
- 网络资源管理
GPU 进程
- 用于 3D 渲染绘制
网络进程
- 发起网络请求
插件进程
- 第三方插件处理,运行在沙箱中
渲染进程
- 页面渲染
- 脚本执行
- 事件处理
常问
1、自我介绍
2、你的项目中技术难点是什么?遇到了什么问题?你是怎么解决的?
3、你认为哪个项目做得最好?
4、平时是如何学习前端开发的?
5、你最有成就感的一件事?
6、你是怎么学习前端的?
人事面
1、面试完你还有什么问题要问的吗?
2、你有什么爱好?
3、你最大的优点和缺点是什么?
4、你为什么会选择这个行业,职位?
5、你觉得你适合从事这个岗位吗?
6、你有什么职业规划?
7、你对工资有什么要求?
8、如何看待前端开发?
9、未来三到五年的规划是怎样的?
LAST
什么样的前端代码是好的?
高复用低耦合,这样文件小,好维护,而且好扩展
对前端工程师这个职位是怎么样理解的?它的前景会怎么样?
前端是最贴近用户的程序员,比后端、数据库、产品经理、运营、安全都近
- 实现界面交互
- 提升用户体验
- 有了Node.js,前端可以实现服务端的一些事情
前端是最贴近用户的程序员,前端的能力就是能让产品从 90分进化到 100 分,甚至更好,与团队成员,UI设计,产品经理的沟通;做好的页面结构,页面重构和用户体验;
你觉得前端工程的价值体现在哪?
- 为简化用户使用提供技术支持(交互部分)
- 为多个浏览器兼容性提供支持
- 为提高用户浏览速度(浏览器性能)提供支持
- 为跨平台或者其他基于webkit或其他渲染引擎的应用提供支持
- 为展示数据提供支持(数据接口)
平时如何管理你的项目?
- 先期团队必须确定好全局样式(globe.css),编码模式(utf-8) 等;
- 编写习惯必须一致(例如都是采用继承式的写法,单样式都写成一行);
- 标注样式编写人,各模块都及时标注(标注关键样式调用的地方);
- 页面进行标注(例如 页面 模块 开始和结束);
- CSS跟HTML 分文件夹并行存放,命名都得统一(例如style.css);
- JS 分文件夹存放 命名以该JS功能为准的英文翻译。
- 图片采用整合的 images.png png8 格式文件使用 - 尽量整合在一起使用方便将来的管理