
高级Web前端必会面试题知识点,大厂面试必备
高级Web前端工程师必会面试题,这里只是整理一些范围知识点,并没有特别具体的面试题目,只要把这些知识点搞明白了,面试题都不是问题。文档持续更新中。。。加油骚年!!
高级Web前端工程师必会面试题,这里只是整理一些范围知识点,并没有特别具体的面试题目,只要把这些知识点搞明白了,面试题都不是问题。
最近整理了一套 2021最新最全的前端面试题集锦(持续更新中),内容涉及前端各个方面,并且每道题目基本都有对应得答案解析,是你面试必备宝典。
骚年,加油!高薪等你。
目录
- 一、HTML+CSS 系列
- 二、JavaScript 系列
-
-
- 1、变量对象 + 作用域链 + this(执行上下文)
- 2、闭包
- 3、原型与原型链
- 4、继承
- 5、函数节流和防抖
- 6、函数柯里化与反柯里化
- 7、设计模式
- 8、ES6新增API(`Proxy`, `Reflect`, `Promise`, `Generator`, `async`, `Decorator`, `Class`)
- 9、简述ES6的新特性
- 10、浏览器渲染页面的过程
- 11、浏览器缓存(强缓存,协商缓存)
- 12、浏览器端 Event loop
- 13、路由实现原理
- 14、session、cookie 与 token 认证
- 15、跨域的解决方案
- 16、Ajax (axios、fetch、原生xhr)
-
- 三、webpack 系列
- 四、nodejs 系列
- 五、其他
- 六、Vue 高级
- 七、React 高级
一、HTML+CSS 系列
1、布局的三种模式
-
弹性盒布局
flex
盒子是并列的,可以设置指定宽度,轻松实现两栏,三栏布局。
但是,flexbox
布局方式对浏览器的支持不太友好,有一些兼容性问题,但是,这应该是未来发展的趋势。 -
浮动布局
float
float
布局是目前各大网站用的最多的一种布局方式了。
通过给元素设置float属性来实现元素浮动,浮动的元素是脱离文档流的,但是不脱离文本流。特点:
- 对自身:
float
元素可以形成块,可以让行内元素变成块元素,也拥有宽和高;- 浮动元素的位置尽量靠上;
- 设置
float:left
或float:right
,如果这一行满足不了浮动元素的宽度,则会被挤到下一行。
- 对兄弟元素:
- 不影响其他块元素的位置,但影响其他块元素的文本。
- 对父元素:
- 造成高度塌陷
- 解决高度塌陷的方法:给父元素添加
overflow:hidden;
。overflow:auto/scroll
或者display:flex/inline-flex/table
等,也都可以解决高度塌陷,也是因为触发了父元素为BFC
- 详细讲解:BFC深层解读
- 解决高度塌陷的方法:给父元素添加
- 造成高度塌陷
- 对自身:
-
响应式布局
- 最简单的方式是加上一个
meta
标签:<meta name="viewport" content="width=device-width, initial-scale=1">
- 其中
width = device-width
这一句的意思是让页面的宽度等于屏幕的宽度。
- 其中
rem
是指html
的font-size
的大小, 根据rem
来计算各个元素的宽高,然后在配合media query
就可以实现自适应。@media query
语法
@media screen and (max-width: 360px) { html { font-size: 12px; } }
- 最简单的方式是加上一个
2、移动端布局适配设备的方案
rem
布局rem
以根元素字体大小作为参照的布局方式,可以实现等比缩放布局,不管内容是多大的,显示的内容是一样的。
- 百分比布局
%
- 页面宽度为100%,高度固定,当屏幕越大,显示的内容也就越多。
- 特点:控件弹性,图片等比例缩放,文字流式
3、1px
边框的解决方案
参考文章:移动端 1px 解决方案(完整版)
- 原因:
在高分辨率的显示屏中,像素比dpr
为2或者3,1px边框看起来比真实的1px边框看起来更宽 - 方案:
- 伪类 +
transform
:把原先元素的border
去掉,然后利用:before
或者:after
重做border
,并transform
的scale
缩小0.5 或者 0.33,原先的元素相对定位,新做的border
绝对定位。- 根据像素比值
(dpr => device-pixel-ratio)
设置缩放比例:- 当 dpr > 2.5 时设置 scale(0.333)
- 当 2.49 > dpr > 1.5 时设置 scale(0.5)
- 当 dpr < 1.49 时设置 scale(1)
- 此方式,所有场景都能满足,支持圆角(伪类和本体类都需要加
border-radius
)。但是,代码量也很大,对于已经使用伪类的元素,可能需要多层嵌套。
- 根据像素比值
viewport
+rem
:- 根据
meta
设置对应viewport
的rem
基准值,这种方式就可以像以前一样轻松愉快的写1px了。 - 此方式,所有场景都能满足,一套代码,可以兼容基本所有布局。但是,老项目修改代价过大,只适用于新项目。
- 根据
border-image
- 通常手机端的页面设计稿都是放大一倍的,如:为适应iphone retina,设计稿会设计成750*1334的分辨率,图片按照2倍大小切出来,在手机端看着就不会虚化,非常清晰。同样,在使用
border-image
时,将border
设计为物理1px
- 通常手机端的页面设计稿都是放大一倍的,如:为适应iphone retina,设计稿会设计成750*1334的分辨率,图片按照2倍大小切出来,在手机端看着就不会虚化,非常清晰。同样,在使用
background-image
- 伪类 +
4、BFC(Block Formatting Contexts)
5、BootStrap布局原理
- Bootstrap 是一个用于快速构建Web应用程序和网站的前端框架,是基于html、css、js。
- 栅格布局:
Bootstrap 内置了一套响应式、移动设备优先的流式栅格系统,会随着屏幕设备或者视口(Viewport
)尺寸的增加,系统会自动分为最多12列。
网格系统的实现原理也是非常简单的,只需要通过定义容器的大小,然后平分12份(也有24,32的,但是12是最常见的),再调整内外边距,最后结合媒体查询,就可以实现了这种栅格布局系统。 - 优点:自适应布局,友好的移动端布局
- 缺点:不兼容IE,需要修改bootstrap的样式
- 相关问题:
- 分辨率:xs(超小屏幕手机 < 780px)、sm(小屏幕平板 >= 780px)、md(中等屏幕桌面显示器 >= 992px)、lg(大屏幕大桌面显示器 >= 1200px)
- bootstrap 栅格还有24格的 要怎么设置:分别在十二的基础上嵌套,每个嵌套一个!
- container 与 container-fluid 有什么区别:container是文本居中、container-fluid占整行
6、Sass/Less/Stylus区别,联系,在什么场景下使用
- CSS预处理器(Sass/less/stylus):就是一种专门的编程语言,为css增加一些编程的特性,将css作为目标生成文件,然后就可以使用这种语言进行编程。可以让代码更加简洁、适应性更强、可读性更强、易于代码的维护。
- 区别与联系:
见如上文章详解。 - 使用场景:
- Sass:在编写代码时,需要用到变量、嵌套、混合、导入等高级操作时,可以更好的组织管理样式文件,更高效的开发项目。
- Less:它更适用于皮肤、模板等整体框架固定死的网站制作,比如论坛、空间
- Stylus:可编程
- StyledComponents:在React中使用,编写实际的css代码来设计组件样式,不需要组件和样式之间的映射,创建成功后就是一个React组件
二、JavaScript 系列
1、变量对象 + 作用域链 + this(执行上下文)
详细讲解:深入理解JavaScript的函数作用域
-
变量对象:如果变量与执行上下文相关,那变量自己应该知道它的数据存储在哪里,并且知道如何访问,这种机制就是变量对象(VO)。
变量对象是一个与执行上下文相关的特殊对象,它存储着在上下文中声明的变量、函数声明、函数形参等内容。
-
全局对象:是在进入任何执行上下文之前就已经创建了的对象。
全局对象只存在一份,它的属性在程序中任何地方都可以访问,全局对象的生命周期终止于程序退出那一刻。
-
作用域链与原型链:
详细讲解:
【JavaScript高级】原型与原型链- 区别:
1.作用域链是对于变量而言,原型链是对于对象的属性。
2.作用域链顶层是window
,原型链顶层是Object
。 - 联系:从链表开始查找,直到找到为止。
this
指向:取决于被调用的方式:
- 如果普通的函数调用,非严格模式下,
this
指向window
,严格模式下,this
是undefined
; - 如果是对象调用的方式,
this
指向该对象; - 如果是
call()
、apply()
或者bind()
方式调用,this
指向被绑定的对象; - 如果是构造函数调用方式,
this
指向实例化出来的新对象; - 如果是箭头函数,是根据当前的词法作用域来决定
this
,具体来说,箭头函数会继承外层函数调用的this
绑定。
- 区别:
2、闭包
- 闭包:就是能够读取其他函数内部变量的函数
- 变量的作用域分为全局变量和局部变量两种。而 js 中的函数内部可以直接读取全局变量,但是函数外部无法读取函数内部的局部变量。如果想要取得函数内部的变量,就要在函数的内部再定义一个函数,将函数作为返回值返回,就可以在函数的外部读取他的内部变量了。
- 闭包的作用:
- 可以读取函数内部的变量
- 让这些变量的值始终保存在内存中 => 可能导致内存泄漏
- 闭包的
this
指向:外部函数的this
指向调用他的对象,内部函数的this
指向了全局对象。
3、原型与原型链
- 原型(
__proto__
对象属性):原型为同一个构造函数new
出来的实例对象提供了一个公共的区域来存放共同的属性和方法。- js规定,每一个函数都有一个
prototype
对象属性,指向另一个对象,prototype
的所有属性和方法,都会被构造函数的实例继承。这就意味着,我们可以把那些不变(公共)的属性和方法,直接定义在prototype
对象属性上。prototype
就是调用构造函数所创建的那个实例对象的原型。 prototype
可以让所有的对象实例共享它所包含的属性和方法。也就是说,不必再构造函数中定义对象信息,而是可以直接将这些信息添加到原型中。
- js规定,每一个函数都有一个
- 为什么要使用原型:可以节省一定的内存,特别是需要构建多个实例的时候。
- 原型链:实例对象与原型之间的连接,叫做原型链。
- js在创建对象的时候,都有一个叫做
__proto__
的内置属性,用于指向创建它的函数对象的原型对象prototype
。
- js在创建对象的时候,都有一个叫做
- 内部原型(
__proto__
)和构造器原型(prototype
)- 每个对象都有一个
__proto__
属性,原型链上的对象正是依赖这个属性连结在一起。 - 作为一个对象,当访问其中的一个属性或者方法的时候,如果这个对象中没有这个方法或属性,那么js引擎将会访问这个对象的
__proto__
属性所指向的上一个对象,并在那个对象中查找指定的方法或属性,如果不能找到,那就会继续通过这个对象的__proto__
属性指向的对象进行向上查找,直到这个链表结束。
- 每个对象都有一个
4、继承
-
前提:提供父类(继承谁,提供谁的属性)
-
分类:
1、原型链继承:可以让新实例的原型等于父类的实例
- 特点:实例可继承的属性有:实例的构造函数的属性、父类构造函数属性、父类原型的属性。(新实例不会继承父类实例的属性)
- 缺点:
1. 新实例无法向父类构造函数传参
2. 继承单一
3. 所有新实例都会共享父类实例的属性(原型上的属性是共享的,一个实例修改了原型属性,另一个实例的原型属性也会被修改)
2、构造函数继承:用
.call()
和.apply()
将父类构造函数引入子类函数(在子类函数中做了父类的复制)- 特点:
1. 只继承了父类构造函数的属性,没有继承父类原型的属性
2. 解决了原型链继承的缺点
3. 可以继承多个构造函数属性(call
多个)
4. 在子实例中可以向父实例传参 - 缺点:
1. 只能继承父类构造函数的属性
2. 无法实现构造函数的复用(每次用都要重新调用)
3. 每个新实例都有父类构造函数的副本
3、组合继承(原型链继承 + 构造函数继承):结合了两种模式的优点,传参和复用
- 特点:
1. 可以继承父类原型上的属性,可以传参,可复用
2. 每个新实例引入的构造函数属性是私有的 - 缺点:调用了两次父类构造函数(耗内存),子类的构造函数会代替原型上的那个父类构造函数。
4、原型式继承:用一个函数包装一个对象,然后返回这个函数的调用,这个函数就变成了一个可以随意增添属性的实例或对象,
Object.create()
就是这个原理- 特点:类似于复制一个对象,用函数来包装
- 缺点:
1. 所有实例都会继承原型上的属性
2. 无法实现复用(新实例属性都是后面添加的)
5、寄生式继承:就是给原型式继承外面套个壳子
- 特点:没有创建自定义类型,因为只是套了个壳子返回对象,这个函数就成了创建的新对象。
- 缺点:没有用到原型,无法复用
6、寄生组合继承:(常用)修复了组合继承的问题
- 寄生:在函数内返回对象然后调用
- 组合:
1. 函数的原型等于另一个实例;
2. 在函数中用apply
或者call
引用另一个构造函数,可传参
5、函数节流和防抖
- 函数的节流和防抖是优化高频率执行js代码的一种手段,js中的一些事件在执行触发时,会不断调用 绑定在事件上的回调函数,极大的浪费资源,降低性能。为了优化体验,需要对这类事件进行调用次数的限制,此时引入函数节流防抖。
- 节流(throttle):控制事件发生的频率,比如控制为1s发生一次,甚至1分钟发生一次。
- 应用场景:
scroll
事件,滚动监听事件,每隔一段时间计算一次位置信息等- 浏览器的播放事件,每隔1s计算一次进度信息
input
框实时搜索并发送请求展示下拉列表,每隔1s发送一次请求。(防抖也可以)- 高频点击提交,表单重复提交
- 代码实现:
function throttle(fn, delay) { let timer; return function () { let _this = this; let args = arguments; if (timer) { return; } timer = setTimeout(function () { fn.apply(_this, args); timer = null; // 在delay后执行完fn之后清空timer,此时timer为假,throttle触发可以进入计时器 }, delay) } }
- 防抖(debounce):防止抖动,以免把一次事件误执行多次,影响性能。
- 应用场景:
- 登录注册、发短信等按钮避免用户点击过快,导致多次发送请求,需要防抖
- 调整浏览器窗口大小时,resize次数过于频繁,造成计算过多,此时需要一次到位,需要防抖
- 文本编辑器实时保存,无任何更改操作一段时间后自动保存
mousemove
、mouseover
鼠标移动事件防抖- 搜索框搜索输入,只需要用户最后一次输入完,在发送请求防抖
- 手机号、邮箱验证输入检测
- 代码实现:
function debounce(fn, delay) { let timer; // 维护一个 timer return function () { let args = arguments; if (timer) { clearTimeout(timer); } timer = setTimeout(()=> { fn.apply(this, args); // 用apply指向调用debounce的对象,相当于this.fn(args); }, delay); }; }
- 比较:
- 相同点:
- 都可以通过使用
setTimeout
实现 - 目的都是为了降低回调执行频率,节省计算资源
- 不同点:
- 函数防抖:在一段连续操作结束后,处理回调,利用
clearTimeout
和setTimeout
实现。 - 函数节流:在一段连续操作中,每一段时间只执行一次,频率较高的事件中使用它来提高性能。
- 函数防抖关注一定时间连续触发的事件只在最后执行一次,而函数节流侧重于一段时间内只执行一次。
- 总结:
- 防抖:防止抖动,单位时间内事件触发会被重置,避免事件被误伤触发多次。代码实现重在清零
clearTimeout
。防抖可以比作等电梯,只要有人进来,就需要再等一会。业务场景有避免触发按钮多次重复提交。 - 节流:控制流量,单位时间内事件只能触发一次。代码实现重在开锁关锁
timer = timeout; timer = null
。节流可以比作红绿灯,每等一个红灯时间就可以过一批。
- 防抖:防止抖动,单位时间内事件触发会被重置,避免事件被误伤触发多次。代码实现重在清零
6、函数柯里化与反柯里化
- 柯里化(currying):是一种编程技术,主要就是把原本接收多个参数的函数变成只接受一个参数的函数,并且返回一个接收剩余参数的函数。
- 应用场景:
1. 封装一些含有环境判断的方法时,函数柯里化可以帮助我们减少判断条件执行的次数;
2. 在封装函数节流、防抖、bind此类返回值是函数的方法时,会用到函数柯里化;
3. 可用与 vue、react、小程序中的事件传参,在事件绑定时就执行回调函数传参,根据传参值返回真正的事件callback
函数。
- 应用场景:
- 反柯里化:扩大方法的适用范围。
1. 可以让任何对象拥有其他对象的方法(改变原来方法上下文)
2. 增加被反柯里化方法接收的参数
7、设计模式
设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。
- 工厂模式
工厂模式定义了一个用于创建对象的接口,用户只负责传递需要的参数,不需要关心内部的逻辑,隐藏了创建实例的复杂度,最后返回一个实例。 - 单例模式
单例模式是指在内存中只会创建且仅创建一次对象的设计模式。单例模式很常用,比如全局缓存、全局状态管理等,这些只需要一个对象,就可以使用单例模式。比如 Redux、Vuex的store。 - 观察者模式(发布-订阅模式)
观察者模式中一般都需要实现三个接口:subscribe()
接收观察者,使其订阅;unsubscribe()
取消订阅;fire()
触发事件,通知到所有观察者。 - 装饰器模式
装饰器模式不需要改变已有的接口,它的作用是给对象添加功能。比如 react里的高阶组件。 - 适配器模式
适配器用来解决两个接口不兼容的问题,不需要改变已有的接口,通过包装一层的方式实现两个接口的正常协作。我们其实经常使用到适配器模式。比如父组件传递给子组件一个属性,组件内部需要使用computed
计算属性来做处理,这个过程就用到了适配器模式。
8、ES6新增API(Proxy
, Reflect
, Promise
, Generator
, async
, Decorator
, Class
)
-
Proxy
:用于修改某些操作的默认行为,也可以理解为在目标对象之前架设一层拦截,外部所有的访问都必须先通过这层拦截,因此提供了一种机制,可以对外部的访问进行过滤和修改。这个词的原理为代理,在这里可以表示由它来“代理”某些操作,译为“代理器”。- 基本用法:ES6原生提供给了Proxy构造函数,用来生成Proxy实例。
Proxy对象的所有用法,都是通过这种形式。不同的只是handle参数的写法。其中new Proxy用来生成Proxy实例,target是表示所要拦截的对象,handle是用来定制拦截行为的对象。var proxy = new Proxy(target, handler);
- 基本用法:ES6原生提供给了Proxy构造函数,用来生成Proxy实例。
-
Reflect
:是一个全局的普通的对象,原型是Object。- 目的:
1. 将Object对象的一些属于语言内部的方法放到Reflect对象上,从Reflect上能拿到语言内部的方法。如:Object.defineProperty;
2. 修改某些object方法返回的结果。如:Object.defineProperty(obj, name, desc)在无法定义属性的时候会报错,而Reflect.defineProperty(obj, name, desc)则会返回false;
3. 让Object的操作都变成函数行为。如object的命令式:name in obj和delete obj[name] 则与 Reflect.has(obj, name)、Reflect.deleteProperty(obj, name)相等;
4. Reflect对象的方法与Proxy对象的方法一一对应,只要proxy对象上有的方法reflect也能找到。
- 目的:
-
Promise
:是一个专门解决异步回调地狱的问题。-
所谓 Promise,简单点来说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果,promise是一个对象,它可以从获取异步操作的消息,promise提供了统一的API,各种异步操作都可以用同样的方法进行处理。
- 特点:
- 对象的状态不受外界影响,promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)、rejected(失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
- 一旦状态改变就不会再变,任何时候都可以得到这个结果,promise对象的状态改变,只有两种可能:从 pending => fulfilled,从pending => rejected。这个就称为 resolved。如果改变已经发生,再对 promise 对象添加回调函数,也会立即得到这个结果,这与事件(event)完全不同,事件的特点是:如果你错过了它,再去监听是得不到结果的。
- 用法:
- Promise 是一个构造函数,这个构造函数有两个参数,分别是 resolve(成功之后的回调函数)和 reject(失败之后的回调函数)
因为 Promise 表示的是一个异步操作,每当我们 new 一个 promise 实例,就表示一个具体的异步操作,那么这个异步操作的结果就只能有两种状态:成功/失败,两者都需要回调函数 resolve/reject返回。所以内部拿到操作结果后,无法使用 return 把操作结果返回给调用者,这个时候只能用回调函数的形式来吧成功或者失败的结果返回给调用者。 - promise 实例生成以后,可以用 then方法分别指定 resolved 状态和 rejected 状态的回调函数,then方法可以接收两个回调函数作为参数,第一个回调函数是 promise 对象的状态变成 resolved 时调用,第二个回调函数是 promise 对象的状态变为 rejected 时调用。第二个回调函数是可选的,这两个函数都接受 promise 对象传出的值作为参数。
-
-
Generator
:与平常的函数不同,它可以理解为是一个分布执行的函数,返回值是一个遍历器。- 用法:
1. 外部可以通过next(),thow()和return()调用,只是调用的形式不同。
2. 在应用方面主要是异步调用,不同于以前的回调函数和Promise(Promise算是对回调函数解决嵌套繁琐问题提出的)
3. 它在每一个yield中部署自己的异步操作,等到需要执行的时候再调用。
4. generator函数和Ajax可以一起进行同步操作。
5. 它的分布执行的特性决定了它对耗时大的多步操作有很大的改进(generator如果你不执行,那之后的程序系统不会编译)。
6. 部署Iterator接口:generator函数可以再任何对象上部署Iterator接口。
- 用法:
-
async/await
:回调地狱的终极解决方案,使用它可以把异步代码写的看起来像同步代码。- await 是一个函数中的关键字,要求函数必须是 async 声明的函数。当 await 中的方法执行完毕或者返回后执行后续代码。
-
Decorator
:修饰器,是一个函数,用来修饰类的行为。不过目前主流浏览器都没有很好的支持,我们需要用babel来转换为浏览器能识别的语言。- 装饰器在javascript 中仅仅可以修饰类和属性,不能修饰函数。
- 装饰器对类的行为的改变,是代表编译时发生的,而不是在运行时。
- 装饰器能在编译阶段运行代码。
- 装饰器是经典的AOP模式的一种实现方式。
- 执行顺序:同一处的多个装饰器是按照洋葱模型,由外到内进入,再由内到外执行。
-
Class
:类,通过class关键字可以定义类。- class 关键字的出现使得其在对象写法上更加清晰,更像是一种面向对象的语言。
- 注意:
1. 在类中声明方法的时候,千万不要给该方法加上 function 关键字
2. 方法之间不要用逗号分割,否则会报错 - 用法:
1. 类自身指向的就是构造函数,所以可以认为 ES6 中的类其实就是构造函数的另一种写法。
2. 类的所有方法都定义在类的prototype属性上,也可以通过prototype属性对类添加方法。
3. 可以通过 Object.assign() 来为对象动态增加方法。
4. constructor方法是类的构造函数的默认方法,通过new命令生成对象实例时,自动调用该方法。
5. constructor方法如果没有显式定义,会隐式生成一个constructor方法。所以即使你没有添加构造函数,构造函数也是存在的。constructor方法默认返回实例对象this,但是也可以指定constructor方法返回一个全新的对象,让返回的实例对象不是该类的实例。
6. 类的所有实例共享一个原型对象,他们的原型都是 Person.prototype,所以proto属性是相等的。
7. class不存在变量提升,所以需要先定义再使用。
9、简述ES6的新特性
- 不一样的变量声明:const和let
- 模板字符串
- 箭头函数(Arrow Functions)
- 函数的参数默认值
- Spread / Rest 操作符
- 二进制和八进制字面量
- 对象和数组解构
- 对象超类
- for…of 和 for…in
- ES6中的类 Class
10、浏览器渲染页面的过程
-
浏览器从服务器那收到的HTML,CSS,JavaScript等相关资源,然后经过一系列处理后渲染出来的web页面。
-
过程:
- 浏览器将获取的HTML文档并解析成DOM树。
- 处理CSS标记,构成层叠样式表模型CSSOM(CSS Object Model)。
- 将DOM和CSSOM合并为渲染树(rendering tree)将会被创建,代表一系列将被渲染的对象。
- 渲染树的每个元素包含的内容都是计算过的,它被称之为布局layout。浏览器使用一种流式处理的方法,只需要一次pass绘制操作就可以布局所有的元素。
- 将渲染树的各个节点绘制到屏幕上,这一步被称为绘制painting。
以上五个步骤并不一定一次性顺序完成,比如DOM或CSSOM被修改时,亦或是哪个过程会重复执行,这样才能计算出哪些像素需要在屏幕上进行重新渲染。而在实际情况中,JavaScript和CSS的某些操作往往会多次修改DOM或者CSSOM。
11、浏览器缓存(强缓存,协商缓存)
- 缓存:就是一个资源副本,当我们向服务器请求资源后,会根据情况将资源copy一份副本存在本地,方便下次读取。缓存最根本的作用是减少没必要的请求,使用缓存可以减少时长,从而优化用户体验,减少流量消耗,减轻服务器的压力。
- 强缓存:直接从本地副本比对读取,不去请求服务器,返回的状态码是 200(expires 和 cache-control)
- 协商缓存:会去服务器比对,若没改变才直接读取本地缓存,返回的状态码是 304(last-modified 和 etag)
- 获取缓存的流程:
1. 先根据这个资源的 http header 判断它是否命中强缓存,如果命中,则直接从本地缓存中获取资源,不会则向服务器请求 资源。
2. 当强缓存没有命中时,客户端会发送请求到服务器,服务器通过另一些request header验证这个资源是否命中协商缓存,这个过程成为http再验证,如果命中,服务器直接返回请求而不返回资源,而是告诉客户端之间从缓存中获取,客户端收到返回后就直接从客户端获取资源。
3. 强缓存和协商缓存的共同之处在于:如果命中缓存,服务器不会返回资源;区别是:强缓存不发送请求打服务器,但是协商缓存会发送请求到服务器。
4. 当协商缓存没有命中时,服务器会返回资源给客户端。
5. 当ctrl+F5强制刷新网页时,直接从服务器加载,跳过强缓存和协商缓存。
6. 当F5刷新页面时,跳过强缓存但会检查协商缓存
12、浏览器端 Event loop
https://segmentfault.com/a/1190000018181334
- 事件循环(event-loop):主线程从"任务队列"中读取执行事件,这个过程是循环不断的,这个机制被称为事件循环。此机制具体如下:主线程会不断从任务队列中按顺序取任务执行,每执行完一个任务都会检查microtask队列是否为空(执行完一个任务的具体标志是函数执行栈为空),如果不为空则会一次性执行完所有microtask。然后再进入下一个循环去任务队列中取下一个任务执行。
- 什么需要:因为JavaScript是单线程的。单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。为了协调事件(event),用户交互(user interaction),脚本(script),渲染(rendering),网络(networking)等,用户代理(user agent)必须使用事件循环(event loops)。
13、路由实现原理
-
早期的路由都是后端实现的,直接根据 url 来 reload 页面,页面变得越来越复杂服务器端压力变大,随着 ajax 的出现,页面实现非 reload 就能刷新数据,也给前端路由的出现奠定了基础。我们可以通过记录 url 来记录 ajax 的变化,从而实现前端路由。
-
History API(history.pushState 和 history.replaceState)
- 这两个API都接收三个参数,分别是
状态对象(state object) — 一个JavaScript对象,与用pushState()方法创建的新历史记录条目关联。无论何时用户导航到新创建的状态,popstate事件都会被触发,并且事件对象的state属性都包含历史记录条目的状态对象的拷贝。
标题(title) — FireFox浏览器目前会忽略该参数,虽然以后可能会用上。考虑到未来可能会对该方法进行修改,传一个空字符串会比较安全。或者,你也可以传入一个简短的标题,标明将要进入的状态。
地址(URL) — 新的历史记录条目的地址。浏览器不会在调用pushState()方法后加载该地址,但之后,可能会试图加载,例如用户重启浏览器。新的URL不一定是绝对路径;如果是相对路径,它将以当前URL为基准;传入的URL与当前URL应该是同源的,否则,pushState()会抛出异常。该参数是可选的;不指定的话则为文档当前URL。 - 相同之处是这两个API都会操作浏览器的历史记录,不会引起页面的刷新。
- 不同之处是pushState会增加一条新的历史记录,而replaceState会替换当前的历史记录。
- 这两个API都接收三个参数,分别是
-
hash
- 我们经常在 url 中看到 #,这个 # 有两种情况,一个是我们所谓的锚点,比如典型的回到顶部按钮原理、Github 上各个标题之间的跳转等,路由里的 # 不叫锚点,我们称之为 hash,大型框架的路由系统大多都是哈希实现的。
- hashchange事件:根据监听哈希变化触发的事件
- 使用 window.location 处理哈希的改变时不会重新渲染页面,而是当作新页面加到历史记录中, 这样跳转页面就可以在
hashchange
事件中注册ajax
从而改变页面内容。 - hashchange 在低版本 IE 需要通过轮询监听 url 变化来实现
- 使用 window.location 处理哈希的改变时不会重新渲染页面,而是当作新页面加到历史记录中, 这样跳转页面就可以在
14、session、cookie 与 token 认证
- 用户通过浏览器登录一个网站,在该浏览器内打开网站其他页面时,不需要重新登录。而HTTP是无状态的协议,不同的网站,判断用户登录状态的方法都不一样。有的网站是通过session来验证用户的登录状态,有的网站是通过token来验证用户的登录状态,也有的网站是通过其他的方式来判断。
- Cookie 是服务器发送给客户端的用于验证某一会话信息的数据,不同网站Cookie中字段是不一样的,是由服务器端设置的。Cookie中常放入session_id 或者 token 用来验证会话的登录状态。
- session是保存在服务器端的经过加密的存储特定用户会话所需的属性及配置信息的数据。当我们打开浏览器访问某网站时,session建立,只要浏览器不关闭(也有时间限制,可以自己设置超时时间),这个网站就可以记录用户的状态,当浏览器关闭时,session结束。
- Token是服务器端生成的用于验证用户登录状态的加密数据,和用session验证差不多。只不过Token验证服务器端不需要存储用户会话所需的配置等数据。只需要后端将Token进行验证签名,然后再发给浏览器。所以,使用Token进行验证,在一次会话中,Token值是不变化的,这和session一样。
15、跨域的解决方案
参考文档:前端常见浏览器跨域请求解决方案
- jsonp 跨域
动态创建script,再请求一个带参网址实现跨域通信(script 标签的 src 属性不受同源策略的限制 );
缺点是只能实现 get 一种请求。 - CORS(跨域资源共享)
在后端添加允许访问的请求头js // 配置 cors 跨域 header("Access-Control-Allow-Origin:*"); header("Access-Control-Request-Methods:GET, POST, PUT, DELETE, OPTIONS"); header('Access-Control-Allow-Headers:x-requested-with,content-type,test-token,test-sessid');
- nginx 代理(proxy)
正向代理,反向代理 - nodejs 中间件代理
node中间件实现跨域代理,原理大致与nginx相同,都是通过启一个代理服务器,实现数据的转发
使用 node + express + http-proxy-middleware 搭建一个proxy服务器。
16、Ajax (axios、fetch、原生xhr)
-
原生xhr:XMLHttpRequest对象
- 现代浏览器,最开始与服务器交换数据,都是通过XMLHttpRequest对象。
- 优点:
1. 不重新加载页面的情况下更新网页
2. 在页面已加载后从服务器请求/接收数据
3. 在后台向服务器发送数据。 - 缺点:
1. 使用起来也比较繁琐,需要设置很多值。
2. 早期的IE浏览器有自己的实现,这样需要写兼容代码。// 兼容处理 if (window.XMLHttpRequest) { // model browser xhr = new XMLHttpRequest() } else if (window.ActiveXObject) { // IE 6 and older xhr = new ActiveXObject('Microsoft.XMLHTTP') } xhr.open('POST', url, true) xhr.send(data) xhr.onreadystatechange = function () { try { // TODO 处理响应 if (xhr.readyState === XMLHttpRequest.DONE) { // XMLHttpRequest.DONE 对应值是 4 // Everything is good, the response was received. if (xhr.status === 200) { // Perfect! } else { // There was a problem with the request. // For example, the response may hava a 404 (Not Found) // or 500 (Internal Server Error) response code. } } else { // Not ready yet } } catch (e) { // 通信错误的事件中(例如服务器宕机) alert('Caught Exception: ' + e.description) } }
-
jQuery ajax: $.ajax
- jQuery 里面的 AJAX 请求也兼容了各浏览器,可以有简单易用的方法 . g e t , .get, .get,.post。简单点说,就是对XMLHttpRequest对象的封装。
- 优点:
1. 对原生XHR的封装,做了兼容处理,简化了使用。
2. 增加了对JSONP的支持,可以简单处理部分跨域。 - 缺点:
1. 如果有多个请求,并且有依赖关系的话,容易形成回调地狱。
2. 本身是针对MVC的编程,不符合现在前端MVVM的浪潮。
3. ajax是jQuery中的一个方法。如果只是要使用ajax却要引入整个jQuery非常的不合理。$.ajax({ type: 'POST', url: url, data: data, dataType: dataType, success: function () {}, error: function () {} })
-
Axios
- Axios 是一个基于 promise 的HTTP库,可以用在浏览器和 node.js 中。它本质也是对原生XMLHttpRequest的封装,只不过它是Promise的实现版本,符合最新的ES规范。
- 优点:
1. 从浏览器中创建XMLHttpRequests
2. 从 node.js 创建 http 请求
3. 支持 Promise API
4. 拦截请求和响应
5. 转换请求数据和响应数据
6. 取消请求
7. 自动转换 JSON 数据
8. 客户端支持防御 XSRF - 缺点:
只持现代代浏览器。axios({ method: 'post', url: '/user/12345', data: { firstName: 'liu', lastName: 'weiqin' } }) .then(res => console.log(res)) .catch(err => console.log(err))
-
fetch
- Fetch API提供了一个 JavaScript 接口,用于访问和操作HTTP管道的部分,例如请求和响应。它还提供了一个全局fetch()方法,该方法提供了一种简单,合理的方式来跨网络异步获取资源。
- fetch是低层次的API,代替XHR,可以轻松处理各种格式,非文本化格式。可以很容易的被其他技术使用,例如Service Workers。但是想要很好的使用fetch,需要做一些封装处理。
- 优点:
在配置中,添加mode: 'no-cors’就可以跨域了 - 问题:
1. fetch只对网络请求报错,对400,500都当做成功的请求,需要封装去处理
2. fetch默认不会带cookie,需要添加配置项。
3. fetch不支持abort,不支持超时控制,使用setTimeout及Promise.reject的实现超时控制并不能阻止请求过程继续在后台运行,造成了流量的浪费。
4. fetch没有办法原生监测请求的进度,而XHR可以。fetch('http://example.com/movies.json') .then(function(response) { return response.json(); }) .then(function(myJson) { console.log(myJson); });
- fetch规范与jQuery.ajax()主要有两种方式的不同:
- 当接收到一个代表错误的 HTTP 状态码时,从 fetch()返回的 Promise 不会被标记为 reject, 即使该 HTTP 响应的状态码是 404 或 500。相反,它会将 Promise 状态标记为 resolve (但是会将 resolve的返回值的 ok 属性设置为 false ),仅当网络故障时或请求被阻止时,才会标记为 reject。
- 默认情况下,fetch 不会从服务端发送或接收任何 cookies, 如果站点依赖于用户 session,则会导致未经认证的请求(要发送 cookies,必须设置 credentials 选项)。
三、webpack 系列
1、webpack 细节
- webpack中在使用babel插件处理js代码的时候,为什么使用polyfill,如何使用polyfill?
- 原因:因为在使用preset_env 处理js代码时,无法将所有的ES6的语法全部转换成ES5语法,就比如promise、array.from以及实例方法都无法转换,这个时候需要加入垫片。
- 使用:
1. 在入口文件引入@babel/polyfill ,会污染全局环境
2. 在配置文件中的entry中写 ,也会污染全局环境
3. 可以配置@babel/preset-env useBuiltIns接收3个参数
entry:不管代码 有没有用到,只要目标浏览器不支持的都会引入对应的polyfill;自动引入polyfill模块;
usage: 根据代码的使用情况,按需引入;自动引入polyfill模块;
false:不会自动引入polyfill模块;
4. corejs 3.0以后的版本; 如果参数为entry,则需要在入口文件中引入两个包
2、webpack 和 gulp 的区别
- webpack是一个模块打包器,强调的是一个前端模块化方案,更侧重模块打包,我们可以把开发中的所有资源都看成是模块,通过loader和plugin对资源进行处理。
- gulp是一个前端自动化构建工具,强调的是前端开发的工作流程,可以通过配置一系列的task,第一task处理的事情(如代码压缩,合并,编译以及浏览器实时更新等)。然后定义这些执行顺序,来让glup执行这些task,从而构建项目的整个开发流程。自动化构建工具并不能把所有的模块打包到一起,也不能构建不同模块之间的依赖关系。
3、webpack 从启动构建到输出结果经历了一系列过程
- 解析webpack配置参数,合并从shell传入和webpack.config.js文件里配置的参数,生产最后的配置结果。
2. 注册所有配置的插件,好让插件监听webpack构建生命周期的事件节点,以做出对应的反应。
3. 从配置的entry入口文件开始解析文件构建AST语法树,找出每个文件所依赖的文件,递归下去。
4. 在解析文件递归的过程中根据文件类型和loader配置找出合适的loader用来对文件进行转换。
5. 递归完后得到每个文件的最终结果,根据entry配置生成代码块chunk。
6. 输出所有chunk到文件系统。
4、gulp基本使用
四、nodejs 系列
1、Node.js的 Event loop
在进程启动时,Node便会创建一个类似于while(true)的循环,每执行一次循环体的过程我们称为Tick。每个Tick的过程就是查看是否有事件待处理。如果有就取出事件及其相关的回调函数。然后进入下一个循环,如果不再有事件处理,就退出进程。
2、Node中间件
-
中间件主要是指封装所有Http请求细节处理的方法。一次Http请求通常包含很多工作,如记录日志、ip过滤、查询字符串、请求体解析、Cookie处理、权限验证、参数验证、异常处理等,但对于Web应用而言,并不希望接触到这么多细节性的处理,因此引入中间件来简化和隔离这些基础设施与业务逻辑之间的细节,让开发者能够关注在业务的开发上,以达到提升开发效率的目的。
-
Koa 与 Express 比较
- 语法区别
Express 异步使用 回调
koa1 异步使用 generator + yeild
koa2 异步使用 await/async - 中间件区别
koa采用洋葱模型,进行顺序执行,出去反向执行,支持context传递数据
Express本身无洋葱模型,需要引入插件,不支持context
Express的中间件中执行异步函数,执行顺序不会按照洋葱模型,异步的执行结果有可能被放到最后,response之前。
这是由于,其中间件执行机制,递归回调中没有等待中间件中的异步函数执行完毕,就是没有await中间件异步函数。 - 集成度区别
express 内置了很多中间件,集成度高,使用省心,
koa 轻量简洁,容易定制
- 语法区别
3、Node三大框架:Express、Koa、Nest对比
Express.js
- 规模小、比较灵活的一款开发框架;
- Node官方推荐,具有强大的Api;
- 支持许多其他软件包和模块引擎;
- 线性逻辑:路由和中间件完美融合,通过中间件形式把业务逻辑细分,简化,一个请求进来经过一系列中间件处理后再响应给用户,将复杂的业务线性化,清晰明了;
- 但是,
Express
是基于callback
来组合业务逻辑。Callback
有两大硬伤,一是不可组合,二是异常不可捕获。Express
的中间件模式虽然在一定程度上解决这两个问题,但没法彻底解决。 - 适合初学者学习,有强大的社区支持;
Koa.js
Express
的下一代,性能比较好;- 采用ES6的语法,极大地提升错误处理的效率;
- 需要引用别人开发的中间件或者自己开发,然后再开发业务逻辑;
- 对于初学者来说不太友好;
Nest.js
- 高效、可扩展性高;
- 支持ts编写,兼容
express
中间件; - 有依赖注入和模块化的思想,类似于MVC架构;
- 上手成本较高,但后期维护与扩展会很方便;
- 提供了一套完整的解决方案,包含了认证、数据库、路由、http状态码、安全、配置、请求等开箱即用的技术。
4、Node.js 模块(CommonJS、模块分类、模块导出、模块加载)
CommonJS
-
一个js文件就是一个模块
-
模块内所有的变量均为局部变量,不会污染全局
-
模块中需要提供给其他模块使用的内容需要导出
-
导出使用
exports.xxx = xxx
或module.exports=xxx
或this.xxx=xxx
-
其他模块可以使用
require
函数导入node
实现了CommonJS
规范,在编写模块时,都有require
、exports
、module
三个预先定义好的变量可以使用。require{}
函数的两个作用:
1. 执行导入的模块中的代码;
2. 返回导入模块中的接口;
模块分类
- 核心模块,也叫内置模块、原生模块
- 例如:
fs
,http
,path
,url
模块; - 所有内置模块,在安装
node.js
的时候,就已经编译成 二进制文件,可以直接加载运行(速度较快); - 部分内置模块,在
node.exe
这个进程启动的时候就已经默认加载了,所以可以直接使用。
- 例如:
- 文件模块
- 按文件后缀来分
- 如果加载时,没有指定文件后缀名,那么,就按照
.js
,.json
,.node
顺序依次加载相应模块
- 自定义模块(第三方模块)
模块导出
// 方式一:module.exports 导出
module.exports = {
say: sayName
}
// 方式二:exports 导出
exports.say = sayName
模块加载过程
- 看
require()
加载模块时传入的参数是否以'/'
或'./'
或'./'
等等这样的路径方式开头(相对路径或绝对路径都可以) - 如果是,就会按照传入的路径直接去查询对应的模块
(1)如果传入的为具体的文件名,例如:require('./test.js')
,
直接根据给定的路径去加载模块,找到了就加载成功,找不到加载失败
(2)如果传入的不是具体的文件名,例如:require('./test')
第一步:根据给定的路径,依次添加文件后缀.js
、.json
、.node
进行匹配,如果匹配不到,执行第二步
第二步:查找是否有 test 目录(尝试寻找 test 包)
找不到:加载失败
找到了:依次在test目录下查找package.json
文件(找到该文件后尝试找main
字段中的入口文件)、index.js
、index.json
、index.node
,找不到则加载失败 - 如果不是,那就认为传入的是“模块名称”,例如:
require('http')
、require('fs')
(1)如果是核心模块,就直接加载核心模块
(2)不是核心模块:从当前目录开始,依次递归查找所有父目录下的node_modules
目录中是否包含相应的包,如果查找完毕磁盘根目录依然没有,则加载失败
五、其他
1、http和应用编程相关的问题
参考文章:HTTP协议超级详解
2、git及git工作流
3、数据库(MySQL、MongoDB)
4、前后端模板(artTemplate、ejs)
六、Vue 高级
前端面试题之 热门框架Vue 篇
前端面试题之 进阶 Vue3.0 篇
1、Vue.js 动态组件与异步组件
- 动态组件:
keep-alive
当在组件之间切换的时候,想要保持这些组件的状态,以避免反复渲染导致的性能问题。重新创建动态组件的行为通常是非常有用的,我们希望那些标签的组件实例能够被在他们第一次被创建的时候缓存下来,为了解决这个问题,可以使用<keep-alive>
元素将其动态组件包裹起来。这样,失活的组件将会被缓存。 - 异步组件:
- 在大型应用中,我们可能需要将应用分割成小一些的代码块,并且只在需要的时候才从服务器上加载一个模块。为了简化, Vue允许以一个工厂函数的方式定义组件,这个工厂函数会异步解析这个组件定义。Vue 只有在这个组件需要被渲染的时候才会触发该工厂函数,且会把结果缓存起来供未来重渲染。
- 这样工厂函数会收到一个
resolve
回调,这个回调函数会在从服务器得到组件定义的时候被调用,也可以调用reject(reason)
来表示加载失败。Vue.component('async-webpack-example', function (resolve) { // 这个特殊的 `require` 语法将会告诉 webpack // 自动将你的构建代码切割成多个包,这些包 // 会通过 Ajax 请求加载 require(['./my-async-component'], resolve) })
- 也可以在工厂函数中返回一个
Promise
,使用动态导入Vue.component( 'async-webpack-example', // 这个动态导入会返回一个 `Promise` 对象。 () => import('./my-async-component') )
- 当使用局部注册的时候,也可以之间提供一个返回
promise
的函数new Vue({ components: { 'my-component': () => import('./my-async-component') } })
- 处理加载状态(2.3.0新增)
const AsyncComponent = () => ({ // 需要加载的组件 (应该是一个 `Promise` 对象) component: import('./MyComponent.vue'), // 异步组件加载时使用的组件 loading: LoadingComponent, // 加载失败时使用的组件 error: ErrorComponent, // 展示加载时组件的延时时间。默认值是 200 (毫秒) delay: 200, // 如果提供了超时时间且组件加载也超时了, // 则使用加载失败时使用的组件。默认值是:`Infinity` timeout: 3000 })
2、处理边界情况($root
,$parent
,$refs
,依赖注入,循环引用,$forceUpdate
,v-once
)
-
访问根实例
$root
所有的子组件都可以将这个实例作为一个全局 store 来访问或使用。this.$root.xxx
对于demo或者非常小型的有少量组件的应用来说比较方便,但是在绝大多数情况下,需要使用Vuex
来管理应用的状态。 -
访问父级组件实例
$parent
可以用来从一个子组件访问父组件的实例。可以在后期随时触达父级组件,以替代将数据以 prop 的方式传入子组件的方式。
在绝大多数情况下,会使得应用更难调试和理解,尤其是变更了父级组件的数据的时候。
在需要向任意更深层级的组件提供上下文信息时推荐使用依赖注入。 -
访问子组件实例或子元素
$refs
给子组件添加一个 ref 属性,赋予一个ID引用,就可以使用$refs
来访问这个子组件实例// 子组件定义ref属性 <base-input ref="usernameInput"></base-input> // 允许父组件给子组件输入框获取焦点 this.$refs.usernameInput.focus()
当
ref
和v-for
一起使用的时候,得到的ref会包含对应数据源的这些子组件的数组。$refs
会在组件渲染完成后生效,并且不是响应式的,应该避免在模板或计算属性中访问$refs
-
依赖注入:
provide
和inject
provide
:可以指定想要提供给后代组件的数据/方法
provide: function () { return { getMap: this.getMap } }
inject
:在任何后代组件中,通过 inject 选项接收指定的属性
inject: ['getMap']
- 相比
$parent
:这种用法可以在任意后代组件中访问该定义的属性,而不需要暴露整个组件实例。同时这些组件之间的接口是始终明确定义的,和props
一样。 - 可以把依赖注入看作一部分“大范围有效的prop”,除了
- 祖先组件不需要知道那些后代组件使用它提供的property
- 后代组件不需要知道被注入的property来自哪里
- 负面影响:
- 将应用程序中的组件与它们当前的组织方式耦合起来,使重构变得更加困难;
- 提供的property是非响应式的;
-
程序化的事件侦听器:
$emit
可以被v-on
侦听- 手动侦听事件:
$on(eventName , eventHandler)
侦听一个事件$once(eventName , eventHandler)
一次性侦听一个事件$off(eventName , eventHandler)
停止侦听一个事件
-
循环引用
- 递归组件
组件是可以在自己的模板中调用自身,但是只能通过name
选项来实现。
当使用Vue.component
全局注册一个组件时,这个全局的ID会自动设置为该组件的name
选项。
使用不当,递归组件就可能导致无限循环。所以,使用递归调用是有条件性的。 - 组件之间的循环引用
- 递归组件
-
模板定义替代品
- 内联模板
inline-template
:添加这个属性会将这个组件里面的内容作为模板,但是会让模板的作用域变得难以理解,在组件内优先选择template
选项 或者<template>
元素来定义模板。<my-component inline-template> // ... </my-component>
- X-Template
在<script>
元素中,并为其带上text/x-template
的类型,然后通过id将模板引用过去。// 定义模板 <script type="text/x-template" id="hello-world-template"> // ... </script> // 引用模板 Vue.component('hello-world', { template: '#hello-world-template' })
- 内联模板
-
控制更新
- 强制更新:
$forceUpdate
v-once
创建低开销的静态组件
组件内有大量静态内容,可以在根元素上添加v-once
来使这些内容只计算一次然后缓存起来
- 强制更新:
3、vue-router 守卫和路由懒加载
- 导航守卫分类(按维度)
- 全局守卫:进入任何一个路由都会执行
beforeEach
:全局前置守卫,进入路由前执行beforResolve
:全局解析守卫,在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用afterEach
:全局后置守钩子,导航确认执行时执行,可理解为导航完成时执行 - 路由独享守卫:进入某个路由才会执行
beforeEnter
:进入该路由前 - 组件内守卫:进入某个组件才会执行,组件复用时
beforeRouteEnter
:进入组件时,不能获取组件实例this,因为当守卫执行前,组件实例还没被创建beforeRouteUpdate
:组件被复用时调用beforeRouteLeave
:离开组件时
-
导航解析流程
- 导航被触发
- 在失活的组件里调用
beforeRouteLeave
守卫 - 调用全局的
beforeEach
守卫 - 在重用的组件里调用
beforeRouteUpdate
守卫 - 在路由配置里调用
beforeEnter
- 解析异步路由组件
- 在被激活的组件里调用
beforeRouteEnter
- 调用全局的
beforeResolve
守卫 - 导航被确认
- 调用全局的
afterEach
钩子 - 触发 DOM 操作
- 调用
beforeRouteEnter
守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入
-
路由懒加载
- resolve
这一种方法较常见。它主要是使用了resolve的异步机制,用require代替了import,实现按需加载。 - 官网方法
vue-router在官网提供了一种方法,可以理解也是为通过Promise的resolve机制。因为Promise函数返回的Promise为resolve组件本身,而我们又可以使用import来导入组件。 - require.ensure
这种模式可以通过参数中的webpackChunkName
将js分开打包。
component: resolve => require.ensure([], () => resolve(require(’@/components/’+componentName)), ‘webpackChunkName’)
结合 Vue 的异步组件和 Webpack 的代码分割功能,把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件
- resolve
4、Vue 渲染函数
- Vue 一般使用
template
来创建HTML,有的时候需要使用 js 来创建 html,这个时候就要用到render
渲染函数。 - 使用
render: function(createElement) { return createElement( 'h' + this.level, // tag name 标签名称 this.$slots.default // 组件的子元素存储在组件实列 $slots.default 中。 ) },
createElement
- Vue通过建立一个虚拟DOM对真实的DOM发生变化保存追踪。
- return createElement(‘h1’, this.title);
- createElement 返回的是包含的信息,会告诉VUE页面上需要渲染什么样的节点及其子节点。称这样的节点为虚拟DOM(VNode)。
- data对象
- 在模板语法中,我们可以使用 v-bind:class 和 v-bind:style 来绑定属性,在VNode数据对象中,下面的属性名的字段级别是最高的。
- data对象允许我们绑定普通的html特性,就像DOM属性一样。
- 使用Javascript代替模板功能
- v-if 和 v-for
template 中有 v-if 和 v-for, 但是vue中的render函数没有提供专用的API。在render函数中会被javascript的 if/else 和map重新实现。 - v-model
render函数中没有 与 v-model相应的api,我们必须自己来实现相应的逻辑。
- v-if 和 v-for
- 插槽
可以从 this.$slots 获取VNodes列表中的静态内容 - 函数式组件
- 函数式组件我们标记组件为 functional, 意味着它无状态(没有data), 无实列(没有this上下文)。
- 组件需要的一切通过上下文传递,包括(props、children、slots、data、parent、listeners、injections)
- 在添加 functional: true 之后,组件的 render 函数之间简单更新增加 context 参数,this.$slots.default 更新为 context.children,之后this.level 更新为 context.props.level。
5、Vue 的 mixin
vue中提供了一种混合机制–mixins,用来更高效的实现组件内容的复用。
组件在引用之后相当于在父组件内开辟了一块单独的空间,来根据父组件props过来的值进行相应的操作,单本质上两者还是泾渭分明,相对独立。
而mixins则是在引入组件之后,则是将组件内部的内容如data等方法、method等属性与父组件相应内容进行合并。相当于在引入后,父组件的各种属性方法都被扩充了。
作用:多个组件可以共享数据和方法,在使用mixin的组件中引入后,mixin中的方法和属性也就并入到该组件中,可以直接使用。钩子函数会两个都被调用,mixin中的钩子首先执行。
6、Vuex 模块
参考文章:Vuex模块化应用实践
流程图
可以做异步操作,但是会违背 flux 思想
7、相关原理
Vue.js 运行机制
- 初始化及挂载
在new Vue()
之后,Vue会调用_init
函数进行初始化,也就是进行init 过程,它会初始化生命周期、事件、props、methods、data、computed与watch等。其中重要的是通过Object.defineProperty
设置setter
和getter
函数,用来实现响应式和依赖收集。
初始化之后调用$mount
会挂载组件,如果是运行时编译,即不存在render function
,但是存在template
的情况,需要进行编译的步骤。 - 编译
compile
编译可以分成parse
、optimize
与generate
三个阶段,最终需要得到render function
。parse
:用正则解析 template 模板中的指令、class、style等数据,形成 AST(抽象语法树);optimize
:用来标记静态节点,将来更新视图的时候,通过diff算法会跳过静态节点,达到优化的一个目的;generate
:将AST
转换成render function
字符串,得到结果是 render 的字符串以及 staticRenderFns字符串。 - 响应式
当render function
被渲染的时候,因为会读取所需对象的值,所以会触发getter
函数进行依赖收集,依赖收集的目的的将观察者Watcher
对象存放到当前闭包中的订阅者Dep
的subs
中。
在修改对象的值的时候,会触发对应的setter
,setter
通知之前依赖收集得到的 Dep中的每一个Watcher
,告诉他们自己的值改变了,需要重新渲染视图。这时候这些Watcher
就会开始调用update
来更新视图。 - Virtual DOM(虚拟DOM节点)
Virtual DOM
其实就是一棵以 js 对象作为基础的树,用对象属性来描述节点,实际上它只是一层对真实DOM的抽象。 - 更新视图
patch()
方法对比新的 VNode 和 旧的 VNode。通过diff算法得到差异,进行对应修改。 - 流程图
响应式系统的基本原理
响应式系统的依赖收集追踪原理
实现 Virtual DOM
template 模板是怎样通过 Compile 编译的
数据状态更新时的差异 diff 及 patch 机制
批量异步更新策略及 nextTick 原理
Vuex 状态管理的工作原理
七、React 高级
1、React 组件间信息传递
- 父组件向子组件传值:父组件通过属性进行传递,子组件通过props获取
- 子组件向父组件传值:触发父组件传给子组件的函数传值
- 兄弟组件传值:可以用上面两种方法一点一点方法传给相同父组件在传下去
使用 context 传递数据
利用 redux 传递数据
2、React JSX 原理
- JSX 是js的语法扩展,就像一个拥有js的全部功能的模板语言,在里面可以使用js代码
{}
,经过react语法的构造,编译转化,最后得到dom元素,插到页面中。 - 实现原理:react封装了
createElement
,可以构建一个js对象来描述HTML结构的信息。其中,第一个参数是标签名,第二个参数是对象,包含了所有的属性,第三个是子节点。 - 过程:JSX —— 使用react构造组件,bable编译 => js对象 —— ReactDOM.render() => DOM 元素 => 插入页面
3、React 组件间数据共享(props, PropTypes, Context, Redux, Mobx)
4、React 路由加载
- 路由组件的加载一共有三种方式,component\render\children
- component可以直接引入组件名,也可以通过一个回调函数去引入一个组件
- render只可以通过回调函数引入组件
- children和render一样也是通过一个回调函数组件,但是children引入的组件,无论路径是否匹配都显示引入的组件,最后通过match对象的match属性去对应相应的路由,常用于高亮效果等!
5、Context 的使用
context,通过createContext创建一个context,在所有组件的最外层包裹一个provider,然后通过给provider绑定数据以及方法,然后后代组件可以通过tatic contextType 或者consumer来获取context里面的值,如果是consumer的话,那么就是使用一个回调函数,回调函数的参数就是context的值
6、Redux遵循的三个原则是什么?
- 单一数据来源:整个应用的状态存储在单个 store 中的对象/状态树里。单一状态树可以更容易地跟踪随时间的变化,并调试或检查应用程序。
- 状态是只读的:改变状态的唯一方法是去触发一个动作。动作是描述变化的普通 JS 对象。就像 state 是数据的最小表示一样,该操作是对数据更改的最小表示。
- 使用纯函数进行更改:为了指定状态树如何通过操作进行转换,需要用纯函数。纯函数是那些返回值仅取决于其参数值的函数。
7、immutable
(什么是immutable
, 为什么要使用,重要的API)
- 什么是immutable:
- Immutable Data 就是一旦创建,就不能再被更改的数据。对 Immutable 对象的任何修改或添加删除操作都会返回一个新的 Immutable 对象。
- Immutable 实现的原理是 Persistent Data Structure(持久化数据结构),也就是使用旧数据创建新数据时,要保证旧数据同时可用且不变。
可以利用这个特性,去做时间旅行,也就是调用之前存在过的旧数据。 - 为了避免 deepCopy 把所有节点都复制一遍带来的性能损耗,Immutable 使用了 Structural Sharing(结构共享),即如果对象树中一个节点发生变化,只修改这个节点和受他影响的父节点,其他节点则进行共享。
- Immutable 优点:
- 降低 mutable 带来的复杂度
- 节省内存
- 历史追溯性(时间旅行):时间旅行指的是:每时每刻的值都被保留了,想回退到哪一步只要简单的将数据取出就行,这个特性在 redux或者flux中特别有用。
- 函数式编程:Immutable 本来就是函数式编程的概念,纯函数式编程的特点是:只要输入一致,输出必然一致,相比于面向对象,这样开发组件和调试更方便。
- Immutable 缺点:
- 需要重新学习 api
- 资源包大小增加
- 容易与原生对象混淆:由于 api 与原生不同,混用的话容易出错。
- Immutable API:
- fromJS():将一个js数据转换为Immutable类型的数据
- toJS():将一个Immutable数据转换为JS类型的数据
- is():对两个对象进行比较
- 数据读取:
- get() 、 getIn():获取数据结构中的数据
- has() 、 hasIn():判断是否存在某一个key
- includes():判断是否存在某一个value
- first() 、 last():用来获取第一个元素或者最后一个元素,若没有则返回undefined
- 数据修改:这里对于数据的修改,是对原数据进行操作后的值赋值给一个新的数据,并不会对原数据进行修改,因为Immutable是不可变的数据类型。
- set():设置第一层key、index的值
- setIn():设置深层结构中某属性的值
- delete():删除第一层结构中的属性
- update():对对象中的某个属性进行更新,可对原数据进行相关操作
- clear():清除所有数据,clear(): this
文档持续更新中。。。加油骚年!!
更多推荐
所有评论(0)