目录
-
说说你对盒子模型的理解
当对一个文档进行布局的时候,游览器引擎根据标准之一的css基础框盒模型,将所有元素表示为一个个矩形的盒子,CSS盒子模型本质上就是一个盒子,封装周围的HTML元素,它包括外边距(margin)、边框(border)、内边距(padding)、实际内容(content)四个属性,分为标准模型和IE模型
标准模型的高度和宽度只是内容的宽高度,不包含padding和margin
IE模型的宽高包含padding和border
设置盒子模型
标准盒子模型:box-sizing:content-box
IE盒子模型:box-sizing:border-box
盒模型解决了什么:
主要是针对页面布局的时候来使用,他规范了我们页面的所有所有元素的一个布局规范是由外向内进行布局。
盒模型由外向内:margin(外边距)border(边框)padding(内边距)content(元素)
上面所说的盒模型是w3c的标准的盒子模型,在大多数浏览器上都适用,而还有一种怪异盒子模型,这种怪异模式主要表现在IE内核的浏览器。
怪异盒模型是css3的新特性
标准盒子模型和怪异盒子模型(IE的盒模型)
在标准的盒模型中一个盒子的宽度是:margin(左右外边距)+padding(左右内边距)+border(左右边框)+内容的(width).
而在怪异盒模型中:一个块的宽度=内容的(width)+margin(左右)(这里的内容width包含了padding(左右内边距)+border(左右边框))。
两种模式的转换(通过box-sizing的方法):
box-sizing中比较常用的两个属性值为 content-box 和 border-box ,它可以改变盒子模型的解析计算模式。
当设置box-sizing:content-box时,采用标准模式进行计算,默认就是这种模式;
当设置box-sizing:border-box时,采用怪异模式进行计算;
怎么解决盒子模型外边距重叠?
外边距(margin)重叠示例
外边距重叠是指两个垂直相邻的块级元素,当上下两个边距相遇时,起外边距会产生重叠现象,且重叠后的外边距,等于其中较大者。
另一个重叠现象是当一个元素包含在另一个元素之中时,子元素与父元素之间也会产生重叠现象,重叠后的外边距,等于其中最大者。
同理,如果一个无内容的空元素,其自身上下边距也会产生重叠。
外边距重叠的意义
外边距的重叠只产生在普通流文档的上下外边距之间,这个看起来有点奇怪的规则,其实有其现实意义。设想,当我们上下排列一系列规则的块级元素(如段落P)时,那么块元素之间因为外边距重叠的存在,段落之间就不会产生双倍的距离。
防止外边距重叠解决方案:
虽然外边距的重叠有其一定的意义,但有时候我们在设计上却不想让元素之间产生重叠,那么可以有如下几个建议可供参考:
外层元素padding代替
内层元素透明边框 border:1px solid transparent;
内层元素绝对定位 postion:absolute:
外层元素 overflow:hidden;
内层元素 加float:left;或display:inline-block;
内层元素padding:1px;
以上建议可根据实际情况来采取
为什么要清除浮动?清除浮动的方式
清除浮动是为了清除使用浮动元素产生的影响:浮动的元素,高度会塌陷,而高度的塌陷使我们页面后i按的布局不能正常显示。
清除浮动的方式:
1、额外标签法:给谁清除浮动,就在其后额外添加一个空白标签。
优点:通俗易懂,书写方便。
缺点:添加许多无意义的标签,结构化比较差。
2、父级添加overflow方法:可以通过触发BFC的方式,实现清除浮动的结果。
优点:代码简洁,慎用,若该父盒子里还有position定位会引起麻烦。
缺点:内容增加多时容易造成不会自动换行导致内容被隐藏掉,无法显示需要溢出的元素。
overflow:hidden;
注意:别加错位置,是给父元素加,并不是所有的浮动都需要清除,谁影响布局,才清楚谁。
3、使用after伪元素清除浮动: :after方式为空元素的升级版,好处是不用单独加标签了。
优点:符合闭合浮动思想,解构语义化正确。
缺点:由于IE6-7不支持:after,使用zoom:1,触发hasLayout。
4、使用before和after双伪元素清除浮动
注意:是给父元素添加clearfix
-
css选择器有哪些?优先级?哪些属性可以继承?
它是元素和其他部分组合起来告诉浏览器哪个HTML元素应当是被选为应用规则中的CSS属性值的方式
选择器所选择的元素,叫做“选择器的对象”
关于css属性选择器常用的有:
id选择器(#box),选择id为box的元素
类选择器(.one),选择类名为one的所有元素
标签选择器(div),选择标签为div的所有元素
后代选择器(#box div),选择id为box元素内部所有的div元素
子选择器(.one>one_1),选择父元素为.one的所有.one_1的元素
相邻同胞选择器(.one+.two),选择紧接在.one之后的所有.two元素
群组选择器(div,p),选择div、p的所有元素
还有一些使用频率相对没那么多的选择器:
伪类选择器:
:link :选择未被访问的链接
:visited:选取已被访问的链接
:active:选择活动链接
:hover :鼠标指针浮动在上面的元素
:focus :选择具有焦点的
:first-child:父元素的首个子元素
伪元素选择器:
:first-letter :用于选取指定选择器的首字母
:first-line :选取指定选择器的首行
:before : 选择器在被选元素的内容前面插入内容
:after : 选择器在被选元素的内容后面插入内容
属性选择器:
[attribute] 选择带有attribute属性的元素
[attribute=value] 选择所有使用attribute=value的元素
[attribute~=value] 选择attribute属性包含value的元素
[attribute|=value]:选择attribute属性以value开头的元素
在CSS3中新增的选择器有如下:
层次选择器(p~ul),选择前面有p元素的每个ul元素
伪类选择器:
:first-of-type 父元素的首个元素
:last-of-type 父元素的最后一个元素
:only-of-type 父元素的特定类型的唯一子元素
:only-child 父元素中唯一子元素
:nth-child(n) 选择父元素中第N个子元素
:nth-last-of-type(n) 选择父元素中第N个子元素,从后往前
:last-child 父元素的最后一个元素
:root 设置HTML文档
:empty 指定空的元素
:enabled 选择被禁用元素
:disabled 选择被禁用元素
:checked 选择选中的元素
:not(selector) 选择非 <selector> 元素的所有元素
属性选择器:
[attribute*=value]:选择attribute属性值包含value的所有元素
[attribute^=value]:选择attribute属性开头为value的所有元素
[attribute$=value]:选择attribute属性结尾为value的所有元素
优先级
内联 > ID选择器 > 类选择器 > 标签选择器
到具体的计算层⾯,优先级是由 A 、B、C、D 的值来决定的,其中它们的值计算规则如下:
如果存在内联样式,那么 A = 1, 否则 A = 0
B的值等于 ID选择器出现的次数
C的值等于 类选择器 和 属性选择器 和 伪类 出现的总次数
D 的值等于 标签选择器 和 伪元素 出现的总次数
#nav-global > ul > li > a.nav-link
套用上面的算法,依次求出 A B C D 的值:
因为没有内联样式 ,所以 A = 0
ID选择器总共出现了1次, B = 1
类选择器出现了1次, 属性选择器出现了0次,伪类选择器出现0次,所以 C = (1 + 0 + 0) = 1
标签选择器出现了3次, 伪元素出现了0次,所以 D = (3 + 0) = 3
上面算出的A 、 B、C、D 可以简记作:(0, 1, 1, 3)
知道了优先级是如何计算之后,就来看看比较规则:
从左往右依次进行比较 ,较大者优先级更高
如果相等,则继续往右移动一位进行比较
如果4位全部相等,则后面的会覆盖前面的
经过上面的优先级计算规则,我们知道内联样式的优先级最高,如果外部样式需要覆盖内联样式,就需要使用!Important
继承属性:
在css中,继承是指的是给父元素设置一些属性,后代元素会自动拥有这些属性
关于继承属性,可以分成:
字体系列属性
文本系列属性:
元素可见性
表格布局属性
列表属性
引用
光标属性
继承中比较特殊的几点:
a 标签的字体颜色不能被继承
h1-h6标签字体的大下也是不能被继承的
无继承的属性:
display
文本属性:vertical-align、text-decoration
盒子模型的属性:宽度、高度、内外边距、边框等
背景属性:背景图片、颜色、位置等
定位属性:浮动、清除浮动、定位position等
生成内容属性:content、counter-reset、counter-increment
轮廓样式属性:outline-style、outline-width、outline-color、outline
页面样式属性:size、page-break-before、page-break-after
-
元素水平垂直居中的方法有哪些?如果元素不定宽高呢?
水平居中 使用text-align 和 line-height 定位的方式,但如果不定宽高前面几种方法就不适用,就需要用到display:table和display:table-cell,table具备垂直居中的属性
Position:absolute、50%和translate在css3里提供了translate函数,他的主要作用就是位移,传给transform属性。
-
怎么理解回流跟重绘?什么场景下会触发?
在HTML中,每个元素都可以理解成一个盒子,在游览器解析过程中,会涉及到回流和重绘的概念。
回流:布局引擎会根据各种样式计算每个盒子在页面上的大小与位置
重绘:计算好盒子模型的位置、大小及其其他属性后,浏览器根据每个盒子的特征进行绘制。
游览器解析渲染机制
- 解析HTML,生成DOM树,解析CSS,生成CSSOM树
- 将DOM树和CSSOM树结合,生成渲染树(Render Tree)
- Layout回流 根据生成的渲染树,进行回流,得到节点的信息
- Painting 重绘 根据渲染树以及得到的节点信息,得到节点的绝对像素
- Display 将像素发送给GPU,展示在页面上
触发机制
回流:当页面布局和节点发生变化时触发
- 页面一开始渲染时(这无法避免)
- 元素的位置发生变化
- 添加或删除可见的DOM元素
4、节点内容发生变化,比如文本变化
重绘:触发回流一定会触发重绘,另外还有一些其他引起重绘的行为
- 颜色的修改
- 文本方向的修改
- 阴影的修改
-
什么是响应式设计?响应式设计的基本原理是什么?如何做?
响应式设计是一种网络页面设计布局,页面的设计与开发应当根据用户行为以及设备环境、系统平台、屏幕尺寸等进行相应的响应和调整,也就是说网站的布局会根据视口来调整模块的大小和位置。
实现方式
响应式设计的基本原理是通过媒体查询,检测不同的设备屏幕尺寸做处理,为了处理移动端,页面头部必须有meta声明viewport,使用百分比布局而不是使用固定单位,或者使用rem、vw/vh。
rem:相对长度单位。r’是“root”的缩写,相对于根元素<html>的字体大小。
vh and vw:相对于视口的高度和宽度,而不是父元素的(CSS百分比是相对于包含它的最近的父元素的高度和宽度)。
.vw,vh,百分比布局,vh,和vw是两个相对单位,视窗的宽度的百分之一就是1vw,vh同理,这个时候可能会产生疑惑,那么vh和vw不就和百分比是一样的,并不是,百分比的话是相对于当前元素的父元素的百分比
-
如果要做优化,CSS提高性能的方法有哪些?
内联首屏关键css,在打开一个页面,页面首要内容出现在屏幕的时间影响着用户的体验,而通过内样css关键代码能够使游览器在下载完html后能立即渲染。
异步加载css,在css文件请求、下载、解析完成之后,css会阻塞渲染,浏览器将不会渲染任何已处理的内容,前面加载内联代码后,后面的外部引入css则没必要阻塞浏览器渲染。这时候就可以采取异步加载的方法,使用JavaScript将link标签插到head标签后面
资源压缩,使用webpack、gulp、grunt、rollup等模块化工具,将css代码压缩,使文件变小,大大降低了游览器加载时间。
合理使用选择器
Css匹配规则是从右往左开始匹配的,例如#aa .content h3 匹配规则如下
先去找h3标签元素、然后去除祖先不是.content的元素、最后去除祖先不是#aa的元素。
如果嵌套的层级更多,页面的元素的更多,那么匹配所花费的时间代价也就更高。
所以我们在编写css选择器时,一般遵循以下规则
- 不要嵌套使用过多复杂选择器,最好不要三层以上
- 使用id选择器就没必要在进行嵌套
- 通配符和属性选择器效率最低,避免使用
不要使用@import
Css样式文件有两种引入方式,一种是link元素,一种是@import,而@import会影响浏览器的并行下载,使得页面在加载时增加额外的延迟。
所以css提高性能的方法可以从选择器嵌套、属性特性、减少http这三方面考虑,同时还要注意css代码的加载顺序。
-
对前端工程师这个职位是怎么样理解的?它的前景会怎么样
前端工程师是最贴近用户的程序员,作为一个前端,主要工作领域就是浏览器端,核心技术包括JavaScript、css、html等。有时,还需与产品经理、设计,共同完成一些交互设计。所以前端开发的工作就是根据静态图纸,实现重构,把图纸编辑成游览器能够识别的界面。
通过各种终端来向用户展示数据,或者给用户提供一些和后台的交互接口。
广义的来说,只要涉及展示的都属于前端,包括各种系统,图片,动画,看得见就可以。从这个角度来说,前端永远不会被抛弃,会被淘汰的只有个体,因为个体是有极限,有局限的。
个人需要精确的定位,例如web工程师,也可以是电影特效工程师,工程师还分为软件硬件呢。
前景:首先,在我看来,一切和用户交互的终端都可以属于前端。并且随着现在跨端开发框架的兴起,比如Electron框架等,也使得前端的那套开发技术栈以及开发流程可以复制到桌面端来,使得前端的范畴越来越广泛。
并且,随着AR,VR技术的兴起,手机app中应用了大量的3维场景来提高用户体验,比如手机app上看房,看车,甚至是看一个城市的街景,都已经有了3D的场景,并且用户还能进行简单的操作。而这些都对前端提出了更高的要求
具体到互联网行业的前端前景,在可见的范围内,前端承担的责任会增加而不是减少,保持进步就不会被淘汰,这点对于任何行业都一样,被抛弃的根本原因在于自身没有匹配需求的能力,而不是客观因素,那只是诱因,且必然发生。
-
说说JavaScript中的数据类型?存储上的差别?
Js中数据类型主要分为两大类 基本数据类型和引用数据类型
基本数据类型包含 number、String、Boolean、undefined、null、symbol
引用数据类型包含 function、object、array
基本数据类型是存储在栈中的,而引用数据类型存储在堆中
声明变量时不同的内存地址分配:
简单类型的值存放在栈中,在栈中存放的是对应的值
引用类型对应的值存储在堆中,在栈中存放的是指向堆内存的地址
不同的类型数据导致赋值变量时的不同:
简单类型赋值,是生成相同的值,两个对象对应不同的地址
复杂类型赋值,是将保存对象的内存地址赋值给另一个变量。也就是两个变量指向堆内存中同一个对象
-
typeof 与 instanceof 区别
Ttypeof
用于判断数据类型,返回值为6个字符串,分别为string、Boolean、number、function、object、undefined。
但是你可能会发现,typeof在判断null、array、object以及函数实例(new + 函数)时,得到的都是object。这使得在判断这些数据类型的时候,得不到真是的数据类型。由此引出instanceof。
instance中文翻译为实例,因此instanceof的含义就不言而喻,判断该对象是谁的实例,同时我们也就知道instanceof是对象运算符。
这里的实例就牵扯到了对象的继承,它的判断就是根据原型链进行搜寻,在对象obj1的原型链上如果存在另一个对象obj2的原型属性,那么表达式(obj1 instanceof obj2)返回值为true;否则返回false。
(1)总之,typeof和instanceof都是用来判断变量类型的,两者的区别在于:
typeof判断所有变量的类型,返回值有number,boolean,string,function,object,undefined。
typeof对于丰富的对象实例,只能返回"Object"字符串。
instanceof用来判断对象,代码形式为obj1 instanceof obj2(obj1是否是obj2的实例),obj2必须为对象,否则会报错!其返回值为布尔值。
instanceof可以对不同的对象实例进行判断,判断方法是根据对象的原型链依次向下查询,如果obj2的原型属性存在obj1的原型链上,(obj1 instanceof obj2)值为true。
(2)
instanceof和typeof是两个运算符,在程序设计中用到,常用来判断一个变量是否为空,或者是什么类型的。
instanceof和typeof的区别:
typeof
typeof 是一个一元运算,放在一个运算数之前,运算数可以是任意类型。
返回值是一个字符串,该字符串说明运算数的类型。
typeof 一般只能返回如下几个结果:
number,boolean,string,function,object,undefined。一般可以使用 typeof 来获取一个变量是否存在,如 if(typeof a!="undefined"){alert("ok")},而不要去使用 if(a) 因为如果 a 不存在(未声明)则会出错,对于 Array,Null 等特殊对象使用 typeof 一律返回 object,这正是 typeof 的局限性。。
-
说说你对闭包的理解?闭包使用场景
闭包是指有权访问另一个函数作用域的变量的函数,创建函数闭包的最常见的方式就是在一个函数中创建另一个函数,通过另一个函数访问这个函数的局部变量。
闭包的特性
- 函数嵌套函数
- 内部函数可以引用外部的参数和变量
- 参数和变量不会被垃圾回收机制回收
- 垃圾回收机制 js的垃圾回收机制就是定时回收闲置资源的一种机制 , 每隔一段时间, 执行环境都会清理内存中一些没用的变量释放它所占用的内存
闭包形成的条件
函数嵌套
将内部函数作为返回值返回
内部函数必须使用到外层函数的变量
缺点 闭包的缺点就是常驻内存,会增大内存使用量,使用不当很容易造成内存泄漏
使用场景
返回值 以闭包的形式将内部变量返回
把函数当成参数返回 用闭包返回一个函数,把此函数当作另一个函数的参数
循环赋值
- bind、call、apply 区别?如何实现一个bind?
三者的作用是改变函数执行的上下文,也就是改变函数运行时的this指向
Apply 接受两个参数,第一个参数是this指向,第二个参数是函数接受的参数,以数组的形式传入。改变this指向后原函数会立即执行,且此方法只是临时改变this指向一次。
Call方法第一个参数也是this指向,后面传入的是一个参数列表,跟apply一样,改变this指向后原函数会立即执行,且此方法只是临时改变this指向一次。
Bind方法和call很相似,第一个参数是this的指向,后面传入的也是参数列表(可以分多次传入),改变this指向后不会立即执行,而是返回一个永久改变this指向的函数。
- 说说你对事件循环的理解
Js在设计之初便是单线程,即指程序运行时,只有一个线程存在,同一时间只能做一件事,为了解决单线程运行阻塞问题,JavaScript用到了计算机系统的一种运行机制,这种机制就叫做事件循环(Event Loop)
在JavaScript中,所有的任务都可以分为
同步任务:立即执行的任务,同步任务一般会直接进入到主线程中执行
异步任务:异步执行的任务,比如ajax网络请求,setTimeout定时函数等
同步任务进入主线程,即主执行栈,异步任务进入任务队列,主线程内的任务执行完毕为空,会去任务队列读取对应的任务,推入主线程执行。上述过程的不断重复就是事件循环
- DOM常见的操作有哪些
获取DOM节点
document.getElementById(idName) 通过id来获取元素
document.getElementsByClassName(className) 通过class来获取元素
创建节点
document.createElement("h3") 创建一个HTML元素
document.createTextNode(String); //创建一个文本节点;
增添节点
element.appendChild(Node) //往element内部最后面添加一个节点,参数是节点类型
elelment.insertBefore(newNode,existingNode); //在element内部的中在existingNode前面插入
删除节点
element.removeChild(Node) //删除当前节点下指定的子节点,删除成功返回该被删除的节点,否则返回null
element.removeAttribute(new,old)
- 说说你对BOM的理解,常见的BOM对象你了解哪些?
BOM 游览器对象模型,提供了独立于内容与游览器窗口进行交互的对象。
其作用就是跟游览器做一些交互效果,比如如何进行页面的后退、前进、刷新、游览器的窗口发生变化,滚动条的滚动,以及获取客户的一些信息,比如游览器的品牌版本,屏幕分辨率等。游览器的全部内容可以看作DOM,整个游览器可以看成BOM
Bom的核心对象是window,是js最顶层对象,其他BOM对象都是window对象的属性。它表示浏览器的一个实例,在浏览器中,window对象有双重角色,即是浏览器窗口的一个接口,又是全局对象,因此所有在全局作用域中声明的变量、函数都会变成window对象的属性和方法。
History对象 主要用来操作游览器URL的历史记录,可以通过参数向前,向后,或者向指定URL跳转。
Location对象 游览器当前URL信息
Navigator对象 主要用来获取游览器的属性,区分游览器类型,属性较多,且兼容性比较复杂。
Screen对象 保存的纯粹是客户端能力信息,也就是游览器窗口外的客户端显示器的信息,比如像素宽度和高度
- Javascript本地存储的方式有哪些?区别及应用场景?
在JavaScript中提供了四种可用的本地存储方式:cookie、sessionStorage、localStorage、indexDB。
Cookie类型为小型文本文件,指某些网站为了辨别用户身份而存储在用户本地终端上的数据。是为了解决HTTP无状态导致的问题。Cookie的存储大小一般不超过4k,在每次请求中都会被发送,如果不使用HTTPS并对其加密,其保存的信息很容易被窃取,最后cookie的删除最常用的方法就是给cookie设置一个过期时间,这样过期时间一到就会被游览器删除。
localStorage HTML5新方法,IE8及以上游览器都兼容,无法设置过期时间,只能存字符串,持久化的本地存储,除非主动删除,否则数据是永远不会过期的。
sessionStorage sessionStorage 和localStorage使用方法基本一样,唯一不同的是一旦关闭页面sessionStorage 会自动销毁。
indexDB是一种低级API,用于客户端存储大量结构化数据,该API使用索引来实现对数据的高性能搜索。
- 什么是防抖和节流?有什么区别?如何实现?
防抖(Debounce)和节流(Throttle)是两种可以节省性能的编程技术,两者的目的都是为了优化性能,提高用户体验,都是基于DOM事件限制正在执行的javaScript数量的方法。
防抖 触发⾼频事件后n秒内函数只会执⾏⼀次,如果n秒内⾼频事件再次被触发,则重新计算时间 强制一个函数在一段时间内没有被调用之前不会被再次调用,例如仅当100毫秒过去了而没有被调用时才执行此函数。用户在不断输入值时,用防抖来节约请求资源。
节流会强制执行一个函数在一段时间内可以被调用最大的次数。例如 最多每100毫秒执行一次此函数 ⾼频事件触发,但在n秒内只会执⾏⼀次,所以节流会稀释函数的执⾏频率。鼠标不断点击触发,mousedown(单位时间内只触发一次)
- 如何通过JS判断一个数组
- 通过原型链判断
实例的__proto__属性指向其构造函数的原型对象。
- constructor 实例的constructor 属性指向构造函数本身。
- Instanceof Instanceof 可以判断Array是否是实例的构造函数
- IsPrototypeof 判断Array的原型对象是否在某个对象的原型链上。
- 通过Object原型上的方法判断
Array.isArray()来判断是否是一个数组
Object原型对象上的方法 Object.prototype.toString 判断 返回 ‘【object type】’字符串 Object.prototype.toString.call(obj)
- 说说你对作用域链的理解
作用域链的作用是保证执行环境里有权访问的变量和函数是有序的,作用域链的变量只能向上访问,变量访问到window对象即被终止,作用域链向下访问变量是不被允许的。
简单来说作用域就是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期。一般情况下如果想取一个变量的值,但如果没有在当前作用域中找到,就会去当前作用域的上一级去寻找,直到查到全局作用域,这么一个查找的过程形成的链条就叫做作用域链。
- JavaScript原型,原型链 ? 有什么特点?
每个对象都会在其内部初始化一个属性,就是prototype(原型),所有JavaScript对象都从原型继承属性和方法,这个属性指向函数的原型对象。
当我们去访问一个对象的属性时,如果这个对象内部不存在这个属性,那么就去它的原型对象上去找这个属性,同样原型对象也具有prototype属性,就这样一层一层去找,也就是平时所说的原型链的概念。
特点
一切对象都是继承自Object对象,Object对象直接继承根源对象null,也就是说Object对象的原型对象是原型链的最顶层。
一切的函数对象(包括Object对象)都是继承自Function对象
Object对象直接继承自Function对象
Function对象的原型对象,最终还会继承自Object对象
- 请解释什么是事件代理
事件代理通俗来讲就是把一个元素的响应事件(例如 click,keydown)的函数委托到另一个元素上,事件委托是在冒泡阶段完成的。事件委托会把一个或一组元素的事件委托到它的父级或更外层元素上,真正绑定事件的是外层元素而不是目标元素,当元素响应到目标元素上时,会通过冒泡机制往上走,从而触发外层元素的绑定事件上,然后在外层元素上去执行函数。
如果我们有一个列表,列表之中有许多列表项,如果给这些元素都上绑定事件,那对于内存的消耗肯定是非常大的,所以我们会使用事件委托,将事件绑定到最外层列表上,执行事件时再去找匹配的目标元素。
- 谈谈This对象的理解
This关键字是函数运行时自动生成的一个内部对象,只能在函数内部使用,总指向调用它的对象。
函数this的关键字在JavaScript中的表现略有不同,此外在严格模式和非严格模式之间也会有一些差别。
在大多数情况下,函数的调用方法决定了this的值,根据不同的使用场合,this有不同的值,主要分为下面几种情况
- 默认绑定:全局环境中定义的函数this指向的是window对象,但在严格模式下不能将全局对象用于默认绑定,this会绑定到undefined,只有函数运行在非严格模式下,默认绑定才能绑定到全局对象。
- 隐士绑定 当函数作为一个对象的方法调用时,此时this指向就是这个上级对象,不管对象中包含多少层,this指向也只是它的上一层对象,所以this永远指向的是最后调用它的对象。
- New 绑定 如果通过new关键字来生成一个实例对象,此时this指向的是这个实例对象
- 显示绑定 可以使用apply、call、bind三种方法来改变函数的上下文。
- new操作符具体干了什么
New是什么 在JavaScript中,new操作符用于创建一个给定构造函数的实例对象
New 关键字主要做了以下工作
- 创建一个新的实例对象
- 将对象与构造函数通过原型链连起来,这样就可以通过原型链来访问构造函数的属性或方法。
- 将构造函数中的this绑定到创建的对象上。
- null,undefined 的区别
Null是一个表示无的对象,转为数值时为0,而undefined是一个表示无的原始值,转为数值时为NaN。
Null表示没有对象,即该处不应该有值,典型用法是作为函数的参数,表示该函数的参数不是对象。作为对象原型链的终点。
Undefined表示缺少值,就是此处应该有一个值,但还没有定义。典型用法是
- 变量被声明了但没有赋值时,即等于undefined
- 调用函数时,应该提供的参数没有提供,该参数就为undefined
- 对象没有赋值的属性,该属性的值为undefined
- 函数没有返回值时,默认返回undefined
- javascript 代码中的"use strict";是什么意思
‘use strict’意思是严格模式,是一个字符串字面量,是ES5中增加的,用来指定代码运行与严格模式下。在严格模式下运行代码有很多限制,也有很多好处。
严格模式的优点:
- 消除JavaScript语法的一些不合理,不严谨之处,减少一些怪异行为
- 消除代码运行的一些不安全之处,保证代码运行的安全
- 提高编译器效率,增加运行速度
- 为未来新版本的JavaScript做好铺垫
缺点:
现在网站的JS都会进行压缩,一些文件用了严格模式,而另一些没有。这是这些本来是严格模式的文件,被merge后,这个串就到了文件的中间,不仅没有指示严格模式,反而在压缩后浪费了字节。
不允许重复的属性名或参数值 当检测到对象或函数中有重复的值时,严格模式会抛出错误。
在严格模式下this不会指向window。
- 同步和异步的区别
同步是阻塞模式,异步是非阻塞模式
同步是指一个进程再执行某个请求时,如果请求需要一段时间才能返回信息,那么这个进程会一直等待下去,直到收到返回信息才继续执行下去。
异步是指进程不需要一直等待,而是继续执行下面的操作,不管其他进程的状态,当有信息返回的时候会通知进程进行处理,这样就可以提高执行的效率了。
- 谈一谈箭头函数与普通函数的区别
1、外形不同:箭头函数使用箭头定义,普通函数中没有
2、箭头函数都是匿名函数,普通函数可以有匿名函数,也可以有具名函数,但是箭头 函数都是匿名函数。
3、箭头函数不能用于构造函数,不能使用new,但是普通函数可以
4、this指向不同,在不同函数中,this总是指向它的调用者,如果用作构造函数,则this指向创建的对象实例。而箭头函数本身其实并没有this,它只是在声明时可以捕获其所在上下文的this供自己使用。箭头函数的this永远指向其上下文的this,任何方法都改变不了其指向。
5、箭头函数没有arguments参数,取而代之用rest参数...解决,每个普通函数调用后都具有一个arguments对象,用来存储实际传递的参数,但是箭头函数并没有此对象。
6、箭头函数不具有prototype对象、箭头函数不具有super。
- JS 数组和对象的遍历方式,以及几种方式的比较
- For in 循环:遍历的是数组则输出的是索引,如果遍历的是对象,输出的则是对象的属性名。for in循环需要分析出array中的每个属性,这个操作性能开销很大。用在key已知的数组上是非常不划算的。 所以尽量不要用for in,除非不清楚要处理哪些属性,例如JSON对象这样的情况。
2、for循环:循环一次,就要检查一下数组长度。读取属性要比局部变量慢,尤其是当array里存放的都是DOM元素,因为每次读取都会扫描页面上的选择器相关元素,速度会大大降低。
3、forEach循环:forEach回调中的两个参数分别为遍历的元素和索引值,它无法遍历对象,无法使用break、continue跳出循环。
- 如何解决跨域问题
跨域指的是游览器不能执行其他网站的脚本,它是由游览器的同源策略(即协议、域名、端口号必须相同)造成的,是游览器施加的安全限制。
游览器执行JavaScript脚本时,会检查这个脚本属于哪个页面,如果不是同源页面,就不会被执行。
如何解决跨域问题
- 跨域资源共享(cors)
游览器将CORS请求分成两类,简单请求和预检请求。
解决方法就是服务器在响应头中加入字段Access-control-Allow-Origin:origin,那么该Origin就可以访问了,如果该字段值为*,那么所有Origin就都可以访问。
- Jsonp
通常为了减轻web服务器的负载,我们把js、css、img等静态资源分离到另一台独立域名的服务器上,在html页面中再通过相应的标签从不同域名下加载静态资源,而被游览器允许,基于此原理,我们可以通过动态创建script,在请求一个带参网址实现跨域通信。
Jsonp只能发起GET请求
为了实现跨域请求可以通过script标签实现跨域请求,然后在服务器端输出JSON数据并执行回调callback函数,从而解决跨域数据请求。
web页面上调用js文件时则不受是否跨域的影响,不仅如此凡是拥有src属性的标签都拥有跨域的能力比如<script>、<img>、<iframe>、<a>
- vue中设置代理服务器
跨域问题只存在游览器,如果是服务器和服务器之间的通信是不存在跨域问题的。
可以在vue-cli中设置代理服务器实现跨域请求。
- XML和JSON的区别
- json是JavaScript Object Notation XML是可扩展标记语言
- JSON是基于JavaScript语言,XML源自于SGML
- JSON是一种表示对象的方式,XML是一种标记语言,使用标记结构来表示数据项。
- JSON不提供对命名空间的任何支持,XML支持命名空间
- JSON支持数组,XML不支持数组。
- JSON的安全性较低,XML比JSON更安全
- JSON不支持注释,XML支持注释
- JSON仅支持UTF-8编码,XML支持各种编码
- 谈谈你对webpack的看法
WebPack是一个模块打包工具、可以使用WebPack管理模块,并分析模块间的依赖关系,最终编译输出模块为HTML、JavaScript、CSS以及各种静态文件(图片、字体等),让开发更加高效。
对于不同的资源,webpack有对应的模块加载器loader比如说
Css 解析css的css-loader、style-loader
解析less的less-loader、sass的sass-loader
Js 解析将ts转化为js的ts-loader
解析ES6为ES5的babel-loader
Vue 解析vue文件的vue-loader
解析常用图片以及音频资源的url-loader
解析文件的file-loader
解析JSON文件的json-loader
webpack基本功能
1、代码转换:ts编译成js、ES6转为ES5、scss编译成css等各种loader
2、代码语法检查:自动检测代码是否符合语法(eslint-loader)
3、代码分割:打包代码时,可以将代码切割成不同的块,实现按需加载,降低了初始化时间,提高了首屏渲染效率
4、监测代码更新,自动编译,刷新页面:监听本地源代码的变化,自动构建,刷新游览器(自动刷新)
5、自动发布:更新完代码后,自动构建出线上发布代码并传输给发布系统。
6、文件压缩:压缩js、css、html代码,缩小文件体积
7、模块合并:由于模块化的开发,一个页面可能会由多个模块组成,所以编译时需要把各个模块合并成一个文件
Webpack两大特点
- 代码切割
- Loader可以处理各种类型的静态资源,并且支持串行操作webpack以CommonJs规范来书写代码,但对AMD/CMD的支持也是很全面,方便对项目进行代码迁移。
- webpack的打包原理。
Webpack打包原理是根据文件间的依赖关系对其静态资源、当webpack处理程序时,它会递归的构建一个依赖关系图,其中包含应用程序需要的每个模块,将所有模块打包成一个或者多个bundle(包)
流程如下
- 通过配置文件找到打包文件入口,一般都是一个js文件
- 在入口文件通过import或者require推断解析出依赖的模块
- 分别解析每个模块对应的依赖,形成体现整个项目依赖关系的依赖树
- 递归依赖树,找到每个节点对应的资源文件
- 通过rules中的配置找到每个资源文件对应的loader,对资源进行加载
- 将通过loader加载的结果放到打包结果bundel.js中
- 如何优化webpack打包速度
- 技术的迭代
- 对webpack、node、npm,yarn等技术进行版本升级,相关技术的版本更新,官网都会做出优化,升级技术可以一定程度的提高打包的性能。
2、在模块上尽可能少的应用loader
- 合理使用exclude/include可以降低使用loader的频率从而提高打包速度,如exclude:/node_modules,因为node_modules中的代码是已经被转换过的代码,可以不必使用loader做二次转换。也尽可能地让loader的作用范围缩小,代码分析的范围少了,性能也就能得到提高
3、plugin尽可能精简并确保可靠
- 插件需要合理的使用,对于没有意义的插件或冗余的插件应当去掉,在使用插件时应首选官方推荐或社区认可的插件。这样在开发时,代码的打包速率在一定程度上会得到提升。
- 合理使用resolve.extensions
- 在开发中我们会有各种各样的模块依赖,这些模块可能来自于自己编写的代码,也可能来自第三方库,resolve可以帮助webpack从每个require/import语句中,找到需要引入到合适的模块代码,通过resolve.extensions是解析到文件时自动添加拓展名,当我们引入文件时,若没有文件后缀名,则会根据extensions数组内的值依次查找,当我们配置时,不要随便把所有后缀名都写在里面,这会调用多次文件的查找,会减慢打包速度。
- 使用DLLPlugin提高打包速度
- 因为第三方库的文件不会改变,不必每次打包时都分析,所以可以对第三方模块只打包一次,生成dll文件,引入第三方模块时去使用dll文件引入。
- 说说webpack中常见的Loader?解决了什么问题?
在webpack内部中,任何文件都是模块,不仅仅是js文件
默认情况下,在遇到import或者require加载模块时,webpack只支持对js和json文件打包,像css、sass、png等这些类型的文件webpack则无法解析,这时候就需要配置对应的loader进行文件内容的解析。
常用的loader有
Style-loader、css-loader 解析css
Less-loader 解析less格式
Sass-loader 解析sass格式
File-loader 解析文件
Url-loader 解析图片以及音频资源
Vue-loader 解析vue文件
- 说说webpack中常见的Plugin?解决了什么问题?
Plugin插件的意思,是一种计算机应用程序,它和主程序互相交互,以提供特定的功能,那么在webpack中plugin目的在于解决loader无法实现的其他事。
常见的plugin
- CommonsChunkPlugin 提取chunks之间共享的通用模块(Chunk是过程中的代码块,Bundle是结果的代码块)
- DefinePlugin 允许在编译时配置的全局常量
- DllPlugin 为了极大减少构建时间,进行分离打包
- IgnorePlugin 从bundle中排除某些模块
- LoaderOptionsPlugin 用于从webpack1迁移到webpack2
- 说说你对promise的了解
Promise介绍
promise是异步编程的一种解决方案,比传统的解决方案(回调函数)更加合理和加强。
使用then方法链式操作减低了编码难度,代码的可读性明显增强。
Promise的状态
Promise对象仅有三种状态 pending(进行中)、fulfilled(已成功)、rejected(已失败)
Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject
Resolve函数的作用是将promise对象的状态从未完成变为成功、reject函数的作用是将promise对象的状态从未完成变为失败。
Promise 特点 对象的状态不受外界影响、只有异步操作的结果可以决定当前是哪一种状态,一旦状态改变,就不会在变,任何时候都可以得到这个结果。
Promise实例方法有then、catch、finally
Then是实例状态发生变化时的回调函数,第一个参数是resolved状态的回调函数,第二个参数时rejected状态的回调函数,then方法返回的是一个新的promise实例,也就是promise能链式书写的原因。
Catch方法用于指定发生错误时的回调函数,promise对象的错误具有冒泡性质,会一直向后传递,直到被捕获为止。
Finally方法用于指定不管promise对象最后状态如何,都会执行的操作。
- async函数是什么,有什么作用
Async函数是使用async关键字声声明的函数。Async函数是asyncFunction构造函数的实例,并且其中允许使用await关键字。
Await有等待的意思,需等待后面的promise执行结束后才会执行下一步,await后面可以跟promise,和其他数据类型,当后面是promise时,值是执行成功状态的返回值,如果是其他数据时,返回值就是数据本身。
Async和await关键字让我们可以用一种更简洁的方式写出基于promise的异步行为,无需刻意的链式调用promise。
Async语法
Async函数总是返回一个promise对象,可以使用.then接受一个回调函数,和promise的所有操作。
Promise的状态改变的几种情况
- 当await其中一个promise执行失败,那么接下来的await不会继续执行,async函数返回promise的状态变为失败,执行.catch方法。
- 当执行到return,则下面的await不会执行,return的值就是.then回调函数的参数。
- 当throw new Error 下面的状态不会执行,async函数返回promise的状态变为失败,执行catch方法
- 当所有异步操作都能成功时,那么只有所有异步操作执行完后,async函数返回promise的状态变为成功,返回值是return的值
- 有使用过vue吗?说说你对vue的理解
Vue是一个用于构建用户界面的渐进式框架,是基于JavaScript语言构建的,其中渐进式的概念就是主张最少,先使用vue的核心库,再根据需要去逐渐增加相应的插件。
Vue的核心特性
- 数据驱动,vue采用MVVM的设计模式
- M就是模型,负责数据存储,
- V是视图负责页面展示,也就是用户界面
- VM模型视图,负责业务逻辑,对数据进行加工,交给视图展示
- 通过vue类创建的Vue实例对象就等于是MVVM中的VM层,模型可以通过它将数据绑定到视图,视图也可以通过它将数据映射到模型上。
- 组件化
- 每一个文件都是一个组件,把一些重复的代码单独封装到文件中,在需要的地方调用即可,降低系统的耦合度,提高重用性、可维护性、方便调试。
- 耦合度就是对象之间的依赖性。对象之间的耦合度越高,维护成本越高。
- 你对SPA单页面的理解,它的优缺点分别是什么?如何实现SPA应用呢
单页面应用SPA是一种网络应用程序,它将所有的活动局限于一个web页面中,仅在该web页面初始化时加载相应的HTML、JavaScript、CSS。一旦页面加载完成,spa不会因为用户的操作而重新加载或跳转。取而代之的是利用JavaScript动态的变换HTML的内容,从而实现UI与用户的交互。由于避免了页面的重新加载,spa可以提供较为流畅的用户体验。得益于ajax,我们可以实现无跳转刷新,又多亏浏览器的history机制,我们可以用hash的变化来推动界面的变化,从而实现客户端单页面的切换效果。
Spa优点
- 良好的交互式体验,内容的改变不需要重新加载整个页面,避免了不必要的跳转和重复渲染。
- Spa对服务器的压力小
- 前后端职责分离,架构清晰,前端进行交互逻辑,后端负责数据处理
Spa缺点
- 初次加载耗时较长,为实现单页面web应用功能及显示效果,需要在加载页面时将JavaScript、css统一加载,部分页面按需加载。
- 由于单页面应用是在一个页面中显示,所以不可以使用浏览器自带的前进后退功能,想要实现页面切换需要自己进行管理
实现一个spa
监听地址栏中hash变化驱动界面变化
用pushState记录浏览器的历史,驱动界面发生变化
监测hash或者pushstate变化-->以当前hash为索引,加载对应资源-->等待资源加载完毕,隐藏之前的界面,执行回调渲染新的页面。
- SPA首屏加载速度慢的怎么解决?
首屏时间,指的是浏览器从响应用户输入网站地址,到首屏内容渲染完成的时间,此时整个网页不一定要全部渲染完成,但需要展示当前视窗需要的内容。首屏加载可以说是用户体验中最重要的环节。
Spa加载时间慢的原因
- 网络延迟问题
- 资源文件是否体积过大
- 资源是否重复发送请求去加载了
- 加载脚本时,渲染内容堵塞了
解决方案
- 减小入口文件
- 常用手段时路由懒加载,把不同路由对应的组件分割成不同的代码块,待路由被请求时会单独打包路由,使得入口文件变小,加载速度大大增加。
- 路由懒加载实现方式,可以使用vue-router配置路由,配置路由文件时采用require引入组件,或者使用import引入组件,这样以函数的形式加载路由,只有在解析给定的路由时,才会加载对应的路由组件
- 静态资源本地缓存
- 后端返回资源采用http缓存
- 前端合理使用local Storage
- UI框架按需加载
- 图片资源的加载,可以对图片资源进行适当的压缩,减轻http请求的压力
- VUE路由的原理
路由的概念起源于服务端,在以前前后端不分离时,是由后端来控制路由,当接受到 客户端发来的请求就会根据所请求的url来找到对应的映射函数并将函数的返回值发 送给客户端。
而后来就提出了前端路由的概念 前端路由的特点就是改变url不刷新页面,对url变化的监听,更新视图模块
根据不同的hash值或者不同路径地址,将不同的内容渲染到router-view中
Vue路由实现有两种模式 hash模式 history模式
Hash模式的url是以#开头,常作为锚点在页面内进行导航,是基于location.hash来实现的location.hash的值就是url中#后的内容,当hash改变时页面不会因此刷新,游览器也不会请求服务器,而是通过键值对获取对应哈希值来进行渲染视图模块。
History主要是利用HTML5History来操作游览器历史记录栈,主要方法有back,go来读取游览器路由历史记录并控制跳转,HTML5新增pushState,replaceState两个方法来修改历史记录,调用这两个方法修改历史信息后,虽然当前url改变了,但游览器不会立即请求该url,这就满足单页面应用更新视图但不重新请求页面的需求。
- Vue中组件和插件有什么区别?
组件是什么
- 组件就是把图形、非图形的各种逻辑均抽象为一个统一的概念(组件)来实现开发的模式,在Vue中每一个.vue文件都可以视为一个组件。
组件的优势
- 降低整个系统的耦合度,在保持接口不变的情况下,我们可以替换不同的组件快速完成需求。
- 调试方便,在出现问题时,可以用排除法直接移除组件来判断到底是哪个组件出现了问题。
- 提高可维护性,由于组件都是单独的只是在其他地方得到复用,所以对代码优化只需更改一个地方即可。
插件是什么
- 插件通常用来为Vue添加全局功能,插件的功能范围没有严格规定,一般有下面几种
- 添加全局方法或属性。
- 添加全局资源指令、过滤器等
- 或者添加Vue实例方法,通过把他们添加到Vue.prototype上实现。
两者区别
编写形式
-
- 编写一个组件最常见的形式就是vue单文件这种格式,每一个.vue文件都属于一个组件,而编写插件应该暴露一个install方法,这个方法的第一个参数是Vue构造器,第二个参数是一个可选对象
注册形式
- Vue组件注册主要分为全局注册和局部注册
- 全局注册通过Vue.component方法,第一个参数为组件名称,第二个参数为配置项,而插件注册同通过Vue.use方法来进行注册
- Vue组件之间的通信方式都有哪些
- Props 适用于父组件向子组件传递数据,但子传父也能通过props来实现
- 状态提升(中间人模式)适用于兄弟组件传值 先将一个组件的数据传递给父组件再通过父组件给到另一个组件
- $emit和$on 这种方式适合于任何组件通信,一般用与兄弟组件之间传递
- 全局事件总线 在给Vue实例上绑定一个全局事件总线 所有组件都能调用到
- Vuex 适合于任何组件通信 vuejs的状态管理库
- Provide和inject 父级给子孙组件传值
- 你了解vue的diff算法吗?说说看
Diff算法介绍
- Diff算法的目的是为了找到那些节点发生了变化,那些节点没有发生变化,可以复用。
- 具体实现就是进行虚拟DOM对比,并返回一个patch对象,来存储两个节点不同的地方,最后用patch记录的消息去局部更新DOM
- Diff算法比较只会在同层级进行,不会跨层级比较
虚拟DOM概念:它是一层对真实DOM的抽象,以JavaScript对象作为基础的树,用对象的属性来描述节点,最终可以通过一系列操作使这棵树映射到真实环境上。
那么vue中vue2和vue3都有各自的diff算法
在Vue2中,当数据发生变化时,它就会新生成一个虚拟DOM树,并和之前的DOM树进行比较,找到不同的节点然后更新。但比较的过程是全量比较,也就是每个节点都会彼此比较,但有些节点中的内容是不会发生改变的,那我们对其比较肯定就浪费了不必要的时间,所以在Vue3中,就对这部分内容进行了优化,在创建虚拟DOM树时,会根据DOM中的内容会不会发生变化,添加一个静态标记。那么之后再与上次虚拟节点进行对比时,就只会对比这些带有静态标记的节点。
- 为什么需要 Virtual Dom
虚拟DOM概念:实际上它是一层对真实DOM的抽象,以JavaScript对象作为基础的树,用对象的属性来描述节点,最终可以通过一系列操作使这棵树映射到真实环境上。
在javaScript对象中,虚拟DOM表现为一个object对象,并且最少包含标签名(tag)、属性(attrs)和子元素对象(children)三个属性,不同框架对这三个属性的命名可能会有差别。
创建虚拟DOM就是为了更好将虚拟的节点渲染到页面视图中,所以虚拟DOM对象节点与真实DOM的属性是一一对应。
在操作DOM时,需要更新10个dom节点,浏览器没那么智能,收到第一个更新DOM请求时,并不知道后续还有9次更新,因此会马上执行流程,最终执行9次更新操作,而通过虚拟DOM,同样更新10个DOM节点,虚拟DOM不会立即操作DOM,而是将这10次更新的diff内容,保存在本地的一个js对象中,最终将这个js对象一次性更新,避免了大量无谓的运算。
- Vue3.0的设计目标是什么?做了哪些优化
更快、更小、TypeScript支持、API设计一致性、提高自身可维护性、开放更多底层功能,一句话概括就是更小更快更友好了。
更小 vue3移除一些不常用的API 引入了tree-shaking,可以将无用模块剪辑,仅打包需要的,使打包体积变小了。
更快 主要体现在编译方面 diff算法优化 静态提升 事件监听缓存 SSR优化
更友好 vue3在兼顾vue2的options API的同时还推出了composition API,大大增加了代码的逻辑组织和代码复用能力。
优化方案
可以分为三个方面 源码、性能、语法API
源码 将不同功能的模块拆分到packages目录下不同的子目录中
性能 体积优化、编译优化、数据劫持优化 在vue2中数据劫持是通过Object.defineProperty,这个API有一些缺陷,它并不能检测到对象属性的添加和删除,在面对层级嵌套较深的情况下就存在性能问题,相比之下vue3是通过proxy来监听整个对象,那么对于删除还是添加就都能监听到。
语法API 在vue2中会存在两个非常明显的问题,命名冲突和数据来源不清晰,而通过vue3中的composition将一些复用的代码抽离出来作为一个函数。
- Vue3.0 所采用的 Composition Api 与 Vue2.x 使用的 Options Api 有什么不同?
Options API 又称为选项API
在vue2中我们组织代码会在vue文件中methods、computed,watch,data,中等等定义方法或属性,共同处理页面逻辑。
其优缺点
- 条理清晰 相同的放在相同的地方,但随着组件功能的增大关联性会大大降低,组件的阅读和理解难度会增加。
- 调用使用this,但逻辑过多时this会出现问题,比如指向不明等;
Options的缺陷
一个功能往往需要在不同的vue配置项定义属性和方法,比较分散,需求复杂之后就会多出watch、computed、等配置,随着文件逐渐增大,往往会分不清哪个方法对应哪个功能。
Vue3中的Composition API就是用来解决这个问题的,通过组合的方式,把零散在各个data、methods中的代码,重新组合,一个功能都放在一起维护,并且这些代码可以单独拆分成函数,这样其代码更易读,不受模板和组件范围的限制,也可以准确的知道我们可以使用哪些属性。
- 说一下Vue数据响应式的原理
Vue2中响应式实现核心是通过observer函数来遍历data中的每个属性,对这些属性依次做响应式处理,而所谓的响应式处理则是拦截属性的get、set方法做订阅-发布处理。订阅-发布处理就是在属性被调用时触发get代理函数订阅调用该属性的组件,而在属性被修改时,触发set代理函数,在set代理函数中,通知订阅者数组里的每一个订阅者进行视图更新。
Vue2响应式原理的不足
- 动态添加响应式属性必须使用Vue.Set
- 直接操作数组索引无法触发视图更新
- 数据的响应式处理和视图未完全解耦
Vue3响应式相对于vue2最大的区别在于用到了ES6中的方法proxy。这个方法不需要直接遍历循环每个属性,加上响应式处理。而是直接代理了整个对象,拦截这个对象所包含的所有属性的get、set方法这么做的好处就是我们在动态为data添加一个属性时,不用做任何处理,这个属性就是响应式的。数组的任何操作也都可以触发响应。
- 说说对 React 的理解?有哪些特性?
React是用于构建用户界面的JavaScript库,只提供了UI层面的解决方案。
React遵循组件设计模式、声明式编程和函数式编程概念,使得前端应用程序更高效。
React特性
- Jsx语法 js语法的一种扩展,有点类似于模板语法,用来声明React中的元素
- 单向数据流,在React中,数据是自顶向下单向流动的,即从父组件到子组件
- 虚拟DOM,react使用虚拟DOM,不总是直接操作页面真实DOM,大大提高了效率。
- 声明式编程 这是一种编程范式,它关注的是你要做什么,而不是如何做
- Component,在React中,一切皆为组件。通常将应用程序的整个逻辑分解为单个部分,我们将每个单独的部分称为组件
- 说说 Real DOM 和 Virtual DOM 的区别?优缺点?
Real DOM指的是真实DOM,意思为文本对象模型,是一个结构化文本的抽象,在页面渲染出的每个节点都是一个真实DOM结构。
Virtual DOM,指的是虚拟DOM,本质上是以JavaScript对象形式存在的对DOM的描述,创建虚拟DOM目的就是为了更好的将虚拟节点渲染到页面视图中,虚拟DOM对象的节点与真实DOM的属性一一对应。
区别
- 虚拟DOM不会进行回流和重绘操作,而真实DOM会频繁回流和重绘
- 虚拟DOM的总损耗是虚拟DOM增删改+真实DOM差异增删改+回流和重绘,真实DOM的总损耗是真实DOM完全增删改+回流和重绘
优缺点
- 真实DOM优势是易用,缺点是效率低,解析速度慢,内存占用量过高,性能差:频繁操作真实DOM,易于导致重绘与回流。
- 虚拟DOM的优势,简单方便:如果手动操作真实DOM,繁琐又容易出错,维护起来也很困难
- 性能方面:使用Virtual DOM,能够有效避免真实DOM频繁更新,减少多次引起回流和重绘,提高性能
- 跨平台:React借助虚拟DOM,带来了跨平台的能力,一套代码多端运行
- 缺点 在一些性能要求极高的应用中虚拟DOM无法进行针对性的极致优化
- 在首次渲染大量DOM时,由于多了一层虚拟DOM的计算,计算速度比正常稍慢。
- 说说 React 生命周期有哪些不同阶段?每个阶段对应的方法是?
React生命周期分为三个阶段,挂载阶段、更新阶段、卸载阶段
挂载阶段
constructor() 在 React 组件挂载之前,会调用它的构造函数。
componentWillMount: 在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。
componentDidMount(): 在组件挂载后(插入 DOM 树中)立即调用
更新阶段
componentWillReceiveProps: 在接受父组件改变后的props需要重新渲染组件时用到的比较多,外部组件传递频繁的时候会导致效率比较低
shouldComponentUpdate():用于控制组件重新渲染的生命周期,state发生变化,组件会进入重新渲染的流程,在这里return false可以阻止组件的更新
render(): render() 方法是 class 组件中唯一必须实现的方法。
componentWillUpdate()*: shouldComponentUpdate返回true以后,组件进入重新渲染完成之前进入这个函数。
componentDidUpdate(): 每次state改变并重新渲染页面后都会进入这个生命周期
卸载或销毁阶段
componentWillUnmount (): 在此处完成组件的卸载和数据的销毁。
- 说说 React中的setState执行机制
一个组件的显示状态可以由数据状态和外部参数决定,而数据状态就是state,当需要修改里面值的状态时,就必须通过调用setState来改变,从而达到更新组件内部数据的作用。
setState第一个参数可以是一个对象,或者是一个函数,而第二个参数是一个回调函数,可以实时获取到更新之后的最新数据。
在使用setState更新数据的时候,setState的更新类型分为两类,分别是同步更新、异步更新。
在组件生命周期或者React合成事件中,setState是异步的,在setTimeout或者原生dom事件中,setState是同步的。
在对同一个值多次setState,setState的批量更新策略会对其进行覆盖,取最后一次的执行结果。
- 说说对React中类组件和函数组件的理解?有什么区别?
类组件,顾名思义就是通过class类去编写的组件,该类必须继承React.Component,如果想要访问父组件传来的参数可通过this.props去访问,在类组件中必须声明render方法,在其内部return一个React对象。
函数组件,函数组件相对类组件来说就比较简单,就是通过纯函数的形式来实现一个React组件,声明的函数组件可以接受一个props参数来接受父组件传递来的数据。
区别
编写形式:类组件使用ES6中的class关键字来声明一个类,并且需要继承自React.Component,而函数组件就直接是一个纯函数。
管理状态:类组件在其内部可以声明state来保存组件的状态,而函数组件则没有,属于无状态组件。
生命周期:在函数组件中是不存在生命周期的,这是因为这些生命周期函数都来自于继承的React.Component,所以如果要使用生命周期就只能使用类组件。
调用方法:如果是函数组件调用时直接执行函数即可,如果是类组件,则需要创建一个实例,通过实例调用这个实例的render方法。
- 说说对React Hooks的理解?解决了什么问题?
Hook是React16.8的新增特性。它可以让你在编写无状态组件时也能够使用state内部状态以及其他的React特性。
至于为什么引入hook,官方给出的动机是解决长时间使用和维护React过程中常遇到的问题,例如:
- 在组件之间复用状态逻辑很难
- 逻辑复杂的组件难以开发与维护,当我们的组件需要处理多个互不相关的状态时,每个生命周期函数中可能包含着各种互不相关的逻辑在里面
- 类组件中的this增加学习成本,在使用两种类型不同的组件时还要区分两种组件的使用场景。
常见的hooks
useState:在函数组件中通过useState实现函数内部维护state,参数为state默认值,返回值是一个数组,第一个值为当前的state,第二个值为更新state的函数,类似与类组件中的setState。
useEffect:
这个hook函数相当于是componentDidMount、componentDidUpdate和componentWillUnmount这三个生命周期函数的组合。
useEffect需要传递两个参数,第一个参数是逻辑处理函数,第二个参数是一个数组。第二个参数存放变量,当数组存放的变量发生改变时,第一个参数,逻辑处理函数将会被执行,第二个参数也可以不传,不会报错,但浏览器会无限循环执行逻辑处理函数。
第二参数也可以只传一个空数组,逻辑处理函数就只会在组件挂载时触发一次,就相当于componentDidMount。
- 说说你对Redux的理解?其工作原理?
当我们构建应用时,会存在很多个组件,每个组件的state是由自身进行管理的,包括组件定义的state、组件之间传递的props、使用Context实现数据共享。
如果我们让每个组件都存储自身相关的状态,理论上来将不会影响应用的运行,但在开发后续维护阶段,我们将花费大量时间取查询状态的变化过程。
这种情况下,如果将所有的状态进行集中式管理,那么后续维护将会变得简单许多,redux就是用来实现集中式管理的容器,他遵循三大基本原则:
- 单一数据源
- State是只读的
- 修改状态时使用纯函数来执行修改
Redux并不只是应用在react中,还可以与其他界面库一起使用,如vue
工作原理
Redux要求我们把数据都放在store公共存储空间,一个组件改变了store里的数据内容,其他组件就能感知到store的变化,再来取数据,从而间接的实现了这些数据传递的功能,通常要改变store数据时,需要通过dispatch方法调用actions进入到reducer函数中去修改store的数据内容。
- 说说 React 性能优化的手段有哪些
- 前端通用优化。这类优化在所有前端框架中都存在,重点就在于如何将这些技巧应用在React组件中。
- 懒加载,在SPA中,懒加载优化一般用于从一个路由跳转到另一个路由。还可用于用户操作后才展示的复杂组件,比如点击按钮后展示的弹窗模块。
- 按优先级更新,及时响应用户,其思想是优先响应用户行为,在完成耗时操作。
- 缓存优化,缓存优化往往是最简单有效的优化方式,在React组件中常用到useMemo缓存上次计算的结果。当useMemo的依赖未发生变化时,就不会触发重新计算。
- 减少不必要的组件更新,这类优化是在组件状态发生变更后,通过减少不必要的组件更新来实现,对应到React中就是:减少渲染的节点、降低组件渲染的复杂度、充分利用缓存避免重新渲染。
- 如果是类组件,可以使用shouldComponentUpdate生命周期函数,可以在重新渲染组件时触发,可以利用此事件来决定何时重新渲染组件。
- 不要使用内联函数定义,会导致每次调用render函数时都会创建一个新的函数实例。
- 不要再render方法中导出数据。
- 在函数组件中使用useCallback和useMemo来进行组件优化,依赖没有变化的话,不重复执行。
- vue、react、angular 区别
三者相同点
- 都是基于JavaScript/typescript的前端开发框架,为前端开发提供高效、复用性高的开发方式
- 都有组件和模板的开发思想
- 各自的组件都有生命周期、不用的组件可以卸载,不占用资源
- 都支持指令、如样式、事件的指令
三者不同之处
- 创始和发行不同
- Angular是由Google提供支持的,初始发行与2016年9月;React由Facebook维护,初始发行与2013年3月;Vue是由前Google人员创建,初始发行与2014年2月
- 数据流流向不同
- Angular使用的是双向数据绑定,React用的是单向数据流,而Vue则支持两者。
- 对微应用和微服务的支持不同
- Angular使用的是Typescript,因此它更适合与单页面Web应用,而非微服务,相反,React和Vue的灵活性更适合微应用和微服务的开发。
- 框架和库
- Angular是一个框架而不是一个库,因为它提供了关于如何构建应用程序的强有力的约束,并且还提供了更多开箱即用的功能,React和Vue都算是一个库,可以和各种包搭配。
- 说说你对 TypeScript 的理解?与 JavaScript 的区别
JavaScript是一种轻量级的解释性脚本语言,可嵌入到HTML页面中,在游览器端执行,能够实现浏览器端丰富的交互功能,为用户带来流畅多样的用户体验。
JavaScript是基于对象和事件驱动的,无需特定的语言环境,只需在支持的游览器上就能运行。
TypeScript是Microsoft开发和维护的一种面向对象的编程语言。它是JavaScript的超集,包含了JavaScript的所有元素,可以载入JavaScript代码运行,并扩展了JavaScript的语法。
Typescript可以使用JavaScript中的所有代码和编程概念,Typescript是为了使JavaScript的开发变得更加容易而创建的。
两者对比
- Typescript中的数据要求带有明确的类型,JavaScript不要求
- Typescript通过类型注释提供编译时的静态类型检查
- JavaScript代码可以在无需任何修改的情况下与Typescript一同工作,同时可以使用编译器将Typescript代码转化为JavaScript
- Typescript中也有模块的概念,可以把声明、数据、函数和类封装在模块中
- 说说你对 TypeScript 中泛型的理解?应用场景?
概念:泛型,是程序设计语言的一种风格或范式,泛型允许我们在强类型程序语言中编写代码时使用一些以后才指定的类型,在实例化时作为参数指明这些类型在typescript中,定义函数,接口或类时,不预先定义好具体的类型,而在使用的时候在指定类型的一种特性。
Typescript可以使用泛型来创建可重用的组件,这种组件不只能被一种类型使用,而是能被多种类型复用。支持当前数据类型,同时也能支持未来的数据类型。扩展灵活。可以在编译时发现你的类型错误,从而保证了类型安全。
应用场景:当函数、接口或类将处理多种数据类型时,或者在多个地方使用该数据类型时。
- 说说你对微信小程序的理解?优缺点?
小程序是一种不需要下载安装即可使用的应用,它实现了应用触手可及的梦想,用户扫一扫或者搜一下即可打开应用,也体现了用完即走的理念,用户不用关心是否安装太多应用的问题。应用无处不在,随时可用,但又无需安装卸载。
小程序可以视为只能用微信或其他平台打开和浏览的H5页面,小程序和网页的技术模型是一样的,用到的JavaScript语言和css样式也是一样的,因此可以说小程序页面本质上就是网页。
优点:
- 随搜随用,用完即走,使得小程序可以替代许多APP,或时作为阉割版本的APP。
- 流量大,易接受:小程序借助自身平台比较容易引入更多的流量。
- 安全,开发门槛低,降低兼容性限制。
缺点:
- 小程序只有2M的大小,这样导致无法开发大型的小程序。
- 不能跳转外链网址,所以间接影响了小程序的开放性。
- 需要像APP一样审核上架,这点比HTML5即做即发布要麻烦些。
- 说说你对发布订阅、观察者模式的理解?区别?
观察者模式
- 当对象之间存在一对多的依赖关系时,其中一个对象的状态发生变化,所有依赖它的对象都会收到通知,这就是观察者模式。
- 在观察者模式中只有两种主体:目标对象(Object)和观察者(Observer)。
- 当目标对象自身发生变化时通过调用自身的notify方法依次通知每个观察者执行update方法(可以执行自定义的业务逻辑)
发布订阅模式
- 发布订阅是一种消息范式,消息的发送者(称为发布者)不会将消息直接发送给特定的接收者(称为订阅者)。而是将发布的消息分为不同的类别,无需了解到哪些订阅者可能存在。
- 发布订阅中有三个角色,发布者(Publisher),事件调度中心(Event Channel),订阅者(Subscriber)。
- 发布者和订阅者不知道对方的存在,需要第三方,将订阅者和发布者串联起来,利用调度中心来过滤和分配所有的消息。
区别
- 在观察者模式中,观察者是知道目标对象的,并且目标对象一直保持对观察者进行记录。然而,在发布订阅中,发布者和订阅者不知道对方的存在,只是通过中间调度来实现消息通信。
- 在发布订阅中,组件是松散耦合的,正好和观察者相反
- 观察者大多数是同步的,而发布订阅模式大多数是异步的。
- 项目做过哪些性能优化
- 尽量减少http请求
- 压缩源码和静态资源
- 使用外部JavaScript和CSS
- 图片懒加载
- 源码优化
- 代码模块化 将重复的代码封装成组件,以得到复用的效果
- 写循环语句时设置key值,可以快速定位到该节点,提升性能
- 减少DOM操作:大量的DOM操作会导致游览器重复的渲染
- 首屏渲染优化
- 合理使用游览器缓存
- 描述浏览器的渲染过程,DOM树和渲染树的区别
渲染过程
- 解析HTML代码,生成DOM树,并行请求css/image/js
- CSS文件下载完成,开始构架CSSOM(CSS树)
- Css树(CSSOM)构架完毕后,和DOM一起生成Render Tree(渲染树)
- 布局,计算出每个节点在屏幕中的位置
- 显示,通过显卡把页面展示到屏幕上
DOM树和渲染树的区别
- DOM树与HTML元素一一对应,包括head和隐藏元素
- 渲染树不包括head和隐藏元素,大段文本的每一个行都是独立节点,每一个节点都有对应的css属性。
- 你认为什么样的前端代码是好的
- 代码可复用、这样文件小,好维护,而且好扩展
- 可读性高,更容易读懂代码,最好带有注释
- 代码是低耦合的,耦合性,也叫耦合度,模块间的耦合度是指模块间的依赖关系,模块间关系越多,其耦合性越强,同时代表其独立性越差。
- 从浏览器地址栏输入url到显示页面的步骤
- 首先,在游览器地址栏输入url
- 游览器先查看游览器缓存,如果缓存中有,会直接在屏幕中显示内容,若没有则跳到第三步操作。
- 在发送http请求前,需要域名解析(DNS解析),解析获取相应的IP地址。
- 游览器向服务器发起tcp连接,与服务器建立tcp三次握手
- 三次握手其实就是指建立一个TCP连接时,需要客户端和服务器总共发送三个包,进行三次握手的主要作用就是为了确认对方的接收和发送能力是否正常。
- 握手成功后,游览器向服务器发送http请求,请求数据包。
- 服务器处理收到的请求,将数据返回至游览器
- 游览器收到http响应,接收传递来的资源
- 如果得到的资源可以缓存,进行缓存
- 读取页面内容,游览器渲染,解析HTML代码
- 生成DOM树、解析css样式、js文件
- 最后渲染到页面
- http 请求报文响应报文的格式
HTTP请求报文主要由请求行、请求头、空行、请求正文
请求行:请求行是由请求方法字段、URL字段、和HTTP协议版本字段三部分组成,它们用空格分隔。比如GET /data/info.html HTTP/1.1,方法字段就是HTTP使用的请求方法。
请求头: 在请求头中存在cookie、客户端的主机名和端口等信息
空行:它的作用是通过一个空行,告诉服务器请求头部到此为止
请求体:可选部分,比如GET请求就没有请求正文
HTTP响应报文主要由状态行、响应头部、响应正文
状态行:状态行格式分别为协议版本、状态码、状态码描述,之间由空格分隔。其中状态码由三位数字组成,第一个数字定义了响应的类别,且有物种可能取值
- 1XX:指示信息--表示请求已接受,继续处理
- 2XX:成功--表示请求已被成功接收、理解
- 3XX:重定向
- 4XX:客户端错误
- 5XX:服务端错误
响应头部:与请求头部类似,为响应报文添加了一些附加信息,常见的有Server服务器的应用程序的名称和版本,Content-Type响应正文的类型等。
响应正文:服务器返回给服务端的响应数据
- Token cookie session 区别
Cookie由服务端生成,发送给浏览器,游览器把cookie以键值对的形式存储到本地,每次请求时网站会自动把cookie发送给服务端,每个cookie存储空间为3-4Kb,不会占据大量内存,但是每个站点的cookie数量是有限的。
Session 由服务端存储,是基于cookie实现的,session本身存储在服务端,但服务端会将sessionID发送给浏览器保存到cookie中,每次请求时会携带上sessionID去服务端。
Token是服务器经过计算生成的一串加密字符串,作为客户端进行请求的令牌,当第一次登陆时,服务器会生成一个token并返回给客户端,当客户端发起请求时携带上Token到服务端,如果验证成功就向客户端返回请求的数据。
- CORS跨域的原理
CORS是跨域资源共享,它是W3C标准,属于跨域AJAX请求的根本解决方法。
CORS跨域原理就是,只需要服务器端设置Access-Control-Allow-Origin。
服务器端对于CORS的支持,主要是通过设置Access-Control-Allow-Origin来进行的。如果游览器检测到相应的设置,就可以允许AJAX进行跨域的访问。
- 什么是MVVM
MVVM是Model-View-ViewModel的简写,即模型层-视图层-视图模型
模型指的是后端传递的数据
视图指的是所看到的页面
视图模型是MVVM模式的核心,它是连接view和model的桥梁
MVVM采用的是双向数据绑定,view中数据变化将反映到model上,反之,model中数据变化也会自动展示在页面上,ViewModel负责把Model的数据同步到View中显示出来,也负责把View的修改同步会Model。
MVVM的核心思想,是关注model的变化,让MVVM框架利用自己的机制自动更新DOM,也就是所谓的数据-视图分离,数据不会影响视图。
- 说说你对版本管理的理解?常用的版本管理工具有哪些?
版本控制就是一种软件工程技巧,借此能在开发的过程中,确保由不同人所编辑的同一程序文件都得到同步。通过版本控制,能记录任何工程项目内各个模块的改动历程,并为每次改动编上序号,版本控制能通过这些序号将程序恢复到之前的任意一状态,简言之,你的修改只要提交到版本控制系统,基本都能找回,版本控制系统就像一台时光机器,可以让你回到任何一个时间点。
版本管理根据类别可以分为三类
- 本地版本控制系统
此系统较为简单,很多系统中都有内置,适合管理文本。但是不支持远程操作,因此不适合多人版本开发
- 集中式版本控制系统
适合多人团队协作开发,代码集中化管理,但是必须联网,无法单机工作
- 分布式版本控制系统
适合多人团队协作开发、代码集中化管理、可以离线工作、每一个计算机都是一个完整仓库
常用的版本管理工具有CVS、Git、SVN、HG
- 说说你对Git的理解?
Git是一个分布式版本控制软件,最初目的是为了更好地管理Linux内核开发而设计的。
分布式版本控制系统的客户端不只是提取最新版本的文件快照,而是把代码仓库完整的克隆下来。这么一来,如果任何一处协同工作的服务器发生故障,那么都可以用任何一个克隆出来的本地仓库恢复。
项目开始,只有一个原始版仓库,别的机器可以clone这个原始版本库,那么所有clone的机器,它们的版本库其实都是一样的,并没有主次之分。
所以在实现团队协作的时候,只要有一台电脑充当服务器的角色,其他每个人都从这个“服务器”仓库clone一份到自己的电脑上,并且各自把各自的提交推送到服务器仓库里,也从服务器仓库中拉取别人的提交。
- 说说Git常用的命令有哪些
Git config 配置用户信息
Git init 初始化仓库
Git clone url 克隆代码
Git add . 提交全部文件到缓存区
Git diff 查看当前代码add后,会add哪些内容
Git status 查看当前分支状态
Git pull <state> 拉取远程仓库的分支与本地当前分支合并
Git commit -m “注释信息” 提交代码到本地仓库
Git merge <分支名> 合并分支
Git fetch 获取线上最新版信息记录,不合并
Git remote -v 显示所有远程仓库
Git push 上传本地仓库代码到远程仓库
- 说说 git 发生冲突的场景?如何解决?
发生冲突的场景
- 多个分支代码合并到一个分支时
- 多个分支向同一个远程仓库分支推送
具体情况就是,多个分支修改了同一个文件或者多个分支修改了同一个文件的名称。
当本地修改的文件和目标远程仓库的同一个文件都有修改,这时,无论是pull/push/merge时都会发生冲突。
解决方法:
修改文件之前先git pull 获取远程最新的代码,同步完之后在对代码进行修改。
- 前端工程化的特点
前端工程化可以分成四个方面来说,分别为模块化、组件化、规范化和自动化。
模块化
模块化是指将一个文件拆分成多个相互依赖的文件,最后进行统一的打包和加载,这样能够很好的保证高效的多人协作。其中包含
JS 模块化:CommonJS、AMD、CMD 以及 ES6 Module。
CSS 模块化:Sass、Less、Stylus、BEM、CSS Modules 等。其中预处理器和 BEM 都会有的一个问题就是样式覆盖。而 CSS Modules 则是通过 JS 来管理依赖,最大化的结合了 JS 模块化和 CSS 生态,比如 Vue 中的 style scoped。
资源模块化:任何资源都能以模块的形式进行加载,目前大部分项目中的文件、CSS、图片等都能直接通过 JS 做统一的依赖关系处理。
组件化
不同于模块化,模块化是对文件、对代码和资源拆分,而组件化则是对 UI 层面的拆分。
通常,我们会需要对页面进行拆分,将其拆分成一个一个的零件,然后分别去实现这一个个零件,最后再进行组装。 在我们的实际业务开发中,对于组件的拆分我们需要做不同程度的考量,其中主要包括细粒度和通用性这两块的考虑。 对于业务组件,你更多需要考量的是针对你负责业务线的一个适用度,即你设计的业务组件是否成为你当前业务的 “通用” 组件。
规范化
正所谓无规矩不成方圆,一些好的规范则能很好的帮助我们对项目进行良好的开发管理。规范化指的是我们在工程开发初期以及开发期间制定的系列规范,其中又包含了
项目目录结构
编码规范:对于编码这块的约束,一般我们都会采用一些强制措施,比如 ESLint、StyleLint 等。
联调规范
文件命名规范
样式管理规范:目前流行的样式管理有 BEM、Sass、Less、Stylus、CSS Modules 等方式。
git flow 工作流:其中包含分支命名规范、代码合并规范等。
定期 code review … 等等
自动化
从最早先的 grunt、gulp 等,再到目前的 webpack、parcel。这些自动化工具在自动化合并、构建、打包都能为我们节省很多工作。而这些只是前端自动化其中的一部分,前端自动化还包含了持续集成、自动化测试等方方面面。
以上就是我所了解的前端工程化,以工程的角度去理解我们的web前端。工程是工程,而不是某项技术。
25万+

被折叠的 条评论
为什么被折叠?



