20-前端面试大全

html

*HTML5或css新特性

  1. 新语义化标签:header、footer、section、nav、aside、article
  2. 画布Canvas绘图
  3. 音频、视频API(audio,video)
  4. WebStorage(localStorage和sessionStorage)
  5. 表单增强(元素、属性placehoder)
  6. WebSocket:单个 TCP 连接上进行全双工通讯的协议
  7. WebWorker
  8. 地图地理定位
  9. 拖拽

HTML5语义化标签的理解

标签元素本身包含标签内部填写内容的部分信息,特定的标签做特定的事。例如解析< h1></ h1>标签时,就会标识为最重要的标题。

h5新增:header、nav、footer、article、section、aside

语义化的优点:

  • 为了在没有CSS的情况下,页面也能呈现出很好地内容结构、代码结构
  • 比< div>标签有更加丰富的含义,方便开发与维护
  • 方便搜索引擎能识别页面结构,有利于SEO
  • 方便其他设备解析(如移动设备、盲人阅读器等)
  • 有利于合作,遵守W3C标准

说一下< label>标签的用法

label标签主要是方便鼠标点击使用,扩大可点击的范围,增强用户操作体验

遍历A节点的父节点下的所有子节点

<script>
    var b=document.getElementById("a").parentNode.children;
    console.log(b)
</script>

css

*CSS 选择符有哪些?

  • id选择器(#id)
  • 类选择器(.class)
  • 标签选择器(div,h1,p)
  • 相邻选择器(h1 + p)
  • 子选择器(ul > li)
  • 后代选择器(li a)
  • 通配符选择器( * )
  • 属性选择器(a[title])
  • 伪类选择器(a:hover,li:nth-child)

*优先级算法如何计算?

  1. 优先级就近原则,同权重情况下样式定义最近者为准;
  2. 载入样式以最后载入的定位为准;
  3. !important > id > class > tag;
  4. important 比 内联优先级高,但内联比id要高;
  5. !important > 内联 style > ID 选择器 > 类选择器 > 标签选择器 > 通配符选择器

*引入样式link和@import的区别

  1. link是HTML标签,@import是css提供的
  2. link引入的样式页面加载时同时加载,@import引入的样式需等页面加载完后再加载
  3. link没有兼容性问题,@import不兼容ie5以下
  4. link可以通过js操作DOM动态引入样式表改变样式,而@import不可以
  5. 总体来说:link 优于@import

*flex和flex:1

flex属性是flex-grow, flex-shrinkflex-basis, 默认值为0 1 auto。后两个属性可选。

  1. flex-grow属性定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大。
  2. flex-shrink属性定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小。
  3. flex-basis属性定义了在分配多余空间之前,项目占据的主轴空间(main size)。浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为auto,即项目的本来大小。
  • flex:1相当于 flex-grow:1, flex-shrink:1flex-basis:0% (子元素宽度 = 父级宽度/3)
  • flex:auto相当于flex-grow:1, flex-shrink:1flex-basis:auto (子元素宽度 = 子元素宽度 +(父级宽度-子元素宽度之和/3)
  • flex:none相当于flex-grow:0, flex-shrink:0flex-basis:auto

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Bj56MLA1-1607138983999)(C:\Users\an\AppData\Roaming\Typora\typora-user-images\image-20201202113845523.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7z5wFF7q-1607138984005)(C:\Users\an\AppData\Roaming\Typora\typora-user-images\image-20201202113856080.png)]

容器相关的属性(6个):

  • flex-direction: 设置主轴的方向 用的比较多

     row 默认值 水平向右
     row-reverse 水平向左
     column 垂直向下
     column-reverse 垂直向上
    1234
    
  • flex-wrap: 当项目足够多(前提)时,项目是否换行

     nowrap  默认值  不换行
     wrap  换行
    12
    
  • flex-flow: 是上面两个属性的复合属性

     flex-flow:flex-direction flex-wrap
     flex-flow:column wrap;
    12
    
  • justify-content(主轴): 处理主轴的富余空间(当项目的宽度没有容器的宽度大时)

     flex-start: 默认值  富余空间在主轴的结束点
     flex-end: 富余空间在主轴的起点
     center: 富余空间在主轴的两侧
     space-between: 富余空间在项目和项目之间
     space-around: 富余空间环绕在项目两侧
    12345
    
  • align-items: 处理交叉轴的富余空间(处理在交叉轴的摆放位置)

     flex-start: 默认值  富余空间在交叉轴的结束点
     flex-end: 富余空间在交叉轴的起点
     center: 富余空间在交叉轴的两侧
    123
    
  • align-content: 当有多根主轴,设置多根主轴的位置关系,当有多根主轴时,处理交叉轴上的富余空间

     flex-start: 不是默认值  有多根主轴,富余空间在交叉轴的结束点
     flex-end:  有多根主轴,富余空间在交叉轴的起点
     center: 有多根主轴,富余空间在交叉轴的两侧
     space-between: 有多根主轴,富余空间在多根主轴多间
     space-around: 有多根主轴,富余空间环绕在多根主轴之间
    12345
    

项目相关的属性(6个):

  • order: 设置项目在主轴的排列的顺序,属性值是一个数字,数字越小,在主轴上越靠前

     默认值是:0
    1
    
  • flex-grow: grow 生长的意思 用的不多

     如果有富余空间,可以让项目生长,说白了,就是让项目伸展。 
      设置生长的比例,属性值是一个数字,如下:
         .son1{ flex-grow: 1; }
         .son2{ flex-grow: 2; }
         .son3{ flex-grow: 4; }
         表示把富余空间分成7份,老大占1份,老二占2份,老三占4份
    123456
    
  • flex-shrink: shrink 压缩的意思 了解

     如果项目总宽度大于容器的宽度,设置每一个项目的压缩比例
    1
    
  • flex-basis: 设置项目在主轴上占据的大小 如果也设置了宽度,那么会心flex-basis为准

  • flex:是上面三个属性的复合属性 用的也比较多

     flex:flex-grow flex-shrink flex-basis
     默认值: flex: 0 1 auto
    auto表示:设置了宽度,auto是设置的宽度;
    		  没设置宽度,auto就是内容的宽度
    flex:最后两个属性值可以不写  
       最常见的写法:flex:1;  等价于:flex-grow:1;富裕空间均分。
    123456
    
  • align-self:单独设置某一个项目在交叉轴上的位置

     flex-start   flex-end   center   
    

*BFC(块级格式化上下文)

https://www.jianshu.com/p/0d713b32cd0d

BFC(Block formatting context)块级格式化上下文,是页面中一个独立渲染的区域,容器中的子元素在布局上不会影响到外面的元素。

BFC的作用

  1. 清除内部浮动:对子元素设置浮动后,父元素会发生高度塌陷,也就是父元素的高度变为0。解决这个问题,只需要把把父元素变成一个BFC就行了。常用的办法是给父元素设置overflow:hidden。
  2. 上下margin重合问题,可以通过触发BFC来解决

BFC 特性(功能)

  1. 使 BFC 内部浮动元素不会到处乱跑;
  2. 和浮动元素产生边界。

使 BFC 内部的浮动元素不会到处乱跑

<div class="out"><div class="in"></div></div>

.out{border:0px solid red;min-height:20px;}
.in{background:grey;height:100px;}

在正常的文档流中,块级元素是按照从上自下,内联元素从左到右的顺序排列的。

如果给里面的元素一个 float 或者绝对定位,它就会脱离普通文档流中。

此时还想让外层元素包裹住内层元素让外层元素产生一个 BFC。

和浮动元素产生边界

<div class="left"></div>
<div class="right"></div>

div{border:3px solid red;heifht:100px}
.left{min-width:200px;margin-right:20px;float:left;}
.right{border-color:blue;display:flow-root;}

我们想要让普通元素与浮动元素产生左右边距,需要将非浮动元素的 margin-left 设置为 200px+20px 。

形成 BFC 的条件

  1. 浮动元素,float 除 none 以外的值

  2. 定位元素,position(absolute,fixed)

  3. display 为以下其中之一的值 inline-block,table-cell,table-caption

  4. overflow 除了 visible 以外的值(hidden,auto,scroll)

*说一下盒模型?

盒模型的组成,由里向外content,padding,border,margin.

在标准的盒子模型中,width指content部分的宽度
在新的盒子模型中,width表示content+padding+border这三个部分的宽度

box-sizing: content-box 是W3C 旧的盒子模型
box-sizing: border-box 是IE 新的盒子模型

box-sizing的默认属性是content-box

*三栏布局,两边固定中间自适应

position布局:

在三栏的父盒子设置relative,让左右两栏使用绝对定位会脱离标准文档流,中间一栏将margin分别设置为左右两栏的宽度。

<div class="content">
    <div class="left">左侧</div>
    <div class="middle">中间</div>
    <div class="right">右侧</div>
</div>
*{margin: 0; padding: 0; border: 0;}
.content{position: relative;}
.left{position: absolute;top: 0;left: 0;width: 200px; height: 100px;background-color: blue;}
.right{position: absolute;top: 0;right: 0;width: 200px; height: 100px;background-color: red;}
.middle{margin-left: 200px;margin-right: 200px;height: 100px;background-color: yellow;}

float布局:

需要将中间的内容放在html结构的最后,否则右侧会沉在中间内容的下侧。 原理: 元素浮动后,脱离文档流,后面的元素受浮动影响,设置受影响元素的margin值即可。

<div class="content">
    <div class="left">左侧</div>
    <div class="right">右侧</div>
    <div class="middle">中间</div>
</div>
*{margin: 0; padding: 0; border: 0;}
.content{overflow: hidden;} 
.left{float:left;width: 200px; height: 100px;background-color: blue;}
.right{float:right;width: 200px; height: 100px;background-color: red;}
.middle{margin-left: 200px;margin-right: 200px;height: 100px;background-color: yellow;}

flex布局:
flex布局最简洁使用,并且没有明显缺陷。

  • 仅需将容器设置为display:flex;,盒内元素两端对其,将中间元素设置为100%宽度即可填充空白,再利用margin值设置边距即可。

  • 并且盒内元素的高度撑开容器的高度。

<div class="content">
    <div class="left">左侧</div>
    <div class="middle">中间</div>
    <div class="right">右侧</div>
</div>
*{margin: 0; padding: 0; border: 0;}
.content{display: flex;justify-content: space-between;} 
.left{width: 200px; height: 100px;background-color: blue;}
.right{width: 200px; height: 100px;background-color: red;}
.middle{width:100%;height: 100px;background-color: yellow;}

*自适应rem布局原理(如何适应不同的手机)

rem: 相对于根元素(html)的字体大小→ 1rem = html标签的font-size

浏览器默认的font-size的大小都为16px。

rem布局的原理:

通过媒体查询的方式动态改变html标签的font-size的大小

  • 当屏幕越大,让html标签的font-size变大即可
  • 当屏幕越小,让html标签的font-size变小即可

优点:rem布局盒子适配所有的屏幕,并且可以在多个屏幕大小中完美还原设计图(等比例缩放)

适应不同的手机:就是根据屏幕的大小,动态的改变html标签的font-size的大小,此时就可以配合媒体查询做到不同屏幕的适配。

为保证等比例缩放

设计图的屏幕宽度/给设计图设置的font-size = 你需要适配的屏幕宽度/你需要适配屏幕的fong-size

如:设计图750px,设计图设置字体75px,那么当适配屏360px,适配屏的字体就是36px

CSS3,transform

先说下 transform、transition、translate的区别

transform 和 transition是css的2个属性,translate属于transform里的一个方法;

transform有4个方法,分别是translate平移、rotate旋转、scale缩放、skew斜切

transform: translate(xxxpx);        // 表示水平方向移动的距离
transform: translate(xxxpx, xxxpx); // 表示水平、垂直方向移动的距离

transform:rotate(xxxdeg); //使元素旋转多少度,正数为顺时针旋转多少度, 负数为逆时针旋转多少度

transform: scale(xxx); // 表示水平和垂直同时缩放多少倍
transform: scale(xxx,xxx); // 表示水平和垂直各缩放多少倍
transform: scaleX(xxx); // x轴缩放多少倍
transform: scaleY(xxx); // y轴缩放多少倍

transform: skew(xxxdeg); // 表示水平方向倾斜多少度
transform: skew(xxxdeg, xxxdeg); // 表示水平和垂直方向各倾斜多少度
transform: skewX(xxxdeg); // x轴旋转多少度
transform: skewY(xxxdeg); // Y轴旋转多少度

transition有4个值(默认是前2个值):property(指定css属性的name)、duration(动画持续时间)、timing-function(切换动画的速度)、delay(动画执行前的延迟时间)

calc, support, media各自的含义及用法?

  • @support主要是用于检测浏览器是否支持CSS的某个属性,其实就是条件判断,如果支持某个属性,你可以写一套样式,如果不支持某个属性,你也可以提供另外一套样式作为替补。
  • calc() 函数用于动态计算长度值。 calc()函数支持 “+”, “-”, “*”, “/” 运算;
  • @media 查询,你可以针对不同的媒体类型定义不同的样式。

css水平、垂直居中的写法,请至少写出4种?

水平居中

行内元素: text-align: center
块级元素: margin: 0 auto
position:absolute +left:50%+ transform:translateX(-50%)
display:flex + justify-content: center

垂直居中

设置line-height 等于height
position:absolute +top:50%+ transform:translateY(-50%)
display:flex + align-items: center
display:table+display:table-cell + vertical-align: middle;

实现上下左右居中

/* 方式一 */
.box {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

/* 方式二 */
.box {
  position: absolute;
  width: 100px;
  height: 100px;
  top: 50%;
  left: 50%;
  margin-top: -50px;
  margin-left: -50px;
}
/* 方式三 */
.box {
  display: flex;
  justify-content: center;
  align-item: center;
}

1rem、1em、1vh、1px各自代表的含义?

  • rem 是根据根节点的 font-size 变化,em 是根据父级节点的 font-size 变化。

  • rem是全部的长度都相对于根元素元素。通常做法是给html元素设置一个字体大小,然后其他元素的长度单位就为rem。

  • 子元素字体大小的em是相对于父元素字体大小。元素的width/height/padding/margin用em的话是相对于该元素的font-size

  • 全称是 Viewport Width 和 Viewport Height,视窗的宽度和高度,相当于 屏幕宽度和高度的 1%,不过,处理宽度的时候%单位更合适,处理高度的 话 vh 单位更好。

  • px像素(Pixel)。相对长度单位。像素px是相对于显示器屏幕分辨率而言的。1920*1024 前者是屏幕宽度总共有1920个像素,后者则是高度为1024个像素

画一条0.5px的直线?

height: 1px;
transform: scale(0.5);

画一个三角形

 .a{
        width: 0;
        height: 0;
        border-width: 100px;
        border-style: solid;
        border-color: transparent #0099CC transparent transparent;
        transform: rotate(90deg); /*顺时针旋转90°*/
 }
 
<div class="a"></div>

清除浮动的几种方式,及原理?

  • clear: both
  • 创建父级 BFC(overflow:hidden)父元素触发块级格式化上下文
  • 父级设置高度

主要考察BFC,BFC (块级格式化上下文),是一个独立的渲染区域,让处于 BFC 内部的元素与外部的元素相互隔离,使内外元素的定位不会相互影响。

清除浮动常用的一般为三种.clearfix, clear:both,overflow:hidden;

clear:both:若是用在同一个容器内相邻元素上,那是贼好的

overflow:hidden:这种若是用在同个容器内,可以形成 BFC避免浮动造成的元素塌陷

触发条件:

根元素
position: absolute/fixed
display: inline-block / table
float 元素
ovevflow !== visible

比较 opacity: 0、visibility: hidden、display: none

联系:它们都能让元素不可见

结构:
display:none: 会让元素完全从渲染树中消失,渲染的时候不占据任何空间, 不能点击,
visibility: hidden:不会让元素从渲染树消失,渲染元素继续占据空间,只是内容不可见,不能点击
opacity: 0: 不会让元素从渲染树消失,渲染元素继续占据空间,只是内容不可见,可以点击

继承:
display: none:是非继承属性,子孙节点消失由于元素从渲染树消失造成,通过修改子孙节点属性无法显示。
visibility: hidden:是继承属性,子孙节点消失由于继承了hidden,通过设置visibility: visible;可以让子孙节点显式。

display,float,position 的关系

  1. 如果display为 none,那么 position 和 float 都不起作用,这种情况下元素不产生框
  2. 否则,如果 position 值为 absolute 或者 fixed,框就是绝对定位的,float 的计算值为 none,display 根据下面的表格进行调整。
  3. 否则,如果 float 不是 none,框是浮动的,display 根据下表进行调整
  4. 否则,如果元素是根元素,display 根据下表进行调整
  5. 其他情况下 display 的值为指定值 总结起来:绝对定位、浮动、根元素都需要调整display

ES6

常用特性
  • 类(class)
  • 模块化
  • 箭头函数
  • 函数参数默认值
  • 模板字符串
  • 解构赋值
  • 延展操作符
  • 对象属性简写
  • Promise
  • Let与Const

类(class)

传统的javascript中只有对象,没有类的概念。

ES6引入了Class(类)这个概念,通过class关键字可以定义类。

使JavaScript更像是一种面向对象的语言。

*注意项:*ss不存在变量提升,所以需要先定义再使用。因为ES6不会把类的声明提升到代码头部,但是ES5就不一样,ES5存在变量提升,可以先使用,然后再定义。

模块化(Module)

ES6的模块化分为导出(export)导入(import)两个模块。

export的用法

在ES6中每一个模块即是一个文件,在文件中定义的变量,函数,对象在外部是无法获取的。如果你希望外部可以读取模块当中的内容,就必须使用export来对其进行暴露(输出)。默认导出(default export) 一个模块只能有一个默认导出,如果要输出多个变量可以将这些变量包装成对象进行模块化输出:

箭头函数和this

this的指向:

  • ES5中: this 永远指向最后调用它的那个对象
  • ES6箭头函数: 箭头函数的 this 始终指向函数定义时的 this,而非执行时。

怎么改变this的指向:

  • 使用 ES6 的箭头函数
  • 在函数内部使用 _this = this
  • 使用 apply、call、bind
  • new 实例化一个对象

箭头函数:箭头函数的 this 始终指向函数定义时的 this,而非执行时

箭头函数中没有 this 绑定,必须通过查找作用域链来决定其值(箭头函数本身没有this,但是在它声明时可以捕获别人的this供自己使用。),如果箭头函数被非箭头函数包含,则 this 绑定的是最近一层非箭头函数的 this,否则,this 为 undefined

函数参数默认值

ES6支持在定义函数的时候为其设置默认值

function foo(height = 50, color = 'red') {    // ... }

模板字符串

var name = `Your name is ${first} ${last}.`

在ES6中通过${}就可以完成字符串的拼接,只需要将变量放在大括号之中。

解构赋值

可以方便的从数组或者对象中快速提取值赋给定义的变量。

延伸扩展符

//我们可以这样合并数组: 
var arr1=['a','b','c']; 
var arr2=[...arr1,'d','e']; //['a','b','c','d','e']

//用于解构赋值
let [arg1,arg2,...arg3] = [1, 2, 3, 4];
arg1 //1
arg2 //2
arg3 //['3','4']

对象属性简写

//使用ES6
const name='Ming',age='18',city='Shanghai'; 
const student = {
    name,
    age,
    city
};
console.log(student);//{name: "Ming", age: "18", city: "Shanghai"}
//对象中直接写变量,非常简洁。

promise

一句话什么是Promise:Promise对象用于异步操作,它表示一个尚未完成且预计在未来完成的异步操作。

Promise 是一个对象,它代表了一个异步操作的最终完成或者失败。由于它的then方法和catch、finally方法会返回一个新的Promise所以可以允许我们链式调用,解决了传统的回调地狱问题。

Promise 是异步编程的一种解决方案,避免回调地狱问题,比传统的异步解决方案【回调函数】和【事件】更合理、更强大。

  1. Promise的状态一经改变就不能再改变。
  2. .then.catch都会返回一个新的Promise
  3. catch不管被连接到哪里,都能捕获上层未捕捉过的错误。
  4. Promise中,返回任意一个非 promise 的值都会被包裹成 promise 对象,例如return 2会被包装为return Promise.resolve(2)
  5. Promise.then 或者 .catch 可以被调用多次, 但如果Promise内部的状态一经改变,并且有了一个值,那么后续每次调用.then或者.catch的时候都会直接拿到该值。
  6. .then 或者 .catchreturn 一个 error 对象并不会抛出错误,所以不会被后续的 .catch 捕获。
  7. .then.catch 返回的值不能是 promise 本身,否则会造成死循环。
  8. .then 或者 .catch 的参数期望是函数,传入非函数则会发生值透传。
  9. .then方法是能接收两个参数的,第一个是处理成功的函数,第二个是处理失败的函数,再某些时候你可以认为catch.then第二个参数的简便写法。
  10. .finally方法也是返回一个Promise,他在Promise结束的时候,无论结果为resolved还是rejected,都会执行里面的回调函数。
  11. Promise 构造函数是同步执行的,但.then是异步的

(1) Promise 构造函数是同步执行的,但.then是异步的

const promise = new Promise((resolve, reject) => {
  console.log(1)
  resolve()
  console.log(2)
})
promise.then(() => {
  console.log(3)
})
console.log(4)
//运行结果:1 2 4 3

解析:Promise 构造函数是同步执行的,promise.then 中的函数是异步执行的。

(2) promise 状态一旦改变则不能再变

const promise = new Promise((resolve, reject) => {
  resolve('success1')
  reject('error')
  resolve('success2')
})
promise
  .then((res) => {
    console.log('then: ', res)
  })
  .catch((err) => {
    console.log('catch: ', err)
  })
//运行结果:then: success1

解析:构造函数中的 resolve 或 reject 只有第一次执行有效,多次调用没有任何作用,promise 状态一旦改变则不能再变

(3) .then 或者 .catch 都会返回一个新的 promise

Promise.resolve(1)
  .then((res) => {
    console.log(res)
    return 2
  })
  .catch((err) => {
    return 3
  })
  .then((res) => {
    console.log(res)
  })
//运行结果:1 2

解析:promise 可以链式调用。提起链式调用我们通常会想到通过 return this 实现,不过 Promise 并不是这样实现的。promise 每次调用 .then 或者 .catch 都会返回一个新的 promise,从而实现了链式调用。

(4) .then 或者 .catch 都会返回一个新的 promise

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log('once')
    resolve('success')
  }, 1000)
})
const start = Date.now()
promise.then((res) => {
  console.log(res, Date.now() - start)
})
promise.then((res) => {
  console.log(res, Date.now() - start)
})
//运行结果:once
          success 1005
          success 1007

解析:promise 的 .then 或者 .catch 可以被调用多次,但这里 Promise 构造函数只执行一次。或者说 promise 内部状态一经改变(第一次调用.then就改变了),并且有了一个值,那么后续每次调用 .then 或者 .catch 都会直接拿到该值。

(5) .then 或者 .catch 都会返回一个新的 promise

Promise.resolve()
  .then(() => {
    return new Error('error!!!')
  })
  .then((res) => {
    console.log('then: ', res)
  })
  .catch((err) => {
    console.log('catch: ', err)
  })
//运行结果:then: Error: error!!!
    at Promise.resolve.then (...)
    at ...

解析:.then 或者 .catch 中 return 一个 error 对象并不会抛出错误,所以不会被后续的 .catch 捕获,需要改成其中一种:

1. return Promise.reject(new Error('error!!!'))
2. throw new Error('error!!!')

因为返回任意一个非 promise 的值都会被包裹成 promise 对象,即 return new Error('error!!!') 等价于 return Promise.resolve(new Error('error!!!'))

(6) .then 或 .catch 返回的值不能是 promise 本身

const promise = Promise.resolve()
  .then(() => {
    return promise
  })
promise.catch(console.error)
//运行结果:TypeError: Chaining cycle detected for promise #<Promise>

解析:.then 或 .catch 返回的值不能是 promise 本身,否则会造成死循环。

(7) .then函数返回值类型与参数传递

Promise.resolve(2) // resolve(2) 函数返回一个Promise<number>对象
.then(x=>{
   console.log( x ); // 输出2, 表示x类型为number且值为2,也就是上面resolve参数值
   return "hello world"; // 回调函数返回字符串类型,then函数返回Promise<string>
}) // then函数返回Promise<string>类型
.then(x=>{
   console.log( x ); // 输出hello world,也就是上一个then回调函数返回值,表明上一个then的返回值就是下一个then的参数
}) // then函数回调函数中没有返回值,则为Promise<void>
.then(x=>{ // 前面的then的回调函数没有返回值所以这个x是undefined
   console.log( x ); // undefined
}) // Promise<void>
.then(()=>{ // 前面没有返回值,这里回调函数可以不加返回值
   return Promise.resolve("hello world"); // 返回一个Promise<string>类型
}) // 这里then的返回值是Promise<string>
.then(x=>{ // 虽然上面的then中回调函数返回的是Promise<string>类型但是这里x并不是Promise<string>类型而是string类型
   console.log(x); // hello world
   return Promise.resolve(2); // 返回一个Promise<number>类型对象
}) // 返回Promise<number>类型

(8) .then 或者 .catch 的参数期望是函数,传入非函数则会发生值穿透。

Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .then(console.log)  //1

解析:.then 或者 .catch 的参数期望是函数,传入非函数则会发生值穿透。

(9) .catch 相当于.then的简写(省略了.then的第二个参数)

Promise.resolve()
  .then(function success (res) {
    throw new Error('error')
  }, function fail1 (e) {
    console.error('fail1: ', e)
  })
  .catch(function fail2 (e) {
    console.error('fail2: ', e)
  })
//运行结果:fail2: Error: error
          at success (...)
          at ...

解析:.then 可以接收两个参数第一个是处理成功的函数,第二个是处理错误的函数。.catch也相当于是一个.then,只不过把.then的第二个参数省略了,但是它们用法上有一点需要注意:.then 的第二个处理错误的函数捕获不了第一个处理成功的函数抛出的错误,而后续的 .catch 可以捕获之前的错误。

(10) 微任务宏任务执行顺序

process.nextTick(() => {
  console.log('nextTick')
})
Promise.resolve()
  .then(() => {
    console.log('then')
  })
setImmediate(() => {
  console.log('setImmediate')
})
console.log('end')
//运行结果:end
          nextTick
          then
          setImmediate

解析:process.nextTickpromise.then 都属于 microtask,而 setImmediate 属于 macrotask,在事件循环的 check 阶段执行。事件循环的每个阶段(macrotask)之间都会执行 microtask,事件循环的开始会先执行一次 microtask。

支持let与const

const与let定义的变量形成块级作用域,

变量的赋值可以分为三个阶段:

  • 创建变量,在内存中开辟空间
  • 初始化变量,将变量初始化为undefined
  • 真正赋值

关于letvarfunction

  • let的「创建」过程被提升了,但是初始化没有提升。
  • var的「创建」和「初始化」都被提升了。
  • function的「创建」「初始化」和「赋值」都被提升了。

async/await

在ES8中加入了对async/await的支持,也就我们所说的异步函数async其实就是 Generator的语法糖。

async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。 aysnc函数返回值为 Promiseasync函数完全可以看作多个异步操作,包装成的一个Promise 对象,而await命令就是内部then命令的语法糖。。

实现一个 sleep

每隔1秒输出 1, 2, 3, 4, 5

function sleep(interval) {
    return new Promise(resolve => {
        setTimeout(resolve, interval);
    })
}

// 用法
async function one2FiveInAsync() {
    for (let i = 1; i <= 5; i++) {
        console.log(i);
        await sleep(1000);
    }
}
one2FiveInAsync();

实现一个红绿灯

红灯2秒,黄灯1秒,绿灯3秒

function sleep(duration) {
    return new Promise(resolve => {
        setTimeout(resolve, duration);
    })
}
async function changeColor(color, duration) {
    console.log('当前颜色', color);
    await sleep(duration);
}
async function main() {
    await changeColor('红色', 2000);
    await changeColor('黄色', 1000);
    await changeColor('绿色', 3000);
}
main();

使用 async 实现 Promise.all()的效果

假设 getFoogetBar是两个用于发起ajax请求的函数。

// 写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);

// 写法二
let fooPromise = getFoo();
let barPromise = getBar();

let foo = await fooPromise;
let bar = await barPromise;

上面两种写法,getFoo 和 getBar 都是同时触发,这样就会缩短程序的执行时间。

js

*作用域,闭包,原型链,原型继承

1.作用域:变量或者函数的有效作用范围

作用域链:我们需要查找某个变量值,会先在当前作用域查找,如果找不到会往上一级查,如果找到的话,就返回停止查找,返回查找的值,这种向上查找的链条关系,叫作用域

ES6中let和const定义的变量具有块级作用域特性;var定义的变量会在自身的作用域中提升,let和const不会;js程序都具有一个全局作用域,每个函数也会有一个作用域;函数嵌套,作用域也会嵌套,形成作用域链,字可以访问父,父不可以访问子;执行函数找变量值会在作用域链中找;

2.闭包函数:闭包的实质是因为函数嵌套而形成的作用域链

闭包指的是能够访问另一个函数作用域中变量的函数。

闭包的定义即:函数 A 内部有一个函数 B,函数 B 可以访问到函数 A 中的变量,那么函数 B 就是闭包。

3.原型链:

prototype : 每个函数都会有这个属性,这里强调,是函数,普通对象是没有这个属性的(这里为什么说普通对象呢,因为JS里面,一切皆为对象,所以这里的普通对象不包括函数对象)。它是构造函数的原型对象;

proto: 每个对象都有这个属性,这里强调,是对象,同样,因为函数也是对象,所以函数也有这个属性。它指向构造函数的原型对象;

constructor : 这是原型对象上的一个指向构造函数的属性。

4.原型链继承:将父类的实例作为子类的原型。实现简单,容易理解

原型继承,将子对象的prototype指向父对象的一个实例

*简述一下原型 / 构造函数 / 实例

  • 原型(prototype): 一个简单的对象,用于实现对象的 属性继承。可以简单的理解成对象的爹。在 Firefox 和 Chrome 中,每个JavaScript对象中都包含一个__proto__(非标准)的属性指向它爹(该对象的原型),可obj.__proto__进行访问。
  • 构造函数: 可以通过new新建一个对象的函数。
  • 实例: 通过构造函数和new创建出来的对象,便是实例。 实例通过__proto__指向原型,通过constructor指向构造函数

*JS有几种数据类型,其中基本数据类型有哪些?

七种数据类型

  • Boolean
  • Null
  • Undefined
  • Number
  • String
  • Symbol (ECMAScript 6 新定义)
  • Object

(ES6之前)其中5种为基本类型:string,number,boolean,null,undefined,

ES6出来的Symbol也是原始数据类型 ,表示独一无二的值

Object为引用类型(范围挺大),也包括数组、函数,

**深拷贝和浅拷贝

浅拷贝:copy后的结果如果修改了,会影响之前的数据

深拷贝:copy后的数据与原数据具没有任何关系

  1. JSON.parse(JSON.stringify(copyObj))
    「优点」 使用简单
    「缺点」
  • 如果对象里的函数,正则,date无法被拷贝下来
  • 无法拷贝copyObj对象原型链上的属性和方法
  • 当数据的层次很深,会栈溢出

JSON.stringify()将js对象转化为json字符串

JSON.parse()将json字符串转化为对象

完整深copy

<script>
    function deepClone(target){
        // 对特殊情况的处理
        if(target == null) return null;
        if(target instanceof Date) return new Date(target);
        if(target instanceof RegExp) return new RegExp(target);
        // ....

        // 递归的出口
        if(typeof target !== "object") return target;

        let cloneTarget = new target.constructor
        for(let key in target){
            if(target.hasOwnProperty(key)){
                // target[key]  可能还是引用数据类型
                cloneTarget[key] = deepClone(target[key])
            }
        }
        return cloneTarget;
    }
    let reg = new RegExp('abc');
    let newObj = deepClone(reg)
    console.log(newObj)
</script>
// 手写深拷贝函数
function deepClone(obj){
  if(obj == null){
    return null
  }
  if(obj instanceof RegExp){
    return new RegExp(obj)
  }
  if(obj instanceof Date){
    return new Date(obj)
  }
  var objClone = Array.isArray(obj) ? [] : {}
  for(let key in obj){
    if(obj.hasOwnProperty(key)){
    //如果还是对象,就递归
      if(obj[key] && typeof obj[key] === "object"){
        objClone[key] = deepClone(obj[key])
      }else{
        objClone[key] = obj[key]
      }
    }
  }
  return objClone
}

// 测试
var dog = {
  name: {
    chineseName:"狗"
  }
}
var newDog = deepClone(dog)
newDog.name.chineseName = "新狗"
console.log("newDog",newDog)//{ name: { chineseName:"新狗"}}
console.log("dog",dog)//{ name: { chineseName:"狗"}}

*防抖节流

频繁地触发一些事件,造成浏览器的性能问题。
解决:防抖 节流 目的:限制事件的频繁触发。

防抖:在规定的时间内,只让最后一次生效,前面的不生效。**适合多次事件一次响应的情况。**触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间。

节流:,函数触发一次后,在规定时间,不会触发第二次,高频事件触发,但在n秒内只会执行一次,所以节流会稀释函数的执行频率。

// 手写防抖函数
function debounce(fn, wait){
  var timer = null;
  return function() {
    // 有定时器了,在规定时间内再触发就要清除前面的定时,重新计时
    if(timer !== null){
      clearTimeout(timer)
    }
    // 重新计时
    timer = setTimeout(fn, (wait));
  }
}

// 测试
function handle(){
  console.log(Math.random())
}
// 窗口大小改变,触发防抖,执行handle
window.addEventListener("resize",debounce(handle,1000))
// 手写节流函数
function throttle(fn, wait,...args){
  var pre = Date.now();
  return function() {
    // 函数可能会有入参
    var context = this
    var now = Date.now()
    if(now - pre >= wait){
      // 将执行函数的this指向当前作用域
      fn.apply(context,args)
      pre = Date.now()
    }
  }
}

// 测试
var name = "夏"
function handle(val){
  console.log(val+this.name)
}
// 滚动鼠标,触发防抖,执行handle
window.addEventListener("scroll", throttle(handle,1000,"龙"))

*手写call、apply、bind 方法

callapply 都是为了解决改变 this 的指向。作用都是相同的,只是传参的方式不同。

除了第一个参数外,call 可以接收一个参数列表,apply 只接受一个参数数组。

// call,改变this指向、函数立即执行
Function.prototype.myCall = function () {
  context = context || window;
  let fn = Symbol();
  context[fn] = this;
  let args = [...arguments].slice(1);
  let ret = context[fn](...args);
  delete context[fn];
  return ret;
}

// apply,改变this指向、函数立即执行
//apply和call的区别:
//仅仅是传参的区别,作用一样。apply有参数时,以数组的形式进行传递。
Function.prototype.myApply = function () {
  context = context || window;
  let fn = Symbol();
  context[fn] = this;
  let args = [...arguments][1] || [];
  let ret = context[fn](...args);
  delete context[fn];
  return ret;
}

// bind,改变this指向,bind前面的函数不执行,返回一个绑定this之后的函数
Function.prototype.myBind = function (context) {
  context = context || window;
  let fn = this;
  let args = [...arguments].slice(1);
  return function () {
    let exeArgs = [...arguments]
    fn.apply(context, [...args, ...exeArgs])
  }
}

*箭头函数的特点

function a() {
    return () => {
        return () => {
            console.log(this)
        }
    }
}
console.log(a()()())

箭头函数其实是没有 this的,这个函数中的 this只取决于他外面的第一个不是箭头函数的函数的 this。在这个例子中,因为调用 a符合前面代码中的第一个情况,所以 thiswindow。并且 this一旦绑定了上下文,就不会被任何代码改变。

用js递归的方式写1到100求和?

function add(num1,num2){
	var num = num1+num2;
        if(num2+1>100){
	 return num;
	}else{
	  return add(num,num2+1)
        }
 }
var sum =add(1,2);        

null和undefined的差异

  • null表示一个"无"的对象,也就是该处不应该有值;而undefined表示未定义
  • 在转换为数字时结果不同,Number(null)0,而undefinedNaN

相同点:

  • 在 if判断语句中,值都默认为 false

差异:

  • null转为数字类型值为0,而undefined转为数字类型为 NaN(Not a Number)
  • undefined是代表调用一个值而该值却没有赋值,这时候默认则为undefined
  • null是一个很特殊的对象,最为常见的一个用法就是作为参数传入(说明该参数不是对象)
  • 设置为null的变量或者对象会被内存收集器回收

说一下CORS?

资源跨域共享,CORS是一种新标准,支持同源通信,也支持跨域通信。它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。

如何中断ajax请求?

一种是设置超时时间让ajax自动断开,另一种是手动停止ajax请求,其核心是调用XML对象的abort方法,ajax.abort()

说一下事件代理?

事件委托是指将事件绑定到目标元素的父元素上,利用冒泡机制触发该事件。

ulEl.addEventListener('click', function(e){
var target = event.target || event.srcElement;
if(!!target && target.nodeName.toUpperCase() === "LI"){
    console.log(target.innerHTML);
}

}, false);

说一下宏任务和微任务?

1、宏任务:当前调用栈中执行的任务称为宏任务。(主代码快,定时器等等)。
2、微任务: 当前(此次事件循环中)宏任务执行完,在下一个宏任务开始之前需要执行的任务为微任务。(可以理解为回调事件,promise.then,proness.nextTick等等)。

Promise 构造函数是同步执行还是异步执行,那么 then 方法呢?

const promise = new Promise((resolve, reject) => {
  console.log(1)
  resolve()
  console.log(2)
})
promise.then(() => {
  console.log(3)
})
console.log(4)
1 2 4 3
promise构造函数是同步执行的,then方法是异步执行的
Promise new的时候会立即执行里面的代码 then是微任务 会在本次任务执行完的时候执行 setTimeout是宏任务 会在下次任务执行的时候执行

target、currentTarget的区别?

currentTarget当前所绑定事件的元素

target当前被点击的元素

说一下继承的几种方式及优缺点?

1、借用构造函数继承,使用call或apply方法,将父对象的构造函数绑定在子对象上
2、原型继承,将子对象的prototype指向父对象的一个实例
3、组合继承

原型链继承的缺点

  • 字面量重写原型会中断关系,使用引用类型的原型,并且子类型还无法给超类型传递参数。

借用构造函数(类式继承)

  • 借用构造函数虽然解决了刚才两种问题,但没有原型,则复用无从谈起。

组合式继承

  • 组合式继承是比较常用的一种继承方法,其背后的思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又保证每个实例都有它自己的属性。

export和export default的区别?

使用上的不同

export default  xxx
import xxx from './'
默认只能导出一个,导出的是整个文件

export xxx
import {xxx} from './'
可以导出多个,导出的文件中的某个函数

什么是会话cookie,什么是持久cookie?

cookie是服务器返回的,指定了expire time(有效期)的是持久cookie,没有指定的是会话cookie

说一下自己常用的es6的功能?

像module、class、promise等,尽量讲的详细一点。

数组去重

自己的代码链接

js

var arr=['12','32','89','12','12','78','12','32'];
    // 最简单数组去重法
    function unique1(array){
        var n = []; //一个新的临时数组
        for(var i = 0; i < array.length; i++){ //遍历当前数组
            if (n.indexOf(array[i]) == -1)
                n.push(array[i]);
        }
        return n;
    }
    arr=unique1(arr);
    // 速度最快, 占空间最多(空间换时间)
    function unique2(array){
        var n = {}, r = [], type;
        for (var i = 0; i < array.length; i++) {
            type = typeof array[i];
            if (!n[array[i]]) {
                n[array[i]] = [type];
                r.push(array[i]);
            } else if (n[array[i]].indexOf(type) < 0) {
                n[array[i]].push(type);
                r.push(array[i]);
            }
        }
        return r;
    }
    //数组下标判断法
    function unique3(array){
        var n = [array[0]]; //结果数组
        for(var i = 1; i < array.length; i++) { //从第二项开始遍历
            if (array.indexOf(array[i]) == i) 
                n.push(array[i]);
        }
        return n;
    }

es6

es6方法数组去重
arr=[...new Set(arr)];
es6方法数组去重,第二种方法
function dedupe(array) {
  return Array.from(new Set(array));       //Array.from()能把set结构转换为数组
}

get、post的区别,传参误区

  • get传参通过地址栏的URL传递,可以看到传递的参数,请求的数据在URL后通过?连接,通过&进行参数分割;post传递不可见,参数存放在HTTP的包体内。
  • get请求可以被缓存,post不可以被缓存
  • get请求的记录会留在历史记录中,post请求不会留在历史记录
  • get后退不会有影响,post后退会重新进行提交
  • get请求只URL编码,post支持多种编码方式

http协议未规定两者传参限制,get传参限制来源那是浏览器或web服务器,不同的限制长度不同。

get和post在缓存方面的区别:

  • get请求类似于查找的过程,用户获取数据,可以不用每次都与数据库连接,所以可以使用缓存。
  • post不同,post做的一般是修改和删除的工作,所以必须与数据库交互,所以不能使用缓存。因此get请求适合于请求缓存。

ES5的继承和ES6的继承有什么区别?

ES5的继承时通过prototype或构造函数机制来实现。ES5的继承实质上是先创建子类的实例对象,然后再将父类的方法添加到this上(Parent.apply(this))。

ES6的继承机制完全不同,实质上是先创建父类的实例对象this(所以必须先调用父类的super()方法),然后再用子类的构造函数修改this

定时器的执行顺序或机制?

**因为js是单线程的,浏览器遇到setTimeout或者setInterval会先执行完当前的代码块,在此之前会把定时器推入浏览器的待执行事件队列里面,等到浏览器执行完当前代码之后会看一下事件队列里面有没有任务,有的话才执行定时器的代码。**所以即使把定时器的时间设置为0还是会先执行当前的一些代码。

setTimeout、Promise、Async/Await 的区别

https://gongchenghuigch.github.io/2019/09/14/awat/

async 是“异步”的简写,而 await 可以认为是 async wait 的简写。所以应该很好理解 async 用于申明一个 function 是异步的,而 await 用于等待一个异步方法执行完成。

JS的四种设计模式

工厂模式:简单的工厂模式可以理解为解决多个相似的问题;

单例模式:只能被实例化(构造函数给实例添加属性与方法)一次

沙箱模式:将一些函数放到自执行函数里面,但要用闭包暴露接口,用变量接收暴露的接口,再调用里面的值,否则无法使用里面的值

发布者订阅模式:就例如如我们关注了某一个公众号,然后他对应的有新的消息就会给你推送,

列举出集中创建实例的方法

1.字面量

let obj={'name':'张三'}

2.Object构造函数创建

let Obj=new Object()
Obj.name='张三'

3.使用工厂模式创建对象

function createPerson(name){
 var o = new Object();
 o.name = name;
 };
 return o; 
}
var person1 = createPerson('张三');

4.使用构造函数创建对象

function Person(name){
 this.name = name;
}
var person1 = new Person('张三');

try/catch/finally

try/catch/finally 语句用于处理代码中可能出现的错误信息。

try语句允许我们定义在执行时进行错误测试的代码块。

catch 语句允许我们定义当 try 代码块发生错误时,所执行的代码块。

finally 语句在 try 和 catch 之后无论有无异常都会执行。

注意: catch 和 finally 语句都是可选的,但你在使用 try 语句时必须至少使用一个。

try {
  *tryCode - 尝试执行代码块
*}
catch(*err*) {
  *catchCode - 捕获错误的代码块
*}
finally {
  *finallyCode - 无论 try / catch 结果如何都会执行的代码块
*}

docoment,window,html,body的层级关系

层级关系

window > document > html > body
复制代码
  • windowBOM的核心对象,它一方面用来获取或设置浏览器的属性和行为,另一方面作为一个全局对象。
  • document对象是一个跟文档相关的对象,拥有一些操作文档内容的功能。但是地位没有window高。
  • html元素对象和document元素对象是属于html文档的DOM对象,可以认为就是html源代码中那些标签所化成的对象。他们跟div、select什么对象没有根本区别。

(我是这样记的,整个浏览器中最大的肯定就是窗口window了,那么进来的我不管你是啥,就算你是document也得给我盘着)

typeof和instanceof的区别

typeof表示是对某个变量类型的检测,基本数据类型除了null都能正常的显示为对应的类型,引用类型除了函数会显示为'function',其它都显示为object

instanceof它主要是用于检测某个构造函数的原型对象在不在某个对象的原型链上

this和this的确定

一句话描述一下this

对于函数而言指向最后调用函数的那个对象,是函数运行时内部自动生成的一个内部对象,只能在函数内部使用;对于全局来说,this指向window

函数内的this是在什么时候确定的?

函数调用时,指向最后调用的那个对象

*同源策略和跨域和jsonp

**同源策略:**协议、域名、端口

浏览器具有同源策略,所谓同源策略就是,两个页面的协议,域名和端口都相同,则两个页面具有相同的源。

**跨域:**在浏览器端;使用js;在不同的域下进行数据传输或通信;

出现跨域的根本原因:浏览器的同源策略不允许非同源的 URL 之间进行资源的交互。

解决跨域:cors;jsonp;代理

解决跨域

  • 1.服务器设置响应头:access-control-allow-origin,放开跨域问题;
  • 2.利用 JSONP 处理,因为 JSONP 是通过创建 script 标签的方式发送请求,所以只能用于 get 方法;
  • 3.通过服务器的反向代理方式。

cors解决跨域是在服务端开发权限,代码是固定的。

jsonp原理:主要就是利用 script 标签的src属性没有跨域的限制,通过指向一个需要访问的地址,由服务端返回一个预先定义好的 Javascript 函数的调用,并且将服务器数据以该函数参数的形式传递过来,此方法需要前后端配合完成。

本质不是ajax,本质是script标签发出的请求

Jsonp的实现是通过动态的添加script标签从而发送http请求来实现的。

缺点:只能进行GET请求

优点:兼容性好,在一些古老的浏览器中都可以运行

浏览器

http状态码

1xx:(临时响应)表示临时响应并需要请求者继续执行操作的状态代码。

  • 100 (继续) 请求者应当继续提出请求。 服务器返回此代码表示已收到请求的第一部分,正在等待其余部分。
  • 101 (切换协议) 请求者已要求服务器切换协议,服务器已确认并准备切换。

2xx (成功)表示成功处理了请求的状态代码。

  • 200:(成功)正确的请求返回正确的结果。 通常,这表示服务器提供了请求的网页。
  • 201 (已创建) 请求成功并且服务器创建了新的资源。
  • 202 (已接受) 服务器已接受请求,但尚未处理。

3xx((已重定向)) 表示要完成请求,需要进一步操作。 通常,这些状态代码用来重定向。

  • 300:请求成功,但结果有多种选择。
  • 301:请求成功,但是资源被永久转移。
  • 303:使用 GET 来访问新的地址来获取资源。
  • 304:请求的资源并没有被修改过

4xx(请求错误)

  • 400 (错误请求) 服务器不理解请求的语法。
  • 401 (未授权) 请求要求身份验证。请求的时候没有带上 Token 等。
  • 403 (禁止) 服务器拒绝请求。
  • 404 (未找到) 服务器找不到请求的网页。请求的内容不存在。

5xx(服务器错误)

  • 500: (服务器内部错误) 服务器遇到错误,无法完成请求。

*web 性能优化

资源优化:预加载、懒加载、资源压缩合并(图片、字体、代码)、利用缓存、减少请求、代码优化(减少操作dom、减少全局变量)

  1. 初始化页面加loding图,这其实没有优化加载时间,但是提升了用户体验。
  2. 减少http请求,合理的利用缓存。
  3. 预加载、预渲染
  4. 组件,路由懒加载;拆分页面,分担加载压力。
  5. 优化webpack打包机制。
  6. 资源压缩合并(图片转base64、字体、代码)
  7. 代码优化(减少操作dom、减少全局变量

*一个url到页面展示的完整流程

网络篇

浏览器端发起 HTTP 请求流程

  1. 构建请求
  2. 查找强缓存
  3. DNS解析
  4. 建立 TCP 连接
  5. 发送 HTTP 请求 服务器端处理 HTTP 请求流程
  6. 返回请求
  7. 断开连接 通常情况下,一旦服务器在发送给客户端

解析篇

  1. 构建 DOM 树
  2. 样式计算
  3. 生成布局树

渲染

  1. 图层树
  2. 生成绘制列表
  3. 生成图块和生成位图
  4. 显示器显示内容

img

网络篇

浏览器端发起 HTTP 请求流程

  1. 构建请求;

    浏览器会构建请求行,get请求

  2. 查找强缓存;

    先检查强缓存,如果命中直接使用,否则进入下一步。

  3. DNS解析;

    因为我们输入的是域名,而数据包是通过ip地址传给对方的,所以需要使用DNS(域名解析系统),将域名解析成IP地址,浏览器具有DNS数据缓存功能,解析过的域名下次直接走缓存。

  4. 建立 TCP 连接;

    建立 TCP连接经历了三个阶段:

    一是三次握手确认连接,二是数据包校验保证数据到达接收方,三是通过四次挥手断开连接。

    1. 通过三次握手(即总共发送3个数据包确认已经建立连接)建立客户端和服务器之间的连接。
    2. 进行数据传输。这里有一个重要的机制,就是接收方接收到数据包后必须要向发送方确认, 如果发送方没有接到这个确认的消息,就判定为数据包丢失,并重新发送该数据包。当然,发送的过程中还有一个优化策略,就是把大的数据包拆成一个个小包,依次传输到接收方,接收方按照这个小包的顺序把它们组装成完整数据包。
    3. 断开连接的阶段。数据传输完成,现在要断开连接了,通过四次挥手来断开连接。

    Chrome 在同一个域名下要求同时最多只能有 6 个 TCP 连接。TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。

  5. 发送 HTTP 请求,服务器端处理 HTTP 请求流程;

    三次握手,TCP连接建立完毕,浏览器可以和服务器开始通信,即开始发送 HTTP 请求。请求包括:请求行(请求方法、URL、协议版本)、请求头(请求的附加信息,成对:分割)和请求体(只有在POST方法下存在,表单提交。)。

  6. 返回请求,网络响应

    网络响应具有三个部分:响应行(协议版本,状态码,状态码描述)、响应头(响应报文的附加信息,由 名/值 对组成)和响应体(回车符、换行符和响应返回数据)

  7. (解析渲染后,四次挥手断开连接)断开连接

解析渲染篇

  1. 根据 HTML 解析 DOM 树

    浏览器解析html源码,会根据html源码生成DOM树。在DOM树中,每一个HTML标签都会对应一个节点,每一个文本也都会有一个对应的文本节点。DOM树的根节点是documentElement,对应的是html标签。

  2. 根据 CSS解析 CSS规则树

    浏览器解析css代码,会根据优先级计算出最终的样式。浏览器默认设置 < 用户设置 < 外链样式 < 内联样式 < html中的style。也会生成相对应的CSS规则树。 对CSS代码中非法的语法它会直接忽略掉。

  3. 结合 DOM 树和 CSS 规则树,生成渲染树。

    渲染树和DOM树有点像,但是是有区别的。DOM树完全和html标签一一对应,但是渲染树会精简css,忽略掉不需要渲染的元素,比如head、display:none的元素等。而且渲染树的每一个节点都会存储对应的css属性。

  4. 根据渲染树计算每一个节点的信息(布局)

    重绘:某个元素的背景颜色,文字颜色等,不影响元素周围或内部布局的属性,将只会引起浏览器的重绘。

    回流:某个元素的尺寸发生了变化,则需重新计算渲染树,重新渲染

  5. 根据计算好的信息绘制页面

    绘制阶段,系统会遍历呈现树,并调用呈现器的“paint”方法,将呈现器的内容显示在屏幕上

重绘和回流

当元素的样式发生变化时,浏览器需要触发更新,重新绘制元素。这个过程中,有两种类型的操作,即重绘与回流。

重绘(repaint):
当元素样式的改变不影响布局时,浏览器将使用重绘对元素进行更新,此时由于只需要UI层面的重新像素绘制,因此 损耗较少

回流(reflow):
当元素的尺寸、结构或触发某些属性时,浏览器会重新渲染页面,称为回流。此时,浏览器需要重新经过计算,计算后还需要重新页面布局,因此是较重的操作。

会触发回流的操作:

  • 添加或者删除可见的DOM元素;
  • 元素位置改变;
  • 元素尺寸改变——边距、填充、边框、宽度和高度
  • 内容改变——比如文本改变或者图片大小改变而引起的计算值宽度和高度改变;
  • 页面渲染初始化;
  • 浏览器窗口尺寸改变——resize事件发生时;

回流必定触发重绘,重绘不一定触发回流。重绘的开销较小,回流的代价较高。

如何减少回流: css

  • 避免使用table布局
  • 将动画效果应用到position属性为absolute或fixed的元素上

javascript

  • 避免频繁操作样式,可汇总后统一 一次修改
  • 尽量使用class进行样式修改
  • 减少dom的增删次数,可使用 字符串 或者 documentFragment 一次性插入
  • 极限优化时,修改样式可将其display: none后修改
  • 避免多次触发上面提到的那些会触发回流的方法,可以的话尽量用 变量存住

强缓存和协商缓存

浏览器判断缓存是否过期,未过期时,直接使用强缓存。当缓存已经过期时,使用协商缓存

强缓存:不需要发送HTTP请求

浏览器判断缓存是否过期,未过期时,直接使用强缓存,Cache-Control的 max-age 优先级高于 Expires。

协商缓存:

缓存过期,进入协商缓存,即发送 HTTP 请求,服务器通过请求头中的If-Modified-Since或者If-None-Match字段检查资源是否更新

  • 若资源更新,返回资源和200状态码
  • 否则,返回304,告诉浏览器直接从缓存获取资源

*事件循环Event Loop(setTimeout)

JavaScript 是单线程的,所有的任务都需要排队,任务又分为同步任务和异步任务。
所有的同步任务都在主线程中排队执行,只有前一个任务执行完毕才会去执行下一个任务。异步任务则放在任务队列中排队,分为宏任务队列和微任务队列。
只有主线程执行栈为空时,才会读取微任务队列中的任务到执行栈中执行,只有当微任务队列为空时,才会读取宏任务队列中的任务到执行栈中执行。

事件循环完整解释:

  • 首先,整体的script(作为第一个宏任务)开始执行的时候,会把所有代码分为同步任务、异步任务两部分
  • 同步任务会直接进入主线程依次执行
  • 异步任务会再分为宏任务和微任务
  • 宏任务进入到Event Table中,并在里面注册回调函数,每当指定的事件完成时,Event Table会将这个函数移到Event Queue(事件队列)中
  • 微任务也会进入到另一个Event Table中,并在里面注册回调函数,每当指定的事件完成时,Event Table会将这个函数移到Event Queue中
  • 主线程内的任务执行完毕,主线程为空时,会检查微任务的Event Queue,如果有任务,就全部执行,如果没有就执行下一个宏任务
  • 上述过程会不断重复,这就是Event Loop,比较完整的事件循环

掘金呆呆:

  • 一开始整个脚本作为一个宏任务执行
  • 执行过程中同步代码直接执行,宏任务进入宏任务队列,微任务进入微任务队列
  • 当前宏任务执行完出队,检查微任务列表,有则依次执行,直到全部执行完
  • 执行浏览器UI线程的渲染工作
  • 检查是否有Web Worker任务,有则执行
  • 执行完本轮的宏任务,回到2,依此循环,直到宏任务和微任务队列都为空

setTimeout的运行机制:执行该语句时,是立即把当前定时器代码推入事件队列,当定时器在事件列表中满足设置的时间值时将传入的函数加入任务队列,之后的执行就交给任务队列负责。但是如果此时任务队列不为空,则需等待,所以执行定时器内代码的时间可能会大于设置的时间

*CSRF、XSS 区别

  • 原理不同,CSRF是利用网站A本身的漏洞,去请求网站A的api;XSS是向目标网站注入JS代码,然后执行JS里的代码。
  • CSRF需要用户先登录目标网站获取cookie,而XSS不需要登录
  • CSRF的目标是用户,XSS的目标是服务器
  • XSS是利用合法用户获取其信息,而CSRF是伪造成合法用户发起请求

CSRF(Cross-site request forgery)跨站请求伪造。

指的是黑客诱导用户点击链接,打开黑客的网站,然后黑客利用用户目前的登录状态发起跨站请求。

XSS攻击对比,CSRF 攻击并不需要将恶意代码注入用户当前页面的html文档中,而是跳转到新的页面,利用服务器的验证漏洞用户之前的登录状态来模拟用户进行操作。

攻击
用户登录 A 网站,并保留了登录凭证(cookie)
用户访问了 B 网站
B 网站向 A 网站发送一个请求,浏览器会默认携带 cookie
A 网站对请求进行验证,确认是用户的凭证
A 网站执行 B 网站的请求

防御

  • **验证来源站点:**通过 http 请求头中的origin或referer字段,确定请求的来源域名
  • 利用Cookie的SameSite属性,设置该属性,让浏览器完全禁止第三方请求携带Cookie
  • **CSRF Token:**第三方站点无法拿到这个token

XSS(Cross Site Scripting)跨站脚本攻击。

XSS 攻击是指浏览器中执行恶意脚本(无论是跨域还是同域),从而拿到用户的信息并进行操作。

XSS 攻击的实现有三种方式——存储型反射型文档型。三种XSS攻击的原理,共同点: 都是让恶意脚本直接能在浏览器中执行。

防范:

无论是在前端和服务端,都要对用户的输入进行转码或者过滤

利用CSP,即浏览器中的内容安全策略,它的核心思想就是服务器决定浏览器加载哪些资源。

设置 Cookie 的 HttpOnly 属性,防止XSS 攻击脚本来窃取Cookie

  • 一个信念: 不要相信用户的输入,对输入内容转码或者过滤,让其不可执行。
  • 两个利用: 利用 CSP,利用 Cookie 的 HttpOnly 属性。

页面渲染html的过程?

1、浏览器解析html源码,会根据html源码生成DOM树。在DOM树中,每一个HTML标签都会对应一个节点,每一个文本也都会有一个对应的文本节点。DOM树的根节点是documentElement,对应的是html标签。

2、浏览器解析css代码,会根据优先级计算出最终的样式。浏览器默认设置 < 用户设置 < 外链样式 < 内联样式 < html中的style。也会生成相对应的CSSOM树。 对CSS代码中非法的语法它会直接忽略掉。

3、html生成的树和css生成的树会结合成渲染树。渲染树和DOM树有点像,但是是有区别的。

DOM树完全和html标签一一对应,但是渲染树会忽略掉不需要渲染的元素,比如head、display:none的元素等。而且渲染树的每一个节点都会存储对应的css属性。

4、一旦渲染树创建好了,浏览器会根据渲染树把页面渲染到屏幕上。

以上四个步骤并不是一次性顺序完成的。如果DOM树或者CSSOM树被修改,以上过程会被重复执行。实际上,CSS和JavaScript往往会多次修改DOM或者CSSOM。

浏览器的渲染过程

  • 根据html构建 DOM
  • 根据css构建 CSSOM
  • 将 DOM 和 CSSOM 合并成 Render Tree渲染树
  • 根据 Render Tree 进行布局
  • 最后绘制

http和https的区别?

http传输的数据都是未加密的,也就是明文的,网景公司设置了SSL协议来对http协议传输的数据进行加密处理,简单来说https协议是由http和ssl协议构建的可进行加密传输和身份认证的网络协议,比http协议的安全性更高。 主要的区别如下:

  • Https协议需要ca证书,费用较高。
  • http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。
  • 使用不同的链接方式,端口也不同,一般而言,http协议的端口为80,https的端口为443
  • http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。

HTTPS并不是一个新的协议, 它在HTTPTCP的传输中建立了一个安全层,利用对称加密非对称加密结合数字证书认证的方式,让传输过程的安全性大大提高。

存储Cookie、sessionStorage、localStorage的区别

我们经常需要对业务中的一些数据进行存储,通常可以分为 短暂性存储持久性储存

  • 短暂性的时候,我们只需要将数据存在内存中,只在运行时可用
  • 持久性存储,可以分为 浏览器端 与 服务器端

浏览器:

  • cookie: 通常用于存储用户身份,登录状态等 http 中自动携带, 体积上限为 4K, 可自行设置过期时间
  • localStorage / sessionStorage: 长久储存/窗口关闭删除, 体积限制为 4~5M
  • indexDB

服务器:

  • 分布式缓存 redis
  • 数据库
  1. cookie并不适合存储,而且存在非常多的缺陷。
  2. Web Storage包括localStoragesessionStorage, 默认不会参与和服务器的通信。
  3. IndexedDB为运行在浏览器上的非关系型数据库,为大型数据的存储提供了接口。

三者异同:

共同点:都是保存在浏览器端,并且是同源的

  • Cookie:cookie数据始终在同源的http请求中携带(即使不需要),即cookie在浏览器和服务器间来回传递。而sessionStorage和localStorage不会自动把数据发给服务器,仅在本地保存。
  • sessionStorage:仅在当前浏览器窗口关闭前有效,自然也就不可能持久保持
  • localStorage:始终有效,窗口或浏览器关闭也一直保存,因此用作持久数据;cookie只在设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭。

TCP三次握手四次挥手

三次握手之所以是三次是保证client客户端和server服务器均让对方知道自己的接收和发送能力没问题而保证的最小次数。

第一次client => server 只能server判断出client具备发送能力
第二次 server => client client就可以判断出server具备发送和接受能力。此时client还需让server知道自己接收能力没问题于是就有了第三次
第三次 client => server 双方均保证了自己的接收和发送能力没有问题

其中,为了保证后续的握手是为了应答上一个握手,每次握手都会带一个标识 seq,后续的ACK都会对这个seq进行加一来进行确认。

简述HTTPS中间人攻击

https协议由 http + ssl 协议构成,具体的链接过程可参考SSL或TLS握手的概述

中间人攻击过程如下:

  1. 服务器向客户端发送公钥。
  2. 攻击者截获公钥,保留在自己手上。
  3. 然后攻击者自己生成一个【伪造的】公钥,发给客户端。
  4. 客户端收到伪造的公钥后,生成加密hash值发给服务器。
  5. 攻击者获得加密hash值,用自己的私钥解密获得真秘钥。
  6. 同时生成假的加密hash值,发给服务器。
  7. 服务器用私钥解密获得假秘钥。
  8. 服务器用加秘钥加密传输信息

防范方法:

  1. 服务端在发送浏览器的公钥中加入CA证书,浏览器可以验证CA证书的有效性

vue

*vue响应式原理?详细说一下过程。

Vue响应式*:数据发生变化后,会重新对页面渲染,这就是Vue响应式

过程

  • 侦测数据的变化
  • 收集视图依赖了哪些数据
  • 数据变化时,自动“通知”需要更新的视图部分,并进行更新

它们对应专业俗语分别是:

  • 数据劫持 / 数据代理
  • 依赖收集
  • 发布订阅模式

1、使用Object.defineProperty和ES6的Proxy,进行数据劫持或数据代理。

2、收集依赖是为了知道那个地方依赖了我的数据,以及当数据更新时派发更新。

响应式的好处

对某些数据的修改就能自动更新视图,让开发者不用再去操作DOM,有更多的时间去思考业务逻辑。

*vue的生命周期

Vue 实例从创建到销毁的过程,就是生命周期。作用是:它的生命周期中有多个事件钩子,让我们控制Vue实例过程更加清晰方便。

分为8个阶段:创建前/后, 载入前/后,更新前/后,销毁前/销毁后。

  • beforeCreate: 什么也做不了,vue实例的挂载元素el和数据对象data都是undefined,还没有初始化。
  • created: · vue实例的数据对象data有了,可以访问里面的数据和方法,未挂载到DOM,el还没有,我们可以在created这个勾子中,发起ajax请求
  • beforeMound: vue实例的el和data都初始化了,但是挂载之前为虚拟的dom节点
  • mounted: · vue实例挂载到真实DOM上,就可以通过DOM获取DOM节点,可以发起ajax请求。DOM渲染在这个周期中就已经完成,请求一般在这里,服务端渲染时在created。
  • beforeUpdate: 当data中的数据发生改变,数据改变,视图要重新刷新,就会调用此勾子函数,在这个函数中,拿的数据是新数据,但是,页面中的还是老数据
  • updated: 数据改变,虚拟DOM重新渲染,并且打补丁,此时页面中的老数据,会被替换成真实的数据
    你能在updated这个勾子函数中,更新数据吗?
    答:不能在updated这个勾子中更新数据,会导致死循环。
  • beforeDestory: vm实例销毁之前调用,实例还可以用,this能获取到实例
  • destroyed: vm实例销毁之后调用 没什么用

DOM渲染在哪个周期中就已经完成?

答:DOM 渲染在 mounted 中就已经完成了

第一次页面加载会触发哪几个钩子?
答:beforeCreate, created, beforeMount, mounted

Vue 组件 data 为什么必须是函数

因为js本身的特性带来的,如果 data 是一个对象,那么由于对象本身属于引用类型,当我们修改其中的一个属性时,会影响到所有Vue实例的数据。如果将 data 作为一个函数返回一个对象,那么每一个实例的 data 属性都是独立的,不会相互影响了

对 keep-alive 的了解

keep-aliveVue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染

Vue的优点及缺点

首先Vue最核心的两个特点,响应式组件化。数据驱动 组件系统

响应式:这也就是vue.js最大的优点,通过MVVM思想实现数据的双向绑定,通过虚拟DOM让我们可以用数据来操作DOM,而不必去操作真实的DOM,提升了性能。且让开发者有更多的时间去思考业务逻辑。

组件化:把一个单页应用中的各个模块拆分到一个个组件当中,或者把一些公共的部分抽离出来做成一个可复用的组件。所以组件化带来的好处就是,提高了开发效率,方便重复使用,使项目的可维护性更强。

虚拟DOM,当然,这个不是vue中独有的。

缺点:基于对象配置文件的写法,也就是options写法,开发时不利于对一个属性的查找。另外一些缺点,在小项目中感觉不太出什么,vuex的魔法字符串,对ts的支持。兼容性上存在一些问题。

  • 不利于seo。
  • 导航不可用,如果一定要导航需要自行实现前进、后退。(由于是单页面不能用浏览器的前进后退功能,所以需要自己建立堆栈管理)。
  • 初次加载时耗时多。

vue 等单页面应用的优缺点

优点:

  • 良好的交互体验
  • 良好的前后端工作分离模式
  • 减轻服务器压力

缺点:

  • SEO难度较高
  • 前进、后退管理
  • 初次加载耗时多

为什么虚拟dom会提高性能?

虚拟dom相当于在js和真实dom中间加了一个缓存,避免了没有必要的dom操作,从而提高性能。

虚拟 DOM 的实现原理主要包括以下 3 部分:

  • 用 JavaScript 对象模拟真实 DOM 树,对真实 DOM 进行抽象;
  • diff 算法 — 比较两棵虚拟 DOM 树的差异;
  • pach 算法 — 将两个虚拟 DOM 对象的差异应用到真正的 DOM 树。

优点:

  • 保证性能下限: 框架的虚拟 DOM 需要适配任何上层 API 可能产生的操作,它的一些 DOM 操作的实现必须是普适的,所以它的性能并不是最优的;但是比起粗暴的 DOM 操作性能要好很多,因此框架的虚拟 DOM 至少可以保证在你不需要手动优化的情况下,依然可以提供还不错的性能,即保证性能的下限;
  • 无需手动操作 DOM: 我们不再需要手动去操作 DOM,只需要写好 View-Model 的代码逻辑,框架会根据虚拟 DOM 和 数据双向绑定,帮我们以可预期的方式更新视图,极大提高我们的开发效率;
  • 跨平台: 虚拟 DOM 本质上是 JavaScript 对象,而 DOM 与平台强相关,相比之下虚拟 DOM 可以进行更方便地跨平台操作,例如服务器渲染、weex 开发等等。

缺点:

无法进行极致优化: 虽然虚拟 DOM + 合理的优化,足以应对绝大部分应用的性能需求,但在一些性能要求极高的应用中虚拟 DOM 无法进行针对性的极致优化。

Vue 中的 key 有什么作用?

Vue 中 key 的作用是:key 是为 Vue 中 vnode 的唯一标记,通过这个 key,我们的 diff 操作可以更准确、更快速

****更准确****:因为带 key 就不是就地复用了,在 sameNode 函数 a.key === b.key 对比中可以避免就地复用的情况。所以会更加准确。

****更快速****:利用 key 的唯一性生成 map 对象来获取对应节点,比遍历方式更快

v-show与v-if区别

共同点:都能控制元素的显示和隐藏;

不同点:实现本质方法不同,v-show本质就是通过控制css中的display设置为none,控制隐藏,只会编译一次;v-if是动态的向DOM树内添加或者删除DOM元素,若初始值为false,就不会编译了。而且v-if不停的销毁和创建比较消耗性能。
总结:如果要频繁切换某节点,使用v-show(切换开销比较小,初始开销较大)。如果不需要频繁切换某节点使用v-if(初始渲染开销较小,切换开销比较大)。

  • v-show是css切换,v-if是完整的销毁和重新创建
  • 使用 频繁切换时用v-show,运行时较少改变时用v-if
  • v-if=‘false’ v-if是条件渲染,当false的时候不会渲染

Vue中hash模式和history模式的区别

  • 最明显的是在显示上,hash模式的URL中会夹杂着#号,而history没有。
  • Vue底层对它们的实现方式不同。hash模式是依靠onhashchange事件(监听location.hash的改变),而history模式是主要是依靠的HTML5 history中新增的两个方法,pushState()可以改变url地址且不会发送请求,replaceState()可以读取历史记录栈,还可以对浏览器记录进行修改。
  • 当真正需要通过URL向后端发送HTTP请求的时候,比如常见的用户手动输入URL后回车,或者是刷新(重启)浏览器,这时候history模式需要后端的支持。因为history模式下,前端的URL必须和实际向后端发送请求的URL一致,例如有一个URL是带有路径path的(例如www.lindaidai.wang/blogs/id),如果后端没有对这个路径做处理的话,就会返回404错误。所以需要后端增加一个覆盖所有情况的候选资源,一般会配合前端给出的一个404页面。

v-model 双向绑定的原理

使用 v-model 指令在表单元素上创建双向数据绑定, v-model 本质上不过是语法糖,采用数据劫持结合发布者-订阅者模式的方式。通过 Object.defineProperty() 来劫持各个属性的 settergetter,在数据变动时发布消息给订阅者,触发相应监听回调

MVC和MVVM

  • MVC是后端的分层开发概念;
  • MVVM是前端视图层的概念,主要关注于 视图层分离MVVM把前端的视图层分为了三部分:Model,View,VM ViewModel

概括起来,MVVM是由MVC发展而来,通过在Model之上而在View之下增加一个非视觉的组件将来自Model的数据映射到View中。

MVC

view=》controller=》model》view

特点:

  1. View 传送指令到 Controller
  2. Controller 完成业务逻辑后,要求 Model 改变状态
  3. Model 将新的数据发送到 View,用户得到反馈

所有通信都是单向的

MVVM

View《=》ViewModel《=》Model

特点:

  1. 各部分之间的通信,都是双向的
  2. 采用双向绑定:View 的变动,自动反映在 ViewModel,反之亦然

ref获取dom

ref=“domName” 用法:this.$refs.domName

ref的作用

  1. 获取dom元素this.$refs.box

  2. 获取子组件中的datathis.$refs.box.msg

  3. 调用子组件中的方法this.$refs.box.open()

vue小知识点

组件通信,父子传递

vue父组件向子组件传递数据?
答:通过props
子组件像父组件传递事件?
答:$emit方法

说出几种vue当中的指令和它的用法?
答:v-model双向数据绑定;
v-for循环;
v-if v-show 显示与隐藏;
v-on事件;v-once: 只绑定一次。

为什么使用key?
答:需要使用key来给每个节点做一个唯一标识,Diff算法就可以正确的识别此节点。
作用主要是为了高效的更新虚拟DOM。

v-modal的使用。
答:v-model用于表单数据的双向绑定,其实它就是一个语法糖,这个背后就做了两个操作:
v-bind绑定一个value属性;
v-on指令给当前元素绑定input事件。

请说出vue.cli项目中src目录每个文件夹和文件的用法?
答:assets文件夹是放静态资源;components是放组件;router是定义路由相关的配置; app.vue是一个应用主组件;main.js是入口文件。

分别简述computed和watch的使用场景
答:computed:本质是一个具备缓存的watcher,依赖的属性发生变化就会更新视图
    当一个属性受多个属性影响的时候就需要用到computed
    最典型的栗子: 购物车商品结算的时候
watch:没有缓存性,更多的是观察的作用
    当一条数据影响多条数据的时候就需要用watch
    栗子:搜索数据

渐进式框架的理解
答:主张最少;可以根据不同的需求选择不同的层级;
19.Vue中双向数据绑定是如何实现的?
答:vue 双向数据绑定是通过 数据劫持 结合 发布订阅模式的方式来实现的, 也就是说数据和视图同步,数据发生变化,视图跟着变化,视图变化,数据也随之发生改变;
核心:关于VUE双向数据绑定,其核心是 Object.defineProperty()方法。

v-if和v-for的优先级
答:当 v-if 与 v-for 一起使用时,v-for 具有比 v-if 更高的优先级,这意味着 v-if 将分别重复运行于每个 v-for 循环中。所以,不推荐v-if和v-for同时使用。
如果v-if和v-for一起用的话,vue中的的会自动提示v-if应该放到外层去。

vue的两个核心点
答:数据驱动、组件系统
数据驱动:ViewModel,保证数据和视图的一致性。
组件系统:应用类UI可以看作全部是由组件树构成的。

对比 jQuery ,Vue 有什么不同

jQuery 专注视图层,通过操作 DOM 去实现页面的一些逻辑渲染; Vue 专注于数据层,通过数据的双向绑定,最终表现在 DOM 层面,减少了 DOM 操作Vue 使用了组件化思想,使得项目子集职责清晰,提高了开发效率,方便重复利用,便于协同开发。

Vue2中注册在router-link上事件无效解决方法
答: 使用@click.native。原因:router-link会阻止click事件,.native指直接监听一个原生事件。

vue初始化页面闪动问题
答:使用vue开发时,在vue初始化之前,由于div是不归vue管的,所以我们写的代码在还没有解析的情况下会容易出现花屏现象,看到类似于{{message}}的字样,虽然一般情况下这个时间很短暂,但是我们还是有必要让解决这个问题的。
首先:在css里加上[v-cloak] {
display: none;
}。
如果没有彻底解决问题,则在根元素加上style=“display: none;” :style="{display: ‘block’}"

nextTick知道吗,实现原理是什么?

nextTick批量异步更新策略,一句话概括在下次DOM更新循环结束之后执行延迟回调

为了解决一个数据data在短时间内重复更新,数据重复更新导致视图重复更新,效率太低。所以在vue源码中定义了。它接收回调函数,多次调用接收多个回调函数存入任务队列中,当前栈任务执行完才会执行。

vue-router路由小知识点

1.mvvm 框架是什么?
答:vue是实现了双向数据绑定的mvvm框架,当视图改变更新模型层,当模型层改变更新视图层。在vue中,使用了双向绑定技术,就是View的变化能实时让Model发生变化,而Model的变化也能实时更新到View。
2.vue-router 是什么?它有哪些组件
答:vue用来写路由一个插件。router-link、router-view
3.active-class 是哪个组件的属性?
答:vue-router模块的router-link组件。children数组来定义子路由
4.怎么定义 vue-router 的动态路由? 怎么获取传过来的值?
答:在router目录下的index.js文件中,对path属性加上/:id。 使用router对象的params.id。
5.vue-router 有哪几种导航钩子?
答:三种,
第一种:是全局导航钩子:router.beforeEach(to,from,next),作用:跳转前进行判断拦截。
第二种:组件内的钩子
第三种:单独路由独享组件
**6.$route 和 r o u t e r 的 区 别 ∗ ∗ 答 : router 的区别** 答: routerrouter是VueRouter的实例,在script标签中想要导航到不同的URL,使用 r o u t e r . p u s h 方 法 。 返 回 上 一 个 历 史 h i s t o r y 用 router.push方法。返回上一个历史history用 router.pushhistoryrouter.to(-1)
$route为当前router跳转对象。里面可以获取当前路由的name,path,query,parmas等。
7.vue-router的两种模式
答:hash模式:即地址栏 URL 中的 # 符号;
history模式:window.history对象打印出来可以看到里边提供的方法和记录长度。利用了 HTML5 History Interface 中新增的 pushState() 和 replaceState() 方法。(需要特定浏览器支持)。
8.vue-router实现路由懒加载( 动态加载路由 )
答:三种方式
第一种:vue异步组件技术 ==== 异步加载,vue-router配置路由 , 使用vue的异步组件技术 , 可以实现按需加载 .但是,这种情况下一个组件生成一个js文件。
第二种:路由懒加载(使用import)。
第三种:webpack提供的require.ensure(),vue-router配置路由,使用webpack的require.ensure技术,也可以实现按需加载。这种情况下,多个路由指定相同的chunkName,会合并打包成一个js文件。

vuex小知识点

1.vuex是什么?怎么使用?哪种功能场景使用它?
答:vue框架中状态管理。在main.js引入store,注入。
新建了一个目录store.js,…… export 。
场景有:单页应用中,组件之间的状态。音乐播放、登录状态、加入购物车
2.vuex有哪几种属性?
答:有五种,分别是 State、 Getter、Mutation 、Action、 Module
state => 基本数据(数据源存放地)
getters => 从基本数据派生出来的数据
mutations => 提交更改数据的方法,同步!
actions => 像一个装饰器,包裹mutations,使之可以异步。
modules => 模块化Vuex
3.Vue.js中ajax请求代码应该写在组件的methods中还是vuex的actions中?
答:如果请求来的数据是不是要被其他组件公用,仅仅在请求的组件内使用,就不需要放入vuex 的state里。
如果被其他地方复用,这个很大几率上是需要的,如果需要,请将请求放入action里,方便复用。

vue的diff算法

  • 给列表结构的每个单元添加唯一的key属性,方便比较。
  • React 只会匹配相同 class 的 component(这里面的class指的是组件的名字)
  • 合并操作,调用 component 的 setState 方法的时候, React 将其标记为 dirty.到每一个事件循环结束, React 检查所有标记 dirty 的 component 重新绘制.
  • 选择性子树渲染。开发人员可以重写shouldComponentUpdate提高diff的性能。。

react

diff算法

  • 给列表结构的每个单元添加唯一的key属性,方便比较。
  • React 只会匹配相同 class 的 component(这里面的class指的是组件的名字)
  • 合并操作,调用 component 的 setState 方法的时候, React 将其标记为 dirty.到每一个事件循环结束, React 检查所有标记 dirty 的 component 重新绘制.
  • 选择性子树渲染。开发人员可以重写shouldComponentUpdate提高diff的性能。

react和vue的区别

相同点:

  • 都支持服务端渲染
  • 数据驱动视图
  • 都有Virtual DOM,组件化开发,通过props参数进行父子组件数据的传递,都实现webComponents规范
  • 都有支持native的方案,React的React native,Vue的weex

不同点:

  • React严格上只针对MVC的view层,Vue则是MVVM模式
  • 数据绑定:Vue有实现了双向数据绑定,React数据流动是单向的
  • state对象在react应用中是不可变的,需要使用setState方法更新状态;在Vue中,state对象并不是必须的,数据由data属性在Vue对象中进行管理。
  • virtual DOM 不一样 vue会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树。而对于React而言,每当应用的状态被改变时,全部子组件都会重新渲染。当然,这可以通过shouldComponentUpdate这个生命周期方法来进行控制,
  • 组件写法不一样 React 推荐的做法是 JSX + inline style,也就是把 HTML 和 CSS 全都写进 JavaScript 了,即”all in js” Vue 推荐的是使用 webpack + vue-loader 的单文件组件格式,即html,css,js写在同一个文件;

react的实现原理?有什么优缺点?

优点:

  1. 可以通过函数式方法描述视图组件(好处:相同的输入会得到同样的渲染结果,不会有副 作 用;组件不会被实例化,整体渲染性能得到提升)

  2. 集成虚拟DOM(性能好)

  3. 单向数据流(好处是更容易追踪数据变化排查问题

  4. 一切都是component:代码更加模块化,重用代码更容易,可维护性高

  5. 大量拥抱 es6 新特性

  6. jsx

缺点:

  1. jsx的一个问题是,渲染函数常常包含大量逻辑,最终看着更像是程序片段,而不是视觉呈现。后期如果发生需求更改,维护起来工作量将是巨大的

  2. 大而全,上手有难度

webpack

webpack中的loader和plugin有什么区别

loader它是一个转换器,只专注于转换文件这一个领域,完成压缩、打包、语言编译,它仅仅是为了打包。并且运行在打包之前。

而plugin是一个扩展器,它丰富了webpack本身,为其进行一些其它功能的扩展。它不局限于打包,资源的加载,还有其它的功能。所以它是在整个编译周期都起作用。

loader用于加载某些资源文件,因为webpack本身只能打包CommonJS规范的js文件,对于其他资源,例如css,图片等,是没有办法加载的,这就需要对应的loader将资源转换 plugin用于扩展webpack的功能,直接作用于webpack,loader只专注于转换文件,而plugin不仅局限于资源加载

Loader只能处理单一文件的输入输出,而Plugin则可以对整个打包过程获得更多的灵活性,譬如 ExtractTextPlugin,它可以将所有文件中的css剥离到一个独立的文件中,这样样式就不会随着组件加载而加载了。

webpack打包后文件体积过大怎么办?

很多方法:异步加载模块(代码分割);提取第三方库(使用cdn或者vender);代码压缩;去除不必要的插件;去除devtool选项,dllplugin等等。

算法

冒泡排序

function bubbleSort (arr) {
  for (let i = 0; i < arr.length; i++) {
    let flag = true;
    for (let j = 0; j < arr.length - i - 1; j++) {
      if (arr[j] > arr[j + 1]) {
        flag = false;
        let temp = arr[j];
        arr[j] = arr[j + 1];
        arr[j + 1] = temp;
      }
    }
    if (flag) break;
  }
  return arr;
}

这个是优化过后的冒泡排序。用了一个flag来优化,它的意思是:如果某一次循环中没有交换过元素,那么意味着排序已经完成了。

冒泡排序总会执行(N-1)+(N-2)+(N-3)+…+2+1趟,但如果运行到当中某一趟时排序已经完成,或者输入的是一个有序数组,那么后边的比较就都是多余的,为了避免这种情况,我们增加一个flag,判断排序是否在中途就已经完成(也就是判断有无发生元素交换)

数组去重

  1. Array.from(new Set(arr))
  2. [...new Set(arr)]
  3. for循环嵌套,利用splice去重
  4. 新建数组,利用indexOf或者includes去重
  5. 先用sort排序,然后用一个指针从第0位开始,配合while循环去重

当然还有很多,例如用filter、reduce、Map、Object等,具体可以看:

JavaScript数组去重(12种方法)

Array.from(new Set(arr))[...new Set(arr)]

var arr = [1,1,2,5,6,3,5,5,6,8,9,8];
console.log(Array.from(new Set(arr)))
// console.log([...new Set(arr)])
复制代码

for循环嵌套,利用splice去重

function unique (origin) {
  let arr = [].concat(origin);
  for (let i = 0; i < arr.length; i++) {
    for (let j = i + 1; j < arr.length; j++) {
      if (arr[i] == arr[j]) {
        arr.splice(j, 1);
        j--;
      }
    }
  }
  return arr;
}
var arr = [1,1,2,5,6,3,5,5,6,8,9,8];
console.log(unique(arr))
复制代码

新建数组,利用includes去重:

function unique (arr) {
  let res = []
  for (let i = 0; i < arr.length; i++) {
    if (!res.includes(arr[i])) {
      res.push(arr[i])
    }
  }
  return res;
}
var arr = [1,1,2,5,6,3,5,5,6,8,9,8];
console.log(unique(arr))
复制代码

先用sort排序,然后用一个指针从第0位开始,配合while循环去重

function unique (arr) {
  arr = arr.sort(); // 排序之后的数组
  let pointer = 0;
  while (arr[pointer]) {
    if (arr[pointer] != arr[pointer + 1]) { // 若这一项和下一项不相等则指针往下移
      pointer++;
    } else { // 否则删除下一项
      arr.splice(pointer + 1, 1);
    }
  }
  return arr;
}
var arr = [1,1,2,5,6,3,5,5,6,8,9,8];
console.log(unique(arr))

作者:LinDaiDai_霖呆呆
链接:https://juejin.cn/post/6844904151369908232
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值