前端面试复习
2021-10-31 23:28:13 0 举报
前端面试复习
作者其他创作
大纲/内容
前端框架
vue和react的相同点和区别
相似处
virtual Dom的使用
React采用的Virtual DOM会对渲染出来的结果做脏检查,react如果父组件传递给子组件的数据发生变化,会引起整个组件树的变化
脏检查
React 每次生成新的 Virtual DOM,与旧 Virtual DOM的 diff 操作本来就可以看做一次脏检查
脏检查最初来自 angularjs,angularJS监测对象变化不是像vue.js那样通过Object.defineproperty这种接口,
而是在某些情况下制定策略,通过复制保存一份数据,进行快照对比,来监测变化。就是我每次更新一个组件,
我就检查一遍这个组件的所有依赖,最终做到按需更新
但是由于当时 angularjs 是双向绑定,是一个无向图,导致会产生大量冗余的检查
所以,当然普遍的认知是,脏检查性能不咋地,大多数时候复杂度会大于 O(n)
React 每次生成新的 Virtual DOM,与旧 Virtual DOM的 diff 操作本来就可以看做一次脏检查
脏检查最初来自 angularjs,angularJS监测对象变化不是像vue.js那样通过Object.defineproperty这种接口,
而是在某些情况下制定策略,通过复制保存一份数据,进行快照对比,来监测变化。就是我每次更新一个组件,
我就检查一遍这个组件的所有依赖,最终做到按需更新
但是由于当时 angularjs 是双向绑定,是一个无向图,导致会产生大量冗余的检查
所以,当然普遍的认知是,脏检查性能不咋地,大多数时候复杂度会大于 O(n)
vue跟踪每一个组件的依赖关系,深度优先diff出需要更新的地方,不需要重新渲染整个组件树,且提供指令操作virtual Dom
组件化
不同之处
单向?双向数据流
react是单向数据流,状态驱动视图state-》view-》New State-》New View -》 ui-》render(data)
vue是双向数据流,Vue 是MVVM框架,双向数据绑定,当ViewModel对Model进行更新时,通过数据绑定更新到View。
模版渲染
React是通过JSX来渲染模板,而Vue是通过扩展的HTML来进行模板的渲染。
组件形式
组件形式不同,Vue文件里将HTML,JS,CSS组合在一起。react提供class组件和function组
自由度
Vue封装好了一些v-if,v-for,React什么都是自己实现,自由度更高
类式的组件写法,还是声明式写法(vue3.0已支持)
react是类式写法
vue是声明式,传入各种options,api和参数都很多
是否提供内置功能
react-redux,react-router交给社区维护
vuex,vue-router官方提供
扩展
react通过高阶组件扩展
vue通过mixins扩展
Vue也能实现高阶组件,只是特别麻烦,因为Vue对与组件的option做了各种处理,想实现高阶组件就要知道每一个option是怎么处理的,然后正确的设置。具体有多复杂,可以参考下面的文章。
Vue也能实现高阶组件,只是特别麻烦,因为Vue对与组件的option做了各种处理,想实现高阶组件就要知道每一个option是怎么处理的,然后正确的设置。具体有多复杂,可以参考下面的文章。
all in js?
react思想是all in js,通过jis来生成html,css,使用的是jsx,vue也有jsx方式,但是官方推崇使用html的语法创建.vue文件
数据是否自动watcher可变
react性能优化需要手动支持,vue自动优化,(当state太多多时候,vue的watcher也会多,造成卡顿,所以大型应用使用react会比较可控)
监听数据变化的实现原理不同
Vue通过 getter/setter以及一些函数的劫持,能精确知道数据变化。
React默认是通过比较引用的方式(diff)进行的,如果不优化可能导致大量不必要的VDOM的重新渲染。
为什么React不精确监听数据变化呢?
这是因为Vue和React设计理念上的区别,Vue使用的是可变数据,而React更强调数据的不可变,两者没有好坏之分,Vue更加简单,而React构建大型应用的时候更加好
为什么React不精确监听数据变化呢?
这是因为Vue和React设计理念上的区别,Vue使用的是可变数据,而React更强调数据的不可变,两者没有好坏之分,Vue更加简单,而React构建大型应用的时候更加好
vue
1.css只在当前组件起作用
答:在style标签中写入scoped即可 例如:
答:在style标签中写入scoped即可 例如:
2.v-if 和 v-show 区别
答:v-if按照条件是否渲染,v-show是display的block或none;
3.$route和$router的区别
答:$route是“路由信息对象”,包括path,params,hash,query,fullPath,matched,name等路由信息参数。
而$router是“路由实例”对象包括了路由的跳转方法,钩子函数等。如push .go .replace
答:$route是“路由信息对象”,包括path,params,hash,query,fullPath,matched,name等路由信息参数。
而$router是“路由实例”对象包括了路由的跳转方法,钩子函数等。如push .go .replace
4.vue.js的两个核心是什么?
答:数据驱动、组件系统
5.vue几种常用的指令
答:v-for 、 v-if 、v-bind、v-on、v-show、v-else,v-bind绑定属性,v-on绑定事件
6.vue常用的修饰符?
.prevent: 提交事件不再重载页面;
.stop: 阻止单击事件冒泡;
.self: 当事件发生在该元素本身而不是子元素的时候会触发;
.capture: 事件侦听,事件发生的时候会调用
.stop: 阻止单击事件冒泡;
.self: 当事件发生在该元素本身而不是子元素的时候会触发;
.capture: 事件侦听,事件发生的时候会调用
7.v-on 可以绑定多个方法吗?
答:可以
8.vue中 key 值的作用?
1. key的作⽤主要是为了更⾼效的对⽐虚拟DOM中的某个节点是否相同。
2. vue在patch过程中判断两个节点是否是相同节点,从源码中可以知道,vue判断两个节点是否相同时主要判断两者的key和元素类型等,key作为唯⼀标识,因此如果不设置key,它的值就是undefined,则可能永远认为这是两个相同节点,只会频繁更新dom,明显是不可取的。使得整个patch过程⽐较低效,影响性能。
什么是vue的计算属性?
答:在模板中放入太多的逻辑会让模板过重且难以维护,在需要对数据进行复杂处理,且可能多次使用的情况下,尽量采取计算属性的方式。
①使得数据处理结构清晰;
②依赖于数据,数据更新,处理结果自动更新;
③计算属性内部this指向vm实例;
④在template调用时,直接写计算属性名即可;
⑤常用的是getter方法,获取数据,也可以使用set方法改变数据;
⑥相较于methods,不管依赖的数据变不变,methods都会重新计算,但是依赖数据不变的时候computed从缓存中获取,不会重新计算。
10.vue等单页面应用及其优缺点
优点:
1、除了首次加载页面,但页面内容改变不需要重新加载整个页面,
利用ajax实现良好的交互体验,没有页面之间的切换,不会出现白屏,也不会闪烁
2、前后端完全分离,后端不在复制模版渲染,输出页面,后端更专注业务开发和性能优化
3、单页面相对服务器压力小,服务器只用输出数据就可以
1、除了首次加载页面,但页面内容改变不需要重新加载整个页面,
利用ajax实现良好的交互体验,没有页面之间的切换,不会出现白屏,也不会闪烁
2、前后端完全分离,后端不在复制模版渲染,输出页面,后端更专注业务开发和性能优化
3、单页面相对服务器压力小,服务器只用输出数据就可以
缺点:
1、首屏加载慢,如果不对路由进行处理,就会将所有组件全部加载并向服务器请求数据,这导致首屏加载速度慢
解决办法
1,路由懒加载-按需加载组件,当路由被访问的时候再加载对应组件,而不是首页的时候加载)
2、使用CDN加速,如果一些常用的库,采用CDN加速
3、异步加载组件
4、服务端渲染(有利于seo)
不利于seo
解决
页面预渲染
构建阶段生成匹配预渲染路径的 html 文件
构建阶段生成匹配预渲染路径的 html 文件
在项目构建时,通过无头浏览器模拟浏览器请求,将得到的数据插入给出的模板中,
从而生成已经包含数据的html,这样有了更多的静态资源,网络爬虫可以抓取到更多的网站信息,提升网站的搜索排名.
预渲染prerender-spa-plugin
从而生成已经包含数据的html,这样有了更多的静态资源,网络爬虫可以抓取到更多的网站信息,提升网站的搜索排名.
预渲染prerender-spa-plugin
phantomjs 页面预渲染,具体参考 phantomjs.org
使用预渲染vue-router必须使用history模式
搜索引擎能根据url爬取到独立页面的内容
(形如http://aiispo.cn/article/1),通过此URL进入文章独立页面能抓取到文章正文。
搜索引擎能根据url爬取到独立页面的内容
(形如http://aiispo.cn/article/1),通过此URL进入文章独立页面能抓取到文章正文。
vue-router 默认 hash 模式 —— 使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载。
如果不想要很丑的 hash,我们可以用路由的 history 模式,这种模式充分利用 history.pushState API 来完成 URL 跳转而无须重新加载页面。
如果不想要很丑的 hash,我们可以用路由的 history 模式,这种模式充分利用 history.pushState API 来完成 URL 跳转而无须重新加载页面。
要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面
vue-meta-info,这个是针对单页面的meta SEO的另一种思路,参考网站 https://zhuanlan.zhihu.com/p/29148760
nuxt 简单易用,参考网站 https://zh.nuxtjs.org/guide/installation
ssr,vue官方文档给出的服务器渲染方案,这是一套完整的构建vue服务端渲染应用的指南,具体参考https://cn.vuejs.org/v2/guide/ssr.html
11.怎么定义 vue-router 的动态路由? 怎么获取传过来的值
答:在 router 目录下的 index.js 文件中,对 path 属性加上 /:id,使用 router 对象的 params.id 获取。
keep-alive的了解
用来对组件进行缓存,从而节省性能,<keep-alive>由于是一个抽象组件,所以在页面渲染完毕后不会被渲染成一个DOM元素,
被包裹在keep-alive中的组件的状态将会被保留,不会重新加载,不会触发mounted
被包裹在keep-alive中的组件的状态将会被保留,不会重新加载,不会触发mounted
keep-alive生命周期钩子函数:activated、deactivated,如果要在每次进入页面的时候获取最新的数据,需要在activated阶段获取数据,承担原来created钩子中获取数据的任务。
vue-cli
如何添加自定义指令
自定义过滤器
vuex
原理
1.vue.use(vuex),调用vuex 的install方法
在beforeCreate钩子前混入vuexInit方法,实现了store注入vue组件实例。并注册了vuex store的引用属性$store
vuex的state是响应式的,借助vue的data实现,将state存入vue实例组件的data
vuex的getter是借助vue的计算属性computed实现数据监听
属性
state
每个应用包含一个store实例,存放数据和状态,不可以直接修改里面对数据
mutations
定义修改store数据的方法
action
通过将mutation里面的数据的方法变成异步处理数据的方法
getters
计算属性,主要用来过滤一些数据
用处
用来保存状态,组件发送dispatch触发action异步,然后action提交mutation,相当于一个处理中心,mutations修改state,最后渲染组件
使用场景:购物车,登陆状态,音乐播放
Vuex和单纯的全局对象有什么区别
1. Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发⽣变化,那么相应的组件也会相应地得到⾼效更新。
2. 不能直接改变 store 中的状态。改变 store 中的状态的唯⼀途径就是显式地提交 (commit)mutation。这样使得我们可以⽅便地跟踪每⼀个状态的变化,从⽽让我们能够实现⼀些⼯具帮助
vue实现路由的方式
hash模式
hash # 后面的字符是不包括在http请求中的,一般用来指导浏览器动作
history模式
提供pushState()、replacState(),可以对浏览器历史记录栈进行修改
history模式下,在服务端要增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面
组件间的参数传递
父传子
props
子传父
事件传递$emit(“绑定的函数名”,'子传父的值')。在父组件中都子组件标签绑定事件,子组件通过this.$emit触发,并传递参数给父组件
$parent / $children与 ref
ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就 指向组件实例
$parent / $children:访问父 / 子实例
4. $attr/$listeners (跨级组件,父子,兄弟组件)
v-bind="$attrs" 里存放的是父组件中绑定的非 Props 属性,
v-on="$listeners"里存放的是父组件中绑定的非原生事件。
v-on="$listeners"里存放的是父组件中绑定的非原生事件。
Vue2.4提供了$attrs , $listeners 来传递数据与事件,跨级组件之间的通讯变得更简单。
父传孙和孙传父->给子组件添加v-bind="$attrs" v-on="$listeners"属性,
父传孙和孙传父->给子组件添加v-bind="$attrs" v-on="$listeners"属性,
(只传递,不处理数据)
inheritAttrs:true 继承除props之外的所有属性;inheritAttrs:false 只继承class属性。
eventBus(跨级组件,父子,兄弟组件)
vue 实例 作为事件总线(事件中心)用来触发事件和监听事件
// bus.js
import Vue from 'vue'
export defult new Vue()
// bus.js
import Vue from 'vue'
export defult new Vue()
bus.$on('sendTitle',(val)=>{console.log(val)})
bus.$emit('sendTitle','传递的值')
vuex(跨级)
inject/provide(跨级/高阶用法)
祖先组件中通过provider来 提供变量,然后在子孙组件中通过inject来注入变量。
provide 和inject绑定并不是可响应的,实现响应式API Vue.observable
vue响应式原理
vue.js 采用数据劫持结合发布者-订阅者模式,
1、通过Observer类,利用Object.defineProperty()来劫持各个属性的getter/setter,
2、通过Dep类作为订阅管理中心,为每个属性管理订阅者,
3、通过Watcher类创建订阅者和绑定update()更新回调,
4、在初始化编译模版compile时触发getter,这时会为属性的Dep订阅中心添加订阅者watcher,在数据变动触发set时通知所有订阅者watcher,触发相应update回调,更新页面
1、通过Observer类,利用Object.defineProperty()来劫持各个属性的getter/setter,
2、通过Dep类作为订阅管理中心,为每个属性管理订阅者,
3、通过Watcher类创建订阅者和绑定update()更新回调,
4、在初始化编译模版compile时触发getter,这时会为属性的Dep订阅中心添加订阅者watcher,在数据变动触发set时通知所有订阅者watcher,触发相应update回调,更新页面
双向绑定过程
1、Observer将this.data转换成getter/setter进行响应式绑定
2、Dep【依赖管理】,收集包括{{}},watch,computed,的依赖,当数据发生变化,调用收集的回调。
3、如何收集依赖:Compile当读取属性当时候收集,就是当触发Object.defineproperty get 的时候
4、什么是订阅者:Watcher,Compile时遇到{{}},v-modle等时,创建订阅者,并触发get添加到dep依赖管理中心
2、Dep【依赖管理】,收集包括{{}},watch,computed,的依赖,当数据发生变化,调用收集的回调。
3、如何收集依赖:Compile当读取属性当时候收集,就是当触发Object.defineproperty get 的时候
4、什么是订阅者:Watcher,Compile时遇到{{}},v-modle等时,创建订阅者,并触发get添加到dep依赖管理中心
什么是mvvm
Model是指数据模型,用于业务逻辑实现和数据改变的定义,
View是视图模型,用户将数据模型的UI展示
ViewModel:是将数据模型和视图模型连接起来,当数据改变触发页面ui的改变,这个是双向的,就是双方的改变都会被同步起来,
用于监听数据模型的改变和管理视图变更,可以理解用于同步View和Model的对象,
好处就是,这个同步复杂的关系和逻辑开发者不需要关注,不需要处理Dom,只需把关注点放在业务逻辑上
View是视图模型,用户将数据模型的UI展示
ViewModel:是将数据模型和视图模型连接起来,当数据改变触发页面ui的改变,这个是双向的,就是双方的改变都会被同步起来,
用于监听数据模型的改变和管理视图变更,可以理解用于同步View和Model的对象,
好处就是,这个同步复杂的关系和逻辑开发者不需要关注,不需要处理Dom,只需把关注点放在业务逻辑上
virtual Dom的理解
用Js对象结构表示的Dom树的结构,本质上就是真实dom的映射。里面包含标签名,属性和子元素对象。vue最终的工作就是通过这颗树批量生成真实dom。直接修改js这个树对象,达到统一更新dom。整个机制是在JavaScript层面计算,在完成之前并不会操作DOM,等数据稳定之后再实现精准的修改。这样更能达到优化性能的目的。
虚拟dom的作用其实有两方面:
1、提供与真实dom节点所对应的虚拟节点vnode
2、将虚拟vnode和旧vnode进行对比(diff),然后更新视图
虚拟dom&diff算法
vue2
1.diff 算法是虚拟 DOM 技术的必然产物:通过新旧虚拟 DOM 作对比,区分出增加、删除、移动,更新的元素,将变化更新在真实 DOM 上;Vue2.diff高效的执行对比过程,将时间复杂度降低为 O(n)
2.vue 2.x 中为了降低 Watcher 粒度,每个组件只有一个 Watcher 与之对应,引入 diff 和key结合能精确找到发生变化的地方。
3.vue 中 diff 执行的时刻是组件实例执行其更新函数时,它会比对上一次渲染结果 oldVnode 和新的渲染结果 newVnode,此过程称为 patch。 (diff发生的地方-patch.js--patchVnode())
4.diff 过程整体遵循深度优先、同层比较的策略;两个节点之间比较会根据它们是否拥有子节点或者文本节点做不同操作;比较两组子节点是diff算法的重点,源码在updateChildren(),首先假设头尾节点可能相同做 4 次比对尝试(首首,尾尾,首尾,尾首), 如果没有找到相同节点才按照通用方式遍历查找,查找结束再按情况处理剩下的节点;借助 key 通常可以非常精确找到相同节点,因此整个patch过程非常高效。(patch.js 高效性分析-updateChildren()),不使用diff算法去比较两颗树的算法复杂度首O(n^3),使用diff算法能将复杂度降低到O(n)
2.vue 2.x 中为了降低 Watcher 粒度,每个组件只有一个 Watcher 与之对应,引入 diff 和key结合能精确找到发生变化的地方。
3.vue 中 diff 执行的时刻是组件实例执行其更新函数时,它会比对上一次渲染结果 oldVnode 和新的渲染结果 newVnode,此过程称为 patch。 (diff发生的地方-patch.js--patchVnode())
4.diff 过程整体遵循深度优先、同层比较的策略;两个节点之间比较会根据它们是否拥有子节点或者文本节点做不同操作;比较两组子节点是diff算法的重点,源码在updateChildren(),首先假设头尾节点可能相同做 4 次比对尝试(首首,尾尾,首尾,尾首), 如果没有找到相同节点才按照通用方式遍历查找,查找结束再按情况处理剩下的节点;借助 key 通常可以非常精确找到相同节点,因此整个patch过程非常高效。(patch.js 高效性分析-updateChildren()),不使用diff算法去比较两颗树的算法复杂度首O(n^3),使用diff算法能将复杂度降低到O(n)
虚拟dom
1、vue通过编译将template模板转换成渲染函数render,执行渲染函数就可以得到虚拟dom树
2、Vnode:可以代表一个真实dom节点,通过creatElement能将Vnode渲染成真实dom,可以将vnode理解成节点描述对象
patch:虚拟dom核心,可以将vnode渲染成真实dom,这个过程是对比新旧节点有哪些不同,然后找到相同节点就复用,不同节点进行批量更新
2、Vnode:可以代表一个真实dom节点,通过creatElement能将Vnode渲染成真实dom,可以将vnode理解成节点描述对象
patch:虚拟dom核心,可以将vnode渲染成真实dom,这个过程是对比新旧节点有哪些不同,然后找到相同节点就复用,不同节点进行批量更新
vue3
但是在Vue3中使用了静态标记后,对于不参与更新的元素,只会被创建一次,在渲染时直接复用即可
vue3展望
vue3适合我嘛?会迅速替代Vue吗
升级是否平滑:vue3兼容之前的写法,仅新增少量api Composition api是可选的
生态是否跟上:相关工具、生态、库更新都需要时间,或许到普及还需要一段时间
vue3比vue2好吗:
杀手级特性:Composition setup函数,
<script setup> 代码简化
用户体验:性能提升,响应式革新
更好类型判断支持
兼容性
vue3新特性
双向数据绑定proxy
vue2通过递归调用defineproperty使之变为响应式对象
vue3 proxy原生支持对象的属性访问,新增,删除的拦截,支持数组属性和长度的属性
编译优化
vue3通过标记所有静态根节点,diff的时候只比较动态节点内容
Fragments
模板不再创建唯一根节点
静态提升
patch flag, 跳过静态节点,直接对比动态节点,缓存事件处理函数
源码体积的优化
vue3移除了一些不常用的api,例如:inline-template、filter等 使用tree-shaking
2. Composition Api 与 Vue 2.x使用的Options Api 有什么区别?Options Api
Options Api
包含一个描述组件选项(data、methods、props等)的对象 options;
API开发复杂组件,同一个功能逻辑的代码被拆分到不同选项 ;
使用mixin重用公用代码,也有问题:命名冲突,数据来源不清晰;
composition Api
它是基于函数的 api,可以更灵活的组织组件的逻辑。解决options api在大型项目中,options api不好拆分和重用的问题。
使用
setup
beforecreat/creat前执行
参数:
props: 组件传入的属性
context(attrs、slot 和emit,分别对应 Vue2.x 中的 $attr属性、slot插槽 和$emit)
reactive
可以代理一个对象, 但是不能代理基本类型,例如字符串、数字、boolean 等
ref
一般用于js 基础类型的双向绑定
toRefs
toRefs 用于将一个 reactive 对象转化为属性全部为 ref 对象的普通对象(...toRefs(user),解构reactive的对象返回给组件使用)
isRef
computed
computed可传入get和set
watch 与 watchEffect 的用法
watchEffect 不需要手动传入依赖
watchEffect 会先执行一次用来自动收集依赖
watchEffect 无法获取到变化前的值, 只能获取变化后的值
jsx
生命周期
beforeDestroy变beforeUnmount
destroyed变unmounted
beforeCreate和created被setup替换了,其次,钩子命名都增加了on
新增用于调试的钩子函数onRenderTriggered和onRenderTricked
Teleport传送门(dialog插入到body上的例子)
Suspense
vue3响应式实现原理
用法变更
在 Vue2.x 中具名插槽和作用域插槽分别使用slot和slot-scope来实现, 在 Vue3.0 中将slot和slot-scope进行了合并同意使用。 Vue3.0 中v-slot:
自定义指令名称变更,update去掉,componentUpdate->update,新增beforeUpdate、beforeUnmount
v-model 升级,Vue 3 中抛弃了.async写法, 统一使用v-model
vue的优点和缺点
优点:数据的双向绑定,组件化,提高了开发效率,方便重复使用,提升了项目的可维护性,便于协同开发,前后端分离,更专注业务
缺点:不支持低版本浏览器,不适于SEO优化
vue3优化
monorepo管理方式:yarn workspace,将模块拆分到package中
性能优化:引入tree-shaking,未被使用到的不会被打包
类型检测增强:从flow换到了ts
内部代码优化:- 响应式相关代码更加简洁,数据劫持私用proxy,在使用时候劫持,性能得到了提高
defineProperty
vue2的响应式实现是基于define property,对data做遍历+递归,每个属性设置了getter、setter,(把不需要相应的数据也进行了劫持)
Proxy
proxy是针对一个对象的,这个对象所有操作都会被监听(真正访问到的内部属性才会变成响应式,简单的可以说是按需实现响应式,减少性能消耗。)
模板编译优化
通过编译阶段对静态模板的分析,生成block tree,可以对子节点动态收集。增加了patchFlag标记动态节点,渲染效率不再与模板大小成正相关,而是与模板中动态节点的数量成正相关。
Vue Composition API 基本使用
基于函数和逻辑复用机制
Vue Composition API 围绕一个新的组件选项 setup 而创建。setup() 为 Vue 组件提供了状态、计算值、watcher 和生命周期钩子。
并没有让原来的 API(Options-based API)消失。允许开发者 结合使用新旧两种 API(向下兼容)
vue2mixin和vue3setup使用区别
命名冲突问题
不清楚暴露出来的变量的作用
逻辑重用到其他 component 经常遇到问题
Vue3.0是如何变得更快的
diff优化
Vue2.x 中的虚拟dom是进行全量的对比。
Vue3.0 中新增了静态标记(PatchFlag):在与上次虚拟结点进行对比的时候,值对比带有patch flag的节点,并且可以通过flag 的信息得知当前节点要对比的具体内容化。
hoistStatic 静态提升
Vue2.x : 无论元素是否参与更新,每次都会重新创建。
Vue3.0 : 对不参与更新的元素,只会被创建一次,之后会在每次渲染时候被不停的复用。
事件侦听器缓存 cacheHandlers
默认情况下onClick会被视为动态绑定,所以每次都会去追踪它的变化,但是因为是同一个函数,所以没必要去追踪它的变化,想办法将它直接缓存起来复用就会提升性能。
周期函数
beforeCreate-> 使用 setup()
created-> 使用setup()
beforeMount -> onBeforeMount
Mounted -> onMounted
beforeUpdate -> onBeforeUpdate
updated -> onUpdated
beforeDestory ->onBeforeDestory
destoryed -> onDestoryed
Proxy和Object.defineProperty对比
Proxy优势
直接监听整个对象而非属性
直接监听数组的变化
13种拦截方法,ownKeys、deleteProperty、has 等是 Object.defineProperty 不具备的。
Proxy返回的是一个新对象,可以操作新对象达到目的,而Object.defineProperty只能遍历对象属性
Proxy性能更好
Proxy缺点
5. Proxy 兼容性差 ⽬前并没有⼀个完整⽀持 Proxy 所有拦截⽅法的Polyfill⽅案
Object.defineProperty缺点
只能劫持对象的属性,因此需要对每个属性进行遍历。
vue不能检测到对象属性的添加或删除,使用Vue.set
不能监听数组长度变化,通过改变.length无法被监听
vue做的处理:Vue使用了重写原型的方案代替。拦截了数组的一些方法,在这个过程中再去做通知变化等操作。
第一步:先获取原生 Array 的原型方法,因为拦截后还是需要原生的方法帮我们实现数组的变化。
第二步:对 Array 的原型方法使用 Object.defineProperty 做一些拦截操作。
第三步:把需要被拦截的 Array 类型的数据原型指向改造后原型。
vue2如何检查数组变化
Vue重写Array原型。对⽅法( push , unshift , splice )进⾏⼿动 observe。
对 Array 的原型方法使用Object.defineProperty 做一些拦截操作。把需要被拦截的 Array 类型的数据原型指向改造后原型。
因为原型链的机制,找到对应的⽅法就不会继续往上找了。编译⽅法中会对⼀些会增加索引的
⽅法( push , unshift , splice )进⾏⼿动 observe。
vue 设计原则的理解
渐进式vue框架
高效性
易用性
灵活性
spa/seo/ssr是什么
vue为什么要求组件模板只能有一个根元素
一个 vue 单文件组件就是一个 vue 实例,如果 template 下有多个 div 那么如何指定 vue 实例的根入口呢, 为了让组件可以正常生成一个 vue 实例,这个 div 会自然的处理成程序的入口,通过这个根节点,来递归遍历整个vue树下的所有节点,并处理为vdom,最后再渲染成真正的 HTML,插入在正确的位置。
不同组件之间的嵌套,她们的生命周期是怎样
渲染过程:
⽗组件挂载完成⼀定是等⼦组件都挂载完成后,才算是⽗组件挂载完,所以⽗组件的mounted在⼦组件mouted之后
⽗beforeCreate -> ⽗created -> ⽗beforeMount -> ⼦beforeCreate -> ⼦created -> ⼦beforeMount
-> ⼦mounted -> ⽗mounted
⼦组件更新过程:
1. 影响到⽗组件: ⽗beforeUpdate -> ⼦beforeUpdate->⼦updated -> ⽗updted
2. 不影响⽗组件: ⼦beforeUpdate -> ⼦updated
⽗组件更新过程:
1. 影响到⼦组件: ⽗beforeUpdate -> ⼦beforeUpdate->⼦updated -> ⽗updted
2. 不影响⼦组件: ⽗beforeUpdate -> ⽗updated
销毁过程:
⽗beforeDestroy -> ⼦beforeDestroy -> ⼦destroyed -> ⽗destroyed
看起来很多好像很难记忆,其实只要理解了,不管是哪种情况,都⼀定是⽗组件等待⼦组件完成后,才
会执⾏⾃⼰对应完成的钩⼦,就可以很容易记住
vue的生命周期
1、new vue()
2、初始化生命周期钩子
beforeCreate :(实例初始化之后,el和data还没初始化,无法访问methods、data、computed)
data observer和事件配置调用
初始化内部事件 inject,provide state
created:(data已经初始化,完成dataObserver/计算属性/event/watch事件回调,dom还没挂载)
是否有el对象(无就挂载vm.$mount(el))
是否有模板(有就转化为render函数,无就编译rl对象外层html作为模版)
beforeMount:(挂载之前,render函数首次被调用成虚拟Dom)
创建vue实例下的$el(虚拟)
并将其替换真正的Dom
mounted:(挂载完成,dom树已经完成
渲染到页面,可以进行dom操作)
beforeUpdate:(数据有更新被调用,发生在Dom重新渲染和打补丁之前,可以进一步修改状态,不会触发重新渲染过程)
Updated:(虚拟dom重新渲染补丁,以最小dom开支来渲染
不建议这步进行状态修改,可能会导致无限循环)
beforeDestroy:(在实例销毁之前,实例还能使用,可以做一些清除定时器,监听dom事件的操作)
清除watcher,子组件,事件监听器等
destroyed:(组件被销毁后调用,事件监听会被移除,所有实例都会被销毁,但是data和$el还是可以取到,只会销毁实例,不会清除dom)
路由
路由原理
更新路由但不重新请求页面,是前端路由原理的核心之一,目前在浏览器环境中这一功能的实现主要有2种方式:
1、利用URL中的hash("#");
2、利用HTML5 History interface在中新增的方法pushState和replaceState;
在初始化对应的history之前,会对mode做校验:若浏览器不支持HTML5History方式(通过supportsPushState变量判断),则mode设为hash;若不是在浏览器环境下运行,则mode设为abstract;
VueRouter类中的onReady(),push()等方法只是一个代理,实际是调用的具体history对象和location的对应方法
$router.push()-->HashHistory.push()-->History.transitionTo()-->History.updateRoute()-->{app._route=route}-->vm.render()
vue路由的实现
hash
HashHistory.push
1、hash虽然出现在url中,但不会被包括在http请求中,它是用来指导浏览器动作的,对服务器端完全无用,因此,改变hash不会重新加载页面。
2、可以为hash的改变添加监听事件:
window.addEventListener("hashchange",funcRef,false),当路由改变,会触发vue实例的render()
3、每一次改变hash(window.location.hash),都会在浏览器访问历史中增加一个记录。
push():window.location.hash=route.fullPath
HashHistroy.replace()
replace()方法与push()方法不同之处在于,它并不是将新路由添加到浏览器访问历史栈顶,而是替换掉当前的路由:
不是直接对window.location.hash进行赋值,而是调用window.location.replace方法将路由进行替换。
history
HTML5History
从HTML5开始,History interface提供了2个新的方法:pushState(),replaceState()使得我们可以对浏览器历史记录栈进行修改:
这2个方法有个共同的特点:当调用他们修改浏览器历史栈后,虽然当前url改变了,但浏览器不会立即发送请求该url,这就为单页应用前端路由,更新视图但不重新请求页面提供了基础。
window.history.pushState(stateObject,title,url)
window.history,replaceState(stateObject,title,url)
这2个方法有个共同的特点:当调用他们修改浏览器历史栈后,虽然当前url改变了,但浏览器不会立即发送请求该url,这就为单页应用前端路由,更新视图但不重新请求页面提供了基础。
window.history.pushState(stateObject,title,url)
window.history,replaceState(stateObject,title,url)
vue路由的钩子函数
全局的钩子函数
1/beforeEach (to,from,next) 路由改变前调用 常用验证用户权限
参数
to :即将要进入的目标路由对象
from:当前正要离开的路由对象
next:路由控制参数
next():如果一切正常,则调用这个方法进入下一个钩子
next(false):取消导航(即路由不发生改变) next('/login'):当前导航被中断,然后进行一个新的导航
next(error):如果一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError ()
2/afterEach (to,from) 路由改变后的钩子 常用自动让页面返回最顶端 用法相似,少了 next 参数
一般写在main.js可以做权限控制
单个的路由配置的钩子函数
beforeEnter (to,from,next)
组件内的钩子函数
钩子函数介绍
1. beforeRouteEnter (to,from,next)
该组件的对应路由被 comfirm 前调用。
此时实例还没被创建,所以不能获取实例(this)
2. beforeRouteUpdate (to,from,next)
当前路由改变,但改组件被复用时候调用
该函数内可以访问组件实例(this)
3. beforeRouteLeave (to,from,next)
当导航离开组件的对应路由时调用。
该函数内可以访问获取组件实例(this)
应用场景
(一) 清除当前组件中的定时器
(二) 当页面中有未关闭的窗口, 或未保存的内容时, 阻止页面跳转
(三) 保存相关内容到Vuex中或Session中
你知道 nextTick 的原理吗?
nextTick是Vue提供的⼀个全局API,由于vue的异步更新策略导致我们对数据的修改不会⽴刻体现在dom变化上,此时如果想要⽴即获取更新后的dom状态,就需要使⽤这个⽅法
2. Vue 在更新 DOM 时是异步执⾏的。只要侦听到数据变化,Vue 将开启⼀个队列,并缓冲在同⼀事件循环中发⽣的所有数据变更。如果同⼀个 watcher 被多次触发,只会被推⼊到队列中⼀次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是⾮常重要的。nextTick⽅法会在队列中加⼊⼀个回调函数,确保该函数在前⾯的dom操作完成后才调⽤。同时也避免了setTimeout可能存在的多次执行问题。确保队列中的微任务在一次事件循环前被执行完毕。
nextTick 优先级是使用微任务(微任务会先于宏任务,微任务执行完后会进入浏览器更新渲染阶段,所以更新渲染前使用微任务会比宏任务块),其次再到宏任务。优先级为promise.then> MutationObserver> setImmediate> setTimeout。
注意:之所以将 nextTick 的回调函数放⼊到数组中⼀次性执⾏,⽽不是直接在 nextTick 中执⾏回调函数,是为了保证在同⼀个tick内多次执⾏了 nextTcik ,不会开启多个异步任务,⽽是把这些异步任务都压成⼀个同步任务,在下⼀个tick内执⾏完毕。
Vue 的模板编译原理
Template > ast 抽象语法树> render function > 执行 render function > VNode
第⼀步:解析
将模板字符串解析⽣成 AST,⽣成的AST 元素节点总共有 3 种类型,1 为普通元素, 2 为表达式,3为纯⽂本。
第⼆步:优化语法树
Vue 模板中并不是所有数据都是响应式的,有很多数据是⾸次渲染后就永远不会变化的,那么这部分数据⽣成的 DOM 也不会变化,我们可以在 patch 的过程跳过对他们的⽐对。
此阶段会深度遍历⽣成的 AST 树,检测它的每⼀颗⼦树是不是静态节点,如果是静态节点则它们⽣成DOM 永远不需要改变,这对运⾏时对模板的更新起到极⼤的优化作⽤。
1. ⽣成代码
const code = generate(ast, options)
通过 generate ⽅法,将ast⽣成 render 函数。
Vue 组件 data 为什么必须是个函数而 Vue 的根实例则没有此限制
组件中的data写成⼀个函数,数据以函数返回值形式定义,这样每复⽤⼀次组件,就会返回⼀份新的data,类似于给每个组件实例创建⼀个私有的数据空间,让各个组件实例维护各⾃的数据。⽽单纯的写成对象形式,就使得所有组件实例共⽤了⼀份data,就会造成⼀个变了全都会变的结果,但由于根实例只有一个,所以不存在数据污染这种情况,也就可以使用对象了。
你了解哪些 Vue 性能优化方法
编码阶段
组件
keep-alive
data数据减少
Object.freeze不需要做响应
路由懒加载,异步组件
函数式组件
列表
v-if和v-for不要同时使用
列表绑定时间用事件代理
key保证唯一,不要用索引
⻓列表滚动到可视区域动态加载
其他
防抖节流
第三⽅模块按需导⼊
SEO优化
预渲染
服务端渲染SSR
打包优化
压缩代码
Tree Shaking/Scope Hoisting
使⽤cdn加载第三⽅模块
多线程打包happypack
splitChunks抽离公共⽂件
sourceMap优化
⽤户体验
⻣架屏
PWA
还可以使⽤缓存(客户端缓存、服务端缓存)优化、服务端开启gzip压缩等。
new Vue()都做了什么?
1. new Vue()是创建Vue实例,它内部执⾏了根实例的初始化过程。
2. 具体包括以下操作:
选项合并 ⽤户选项、默认选项
$children, $refs, $slots, $createElement等实例属性和⽅法初始化
数据响应式处理
⾃定义事件处理
⽣命周期钩⼦调⽤
可能的挂载
3. 总结:new Vue()创建了根实例并准备好数据和⽅法,未来执⾏挂载时,此过程还会递归的应⽤于它的⼦组件上,最终形成⼀个有紧密关系的组件实例树。
computed 和 watch 有什么区别
computed 计算属性,是依赖其他属性的计算值,并且有缓存,只有当依赖的值变化时才会更新。
watch 是在监听的属性发⽣变化时,在回调中执⾏⼀些逻辑。
所以, computed 适合在模板渲染中,某个值是依赖了其他的响应式对象甚⾄是计算属性计算⽽来,
⽽ watch 适合监听某个值的变化去完成⼀段复杂的业务逻辑。
watch支持异步,computed不支持异步
Vue.component()、Vue.use()、Vue.directive,this.$xxx()
Vue.component()方法注册全局组件。
第一个参数是自定义元素名称,也就是将来在别的组件中使用这个组件的标签名称。
第二个参数是将要注册的Vue组件。
vue.use()注册插件
Vue.use注册插件,这接收一个参数。这个参数必须具有install方法。Vue.use函数内部会调用参数的install方法。
如果插件没有被注册过,那么注册成功之后会给插件添加一个installed的属性值为true。Vue.use方法内部会检测插件的installed属性,从而避免重复注册插件。
插件的install方法将接收两个参数,第一个是参数是Vue,第二个参数是配置项options。
在install方法内部可以添加全局方法或者属性
Vue的prototype
vue指令(v-report)
Vue.directive('report', report(options))
自定义指令提供了几个钩子函数:bind:指令第一次绑定到元素时调用inserted:被绑定元素插入父节点时调用update:所在组件的 VNode 更新时调用
Vue 实现一个高阶组件
高阶组件就是一个函数,且该函数接受一个组件作为参数,并返回一个新的组件。在不改变对象自身的前提下在程序运行期间动态的给对象添加一些额外的属性或行为。
vue的runtime版本和带有compiler的版本
react基础
领域知识
构建和打包
webapck
webpack基础配置
开发时
webpack-dev-server、webpack-dev-middleware、sourceMap
loader
css-loader、解析css文件
style-loader、在html页面中插入<style>标签
less-loader、
sass-loader、
url-loader:
1、如果页面图片较多,发很多http请求,会降低页面性能。这个问题可以通过url-loader解决。url-loader会将引入的图片编码,生成dataURl并将其打包到文件中,最终只需要引入这个dataURL就能访问图片了。当然,如果图片较大,编码会消耗性能。因此url-loader提供了一个limit参数,小于limit字节的文件会被转为DataURl,大于limit的还会使用file-loader进行copy
file-loader:解决开发环境和生产环境下资源目录不一致的问题,并加上hash配置来解决缓存问题(加载.jpg\png文件图片资源)、
babel-loader、将高级语法es6/es7解析成低版本的es5
plugins
html-webpack-plugin:自动在express根目录下生成html,且自动引入bundle.js
clean-webpack-plugin:自动清理dist
copy-webpack-plugin:将静态资源一起打包到dist里
BannerPlugin:在bundle.js加入注释
webpack 编译过程
是一个串行的过程
初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数;
开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run方法开始执行编译;
确定入口:根据配置中的 entry 找出所有的入口文件;
编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理;
完成模块编译:在经过第4步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系;
输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会;
输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统。
webpack生命周期
compiler
compiler对象包含了webpack环境所有的配置信息,这个对象在启动webpack时被一次性建立,并配置好所有操作的设置,包括options,loader,plugin,当webpack环境中应用一个插件时,插件将收到此compiler对象的引用,可以用来访问webpack的主环境
compilation
对象包含当前的环境资源、编译生产资源、变化的文件等,当webpack开发环境中间件时,每当检测到一个文件变化,就会创建一个新的compilation,从而生成一组新的编译资源。compilation对象也提供很多关键时机的回调,以供插件做自定义出来时使用
webpack高级配置
- img标签资源处理
- 多页应用打包
- 第三方库的引入方式
- 区分配置文件打包
- 环境变量
- proxy
- HMR
loader
作用:处理非js文件
原理:从下至上,右至左加载loader,将源代码管道式进行加工处理后返回给下一个loader
如何编写loader
其实loader就是对外暴露一个函数
1、module.exports = function(source) {
//处理源码后返回
return source
}
webpack.config.js加载loader
rules: [{ test: /.js$/, use: './loaders/myloader.js' }]
plugins
原理
自定义插件就是在webpack编译过程的生命周期钩子中,进行编码开发,实现一些功能。
编写插件
创建一个类class或js构造函数,类中添加上定义一个 apply 方法(或函数的prototype上定义apply)。
- 从apply参数传入的compiler取出指定事件钩子,在钩子回调中取出complitaion对象compile\emit\done
- 通过 compilation 处理 webpack 内部特定的实例数据
如果是插件是异步的,在插件的逻辑编写完后调用 webpack 提供的 callback
实现生命钩子的原理是tapable,发布订阅
1. 定义钩子
2. 使用者注册事件
3. 在合适的阶段调用钩子,触发事件
webpackCompiler注册了这些钩子并在编译期间触发调用,所有注册了这些钩子的都会被触发调用
class HelloWorldPlugin {
apply(compiler) {
compiler.hooks.done.tap('Hello World Plugin', (stats) => {
console.log('Hello World!');
});
}/
}
module.exports = HelloWorldPlugin;
谈谈webpack的理解
作用
1. 递归构建一个依赖关系图
2. 将这些模块打包成一个或者多个bundle
原理
分析bundle.js可知:
是一个自调用函数
递归分析项目依赖,生成一份键值对作为自调用函数都参数传入,路径作为key,文件内容转化成eval函数作为函数作为value。
webpack实现事件流的原理
在webpack内部实现事件流机制的核心就在于**tapable**,有了它就可以通过事件流的形式,将各个插件串联起来,tapable类似于node中的events库,核心原理也是**发布订阅模式**
1. 定义钩子
2. 使用者注册事件
3. 在合适的阶段调用钩子,触发事件
webpack 通过 tapable 这种巧妙的钩子设计很好的将实现与流程解耦开来
特点
1. 默认只支持js模块,每个js文件都是一个模块
2. 由入口main.js开始,根据依赖关系,将module => 组装成bundle
3. 其中遇到css,图片等非js模块,需要不同的Loader提供不同的打包能力
两大核心
loader:(处理非js文件)
模块加载器,原内容 => 新内容 加载非js模块
plugin:(通过生命钩子扩展webpack功能)
webpack构建时机(生命周期)暴露出来钩子,扩展自己的逻辑达到自己的目的
常用功能
1. 文件优化:js, html, css压缩,图片合并等
2. 代码转换:ts => js scss/less/stylus => css
3. 模块合并:模块化开发的项目中模块众多,把相关模块合并成一个文件
4. 代码分割:多个页面的公共模块可以提取出来,首屏不需要加载的模块可以采用异步加载
babel
作用
将一些es6,es7高级语法转换成目前浏览器能解析的es5,
babel只是转译新标准引入的语法,比如ES6的箭头函数转译成ES5的函数;而新标准引入的新的原生对象,部分原生对象新增的原型方法,新增的API等(如Proxy、Set等),这些babel是不会转译的。需要用户自行引入polyfill来解决
原理
babel的转译过程也分为三个阶段
parsing
ES6代码输入 ==》 babylon进行解析 ==》 得到抽象语法树AST
transforming
plugins:plugin用babel-traverse对AST树进行遍历转译 ==》 得到新的AST树,如果这个阶段不使用任何插件,那么babel会原样输出代码。
generating
用babel-generator通过AST树生成ES5代码
以ES6代码转译为ES5代码为例,babel转译的具体过程如下:ES6代码输入 ==》 babylon进行解析 ==》 得到AST==》 plugin用babel-traverse对AST树进行遍历转译 ==》 得到新的AST树==》 用babel-generator通过AST树生成ES5代码
核心包
babel-core:babel转译器本身,提供了babel的转译API,如babel.transform等,用于对代码进行转译。像webpack的babel-loader就是调用这些API来完成转译过程的。
babel-traverse:用于对AST(抽象语法树,想了解的请自行查询编译原理)的遍历,主要给plugin用
babel-generator:根据AST生成代码
工具包
babel-cli:babel的命令行工具,通过命令行对js代码进行转译
babel-register:通过绑定node.js的require来自动转译require引用的js代码文件
功能包
babel-types:用于检验、构建和改变AST树的节点
babel-template:辅助函数,用于从字符串形式的代码来构建AST树节点
babel-helpers:一系列预制的babel-template函数,用于提供给一些plugins使用
babel-code-frames:用于生成错误信息,打印出错误点源代码帧以及指出出错位置
babel-plugin-xxx:babel转译过程中使用到的插件,其中babel-plugin-transform-xxx是transform步骤使用的
babel-preset-xxx:transform阶段使用到的一系列的plugin
babel-polyfill:JS标准新增的原生对象和API的shim,实现上仅仅是core-js和regenerator-runtime两个包的封装
babel-runtime:功能类似babel-polyfill,一般用于library或plugin中,因为它不会污染全局作用域
polyfill和runtime
polyfill
polyfill是一个针对ES2015+环境的shim,实现上来说babel-polyfill包只是简单的把core-js和regenerator runtime包装了下,这两个包才是真正的实现代码所在
使用babel-polyfill会把ES2015+环境整体引入到你的代码环境中,让你的代码可以直接使用新标准所引入的新原生对象,新API等,一般来说单独的应用和页面都可以这样使用。
如果只是需要引入部分新原生对象或API,那么可以按需引入,而不必导入全部的环境,具体见下文的core-js
runtime
babel-runtime(提供一整套runtime实现)
babel-runtime就是一个提供了regenerator、core-js和helpers的运行时库。
babel-runtime其实也不是真正的实现代码所在,真正的代码实现是在core-js中
transform-runtime(按需导出使用的新特性的runtime实现)
babel-plugin-transform-runtime插件依赖babel-runtime,babel-runtime是真正提供runtime环境的包;也就是说transform-runtime插件是把js代码中使用到的新原生对象和静态方法转换成对runtime实现包的引用
babel-plugin-transform-runtime既保持了新特性的功能,同时又没有像polyfill那样污染全局环境(因为最终生成的代码中,并没有对Symbol的引用)
使用
命令行 babel-cli
创建.babelrc文件
plugins
presets
如果要自行配置转译过程中使用的各类插件,那太痛苦了,所以babel官方帮我们做了一些预设的插件集,称之为preset,这样我们只需要使用对应的preset就可以了。以JS标准为例,babel提供了如下的一些preset:es2015 es2016 es2017 env(最新标准)
webpack性能优化
webpack自带优化详解
tree shacking
默认在production和使用ES6 import下使用,自动移除未引用的代码
scope Hoisting作用域提升
在模块之间关系进行结果推测,让打包文件体积更少,运行更快
原理:分析模块之间到依赖关系,尽可能把打散的模块合并到一个函数中去
css优化
mini-css-extract-plugin:提取到单独的css文件link
`postcss-loader`和`autoprefixer`插件- 自动添加前缀
optimize-css-assets-webpack-plugin-压缩(因为css压缩会覆盖webpack默认的js压缩配置,所以有引入`terser-webpack-plugin`)
js优化
- 代码分离
手动配置多入口、
抽取公共代码、
CommonsChunkPlugin(目前被移除)
SplitChunksPlugin
vendor提取:node_modules
common:提取多次公用文件到common.js
styles:提取公共css
路由懒加载、
- noParse
阻止webpack浪费精力去解析这些明知道没有依赖的库,例如jq bootstrap,chart.js
externals
开发组件库时webpack不打包的库
- IgnorePlugin
需要忽略第三方模块内部依赖
,例如忽略 moment 的本地化内容
- DllPlugin:将固定库抽取成动态链接库节省资源
--DllPlugin--webpack提供的插件 --将一些不修改的依赖只打包一次,下次不打包(vue,react。。。),只打包业务代码大大提升了构建的速度
--DllReferencePlugin,用于主webpack.config.js配置中
--add-asset-html-webpack-plugin:主要思想是将一些不做修改依赖,提前打包,当发布代码的时候就不用对这部分代码进行打包,从而节约打包速度
代码压缩:uglifyjsplugin
tree shacking
默认在production和使用ES6 import下使用,自动移除未引用的代码
scope hoist作用域提升
在模块之间关系进行结果推测,让打包文件体积更少,运行更快
原理:分析模块之间到依赖关系,尽可能把打散的模块合并到一个函数中去
- 多进程打包
happypack
- 打包分析
webpack-bundle-analyzeer
speed-measure-webpack-plugin
如何使用webpack对项目对优化
前向治理(提升构建速度)
speed-measure-webpack-plugin
采集性能指标,可以得到webpack在整个编译过程中在loader、plugin上花费的时间,基于该数据可以专项的进行优化和治理。
开启缓存:如果通过SMP分析得知在loader编译过程耗时较多,那么可以在核心loader,例如babel-loader中添加缓存
开启happyPack多线程编译
dll可以简单理解成提前打包,例如lodash、echarts等大型npm包,可以通过dll将其提前打包好,这样在业务开发过程中就不用再重复去打包了,可以大幅缩减打包时间。
hard-source-webpack-plugin
直接从缓存中读取编译过的文件,提升二次构建速度
忽略不需要打包的内容
moment 的本地化内容,new webpack.IgnorePlugin(/.~Klocale/, /moment/) 打包后 250kb-->56kb
webpack精准过滤不需要解析的文件
noParse: /chart.js/,jq,boostrat
升级webpack5
webpack5利用 持久缓存 来提高构建性能,或许升级webpack后,前述的各种优化,都将成为历史。
后向治理(保证构建质量)
可视化分析构建结果
webpack-bundle-analyzeer
是否需要按需加载
是否需要提取公共代码
是否需要制定cacheGroup的策略
tree-sharking
提取公共chunk(splitChunks)
清理deadcode
未使用的文件
未使用的已暴露变量
业务开发过程中,随着业务迭代,经常有些文件、模块及代码被废弃,这些废弃代码随着时间推移,将逐渐变为历史包袱,所以针对构建后结果,我们要做的就是清理其中的deadcode。
执行:webpack --profile --json > compilation-stats.json
通过对modules和chunks加以分析,就可以得到webpack完整的依赖关系,从而梳理出废弃文件及废弃代码,同时也可以根据业务形态进行定制
通过 webpack 编译源文件时,生成的包含有关于模块的统计数据的 JSON 文件,这些统计数据不仅可以帮助开发者来分析应用的依赖图表,还可以优化编译的速度。
webpack-deadcode-plugin
前面提到分析stats.json,但因为是原始数据,数据量比较大,有一定处理和清洗成本,所以可以使用开源的webpack-deadcode-plugin[5]这个插件
通过webpack-deadcode-plugin,可以快速筛选出:
未使用的文件
未使用的已暴露变量
结合eslint、tslint进行治理
lint可以快速的扫描出未使用的变量,这能够极大的提升我们的deadcode清理效率。
首先通过lint对未使用变量进行清理
再通过webpack-deadcode-plugin再扫描出未使用文件和未使用的导出变量
实际项目中优化过程
项目问题
背景
基于webpack3版本的,采用babel6版本处理js,并对app和single两个版本分别写了配置,提供了开发、生产环境一系列脚本
现存问题
1. 项目热更新配置可能存在问题,热更新速度很慢
2. 项目整体打包速度比较慢,打包生成的chunk很大
3. chunk间没有提取公共模块,只是简单打了一个vendor
4. 没有区分dev和prod环境,缺少一些针对性的配置
5. /pocnode在没有对应pocnode开发需求的时候,希望代理到现网,不需要同时启动前后端项目
6. /poc模块希望能够在特定条件下,直接打到线上机,模拟线上的登录态
7. vue-resource模块在vue2.0官方已经不推荐使用了,并且不再维护。
方案
1. webpack3升级webpack4。webpack4对production的时候会自动开启Scope hoisting和Tree shaking等等,提高编译速度。
2. 重新规划了构建相关的目录,更有利于后续扩展。scripts下放构建相关的script,具体的webpack配置放在config目录下,并且区分了app/single环境,区分了dev和prod环境。
3. dev环境启动devServer时候,使用portfinder寻找能用的端口,并提示出messages在命令行中,可以快速打开。
4. prod打包优化:利用optimization提取第三方库,提取重复使用模块,单独提取css
5. 针对/poc/想要使用外网环境这一诉求,增加npm run dev:app-out支持
6. 针对只想做前端开发,不想起后台服务这一诉求,增加npm run dev:app-fe支持
7. vue-resouce更新为axios。替代方案,axios不仅适用于客户端,也适用于nodejs环境,并且在客户端侧做了xsrf防护,还具有自动处理json数据格式,abort等功能。
splitChunks
vender
common
styles
8. 接入sentry: sentry地址 - https://sentry.oa.com/Tencent/mmbizpoc/ 。sentry目前采样率设置1,只有生产环境会init。
引入eslint
开发注意事项
webpack和gulp区别
glup 基于流的构建工具
gulp是工具链,构建工具,可以配合各种插件做JS压缩,CSS压缩,less编译替代手动实现自动化工作,提高前端工作效率
1.构建工具
2.自动化
3.提高效率
webpack(为SPA 大应用而生)
web是文件打包工具,可以把项目的各种js文件,css文件等打包合成一个或多个文件,主要用于模块化方案,预编译模块的方案
1.打包工具
2.模块化识别
3.编译模块代码方案
webpack-dev-server原理
做了一个express服务器,托管在根目录,找到根目录的index.html
vite
原理:基于浏览器对es模块的支持,启动一个koa服务器拦截浏览器的请求。根据请求路径读取依赖文件,按需以ES模块格式返回给客户端,跳过打包过程,做到项目项目极速启动,模块热更新,按需编译的功能(由于 Vite 在启动的时候不需要打包,也就意味着不需要分析模块的依赖、不需要编译。因此启动速度非常快。当浏览器请求某个模块时,再根据需要对模块内容进行编译。)
1、利用浏览器es module imports,关键变化是index.html中的文件导入方式
<script type="module" src="/src/main.js"></script>
2、第三方依赖打包,并将导入地址修改为相对地址,从而统一所有请求为相对地址
3、启动一个开发服务器处理这些资源请求
4、vite需要根据请求资源做不同解析工作,比如App.vue,将导入模块解析成一个render函数,然后将render设置在函数设置到组件配置对象中,使用
vite对js/ts处理使用esbuild,支持babel,压缩等功能
ts
可扩展性
项目规模
成员规模
是否需要开源
vue是否适合ts
vue2
装饰器风格
vue3
ts重写
函数风格
增量引入ts
同时使用js和ts
监控
页面埋点
idkey+img src
性能监控
性能数据采集需要使用
window.performance API
JS库 web-vitals
FP(首次绘制)
FCP(首次内容绘制 First contentful paint)
LCP(最大内容绘制时间 Largest contentful paint)
FPS(每秒传输帧数)
TTI(页面可交互时间 Time to Interactive)
HTTP 请求响应时间
DNS 解析时间
TCP 连接时间
异常监控
sentry
Vue.config.errorHandler,(Vue提供只能捕获其页面生命周期内的函数,比如created,mounted)
window.onerror()当有js运行时错误触发时,onerror可以接受多个参数(message, source, lineno, colno, error)。
window.addEventListener('error'), function(e) {}, true 会比window.onerror先触发,不能阻止默认事件处理函数的执行,但可以全局捕获资源加载异常的错误
网页崩溃
Service Worker 有自己独立的工作线程,与网页区分开,网页崩溃了,Service Worker 一般情况下不会崩溃;
Service Worker 生命周期一般要比网页还要长,可以用来监控网页的状态
性能
图片优化
懒加载
cdn
防抖节流
预加载
css优化
前端性能优化
js性能优化
网站查看性能优化
实际使用过程的前端性能优化
测试
重构
移动端
前端性能优化
1、前端性能优化
1) 缓存,主要有 cdn、浏览器缓存、本地缓存以及应用离线包 (详细资料:解析Web缓存及其最佳实践)
2) 图⽚优化
3) 静态⽂件优化
4) 浏览器优化
5) ⽂件合并压缩等雅⻁军规常规操作
1)节流防抖
2)按需执⾏
3)回流重绘
4)框架优化(⽐如vue3的静态标记)
5)html、css、javascript
预加载/按需加载
预加载 :资源预拉取(prefetch)则是另一种性能优化的技术。通过预拉取可以告诉浏览器用户在未来可能用到哪些资源。
prefetch支持预拉取图片、脚本或者任何可以被浏览器缓存的资源。
在head里 添加 <linkrel="prefetch"href="image.png">
路由懒加载-按需加载组件,当路由被访问的时候再加载对应组件,而不是首页的时候加载)、第三方依赖按需加载
图片懒加载(IntersectionObserver)
2、前端性能监控
1、前端性能指标分析
2、关键性能指标统计
FP、FCP、FMP与TTI都表示浏览器在屏幕上渲染像素的时间点。
3、性能分析工具
3、框架性能优化
1、Vue性能优化
组件
keep-alive
data数据减少
Object.freeze不需要做响应
路由懒加载,异步组件
函数式组件
列表
v-if和v-for不要同时使用
列表绑定时间用事件代理
key保证唯一,不要用索引
⻓列表滚动到可视区域动态加载
其他
防抖节流
第三⽅模块按需导⼊
2、SEO优化
预渲染;
服务端渲染SSR;
3、用户体验优化
骨架屏;
PWA;
还可以使用缓存(客户端缓存、服务端缓存)优化、服务端开启gzip压缩等。
4、长列表优化
长列表1、vue-virtual-scroll-list优化长列表
虚拟列表的实现原理:只渲染可视区的 dom 节点,其余不可见的数据卷起来,只会渲染可视区域的 dom 节点,提高渲染性能及流畅性,优点是支持海量数据的渲染;
github地址:https://github.com/tangbc/vue-virtual-scroll-list
2、Object.freeze优化长列表
Object.freeze()方法可以冻结一个对象。一个被冻结的对象再也不能被修改;冻结了一个对象则不能向这个对象添加新的属性,不能删除已有属性,不能修改该对象已有属性的可枚举性、可配置性、可写性,以及不能修改已有属性的值。
对于data()或vuex中冻结的对象,vue不会做getter和setter的转换。因此对于一个不变的、大数据量的数组或Object数据来说,使用Object.freeze()可以有效地提升性能。优化
打包优化
压缩代码;uglifyjsplugin
Tree Shaking/Scope Hoisting;
使用cdn加载第三方模块;
多线程打包happypack;
splitChunks抽离公共文件;
sourceMap优化;
nginx
Nginx是一款轻量级HTTP服务器(又叫web服务器),采用事件驱动的异步非阻塞处理方式框架,这让其具有极好的IO性能,时常用于服务器的反向代理和负载均衡
特点
轻量级web服务器
设计思想是事件驱动的异步非阻塞
占用内存少,启动快,并发强
C语言开发
扩展性好,第三方插件多
启动停止命令
配置文件
/usr/local/etc/nginx/nginx.conf (nginx配置文件路径)
/usr/local/var/www (nginx服务器默认的根目录)
/usr/local/Cellar/nginx/1.17.9 (nginx的安装路径)
/usr/local/var/log/nginx/error.log (nginx默认的日志路径)
nginx作用
动静分离
动静分离其实就是 Nginx 服务器将接收到的请求分为动态请求和静态请求。
静态请求直接从 nginx 服务器所设定的根目录路径去取对应的资源,动态请求转发给真实的后台(前面所说的应用服务器,如图中的Tomcat)去处理。
这样做不仅能给应用服务器减轻压力,将后台api接口服务化,还能将前后端代码分开并行开发和部署。(传送门:nginx动静分离的好处)
反向代理
是什么
浏览器或其他终端最终拿到了他想要的内容,但是具体从哪儿拿到的这个过程它并不知道
作用
保障应用服务器的安全(增加一层代理,可以屏蔽危险攻击,更方便的控制权限)
实现负载均衡(稍等~下面会讲)
实现跨域(号称是最简单的跨域方式)
配置
负载均衡
是什么
在服务器集群中,Nginx 可以将接收到的客户端请求“均匀地”(严格讲并不一定均匀,可以通过设置权重)分配到这个集群中所有的服务器上。这个就叫做负载均衡。
作用
分摊服务器集群压力
保证客户端访问的稳定性
配置
正向代理
是什么
意思是一个位于客户端和原始服务器(origin server)之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。客户端才能使用正向代理。当你需要把你的服务器作为代理服务器的时候,可以用Nginx来实现正向代理。
科学上网vpn(俗称翻墙)其实就是一个正向代理工具。该 vpn 会将想访问墙外服务器 server 的网页请求,代理到一个可以访问该网站的代理服务器 proxy 上。这个 proxy 把墙外服务器 server 上获取的网页内容,再转发给客户
Typescript(ts)
TS有什么优势
静态输入:静态类型化是一种功能,可以在开发人员编写脚本时检测错误。
大型的开发项目:使用TypeScript工具来进行重构更变的容易、快捷。
更好的协作:类型安全是在编码期间检测错误,而不是在编译项目时检测错误。
更强的生产力:干净的 ECMAScript 6 代码,自动完成和动态输入等因素有助于提高开发人员的工作效率。
interface 和 type的区别
interface 只能定义对象类型。type声明可以声明任何类型。
interface 能够声明合并,两个相同接口会合并。Type声明合并会报错
type可以类型推导,typeof
脚手架cli
如何开发脚手架
yeoman:老牌的项目脚手架工具
hygen:快速且可扩展的代码生成器
commander:Nodejs处理控制台命令
co:异步控制,同步的逻辑来表达异步的流程
co-prompt:分步接收用户输入
chalk:控制台彩色字体
ora:控制台loading,
exec :require('child_process').exec,执行命令
思路
package.json添加script执行入口文件, 或者bin+npm link全局使用
入口文件#!/usr/bin/env node 以node环境执行
commander库 添加控制台命运选项
引入模板配置文件。获取用户输入,看是否存在文件require('co-prompt')
凭借拉取项目代码的命令并执行,拼接git命令行+切换对应分支(exec)
设计模式
一、单例模式
保证一个类仅有一个实例,并提供一个访问它都全局访问切点(window)
工厂模式
工厂模式主要是为创建对象提供了接口
二、策略模式
定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。
a、 一件事情,有很多方案可以实现。
b、我可以在任何时候,决定采用哪一种实现。
c.、未来可能增加更多的方案。
d、 策略模式让方案的变化不会影响到使用方案的客户。
三、代理模式
为一个对象提供一个代用品或占位符,以便控制对它的访问
四、迭代器模式
提供一种方法顺序访问一个聚合对象中的各个元素
1、generator 天生为迭代器的 API
2、(jq中的迭代器$.each(function(i,j){....i...j}))
3、Map 迭代器
4、 KOA 由插件调用 next() 控制迭代
五、发布—订阅模式(观察这模式)
一对多的依赖关系,当一个对象状态发生变化买所有依赖它的对象都得到通知
应用场景如下:
a、对一个对象状态的更新,需要其他对象同步更新,而且其他对象的数量动态可变。
b、对象仅需要将自己的更新通知给其他对象而不需要知道其他对象的细节。
六、命令模式
和策略模式很相似,命令模式是含有不同的命令(含有接收者的请求):做不同的事情;隐藏接收者执行细节。常见菜单事件,
七、组合模式
使用树形方式创建对象的结构,把相同的操作应用在对象和单个对象
八、模板方法模式
是一种典型的通过封装变化提高系统扩展性的设计模式,将相同逻辑抽象都父类模版中,子类具体自定义(高阶函数更好)
九、享元模式
十、职责链模式
十一、中介者模式
十二、装饰者模式
十三、状态模式
十四、适配器模式
十五、外观模式
构造函数模式
常见问题
怎么做反爬虫,设计思路
后端
反爬虫(判断真人访问)
User-Agent + Referer检测
账号及Cookie验证
验证码
IP限制频次
反反爬虫(模拟真人访问)
chrome headless或phantomjs来模拟浏览器环境
tesseract识别验证码
代理IP淘宝就能买到
前端
1、FONT-FACE拼凑式,不是真正的数字,使用font-face定义字符集并通过unicode去映射展示,图像识别还需要爬取字符集
BACKGROUND拼凑式(与font的策略类似,美团里用到的是background拼凑。数字其实是图片,根据不同的background偏移,显示出不同的字符。)
4、伪元素隐藏式:爬取网页,必须得解析css,需要拿到伪元素的content
元素定位覆盖式(利用css,爬到数据和展示数九以一样)
IFRAME异步加载式(html源码里几乎只有一个iframe,并且它的src是空白的:about:blank。接着js开始运行,把整个页面的框架异步塞到了iframe里面)
字符集替换式,html里明明写的3211,视觉上展示的却是1231
如何搭建mock系统
mock-api(项目地址:/Users/zhoupeiyi/Desktop/面试复习/code/mock-api)
🔥内置支持热Mocker文件替换。
🚀通过JSON快速轻松地配置API。
🌱模拟API代理变得简单。
💥可以独立使用,无需依赖webpack和webpack-dev-server。
低代码底层设计思路
1、建立思维导图,明确需求和功能。
2、使用低代码开始搭应用模型(搭应用有2种方式,一种是从已有模板内导入应用,一种是创建空白应用)
3、定义数据字段类型,设置关联关系,上传数据。
4、设定工作流、审批流、成员权限。
5、api连接、集成第三方平台&外部系统。
6、产品功能测试、用户测试验收。
7、部署生产环境
8、搭建生产环境监控系统。
热更新服务原理
1
Webpack编译初期,启动本地server,让浏览器可以请求本地的静态资源
页面首次打开后,服务端与客户端通过 websocket 建立长连接,把下一次的 hash 返回前端(webpack-dev-server使用websocket通信,webpack-hot-middleware使用Eventsocket)
修改页面代码后,Webpack 监听到文件修改后,开始编译,编译完成后,发送 build 消息给客户端
客户端获取到hash,这个hash将作为下一次请求服务端 hash.js 和 hash.json的hash
客户端获取到hash,成功后客户端构造hot-update.js script链接,然后插入主文档
hash.js 插入成功后,重新 render 组件, 继而实现 UI 无刷新更新。
2
webpack在watch模式下,webpack监听文件变化,根据配置重新编译打包,并将打包后的代码以js对象保存在内存中(不生成文件的原因就在于访问内存中的代码比访问文件系统中的文件更快,而且也减少了代码写入文件的开销)
websocket建立长连接,将 webpack 编译和打包的各个阶段状态告知浏览器,最关键的步骤还是 webpack-dev-server 调用 webpack api 监听 compile的 done 事件,当compile 完成后,webpack-dev-server通过 _sendStatus 方法将编译打包后的新模块 hash 值发送到浏览器端。
webpack-dev-server 修改了webpack 配置中的 entry 属性,在bundle.js里面添加了 webpack-dev-client 的代码,这样在最后的 bundle.js 文件中就会有接收 websocket 消息的代码了。
webpack-dev-server/client 当接收到 type 为 hash 消息后会将 hash 值暂存起来,当接收到 type 为 ok 的消息后对应用执行 reload 操作
webpack 接收到最新 hash 值验证并请求模块代码(成功后客户端构造hot-update.js script链接,然后插入主文档)
HotModuleReplacement.runtime 对模块进行热更新(
1、找到缓存中的依赖,
2、删除缓存中过期的依赖,
3、将新模块添加到modules中,给下次调用webpack_require()用)
计算机基础
http2.0和http区别,为什么2.0发展受阻
区别
HTTP/1.0
增加了POST和HEAD方法
可以传输任何格式的内容
请求和响应都加上了header
不能保持长连接
支持缓存
HTTP/1.1
支持长连接(Connection: keep-alive)和流水线
协商缓存支持Etag
支持断点续传(只请求一部分,header中的range字段)状态码是206
增加HOST头,一台物理服务器(一个ip)可以对应多个虚拟主机(域名)
HTTP/2.0
多路复用
HTTP1.0,因为不支持长连接,每发送一次请求就要建立一次TCP连接。
HTTP/1.1,开始支持长连接,但是 HTTP/1.1 协议中 「浏览器客户端在同一时间,针对同一域名下的请求有一定数量限制。超过限制数目的请求会被阻塞」
HTTP/2.0,支持多路复用,即并行复用TCP连接,多个请求可以同时发送,对同一个域名的多个请求只用建立1个TCP连接了
二进制分帧
二进制分帧层中, HTTP/2 会将所有传输的信息分割为更小的消息和帧(frame),并对它们采用二进制格式的编码
其中 HTTP1.x 的首部信息会被封装到 HEADER frame,而相应的 Request Body 则封装到 DATA frame 里面
header压缩
服务端推送 server push
服务端能够更快的把资源推送给客户端。例如服务端可以主动把 JS 和 CSS 文件推送给客户端,而不需要客户端解析 HTML 再发送这些请求。当客户端需要的时候,它已经在客户端了。
HTTP2.0的副作用
HTTP2.0不会是万金油,HTTP2.0最大的亮点在于多路复用,而多路复用的好处只有在http请求量大的场景下才明显,所以有人会觉得只适用于浏览器浏览大型站点的时候。这么说其实没错,但http2.0的好处不仅仅是multiplexing,请求压缩,优先级控制,server push等等都是亮点。对于内容型移动端app来说,比如淘宝app,http请求量大,多路复用还是能产生明显的体验提升。
浏览器
浏览器对tcp请求有限制吗?什么是websocket
网页中的图片资源为什么分放在不同的域名下?
HTTP/1.1,开始支持长连接,但是 HTTP/1.1 协议中 「浏览器客户端在同一时间,针对同一域名下的请求有一定数量限制。超过限制数目的请求会被阻塞」
浏览器与服务器建立一个TCP连接后,是否会在完成一个http请求后断开?什么条件下会断开?
HTTP/1.1将Connection写入了标准,默认值为keep-alive。除非强制设置为Connection: close,才会在请求后断开TCP连接。
默认情况下建立的TCP连接不会断开,只有在请求头中设置Connection: close才会在请求后关闭TCP连接
一个TCP连接可以同时发送几个HTTP请求?
HTTP/1.1中,单个TCP连接,在同一时间只能处理一个http请求,虽然存在Pipelining技术支持多个请求同时发送,但由于实践中存在很多问题无法解决,所以浏览器默认是关闭,所以可以认为是不支持同时多个请求。
HTTP2提供了多路传输功能,多个http请求,可以同时在同一个TCP连接中进行传输
浏览器http请求的并发性是如何体现的?并发请求的数量有没有限制?
页面资源请求时,浏览器会同时和服务器建立多个TCP连接,在同一个TCP连接上顺序处理多个HTTP请求。所以浏览器的并发性就体现在可以建立多个TCP连接,来支持多个http同时请求。
Chrome浏览器最多允许对同一个域名Host建立6个TCP连接,不同的浏览器有所区别。
浏览器xss(跨站脚本攻击)和CSRF(跨站请求伪造)是什么
xss(跨站脚本攻击)
嵌入js脚本
如何攻击
存储型 XSS 攻击
存储型 XSS 攻击是指黑客利用站点漏洞将一段恶意 JavaScript 代码提交到网站的数据库中 存储 ,当用户访问网站的时候,网站将恶意脚本同正常页面一起返回,浏览器解析执行了网站中的恶意脚本,将用户的 Cookie 信息等数据上传到恶意服务器
存储型 XSS 攻击经常出现在个人信息或发表文章等地方,插入代码,如果没有过滤或过滤不严,那么这些代码将储存到服务器中,用户访问该页面的时候触发代码执行。这种 XSS 比较危险,容易造成蠕虫,盗窃 cookie 等
反射型 XSS 攻击
反射型 XSS 一般是黑客通过特定的手段(例如电子邮件等),诱导用户去访问一个包含恶意脚本的 URL,当用户访问这个带有恶意脚本的 URL 时,网站又把恶意 JavaScript 脚本返回给用户执行
反射型 XSS 通常出现在网站的搜索栏、用户登录口等地方,常用来窃取客户端 Cookies 或进行钓鱼欺骗
简单解释一下大致意思,当用户在网站的地址栏输入URL,从服务器端获取到数据时,网站页面会根据返回的信息而呈现不同的返回页面,如果这个时候恶意用户在页面中输入的数据不经过验证且不经过超文本标记语言的编码就录入到脚本中,就会产生漏洞(就是直接输入脚本,发送请求都时候,没有服务端没做验证直接返回页面)
基于 DOM 的 XSS 攻击
基于 DOM 的 XSS 攻击是指通过恶意脚本修改页面的 DOM 结构,是纯粹发生在客户端的攻击。
DOM 型 XSS 攻击中,取出和执行恶意代码由浏览器端完成,属于前端 JavaScript 自身的安全漏洞。
如何阻止
1. 过滤特殊字符,或对特定字符进行编译转码
2. 对重要的 cookie 设置 httpOnly
3. URLEncode 操作
将不可信的值输出 URL参数之前,进行 URLEncode操作。对于从 URL 参数中获取值一定要进行格式检测(比如你需要的时URL,就判读是否满足URL格式)
Web 安全头支持
浏览器自带的防御能力,一般是通过开启 Web 安全头生效的
CSP :W3C 的 Content Security Policy,简称 CSP,主要是用来定义页面可以加载哪些资源,减少 XSS 的发生。要配置 CSP , 需要对 CSP 的 policy 策略有了解,具体细节可以参考 CSP 是什么。
X-Download-Options: noopen :默认开启,禁用 IE 下下载框 Open 按钮,防止 IE 下下载文件默认被打开 XSS。
X-Content-Type-Options: nosniff :禁用 IE8 自动嗅探 mime 功能例如 text/plain 却当成 text/html 渲染,特别当本站点 server 的内容未必可信的时候。
X-XSS-Protection :IE 提供的一些 XSS 检测与防范,默认开启
后果
Cookie 信息
document.cookie 获取 Cookie 信息,然后通过 XMLHttpRequest 或 Fetch 加上 CORS 功能将数据发送给恶意服务器
监听用户行为
通过 addEventListener 监听用户行为,获取用户输入的银行卡支付密码等信息
更改 DOM 结构
伪造登录、输入支付秘密等窗口,获取用户私密信息
在页面内生成浮窗广告
劫持流量实现恶意跳转
CSRF(跨站请求伪造)
盗用身份,以你的名义发送恶意请求
攻击流程如下:
受害者登录 http://a.com ,并保留了登录凭证(Cookie)
攻击者引诱受害者访问了 http://b.com
http://b.com 发送了一个请求:http://a.com/act=xx 。浏览器会默认携带 http://a.com 的Cookie。
http://a.com 接收到请求后,对请求进行验证,并确认是受害者的凭证,误以为是受害者自己发送的请求。
http://a.com 以受害者的名义执行了act=xx。
攻击完成,攻击者在受害者不知情的情况下冒充受害者,让 http://a.com 执行了自己定义的操作。
产生条件
目标站点一定要有 CSRF 漏洞
用户要登录过目标站点,并且在浏览器上保持有该站点的登录状态
需要用户打开一个第三方站点,可以是攻击者的站点,也可以是一些论坛
几种常见的攻击方式
自动发起 GET 请求的 CSRF
<img src="http://a.com/pay?amount=10000&for=hacker" >
攻击者将支付的接口请求隐藏在 img 标签内,在加载这个标签时,浏览器会自动发起 img 的资源请求,a.com 就会收到包含受害者登录信息的一次跨域请求
自动发起 POST 请求的 CSRF
访问该页面后,表单会自动提交,相当于模拟用户完成了一次 POST 操作。
引诱用户点击链接的 CSRF
链接类型的CSRF并不常见,比起其他两种用户打开页面就中招的情况,这种需要用户点击链接才会触发。这种类型通常是在论坛中发布的图片中嵌入恶意链接,或者以广告的形式诱导用户中招
如何冒充身份
登陆原理:由于http无状态,所以维持登陆一般方式就是利用cookies,发送请求判断这个cookies来看登陆态,一般分两种:1/登陆后将用户信息加密得到token存到cookies里,验证通过解密检查是否存在某个字段,2/使用session,服务器存一份key-value的session信息表,将登陆后用户赋予一个sessionid并存到cookies。
冒充原理:利用之前登陆的cookies,冒充用户访问一个域名下的其他页面,让浏览器携带这个cookies
防范
token验证
请求中添加token字段
后端给cookies添加一个csrftoken ,请求的时候取出csrftoken字段放到body中,后端对比token是否个cookies的token一致(利用同源能访问该源下的cookie)
验证referer
验证是否同域发送的请求(referer可以被修改,不可靠)
请求头添加token自定义属性
验证码
深度遍历和广度遍历,怎么进行非递归广度遍历
什么是纯函数
相同输入总是会返回相同的输出。
不产生副作用。
不依赖于外部状态。
学习和习惯纯函数可以使你更轻松地测试和调试代码
运算符优先级,&&和++
"++,--">"&&">"||"
jq
jquery是把源码封装在一个匿名函数的自执行环境中, 有助于防止变量全局污染
然后再通过传入window对象作为window的局部变量使用, 可以很快的访问window对象
传入undefined参数可以缩短查找undefined的时间
jquery将一些原型属性和方法封装在jquery.prototype中为了缩短使用名称又赋值给了jquery.fn
c++、Java、js区别
C++,java是一门面向对象的编程语言
而且JavaScript 是动态类型语言,而 Java 是静态类型语言
C++、Java是后端语言,是面向对象编程语言,javascript是前端语言,一种弱类型语言。虽然javascript和java是名称很相似,但它们也没有关系
图片懒加载原理
滚动到可视区域再加载
什么是渐进加载
渐进式渲染就是图片的内容从模糊到清晰的过程
预加载(预见性的加载一些不可见区域的资源,提高用户在快速滚动浏览器时候的体验)。
骨架屏
vue-lazyload
页面滚动需要请求加载数据太频繁如何提高性能
getBoundingClientRect:该方案需要监听scroll事件,注意节流处理
Intersection Observer: 提供了一种异步观察目标元素与其祖先元素或顶级文档视窗(viewport)交叉状态的方法。Intersection Observer API 允许你配置一个回调函数,每当目标(target)元素与设备视窗或者其他指定元素发生交集的时候执行。设备视窗或者其他元素我们称它为根元素或根(root)。PS:该方案较前者的优点就是不需要监听,其实兼容性在chrome中还不错
异步加载JS方式
app与H5 如何通讯交互的
// 兼容IOS和安卓
callMobile(parameters,messageHandlerName) {
//handlerInterface由iOS addScriptMessageHandler与andorid addJavascriptInterface 代码注入而来。
if (/(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent)) {
// alert('ios')
window.webkit.messageHandlers[messageHandlerName].postMessage(JSON.stringify(parameters))
} else {
// alert('安卓')
//安卓传输不了js json对象,只能传输string
window.webkit[messageHandlerName](JSON.stringify(parameters))
}
}
由app将原生方法注入到window上供js调用
messageHandlerName 约定的通信方法parameters 需要传入的参数
callMobile(parameters,messageHandlerName) {
//handlerInterface由iOS addScriptMessageHandler与andorid addJavascriptInterface 代码注入而来。
if (/(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent)) {
// alert('ios')
window.webkit.messageHandlers[messageHandlerName].postMessage(JSON.stringify(parameters))
} else {
// alert('安卓')
//安卓传输不了js json对象,只能传输string
window.webkit[messageHandlerName](JSON.stringify(parameters))
}
}
由app将原生方法注入到window上供js调用
messageHandlerName 约定的通信方法parameters 需要传入的参数
手写代码
柯里化
JSON.parse()
使用eval和new Function可实现序列化json,但是要注意xss
使用eval和new Function可实现序列化json,但是要注意xss
eval("("+json+")")
(new Function('return'+json))()
算法
排序
冒泡排序O(n^2)
对比相邻分的两个,比较大的右移,做两个嵌套循环,每一次循环将最大数移到最右边
插入排序O(n^2)
选一个元素向前移动,直到移动到合适位置
归并排序O(NlogN)、稳定
将数组均分两份,直到分到最小单位,排序,最后合并俩个有序数组
快速排序O(nlongn)-n^2、不稳定
以左边第一位作为标记,分别将小于标记的和大于标记的安置在左侧和右侧,递归两边
查找
二分查找O(log2n)
基础部分
html
哪些标签可以跨域加载资源
script、img、link(href)、iframe
attribute和property有什么区别
attribute是HTML标签属性,值只能是字符串
property是DOM对象的属性,是js对象
img的titl和alt有什么区别
title是鼠标移动到图片上的展示,alt是图片加载不出来时显示
标签语义化
用正确的标签做正确的事
例如header,nav,footer,article
作用:让人和机器更易读懂,也有利于搜索引擎
html5新特性
语义标签 比如 article、footer、header、nav、section
增强型表单 calendar、date、time、email、url、search
视频和音频
audio
video
canvas
原理
canvas本身并不具备绘画能力,它本身只是一个画布,是一个容器。绘图能力是基于html5的getContext('2d')返回的CanvasRenderingContext2D对象来完成的。
const canvas = document.getElementById('payAbilityLoginTree');//获取canvas dom对象
const ctx = canvas.getContext('2d'); //获取绘图对象
<canvas id="payAbilityLoginTree" width="1000" height="800"></canvas>
canvas是一个二维网络,以画布左上角(0,0)为坐标原点,x轴向右延伸,y轴向下延伸。所以canvas画布中的坐标全为正数,没有负数。
CanvasRenderingContext2D对象提供了很多绘图方法,我们可以通过这些方法来绘制任何你需要的图形,我就不一一介绍了,大家可以参考HTML5 参考手册。但有几个常用的方法我着重强调一下。
const ctx = canvas.getContext('2d'); //获取绘图对象
<canvas id="payAbilityLoginTree" width="1000" height="800"></canvas>
canvas是一个二维网络,以画布左上角(0,0)为坐标原点,x轴向右延伸,y轴向下延伸。所以canvas画布中的坐标全为正数,没有负数。
CanvasRenderingContext2D对象提供了很多绘图方法,我们可以通过这些方法来绘制任何你需要的图形,我就不一一介绍了,大家可以参考HTML5 参考手册。但有几个常用的方法我着重强调一下。
svg
地理位置
拖放api
web worker
为js创建多线程环境。web worker是运行在后台的javaScript 独立于其他脚本,不会影响页面性能,可以继续做其他事情:如点击
web Storage
localStorage
清除缓存就没有
sessionStorage
窗口关闭就没有
webSocket
是h5提供的一种在单个tcp连接上进行双工通讯的协议
浏览器和服务器只需做一个握手动作,就建立一条快速通道,两者之间可以数据相互传送
iframe特点
1、阻塞onload事件
iframe和主页面共享连接池,影响页面并行加载
2、不利于seo
3、增加服务器http请求
4、无法兼容移动设备
xml和html区别
xml可扩展标记语言
html超文本标记语言
xml写法严谨,要求闭合/对大小写敏感、正确嵌套,html没这么严格要求,
HTML是静态的,用于显示数据。 XML是动态的,用于传输数据
svg和canvas区别
svg
使用xml绘制的2d图形语言
可以为某个元素附加javascript事件处理
每个被绘制的图形均被视为对象,如果对象属性变化会重新绘制
复杂度高会减慢渲染速度(原理都是操作dom)
不适合游戏
适合带有大型渲染区域的应用程序
canvas
通过js绘制出来的2D图形
canvas是逐个像素进行渲染的
依赖分辨率
不支持js事件处理
能够以.jpg和png保存的图片
适合图像密集型游戏
html5 的drag api
dragstart
darg
dragenter
dragover
dragleave
drop
gragend
javascript
基础问题
基础语法
字符串
toUpperCase()
toLowerCase()
indexOf()
substring(开始位置,结束位置)
s.substring(0,5) 从索引0-5(不包括5)
s.substring(7) 从索引7到结束
参数是负数,就是重0开始
slice(开始位置,结束位置)
和substring差不多,但是接收负数的时候不一样,
参数是负数就从后面数起
substr(开始位置,长度)
split
字符串切割成数组
includles
数组
for...in
遍历对象的key
如果是循环数组或者字符串,返回是索引
for...of
遍历数组,返回是数组里的value
map
js对象中,键必须是字符串,但实际其他可能作为键,所以es6引入了map(键可以是任意类型)
代码
使用map,对象会占用内存,可能不会被垃圾回收。Map对一个对象是强引用
WeakMap
只接受对象作为键名(null除外),不接受其他类型的值作为键名。
Weakmap它不会阻止关键对象的垃圾回收
Map 和Set可以被遍历, WeakMap 和WeakSet不能被遍历,因为WeakMap和WeakSet都是对象的弱引用,会可能被垃圾回收,所以不能遍历
set
和map类似,但没value,但是set不重复,多用于去重数组
代码
用add方法添加(Map用set(key,'关联的值')),参数是放一个值(map是两个(key,value)),是唯一的
WeakSet
的成员只能是对象,而不能是其他类型的值。WeakSet 不可遍历。
forEach
没有返回值
不能return停止
indexOf()
sort(fn)
排序,返回true就交换
reverse()
反转数组
slice(start,end)
截取slice(0,3) 从索引0开始,到索引3结束(不包括3)
slice(3),从索引3开始到结束
不改变原数组,返回截取的数组
splice(index,howmany,item1,...itemX)
删除、添加和替换
三个参数(开始位置,删除个数,添加的值)
改变原数组,返回被删除的数组
slice(start,end)和splice(start,num,item1,...itemX)区别
splice改变原数组,返回被删除的item
slice返回新数组,但不改变原数组
slice返回新数组,但不改变原数组
slice可以用于切割字符串,substring(start,end),subStr(start,length)
concat
数组拼接,不改变原来的数组并返回一个新的数组
join
以某字符连接数组
pop/push
栈常用
删除/添加数组最后一位
unshift/shift
队列常用
unshift 队头添加
shift 对头删除
js判断数组
typeof无法判断数组,数组在typeof中归到object中(null,object ,array都归到Object中)
instanceof
要判断是不是数组,如果这个Object是原型链上能找到Array构造函数的话,那么这个Object应该是一个数组,如果只能找到Object构造函数的话,那么它就不是一个数组
const a = [];
const b = {};
console.log(a instanceof Array);//true
console.log(a instanceof Object);//true,在数组的原型链上也能找到Object构造函数
console.log(b instanceof Array);//false
constructor
a.constructor 实例的constructor指向生成这个数组的方法,但是要保证这个属性不被改写
Object.prototype.toString.call(arr) === '[object Array]'
每一个继承自Object的对象都拥有toSring方法,返回"[object type]"
因为数组的.toString() 方法只返回字符串,所以要借用Object的toString() + call 改变this指向
const a = ['Hello','Howard'];
const b = {0:'Hello',1:'Howard'};
const c = 'Hello Howard';
Object.prototype.toString.call(a);//"[object Array]"
Object.prototype.toString.call(b);//"[object Object]"
Object.prototype.toString.call(c);//"[object String]"
const b = {0:'Hello',1:'Howard'};
const c = 'Hello Howard';
Object.prototype.toString.call(a);//"[object Array]"
Object.prototype.toString.call(b);//"[object Object]"
Object.prototype.toString.call(c);//"[object String]"
es5 arr.isArray()
js改变原数组的方法:pop,push,shift,unshift,splice,reverse,sort
js不改变原数组的方法:concat,join,slice
函数
arguments
代表传入的参数
函数作用域
函数作用域(函数调用栈,变量环境),函数内部作用域不影响外部作用域,但是内部可以向上访问变量
在还没有ES6的let、const之前,只有函数作用域和全局作用域
变量提升
定义在函数内部的变量,会先被提前声明到顶部,执行函数前会先扫描函数内部,最开始这样设计的原因:函数提升就是为了解决相互递归的问题
函数的优先权是最高的,它永远被提升至作用域最顶部,然后才是函数表达式和变量按顺序提升,这一点要牢记
JS的变量提升和函数声明提升规则:(相同覆盖,不同函数优先)
call/apply/bind
高阶函数
map
处理数组返回新数组
reduce
把结果继续和下一个元素累积计算
累加/累积
filter
用于过滤数组
sort
排序
every
判断所有元素是否符合,只有又一个不符合就false
find
找出第一个符合条件的元素,没找到就返回undefind
findIndex
和find()类似,不过是返回下标
forEach(fn)
和map类似,不过不会返回数组,fn(值,下标,数组)
闭包
原理:利用js的函数作用域,在函数内建立函数来进行,并且函数的作用域是向上访问变量
定义:可以访问函数外部作用域变量的函数
被内部函数访问的外部函数的变量可以保存在外部函数作用域中而不被回收(遇到闭包,可以重点关注这个被内部函数引用的变量)
例子
作用
封装变量:管理私有变量和私有方法,将对变量的变化封装在安全的环境中
避免命名全局污染问题,命名可以和全局一致
缺点:由于没有垃圾回收,可能会内存泄露
genertor生成器
可以分步执行js代码
使用yield可以类似中断一样,通过.next()来调用下一步运行
分类
IIFF(立即执行函数)
箭头函数
函数表达式&函数声明
对象
浅拷贝和深拷贝
深拷贝
JSON.parse(JSON.stringify(test))
如果对象中包含 function 或 RegExp 这些就不能用这种方法了。
Object.assign({},obj)
当对象中只有一级属性是深拷贝,但是对象中有对象的时候,是浅拷贝
递归克隆
(就是只能实现特定的object的深度复制(比如数组和函数),不能实现包装对象Number,String, Boolean,以及Date对象,RegExp对象的复制)
栈的实现
引入一个数组 uniqueList 用来存储已经拷贝的数组,每次循环遍历时,先判断对象是否在 uniqueList 中了,如果在的话就不执行拷贝逻辑了。
浅拷贝
直接赋值
obj = {...obj1}
obj = Object.assign({},obj1)
api
删除对象
delete user.name;
检查属性是否存在
for (key in object) {...}
object.hasOwnProperty(key)
常用方法
Object.keys(obj) —— 返回一个包含该对象所有的键的数组。
Object.values(obj) —— 返回一个包含该对象所有的值的数组。
Object.entries(obj) —— 返回一个包含该对象所有 [key, value] 键值对的数组。
Object.is(NaN,NaN)=== true // 相当于判断 a === b成不成立
Object.defineProperty(),它可以劫持属性的 setter 与 getter:
标准对象
Date
var now = new Date();
now; // Wed Jun 24 2015 19:49:22 GMT+0800 (CST)
now.getFullYear(); // 2015, 年份
now.getMonth(); // 5, 月份,注意月份范围是0~11,5表示六月
now.getDate(); // 24, 表示24号
now.getDay(); // 3, 表示星期三
now.getHours(); // 19, 24小时制
now.getMinutes(); // 49, 分钟
now.getSeconds(); // 22, 秒
now.getMilliseconds(); // 875, 毫秒数
now.getTime(); // 1435146562875, 以number形式表示的时间戳
RegExp正则
api
创建new RegExp()
验证字符串 reg.test(str)
分组reg.exec(str)
切分str.splirt(reg)
replace
replace 函数用于在字符串中用一些字符替换另一些字符
match
返回的结果与 exec 函数的结果比较类似,match 是字符串的方法
特殊字符,用于匹配某类字符
^ 匹配字符串的开头
$ 匹配字符串的结尾
[] 匹配给定字符集中的一个字符
a|b 匹配a或b,
. 匹配除了换行符\n之外的任何字符;
\s 匹配任意空白字符,等价于[\t\n\r\f\v]
\S 匹配任意非空白字符,等价于[^\t\n\r\f\v]
\w 匹配字母数字下划线,等价于[0-9a-zA-Z_]
\W 匹配非字母数字及下划线,等价于[^0-9a-zA-Z_]
\d 匹配任意一个数字,等价于[0-9]
\D 匹配任意一个非数字,等价于[^0-9]
\b 匹配边界
$ 匹配字符串的结尾
[] 匹配给定字符集中的一个字符
a|b 匹配a或b,
. 匹配除了换行符\n之外的任何字符;
\s 匹配任意空白字符,等价于[\t\n\r\f\v]
\S 匹配任意非空白字符,等价于[^\t\n\r\f\v]
\w 匹配字母数字下划线,等价于[0-9a-zA-Z_]
\W 匹配非字母数字及下划线,等价于[^0-9a-zA-Z_]
\d 匹配任意一个数字,等价于[0-9]
\D 匹配任意一个非数字,等价于[^0-9]
\b 匹配边界
量词
* 匹配前一个表达式0次或多次,等价于{0,}
+ 匹配前一个表达式1次或多次,等价于{1,}
? 匹配前一个表达式0次或1次,等价于{0,1}
{m,n} 匹配前一个表达式m至n次
{m} 精确匹配前一个表达式m次
{m,} 匹配前一个表达式至少m次
字符集
字符集,字符集规定了可选的字符,通常表示为 []。
0到9之间的任意字符可以表示为 [0-9],在字符集里面,- 在两个字符之间,和两边的字符连在一起表示为两个字符之间的全部字符。
在字符集里面,"." 没有特殊含义。
值得注意的是在没有使用量词的情况下,在正则表达式中,一个字符集占用一个字符的位置,只是该位置的字符可能是字符集中的任意一个字符。如果要表示两个字符,在不适用量词的前提下,要使用两个字符集。
0到9之间的任意字符可以表示为 [0-9],在字符集里面,- 在两个字符之间,和两边的字符连在一起表示为两个字符之间的全部字符。
在字符集里面,"." 没有特殊含义。
值得注意的是在没有使用量词的情况下,在正则表达式中,一个字符集占用一个字符的位置,只是该位置的字符可能是字符集中的任意一个字符。如果要表示两个字符,在不适用量词的前提下,要使用两个字符集。
组()
/^abc{3}$/ //abccc
/^(abc){3}$/ abcabcabc //abc重复3次
/^(abc){3}$/ abcabcabc //abc重复3次
Json
json字符串转json对象
var obj = eval('('+str+')');
var obj = JSON.parse(str);
var obj = (new Function('renturn'+json))()
json对象转为json字符串
var str = JSON.stringify(obj);
其他
怎样判断类型
typeof()
instanceof instanceof 运算符用来检测 constructor.prototype 是否存在于参数 object 的原型链上。
Object.prototype.toString.call()等
js有几种类型,分别怎样存储
javascript一共有8种数据类型,原始数据类型(7种)和引用数据类型(1种 object)
原始数据类型, undefined null number string boolean、
symbol (ES6,用于创建一个独一无二的标识)、
bigint (ES10解决超大整数值16位的精度问题)
以上都是通过栈存储
symbol (ES6,用于创建一个独一无二的标识)、
bigint (ES10解决超大整数值16位的精度问题)
以上都是通过栈存储
引用数据类型object,包括(对象, 数组, 函数) 通过堆存储, 因为他们大小不固定如果存在栈中会影响性能
==,===,Object.is 区别
数字
0.1+0.2 !== 0.3
原因:js对于超大整数超过16位的会出现精度丢失问题,
计算机使用二进制表示0.1和0.2都是一个无限数,所以相加得出的数是一个接近于0.3的数,所以就有0.1+0.2 !== 0.3
计算机使用二进制表示0.1和0.2都是一个无限数,所以相加得出的数是一个接近于0.3的数,所以就有0.1+0.2 !== 0.3
解决:取整
移位符
x >> 1
x除以2后取整
取整:
x >>> 0 本质上就是保证x有意义(为数字类型),且为正整数
~~3.14 = 3
3.14 >> 0 = 3
面向对象编程
es6
es6新特性
let const变量
模板字符串
解构
for of
展开运算符
箭头函数(表达式)
class
super
expends
在子类构造函数中,使用this前,必须先调用超级类super
WeakMap 和 WeakSet
Proxy
Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。
const p = new Proxy(target, handler)
Symbol 代表用给定名称作为唯一标识
创建let id = Symbol('aa')
特性
唯一
Symbol确保唯一,即使采用相同的名称。也会产生不同的值
隐藏
不能for in 来遍历得到,大多数库内置方法和语法结构约定Symbol是隐藏的
内置Object.getOwnPropertSymbol(obj) 获取所有Symbol
或者Reflect.ownKeys(obj), 返回对象所有key,包括Symbol
箭头函数
不能new ,因为是匿名函数,不能作为构造函数
没有arguments,使用解构... 解决
不绑定this,捕获其所在的上下文的this值,作为自己的this值
有__propo__ ,没有prototype (箭头函数的构造函数是Funtion)
fn.__proto__ === Function.prototype
fn.__proto__ === Function.prototype
this指向其上下文,且不被改变,call,bind apply都不行
不能使用函数作为构造函数时
不能用generator(因为标准规范定义了生成器是function*。箭头函数就无法匹配function*关键字)
Promise
async await
是promise和generator语法糖 ,async和await本身返回的也是一个Promise,它只是把await后面的代码放到了await返回的Promise的.then后面,以此来实现的。
await async2();
// 可以理解为这样
// new Promise((reslove) => {
// async2()
// }).then(() => {
// // await 等待后面所有语句
// console.log('async1 end')
// })
console.log("async1 end");
// 可以理解为这样
// new Promise((reslove) => {
// async2()
// }).then(() => {
// // await 等待后面所有语句
// console.log('async1 end')
// })
console.log("async1 end");
使用同步方式运行异步代码,解决地狱回调
Promise.all和Promise.race
generator
以迭代器的方式调用异步代码,使用function* 和yield控制函数的返回,通过返回一个对象,使用. next()来触发函数一步一步的调用
手写
手写Pormise
https://mp.weixin.qq.com/s/GZ_36gxyZLC2c-WS68ualA
如果已经有三个promise,A、B和C,想串行执行,该怎么写
```JavaScript
// promise
A.then(B).then(C).catch(...)
// async/await
(async () => {
await a();
await b();
await c();
})()
```
规范
js严格模式
use strict
避免不必要性能损耗并且安全
规范
https://www.jianshu.com/p/14d285639aaf
所有变量需要定义var
全局this指向undefined
禁用whit(),whit性能不好
参数和属性不可以重名
禁止用八进制表示法
函数有申明的必须写在顶层
eval单独作用域
无法删除变量
等等
模块化
说一下Commonjs/AMD/CMD/ES6
CommonJs
NodeJS、webpack都是基于该规范来实现的
ES6 Module
在ES6前,前端也实现了一套相同的模块规范(如AMD),自ES6之后,引入了一套新的ES6 Module规范,有望成为浏览器和服务端通用的模块管理方案,但目前浏览器对ES6 模块兼容还不太好,所以平时对项目都需要经过webpack转成commonJS规范
es6和CommonJs区别
CommonJS模块输出是一个值的拷贝,ES6模块输出的是值的引用
CommonJS模块是运行时加载,ES6是编译时就能确定模块依赖关系
CommonJS是单个值导出,ES6Module可以导出多个
CommonJS是动态语法(运行时加载)可以写在任何地方,ES6Module静态(编译时加载)import 和 export只能写在顶层
CommonJS的this是当前模块,ES6 Module的this是undefined
CommonJS是同步导入,因为用于服务端,文件都在本地,同步导入即使卡住主线程影响也不大。
而ES6 Module是异步导入,因为用于浏览器,需要下载文件,如果也采用同步导入会对渲染有很大影响
而ES6 Module是异步导入,因为用于浏览器,需要下载文件,如果也采用同步导入会对渲染有很大影响
AMD
RequireJs-异步加载js文件,依赖前置,提前执行
通过define第一个参数引入一来包,return 输出结果,
所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。
所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。
requireJs它本身就是先加载后执行
RequireJS的做法是并行加载所有依赖的模块, 并完成解析后, 再开始执行其他代码, 因此执行结果只会"停顿"1次, 完成整个过程是会比SeaJS要快.
原理
requireJS 的核心原理是什么?(如何动态加载的?如何避免多次加载的?如何缓存的?
1,概念
requireJS是基于AMD模块加载规范,使用回调函数来解决模块加载的问题。
2,原理
requireJS是使用创建script元素,通过指定script元素的src属性来实现加载模块的。
3,特点
1. 实现js文件的异步加载,避免网页失去响应
2,管理模块之间的依赖,便于代码的编写和维护
4,项目优化
r.js 是基于requirejs模块化的基础上进一步的压缩和打包成一个js,请求数大大减少,便于优化
————————————————
原文链接:https://blog.csdn.net/mzrxLXR/article/details/81703099
CMD
seajs 通过require引入依赖,CMD依赖就近,延迟执行
SeaJS一样是并行加载所有依赖的模块, 但不会立即执行模块, 等到真正需要(require)的时候才开始解析
按需加载
当用户触发了动作时才加载对应的功能。触发的动作,是要看具体的业务场景而言,包括但不限于以下几个情况:鼠标点击、输入文字、拉动滚动条,鼠标移动、窗口大小更改等。加载的文件,可以是JS、图片、CSS、HTML等。
动画
setTimeout
低端机出现动画不流畅
执行时间不确定(seTimeout等主线程执行完毕在执行异步队列
分辨率刷新时间和设置时间不一样同步(使用requestanimationframe)
requestanimationframe
进阶
通讯
原生ajax封装成Promise
异步加载js的方式
async script插入到dom中通过加载后执行callback函数来进行加载
浏览器之间的通讯
cookie
同域可传送
大小4k,太多影响速度和流量
兼容性好,每次http请求都携带
session Storage
不支持跨标签页面共享数据
关闭标签就数据消失
localStorage
大小5m
web worker
为js创建多线程环境
注意
同源限制
DOM限制
只能读取navigator和location对象
通讯限制
和主线程不在一个上下文环境,不能直接通讯,必须通过消息完成postMessage
脚本限制
不能用alert()和confirm()
文件限制
无法打开本机文件系统
ajax、fetch和axios
ajax
手写ajax
原理
在用户和服务器之间加了一个中间层,通过XMLHttpRequest()对象来向服务器发送异步请求
通过监听readyStatechange事件,通过实例的readyState来判断ajax状态
ajax解决浏览器缓存问题
http缓存
强缓存200 from cache
expires(http/1.0)
Expires: Wed, 22 Oct 2018 08:41:00 GMT
表示资源过期时间
Catch-Control(http/1.1)
优先级较expires高
Cache-control: max-age=30
资源多少s后过期
协商缓存304
Last-Modified响应头和If-Modified-Since请求头(htpp/1.0),记录文件最后修改时间,只能精确到1s以内
ETagh(响应头)和If-None-Match(请求头)(http/1.1)(优先级高,记录文件标识序列)
readyState的4种状态
0:未初始化
1:正在加载
2:已加载
3:交互中
4:完成
优点
提高了性能和速度:减少了客户端和服务器之间的流量传输,同时减少了双方响应的时间,响应更快,因此提高了性能和速度
交互性好:使用ajax,可以开发更快,更具交互性的Web应用程序
异步调用:Ajax对Web服务器进行异步调用。这意味着客户端浏览器在开始渲染之前避免等待所有数据到达。
节省带宽:基于Ajax的应用程序使用较少的服务器带宽,因为无需重新加载完整的页面
底层使用XMLHttpRequest
拥有开源JavaScript库 : JQuery,Prototype,Scriptaculous等。
AJAX通过HTTP协议进行通信。
缺点
Ajax应用程序中的安全性较低(容易收到CSRF和XSS攻击),因为所有文件都是在客户端下载的
JQuery整个项目太大,单纯使用ajax却要引入整个JQuery非常的不合理(采取个性化打包的方案又不能享受CDN服务)
配置和调用方式非常混乱,而且基于事件的异步模型不友好。
fetch
优点
语法简洁,更加语义化
基于标准 Promise 实现,支持 async/await,支持链式调用
更加底层,提供的API丰富(request,response)
脱离了XHR,是ES规范里新的实现方式
缺点
fetch只对网络请求报错,对400,500都当做成功的请求,需要封装去处理
fetch默认不会带cookie,需要添加配置项
fetch不支持abort,不支持超时控制,使用setTimeout及Promise.reject和Promise.race结合setTimeout实现的超时控制并不能阻止请求过程继续在后台执行,造成了量的浪费
fetch没有办法原生监测请求的进度,而XHR可以
axios
优点
从node.js创建http请求
在浏览器中创建XMLHttpRequest
支持Promise API
提供了一些并发请求的接口
支持拦截请求和响应
转换请求和响应数据
取消请求
自动转换JSON数据
客户端支持防御CSRF(跨站请求攻击)/XSRF
让每一个请求带一个从cookies中拿到带key,同源策略假冒网站是拿不到cookies的,这样,后台就可以辨别这个请求是否假冒网站误导输入
http、https
七层网络协议(巫术忘传会飙鹰)
从应用层的发送http请求,
到传输层通过三次握手建立tcp/ip连接,
再到网络层的ip寻址,
再到数据链路层的封装成帧,
最后到物理层的利用物理介质传输。
从应用层的发送http请求,
到传输层通过三次握手建立tcp/ip连接,
再到网络层的ip寻址,
再到数据链路层的封装成帧,
最后到物理层的利用物理介质传输。
1.应用层(dns,http) DNS解析成IP并发送http请求
表示层:主要处理两个通信系统中交换信息的表示方式,包括数据格式交换,数据加密与解密,数据压缩与终端类型转换等
会话层:它具体管理不同用户和进程之间的对话,如控制登陆和注销过程
2.传输层(tcp,udp) 建立tcp连接(三次握手)
3.网络层(IP,ARP) IP寻址
4.数据链路层(PPP) 封装成帧
5.物理层(利用物理介质传输比特流) 物理传输(然后传输的时候通过双绞线,电磁波等各种介质)
TCP(传输层)
面向连接、传输可靠、用于传输大量数据(流模式)、速度慢,需要建立连接三次握手开销大(面向场景:http、https、邮件)
UDP(传输层)
面向非连接、传输不可靠、用于传输少量的数据、速度快、可能丢包(主要用于视频会议、聊天,缺少点数据无关紧要,可靠性要求比较低,传输速度快的应用。)
https
超文本传输安全协议,默认端口443。经由http通信+SSL/TLS来加密信息包,主要采用身份认证和数据加密等手段来保证传输安全
HTTPS在TCP三次握手阶段之后,还需要进行SSL 的handshake,协商加密使用的对称加密密钥
过程
浏览器向服务器发送加密通信的请求(请求包包括:随机数R1+支持的加密算法+SSL版本)
服务器返回R2随机数+确认使用的加密算法+确认的SSL版本+服务器证书(包括了用来加密数据是公钥)
浏览器收到证书,校验证书合法性,生成R3随机数,用证书的服务器公钥加密发给服务器
服务器收到数据用私钥解密,这时,服务器和浏览器都有了R1+R2+R3随机数生成的一对对称加密使用的秘钥
校验证书合法性
浏览器读取证书中的证书所有者、有效期等信息进行一一校验
浏览器开始查找操作系统中已内置的受信任的证书发布机构CA,与服务器发来的证书中的颁发者CA比对,用于校验证书是否为合法机构颁发
如果找不到,浏览器就会报错,说明服务器发来的证书是不可信任的。
如果找到,那么浏览器就会从操作系统中取出 颁发者CA 的公钥,然后对服务器发来的证书里面的签名进行解密
浏览器使用相同的hash算法计算出服务器发来的证书的hash值,将这个计算的hash值与证书中签名做对比 (防止信息被修改)
对比结果一致,则证明服务器发来的证书合法,没有被冒充, 此时浏览器就可以读取证书中的公钥,用于后续加密了
浏览器开始查找操作系统中已内置的受信任的证书发布机构CA,与服务器发来的证书中的颁发者CA比对,用于校验证书是否为合法机构颁发
如果找不到,浏览器就会报错,说明服务器发来的证书是不可信任的。
如果找到,那么浏览器就会从操作系统中取出 颁发者CA 的公钥,然后对服务器发来的证书里面的签名进行解密
浏览器使用相同的hash算法计算出服务器发来的证书的hash值,将这个计算的hash值与证书中签名做对比 (防止信息被修改)
对比结果一致,则证明服务器发来的证书合法,没有被冒充, 此时浏览器就可以读取证书中的公钥,用于后续加密了
http2.0和http1.1
区别
HTTP/1.0
增加了POST和HEAD方法
可以传输任何格式的内容
请求和响应都加上了header
不能保持长连接
支持缓存
HTTP/1.1
支持长连接(Connection: keep-alive)和流水线
协商缓存支持Etag
支持断点续传(只请求一部分,header中的range字段)状态码是206
增加HOST头,一台物理服务器(一个ip)可以对应多个虚拟主机(域名)
HTTP/2.0
多二 服头
多二 服头
多路复用
HTTP1.0,因为不支持长连接,每发送一次请求就要建立一次TCP连接。
HTTP/1.1,开始支持长连接,但是 HTTP/1.1 协议中 「浏览器客户端在同一时间,针对同一域名下的请求有一定数量限制。超过限制数目的请求会被阻塞」
HTTP/2.0,支持多路复用,即并行复用TCP连接,多个请求可以同时发送,对同一个域名的多个请求只用建立1个TCP连接了
http1.1keep-alive和htttp2,0多路复用区别:
http1:要按顺序返回响应,等待上一个响应后才发起下一个响应,为解决队头阻塞使用多个域名开启多个tcp,单个TCP连接同一时刻只能处理一个请求
http2:二进制分帧下,不需按照顺序发送和返回,不需等待上一次请求返回,同一个域名下建立一个tcp,处理多个同域名的http请求,同一时刻可发送多个请求和响应
HTTP/1.1,开始支持长连接,但是 HTTP/1.1 协议中 「浏览器客户端在同一时间,针对同一域名下的请求有一定数量限制。超过限制数目的请求会被阻塞」
HTTP/2.0,支持多路复用,即并行复用TCP连接,多个请求可以同时发送,对同一个域名的多个请求只用建立1个TCP连接了
http1.1keep-alive和htttp2,0多路复用区别:
http1:要按顺序返回响应,等待上一个响应后才发起下一个响应,为解决队头阻塞使用多个域名开启多个tcp,单个TCP连接同一时刻只能处理一个请求
http2:二进制分帧下,不需按照顺序发送和返回,不需等待上一次请求返回,同一个域名下建立一个tcp,处理多个同域名的http请求,同一时刻可发送多个请求和响应
HTTP1.1 keep-alive和http2.0多路复用区别:
HTTP/1.x 虽然引入了 keep-alive 长连接,但它每次请求必须等待上一次响应之后才能发起,所谓队头阻塞
HTTP/1.x 是基于文本的,只能整体去传;HTTP/2 是基于二进制流的,可以分解为独立的帧,交错发送
HTTP/1.x keep-alive 必须按照请求发送的顺序返回响应;HTTP/2 多路复用不按序响应
HTTP/1.x keep-alive 为了解决队头阻塞,将同一个页面的资源分散到不同域名下,开启了多个 TCP 连接;HTTP/2 同域名下所有通信都在单个连接上完成
HTTP/1.x keep-alive 单个 TCP 连接在同一时刻只能处理一个请求(两个请求的生命周期不能重叠);HTTP/2 单个 TCP 同一时刻可以发送多个请求和响应
二进制分帧
在应用层跟传送层之间增加了一个二进制分帧层,改进传输性能,实现低延迟和高吞吐量)
二进制分帧层中, HTTP/2 会将所有传输的信息分割为更小的消息和帧(frame),并对它们采用二进制格式的编码
其中 HTTP1.x 的首部信息会被封装到 HEADER frame,而相应的 Request Body 则封装到 DATA frame 里面
二进制分帧层中, HTTP/2 会将所有传输的信息分割为更小的消息和帧(frame),并对它们采用二进制格式的编码
其中 HTTP1.x 的首部信息会被封装到 HEADER frame,而相应的 Request Body 则封装到 DATA frame 里面
服务端推送
服务端可以对客户端的一个请求发出多个响应,可以主动通知客户端
服务端能够更快的把资源推送给客户端。例如服务端可以主动把 JS 和 CSS 文件推送给客户端,而不需要客户端解析 HTML 再发送这些请求。当客户端需要的时候,它已经在客户端了。
服务端能够更快的把资源推送给客户端。例如服务端可以主动把 JS 和 CSS 文件推送给客户端,而不需要客户端解析 HTML 再发送这些请求。当客户端需要的时候,它已经在客户端了。
header压缩
HTTP2.0的副作用
HTTP2.0不会是万金油,HTTP2.0最大的亮点在于多路复用,而多路复用的好处只有在http请求量大的场景下才明显,所以有人会觉得只适用于浏览器浏览大型站点的时候。这么说其实没错,但http2.0的好处不仅仅是multiplexing,请求压缩,优先级控制,server push等等都是亮点。对于内容型移动端app来说,比如淘宝app,http请求量大,多路复用还是能产生明显的体验提升。
如果http2.0全面应用,很多http1.1中的优化方案就无需用到了(譬如打包成精灵图,静态资源多域名拆分等)
浏览器xss(跨站脚本攻击)
CSRF(跨站请求伪造)
CSRF(跨站请求伪造)
xss(跨站脚本攻击)
嵌入js脚本
如何攻击:
存储型 XSS 攻击
存储型 XSS 攻击是指黑客利用站点漏洞将一段恶意 JavaScript 代码提交到网站的数据库中 存储 ,当用户访问网站的时候,网站将恶意脚本同正常页面一起返回,浏览器解析执行了网站中的恶意脚本,将用户的 Cookie 信息等数据上传到恶意服务器
存储型 XSS 攻击经常出现在个人信息或发表文章等地方,插入代码,如果没有过滤或过滤不严,那么这些代码将储存到服务器中,用户访问该页面的时候触发代码执行。这种 XSS 比较危险,容易造成蠕虫,盗窃 cookie 等
反射型 XSS 攻击
反射型 XSS 一般是黑客通过特定的手段(例如电子邮件等),诱导用户去访问一个包含恶意脚本的 URL,当用户访问这个带有恶意脚本的 URL 时,网站又把恶意 JavaScript 脚本返回给用户执行
反射型 XSS 通常出现在网站的搜索栏、用户登录口等地方,常用来窃取客户端 Cookies 或进行钓鱼欺骗
简单解释一下大致意思,当用户在网站的地址栏输入URL,从服务器端获取到数据时,网站页面会根据返回的信息而呈现不同的返回页面,如果这个时候恶意用户在页面中输入的数据不经过验证且不经过超文本标记语言的编码就录入到脚本中,就会产生漏洞(就是直接输入脚本,发送请求都时候,没有服务端没做验证直接返回页面)
基于 DOM 的 XSS 攻击
基于 DOM 的 XSS 攻击是指通过恶意脚本修改页面的 DOM 结构,是纯粹发生在客户端的攻击。
DOM 型 XSS 攻击中,取出和执行恶意代码由浏览器端完成,属于前端 JavaScript 自身的安全漏洞。
产生主要原因
产生xss的主要原因是程序对输入输出的控制不够严格,导致恶意脚本输入后,再输到前端被浏览器当有效代码解析,从而产生危害
如何阻止
1.对脚本的检验:过滤特殊字符,或对特定字符进行编译转码
2. 对cookies限制。对重要的 cookie 设置 httpOnly,无法通过js脚本将无法读取到cookie信息
3. 对URL检验:URLEncode 操作
将不可信的值输出 URL参数之前,进行 URLEncode操作。对于从 URL 参数中获取值一定要进行格式检测(比如你需要的时URL,就判读是否满足URL格式)
Web 安全头支持
浏览器自带的防御能力,一般是通过开启 Web 安全头生效的
CSP :W3C 的 Content Security Policy,简称 CSP,主要是用来定义页面可以加载哪些资源,减少 XSS 的发生。要配置 CSP , 需要对 CSP 的 policy 策略有了解,具体细节可以参考 CSP 是什么。
X-Download-Options: noopen :默认开启,禁用 IE 下下载框 Open 按钮,防止 IE 下下载文件默认被打开 XSS。
X-Content-Type-Options: nosniff :禁用 IE8 自动嗅探 mime 功能例如 text/plain 却当成 text/html 渲染,特别当本站点 server 的内容未必可信的时候。
X-XSS-Protection :IE 提供的一些 XSS 检测与防范,默认开启
后果
Cookie 信息
document.cookie 获取 Cookie 信息,然后通过 XMLHttpRequest 或 Fetch 加上 CORS 功能将数据发送给恶意服务器
监听用户行为
通过 addEventListener 监听用户行为,获取用户输入的银行卡支付密码等信息
更改 DOM 结构
伪造登录、输入支付秘密等窗口,获取用户私密信息
在页面内生成浮窗广告
劫持流量实现恶意跳转
CSRF(跨站请求伪造)
盗用身份,以你的名义发送恶意请求
盗用身份,以你的名义发送恶意请求
攻击流程如下:
受害者登录 http://a.com ,并保留了登录凭证(Cookie)
攻击者引诱受害者访问了 http://b.com
http://b.com 发送了一个请求:http://a.com/act=xx 。浏览器会默认携带 http://a.com 的Cookie。
http://a.com 接收到请求后,对请求进行验证,并确认是受害者的凭证,误以为是受害者自己发送的请求。
http://a.com 以受害者的名义执行了act=xx。
攻击完成,攻击者在受害者不知情的情况下冒充受害者,让 http://a.com 执行了自己定义的操作。
产生条件
目标站点一定要有 CSRF 漏洞
(1)登录受信任网站A,并在本地生成Cookie。(如果用户没有登录网站A,那么网站B在诱导的时候,请求网站A的api接口时,会提示你登录)
(2)在不登出A的情况下,访问危险网站B(其实是利用了网站A的漏洞)。
几种常见的攻击方式
自动发起 GET 请求的 CSRF
<img src="http://a.com/pay?amount=10000&for=hacker" >
攻击者将支付的接口请求隐藏在 img 标签内,在加载这个标签时,浏览器会自动发起 img 的资源请求,a.com 就会收到包含受害者登录信息的一次跨域请求
自动发起 POST 请求的 CSRF
访问该页面后,表单会自动提交,相当于模拟用户完成了一次 POST 操作。
引诱用户点击链接的 CSRF
链接类型的CSRF并不常见,比起其他两种用户打开页面就中招的情况,这种需要用户点击链接才会触发。这种类型通常是在论坛中发布的图片中嵌入恶意链接,或者以广告的形式诱导用户中招
如何冒充身份
登陆原理:由于http无状态,所以维持登陆一般方式就是利用cookies,发送请求判断这个cookies来看登陆态,一般分两种:1/登陆后将用户信息加密得到token存到cookies里,验证通过解密检查是否存在某个字段,2/使用session,服务器存一份key-value的session信息表,将登陆后用户赋予一个sessionid并存到cookies。
冒充原理:利用之前登陆的cookies,冒充用户访问一个域名下的其他页面,让浏览器携带这个cookies
防范
token验证
请求中添加token字段
后端给cookies添加一个csrftoken ,请求的时候取出csrftoken字段放到body中,后端对比token是否个cookies的token一致(利用同源能访问该源下的cookie)
验证referer
验证是否同域发送的请求(referer可以被修改,不可靠)
请求头添加token自定义属性
验证用户身份:例如验证码
内存与事件
内存泄漏是什么原因怎么解决
原因
一个对象不再使用应该回收没被回收,导致停留在堆内存中
没被声明的全局变量,关闭页面前都不会被释放
dom绑定了事件,但是在移除都时候没解除事件绑定
定时器没移除
闭包
内存分配有限,当超过限制就造成内存溢出
解决
减少全局变量
避免死循环
不用都东西要及时回收
减少层级过多都引用
js垃圾回收
概念
垃圾收集器会按照固定的时间间隔周期性的执行。对象在没有任何引用的时候才会被定时的垃圾回收机制回收。垃圾回收是自动完成的,我们不能强制执行或是阻止执行。也可以通过手动设置null清除一个个引用来进行垃圾回收。
js清除内存使用的是`标记清除`
js清除内存使用的是`标记清除`
垃圾回收有两种方法
标记清除
当变量进入环境的时候,就标记这个变量为“进入环境,当离开环境的时候就标记为”离开环境“
垃圾回收机器在运行时候会给存储在内存中的变量都加上标记,然后去掉环境变量中都变量,以及被环境变量中的变量引用的变量(条件性去除标记),删除所有标记的变量,回收了所占有的内存
引用计数
会记录每个值被引用的次数,当引用次数变成0后,就会被释放掉。
在IE中虽然JavaScript对象通过标记清除的方式进行垃圾回收,但BOM与DOM对象却是通过引用
计数回收垃圾的, 也就是说只要涉及BOM及DOM就会出现循环引用问题。两个对象的引用次数都为2,而不被回收
计数回收垃圾的, 也就是说只要涉及BOM及DOM就会出现循环引用问题。两个对象的引用次数都为2,而不被回收
GC的缺陷
GC时,停止响应其他操作,在100ms
GC优化策略
分代回收(Generation GC)
多回收“临时对象”区(young generation)
少回收“持久对象”区(tenured generation)
减少每次需遍历的对象,从而减少每次GC的耗时
浏览器垃圾回收
js的防抖节流
节流
保证一段时间内调用一次时间
防抖
每触发一次重新计算时间
event loop
异步的原理
js采用eventloop(事件循环)来解决单线程运行时阻塞带来的问题,
(在程序中设置两个线程:一个负责程序本身的运行,称为“主线程”,另一个负责其他进程),被称为eventloop
(在程序中设置两个线程:一个负责程序本身的运行,称为“主线程”,另一个负责其他进程),被称为eventloop
事件轮询就是解决javaScript单线程对于异步操作的一些缺陷,
让 javaScript做到既是单线程,又绝对不会阻塞的核心机制,
是用来协调各种事件、用户交互、脚本执行、UI 渲染、网络请求等的一种机制
让 javaScript做到既是单线程,又绝对不会阻塞的核心机制,
是用来协调各种事件、用户交互、脚本执行、UI 渲染、网络请求等的一种机制
事件循环event loop
1、所有同步任务在主线程中执行,形成一个执行栈
2、主线程之外,还存在一个消息队列,只要遇到异步任务,就到消息队列中排队
3、一旦执行栈中的所有同步任务执行完毕,系统就会按次序读取消息队列中的异步任务
4、被读取的异步任务结束等待,进入执行栈,开始执行
5、主线程不断重复上面
宏任务和微任务定义
宏任务:遇到宏任务会在下一次事件循环前执行。
————在一次新的事件循环的过程中,遇到宏任务时,宏任务将被加入任务类别,但需要等到下一次事件循环才会执行。常见的宏任务有setTimeout,setImmediate,requestAnimationFrame
————在一次新的事件循环的过程中,遇到宏任务时,宏任务将被加入任务类别,但需要等到下一次事件循环才会执行。常见的宏任务有setTimeout,setImmediate,requestAnimationFrame
微任务:只要有微任务就会继续执行,而不是放到下一个事件循环才执行。常见的微任务有MutationObserver,Promise.then
event loop 和DOM渲染
微任务是在DOM渲染前触发,如Promise
当主线程中的调用栈空闲时就尝试DOM渲染
宏任务的发生是在DOM渲染后触发,如setTimeout
当主线程中的调用栈空闲时就尝试DOM渲染
宏任务的发生是在DOM渲染后触发,如setTimeout
宏任务和微任务优先问题
微任务执行时机比宏任务早,每轮事件循环执行一个宏任务和所有的微任务。
宏任务队列有多个,微任务只有一个
微任务是ES6语法规定,宏任务是浏览器规定
宏任务(macro Task)
script(整体代码)
AJAX
DOM事件
setTimeout/setInterval
setImmediate(Node环境)
setImmediate比setTimeout(fn,0)快
I/O
UI 渲染
requestAnimationFrame
微任务(micro Task)
Promise的then()、catch()、finally()里面的回调
process.nextTick(Node 环境)优先级较高
例子
> ***script(主程序代码)—>process.nextTick—>Promises...——>setTimeout——>setInterval——>setImmediate——>
> I/O——>UI rendering***
事件
dom事件传播机制
事件捕获
从外向里依次查找元素
target.addEventListener('click',fn(){},true)
目标阶段
从当前事件源本身的操作
冒泡阶段
从内到外依次触发相关行为(最常用是冒泡阶段)
target.addEventListener('click',fn(){},false)
或
target.addEventListener('click',fn(){})
或
target.addEventListener('click',fn(){})
停止事件传播
stopPropagation()
停止后面的事件传播-绑定在当前目标上的其他剩余监听器仍将调用
stopImmediatePropagation
立即停止传播-甚至阻止当前节点上的其他监听器被调用。
事件的取消
preventDefault()
取消默认的浏览器操作。例如,单击一个链接或单击表单提交按钮分别会使浏览器导航到新页面和提交表单。
事件代理(事件委托)
概念:把原本要处理的事情委托给父元素
优点:利用的是DOM事件冒泡,提高性能,节省内存占用,减少事件注册
例子,点击ul里面的li,使用冒泡,将事件绑定在ul上,当点击li,触发冒泡绑定的事件,并且使用target来确定目标节点,而不是把每一个事件都绑定在li上
原理问题
js作用域有什么用
避免全局污染
提升性能
避免命名冲突
有利于压缩
保存闭包状态
模块化
原型和原型链
原型链
什么是原型链
所有对象都有自己的原型对象(__proto__),指向它的构造函数的prototype。由于原型对象也是对象,所以它也有自己的原型__proto__。因此,就会形成一个“原型链”,(prototype chain)都可以上溯到Object.prototype,最后指向null,当试图访问对象的某个属性时,不仅在该对象上找,还会在对象的原型和对象原型的原型上找,直到匹配到一样名字的属性或者达到原型链的末端
什么是原型对象
绝⼤部分的函数都有⼀个 prototype 属性,所有通过这个构造函数new出来的实例都会共享构造函数的原型对象prototype属性和方法
例如Object是构造函数,而Object.prototype是构造函数的原型对象。构造函数自身的属性和方法无法被共享,而原型对象prototype的属性和方法可以被所有实例对象所共享。[].__proto__===Array.prototype. / a.__proto__ == Object.prototype
Object:Object是一个函数对象,Object的原型就是一个Object对象,它里面存在着一些对象的方法和属性,例如最常见的toString方法。
js中的原型
新建对象:用new Object或者{}建的对象是普通对象,它没有prototype属性,只有__proto__属性,它指向Object.prototype。
Array:Array也是一个函数对象,它的原型就是Array.prototype,它里面存在着一些数组的方法和属性,例如常见的push,pop等方法。
Function:Function也是一个函数对象,但它有点特殊,它的原型就是一个function空函数。
自定义函数:它的原型就是你给它指定的那个东西。如果你不指定,那它的原型就是一个Object.prototype。
prototype和__proto__
每个函数都有一个原型属性prototype指向自身的原型,而由这个函数创建的对象也有一个__proto__属性指向这个原型
fn.__proto__=== Object.prototype
函数有prototype和__proto__,对象有__proto__
fn.__proto__=== Object.prototype
函数有prototype和__proto__,对象有__proto__
js在创建对象(不论是创建普通对象还是函数对象,都有一个叫__proto__的属性,用于指向创建它的函数对象的prototype原型对象)
构造对象的__proto__
数组的__proto__
函数的__proto
普通对象的__proto__
继承
1构造函数继承
call和apply一份给自己,是一次性拷贝,对方修改不知道
只能实现部分继承,无法继承父类原型链的属性方法
只能实现部分继承,无法继承父类原型链的属性方法
2.原型链继承
原型中都引用类型属性被所有实例共享,就是其中一个实例对父级引用类型属性的修改,导致所有实例都会被改变
3.组合继承
调用了两次父类构造函数, 性能浪费, 第一次new一个你的构造函数返回给我, 目的是为了成为你的实例好去引用你的原型
4.原型式继承(继承对象)
es5的object.create(对象),原理是浅拷贝,直接抄,不想改
5.寄生式继承(继承对象)
原型链基础上添加属性和方法
6. 寄生组合式继承(常用!)
(call+寄生)
核心:因为是对父类原型的复制,所以不包含父类的构造函数,也就不会调用两次父类的构造函数造成浪费。解决了之前组合继承的两次调用父类构造函数问题
7.es6 class 继承
原理
Class 基于原型链实现,本质是function,作为构造函数的语法糖,同时有prototype属性和__proto__属性,因此同时存在两条继承链。
(1)子类的__proto__属性,表示构造函数的继承,总是指向父类。(把子类构造函数(Child)的原型(__proto__)指向了父类构造函数(Parent),)
(2)子类prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性
(1)子类的__proto__属性,表示构造函数的继承,总是指向父类。(把子类构造函数(Child)的原型(__proto__)指向了父类构造函数(Parent),)
(2)子类prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性
类的继承其实就是基于寄生组合继承来实现的
使用借用构造函数(call)来继承父类this声明的属性/方法
通过寄生式封装函数设置父类prototype为子类prototype的原型来继承父类的prototype声明的属性/方法。
继承的时候extends干了什么
extends在实现继承方面,本质上也是原型链继承,该方法实现了两步原型链继承
大多数浏览器的 ES5 实现之中,每一个对象都有__proto__属性,指向对应的构造函数的prototype属性。
Class 作为构造函数的语法糖,同时有prototype属性和__proto__属性,因此同时存在两条继承链。
(1)子类的__proto__属性,表示构造函数的继承,总是指向父类。(把子类构造函数(Child)的原型(__proto__)指向了父类构造函数(Parent),)
(2)子类prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性。
Extents 和supper
主动调用super(),否则无法调用父构造函数,无法完成继承。
super() 等同于Parent.prototype.construtor.call(this) //调用父类构造器,并改变指向
默认的构造函数中会主动调用父类构造函数,并默认把当前constructor传递的参数传给了父类。
new操作符做了什么
new 操作符新建了一个空对象,这个对象原型指向构造函数的prototype,执行构造函数后返回这个对象。
this
this指向
普通函数指向window
对象的方法指向对象
构造函数中,this指向实例
在事件中,指触发这个事件的对象
定时器指向window
立即执行this指向window
匿名函数this指向window,var a = function(){}
箭头函数没有自己的this,默认指向作用域的this
call/apply/bind(改变this指向)
call
手写fn.call(o,1,2)
apply
手写fn.apply(o,[1,2])
bind
fn.bind(o,1,2) 返回 一个新函数,不调用
词法作用域与作用域链
作用域规定了如何查找变量,也就是确定当前执行代码对变量的访问权限。当尝试获取某属性值时先从所在作用域查找,找不到会向上作用域查找
ES5只有全局作用域没和函数作用域,ES6增加块级作用域
暂时性死区:在代码块内,使用 let 和 const 命令声明变量之前,该变量都是不可用的,语法上被称为暂时性死区。
JavaScript 采用词法作用域(lexical scoping),也就是静态作用域。在你书写代码时就确定了。
词法作用域就是作用域是由书写代码时函数声明的位置来决定的。编译阶段就能够知道全部标识符在哪里以及是如何声明的,所以词法作用域是静态的作用域,也就是词法作用域能够预测在执行代码的过程中如何查找标识符。
词法作用域就是作用域是由书写代码时函数声明的位置来决定的。编译阶段就能够知道全部标识符在哪里以及是如何声明的,所以词法作用域是静态的作用域,也就是词法作用域能够预测在执行代码的过程中如何查找标识符。
函数的作用域在函数定义的时候就决定了。
当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级(词法层面上的父级执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链
css
盒子模型
border
content
padding
margin
BFC(块级格式化上下文)
什么是BFC
BFC(Block formatting context),即块级格式化上下文,它作为HTML页面上的一个独立渲染区域,只有区域内元素参与渲染,且不会影响其外部元素。简单来说,可以将 BFC 看做是一个“围城”,外面的元素进不来,里面的元素出不去(互不干扰)
形成BFC的条件
1、浮动元素,float 除 none 以外的值;
2、定位元素,position(absolute,fixed);
3、display 为以下其中之一的值 inline-block,table-cell,table-caption;
4、overflow 除了 visible 以外的值(hidden,auto,scroll);
5、body 根元素
满足以上任一条件,就出发BFC布局
BFC 一般用来解决以下几个问题
边距重叠问题
消除浮动问题
自适应布局问题
一个决定如何渲染元素的容器 ,渲染规则
1、内部的块级元素会在垂直方向,一个接一个地放置。
2、块级元素垂直方向的距离由margin决定。属于同一个BFC的两个相邻块级元素的margin会发生重叠。
3、对于从左往右的格式化,每个元素(块级元素与行内元素)的左边缘,与包含块的左边缘相接触,(对于从右往左的格式化则相反)。即使包含块中的元素存在浮动也是如此,除非其中元素再生成一个BFC。
4、BFC的区域不会与浮动元素重叠。
5、BFC是一个隔离的独立容器,容器里面的子元素和外面的元素互不影响。
6、计算BFC容器的高度时,浮动元素也参与计算。
IFC(内联格式化上下文)
IFC的line box(线框)高度由其包含行内元素中最高的实际高度计算而来(不受到竖直方向的padding/margin影响)I
作用
水平居中:当一个块要在环境中水平居中时,设置其为inline-block则会在外层产生IFC,通过text-align则可以使其水平居中。
垂直居中:创建一个IFC,用其中一个元素撑开父元素的高度,然后设置其vertical-align:middle,其他行内元素则可以在此父元素下垂直居中
FFC(弹性盒模型、自适应格式上下文)
display值为flex或者inline-flex
清除浮动
添加新的元素 、应用 clear:both;
父级div定义 overflow: auto
使用伪元素来清除浮动(:after,注意:作用于浮动元素的父亲)
使用双伪元素清除浮动
flex
容器上的属性
align-content
flex-start | flex-end | center | space-between | space-around | stretch;
align-items
flex-start | flex-end | center | baseline | stretch;
justify-content
flex-start | flex-end | center | space-between | space-around;
flex-flow
flex-flow属性是flex-direction属性和flex-wrap属性的简写形式,默认值为row nowrap。
flex-wrap
nowrap | wrap | wrap-reverse;
flex-direction
row | row-reverse | column | column-reverse
单个item属性
order
数值越小排列越前
flex-grow
number /* default 0 */,放大比例,如果所有项目的flex-grow属性都为1,则它们将等分剩余空间(如果有的话)。如果一个项目的flex-grow属性为2,其他项目都为1,则前者占据的剩余空间将比其他项多一倍
flex-shrink
f缩小比例,默认为1,即如果空间不足,该项目将缩小。
flex-basis
沿着主轴方向的元素大小,如300px
flex
flex: none | flex属性是flex-grow, flex-shrink 和 flex-basis的简写;它的默认值是0(grow:有剩余空间时不扩展宽度),1(shrink:剩余空间不足时,以一倍速度缩小宽度)和 auto(basis)
常用值
flex: 0 auto 等同于 flex: initial 等同于 flex: 0 1 auto 的简写表达。
它根据元素自身的width 或 height 属性来调节元素大小。
当还剩余一些空闲空间时,它使 flex 元素呈现的是固定大小的样式;
当没有足够的空间时,它允许它收缩到最小。 auto 边距可用于根据主轴来对齐元素。
它根据元素自身的width 或 height 属性来调节元素大小。
当还剩余一些空闲空间时,它使 flex 元素呈现的是固定大小的样式;
当没有足够的空间时,它允许它收缩到最小。 auto 边距可用于根据主轴来对齐元素。
flex: auto 等同于 flex: 1 1 auto ,
它根据元素的 width 或 height 属性调整元素的大小,
但是其非常灵活,以便让它们吸收沿主轴的任何额外空间。
它根据元素的 width 或 height 属性调整元素的大小,
但是其非常灵活,以便让它们吸收沿主轴的任何额外空间。
flex: none 等同于 flex: 0 0 auto 。
它根据 width 和 `height 来调节元素大小,
但是完全不灵活。不会按照容器宽度伸缩
它根据 width 和 `height 来调节元素大小,
但是完全不灵活。不会按照容器宽度伸缩
align-self
align-self属性允许单个项目有与其他项目不一样的对齐方式,可覆盖align-items属性。
auto | flex-start | flex-end | center | baseline | stretch;
兼容
position
动画
居中
水平居中
行内元素
text-align:center
块级元素
宽度固定margin/absolute
margin:0 auto
absolute+left:50%+margin-left:-px
absolute+left:calc(50%-2/宽度)
absolute+left0+right0+margin:0 auto
宽度不固定transform/flex
tansform:translate(-50%,0)
flex布局
justify-content: center;
display: flex;
垂直居中
行内元素display
单行文本
line-height
多行文本
父:display: table;
子: display: table-cell;
vertical-align: middle;
display:inline-block + vertical-align
.parent3{
display: grid;
}
.child3{
margin: auto;
}
块级元素
宽高固定 position:absoliute
absolute+margin-top
absolute+calc
absolute+top0+bottom0+margin:auto
宽高不定tansform/flex
transform
flex
display: flex;
justify-content: center; /* 水平居中 */
align-items: center; /* 垂直居中 */
css三列布局
float布局:左边左浮动,右边右浮动,中间margin:0 100px;
Position布局: 左边left:0; 右边right:0; 中间left: 100px; right: 100px;
table布局: 父元素 display: table; 左右 width: 100px; 三个元素display: table-cell;
弹性(flex)布局:父元素 display: flex; 左右 width: 100px;
网格(gird)布局:
避免CSS全局污染
scoped 属性
css in js
CSS Modules
使用less,尽量少使用全局对选择器
浏览器
通信
跨域
jsonp
jsonp 可以解决老版本浏览器跨域访问,但是只能get不能post 原理是创建script标签获取资源
原理:jsonp原理是插入script标签,利用src向服务器发送(跨域)请求,前端将callback挂在window,等待请求回来调用callback,后台通过callback参数传递到浏览器
服务器代理
可以使用node中间件转发请求
nginx反向代理
CORS
最常用的跨域方式,支持post
实现cors(跨域资源共享)关键是服务器,只有服务器实现了CORS,就能跨域通信,
后台需要允许请求跨域,access-contorl-allow-origin 该值要和Origin一致才能生效。
可以通过iframe跨域
window.postMessage
当一个域名为domain1下的页面A想要向domain2发出ajax请求时,由于同源策略的限制无法直接请求到数据,但是可以在页面A中动态添加一个display设置为none的iframe,该iframe的src为domain2下的html页面B,由页面B来发出ajax请求,因为页面B是domain2下的所以可以成功发出请求。当B页面拿到数据之后再传递给A页面。
window.name
携带数据页面跳转
当a页面中有一个b页面的iframe时,两个页面共享window.name。在b页面拿到数据后赋值给window.name。但是由于不使用postMessage API,数据无法传输给a页面。我们可以将location.href跳转为domain1(a页面所在的域名)下的c页面,由c页面调用a页面的回调方法。
websocket跨域
原理:利用webSocket的API,可以直接new一个socket实例,然后通过open方法内send要传输到后台的值,也可以利用message方法接收后台传来的数据。
后台是通过new WebSocket.Server({port:3000})实例,利用message接收数据,利用send向客户端发送数据。
后台是通过new WebSocket.Server({port:3000})实例,利用message接收数据,利用send向客户端发送数据。
websocket
WebSocket是HTML5中的协议,本质上是基于TCP,先通过HTTP/HTTPS协议发起一条特殊HTTP请求进行握手后创建一个用于交换数据的TCP连接,也是支持长连接的。WebSocket是由HTTP先发起的,然后在转为WebSocket。
Socket.io 支持向下兼容:socket.io封装了websocket,同时包含了其它的连接方式,你在任何浏览器里都可以使用socket.io来建立异步的连接。socket.io包含了服务端和客户端的库,如果在浏览器中使用了socket.io的js,服务端也必须同样适用。
socket.io底层是基于engine.io这个库。engine.io为 socket.io 提供跨浏览器/跨设备的双向通信的底层库。engine.io使用了 Websocket 和 XHR 方式封装了一套 socket 协议。在低版本的浏览器中,不支持Websocket,为了兼容使用长轮询(polling)替代
Socket.io 支持向下兼容:socket.io封装了websocket,同时包含了其它的连接方式,你在任何浏览器里都可以使用socket.io来建立异步的连接。socket.io包含了服务端和客户端的库,如果在浏览器中使用了socket.io的js,服务端也必须同样适用。
socket.io底层是基于engine.io这个库。engine.io为 socket.io 提供跨浏览器/跨设备的双向通信的底层库。engine.io使用了 Websocket 和 XHR 方式封装了一套 socket 协议。在低版本的浏览器中,不支持Websocket,为了兼容使用长轮询(polling)替代
和http有什么关系
1、WebSocket是双向通信协议,模拟Socket协议,可以双向发送或接受信息,而HTTP是单向的;http只能由客户端发起,http不能做到服务器主动向客户端发送信息,基于这种单向的特点,如果服务器由连续的状态变化,只能轮询,但是轮询很浪费资源(不停连接),
2、WebSocket是需要浏览器和服务器握手进行建立连接的,而http是浏览器发起向服务器的连接。
3、HTTP2的推送是浏览器和服务端之间的一个推送的概念,比如我在HTTP2中请求 a.html 服务端会自动把css和一些其他资源一并返回,
而websocket的推送是有API的,可以在客户端手动控制的,这就是区别
而websocket的推送是有API的,可以在客户端手动控制的,这就是区别
特点
建立在tcp上,服务器实现比较容易
它是一个有状态协议,客户端和服务器之间连接保持活动状态
与http协议有好的兼容性,握手阶段采用http
数据格式比较轻,性能开销小,通讯高效
可以发文本和二进制
没有同源限制,客户端可以任意服务器通信
协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。
new WebSocket('ws://localhost:8000')
new WebSocket('ws://localhost:8000')
作用
聊天室
游戏
交易
web worker
HTML页面中,如果在执行脚本时,页面的状态是不可响应的,直到脚本执行完成后,页面才变成可响应。
web worker是运行在后台的js,独立于其他脚本,不会影响页面你的性能。
并且通过postMessage将结果回传到主线程。这样在进行复杂操作的时候,就不会阻塞主线程了
并且通过postMessage将结果回传到主线程。这样在进行复杂操作的时候,就不会阻塞主线程了
创建Worker时,JS引擎向浏览器申请开一个子线程(子线程是浏览器开的,完全受主线程控制,而且不能操作DOM)
JS引擎线程与worker线程间通过特定的方式通信(postMessage API,需要通过序列化对象来与线程交互特定的数据)
JS引擎线程与worker线程间通过特定的方式通信(postMessage API,需要通过序列化对象来与线程交互特定的数据)
限制
通信限制,dom限制,脚本限制,文件访问限制、跨域限制
如何实现会话跟踪
cookie
session
url重写
隐藏的input
ip地址
状态码
1xx(消息)
100 继续Continue。客户端应继续其请求
101--切换协议Switching。服务器根据客户端的请求切换协议。只能切换到更高级的协议,例如,切换到的websocket协议
2xx(成功)
200 OK客户端请求成功
201 Created已创建。成功请求并创建了新的资源
202 Accepted 服务器已接受请求,但尚未处理
203 No-Authoritative 非授权信息,服务器已成功处理了请求,但返回的信息可能来自另一来源。
204,No Content请求成功,但没资源返回
3xx(重定向)
300 Multiple Choices多种选择。请求的资源可包括多个位置,相应可返回一个资源特征与地址的列表用于用户终端(例如:浏览器)选择
301 Move Permanently永久移动。请求的资源已被永久的移动到新URI,返回信息会包括新的URI,浏览器会自动定向到新URI。今后任何新的请求都应使用新的URI代替
302 Move Temporarily临时移动。与301类似。但资源只是临时被移动。客户端应继续使用原有URI
303 See Other查看其它地址。与301类似。使用GET和POST请求查看
304 Not Modified,未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。客户端通常会缓存访问过的资源,通过提供一个头信息指出客户端希望只返回在指定日期之后修改的资源
305 Use Proxy使用代理。所请求的资源必须通过代理访问
4xx(请求错误)
400 Bad Request客户端请求有语法错误,不能被服务器所理解
401--Unauthorized请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用
402 Payment Required 该状态码是为了将来可能的需求而预留的
403Forbidden 服务器收到请求,但是拒绝提供服务
404Not Found //请求资源不存在
405 Method Not Allowed客户端请求中的方法被禁止
5xx(服务器错误)
500 Internal Server Error //服务器发生不可预期的错误
501 Not Implemented服务器不支持请求的功能,无法完成请求
502 Bad Gateway作为网关或者代理工作的服务器尝试执行请求时,从远程服务器接收到了一个无效的响应
503 Server Unavailable //超载或系统维护,服务器当前不能处理客户端的请求,一段时间后可能恢复正常
504 Gateway Timeout充当网关或代理的服务器,未及时从远端服务器获取请求
505 HTTP Version Not Supported服务器不支持请求的HTTP协议的版本,无法完成处理
存储
cookies/sessionStorage/localStorage
cookies
和localsession区别
生存周期被指定一个maxAge的值,关闭浏览器还在
使用麻烦,需要封装setCookies,getCookies
由服务器的请求来传递每次携带在htttp请求头中,如果使用过多cookies会影响性能
4k
作用
由于http是一个无状态协议,因此可以借助cookies存储sessionId来唯一标识用户,cookies存在浏览器,session存在服务器
保存用户登陆状态:例如将用户id存在cookies,下次用户访问页面不需要重新登陆,可以设置过期时间
跟踪用户行为,记住用户选择,下次打开,仍然保留该选择
举个登陆栗子
cookies如何防范xss攻击
XSS(跨站脚本攻击)是指攻击者在返回的HTML中嵌入javascript脚本,为了减轻这些攻击,需要在HTTP头部配上,set-cookie:
httponly-这个属性可以防止XSS,它会禁止javascript脚本来访问cookie。
secure - 这个属性告诉浏览器仅在请求为https的时候发送cookie。
response.setHeader("Set-Cookie", "cookiename=httponlyTest;Path=/;Domain=domainvalue;Max-Age=seconds;HTTPOnly");
localStorage
除非清除,否则一直存储
5M
不参与服务器通信
较易使用
sessionStorage
会话存储
5M
不参与服务器通信
较易使用
不同浏览器无法共享localStorage和sessionStorage中的信息。同一浏览器的相同域名和端口的不同页面间可以共享相同的 localStorage,但是不同页面间无法共享sessionStorage的信息。
解决:目前广泛采用的是postMessage和iframe相结合的方法。postMessage(data,origin)方法允许来自不同源的脚本采用异步方式进行通信,可以实现跨文本档、多窗口、跨域消息传递。
解决:目前广泛采用的是postMessage和iframe相结合的方法。postMessage(data,origin)方法允许来自不同源的脚本采用异步方式进行通信,可以实现跨文本档、多窗口、跨域消息传递。
缓存
强缓存
200 OK (from cache)
Cache-Control(较高优先级):请求/响应头,精确控制缓存策略
可以是一个相对时间
no-cache;max-age=<1000000>
no-cache;max-age=<1000000>
Expires(ɪk;spaɪəz):响应头,代表资源过期时间
是一个具体时间(会被客户端影响)
协商缓存
304 Not Modified
If-Modified-Since/Last-Moddified(资源最近修改时间)
If-Modified-Since:请求头,浏览器再次请求:浏览器->服务器,比较两个字段是否一致
Last-Moddified:响应头,首次请求 服务器->浏览器。只能精确到1s以内
If-None-Match/Etag(资源标识,优先级较高)
If-None-Match:请求头,浏览器->服务器
Etag:响应头,服务器->浏览器
整个缓存分析过程
1、浏览器每次发起请求,都会先在浏览器缓存中查找该请求的结果以及缓存标识
2、浏览器每次从服务器端拿到返回的请求结果,都会将该结果和缓存标识存入浏览器缓存中
3、先看强缓存是否符合,然后再协商缓存
请求相关
常用的请求头部(部分):
Accept: 接收类型,表示浏览器支持的MIME类型
(对标服务端返回的Content-Type)
Accept-Encoding:浏览器支持的压缩类型,如gzip等,超出类型不能接收
Content-Type:客户端发送出去实体内容的类型
Cache-Control: 指定请求和响应遵循的缓存机制,如no-cache
If-Modified-Since:对应服务端的Last-Modified,用来匹配看文件是否变动,只能精确到1s之内,http1.0中
Expires:缓存控制,在这个时间内不会请求,直接使用缓存,http1.0,而且是服务端时间
Max-age:代表资源在本地缓存多少秒,有效时间内不会请求,而是使用缓存,http1.1中
If-None-Match:对应服务端的ETag,用来匹配文件内容是否改变(非常精确),http1.1中
Cookie: 有cookie并且同域访问时会自动带上
Connection: 当浏览器与服务器通信时对于长连接如何进行处理,如keep-alive
Host:请求的服务器URL
Origin:最初的请求是从哪里发起的(只会精确到端口),Origin比Referer更尊重隐私
Referer:该页面的来源URL(适用于所有类型的请求,会精确到详细页面地址,csrf拦截常用到这个字段)
User-Agent:用户客户端的一些必要信息,如UA头部等
常用的响应头部(部分):
Access-Control-Allow-Headers: 服务器端允许的请求Headers
Access-Control-Allow-Methods: 服务器端允许的请求方法
Access-Control-Allow-Origin: 服务器端允许的请求Origin头部(譬如为*)
Content-Type:服务端返回的实体内容的类型
Date:数据从服务器发送的时间
Cache-Control:告诉浏览器或其他客户,什么环境可以安全的缓存文档
Last-Modified:请求资源的最后修改时间
Expires:应该在什么时候认为文档已经过期,从而不再缓存它
Max-age:客户端的本地资源应该缓存多少秒,开启了Cache-Control后有效
ETag:请求变量的实体标签的当前值
Set-Cookie:设置和页面关联的cookie,服务器通过这个头部把cookie传给客户端
Keep-Alive:如果客户端有keep-alive,服务端也会有响应(如timeout=38)
Server:服务器的一些相关信息
举个栗子
一般来说,请求头部和响应头部是匹配分析的。
譬如,请求头部的Accept要和响应头部的Content-Type匹配,否则会报错
譬如,跨域请求时,请求头部的Origin要匹配响应头部的Access-Control-Allow-Origin,否则会报跨域错误
譬如,在使用缓存时,请求头部的If-Modified-Since、If-None-Match分别和响应头部的Last-Modified、ETag对应
页面渲染这个过程
浏览器从url到输入到显示页面
DNS解析出ip
浏览器输入url后,首先要经过域名解析,因为浏览器不能之间找到服务器,要通过ip地址
DNS服务就是通过域名查找ip,或者逆向查询
先找缓存,没有再找DNS。浏览器缓存->系统缓存->路由器缓存->系统hosts->DNS查询
DNS查询方式
递归查询
到DNS服务器查询,查不到,DNS发请求到其他服务器直到查到为止
迭代查询(常用)
DNS查询,查不到,返回一个可能查到到DNS 服务器地址给浏览器,浏览器再查询,直到查到为止
TCP连接,3次握手
SYN:同步标识,表示TCP连接已初始化。
ACK:确认标识,用于表示对数据包的成功接收。
ack,是对上一个包的序号进行确认的号,ack=seq+1。
Seq:随机数序号
三次握手的目的:为了防止已经失效的连接请求报文段突然又传送到了服务器端,从而产生错误。
发送http请求
服务器处理请求,并返回报文
浏览器渲染页面
1)HTML解析,处理HTML标记并构建DOM树。
2)CSS解析,处理CSS标记并构建CSSOM树。
3)将DOM树和CSSOM合并称render tree(渲染树)。将每条css规则按照【从右至左】的方式在dom树上进行逆向匹配,然后生成具有样式规则描述的渲染树。
4)渲染树布局,计算每个节点的集合信息。包括repaint和reflow。
reflow与repaint的时机:
渲染树转换为网页布局,称为“布局流”(flow),布局显示到页面这个过程叫“绘制”(paint),
都具有阻塞效果,且耗费时间和资源
页面生成之后,脚本和样式操作,都会触发reflow和repaint,两个不一定同时发生,重流必会重绘,重绘不一定重流,
display:none 会触发 reflow,而 visibility:hidden 只会触发 repaint,因为没有发生位置变化。
有些情况下,比如修改了元素的样式,浏览器并不会立刻 reflow 或 repaint 一次,而是会把这样的操作积攒一批,然后做一次 reflow,这又叫异步 reflow 或增量异步 reflow。
有些情况下,比如 resize 窗口,改变了页面默认的字体等。对于这些操作,浏览器会马上进行 reflow。
大多数浏览器会限制重绘发生在子树上面,最小化耗费资源,而不会全局重新生成页面
渲染树转换为网页布局,称为“布局流”(flow),布局显示到页面这个过程叫“绘制”(paint),
都具有阻塞效果,且耗费时间和资源
页面生成之后,脚本和样式操作,都会触发reflow和repaint,两个不一定同时发生,重流必会重绘,重绘不一定重流,
display:none 会触发 reflow,而 visibility:hidden 只会触发 repaint,因为没有发生位置变化。
有些情况下,比如修改了元素的样式,浏览器并不会立刻 reflow 或 repaint 一次,而是会把这样的操作积攒一批,然后做一次 reflow,这又叫异步 reflow 或增量异步 reflow。
有些情况下,比如 resize 窗口,改变了页面默认的字体等。对于这些操作,浏览器会马上进行 reflow。
大多数浏览器会限制重绘发生在子树上面,最小化耗费资源,而不会全局重新生成页面
1.页面渲染初始化
2.DOM结构改变,比如删除了某个节点
3.render树变化,比如减少了padding
4.窗口resize
5.最复杂的一种:获取某些属性,引发回流,
很多浏览器会对回流做优化,会等到数量足够时做一次批处理回流,
但是除了render树的直接变化,当获取一些属性时,浏览器为了获得正确的值也会触发回流,这样使得浏览器优化无效,包括
(1)offset(Top/Left/Width/Height)
(2) scroll(Top/Left/Width/Height)
(3) cilent(Top/Left/Width/Height)
(4) width,height
(5) 调用了getComputedStyle()或者IE的currentStyle
reflow
1.页面渲染初始化
2.DOM结构改变,比如删除了某个节点(display:none,字体大小修改)
3.render树变化,比如减少了padding
4.窗口resize
5.最复杂的一种:获取某些属性,引发回流,
很多浏览器会对回流做优化,会等到数量足够时做一次批处理回流,
但是除了render树的直接变化,当获取一些属性时,浏览器为了获得正确的值也会触发回流,这样使得浏览器优化无效,包括
(1)offset(Top/Left/Width/Height)
(2) scroll(Top/Left/Width/Height)
(3) cilent(Top/Left/Width/Height)
(4) width,height
(5) 调用了getComputedStyle()或者IE的currentStyle
2.DOM结构改变,比如删除了某个节点(display:none,字体大小修改)
3.render树变化,比如减少了padding
4.窗口resize
5.最复杂的一种:获取某些属性,引发回流,
很多浏览器会对回流做优化,会等到数量足够时做一次批处理回流,
但是除了render树的直接变化,当获取一些属性时,浏览器为了获得正确的值也会触发回流,这样使得浏览器优化无效,包括
(1)offset(Top/Left/Width/Height)
(2) scroll(Top/Left/Width/Height)
(3) cilent(Top/Left/Width/Height)
(4) width,height
(5) 调用了getComputedStyle()或者IE的currentStyle
优化方案
减少逐项更改样式,最好一次性更改style,或者将样式定义为class并一次性更新
避免循环操作dom,创建一个documentFragment或div,在它上面应用所有DOM操作,最后再把它添加到window.document
避免多次读取offset等属性。无法避免则将它们缓存到变量
将复杂的元素绝对定位或固定定位,使得它脱离文档流,否则回流代价会很高
避免循环操作dom,创建一个documentFragment或div,在它上面应用所有DOM操作,最后再把它添加到window.document
避免多次读取offset等属性。无法避免则将它们缓存到变量
将复杂的元素绝对定位或固定定位,使得它脱离文档流,否则回流代价会很高
5)渲染树绘制,将每个节点绘制到屏幕上。
在dom树的构建过程中如果遇到JS脚本和外部JS连接,则会停止构建DOM树来执行和下载相应的代码,这会造成阻塞,这就是为什么推荐JS代码应该放在html代码的后面
CSS 不会阻塞 DOM 的解析,但会阻塞 DOM 渲染。有例外,media query声明的CSS是不会阻塞渲染的
JS 阻塞 DOM 解析和渲染,但浏览器会偷看DOM,预先下载相关资源。
CSS加载阻塞JS脚本执行。浏览器遇到 <script>且没有defer或async属性的 标签时,会触发页面渲染,因而如果前面CSS资源尚未加载完毕时,浏览器会等待它加载完毕在执行脚本。
所以,<script>最好放底部,<link>最好放头部,如果头部同时有<script>与<link>的情况下,最好将<script>放在<link>上
面
假如我们将js放在header,js将阻塞解析dom解释和渲染,dom的内容会影响到First Paint,导致First Paint延后。所以说我们会将js放在后面,以减少First Paint的时间,但是不会减少DOMContentLoaded(DOM解析后)被触发的时间。
JS 阻塞 DOM 解析和渲染,但浏览器会偷看DOM,预先下载相关资源。
CSS加载阻塞JS脚本执行。浏览器遇到 <script>且没有defer或async属性的 标签时,会触发页面渲染,因而如果前面CSS资源尚未加载完毕时,浏览器会等待它加载完毕在执行脚本。
所以,<script>最好放底部,<link>最好放头部,如果头部同时有<script>与<link>的情况下,最好将<script>放在<link>上
面
假如我们将js放在header,js将阻塞解析dom解释和渲染,dom的内容会影响到First Paint,导致First Paint延后。所以说我们会将js放在后面,以减少First Paint的时间,但是不会减少DOMContentLoaded(DOM解析后)被触发的时间。
defer和async
都是异步加载,但是async阻塞dom渲染
都是异步加载,但是async阻塞dom渲染
defer(推迟):异步加载,等待dom渲染完毕再按照顺序执行脚本,执行完毕后会触发DOMContentLoaded
async:异步加载,阻塞dom渲染,加载完毕即刻执行脚本,不按加载顺序顺序执行,谁加载完就立即执行,再继续前面的dom的渲染,
DOMContentLoaded是在html解析完就触发,不等async的js脚本执行完,可能是js执行的前面可能j是s执行后面
"js高级程序设计"上说当多个js有async标识时,js脚本一定会在load执行前执行,但不一定在DOMContentLoaded事件触发前后执行。
DOMContentLoaded是在html解析完就触发,不等async的js脚本执行完,可能是js执行的前面可能j是s执行后面
"js高级程序设计"上说当多个js有async标识时,js脚本一定会在load执行前执行,但不一定在DOMContentLoaded事件触发前后执行。
正常遇到script标签时,阻塞Dom解析和渲染;转而去处理script脚本,如果脚本是内联的,浏览器会先去执行这段内联的脚本;如果是外链的,那么会先加载脚本js文件,后执行js文件。在处理完js脚本后,浏览器便继续解析HTML文档。
DOMContentLoaded和window.onload和首屏时间关系
DOMContentLoaded 事件触发时,仅当DOM加载完成,不包括样式表,图片(譬如如果有async加载的脚本就不一定完成)。
load 事件触发时,页面上所有的DOM,样式表,脚本,图片都已经加载完成了(包括async)。
所以DOMContentLoaded之后用户就可以进行交互,绑定事件可以在DOMContentLoaded后绑定,因为这时候已经能够获取到DOM节点。
load 事件触发时,页面上所有的DOM,样式表,脚本,图片都已经加载完成了(包括async)。
所以DOMContentLoaded之后用户就可以进行交互,绑定事件可以在DOMContentLoaded后绑定,因为这时候已经能够获取到DOM节点。
关于首屏时间和css/js加载问题
css才是影响页面首屏时间的罪魁祸首,如果css太大,页面白屏的时间就长了,就是render tree被推迟了,所以如果css太大就需要把非首屏css也放到底部,js只不过是影响用户和js相关的那些操作的时间而已。如果面试的话,就是这么回答。
结论:以下首屏是指(首次绘制dom在页面上的时间,从白屏到显示一部分元素。也就是第一次Painting)
首屏时间和DomContentLoad事件没有必然的先后关系
所有CSS尽早加载是减少首屏时间的最关键
js的下载和执行会阻塞Dom树的构建(严谨地说是中断了Dom树的更新),所以script标签放在首屏范围内的HTML代码段里会截断首屏的内容。
script标签放在body底部,做与不做async或者defer处理,都不会影响首屏时间,但影响DomContentLoad和load的时间,进而影响依赖他们的代码的执行的开始时间。
---------------------------------------
如果script标签的位置不在首屏范围内,不影响首屏时间
所有的script标签应该放在body底部是很有道理的
但从性能最优的角度考虑,即使在body底部的script标签也会拖慢首屏出来的速度,因为浏览器在最一开始就会请求它对应的js文件,而这,占用了有限的TCP链接数、带宽甚至运行它所需要的CPU。这也是为什么script标签会有async或defer属性的原因之一。
首屏时间和DomContentLoad事件没有必然的先后关系
所有CSS尽早加载是减少首屏时间的最关键
js的下载和执行会阻塞Dom树的构建(严谨地说是中断了Dom树的更新),所以script标签放在首屏范围内的HTML代码段里会截断首屏的内容。
script标签放在body底部,做与不做async或者defer处理,都不会影响首屏时间,但影响DomContentLoad和load的时间,进而影响依赖他们的代码的执行的开始时间。
---------------------------------------
如果script标签的位置不在首屏范围内,不影响首屏时间
所有的script标签应该放在body底部是很有道理的
但从性能最优的角度考虑,即使在body底部的script标签也会拖慢首屏出来的速度,因为浏览器在最一开始就会请求它对应的js文件,而这,占用了有限的TCP链接数、带宽甚至运行它所需要的CPU。这也是为什么script标签会有async或defer属性的原因之一。
dom解析中没有js
dom解析中遇到css
dom解析中遇到css,js
嵌入的JS应该放在什么位置
1、放在底部,只会阻塞所有内容的呈现,但是不会则阻塞资源的下载(js放在</body>前)
2、如果嵌入JS放在head中,则嵌入JS放在css头部
3、使用defer
4、不要在嵌入的JS中调用执行时间较长的函数,如果一定要用,可以用setTimeout来调用
5、动态脚本元素使用js动态创建HTML的几乎全部文档内容
2、如果嵌入JS放在head中,则嵌入JS放在css头部
3、使用defer
4、不要在嵌入的JS中调用执行时间较长的函数,如果一定要用,可以用setTimeout来调用
5、动态脚本元素使用js动态创建HTML的几乎全部文档内容
以上结论:CSS加载阻塞JS脚本执行,JS阻塞Dom解析,CSS 不阻塞DOM解析,但阻塞DOM渲染 ,media query声明的例外,
defer和async异步加载,defer等dom解析完延时按顺序执行之后DOMContentLoaded,async谁加载完就马上执行,因为不确定Dom解析好没有,所以DOMContentLoaded可能在之前或者之后。
onload是资源加载完毕后执行,包括图片资源
DOMContentLoaded等html,js解析完后执行,总的来说,当文档中没有脚本时,浏览器解析完文档便能触发 DOMContentLoaded 事件;如果文档中包含脚本,则脚本会阻塞文档的解析,而脚本需要等 CSSOM 构建完成才能执行。在任何情况下,DOMContentLoaded 的触发不需要等待图片等其他资源加载完成。
defer和async异步加载,defer等dom解析完延时按顺序执行之后DOMContentLoaded,async谁加载完就马上执行,因为不确定Dom解析好没有,所以DOMContentLoaded可能在之前或者之后。
onload是资源加载完毕后执行,包括图片资源
DOMContentLoaded等html,js解析完后执行,总的来说,当文档中没有脚本时,浏览器解析完文档便能触发 DOMContentLoaded 事件;如果文档中包含脚本,则脚本会阻塞文档的解析,而脚本需要等 CSSOM 构建完成才能执行。在任何情况下,DOMContentLoaded 的触发不需要等待图片等其他资源加载完成。
资源预加载
注意:在现代浏览器中,为减缓渲染被阻塞的情况,现代浏览器都使用了猜测预加载技术。当解析被阻塞时,浏览器会有一个轻量级HTML(或CSS)扫描器(scanner)继续在文档中扫描,查找那些将来可能能够用到的resource资源文件url,在渲染器使用它们之前将其下载下来。
断开连接,TCP四次挥手
(由浏览器发起,告诉服务器,我响应报文接受完了,我准备关闭了,你也准备吧)
(由浏览器发起,告诉服务器,我响应报文接受完了,我准备关闭了,你也准备吧)
1) 发起方向被动方发送报文,Fin、Ack、Seq,表示已经没有数据传输了。并进入 FIN_WAIT_1 状态。(第一次挥手:由浏览器发起的,发送给服务器,我请求报文发送完了,你准备关闭吧)
2) 被动方发送报文,Ack、Seq,表示同意关闭请求。此时主机发起方进入 FIN_WAIT_2 状态。(第二次挥手:由服务器发起的,告诉浏览器,我请求报文接受完了,我准备关闭了,你也准备吧)
3) 被动方向发起方发送报文段,Fin、Ack、Seq,请求关闭连接。并进入 LAST_ACK 状态。(第三次挥手:由服务器发起,告诉浏览器,我响应报文发送完了,你准备关闭吧)
4) 发起方向被动方发送报文段,Ack、Seq。然后进入等待 TIME_WAIT 状态。被动方收到发起方的报文段以后关闭连接。发起方等待一定时间未收到回复,则正常关闭。(第四次挥手:由浏览器发起,告诉服务器,我响应报文接受完了,我准备关闭了,你也准备吧)
为什么是四次挥手
服务端收到FIN请求后要先应答ACK,
服务器等等请求报文接收完毕 和 响应报文发送完毕在通知浏览器关闭
TCP断开连接为什么比建立连接多一个步骤呢?其实很简单,因为刚开始时候还没有共同财产,但是分手的时候还需要分东西呀
服务端收到FIN请求后要先应答ACK,
服务器等等请求报文接收完毕 和 响应报文发送完毕在通知浏览器关闭
TCP断开连接为什么比建立连接多一个步骤呢?其实很简单,因为刚开始时候还没有共同财产,但是分手的时候还需要分东西呀
简单版浏览器渲染流程
- 浏览器输入url,浏览器主进程接管,开一个下载线程,
然后进行 http请求(略去DNS查询,IP寻址等等操作),然后等待响应,获取内容,
随后将内容通过RendererHost接口转交给Renderer进程
- 浏览器渲染流程开始
常见兼容性问题
渐进增强,优雅降级
浏览器内核
1、IE浏览器内核:Trident内核,也被称为IE内核;
2、Chrome浏览器内核:
Chromium内核 → Webkit内核 → Blink内核;
Chromium内核 → Webkit内核 → Blink内核;
4、Safari浏览器内核:Webkit内核;
3、Firefox浏览器内核:Gecko内核,也被称Firefox内核;
5、Opera浏览器内核:最初是自主研发的Presto内核,后跟随谷歌,从Webkit到Blink内核;
6、360浏览器、猎豹浏览器内核:IE+Chrome双内核;
7、搜狗、遨游、QQ浏览器内核:Trident(兼容模式)+Webkit(高速模式);
8、百度浏览器、世界之窗内核:IE内核;
浏览器运行机制(JS运行机制、渲染进程)
进程和线程的关系
进程是资源分配的基本单位,线程是CPU独立运行和独立调度的基本单位
(可以理解为一个进程中执行的代码片段)
(可以理解为一个进程中执行的代码片段)
浏览器的渲染进程是多线程的
JS单线程指的是浏览器中负责解释和执行JS代码的只有一个线程,即为 JS引擎线程
一个进程下面的线程是可以去通信的,共享资源
进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程没有单独的地址空间,一个线程死掉就等于整个进程死掉。
每一个tab页面可以看作是浏览器内核进程
GUI线程
负责html/css构建render tree页面渲染、reflow、repaint
负责html/css构建render tree页面渲染、reflow、repaint
负责渲染浏览器界面,解析HTML,CSS,构建DOM树和RenderObject树,布局和绘制等。
当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行
注意,GUI渲染线程与JS引擎线程是互斥的,当JS引擎执行时GUI线程会被挂起(相当于被冻结了),GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行。
JS引擎线程(V8引擎)
负责处理javascript脚本
负责处理javascript脚本
JS为处理页面中用户的交互,负责处理Javascript脚本程序
这个引擎主要由两个部分组成,Memory Heap 和 Call Stack,即内存堆和调用栈。(只负责取消息,不负责生产消息)
内存堆:进行内存分配。如变量赋值。
调用栈:这是代码在栈帧中执行的地方。调用栈中顺序执行主线程的代码,当调用栈中为空时,js引擎会去消息队列取消息,取到后就执行。
内存堆:进行内存分配。如变量赋值。
调用栈:这是代码在栈帧中执行的地方。调用栈中顺序执行主线程的代码,当调用栈中为空时,js引擎会去消息队列取消息,取到后就执行。
当遇到计时器、DOM事件监听或者是网络请求的任务时,JS引擎会将它们直接交给WebAPI,
也就是浏览器提供的相应线程(如定时器线程为setTimeout计时、异步http请求线程处理网络请求)去处理,
而JS引擎线程继续后面的其他任务,这样便实现了 异步非阻塞。
定时器触发线程也只是为 setTimeout(..., 1000) 定时而已,时间一到,还会把它对应的回调函数(callback)交给 消息队列 去维护,
JS引擎线程会在适当的时候去消息队列取出消息并执行。这里,JavaScript 通过 事件循环 event loop 的机制来解决这个问题
也就是浏览器提供的相应线程(如定时器线程为setTimeout计时、异步http请求线程处理网络请求)去处理,
而JS引擎线程继续后面的其他任务,这样便实现了 异步非阻塞。
定时器触发线程也只是为 setTimeout(..., 1000) 定时而已,时间一到,还会把它对应的回调函数(callback)交给 消息队列 去维护,
JS引擎线程会在适当的时候去消息队列取出消息并执行。这里,JavaScript 通过 事件循环 event loop 的机制来解决这个问题
GUI渲染线程与JS引擎线程互斥的,是由于JavaScript是可操纵DOM的,如果在修改这些元素属性同时渲染界面(即JavaScript线程和UI线程同时运行),
那么渲染线程前后获得的元素数据就可能不一致。
当JavaScript引擎执行时GUI线程会被挂起,
GUI更新会被保存在一个队列中等到引擎线程空闲时立即被执行。
由于GUI渲染线程与JS执行线程是互斥的关系,
因此如果JS执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞的感觉。
那么渲染线程前后获得的元素数据就可能不一致。
当JavaScript引擎执行时GUI线程会被挂起,
GUI更新会被保存在一个队列中等到引擎线程空闲时立即被执行。
由于GUI渲染线程与JS执行线程是互斥的关系,
因此如果JS执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞的感觉。
事件触发线程
负责管理事件循环和任务队列
只要异步任务有了运行结果,就在任务队列之中放置一个事件
负责管理事件循环和任务队列
只要异步任务有了运行结果,就在任务队列之中放置一个事件
事件循环 机制和 消息队列 的维护是由事件触发线程控制的。事件触发线程 同样是浏览器渲染引擎提供的,
它会维护一个 消息队列。然后由事件触发线程将异步对应的回调函数加入到消息队列中,等待JS引擎的处理。
它会维护一个 消息队列。然后由事件触发线程将异步对应的回调函数加入到消息队列中,等待JS引擎的处理。
这些事件可以是当前执行的代码块如定时任务、也可来自浏览器内核的其他线程如 鼠标点击、AJAX异步请求等,
但由于JS的单线程关系所有这些事件都得排队等待JS引擎处理。
但由于JS的单线程关系所有这些事件都得排队等待JS引擎处理。
事件触发是异步的,但它们的处理程序在调用堆栈上同步执行。
事件线程与宏任务微任务之间的关系
macrotask中的事件都是放在一个事件队列中的,而这个队列由事件触发线程维护
microtask中的所有微任务都是添加到微任务队列(Job Queues)中,等待当前macrotask执行完毕后执行,而这个队列由JS引擎线程维护
定时器线程
setInterval与setTimeout所在线程
负责计时
setInterval与setTimeout所在线程
负责计时
浏览器定时计数器并不是由JS引擎计数的, 因为JS引擎是单线程的, 如果处于阻塞线程状态就会影响记计时的准确,
因此通过单独线程来计时并触发定时是更为合理的方案。
因此通过单独线程来计时并触发定时是更为合理的方案。
为什么有时候setTimeout推入的事件不能准时执行?因为可能在它推入到事件列表时,主线程还不空闲,正在执行其它代码,
setTimeout中低于4ms的时间间隔算为4ms
setTimeout而不是setInterval
setInterval有一些比较致命的问题就是:
累计效应(上面提到的),如果setInterval代码在(setInterval)再次添加到队列之前还没有完成执行,
就会导致定时器代码连续运行好几次,而之间没有间隔。
就算正常间隔执行,多个setInterval的代码执行时间可能会比预期小(因为代码执行需要一定时间)
目前一般认为的最佳方案是:用setTimeout模拟setInterval,或者特殊场合直接用requestAnimationFrame
补充:JS高程中有提到,JS引擎会对setInterval进行优化,如果当前事件队列中有setInterval的回调,不会重复添加。
累计效应(上面提到的),如果setInterval代码在(setInterval)再次添加到队列之前还没有完成执行,
就会导致定时器代码连续运行好几次,而之间没有间隔。
就算正常间隔执行,多个setInterval的代码执行时间可能会比预期小(因为代码执行需要一定时间)
目前一般认为的最佳方案是:用setTimeout模拟setInterval,或者特殊场合直接用requestAnimationFrame
补充:JS高程中有提到,JS引擎会对setInterval进行优化,如果当前事件队列中有setInterval的回调,不会重复添加。
HTTP请求线程
每建立一个请求建立一个线程
每建立一个请求建立一个线程
在XMLHttpRequest在连接后是通过浏览器新开一个线程请求
将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将这个回调再放入事件队列中。再由JavaScript引擎执行。
各线程之间的关系
事件循环机制
JS引擎解析过程
JS的解释阶段
JS是解释型语音,所以它无需提前编译,而是由解释器实时运行
引擎对JS的处理过程
1. 读取代码,进行词法分析(Lexical analysis),然后将代码分解成词元(token)
2. 对词元进行语法分析(parsing),然后将代码整理成语法树(syntax tree)
3. 使用翻译器(translator),将代码转为字节码(bytecode)
4. 使用字节码解释器(bytecode interpreter),将字节码转为机器码
最终计算机执行的就是机器码。
为了提高运行速度,现代浏览器一般采用即时编译(JIT-Just In Time compiler)
即字节码只在运行时编译,用到哪一行就编译哪一行,并且把编译结果缓存(inline cache)
这样整个程序的运行速度能得到显著提升。
而且,不同浏览器策略可能还不同,有的浏览器就省略了字节码的翻译步骤,直接转为机器码(如chrome的v8)
总结起来可以认为是: 核心的JIT编译器将源码编译成机器码运行
读取代码->词法分析->语法分析->字节码(chrome v8忽略这步)->机器码->运行
为了提高运行速度,现代浏览器一般采用即时编译(JIT-Just In Time compiler)
即字节码只在运行时编译,用到哪一行就编译哪一行,并且把编译结果缓存(inline cache)
这样整个程序的运行速度能得到显著提升。
而且,不同浏览器策略可能还不同,有的浏览器就省略了字节码的翻译步骤,直接转为机器码(如chrome的v8)
总结起来可以认为是: 核心的JIT编译器将源码编译成机器码运行
读取代码->词法分析->语法分析->字节码(chrome v8忽略这步)->机器码->运行
JS的预处理阶段
譬如变量提升
一般包括函数提升和变量提升
分号补全等
JS的执行阶段
执行上下文,执行堆栈概念
(如全局上下文,当前活动上下文)
(如全局上下文,当前活动上下文)
执行上下文简单解释
JS有执行上下文)
浏览器首次载入脚本,它将创建全局执行上下文,并压入执行栈栈顶(不可被弹出)
然后每进入其它作用域就创建对应的执行上下文并把它压入执行栈的顶部
一旦对应的上下文执行完毕,就从栈顶弹出,并将上下文控制权交给当前的栈。
这样依次执行(最终都会回到全局执行上下文)
每一个执行上下文,都有三个重要属性:
变量对象(Variable object,VO)
作用域链(Scope chain)
this
VO(变量对象)和AO(活动对象)
VO中会存放一些变量信息
(如声明的变量,函数,arguments参数等等)
VO中会存放一些变量信息
(如声明的变量,函数,arguments参数等等)
VO是执行上下文的属性(抽象概念),但是只有全局上下文的变量对象允许通过VO的属性名称来间接访问(因为在全局上下文里,全局对象自身就是变量对象)
AO(activation object),当函数被调用者激活,AO就被创建了,局部变量、内部函数、形式参数储存在给定函数的激活对象中。
作用域链
它是执行上下文中的一个属性,原理和原型链很相似,作用很重要。
在函数上下文中,查找一个变量foo
如果函数的VO中找到了,就直接使用
否则去它的父级作用域链中(__parent__)找
如果父级中没找到,继续往上找
直到全局上下文中也没找到就报错
this机制等
this是没有一个类似搜寻变量的过程
当代码中使用了this,这个 this的值就直接从执行的上下文中获取了,而不会从作用域链中搜寻
this的值只取决中进入上下文时的情况
就要明白了上面this的介绍,上述例子很好理解
var baz = 200;
var bar = {
baz: 100,
foo: function() {
console.log(this.baz);
}
};
var foo = bar.foo;
// 进入环境:global
foo(); // 200,严格模式中会报错,Cannot read property 'baz' of undefined
// 进入环境:global bar
bar.foo(); // 100
var baz = 200;
var bar = {
baz: 100,
foo: function() {
console.log(this.baz);
}
};
var foo = bar.foo;
// 进入环境:global
foo(); // 200,严格模式中会报错,Cannot read property 'baz' of undefined
// 进入环境:global bar
bar.foo(); // 100
this取决于调用函数的方式。当调用括号的左边不是引用类型而是其它类型,这个值自动设置为null,结果为全局对象
window
navgator
navigator.appName:浏览器名称;
navigator.appVersion:浏览器版本;
navigator.language:浏览器设置的语言;
navigator.platform:操作系统类型;
navigator.userAgent -- 返回用户代理头的字符串表示(就是包括浏览器版本信息等的字符串)
navigator.cookieEnabled -- 返回浏览器是否支持(启用)cookie
screen
screen.width:屏幕宽度,以像素为单位;
screen.height:屏幕高度,以像素为单位;
screen.colorDepth:返回颜色位数,如8、16、24
location
location.protocol; // 'http'
location.host; // 'www.example.com'
location.port; // '8080'
location.pathname; // '/path/index.html'
location.search; // '?a=1&b=2'
location.hash; // 'TOP'
Dom
添加/删除节点
document.createElement(element) 创建 HTML 元素
document.removeChild(element) 删除 HTML 元素
document.appendChild(element) 添加 HTML 元素
document.replaceChild(element) 替换 HTML 元素
document.write(text) 写入 HTML 输出流
查找 HTML 元素
document.getElementById(id) 通过元素 id 来查找元素
document.getElementsByTagName(name) 通过标签名来查找元素
document.getElementsByClassName(name) 通过类名来查找元素
document.querySelectorAll("p.intro") CSS 选择器查找 HTML 元素
parentNode
childNodes[nodenumber]
firstChild
lastChild
nextSibling
previousSibling
改变 HTML 元素
element.innerHTML = new html content 改变元素的 inner HTML
element.attribute = new value 改变 HTML 元素的属性值
element.setAttribute(attribute, value) 改变 HTML 元素的属性值
element.style.property = new style 改变 HTML 元素的样式
添加事件处理程序
document.getElementById(id).onclick = function(){code} 向 onclick 事件添加事件处理程序
history
history.go() -- 前进或后退指定的页面数 history.go(num);
history.back() -- 后退一页
history.forward() -- 前进一页
replaceState会替换当前的history中的记录,并且不刷新浏览器
history.replaceState({}, "title", "/test.html")
history.replaceState({}, "title", "/test.html")
pushState只会在当前history中添加一条记录,并不会刷新浏览器
history.pushState({}, "title", "/test.html")
history.pushState({}, "title", "/test.html")
Promise
手写promise
Promise().then().then....catch() 多任务串行执行.
Promise.all([p1,p2,...]) 多任务并行执行
Promise.race([p1,p2,...]) 多任务赛跑.
node
nodejs事件循环
在浏览器或者nodejs环境中,运行时对js脚本的调度方式就叫做事件循环。
6个阶段
timers(定时器) : 此阶段执行那些由 `setTimeout()` 和 `setInterval()` 调度的回调函数.
I/O callbacks(I/O回调) : 此阶段会执行几乎所有的回调函数, 除了 close callbacks(关闭回调) 和 那些由 timers 与 setImmediate()调度的回调
.setImmediate 约等于 setTimeout(cb,0)
.setImmediate 约等于 setTimeout(cb,0)
idle(空转), prepare : 此阶段只在内部使用
poll(轮询): 检索新的I/O事件; 在恰当的时候Node会阻塞在这个阶段
(其中poll阶段除了执行当前阶段的队列里的所有callback之外,
poll还负责检测是否有timer的callback,如timer到达时间,并且timer的callback还未执行,
那么就循环至开头执行timer的callback
(其中poll阶段除了执行当前阶段的队列里的所有callback之外,
poll还负责检测是否有timer的callback,如timer到达时间,并且timer的callback还未执行,
那么就循环至开头执行timer的callback
check(检查) : `setImmediate()` 设置的回调会在此阶段被调用
close callbacks(关闭事件的回调): 诸如 `socket.on('close', ...)` 此类的回调在此阶段被调用
阶段执行规则
当事件循环进入到某一个阶段,其会执行该阶段的任何操作。直到这个阶段的任务队列为空,或者是回调函数的调用次数达到上限。此时事件循环将会到下一个阶段。
当我们在事件循环的某个阶段执行某个回调时候,该回调可能会生成更多的回调。这些回调都是被添加到对应阶段的队列中。因此,长时间运行的回调可以允许轮询阶段的运行时间远远超过计时器的阀值
当我们在事件循环的某个阶段执行某个回调时候,该回调可能会生成更多的回调。这些回调都是被添加到对应阶段的队列中。因此,长时间运行的回调可以允许轮询阶段的运行时间远远超过计时器的阀值
poll轮询阶段
功能
计算应该阻塞和轮询 I/O 的时间。
处理轮询队列里的事件。
进入轮询阶段,且代码未设定timer
如果轮询队列不是空的
那么事件循环将循环访问回调队列并同步执行它们,直到清空队列,或者达到了最大限制
如果轮询队列是空的(这时可能会阻塞)
有没有setImmediate() 调度
有:那么事件循环将结束轮询阶段,并到check阶段以执行那些被调度的代码。
没有:event loop将阻塞在该阶段等待callbacks加入poll queue,一旦到达就立即执行
进入轮询阶段且一旦队列为空
在轮询阶段的执行过程中,一旦轮询队列为空,事件循环将检查是否有到期的定时器settimeout或setintval。
如果一个或多个定时器已准备就绪,则事件循环将绕回timer阶段以执行这些定时器的回调。
如果一个或多个定时器已准备就绪,则事件循环将绕回timer阶段以执行这些定时器的回调。
setImmediate() 与 setTimeout()执行顺序
setImmediate()处于check阶段,设计用于在当前轮询阶段后执行脚本
setTimeout() 安排在经过最小阀值后执行的脚本
setTimeout(function timeout () {
console.log('timeout');
},0);
setImmediate(function immediate () {
console.log('immediate');
});
//setImmediate和setTimeout执行顺序不确定
受程序启动时间限制。设置的 delay 是 0,但其实是1,就经过了check阶段,直接进入下一个timer阶段,所以有时setImmediate先执行,有时setTimeout,
console.log('timeout');
},0);
setImmediate(function immediate () {
console.log('immediate');
});
//setImmediate和setTimeout执行顺序不确定
受程序启动时间限制。设置的 delay 是 0,但其实是1,就经过了check阶段,直接进入下一个timer阶段,所以有时setImmediate先执行,有时setTimeout,
执行定时器的顺序将根据调用他们的上下文有所不同,
如果是从主模块中调用两者,那么时间将会受到进程性能的限制;
如果这两者运行在非I/O周期内,这两者的顺序无法保证,
如果是在I/O周期内的话,setImmediate()与优先于setTimeout()
如果是从主模块中调用两者,那么时间将会受到进程性能的限制;
如果这两者运行在非I/O周期内,这两者的顺序无法保证,
如果是在I/O周期内的话,setImmediate()与优先于setTimeout()
nodeJS中. settimeout(fn,1) === setTimeout(fn,0)
微任务优先级:porcess.nextTick() > Promise.then()
nodejs多进程
nodejs单线程,在处理http请求的时候一个错误都会导致整个进程的退出,这是灾难级的。
总结: 线程快而进程可靠性高
**1)需要频繁创建销毁的优先用线程**
这种原则最常见的应用就是Web服务器了,来一个连接建立一个线程,断了就销毁线程,要是用进程,创建和销毁的代价是很难承受的
**2)需要进行大量计算的优先使用线程**
所谓大量计算,当然就是要耗费很多CPU,切换频繁了,这种情况下线程是最合适的。
这种原则最常见的是图像处理、算法处理。
**3)强相关的处理用线程,弱相关的处理用进程**
什么叫强相关、弱相关?理论上很难定义,给个简单的例子就明白了。
一般的Server需要完成如下任务:消息收发、消息处理。“消息收发”和“消息处理”就是弱相关的任务,而“消息处理”里面可能又分为“消息解码”、“业务处理”,这两个任务相对来说相关性就要强多了。因此“消息收发”和“消息处理”可以分进程设计,“消息解码”、“业务处理”可以分线程设计。
**4)可能要扩展到多机分布的用进程,多核分布的用线程**
**5)都满足需求的情况下,用你最熟悉、最拿手的方式
**1)需要频繁创建销毁的优先用线程**
这种原则最常见的应用就是Web服务器了,来一个连接建立一个线程,断了就销毁线程,要是用进程,创建和销毁的代价是很难承受的
**2)需要进行大量计算的优先使用线程**
所谓大量计算,当然就是要耗费很多CPU,切换频繁了,这种情况下线程是最合适的。
这种原则最常见的是图像处理、算法处理。
**3)强相关的处理用线程,弱相关的处理用进程**
什么叫强相关、弱相关?理论上很难定义,给个简单的例子就明白了。
一般的Server需要完成如下任务:消息收发、消息处理。“消息收发”和“消息处理”就是弱相关的任务,而“消息处理”里面可能又分为“消息解码”、“业务处理”,这两个任务相对来说相关性就要强多了。因此“消息收发”和“消息处理”可以分进程设计,“消息解码”、“业务处理”可以分线程设计。
**4)可能要扩展到多机分布的用进程,多核分布的用线程**
**5)都满足需求的情况下,用你最熟悉、最拿手的方式
node相关api
Process 进程
属性
1. **process.argv** 属性,返回一个数组,包含了启动 node 进程时的命令行参数
2. **process.env** 返回包含用户环境信息的对象,可以在 脚本中对这个对象进行增删改查的操作
3. **process.pid** 返回当前进程的进程号
4. **process.platform** 返回当前的操作系统
5. **process.version** 返回当前 node 版本
方法
1. **process.cwd()** 返回 node.js 进程当前工作目录
2. process.chdir() 变更 node.js 进程的工作目录
3. **process.nextTick(fn)** 将任务放到当前事件循环的尾部,添加到 ‘next tick’ 队列,一旦当前事件轮询队列的任务全部完成,在 next tick 队列中的所有 callback 会被依次调用
4. **process.exit()** 退出当前进程,很多时候是不需要的
5. process.kill(pid[,signal]) 给指定进程发送信号,包括但不限于结束进程
事件
1. beforeExit 事件,在 Node 清空了 EventLoop 之后,再没有任何待处理任务时触发,可以在这里再部署一些任务,使得 Node 进程不退出,显示的终止程序时(process.exit()),不会触发
2. exit 事件,当前进程退出时触发,回调函数中只允许同步操作,因为执行完回调后,进程金辉退出
(重要‼️)错误捕获. uncaughtException 事件,当前进程抛出一个没有捕获的错误时触发,可以用它在进程结束前进行一些已分配资源的同步清理操作,尝试用它来恢复应用的正常运行的操作是不安全的
4. warning 事件,任何 Node.js 发出的进程警告,都会触发此事件
child_process 子进程
exec():执行shell,并返回输出
execSync(). exec()的同步版本
execFile(),参数有区别的exec
execFile方法直接执行特定的程序shell,参数作为数组传入,不会被bash解释,因此具有较高的安全性。
spawn 和execFile类似,但没有回调函数,只能通过监听事件,来获取运行结果。
fork() 用于进程之间的通信,也是IPC通信的基础
Clusternode进行多进程的模块
属性和方法
1. **isMaster** 属性,返回该进程是不是主进程
2. **isWorker** 属性,返回该进程是不是工作进程
3. **fork()** 方法,只能通过主进程调用,衍生出一个新的 worker 进程,返回一个 worker 对象。和process.child的区别,不用创建一个新的child.js
4. setupMaster([settings]) 方法,用于修改 fork() 默认行为,一旦调用,将会按照cluster.settings进行设置。
5. settings 属性,用于配置,参数 exec: worker文件路径;args: 传递给 worker 的参数;execArgv: 传递给 Node.js 可执行文件的参数列表
事件
1. **fork** 事件,当新的工作进程被 fork 时触发,可以用来记录工作进程活动
2. **listening** 事件,当一个工作进程调用 listen() 后触发,事件处理器两个参数 worker:工作进程对象
3. **message**事件, 比较特殊需要去在单独的worker上监听。
4. online 事件,复制好一个工作进程后,工作进程主动发送一条 online 消息给主进程,主进程收到消息后触发,回调参数 worker 对象
5. **disconnect** 事件,主进程和工作进程之间 IPC 通道断开后触发
6. **exit** 事件,有工作进程退出时触发,回调参数 worker 对象、code 退出码、signal 进程被 kill 时的信号
7. setup 事件,cluster.setupMaster() 执行后触发
**cluster多进程模型**
Node.js 的适用场景
1. 高并发
2. 聊天
3. 实时消息推送
优点:Nodejs 是单线程,⾮阻塞 I/O,事件驱动,它的特点决定了它适合做⼀些⼤量 I/O 的东⻄,⽐
如,聊天室,表单提交等不需要⼤量计算的功能。做⼀些微信后端开发,或者做消息系统等。可以整个
项⽬⽤, 也可以根据它的特点在某个模块使⽤,⽐如 socketio,打造⼀个消息系统等。
如,聊天室,表单提交等不需要⼤量计算的功能。做⼀些微信后端开发,或者做消息系统等。可以整个
项⽬⽤, 也可以根据它的特点在某个模块使⽤,⽐如 socketio,打造⼀个消息系统等。
Nodejs 中的 Stream 和 Buffer 有什么区别
Buffer:为数据缓冲对象,是⼀个类似数组结构的对象,可以通过指定开始写⼊的位置及写⼊的
数据⻓度,往其中写⼊⼆进制数据。Stream:是对 buffer 对象的⾼级封装,其操作的底层还是 buffer
对象, stream 可以设置为可读、可写,或者即可读也可写,在 nodejs 中继承了 EventEmitter 接⼝,
可以监听读⼊、写⼊的过程。具体实现有⽂件流,httpresponse 等。
Express 和 koa和egg 有什么关系
1、express和koa都是基于nodejs的web开发框架,都可以作为HTTP Server或者TCP Server使⽤
2、express是⼀个快速,开发,极简的web开发框架。
3、并且koa是express原版⼈⻢打造。但是koa致⼒于成为⼀个更⼩、更富有表现⼒、更健壮的 Web 框架,
4、Egg 选择了 Koa 作为其基础框架,在它的模型基础上,进一步对它进行了一些增强,是一个开箱即用的web框架
2、express是⼀个快速,开发,极简的web开发框架。
3、并且koa是express原版⼈⻢打造。但是koa致⼒于成为⼀个更⼩、更富有表现⼒、更健壮的 Web 框架,
4、Egg 选择了 Koa 作为其基础框架,在它的模型基础上,进一步对它进行了一些增强,是一个开箱即用的web框架
内置对象
express的四⼤模块为application、request、response、router;
koa也是四个模块,分别是Application、Request、Response、Context。封装请求和响应
koa也是四个模块,分别是Application、Request、Response、Context。封装请求和响应
中间件模型不同
Express的中间件是线型的
Koa 的中间件是U型的(洋葱模型)
对语言特性的使用不同
express使用回调函数next(),基于ES5语法
koa v1.x 使用generator 语法
koa v2.x 使用async/await 语法
没有内置中间件
Koa API
基本和Express一样
Koa API
基本和Express一样
基本和Express一样
egg
Egg 的插件机制有很高的可扩展性,一个插件只做一件事,Egg 奉行『约定优于配置』,按照[一套统一的约定](https://eggjs.org/zh-cn/advanced/loader.html)进行应用开发,团队内部采用这种方式可以减少开发人员的学习成本,开发人员不再是『钉子』,可以流动起来。
Egg 选择了 Koa 作为其基础框架,在它的模型基础上,进一步对它进行了一些增强
前端缓存
HTTP缓存
强缓存200 from cache
cache-contorl(max-age相对时间)
expires
过期时间
协商缓存304
Etag/if-none-Match
文件标识
last-modify(响应头)/if-modify-since(请求头)
文件更新最后时间,只能精确到1s以内
浏览器缓存
本地缓存
webStorage
localStorage
SessionStorage
Cookie
websql
indexDB
应用缓存,Application Cache
PWA(渐进式页面应用)
主要功能
web app Manifest(实现添加至主屏幕)
service worker实现离线缓存(主要功能)
push notification(实现消息推送)
移动端
移动端适配方案
rem是相对于HTML的根元素em相对于父级元素的字体大小。VW,VH 屏幕宽度高度的高分比
app与H5 如何通讯交互的?
3. 应该避免使⽤数组索引作为key,这可能导致⼀些隐蔽的bug;(例如当删除数组中某个值时,这时候它和它后面的index变了,但是vue并不能知道哪一个值被删除,key对应index,所以它和后面的所有vdom 都会重新渲染,没有重复利用。如果使用唯一id作为key,vue可以准确找到那个节点被删除,直接删除vdom就好了,其他直接复用)
diff算法
分层diff:不考虑跨层级移动节点,让新旧两个vdom树对比无需循环递归
同层比较,深度优先:双端比较算法(首首,尾尾,首尾,尾首)比较,按照不同情况可以找到节点是新增、删除、还是移动都情况,这过程还会结合key唯一标识来比较,能将复杂度大幅降底
痛点和vue3优化
痛点:vue2都虚拟dom是全量比较的,在运行时会对所有节点生成一个虚拟节点树,当页面数据发生变更,会遍历判断vdom树所有节点,包括一些不会变的节点,虽然diff算法已经减少了很多dom节点的操作。但是如果是复杂的大型项目,其中会包含很多递归的patchVNode,最终造成VNode更新更缓慢
vue3的动静结合,在patch过程采用位运算来判断VNode类型以及通过静态标记,在模版编译时(生成VNode时打标记),就将一些动态标签末尾加上标记,在这基础上再配合diff算法,对于不参与更新的元素做静态标记,只会被创建一次,渲染的时候直接复用,
0 条评论
下一页