前端复习资料
落叶的位置,谱出一首诗,时间在消逝,我们的故事。
这篇文章呢,整理写给需要的前端同学的。 核心知识,必须掌握的,也是最基础的,譬如浏览器模型,渲染原理,JS解析过程,JS运行机制等,作为骨架来承载知识体系
Vue路由
vue页面跳转会经过两个钩子函数beforeEach、afterEach
组见守卫
beforeRouteEnter前置组见守卫
**beforeRouteUpdate*更新之前
watch和computed区别
数据没有改变,则 computed 不会重新计算)。若没改变,计算属性会立即返回之前缓存的计算结果。
不支持异步,当 computed 内有异步操作时无效,无法监听数据的变化的值。
watch 支持异步
当一个属性发生变化时,需要执行对应的操作;一对多时,一般用 watch。
不支持缓存,数据变或者触发重新渲染时,直接会触发相应的操作。
值类型(基本类型):字符串(String)、数字(Number)、布尔(Boolean)、对空(Null)、未定义(Undefined)、Symbol。
引用数据类型:对象(Object)、数组(Array)、函数(Function)。
防抖和节流
- 节流: n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效
- 防抖: n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时
继承方式
- 原型链继承
- 构造函数继承(借助 call)
- 组合继承
- 原型式继承
- 寄生式继承
- 寄生组合式继承
宏任务和微任务
宏任务(macrotask) | 微任务(microtask) | |
---|---|---|
谁发起的 | 宿主(Node、浏览器) | JS引擎 |
具体事件 | 1. script (可以理解为外层同步代码) 2. setTimeout/setInterval 3. UI rendering/UI事件 4. postMessage,MessageChannel 5. setImmediate,I/O(Node.js) | 1. 2. MutaionObserver 3. Object.observe(已废弃;Proxy 对象替代) 4. process.nextTick(Node.js) |
谁先运行 | 后运行 | 先运行 |
会触发新一轮Tick吗 | 会 | 不会 |
SSR和SPA区别
服务端渲染SSR和客户端渲染SPA(单页面应用)的区别:
SSR (Server Side Render):最终HTML文件由服务端来生成,然后浏览器直接下载进行显示。支持SSR的框架有基于React的Next和基于Vue的Nuxt。
SPA (Single Page Application):浏览器下载的是整个单页面应用的相关代码(框架代码、业务代码)资源,最终HTML文件通过js代码在浏览器上生成。三大框架Angular、Vue、React均为SPA。
SSR主要解决SPA首屏渲染慢以及不利于SEO的问题,其优势为:
首屏渲染快。SPA会一次性下载整个单页面应用的所有代码和资源,造成首屏渲染缓慢,而SSR则对每个页面单独进行服务端渲染的HTML文件,这样访问什么页面就下载什么页面,并且由于直接下载HTML文件,文件容量相对较小,所以可以被更快地加载好,给带宽较差的访问者带来速度上的提升。
支持SEO。SPA在直到浏览器运行其JS代码前页面的数据是未知的,因此无法被爬虫,而SSR由于服务端直接输出HTML文件,因此可以解决这个问题。一些非最优的解决方案:如取巧地将某些需要SEO的页面进行预渲染,或者判断访问来源是爬虫时,才输出服务端渲染的html,否则输出SPA,但是这样做也有弊端比如爬虫抓取的数据可能和真实访问数据不一致等等。
SPA与SSR选择:
如果采取前后端分离开发,SPA效率更高,无需后端人员操心前端内容。相反,如果采用SSR,则需要后端人员也对前端的技能有一定掌握,并且前端人员做好页面后需要由后端人员来进行修改。
如果对SEO有需求,可采用SSR的方法。
总结:若无SEO需求,且前后端分离开发,首选还是SPA。而在需要加速首屏渲染时,可以采用SSR的方法或者webpack的方法等等。若需要SEO,则采用SSR或对部分页面预渲染的方法。Nuxt是一个Node程序,这是由于我们要把Vue在服务端上运行,需要Node环境。当我们访问Nuxt应用时,实际上在访问Node程序的路由,程序输出为首屏渲染内容+用于重新渲染的SPA的脚本代码。其中,路由是由Nuxt中定义好的pages文件夹来生成。
1、SSR渲染的优势:
(1)更利于SEO;
(2)更利于首屏渲染(特别是对于缓慢的网络情况或运行缓慢的设备,内容更快到达)
2、SSR渲染的缺点:
(1)服务器压力大,考虑服务器端负载。
(2)开发条件受限,只会执行到ComponentMount之前的生命周期钩子,引用第三方库不可用其他生命周期钩子,引用库选择产生很大的限制。
(3)学习成本增大,需要学习构建设置和部署的更多要求。
怎样判断对象是否为空
把对象转换为数组来进行判断
父子组件的执行顺序为,
父组件beforeCreated ->父组件created ->父组件beforeMounted ->子组件beforeCreated ->子组件created ->子组件beforeMounted ->子组件mounted -> 父组件mounted
为什么用set
由于 Vue 会在初始化实例时进行双向数据绑定,使用Object.defineProperty()对属性遍历添加 getter/setter 方法,所以属性必须在 data 对象上存在时才能进行上述过程 ,这样才能让它是响应的。如果要给对象添加新的属性,此时新属性没有进行过上述过程,不是响应式的,所以会出想数据变化,页面不变的情况。此时需要用到$set。
任务队列
“ 任务队列 " 是一个先进先出的数据结构,排在前面的事件,优先被主线程读取。主线程的读取过程基本上是自动的,只要执行栈一清空," 任务队列 " 上第一位的事件就会自动进入主线程。
所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。
同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务
异步任务指的是,不进入主线程、而进入 " 任务队列 "(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
JS如何实现异步操作
JS 的异步是通过回调函数实现的,即通过任务队列,在主线程执行完当前的任务栈(所有的同步操作),主线程空闲后轮询任务队列,并将任务队列中的任务(回调函数)取出来执行。
" 回调函数 "(callback),就是那些会被主线程挂起来的代码。异步任务必须指定回调函数,当主线程开始执行异步任务,就是执行对应的回调函数。
虽然 JS 是单线程的,但是浏览器的内核是多线程的,在浏览器的内核中不同的异步操作由不同的浏览器内核模块调度执行,异步操作会将相关回调添加到任务队列中。而不同的异步操作添加到任务队列的时机也不同,如 onclick, setTimeout,ajax 处理的方式都不同,这些异步操作是由浏览器内核的 webcore 来执行的,webcore 包含上图中的3种 webAPI,分别是 DOM Binding、network、timer模块。
onclick 由浏览器内核的 DOM Binding 模块来处理,当事件触发的时候,回调函数会立即添加到任务队列中。
setTimeout 会由浏览器内核的 timer 模块来进行延时处理,当时间到达的时候,才会将回调函数添加到任务队列中。
ajax 则会由浏览器内核的 network 模块来处理,在网络请求完成返回之后,才将回调函数添加到任务队列中。
异步执行机制
所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
主线程之外,还存在一个 " 任务队列 "(task queue)。只要异步任务有了运行结果,就在 " 任务队列 " 之中放置一个事件。
一旦 " 执行栈 " 中的所有同步任务执行完毕,系统就会读取 " 任务队列 ",看看里面有哪些事件。于是那些对应的异步任务结束等待状态,进入执行栈,开始执行。
主线程不断重复上面的第三步(事件轮询)
JS中事件队列的优先级
在 JS 的ES6 中新增的任务队列(promise)优先级是在事件循环之上的,事件循环每次 tick 后会查看 ES6 的任务队列中是否有任务要执行,也就是 ES6 的任务队列比事件循环中的任务(事件)队列优先级更高。
先执行同步代码再执行异步代码,先执行微任务再执行宏任务
setTimeout( fn, 0 ),它在 " 任务队列 " 的尾部添加一个事件,因此要等到同步任务和 " 任务队列 " 现有的事件都处理完,才会得到执行。
console.log(1)
setTimeout(function () {
console.log(6)
}, 1000)
new Promise(function (resolve, reject) {
console.log(2)
setTimeout(function () {
console.log(3)
}, 500)
resolve()
}).then(function (res) {
console.log(4)
})
console.log(5)
// 1 2 5 4 3 6
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
微观任务队列
微任务:语言标准(ECMAScript)提供,如process.nextTick(node)、Promise、Object.observe、MutationObserver
宏观任务队列
宏任务:由宿主环境提供,比如setTimeout、setInterval、网络请求Ajax、用户I/O、script(整体代码)、UI rendering、setImmediate(node)
call、apply、bind区别
- 三者都可以改变函数的this对象指向。
- 三者第一个参数都是this要指向的对象,如果如果没有这个参数或参数为undefined或null,则默认指向全局window。
- 三者都可以传参,但是apply是数组,而call是参数列表,且apply和call是一次性传入参数,而bind可以分为多次传入。
- bind 是返回绑定this之后的函数,便于稍后调用;apply 、call 则是立即执行 。
query和params区别
区别:
1.首先就是写法得不同,query 得写法是 用 path 来编写传参地址,而 params 得写法是用 name 来编写传参地址,你可以看一下编写路由时候得相关属性,你也可以输出一下 路由对象信息 看一下
2.接收方法不同, 一个用 query 来接收, 一个用 params 接收 ,总结就是谁发得 谁去接收
3.query 在刷新页面得时候参数不会消失,而 params 刷新页面得时候会参数消失,可以考虑本地存储解决
4.query 传得参数都是显示在url 地址栏当中,而 params 传参不会显示在地址栏
http请求状态码
一般我们看不到,因为表示请求继续
100: 继续请求,前面的一部分内容服务端已经接受到了,正在等待后续内容
101: 请求者已经准备切换协议,服务器
2 开头的都是表示成功,本次请求成功了,只不过不一样的状态码有不一样的含义(语义化)
200: 标准请求成功(一般表示服务端提供的是网页)
201: 创建成功(一般是注册的时候,表示新用户信息已经添加到数据库)
203: 表示服务器已经成功处理了请求,但是返回的信息可能来自另一源
204: 服务端已经成功处理了请求,但是没有任何数据返回
3 开头也是成功的一种,但是一般表示重定向
301: 永久重定向
302: 临时重定向
304: 使用的是缓存的数据
305: 使用代理
4 开头表示客户端出现错误了
400: 请求的语法服务端不认识
401: 未授权(你要登录的网站需要授权登录)
403: 服务器拒绝了你的请求
404: 服务器找不到你请求的 URL
405:本页面拒绝访问
407: 你的代理没有授权
408: 请求超时
410: 你请求的数据已经被服务端永久删除
5 开头的表示服务端出现了错误
500: 服务器内部错误
503: 服务器当前不可用(过载或者维护)
505: 请求的协议服务器不支持
防抖和节流
-
节流: n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效
function throttled(fn, delay) { let timer = null let starttime = Date.now() return function () { let curTime = Date.now() // 当前时间 let remaining = delay - (curTime - starttime) // 从上一次到现在,还剩下多少多余时间 let context = this let args = arguments clearTimeout(timer) if (remaining <= 0) { fn.apply(context, args) starttime = Date.now() } else { timer = setTimeout(fn, remaining); } } }
-
防抖: n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时
function debounce(func, wait, immediate) {
let timeout;
return function () {
let context = this;
let args = arguments;
if (timeout) clearTimeout(timeout); // timeout 不为null
if (immediate) {
let callNow = !timeout; // 第一次会立即执行,以后只有事件执行后才会再次触发
timeout = setTimeout(function () {
timeout = null;
}, wait)
if (callNow) {
func.apply(context, args)
}
}
else {
timeout = setTimeout(function () {
func.apply(context, args)
}, wait);
}
}
}
hash和history区别
hash支持缓存
history不支持缓存需要有后台来配置,来做兼容
Vue-Router路由模式的选择和底层原理
.路由类型
Hash模式:丑,无法使用锚点定位。
History模式:需要后端配合,IE9不兼容(可使用强制刷新处理IE9不兼容)
hash路由===》location.hash切换
windos.onhashchange监听路径的切换
hisory路由==》hisory.pushState切换
Windows.onpopstate监听路径的切换
理解:
Vue.util.defineReactive_route这个工具库把路由的信息变成了响应式数据,而defineReactive()内部就是调用了Object.defineProperty()和依赖收集实现了路由信息的响应式,而Vue的data也是通过这种方式实现响应式。
图中左边五种方式都会触发调用updateRoute这个API,这个API的功能是修改路由中的响应式数据,当响应式数据被改变后就会自动触发router-view的更新,router-view会根据url和路由匹配规则进行相应组件的展示。
hash和History原理
hash模式背后的原理是onhashchange
事件
-
前端路由的底层原理:hashchange 和H5的history API中的popState和replaceState来进行实现的
hash路由===》location.hash切换
windos.onhashchange监听路径的切换
hisory路由==》hisory.pushState切换
Windows.onpopstate监听路径的切换
import和require的区别
import是ES6标准中的模块化解决方案,require是node中遵循CommonJS规范的模块化解决方案。
后者支持动态引入,也就是require(${path}/xx.js)
,前者目前不支持,但是已有提案。
前者是关键词,后者不是。
前者是编译时加载,必须放在模块顶部,在性能上比后者好一些;后者是运行时加载,理论来说放在哪里都可以。
前者采用的是实时绑定方式,即导入和导出的值都指向同一个内存地址,所以导入的值会随着导出值变化;而后者在导出时是指拷贝,就算导出的值变化了,导入的值也不会变化,如果想要更新导入的值,就要重新导入。
前者会编译成require/exports
来执行。
Vue父组件监听子组件值变化
通过this.$emit(‘方法’,监听的值)
vue单页项目常用优化
vue项目是单页应用,项目在第一次加载的时候会将大部分内容都加载进来,故而会导致加载很慢,以下是优化方案:
1.使用cdn加载一些不常变化的文件,比如用到的UI框架,vue脚手架相关的文件 (参见vue项目优化–引入cdn文件)
2.将静态的js,css以及图片放到第三方服务器
3.按需加载路由
4.webpack-parallel-uglify-plugin 可以优化打包js文件
5.在代码层面进行优化:
(1) 将重复的功能分块提取出来,封装成组件,让代码进行有效的复用
(2) 尽量利用computed属性代替for循环来渲染数据
(3) 提取公共方法,减少js代码量
(4) 提取公共的样式,减少页面样式的代码量
(5) 按照功能设置打包的代码块
(6) 尽量用v-if 代替v-show.因为v-show为false只是设置页面的功能模块不可见,它还是会渲染. 而v-if为false,则不会渲染该模块的功能
js是怎样监听数组变化 的
深拷贝一份放在数组原型上(Object.defineProperty)
数据劫持
vue监听数组变化
Object.defineProperty来监听数组变化
rem单位是基于根节点
JavaScript中的null和undefined的区别
1、null
表示没有对象,即该处不应该有值,用法如下:
作为函数的参数,表示该函数的参数不是对象;
作为原型链的终点。
2、undefined
表示缺少值,就是此处应该有一个值,但是还没有定义,情况如下:
变量被声明了,但没有赋值时,就等于undefined;
调用函数时,应该提供的参数没有提供,该参数等于undefined;
对象没有赋值的属性,该属性的值为undefined;
函数没有返回值的时,默认返回undefined;
JS如何判断一个对象为空
LuckDay
2017年09月29日 17:50 · 阅读 4517
关注
原文链接: blog.csdn.net
昨天面试的时候被问到的问题。只怪自己根基不牢,没有回答好。然后面试官提示用for in,突然恍然大悟,然后又查询了网上的一些方法,对这个问题做一下整理。
1、最常见的思路,for…in…遍历属性,为真则为“非空数组”;否则为“空数组”
var judgeObj = function(obj){
for(var item in obj){
return true;
}
return false;
}复制代码
2.通过JSON自带的.stringify方法来判断
var judgeObj = function(obj){
if(JSON.stringify(obj) == "{}") return true;
else return false;
}复制代码
3.ES6新增的方法Object.keys()
var judgeObj = function(obj){
if(Object.keys(obj).length == 0) return true;
else return false;
}
JavaScript 判断对象属性是否存在(对象是否包含某个属性)
一般推荐使用 in 运算符,用法是属性名 in 对象,如果存在返回 true,反之为 false,来看个例子:
var echo = {
name:'听风是风',
job:undefined
};
console.log('name' in echo); //true
console.log('job' in echo); //true
console.log('age' in echo); //false
1234567
但需要注意的是,in 只能判断对象有没有这个属性,无法判断这个属性是不是自身属性,啥意思?咱们接着说。
判断是否是自身属性
一个最简单的构造函数创建实例的例子:
function Parent(){
this.name = 'echo';
};
Parent.prototype.age = 26;
var o = new Parent();
o.name//echo
o.age//26
1234567
在这个例子中,对于实例 o 而言,name 就是是自身属性,这是它自己有的,而 age 是原型属性,o 虽然没有这个属性,但在需要时,它还是顺着原型链找到了自己的老父亲 Parent,并借用了这个属性。
所以当我们用 in 判断时可以发现结果均为 true:
'name' in o;//true
'age' in o;//true
12
针对这个问题,如果我们还要判断是否是自身属性,就得借用方法hasOwnProperty()
,不信你看:
o.hasOwnProperty('name');//true
o.hasOwnProperty('age');//false
12
说到底hasOwnProperty()
做了两件事,除了判断对象是否包含这个属性,还判断此属性是不是对象的自身属性。
所以我们可以简单总结一下,如果我们只需判断对象有没有某个属性,使用 in
运算符就好了。而如果我们还要关心这个属性是不是自身属性,那么推荐hasOwnProperty()
方法。
说了这么多,这两个判断有什么使用场景呢?
关于 in 运算符判断对象有没有某个属性,最常见的,我们希望给某个对象添加一个方法,直接添加又担心其他同事已经声明过,存在覆盖的可能性,所以使用 in 来判断,没有这个属性,咱们再加。
当然针对这个问题,使用 Symbol 来确保对象 key 的唯一性也是不错的做法,这个就看实际场景了。
而关于hasOwnProperty()
这个方法呢,我目前遇到的就是深拷贝实现时会使用,因为我们知道每个对象都有原型,而深拷贝时我们只是希望拷贝对象的自身属性,使用此方法做个区分就是非常奈斯的做法了
js循环机制
事件循环过程可以简单描述为:
a、函数入栈,当 Stack 中执行到异步任务的时候,就将他丢给 WebAPIs ,接着执行同步任务,直到 Stack 为空;
b、在此期间 WebAPIs 完成这个事件,把回调函数放入 CallbackQueue (任务队列)中等待;
c、当执行栈为空时,Event Loop 把 Callback Queue中的一个任务放入Stack中,回到第1步。
事件循环(Event Loop) 是让 JavaScript 做到既是单线程,又绝对不会阻塞的核心机制,也是 JavaScript 并发模型(Concurrency Model)的基础,是用来协调各种事件、用户交互、脚本执行、UI 渲染、网络请求等的一种机制。在执行和协调各种任务时,Event Loop 会维护自己的事件队列。
事件队列是一个存储着待执行任务的队列,其中的任务严格按照时间先后顺序执行,排在队头的任务将会率先执行,而排在队尾的任务会最后执行。事件队列每次仅执行一个任务,在该任务执行完毕之后,再执行下一个任务,一个任务开始后直至结束,不会被其他任务中断。执行栈则是一个类似于函数调用栈的运行容器,当执行栈为空时,JS 引擎便检查事件队列,如果不为空的话,事件队列便将第一个任务压入执行栈中运行。
任务队列:在JavaScript中,异步任务被分为两种,一种宏任务(MacroTask)也叫Task,一种叫微任务:
宏任务的例子很多,包括创建主文档对象、解析HTML、执行主线(或全局)JavaScript代码,更改当前URL以及各种事件,如页面加载、输入、网络事件和定时器事件。从浏览器的角度来看,宏任务代表一个个离散的、独立工作单元。运行完任务后,浏览器可以继续其他调度,如重新渲染页面的UI或执行垃圾回收。
而微任务是更小的任务。微任务更新应用程序的状态,但必须在浏览器任务继续执行其他任务之前执行,浏览器任务包括重新渲染页面的UI。微任务的案例包括promise回调函数、DOM发生变化等。微任务需要尽可能快地、通过异步方式执行,同时不能产生全新的微任务。微任务使得我们能够在重新渲染UI之前执行指定的行为,避免不必要的UI重绘,UI重绘会使应用程序的状态不连续。
当当前执行栈中的事件执行完毕后,js 引擎首先会判断微任务对列中是否有任务可以执行,如果有就将微任务队首的事件压入栈中执行。当微任务对列中的任务都执行完成后再去判断宏任务对列中的任务。每次宏任务执行完毕,都会去判断微任务队列是否产生新任务,若存在就优先执行微任务,否则按序执行宏任务。
事件循环通常至少需要两个任务队列:宏任务队列和微任务队列。两种队列在同一时刻都只执行一个任务。
vue中data为什么是一个函数
vue中的data
是一个对象类型,对象类型的数据是按引用传值
的,这就会导致所有组件的实例都共享
同一份数据,这是不对的,我们要的是每个组件实例都是独立的
为了解决对象类型数据共享的问题,我们需要将 data
定义成一个函数,每个实例需要通过调用函数生成一个独立的数据对象
js中typeof与instanceof分别是什么?
一、typeof的定义和用法:
返回值是一个字符串,用来说明变量的数据类型。
具体用法细节:
1、typeof 一般只能返回如下几个结果:
‘undefined’ :这个值未定义。
‘boolean’:这个值是布尔值。
‘string’ :这个值是字符串。
‘number’ :这个值是数值。
‘object’:这个值是对象或null。
‘function’ :这个值是函数。
2、typeof 来获取一个变量是否存在,如
`if``(``typeof` `a!=``"undefined"``){alert(``"ok"``)}`
而不要去使用 if(a) 因为如果 a 不存在(未声明)则会出错。
3、对于 Array,Null 等特殊对象使用 typeof 一律返回 object,这正是 typeof 的局限性。
二、Instanceof定义和用法:
Instanceof定义和用法:instanceof 用于判断一个变量是否属于某个对象的实例。也可以用来判断某个构造函数的prototype属性是否存在另外一个要检测对象的原型链上。
前端重绘和重排
有经验的大佬对这个概念一定不会陌生,“浏览器输入URL发生了什么”。估计大家已经烂熟于心了,从计算机网络到JS引擎,一路飞奔到浏览器渲染引擎。 经验越多就能理解的越深。
感兴趣的同学可以看一下这篇文章,深度和广度俱佳: 《从输入 URL 到页面加载的过程?如何由一道题完善自己的前端知识体系!》
切回正题,我们继续探讨何为重排。浏览器下载完页面所有的资源后,就要开始构建DOM树,与此同时还会构建渲染树(Render Tree)。(其实在构建渲染树之前,和DOM树同期会构建Style Tree。DOM树与Style Tree合并为渲染树)
**DOM树:**表示页面的结构
**渲染树:**表示页面的节点如何显示
一旦渲染树构建完成,就要开始绘制(paint)页面元素了。
当DOM的变化引发了元素几何属性的变化,比如改变元素的宽高
,元素的位置
,导致浏览器不得不重新计算元素的几何属性,并重新构建渲染树,这个过程称为“重排”。完成重排后,要将重新构建的渲染树渲染到屏幕上,这个过程就是“重绘”。
简单的说,重排负责元素的几何属性更新,重绘负责元素的样式更新。而且,重排必然带来重绘,但是重绘未必带来重排。比如,改变某个元素的背景
,这个就不涉及元素的几何属性,所以只发生重绘。
二、 重排触发机制绘
上面已经提到了,重排发生的根本原理就是元素的几何属性发生了改变,那么我们就从能够改变元素几何属性的角度入手
- 添加或删除可见的DOM元素
- 元素位置改变
- 元素本身的尺寸发生改变
- 内容改变
- 页面渲染器初始化
- 浏览器窗口大小发生改变
Vue.nextTick 的原理和用途
Vue在修改数据后,视图不会立刻更新,而是等同一事件循环中的所有数据变化完成之后,再统一进行视图更新。
事件循环:
第一个tick(本次更新循环)
1.首先修改数据,这是同步任务。同一事件循环的所有的同步任务都在主线程上执行,形成一个执行栈,此时还未涉及DOM.
2.Vue开启一个异步队列,并缓冲在此事件循环中发生的所有数据变化。如果同一个watcher被多次触发,只会被推入队列中一次。
第二个tick(‘下次更新循环’)
同步任务执行完毕,开始执行异步watcher队列的任务,更新DOM。Vue在内部尝试对异步队列使用原生的Promise.then和MessageChannel 方法,如果执行环境不支持,会采用 setTimeout(fn, 0) 代替。
第三个tick(下次 DOM 更新循环结束之后)
二、应用场景及原因
1.在Vue生命周期的created()钩子函数进行DOM操作一定要放到Vue.nextTick()的回调函数中。
在created()钩子函数执行的时候DOM 其实并未进行任何渲染,而此时进行DOM操作无异于徒劳,所以此处一定要将DOM操作的js代码放进Vue.nextTick()的回调函数中。与之对应的就是mounted()钩子函数,因为该钩子函数执行时所有的DOM挂载和渲染都已完成,此时在该钩子函数中进行任何DOM操作都不会有问题。
2.在数据变化后要执行的某个操作,而这个操作需要使用随数据改变而改变的DOM结构的时候,这个操作都应该放进Vue.nextTick()的回调函数中。
具体原因在Vue的官方文档中详细解释:
Vue 异步执行 DOM 更新。只要观察到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作上非常重要。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部尝试对异步队列使用原生的Promise.then和MessageChannel,如果执行环境不支持,会采用setTimeout(fn, 0)代替。
async awit实现原理
async/await 就是 Generator 的语法糖,使得异步操作变得更加方便
2)async 函数就是将 Generator 函数的星号(*)替换成 async,将 yield 替换成await
3)async 是 Generator 的语法糖,这个糖体现在这几个方面:
async函数内置执行器,函数调用之后,会自动执行,输出最后结果,而Generator需要调用next或者配合co模块使用
更好的语义,async和await,比起星号和yield,语义更清楚了,async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果
更广的适用性,co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async 函数的 await 命令后面,可以是 Promise 对象和原始类型的值
返回值是Promise,async函数的返回值是 Promise 对象,Generator的返回值是 Iterator,Promise 对象使用起来更加方便
4)async/await 函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里
Promise的构造函数方法
Promise是什么:
1、认识Promise:
Promise 是异步操作的一种解决方案。
先给大家讲一下什么是异步:
回调函数其实就是异步操作,例:
document.addEventListener(
'click',
() => {
console.log('这里是异步的');
},
false
);
console.log('这里是同步的');
2、什么时候使用 Promise:
Promise 一般用来解决层层嵌套的回调函数(回调地狱 callback hell)的问题。
在这里插入代码片
Promise的基本用法:
1、实例化构造函数生成实例对象:
Promise 解决的不是回调函数,而是回调地狱,即并不是用了Promise就用不了回调函数,它们两个是可以共存的。
// 实例化构造函数生成实例对象:
const p = new Promise(() => {});
2、Promise 的状态:
Promise的状态解决了Promise的一系列行为。Promise 有 3 种状态,一开始是 pending(未完成),执行 resolve,变成 fulfilled(resolved),已成功;执行 reject,变成 rejected,已失败;Promise 的状态一旦变化,就不会再改变了。
一开始是 pending(未完成):
const p = new Promise((resolve, reject) => {
});
console.log(p); // Promise {<pending>
执行 resolve,变成 fulfilled(resolved),已成功:
const p = new Promise((resolve, reject) => {
resolve();
});
console.log(p); // Promise {<fulfilled>: undefined}
执行 reject,变成 rejected,已失败:
const p = new Promise((resolve, reject) => {
reject();
});
console.log(p); // Promise {<rejected>: undefined}
但这里请大家注意:Promise里面第一个参数是成功状态的回调函数,第二个参数是失败状态的回调函数,Promise里面的resolve和reject只是形参(名字可以随便写,想起什么名就起什么名),写成resolve和reject的原因是为了语义化更强一些,那么,请问下面代码执行结果是什么:
const p = new Promise((r1,r2) => {
r2();
});
console.log(p);
因为r1 和 r2 只是形参,第二个参数是失败状态的回调函数,所以调用 r2 就等同于调用 失败状态的回调函数 所以毫无疑问结果是Promise {<rejected>: undefined}
,在这里只是给大家提个醒,大家不要弄迷糊了。
Promise 的状态一旦变化,就不会再改变了:
const p = new Promise((resolve, reject) => {
resolve();
reject();
});
console.log(p); // Promise {<fulfilled>: undefined}
3、then方法(简单说明一下,具体在后面讲):
这里then传两个函数,成功了用第一个函数,失败了用第二个函数。
// 失败:
const p = new Promise((resolve, reject) => {
reject();
});
p.then(
() => {
console.log('success');
},
() => {
console.log('error');
}
);
// error
// 成功:
const p = new Promise((resolve, reject) => {
resolve();
});
p.then(
() => {
console.log('success');
},
() => {
console.log('error');
}
);
// success
4、resolve 和 reject 函数的参数:
resolve 和 reject 函数的参数都会传给相应的then方法里面函数的形参,即resolve()传它的参数给then里面第一个函数的形参,reject()传它的参数给then里面第二个函数的形参;举例:
// 成功的时候:
const p = new Promise((resolve, reject) => {
resolve({username:'Alex'});
});
p.then(
data => {
console.log('success', data);
},
err => {
console.log('error', err);
}
);
// success {username: "alex"}
// 失败的时候:
const p = new Promise((resolve, reject) => {
reject(new Error('reason'));
});
p.then(
data => {
console.log('success', data);
},
err => {
console.log('error', err);
}
);
// error Error: reason
// at 2-2.Promise 的基本用法.html:28
// at new Promise (<anonymous>)
// at 2-2.Promise 的基本用法.html:16
then方法:
1、什么时候执行:
pending->fulfilled 时,执行 then 的第一个回调函数
pending->rejected 时,执行 then 的第二个回调函数
2、执行后的返回值:
then 方法执行后返回一个新的 Promise 对象。
const p = new Promise((resolve, reject) => {
resolve();
// reject();
});
const p2 = p
.then(
() => {},
() => {}
)
.then() // 因为then方法返回的是一个promise,所以可以在后面再添加then方法。
.then();
console.log(p, p2, p === p2); // Promise {<fulfilled>: undefined} Promise {<pending>} false
3、then 方法返回的 Promise 对象的状态改变:
在前面讲过第一个new 的promise可以决定紧挨着后面then的执行回调函数,例:
const p = new Promise((resolve, reject) => {
reject();
});
p.then(
() => {
console.log('success');
},
() => {
console.log('err');
});
// err
因为在p中执行reject(),所以执行p紧挨着的then的第二个回调函数,因此输出err,
而我们都知道then方法可以继续使用在上一个then方法的后面的,但是大家有没有想过新的then方法是怎么执行的呢?下面进行讲解:
在讲解之前大家先判断一下下面这块代码的打印出来的值是多少?
// 案例1:
const p = new Promise((resolve, reject) => {
reject();
});
p.then(
() => {
console.log('success');
},
() => {
console.log('err');
}).then(
data => {
console.log('success2', data);
},
err => {
console.log('err2', err);
}
)
这个值是err success2 undefined,
我先给大家分析一下,在p中执行reject(),所以在第一个then中执行第二个回调函数,所以打印出err,然后在第一个then中默认返回的永远都是成功状态的 Promise 对象,所以在第二个then中调用第第一个回调函数,所以接着打印出success2 undefined,由此可见后面的then执行第一个回调函数还是第二个回调函数看的是前面的then,可能大家对
在第一个then中默认返回的永远都是成功状态的 Promise 对象
这句话不太理解 ,下面我通过代码给大家演示:
先简单通过代码看一下:
// 在 then 的回调函数中,return 后面的东西,会用 Promise 包装一下
return undefined;
// 等价于
return new Promise(resolve => {
resolve(undefined);
});
这是详细讲解:
1、在所有函数中都有return值,没有写的时候就默认返回undefined,所以在第一个then里面就默认返回undefined(相当于程序中默认有 return undefined;
这样一行代码)
() => {
console.log('err');
// 默认这里有 return undefined;这样一行代码
})
又因为 默认返回的永远都是成功状态的 Promise 对象,即:
() => {
console.log('err');
// 默认这里有 return undefined;这样一行代码
// 因为默认返回的永远都是成功状态的 Promise 对象,所以相当于一个新的Promise里面执行resolve(),即:
return new Promise((resolve, reject) => {
resolve(undefined); // 因为默认返回undefined,所以resolve里面参数是undefined
})
因为上一个then执行resolve,所以到第二个then那里就执行第一个回调函数,所以打印出 success2 undefined,打印出undefined是因为在一个then里面的reject传进去的undefined。讲到这里大家可能会问:默认返回的永远都是成功状态的 Promise 对象,那么如何返回失败状态的Promise对象呢?其实是这样的,then里面默认的是返回的永远都是成功状态的 Promise 对象,那么我直接给一个失败状态的Promise,那么不就不会再出现默认值了呢,没错,就是这样。举例:
const p = new Promise((resolve, reject) => {
reject();
});
p.then(
() => {
console.log('success');
},
() => {
console.log('err');
/
return new Promise((resolve, reject) => {
reject('reason');
});
/
}).then(
data => {
console.log('success2', data);
},
err => {
console.log('err2', err);
}
)
大家注意横线里面的两行代码,那样写即程序不会再执行那个 默认的正确状态的Promis对象了,而执行自己写的那个错误的Promise对象(即/中间的代码),所以,这个案例的执行结果是:err err2 reason,原理同案例1一样。
那么简单测试一下上面知识是否掌握了,上案例:
const p = new Promise((resolve, reject) => {
reject();
});
p.then(
() => {
console.log('success');
},
() => {
// 步骤一:
console.log('err');
}).then(
data => {
// 步骤二:
console.log('success2', data);
},
err => {
console.log('err2', err);
}
).then(
data => {
// 步骤三:
console.log('success3', data);
},
err => {
console.log('err3', err);
}
);
只要大家将上面知识理解了,那么这个案例就是小菜一碟,答案是:err success2 undefined success3 undefined,执行的分别是步骤一,步骤二,步骤三。
catch方法:
1、有什么用:
在前面的then方法中,我们知道它里面有两个回调函数(第一个处理成功的状态,第二个处理失败的状态),但在实际开发中一般then只处理成功的状态,因此只传一个回调函数,因此catch就是用来专门处理失败的状态的,即catch 本质上是 then 的特例,就是then(null, err => {});
2、基本用法:
new Promise((resolve, reject) => {
// resolve(123);
reject('reason');
}).then(data => {
console.log(data); // then 处理正确的状态
}).catch(err => {
console.log(err); // catch 处理错误的状态
})
// reason
// 上面的catch其实就相当于
.then(null, err => {
console.log(err);
});
catch() 可以捕获它前面的错误,一般总是建议,Promise 对象后面要跟 catch 方法,这样可以处理 Promise 内部发生的错误:
catch可以捕获前面的错误,如果错误一旦被catc捕获到则错误就消失了。还有就是catch默认也是返回一个成功状态的Promise对象(原因很简单,catch本质是then,因为then默认返回成功状态的Promise对象,所以catch也是返回成功状态的Promise对象),所以catch后面跟的then只会执行成功的回调函数,如果想执行失败状态的回调函数需要自己手动return 失败状态的Promise对象,除了这种方法外还有一种方法,见代码:
new Promise((resolve, reject) => {
// resolve(123);
reject('reason');
})
.then(data => {
console.log(data);
}) /// 第一块
.catch(err => {
console.log(err);
throw new Error('reason');
})
.then(data => {
console.log(data);
}) 第二块
.catch(err => {
console.log(err);
}); /
// reason
// Error: reason
reason是第一块代码执行的结果, Error: reason是第二块代码执行的结果。在第一块代码里扔一个错误,那么就会被第二块代码中的catch捕获到,这样也不会再执行then里面的成功状态的回调函数了。
Promise.resolve() 和 Promise.reject():
1、Promise.resolve():
是成功状态 Promise 的一种简写形式。
new Promise(resolve => resolve('foo'));
// 这个地方的resolve是一个形参,名字想怎么写就怎么写
// 简写
Promise.resolve('foo');
// 这里的resolve是一个固定的方法名,不能乱写
参数:
(1)一般参数(最应该关注):
Promise.resolve('foo').then(data => {
console.log(data);
}); // foo
(2)特殊参数(简单了解一下即可,不要深究):
1、把Promise当作参数传进去。
const p1 = new Promise(resolve => {
setTimeout(resolve, 1000, '我执行了');
});
Promise.resolve(p1).then(data => {
console.log(data);
}); // 我执行了(注:在一秒后打印)
这是为什么呢?原因是:当 Promise.resolve() 接收的是 Promise 对象时,直接返回这个 Promise 对象,什么都不做。
即上面的代码等同于:
const p1 = new Promise(resolve => {
setTimeout(resolve, 1000, '我执行了');
// 上面的setTimeout等同于:
// setTimeout(() => {
// resolve('我执行了');
// }, 1000);
});
p1.then(data => {
console.log(data);
}); // 我执行了(注:一秒后打印)
console.log(Promise.resolve(p1) === p1); // true
还有就是,当 resolve 函数接收的是 Promise 对象时,后面的 then 会根据传递的 Promise 对象的状态变化决定执行哪一个回调。
上代码进行描述:
const p1 = new Promise(resolve => {
setTimeout(resolve, 1000, '我执行了');
});
new Promise(resolve => resolve(p1)).then(data => {
console.log(data);
});
上面那句话的意思就是如果函数接收的是一个Promise对象(即上面代码的p1)时,后面的then受p1支配,因为p1在一秒后执行了resolve('我执行了');
所以就给then里面传了个 ‘我执行了’ ,所以then执行后打印出 我执行了。
2、具有 then 方法的对象:
因这种形式在实际开发中不咋用,这里不再细讲,大家知道有这个东西就行。(要是碰到了直接查文档就完事了)
2、Promise.reject():
失败状态 Promise 的一种简写形式。
new Promise((resolve, reject) => {
reject('reason');
});
// 等价于
Promise.reject('reason');
参数:
不管什么参数,都会原封不动地向后传递,作为后续方法的参数。(跟resolve相比简单多了)
还有一个Promise.resolve() 和 Promise.reject()的用处就是用在then方法的return中,如:
new Promise((resolve, rejcet) => {
resolve(123);
})
.then(data => {
// return data;
// return Promise.resolve(data); // 传失败状态的Promise对象的简写
return Promise.reject('reason'); // 传失败状态的Promise对象的简写
})
.then(data => {
console.log(data);
})
.catch(err => console.log(err));
// reason
Promise.all():
1、有什么用:
Promise.all() 关注多个 Promise 对象的状态变化;可以在Promise对象中传入多个 Promise 实例,包装成一个新的 Promise 实例返回。
2、基本用法:
Promise.all() 的状态变化与所有传入的 Promise 实例对象状态有关
所有状态都变成 resolved,最终的状态才会变成 resolved
只要有一个变成 rejected,最终的状态就变成 rejected
见代码:
// 封装一个延迟函数:
const delay = ms => {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
};
const p1 = delay(1000).then(() => {
console.log('p1 完成了');
return 'p1'; // 默认返回成功状态的Promise对象
});
const p2 = delay(2000).then(() => {
console.log('p2 完成了');
return 'p2'; // 默认返回成功状态的Promise对象
});
const p = Promise.all([p1, p2]);
p.then(
data => {
console.log(data);
},
err => {
console.log(err);
}
);
上面代码中的执行结果是:p1完成了(一秒后),p2完成了 (2) [“p1”, “p2”] (两秒后)
这块代码是最终的状态是 resolved
再看下面的代码:
const delay = ms => {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
};
const p1 = delay(1000).then(() => {
console.log('p1 完成了');
return Promise.reject('reason');
});
const p2 = delay(2000).then(() => {
console.log('p2 完成了');
return 'p2';
});
const p = Promise.all([p1, p2]);
p.then(
data => {
console.log(data);
},
err => {
console.log(err);
}
);
这块代码的结果是:p1完成了 reason (注:一秒后打印) p2完成了 (注:两秒后打印)
这块代码中有rejected,所以最终状态就是rejected,可能大家疑惑为什么reason在 p1完成了 后面出现,大家可以这样理解:因为系统检测到某个地方出现了rejected,所以直接判定Promise.all的状态是rejected,所以直接执行p后面then方法的第二个回调函数,所以打印出reason,又因为那个rejected的状态是在p1中检测到的,所以reason出现在 p1完成了 的后面。
大家可以通过下面的例子检测字迹是否理解:
const delay = ms => {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
};
const p1 = delay(1000).then(() => {
console.log('p1 完成了');
return 'p1';
});
const p2 = delay(2000).then(() => {
console.log('p2 完成了');
return Promise.reject('reason');
});
const p = Promise.all([p1, p2]);
p.then(
data => {
console.log(data);
},
err => {
console.log(err);
}
);
这里的结果是:p1完成了(注:一秒后打印出来) p2完成了 reason(注:两秒后打印出来)
Promise.race() 和 Promise.allSettled():
先声明一下,这两个方法大家简单了解一下即可。
1、Promise.race():
Promise.race() 的状态取决于第一个完成的 Promise 实例对象,如果第一个完成的成功了,那最终的就成功;如果第一个完成的失败了,那最终的就失败。
const delay = ms => {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
};
const p1 = delay(1000).then(() => {
console.log('p1 完成了');
return 'p1';
});
const p2 = delay(2000).then(() => {
console.log('p2 完成了');
return 'p2';
});
const racePromise = Promise.race([p1, p2]);
racePromise.then(
data => {
console.log(data);
},
err => {
console.log(err);
}
);
const delay = ms => {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
};
const p1 = delay(1000).then(() => {
console.log('p1 完成了');
return 'p1';
});
const p2 = delay(2000).then(() => {
console.log('p2 完成了');
return Promise.reject('reason');
});
const racePromise = Promise.race([p1, p2]);
racePromise.then(
data => {
console.log(data);
},
err => {
console.log(err);
}
);
这里提供两个例子,供大家自己自行下去进行验证学习,这里就不细讲了,大家在自己编译器上一运行就一目了然了。
2、Promise.allSettled():
Promise.allSettled() 的状态与传入的Promise 状态无关,它后面的then方法永远执行成功的回调函数,它只会忠实的记录下各个 Promise 的表现。见代码:
const delay = ms => {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
};
const p1 = delay(1000).then(() => {
console.log('p1 完成了');
return 'p1';
});
const p2 = delay(2000).then(() => {
console.log('p2 完成了');
return Promise.reject('reason');
});
const allSettledPromise = Promise.allSettled([p1, p2]);
allSettledPromise.then(data => {
console.log('succ', data);
});
const delay = ms => {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
};
const p1 = delay(1000).then(() => {
console.log('p1 完成了');
return Promise.reject('reason');
});
const p2 = delay(2000).then(() => {
console.log('p2 完成了');
return Promise.reject('reason');
});
const allSettledPromise = Promise.allSettled([p1, p2]);
allSettledPromise.then(data => {
console.log('succ', data);
});
上面两个例子大家自行演示,看到结果大家都会明白的。
Promise的注意事项:
1、resolve 或 reject 函数执行后的代码:
推荐在调用 resolve 或 reject 函数的时候加上 return,不再执行它们后面的代码。
如:
new Promise((resolve, reject) => {
// // return resolve(123);
return reject('reason');
});
虽然 resolve 或 reject 函数的后面的代码仍可以执行。如:
new Promise((resolve, reject) => {
// // return resolve(123);
return reject('reason');
console.log('hi');
}); // hi
但是不建议这样写。
2、Promise.all/race/allSettled 的参数问题:
参数如果不是 Promise对象 的数组,系统会默认的将不是 Promise 的数组元素转变成 Promise对象 的数组。
Promise.all([1, 2, 3]).then(datas => {
console.log(datas);
});
// 等价于
Promise.all([
Promise.resolve(1),
Promise.resolve(2),
Promise.resolve(3)
]).then(datas => {
console.log(datas);
});
参数不只是数组,任何可遍历的都可以作为参数:
如:原生可遍历的:数组、字符串、Set、Map、NodeList、arguments
和非原生可遍历的(通过Iterate)
举个栗子:
// Set作为参数
Promise.all(new Set([1, 2, 3])).then(datas => {
console.log(datas);
});
3、Promise.all/race/allSettled 的错误处理:
单独处理:
// 第一个案例:
const delay = ms => {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
};
const p1 = delay(1000).then(() => {
console.log('p1 完成了');
return Promise.reject('reason');
}).catch(err => {
console.log('p1', err);
});
const p2 = delay(2000).then(() => {
console.log('p2 完成了');
return Promise.reject('reason');
}).catch(err => {
console.log('p2', err);
});
const allPromise = Promise.all([p1, p2]);
allPromise.then(datas => {
console.log(datas);
});
统一处理:
// 第二个案例:
const delay = ms => {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
};
const p1 = delay(1000).then(() => {
console.log('p1 完成了');
return Promise.reject('reason');
});
const p2 = delay(2000).then(() => {
console.log('p2 完成了');
return Promise.reject('reason');
});
const allPromise = Promise.all([p1, p2]);
allPromise.then(datas => {
console.log(datas);
}).catch(err => console.log(err));
错误既可以单独处理,也可以统一处理
一旦被处理,就不会在其他地方再处理一遍(上面第二个案例)
宏任务和微任务
异步队列,当所有的同步任务执行完成之后会执行异步任务。如果异步队列中还有异步,那么就会牵涉到异步轮询机制,会先走微任务在执行宏任务。当前宏任务周期的所有微任务都执行完成之后会执行下一个宏任务。
宏任务:运行环境提供的异步
微任务:语言提供的异步
js事件轮询
事件轮询 (eventloop) 是"一个解决和处理外部事件时将它们转换为回调函数的调用的实体(entity)"
JavaScript 语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。所有任务都需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。
任务队列
“ 任务队列 " 是一个先进先出的数据结构,排在前面的事件,优先被主线程读取。主线程的读取过程基本上是自动的,只要执行栈一清空," 任务队列 " 上第一位的事件就会自动进入主线程。
所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。
同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务
异步任务指的是,不进入主线程、而进入 " 任务队列 "(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
JS如何实现异步操作
JS 的异步是通过回调函数实现的,即通过任务队列,在主线程执行完当前的任务栈(所有的同步操作),主线程空闲后轮询任务队列,并将任务队列中的任务(回调函数)取出来执行。
" 回调函数 "(callback),就是那些会被主线程挂起来的代码。异步任务必须指定回调函数,当主线程开始执行异步任务,就是执行对应的回调函数。
虽然 JS 是单线程的,但是浏览器的内核是多线程的,在浏览器的内核中不同的异步操作由不同的浏览器内核模块调度执行,异步操作会将相关回调添加到任务队列中。而不同的异步操作添加到任务队列的时机也不同,如 onclick, setTimeout,ajax 处理的方式都不同,这些异步操作是由浏览器内核的 webcore 来执行的,webcore 包含上图中的3种 webAPI,分别是 DOM Binding、network、timer模块。
onclick 由浏览器内核的 DOM Binding 模块来处理,当事件触发的时候,回调函数会立即添加到任务队列中。
setTimeout 会由浏览器内核的 timer 模块来进行延时处理,当时间到达的时候,才会将回调函数添加到任务队列中。
ajax 则会由浏览器内核的 network 模块来处理,在网络请求完成返回之后,才将回调函数添加到任务队列中。
异步执行机制
所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
主线程之外,还存在一个 " 任务队列 "(task queue)。只要异步任务有了运行结果,就在 " 任务队列 " 之中放置一个事件。
一旦 " 执行栈 " 中的所有同步任务执行完毕,系统就会读取 " 任务队列 ",看看里面有哪些事件。于是那些对应的异步任务结束等待状态,进入执行栈,开始执行。
主线程不断重复上面的第三步(事件轮询)
JS中事件队列的优先级
在 JS 的ES6 中新增的任务队列(promise)优先级是在事件循环之上的,事件循环每次 tick 后会查看 ES6 的任务队列中是否有任务要执行,也就是 ES6 的任务队列比事件循环中的任务(事件)队列优先级更高。
先执行同步代码再执行异步代码,先执行微任务再执行宏任务
setTimeout( fn, 0 ),它在 " 任务队列 " 的尾部添加一个事件,因此要等到同步任务和 " 任务队列 " 现有的事件都处理完,才会得到执行。
803
块级作用域
1.在ES6之前JavaScript是没有块级作用域的,所有的变量都通过var关键字去声明,即在控制语句中的变量也可以在外部的作用域去访问。
2.随着ES6的到来JavaScript给我们带来的let 和 const关键字,也让它本身拥有了块级作用域的概念( { }内部都是块级作用域,在测试小demo的时候可以使用{ }来创建一个块级作用域来避免变量名称的冲突 )。我们在控制语句中使用let 和 const定义的
for循环中使用let定义的变量在全局作用域访问
3.用处:在你需要一些临时的变量的时候,块级作用域就可以发挥他的作用。而通过创建块级作用域,我们就不会担心会不会搞乱其他人定义的全局变量,我们就可以根据自己的想法来定义自己的变量了
全局作用域
JavaScript中的全局作用域中的变量,在任何地方都是可以访问的,全局作用域中的变量在定义的时候生成,浏览器关闭的时候销毁。
函数作用域
js中可以通过函数来创建一个独立作用域称为函数作用域,函数可以嵌套,所以作用域也可以嵌套;
自由变量的概念
在当前作用域没有定义的变量,但是却被访问了。向上级作用域一层一层向上查找,直到找到全局作用域,如果没有找到会抛出:xx is not defined 的错误;
**作用域链:**自由变量的向上级作用域一层一层查找,直到找到为止,最高找到全局作用域,就形成了作用域链
作用域链的理解
对于一个变量,引擎从当前作用域开始查找变量,如果找不到,就会向上一级继续查找。当抵达最外层全局作用域时,无论找到还是没有找到,查找过程中都会停止。
TCP和UDP的区别
连接性
TCP是面向连接的协议,在收发数据前必须和对方建立可靠的连接,建立连接的3次握手、断开连接的4次挥手,为数据传输打下可靠基础;UDP是一个面向无连接的协议,数据传输前,源端和终端不建立连接,发送端尽可能快的将数据扔到网络上,接收端从消息队列中读取消息段。
可靠性
TCP提供可靠交付的服务,传输过程中采用许多方法保证在连接上提供可靠的传输服务,如编号与确认、流量控制、计时器等,确保数据无差错,不丢失,不重复且按序到达;UDP使用尽可能最大努力交付,但不保证可靠交付。
TCP报文结构
TCP协议面向字节流,将应用层报文看成一串无结构的字节流,分解为多个TCP报文段传输后,在目的站重新装配;UDP协议面向报文,不拆分应用层报文,只保留报文边界,一次发送一个报文,接收方去除报文首部后,原封不动将报文交给上层应用。
吞吐量控制
TCP拥塞控制、流量控制、重传机制、滑动窗口等机制保证传输质量;UDP没有。
双工性
TCP只能点对点全双工通信;UDP支持一对一、一对多、多对一和多对多的交互通信
从上面TCP、UDP编程步骤可以看出,UDP 服务器端不需要调用监听(listen)和接收(accept)客户端连接,而客户端也不需要连接服务器端(connect)。UDP协议中,任何一方建立socket后,都可以用sendto发送数据、用recvfrom接收数据,不必关心对方是否存在,是否发送了数据。
TCP和UDP的使用场景
为了实现TCP网络通信的可靠性,增加校验和、序号标识、滑动窗口、确认应答、拥塞控制等复杂的机制,建立了繁琐的握手过程,增加了TCP对系统资源的消耗;TCP的重传机制、顺序控制机制等对数据传输有一定延时影响,降低了传输效率。TCP适合对传输效率要求低,但准确率要求高的应用场景,比如万维网(HTTP)、文件传输(FTP)、电子邮件(SMTP)等。
UDP是无连接的,不可靠传输,尽最大努力交付数据,协议简单、资源要求少、传输速度快、实时性高的特点,适用于对传输效率要求高,但准确率要求低的应用场景,比如域名转换(DNS)、远程文件服务器(NFS)等。
prototype 和 proto 区别是什么
原型是为了实现对象间的联系,解决构造函数无法数据共享而引入的一个属性,而原型链是一个实现对象间联系即继承的主要方法
-
prototype是构造函数的属性
-
__proto__
是每个实例都有的属性,可以访问 [[prototype]] 属性 -
实例的
__proto__
与其构造函数的prototype指向的是同一个对象 -
显式原型
prototype 每一个函数在创建之后,便会拥有一个prototype属性,这个属性指向函数的原型对象,显示原型的作用是用来实现基于原型的继承与属性的共享
隐式原型
_proto__上面说的这个原型是JavaScript中的内置属性prototype,此属性继承自object对象,但Firefox、Safari和Chrome在每个对象上都支持一个属性_proto,隐式原型的作用是用来构成原型链,实现基于原型的继承
作者:漫卷诗书喜欲狂链接:https://juejin.cn/post/7095651623812202533来源:稀土掘金著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
react事件
react并不会真正的绑定事件到每一个具体元素上,而是采用事件代理模式
stestate处在同步逻辑,异步更新状态,更新真实dom
stestate处在异步逻辑,同步更新状态,同步更新真实dom
stestate接受第二个参数,第二个参数回调函数,状态和dom更新完就会被触发
VUE SEO 几种方案
1、SSR服务器渲染
Vue.js 是构建客户端应用程序的框架。默认情况下,可以再浏览器中输出Vue组件,进行生成DOM和操作DOM。然而,也可以将同一个组件渲染未服务器端的HTML字符串,将它们直接发送到浏览器,最后将这些静态标记“激活”为客户端上完全可交互的应用程序。
服务端渲染的Vue.js应用程序也可以被认为是“同构”或“通用”,因为应用程序的大部分代码都可以在服务器和客户端上运行。
权衡之处:
- 开发条件所限,浏览器特定的代码,只能在某些生命周期钩子函数(lifecycle hook)中使用;一些外部扩展库(external library)可能需要特殊处理,才能在服务器渲染应用程序中运行。
- 环境和部署要求更高,需要Node.js server运行环境;
- 高流量的情况下,需要准备相应的服务器负载,并明智地采用缓存策略。
优势:
- 更好的SEO,因为搜索引擎爬虫抓取工具可以直接查看完全渲染的页面;
- 更快的内容到达时间(time-to-content),特别是对于缓慢的网络情况或运行缓慢的设备。
不足:
- 一套代码两套执行环境,会引起各种问题,比如服务端没有window、document对象,处理方式是增加判断,如果是客户端才执行;
- 涉及构建设置和部署的更多要求,需要处于node server的运行环境
- 更多的服务端负载
2、Nuxt静态化
Nuxt.js框架,官方是这样介绍的: 从头搭建一个服务端渲染的应用是相当复杂的。幸运的是,我们有一个优秀的社区项目Nuxt.js这让一切变得非常简单。Nuxt是一个基于Vue生态的更高层的框架,为开发服务端渲染的Vue应用提供了极其便利的开发体验。更酷的是,你甚至可以用它来做为静态站生成器。
静态化是Nuxt.js打包的另一种方式,算是Nuxt.js的一个创新点,页面加载速度很快。
注意:在Nuxt.js执行 generate静态化打包时,动态路由会被忽略。
优势:
- 纯静态文件,访问速度超快
- 对比SSR,不涉及到服务器负载方面问题;
- 静态网页不宜遭到黑客攻击,安全性更高。
不足:
- 如果动态路由参数多都化不适用。
3、 预渲染 prerender-spa-plugin
如果你只是用来改善少数营销页面(例如: /,/about,/contact等=)的SEO,那么你可能需要预渲染。无需使用Web服务器实时动态编译HTML,而是使用预渲染方式,在构建时(build time)简单地生成针对特定路由等静态HTML文件。优点是设置预渲染更简单,并可以将你的前端作为一个完全静态的站点。
优势:
- 改动过小,引入插件配置即可
不足:
- 无法使用动态路由
- 只使用少量页面的项目,页面多达几百个的情况下,打包会非常慢
4、使用Phantomjs针对爬虫做处理
Phantomjs是一个基于webkit内核的无头浏览器,即没有UI界面,即它就是一个浏览器,只是其内的点击、翻页等人为相关操作需要程序设计实现。
虽然“PhantomJS宣布终止开发”,但是已经满足对Vue 的SEO处理。
这种解决方案其实是一种旁路机制,原理就是通过Nginx配置,判断访问来源UA是否是爬虫访问,如果是则将搜索引擎的爬虫请求转发到一个node server,再通过PhantomJS来解析完整的HTML,返回给爬虫。
优势:
- 完全不用改动项目代码,按原本的SPA开发即可,对比开发SSR成本小的不要太多;
- 对已用SPA开发完成的项目,这是不二之选。
不足:
- 部署需要node 服务器支持。
- 爬虫访问比网页访问要慢一些,因为定时要定时资源加载完成才返回给爬虫;
- 如果呗恶意模拟百度爬虫大量循环爬取,会造成服务器负载方面问题,解决方法是判断访问的IP,是否是百度官方爬虫的IP;
5、总结
如果构建大型网站,如商城类,不要犹豫直接上SSR服务端渲染,当然也有相应的坑等你,社区较成熟,英文好一些, 一切问题都迎刃而解。
如果只是个人博客、公司官网这类,其余三种都可以。
如果对已用SPA开发完成的项目进行SEO优化,而且支持node服务器,请使用Phantomjs。
对于VUE SSR项目已经接手开发过好几个,目前也是维护的状态。 从0~1的项目搭建, 开发使用Vue-SSR中也确实遇到一些坑和问题, 但这些都能得到解决,办法总比困难多的哈哈。
JS判断数组是数组
通过instanceof判断
通过constructor判断
通过Object.prototype.toString.call()判断
通过Array.isArray()判断
js如何合并对象
- 在 JavaScript 中使用
object.assign()
函数合并对象 - 在 JavaScript 中使用扩展运算符合并对象
- 在 JavaScript 中使用
array.reduce()
函数合并对象 - 在 JavaScript 中使用
jquery.extend()
函数合并对象 - 在 JavaScript 中使用用户定义的函数合并对象
vue3 +TS 父子组件之间的传值 通信
- import { definedProps, definedEmits } from ‘vue’//使用语法糖才能使用他们
css绝对定位如何居中?css绝对定位居中的四种实现方法
1、兼容性不错的主流css绝对定位居中的用法:
.conter{
width: 600px; height: 400px;
position: absolute; left: 50%; top: 50%;
margin-top: -200px; /* 高度的一半 */
margin-left: -300px; /* 宽度的一半 */
}
注意:这种方法有一个很明显的不足,就是需要提前知道元素的尺寸。否则margin负值的调整无法精确。此时,往往要借助JS获得。
2、css3的出现,使得有了更好的解决方法,就是使用transform代替margin. transform中translate偏移的百分比值是相对于自身大小的,可以这样实现css绝对定位居中:
.conter{
width: 600px; height: 400px;
position: absolute; left: 50%; top: 50%;
transform: translate(-50%, -50%); /* 50%为自身尺寸的一半 */
}
3、margin:auto实现绝对定位元素的居中(上下左右均0位置定位;margin: auto)
.conter{
width: 600px; height: 400px;
position: absolute; left: 0; top: 0; right: 0; bottom: 0;
margin: auto; /* 有了这个就自动居中了 */
}
4、使用css3盒模型:flex布局实现css绝对定位居中。这种情况是在不考虑低版本浏览器的情况下可以使用。
1.数组常用方法之 push()(改变原数组,产生新数组)
-
push
是用来在数组的末尾追加一个元素,返回添加以后的长度var arr = [1, 2, 3] // 使用 push 方法追加一个元素在末尾 arr.push(4) console.log(arr) // [1, 2, 3, 4] var res = arr.push(1,2,3,34); res//8
2.数组常用方法之 pop()(改变原数组,产生新数组)
-
pop
是用来删除数组末尾的一个元素,返回删除的元素var arr = [1, 2, 3] // 使用 pop 方法删除末尾的一个元素 var res = arr.pop() console.log(arr) // [1, 2]
3.数组常用方法之 unshift()(改变原数组,产生新数组)
-
unshift
是在数组的最前面添加一个元素var arr = [1, 2, 3] // 使用 unshift 方法想数组的最前面添加一个元素 arr.unshift(4) console.log(arr) // [4, 1, 2, 3]
4.数组常用方法之 shift()(改变原数组,产生新数组)
-
shift
是删除数组最前面的一个元素var arr = [1, 2, 3] // 使用 shift 方法删除数组最前面的一个元素 arr.shift() console.log(arr) // [2, 3]
5.数组常用方法之 splice(改变原数组)
-
splice
是截取数组中的某些内容,按照数组的索引来截取 -
语法:
splice(从哪一个索引位置开始,截取多少个,替换的新元素)
(第三个参数可以不写)var arr = [1, 2, 3, 4, 5] // 使用 splice 方法截取数组 var res = arr.splice(1, 2) console.log(arr) // [1, 4, 5] console.log(res)//[2,3] - `arr.splice(1, 2)` 表示从索引 1 开始截取 2 个内容 - 第三个参数没有写,就是没有新内容替换掉截取位置 ```javascript var arr = [1, 2, 3, 4, 5] // 使用 splice 方法截取数组 arr.splice(1, 2, '我是新内容') console.log(arr) // [1, '我是新内容', 4, 5]
1)、删除元素,并返回删除的元素
可以删除任意数量的项,只需指定 2 个参数:要删除的第一项的位置和要删除的项数。例如, splice(0,2)会删除数组中的前两项。
var arr = [1,3,5,7,9,11];
var arrRemoved = arr.splice(0,2);
console.log(arr); //[5, 7, 9, 11]
console.log(arrRemoved); //[1, 3]
2)、向指定索引处添加元素
可以向指定位置插入任意数量的项,只需提供 3 个参数:起始位置、 0(要删除的项数)和要插入的项。例如,splice(2,0,4,6)会从当前数组的位置 2 开始插入 4 和 6。
var array1 = [22, 3, 31, 12];
array1.splice(1, 0, 12, 35); //[]
console.log(array1); // [22, 12, 35, 3, 31, 12]
3)、替换指定索引位置的元素
可以向指定位置插入任意数量的项,且同时删除任意数量的项,只需指定 3 个参数:起始位置、要删除的项数和要插入的任意数量的项。插入的项数不必与删除的项数相等。例如,splice (2,1,4,6)会删除当前数组位置 2 的项,然后再从位置 2 开始插入 4 和 6。
const array1 = [22, 3, 31, 12];
array1.splice(1, 1, 8); //[3]
console.log(array1); // [22, 8, 31, 12] 作者:物理达人Physics侯老师 https://www.bilibili.com/read/cv9715493/ 出处:bilibili
arr.splice(1, 2, '我是新内容')
表示从索引 1 开始截取 2 个内容- 然后用第三个参数把截取完空出来的位置填充
6.数组常用方法之 reverse(改变原数组,产生新数组)
-
reverse
是用来反转数组使用的var arr = [1, 2, 3] // 使用 reverse 方法来反转数组 arr.reverse() console.log(arr) // [3, 2, 1]
7.数组常用方法之 sort(改变原数组,产生新数组)
-
sort
是用来给数组排序的(默认按照字典排序 先按==照第一位排序-==如果第一位相等就按照第二位)var arr = [2, 3, 1] // 使用 sort 方法给数组排序 arr.sort() console.log(arr) // [1, 2, 3]
- 这个只是一个基本的简单用法
// 升序 arr4.sort(function (a, b) { return a - b }) // 降序 arr4.sort(function (a, b) { return b - a })
let arr5 = [{ username: 'zhangsan', age: 19 }, { username: 'lisi', age: 10 }, ] // 按照对象的年龄 降序 // a ,b 数组的元素 arr5.sort(function (a, b) { return b.age - a.age })
8.数组常用方法之 concat(不改变原数组)
-
concat
是把多个数组进行拼接 -
和之前的方法有一些不一样的地方,就是
concat
不会改变原始数组,而是返回一个新的数组var arr = [1, 2, 3] // 使用 concat 方法拼接数组 var newArr = arr.concat([4, 5, 6]) console.log(arr) // [1, 2, 3] console.log(newArr) // [1, 2, 3, 4, 5, 6]
- 注意: **
concat
方法不会改变原始数组**
- 注意: **
9.数组常用方法之 join==(不会改变原始数组,而是把链接好的字符串返回)==
-
join
是把数组里面的每一项内容链接起来,变成一个字符串 -
可以自己定义每一项之间链接的内容
join(要以什么内容链接)
默认使用逗号作为分隔符 -
不会改变原始数组,而是把链接好的字符串返回
var arr = [1, 2, 3] // 使用 join 链接数组 var str = arr.join('-') console.log(arr) // [1, 2, 3] console.log(str) // 1-2-3
- 注意: join 方法不会改变原始数组,而是返回链接好的字符串
10.slice 复制数组的一部分 (不改变原始数组,返回一个新的数组)
var arr9 = [9,8,7,6,5,4,3,2];
// 参数 两个 开始位置 结束位置,不包含结束位置,不改变原数组
var res9 = arr9.slice(1,3)
console.log(res9)//[8, 7]
// 第二个参数不传入,复制到最后
console.log(arr9.slice(1))//[8, 7, 6, 5, 4, 3, 2]
// 如果传入负数,倒着数位置,最后一个是 -1
console.log(arr9.slice(1,-2))// [8, 7, 6, 5, 4]
注:
如果之只传入一个大于数组长度的参数,则返回一个空数组
无论是如何提取数组元素,原数组始终保持不变
ES5 中常见的数组常用方法
11.indexOf
接收两个参数:要查找的项和(可选的)表示查找起点位置的索引。
indexOf():从数组的开头(位置 0)开始向后查找。
lastIndexOf:从数组的末尾开始向前查找。
这两个方法都返回要查找的项在数组中的位置,或者在没找到的情况下返回-1。在比较第一个参数与数组中的每一项时,会使用全等操作符。
-
indexOf
用来找到数组中某一项的索引 -
语法:
indexOf(你要找的数组中的项)
var arr = [1, 2, 3, 4, 5] // 使用 indexOf 超找数组中的某一项 var index = arr.indexOf(3) console.log(index) // 2
- 我们要找的是数组中值为 3 的那一项
- 返回的就是值为 3 的那一项在该数组中的索引
-
如果你要找的内容在数组中没有,那么就会返回 -1
var arr = [1, 2, 3, 4, 5] // 使用 indexOf 超找数组中的某一项 var index = arr.indexOf(10) console.log(index) // -1
- 你要找的值在数组中不存在,那么就会返回 -1
12.forEach
-
和
for
循环一个作用,就是用来遍历数组的 -
这个方法没有返回值。参数都是 function 类型,默认有传
-
语法:
arr.forEach(function (item, index, arr) {})
var arr = [1, 2, 3] // 使用 forEach 遍历数组 arr.forEach(function (item, index, arr) { // item 就是数组中的每一项 // index 就是数组的索引 // arr 就是原始数组 console.log('数组的第 ' + index + ' 项的值是 ' + item + ',原始数组是', arr) })
forEach()
的时候传递的那个函数,会根据数组的长度执行- 数组的长度是多少,这个函数就会执行多少回
13.map (不改变原数组,返回新数组)
-
和
forEach
类似,只不过可以对数组中的每一项进行操作,返回一个新的数组var arr = [1, 2, 3] // 使用 map 遍历数组 var newArr = arr.map(function (item, index, arr) { // item 就是数组中的每一项 // index 就是数组的索引 // arr 就是原始数组 return item + 10 }) console.log(newArr) // [11, 12, 13]
14.filter==(不改变原数组,返回新增数组)==
-
和
map
的使用方式类似,按照我们的条件来筛选数组 -
把原始数组中满足条件的筛选出来,组成一个新的数组返回
var arr = [1, 2, 3] // 使用 filter 过滤数组 var newArr = arr.filter(function (item, nindex, arr) { // item 就是数组中的每一项 // index 就是数组的索引 // arr 就是原始数组 return item > 1 }) console.log(newArr) // [2, 3]
- 我们设置的条件就是
> 1
- 返回的新数组就会是原始数组中所有
> 1
的项
15.every 返回布尔值
判断 是否数组所有的元素都满足条件
let flag = arr.every(function(item,index,arr){ return 条件 }) let res4 = arr4.every(function (v) { return v >= 18 }) console.log(res4);
- 我们设置的条件就是
16.some 返回布尔值
判断是否数组有一些元素满足条件
let flag = arr.some(function(item,index,arr){
return 条件
})
let res5 = arr4.some(function (v) {
return v >= 18
})
console.log(res5);//true
17.find
查找数组中第一个满足条件的元素
var arr6 = [10, 19, 17, 20];
let res6 = arr6.find(function (v, i) {
return v === 19
})
console.log(res6);
var arr7 = [{ id: 1, name: 'zs' }, { id: 2, name: 'lisi' }];
var obj = arr7.find(function (v) {
return v.id == 2
})
console.log(obj);
console.log(obj.name);
18.findIndex
查找数组中第一个满足条件的元素的下标
var res7 = arr7.findIndex(function (v) {
return v.id == 2
})
console.log(res7);
19.reduce
let res8 = arr8.reduce(function (a, b) {
console.log(a, b);
//a 第一次是 数组中第一个元素
//10+19 =29
// 然后几次 就是前面 数字的和
//a = 29
// b 17
return a + b;
})
console.log(res8);
let res9 = arr8.reduce(function (a, b) {
console.log(a, b);
//a 是累积变量 ,1000就是累积变量的初始值
// b 是数组的元素
return a + b;
}, 1000)
console.log(res9);
20.fill() es6 新增 (会改变原数组)
fill()方法能使用特定值填充数组中的一个或多个元素。当只是用一个参数时,该方法会用该参数的值填充整个数组。
let arr = [1, 2, 3, cc , 5];
arr.fill(1);
console.log(arr);//[1,1,1,1,1];
如果不想改变数组中的所有元素,而只是想改变其中一部分,那么可以使用可选的起始位置参数与结束位置参数(不包括结束位置的那个元素)
3 个参数: 填充数值,起始位置参数,结束位置参数(不包括结束位置的那个元素)
let arr = [1, 2, 3, arr , 5];
arr.fill(1, 2);
console.log(arr);//[1,2,1,1,1]
arr.fill(0, 1, 3);
console.log(arr);//[1,0,0,1,1];
21.includes() es7 新增,返回布尔值
includes() 方法用来判断一个数组是否包含一个指定的值,如果是返回 true,否则 false。
参数有两个,其中第一个是(必填)需要查找的元素值,第二个是(可选)开始查找元素的位置
const array1 = [22, 3, 31, 12, arr ];
const includes = array1.includes(31);
console.log(includes); // true
const includes1 = array1.includes(31, 3); // 从索引3开始查找31是否存在
console.log(includes1); // false
需要注意的是:includes使用===运算符来进行值比较,仅有一个例外:NaN 被认为与自身相等。
let values = [1, NaN, 2];
console.log(values.indexOf(NaN));//-1
console.log(values.includes(NaN));//true
22.toLocaleString() 和 toString()
将数组转换为字符串
const array1 = [22, 3, 31, 12];
const str = array1.toLocaleString();
const str1 = array1.toString();
console.log(str); // 22,3,31,12
console.log(str1); // 22,3,31,12
23.copyWithin() [es6 新增](该方法会改变现有数组)
copyWithin() 方法用于从数组的指定位置拷贝元素到数组的另一个指定位置中。
//将数组的前两个元素复制到数组的最后两个位置
let arr = [1, 2, 3, arr , 5];
arr.copyWithin(3, 0);
console.log(arr);//[1,2,3,1,2]
默认情况下,copyWithin()方法总是会一直复制到数组末尾,不过你还可以提供一个可选参数来限制到底有多少元素会被覆盖。这第三个参数指定了复制停止的位置(不包含该位置本身)。
let arr = [1, 2, 3, arr , 5, 9, 17];
//从索引3的位置开始粘贴
//从索引0的位置开始复制
//遇到索引3时停止复制
arr.copyWithin(3, 0, 3);
console.log(arr);//[1,2,3,1,2,3,17]
24.flat() 和 flatMap() es6 新增(该方法返回一个新数组,不改变原数组。)
flat() 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。
该方法返回一个新数组,对原数据没有影响。
参数: 指定要提取嵌套数组的结构深度,默认值为 1。
const arr1 = [0, 1, 2, [3, 4]];
console.log(arr1.flat());
// expected output: [0, 1, 2, 3, 4]
const arr2 = [0, 1, 2, [[[3, 4]]]];
console.log(arr2.flat(2));
// expected output: [0, 1, 2, [3, 4]]
//使用 Infinity,可展开任意深度的嵌套数组
var arr4 = [1, 2, [3, 4, [5, 6, [7, 8, [9, 10]]]]];
arr4.flat(Infinity);
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
// 扁平化数组空项,如果原数组有空位,flat()方法会跳过空位
var arr4 = [1, 2, , 4, 5];
arr4.flat();
// [1, 2, 4, 5]
flatMap()方法对原数组的每个成员执行一个函数,相当于执行Array.prototype.map(),然后对返回值组成的数组执行flat()方法。
该方法返回一个新数组,不改变原数组。
// 相当于 [[2, 4], [3, 6], [4, 8]].flat()
[2, 3, 4].flatMap((x) => [x, x * 2])
// [2, 4, 3, 6, 4, 8]
25、 entries(),keys() 和 values() 【ES6】
entries(),keys()和values() —— 用于遍历数组。它们都返回一个遍历器对象,可以用for…of循环进行遍历
区别是keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历
for (let index of [ a , b ].keys()) {
console.log(index);
}
// 0
// 1
for (let elem of [ a , b ].values()) {
console.log(elem);
}
// a
// b
for (let [index, elem] of [ a , b ].entries()) {
console.log(index, elem);
}
// 0 "a"
// 1 "b"
如果不使用for…of循环,可以手动调用遍历器对象的next方法,进行遍历。
let letter = [ a , b , c ];
let entries = letter.entries();
console.log(entries.next().value); // [0, a ]
console.log(entries.next().value); // [1, b ]
console.log(entries.next().value); // [2, c ]
JS 如何终止 forEach 循环 break 报错,return 跳不出循环
-
终止 forEach 可以使用 try catch 内部抛出错误,catch 捕获错误。
let arr = [1, 2, 3] try { arr.forEach(item => { if (item === 2) { throw('循环终止') } console.log(item) }) } catch(e) { console.log('e: ', e) } 复制代码
当然我们大可以用其他方法代替
-
Array.prototype.some
当 return true 的时候,会终止遍历
-
Array.prototype.every
当 return false 的时候,会终止遍历
JS的节流、防抖及使用场景
结合应用场景
- debounce
- search搜索联想,用户在不断输入值时,用防抖来节约请求资源。
- window触发resize的时候,不断的调整浏览器窗口大小会不断的触发这个事件,用防抖来让其只触发一次
- throttle
- 鼠标不断点击触发,mousedown(单位时间内只触发一次)
- 监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断
JS 运行机制-EventLoop(事件循环)
javascript 是单线程的
JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。那么,为什么JavaScript不能有多个线程呢?这样能提高效率啊。
JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?
所以,为了避免复杂性,从一诞生,JavaScript就是单线程。
为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完
全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。
主线程和任务队列
单线程就意味着,所有任务需要排队。所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。
同步任务:在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;
异步任务:不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
具体来说:
-
所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
-
主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件
-
一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",根据顺序循环调用异步任务,进入执行栈,开始执行,直到所有任务队列异步任务执行完
-
另外,任务队列中的每一个事件都是一个宏任务,执行栈执行的过程中也会有微任务,他们的执行顺序是:见下面总结
下图就是主线程和任务队列的示意图
主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。
只要主线程空了,就会去读取"任务队列",这就是JavaScript的运行机制。
JS中Map和ForEach的区别
区别
forEach()方法不会返回执行结果,而是undefined。也就是说,forEach()会修改原来的数组。而map()方法会得到一个新的数组并返回。
css实现0.5px线
使用scale缩放
<style>
.hr.scale-half {
height: 1px;
transform: scaleY(0.5);
}
</style>
<p>1px + scaleY(0.5)</p>
<div class="hr scale-half"></div>
css实现三角形
.triangle {
width: 0; height: 0;
border: 100px solid; border-color: orangered skyblue gold yellowgreen;
}
localStorage、sessionStorage、cookie区别
一、区别
- localStorage: localStorage 的生命周期是永久的,关闭页面或浏览器之后 localStorage 中的数据也不会消失。localStorage 除非主动删除数据,否则数据永远不会消失
- sessionStorage: sessionStorage 的生命周期是仅在当前会话下有效。sessionStorage 引入了一个“浏览器窗口”的概念,sessionStorage 是在同源的窗口中始终存在的数据。只要这个浏览器窗口没有关闭,即使刷新页面或者进入同源另一个页面,数据依然存在。但是 sessionStorage 在关闭了浏览器窗口后就会被销毁。同时独立的打开同一个窗口同一个页面,sessionStorage 也是不一样的
- cookie: cookie生命期为只在设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭。 存放数据大小为4K左右, 有个数限制(各浏览器不同),一般不能超过20个。缺点是不能储存大数据且不易读取
正文
Cookie
数据生命性:一般由服务器生成,可设置失效时间。(也可以由客户端生成)
存放数据大小: 一般大小不能超过4KB
作用域:Cookie的作用域仅仅由domain和path决定,与协议和端口无关
与服务器端通信:浏览器每次向服务器发出请求,就会自动把当前域名下所有未过期的Cookie一同发送到服务器(会带来额外的性能开销)
易用性:缺乏数据操作接口(document.cookie)。
适用场景:只有那些每次请求都需要让服务器知道的信息(保持用户的登录状态),才应该放在 Cookie 里面。
localStorage
数据生命性::存储在 localStorage 的数据可以长期保留;
存放数据大小: 一般为5MB
作用域:localStorage的作用域是限定在文档源级别的。文档源通过协议、主机名以及端口三者来确定。
与服务器端通信:不会自动把数据发给服务器,仅在本地保存。
易用性:有很多易用的数据操作接口,比如setItem、getItem、removeItem
适用场景:常用于长期登录(+判断用户是否已登录),适合长期保存在本地的数据
sessionStorage
数据生命性:sessionStorage 里面的数据在页面会话结束(关闭对应浏览器标签或窗口)时会被清除。
存放数据大小: 一般为5MB
作用域:sessionStorage的作用域也是限定在文档源级别。但需要注意的是,如果相同文档源的页面渲染在不同的标签中,sessionStorage的数据是无法共享的。
与服务器端通信:不会自动把数据发给服务器,仅在本地保存。
易用性:有很多易用的数据操作接口,比如setItem、getItem、removeItem
适用场景:敏感账号一次性登录;
Web Storage (localStorage、sessionStorage)的优势
存储空间更大
更节省流量(没有额外性能开销)
获取数据从本地获取会比服务器端获取快得多,所以显示更快
IndexedDB(补充内容)
IndexedDB 就是浏览器提供的本地数据库,它可以被网页脚本创建和操作。
IndexedDB 不属于关系型数据库(不支持 SQL 查询语句),更接近 NoSQL 数据库。
IndexedDB储存空间大,一般来说不少于 250MB,甚至没有上限。
IndexedDB不仅可以储存字符串,还可以储存二进制数据(ArrayBuffer 对象和 Blob 对象)。
提供查找接口,还能建立索引
异步操作:IndexedDB 操作时不会锁死浏览器,用户依然可以进行其他操作,这与 LocalStorage 形成对比,后者的操作是同步的。异步设计是为了防止大量数据的读写,拖慢网页的表现。
JS中find和filter的区别
find的用法
通俗的讲find是找到数组中符合条件的第一项,看下面代码
filter的用法
filter的功能也是过滤,不过是在遍历数组后拿到所有符合条件的item项
nuxt上线
.nuxt文件、nuxt.config.js、package.json、static
原生js怎样获取参数
window.location.search
在JavaScript中,可以使用window.location
对象来获取地址栏传递的参数。具体可以通过以下几种方式获取:
- 使用
window.location.search
获取查询字符串参数:
javascriptCopy code// 假设当前页面的URL为:http://example.com?param1=value1¶m2=value2
const queryString = window.location.search; // 获取查询字符串部分,即 "?param1=value1¶m2=value2"
// 使用URLSearchParams解析查询字符串
const params = new URLSearchParams(queryString);
// 获取特定参数的值
const param1Value = params.get('param1'); // "value1"
const param2Value = params.get('param2'); // "value2"
- 使用
window.location.href
获取完整URL,然后通过字符串操作截取参数:
javascriptCopy code// 假设当前页面的URL为:http://example.com?param1=value1¶m2=value2
const url = window.location.href; // 获取完整URL
// 截取参数部分
const queryString = url.split('?')[1];
// 使用URLSearchParams解析查询字符串
const params = new URLSearchParams(queryString);
// 获取特定参数的值
const param1Value = params.get('param1'); // "value1"
const param2Value = params.get('param2'); // "value2"
请注意,URLSearchParams
是ES6引入的新特性,用于处理URL查询字符串。在使用前请确保浏览器的兼容性。
使用以上方法,您可以在JavaScript中获取地址栏传递的参数。首先获取完整URL或查询字符串,然后使用适当的方式解析和提取参数的值。
ts接口定义类型怎样使用里面某一个类型的值
在TypeScript中,可以使用接口定义类型,并且可以通过类型注解将接口中的某个属性的类型应用到其他变量或函数中。
以下是一个示例,展示了如何使用接口中的某个类型的值:
typescriptCopy codeinterface Person {
name: string;
age: number;
}
// 定义一个接口类型的变量
const person: Person = {
name: 'John',
age: 25,
};
// 使用接口中的某个类型的值
let personName: string = person.name;
let personAge: number = person.age;
console.log(personName); // 输出 "John"
console.log(personAge); // 输出 25
在这个示例中,我们定义了一个名为Person
的接口,该接口包含name
和age
属性的类型定义。然后,我们创建了一个person
变量,该变量符合Person
接口的定义。
接下来,我们使用person
对象中的name
和age
属性的类型注解,将其应用到personName
和personAge
变量上。这样,personName
的类型就是string
,personAge
的类型就是number
,并且它们分别被赋值为person
对象中对应的属性值。
通过这种方式,我们可以在TypeScript中定义接口并利用接口中的某个类型的值,以便在其他变量或函数中使用相应的类型
js存储数组方式
数组的存储结构
1.基本数据类型变量都存储在栈区域
2.引用变量存储在堆区域,如对象。
3.复杂数据类型是存储在堆区域,赋值时是将对象在堆区域的地址赋值给变量
4.- 因为复杂数据类型,变量存储的是地址,真实内容在 堆空间 内存储
- 所以赋值的时候相当于把 obj 存储的那个地址复制了一份给到了 obj1变量
现在 obj 和 obj1 两个变量存储的地址一样,指向一个内存空间
- 所以使用 obj1 这个变量修改空间内的内容,obj 指向的空间也会跟着改变了
vue2为啥不对数组进行劫持呢
Vue.js 2.x中的响应式系统使用了一种称为"劫持"或"代理"的技术来实现对对象属性的监测和响应。然而,Vue.js 2.x没有直接对数组进行劫持,而是通过重写数组的原型方法来实现对数组的响应式支持。
Vue.js之所以选择不直接对数组进行劫持,是因为数组的变异方法(例如push、pop、splice等)是无法通过普通的对象属性劫持来监听的。如果Vue.js直接对数组进行劫持,那么只有通过Vue提供的特殊方法才能保证响应式更新,而不能直接使用JavaScript原生的数组方法。这样会导致语法和用法上的不一致,增加了学习和使用的复杂性。
相反,Vue.js 2.x通过重写数组的原型方法(例如push、pop、splice等)来捕获数组的变化,并触发响应式更新。这意味着当你使用这些变异方法修改数组时,Vue.js能够监听到这些变化并更新相关的视图。
总结一下,Vue.js 2.x选择不对数组进行直接劫持的原因是为了保持语法和用法上的一致性,避免增加复杂性,并通过重写数组的原型方法来实现对数组的响应式支持。
浏览输入URL地址会进过那些流程
当你在浏览器中输入URL地址并按下回车键时,以下是浏览器的一般流程:
- URL解析:浏览器会解析输入的URL,提取出协议(如HTTP、HTTPS)、主机名(例如www.example.com)和路径(如/page)等信息。
- DNS解析:浏览器将主机名发送给域名系统(DNS)服务器,以获取与主机名对应的IP地址。这个过程涉及到查询DNS缓存、本地主机文件和递归查询等步骤,最终得到主机的IP地址。
- 建立TCP连接:使用获取到的IP地址,浏览器会与服务器建立TCP连接。这个过程涉及到TCP的三次握手,确保浏览器和服务器之间建立可靠的连接。
- 发起HTTP请求:建立TCP连接后,浏览器会向服务器发送HTTP请求。这个请求包括请求方法(如GET、POST)、请求头(包含一些额外的信息,如用户代理、Cookie等)以及可能的请求体(对于POST请求)。
- 服务器处理请求:服务器接收到浏览器发送的请求后,根据请求的路径和其他信息,处理请求并生成响应。
- 接收响应:浏览器接收到服务器发送的响应,响应包括状态码(表示请求成功、失败或其他状态)、响应头(包含一些元数据,如内容类型、缓存控制等)和响应体(实际的响应数据)。
- 渲染页面:如果响应的内容是HTML,浏览器会解析HTML、构建DOM树和CSSOM树,然后将它们合并为渲染树,最终绘制到屏幕上。
- 关闭连接:页面渲染完成后,浏览器会关闭与服务器的TCP连接,释放资源。
需要注意的是,上述流程是一个简化的描述,实际上还包括其他步骤和优化。例如,浏览器可能会使用HTTP持久连接来重复使用已经建立的连接,减少TCP握手的开销;还有缓存机制、安全验证等等。但总体来说,上述流程涵盖了从输入URL到页面展示的主要步骤。
js数组方法怎样把二位数组变成一维数组
JavaScript提供了多种方法将二维数组转换为一维数组。以下是几种常见的方法:
- 使用
concat()
方法和扩展运算符(spread operator):
javascriptCopy codeconst twoDimensionalArray = [[1, 2], [3, 4], [5, 6]];
const flattenedArray = [].concat(...twoDimensionalArray);
console.log(flattenedArray);
输出:
javascriptCopy code
[1, 2, 3, 4, 5, 6]
- 使用
reduce()
方法:
javascriptCopy codeconst twoDimensionalArray = [[1, 2], [3, 4], [5, 6]];
const flattenedArray = twoDimensionalArray.reduce((result, currentArray) => result.concat(currentArray), []);
console.log(flattenedArray);
输出:
javascriptCopy code
[1, 2, 3, 4, 5, 6]
- 使用
flatMap()
方法(适用于ES2019及以上版本):
javascriptCopy codeconst twoDimensionalArray = [[1, 2], [3, 4], [5, 6]];
const flattenedArray = twoDimensionalArray.flatMap((currentArray) => currentArray);
console.log(flattenedArray);
输出:
javascriptCopy code
[1, 2, 3, 4, 5, 6]
这些方法都能将二维数组中的所有元素合并到一个新的一维数组中。你可以根据自己的需求选择适合的方法来转换二维数组。
1.setup函数怎么实现
import unittest
class TestMyFunction(unittest.TestCase):
def setUp(self):
# 在这里设置你的测试环境
# 例如,初始化一个数据库连接,或者创建一个临时文件等
pass
def tearDown(self):
# 在这里清理你的测试环境
# 例如,关闭数据库连接,删除临时文件等
pass
def test_my_function(self):
# 在这里编写你的测试代码
pass
if name == ‘main’:
unittest.main()
2.websocket会不会跨域
WebSocket本身是不存在跨域问题的,因此,可以利用WebSocket来进行非同源之间的通信。这意味着你可以自由地创建WebSocket实例并通过open方法发送数据到后台,同时也可以利用message方法接收后台传来的数据。
然而,在实践中,你可能仍会碰到一些跨域问题。例如,如果你使用socket.io库,它只会使用WebSocket传输协议进行连接,从而避免一些跨域问题。在前端解决io访问跨域问题,可以通过一些配置和选项来实现。
3.怎么封装aixos
要封装axios,首先需要安装axios,可以通过npm install axios --save命令进行安装。然后,在项目中创建一个http.js文件,在这个文件中进行二次封装。
封装的主要目的是为了使axios的使用更加方便和高效。以下是一些常见的封装方法:
基本配置:可以配置默认的请求地址、超时时间等。
请求拦截:可以在发送请求之前做一些处理,例如添加token到header中等。
响应拦截:可以对响应数据进行处理,例如统一处理错误信息等。
封装实例:可以将axios实例化,并在需要的地方引入和使用。
创建API:可以根据项目需求,创建对应的API,如get、post、put、delete等,并保持参数格式一致。
4.pinia怎么用
Pinia是一个Vue的状态管理库,相当于Vuex的替代品,主要用来实现跨组件或页面的状态管理。下面是如何在项目中使用Pinia的步骤:
安装Pinia:可以通过npm install pinia命令进行安装。
引入Pinia:在项目的main.js文件中引入Pinia并创建容器,然后挂载到根实例上。例如:import { createPinia } from ‘pinia’ const pinia = createPinia()。
创建Store:在项目中创建一个store文件夹,然后在该文件夹中新建一个文件(例如user.ts),用于定义仓库Store。
修改Vue应用:在使用app.use(pinia)后,你的所有Vue组件现在都可以访问到pinia提供的功能
5.怎么首页白屏优化
减少HTTP请求:合并CSS和JavaScript文件,使用CSS Sprites等技术来减少请求的数量。
使用CDN加速:将静态资源部署到CDN上,以加速资源的加载速度。
开启GZIP压缩:启用服务器端的GZIP压缩可以减少文件大小,从而加快加载速度。
优化图片:通过压缩图片大小、选择合适的图片格式等方式来减少图片的加载时间。
服务端渲染:在服务端将渲染逻辑处理好,然后将处理好的HTML直接返回给前端展示,这样既可以解决白屏问题,也可以提高SEO效果。
6.vue2和vue3中有哪些优点和缺点
Vue2的优点主要体现在以下几个方面:首先,Vue2简单易学,上手成本低,使得开发者能够快速构建用户界面。其次,Vue2支持渐进式框架,可以适用于小型到大型的项目。最后,双向数据绑定和模板语法使Vue2在处理复杂场景时渲染效率较高。然而,Vue2也存在一些不足之处,例如在处理大型应用时,其性能和体积控制不如React好。
Vue3则在许多方面进行了优化和改进。首先,Vue3的diff算法的优化使得其在渲染大量静态节点时更加高效。此外,Vue3的速度更快,相比Vue2来说,重写了虚拟 Dom 实现,编译模板的优化,更高效的组件初始化,update性能提高1.32倍,SSR速度提高了23倍。同时,通过webpack的tree-shaking功能,Vue3可以将无用模块“剪辑”,仅打包需要的模块,这样既可以帮助开发人员实现更多其他的功能,而不必担忧整体体积过大,也对使用者有益,因为打包出来的包体积变小了。另外,
字节笔试题
ts interface和type有哪些区别
- 可扩展性(Extensibility):
interface
是可扩展的。这意味着你可以在后续的代码中多次声明同一个接口,TypeScript会合并它们成一个。type
也是可扩展的,但它支持更广泛的合并。例如,可以使用联合类型、交叉类型等更复杂的合并。
- 合并(Declaration Merging):
interface
具有声明合并的特性,当定义多个同名的接口时,它们会自动合并成一个。type
也支持合并,但相较于interface
,type
的合并更为灵活。
- 适用场景:
- 通常来说,如果你要描述对象的形状,使用
interface
会更自然。 - 如果你需要联合类型、交叉类型、映射类型,或者其他高级类型,使用
type
会更合适。
- 通常来说,如果你要描述对象的形状,使用
- 实现(Implements):
interface
可以用于类的实现,通过implements
关键字。type
不支持类的实现。
- 可辨识联合(Discriminated Unions):
interface
可以用于创建可辨识联合类型,通过添加共同的字段,实现在联合类型中判断具体类型。type
也可以创建可辨识联合,但通常情况下,interface
更为常见。
- 兼容性(Compatibility):
interface
和type
在很多情况下是可以互相替代的,但在一些复杂的类型操作中,它们的行为可能略有不同。
any和unknown 区别
any
是一种逃避类型检查的手段,但这也意味着你失去了 TypeScript 提供的类型安全性。
unknown
类型也表示任何类型的值,但与any
不同,对unknown
类型的值的操作受到限制。- 当你有一个值的类型是
unknown
时,你必须在使用之前进行类型检查或类型断言,否则 TypeScript 不允许对其进行任何操作。 unknown
类型更为安全,因为它要求你在使用之前明确地处理类型
基本概念
HTTP(HyperText Transfer Protocol:超文本传输协议)是一种用于分布式、协作式和超媒体信息系统的应用层协议。 简单来说就是一种发布和接收 HTML 页面的方法,被用于在 Web 浏览器和网站服务器之间传递信息。
HTTP 默认工作在 TCP 协议 80 端口,用户访问网站 http:// 打头的都是标准 HTTP 服务。
HTTP 协议以明文方式发送内容,不提供任何方式的数据加密,如果攻击者截取了Web浏览器和网站服务器之间的传输报文,就可以直接读懂其中的信息,因此,HTTP协议不适合传输一些敏感信息,比如:信用卡号、密码等支付信息。
HTTPS(Hypertext Transfer Protocol Secure:超文本传输安全协议)是一种透过计算机网络进行安全通信的传输协议。HTTPS 经由 HTTP 进行通信,但利用 SSL/TLS 来加密数据包。HTTPS 开发的主要目的,是提供对网站服务器的身份认证,保护交换数据的隐私与完整性。
HTTPS 默认工作在 TCP 协议443端口,它的工作流程一般如以下方式:
- 1、TCP 三次同步握手
- 2、客户端验证服务器数字证书
- 3、DH 算法协商对称加密算法的密钥、hash 算法的密钥
- 4、SSL 安全加密隧道协商完成
- 5、网页以加密的方式传输,用协商的对称加密算法和密钥加密,保证数据机密性;用协商的hash算法进行数据完整性保护,保证数据不被篡改。
截至 2018 年 6 月,Alexa 排名前 100 万的网站中有 34.6% 使用 HTTPS 作为默认值,互联网 141387 个最受欢迎网站的 43.1% 具有安全实施的 HTTPS,以及 45% 的页面加载(透过Firefox纪录)使用HTTPS。2017 年3 月,中国注册域名总数的 0.11%使用 HTTPS。
根据 Mozilla 统计,自 2017 年 1 月以来,超过一半的网站流量被加密。
HTTP 与 HTTPS 区别
- HTTP 明文传输,数据都是未加密的,安全性较差,HTTPS(SSL+HTTP) 数据传输过程是加密的,安全性较好。
- 使用 HTTPS 协议需要到 CA(Certificate Authority,数字证书认证机构) 申请证书,一般免费证书较少,因而需要一定费用。证书颁发机构如:Symantec、Comodo、GoDaddy 和 GlobalSign 等。
- HTTP 页面响应速度比 HTTPS 快,主要是因为 HTTP 使用 TCP 三次握手建立连接,客户端和服务器需要交换 3 个包,而 HTTPS除了 TCP 的三个包,还要加上 ssl 握手需要的 9 个包,所以一共是 12 个包。
- http 和 https 使用的是完全不同的连接方式,用的端口也不一样,前者是 80,后者是 443。
- HTTPS 其实就是建构在 SSL/TLS 之上的 HTTP 协议,所以,要比较 HTTPS 比 HTTP 要更耗费服务器资源。
TCP 三次握手
在TCP/IP协议中,TCP协议通过三次握手建立一个可靠的连接
- 第一次握手:客户端尝试连接服务器,向服务器发送 syn 包(同步序列编号Synchronize Sequence Numbers),syn=j,客户端进入 SYN_SEND 状态等待服务器确认
- 第二次握手:服务器接收客户端syn包并确认(ack=j+1),同时向客户端发送一个 SYN包(syn=k),即 SYN+ACK 包,此时服务器进入 SYN_RECV 状态
- 第三次握手:第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手
简化:
HTTPS 的工作原理
我们都知道 HTTPS 能够加密信息,以免敏感信息被第三方获取,所以很多银行网站或电子邮箱等等安全级别较高的服务都会采用 HTTPS 协议。
1、客户端发起 HTTPS 请求
这个没什么好说的,就是用户在浏览器里输入一个 https 网址,然后连接到 server 的 443 端口。
2、服务端的配置
采用 HTTPS 协议的服务器必须要有一套数字证书,可以自己制作,也可以向组织申请,区别就是自己颁发的证书需要客户端验证通过,才可以继续访问,而使用受信任的公司申请的证书则不会弹出提示页面(startssl 就是个不错的选择,有 1 年的免费服务)。
这套证书其实就是一对公钥和私钥,如果对公钥和私钥不太理解,可以想象成一把钥匙和一个锁头,只是全世界只有你一个人有这把钥匙,你可以把锁头给别人,别人可以用这个锁把重要的东西锁起来,然后发给你,因为只有你一个人有这把钥匙,所以只有你才能看到被这把锁锁起来的东西。
3、传送证书
这个证书其实就是公钥,只是包含了很多信息,如证书的颁发机构,过期时间等等。
4、客户端解析证书
这部分工作是有客户端的TLS来完成的,首先会验证公钥是否有效,比如颁发机构,过期时间等等,如果发现异常,则会弹出一个警告框,提示证书存在问题。
如果证书没有问题,那么就生成一个随机值,然后用证书对该随机值进行加密,就好像上面说的,把随机值用锁头锁起来,这样除非有钥匙,不然看不到被锁住的内容。
5、传送加密信息
这部分传送的是用证书加密后的随机值,目的就是让服务端得到这个随机值,以后客户端和服务端的通信就可以通过这个随机值来进行加密解密了。
6、服务端解密信息
服务端用私钥解密后,得到了客户端传过来的随机值(对称秘钥),然后把内容通过该值进行对称加密,所谓对称加密就是,将信息和私钥通过某种算法混合在一起,这样除非知道私钥,不然无法获取内容,而正好客户端和服务端都知道这个私钥,所以只要加密算法够彪悍,私钥够复杂,数据就够安全。
7、传输加密后的信息
这部分信息是服务段用私钥加密后的信息,可以在客户端被还原。
8、客户端解密信息
客户端用之前生成的私钥解密服务段传过来的信息,于是获取了解密后的内容,整个过程第三方即使监听到了数据,也束手无策。
跨域结局方案及原理
-
JSONP (JSON with Padding):
- 原理:利用
<script>
标签不受同源策略的限制的特性,通过在请求中嵌入回调函数的方式来实现跨域请求。 - 使用场景:适用于只支持JSONP的服务器。
- 原理:利用
-
CORS (Cross-Origin Resource Sharing):
- 原理:服务器在响应头中添加CORS相关的信息,浏览器根据这些信息决定是否允许跨域请求。
- 使用场景:适用于现代浏览器,并且需要服务器端支持。
-
代理服务器:
- 原理:在同一域下设置一个代理服务器,前端请求该代理服务器,再由代理服务器向目标服务器发起请求,最后将响应返回给前端。
- 使用场景:适用于无法修改目标服务器响应头的情况。
-
WebSocket:
- 原理:WebSocket协议允许在不受同源策略限制的情况下进行双向通信。
- 使用场景:适用于需要实时通信的场景,支持现代浏览器。
-
跨文档消息(Cross-document Messaging):
- 原理:使用
window.postMessage
方法在不同窗口或框架之间传递消息,绕过同源策略。 - 使用场景:适用于在不同窗口或框架之间进行简单的数据传递。
原理:ip地址、域名、端口号一致
- 原理:使用
react中differ算法
React 中的 “Reconciliation”(协调)是指在虚拟 DOM 中进行比较和更新的过程。React 使用一种称为 “Reconciliation Algorithm” 的算法来确定如何高效地更新视图,这也被称为 “diffing”(差异化比较)算法。React 的 diff 算法主要发生在虚拟 DOM 层面,而非实际的 DOM 元素。
以下是 React diff 算法的主要思想:
- 同层比较: React 假设相邻的两个组件拥有相似的结构,因此在进行差异比较时,它会对树的同层节点进行比较,而不会跨层级。
- 节点类型比较: React 会比较节点的类型(例如,组件类型或 HTML 元素类型)。如果节点类型不同,React 会销毁旧节点并创建新节点,而不会深度比较其子节点。
- 唯一 key 的使用: React 鼓励使用唯一的
key
属性来标识列表中的各个元素,以便更准确地定位差异。当列表项的顺序改变时,React 可以更精确地识别出是移动操作而不是销毁和重建。 - 逐层比较子节点: 如果节点类型相同,React 会逐层比较它们的子节点。这个过程是递归的,使得 React 能够在虚拟 DOM 树中高效地找到变化。
- 函数组件和类组件的处理: React 16 之前的版本中,函数组件和类组件的处理方式略有不同。在 React 16 中引入了
Fiber
架构,函数组件和类组件的处理方式更一致,都被转化为Fiber
节点。
React diff 算法的目标是在性能和效率之间取得平衡,以便在进行页面更新时尽量减少 DOM 操作的次数。这使得 React 能够提供高性能的用户界面更新,同时保持代码的简洁性。要注意的是,React 的 diff 算法并不是完美的,有时候也可能出现一些不太直观的结果,因此在特定场景下需要进行手动优化。****
useMemo和useCallback区别
useMemo
和 useCallback
是 React 中用于性能优化的两个钩子,它们的作用有一些相似,但在使用场景和具体效果上有一些不同。
useMemo
:
- 返回值:
useMemo
的返回值是计算结果。 - 使用场景: 适用于在渲染过程中执行昂贵的计算,并希望缓存计算结果,避免在每次渲染时都重新计算。
- 依赖项: 接受一个依赖数组,当依赖项发生变化时,重新计算结果。如果依赖项没有变化,将返回上一次缓存的结果。
javascriptCopy code
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
useCallback
:
- 返回值:
useCallback
的返回值是一个 memoized 版本的回调函数。 - 使用场景: 适用于在渲染过程中传递回调函数给子组件,并希望子组件不会因为父组件的重新渲染而导致函数引用变化,从而触发不必要的重新渲染。
- 依赖项: 接受一个依赖数组,当依赖项发生变化时,返回一个新的回调函数。如果依赖项没有变化,将返回上一次缓存的回调函数。
javascriptCopy code
const memoizedCallback = useCallback(() => handleCallback(a, b), [a, b]);
主要区别:
- 返回值类型:
useMemo
返回计算结果,而useCallback
返回一个 memoized 版本的回调函数。 - 使用场景:
useMemo
适用于缓存计算结果,而useCallback
适用于缓存回调函数。 - 依赖项变化:
useMemo
和useCallback
都依赖于传入的依赖项数组,当数组中的某个值发生变化时,它们都会重新计算或返回新的值。
总的来说,useMemo
主要用于缓存计算结果,而 useCallback
主要用于缓存回调函数,但二者都可以用于避免在每次渲染时都重新创建对象或函数。
react的differ算法实现
React 的 diff 算法实现是基于 Virtual DOM(虚拟 DOM)的概念。以下是 React 的 diff 算法的基本实现原理:
- 生成 Virtual DOM 树: 在每次组件状态变化时,React 会重新生成一颗新的 Virtual DOM 树。这颗树结构包括了整个组件树的层级结构,每个节点表示一个虚拟元素。
- 生成新旧树的差异(Diffing): React 通过比较新旧两颗 Virtual DOM 树的差异,找出需要更新的部分。这一步是 diff 算法的核心。
- 递归更新差异: React 会递归地遍历差异,只更新实际发生变化的部分,而不更新没有变化的部分。
React 的 diff 算法采用了两个主要的策略来进行优化:
- 同层比较(Same Level Comparison): React 假设相邻的两个组件拥有相似的结构,因此在进行差异比较时,它会对树的同层节点进行比较,而不会跨越层级。
- 唯一 key 的使用: React 鼓励使用唯一的
key
属性来标识列表中的各个元素,以便更准确地定位差异。当列表项的顺序改变时,React 可以更精确地识别出是移动操作而不是销毁和重建。
React 的 diff 算法的时间复杂度通常是 O(n),其中 n 是 Virtual DOM 树的节点数量。这使得 React 能够在性能和效率之间取得平衡,以提高页面更新的速度。
需要注意的是,React 在不同版本中可能对 diff 算法进行了一些优化和改进,确保在不同场景下都能够提供较好的性能。
ES6 模块化、CommonJS 模块化的区别
-
语法差异:
- ES6 模块化 使用
import
和export
关键字来导入和导出模块。
javascriptCopy code// 导入模块 import { someFunction } from 'someModule'; // 导出模块 export function myFunction() { // code }
- CommonJS 模块化 使用
require
和module.exports
或exports
来导入和导出模块。
javascriptCopy code// 导入模块 const someModule = require('someModule'); // 导出模块 module.exports = { myFunction: function() { // code } };
- ES6 模块化 使用
-
加载时机:
- ES6 模块化 是静态的,意味着模块在解析阶段就会被加载,而不是在运行时。这使得工具可以在编译时进行优化。
- CommonJS 模块化 是动态的,模块在运行时加载,这使得一些优化手段无法在编译时进行。
-
导入和导出的方式:
- ES6 模块化 是明确的,你必须在代码的顶层使用
import
和export
,不能在条件语句中使用。 - CommonJS 模块化 允许在运行时动态导入模块,也可以在条件语句中使用
require
。
- ES6 模块化 是明确的,你必须在代码的顶层使用
-
导出的内容:
- ES6 模块化 可以导出任何 JavaScript 变量,包括基本类型、函数、类等。
- CommonJS 模块化 主要导出的是对象,可以将多个变量放在一个对象中导出。
-
循环依赖处理:
- ES6 模块化 能够正确处理循环依赖,因为在静态解析阶段就已经确定了导入关系。
- CommonJS 模块化 对循环依赖的处理相对复杂,可能需要在运行时解析。
总体来说,ES6 模块化更加现代且具有一些优势,尤其在静态分析和编译优化方面。然而,CommonJS 仍然在许多服务器端和早期的前端项目中广泛使用。在前端开发中,随着浏览器对 ES6 模块的支持变得更加普遍,ES6 模块化正逐渐成为主流。
js实现深拷贝和浅拷贝
浅拷贝(Shallow Copy):
浅拷贝只复制对象的一层结构,如果对象内部包含引用类型(如数组、对象),则只复制引用而不复制引用指向的对象。
1. 使用展开运算符(Spread Operator):
const shallowCopy = (original) => ({ ...original });
// 示例
const originalObject = { a: 1, b: { c: 2 } };
const shallowCopiedObject = shallowCopy(originalObject);
console.log(shallowCopiedObject); // { a: 1, b: { c: 2 } }
console.log(originalObject.b === shallowCopiedObject.b); // true,引用相同
2. 使用Object.assign:
const shallowCopy = (original) => Object.assign({}, original);
// 示例
const originalObject = { a: 1, b: { c: 2 } };
const shallowCopiedObject = shallowCopy(originalObject);
console.log(shallowCopiedObject); // { a: 1, b: { c: 2 } }
console.log(originalObject.b === shallowCopiedObject.b); // true,引用相同
深拷贝(Deep Copy):
深拷贝会递归地复制对象及其嵌套结构,确保每个对象都是全新的。
1. 使用JSON.stringify和JSON.parse:
const deepCopy = (original) => JSON.parse(JSON.stringify(original));
// 示例
const originalObject = { a: 1, b: { c: 2 } };
const deepCopiedObject = deepCopy(originalObject);
console.log(deepCopiedObject); // { a: 1, b: { c: 2 } }
console.log(originalObject.b === deepCopiedObject.b); // false,引用不同
请注意,使用JSON.stringify和JSON.parse的方法有一些限制,例如不能处理循环引用、函数等特殊情况。
2. 使用递归:
const deepCopy = (original) => {
if (typeof original !== 'object' || original === null) {
return original;
}
const copiedObject = Array.isArray(original) ? [] : {};
for (const key in original) {
if (original.hasOwnProperty(key)) {
copiedObject[key] = deepCopy(original[key]);
}
}
return copiedObject;
};
// 示例
const originalObject = { a: 1, b: { c: 2 } };
const deepCopiedObject = deepCopy(originalObject);
console.log(deepCopiedObject); // { a: 1, b: { c: 2 } }
console.log(originalObject.b === deepCopiedObject.b); // false,引用不同
js数组方法
JavaScript提供了丰富的数组方法,用于对数组进行各种操作和转换。以下是一些常用的数组方法:
-
forEach(callback(element, index, array)): 遍历数组并对每个元素执行回调函数。
const array = [1, 2, 3]; array.forEach((element, index) => { console.log(`Index ${index}: ${element}`); });
-
map(callback(element, index, array)): 创建一个新数组,其元素是原数组经过回调函数处理后的结果。
const array = [1, 2, 3]; const squaredArray = array.map((element) => element ** 2);
-
filter(callback(element, index, array)): 创建一个新数组,其中包含原数组中满足条件的元素。
const array = [1, 2, 3, 4, 5]; const evenNumbers = array.filter((element) => element % 2 === 0);
-
reduce(callback(accumulator, element, index, array), initialValue): 对数组中的所有元素执行一个累加器函数,返回一个累积的结果。
const array = [1, 2, 3, 4]; const sum = array.reduce((accumulator, element) => accumulator + element, 0);
-
find(callback(element, index, array)): 返回数组中满足条件的第一个元素,如果没有找到则返回 undefined。
const array = [1, 2, 3, 4, 5]; const foundElement = array.find((element) => element > 2);
-
indexOf(element, startIndex): 返回数组中指定元素的第一个匹配项的索引,如果未找到则返回 -1。
const array = [1, 2, 3, 4, 5]; const index = array.indexOf(3);
-
push(element1, element2, …): 向数组末尾添加一个或多个元素,并返回新数组的长度。
const array = [1, 2, 3]; const newLength = array.push(4, 5);
-
pop(): 移除数组末尾的元素,并返回被移除的元素。
const array = [1, 2, 3]; const removedElement = array.pop();
-
shift(): 移除数组头部的元素,并返回被移除的元素。
const array = [1, 2, 3]; const removedElement = array.shift();
-
unshift(element1, element2, …): 在数组头部添加一个或多个元素,并返回新数组的长度。
const array = [2, 3, 4]; const newLength = array.unshift(1);
js实现去重方法
在JavaScript中,有多种方法可以实现数组去重。以下是一些常用的方法:
-
使用 Set:
Set 是 ES6 中引入的一种数据结构,它只允许存储唯一的值,可以利用这一特性来实现数组去重。
const array = [1, 2, 2, 3, 4, 4, 5]; const uniqueArray = [...new Set(array)];
-
使用 indexOf 或 includes 方法:
遍历原数组,将不重复的元素添加到新数组中。
const array = [1, 2, 2, 3, 4, 4, 5]; const uniqueArray = []; array.forEach((element) => { if (uniqueArray.indexOf(element) === -1) { uniqueArray.push(element); } });
或者使用
includes
方法:const array = [1, 2, 2, 3, 4, 4, 5]; const uniqueArray = []; array.forEach((element) => { if (!uniqueArray.includes(element)) { uniqueArray.push(element); } });
-
使用 filter 方法:
利用 filter 方法和 indexOf 或 includes 方法来创建新数组。
const array = [1, 2, 2, 3, 4, 4, 5]; const uniqueArray = array.filter((element, index, self) => { return self.indexOf(element) === index; });
或者使用
includes
方法:const array = [1, 2, 2, 3, 4, 4, 5]; const uniqueArray = array.filter((element, index, self) => { return !self.includes(element, index + 1); });
-
使用 reduce 方法:
使用 reduce 方法遍历数组,将不重复的元素添加到累积的结果中。
econst array = [1, 2, 2, 3, 4, 4, 5]; const uniqueArray = array.reduce((accumulator, element) => { if (!accumulator.includes(element)) { accumulator.push(element); } return accumulator; }, []);
flex实现头部固定中间滚动
2 / 2
如果你想使用Flex布局(Flexbox)来实现一个头部固定、中间内容滚动的布局,可以按照以下步骤进行:
HTML 结构:
htmlCopy code<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="styles.css">
<title>Fixed Header and Scrollable Content</title>
</head>
<body>
<div class="container">
<header class="header">
<!-- Your header content goes here -->
<h1>Fixed Header</h1>
</header>
<main class="main">
<!-- Your main content goes here -->
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. ...</p>
</main>
</div>
</body>
</html>
CSS 样式(styles.css):
cssCopy codebody {
margin: 0;
font-family: Arial, sans-serif;
}
.container {
display: flex;
flex-direction: column;
height: 100vh;
}
.header {
background-color: #333;
color: #fff;
padding: 10px;
text-align: center;
position: fixed;
top: 0;
left: 0;
right: 0;
}
.main {
flex: 1;
overflow-y: auto;
padding: 20px;
}
上述代码中,.container
使用 Flex 布局,并设置了 height: 100vh;
,使其占满整个视口的高度。
.header
设置了固定定位 (position: fixed;
),并通过 top: 0;
、left: 0;
、right: 0;
将其固定在页面顶部。
.main
使用 Flex 的 flex: 1;
属性,使其占据剩余的高度,并设置 overflow-y: auto;
以实现内容溢出时的滚动。
你可以根据实际需要修改头部和内容的样式,以及添加更多的内容和布局。这样的布局适用于创建固定头部,而中间内容可以滚动的页面。
vite和vue-cli实现原理和优化及区别
Vite:
1. 实现原理:
Vite 是一个基于 ESModule 的构建工具。它利用原生 ESModule 的特性,将每个文件作为一个模块,通过浏览器去解析和执行,而不需要提前将文件打包成一个单独的 bundle。Vite 利用浏览器的原生 ESModule 支持,实现了快速的开发环境启动和热模块替换(HMR)。
2. 优化策略:
Vite 在开发环境中通过将源代码直接发送到浏览器,避免了传统的打包过程,实现了更快的开发启动速度。此外,Vite 还通过按需加载模块和使用缓存来提高构建和打包的性能。
3. 区别:
- 开发环境速度: Vite 的开发环境启动速度较快,因为它充分利用了原生 ESModule 的特性,避免了传统的打包过程。
- 构建速度: 由于 Vite 在开发环境中的优势,构建速度通常也较快。
- 依赖关系分析: Vite 可以更细粒度地分析依赖关系,实现按需加载,减小了构建体积。
- 插件系统: Vite 使用了 Rollup 作为其构建引擎,这与 Vue CLI 使用的 Webpack 不同。这也导致了一些在插件系统上的不同。
Vue CLI:
1. 实现原理:
Vue CLI 使用 Webpack 作为默认的构建工具。Webpack 是一个模块打包工具,它将项目中的所有资源打包成一个或多个 bundle,以优化加载性能。Vue CLI 还支持其他构建工具,如 Parcel。
2. 优化策略:
Vue CLI 通过多个功能和插件来进行优化,包括代码分割、压缩、缓存等。通过 Webpack 提供的各种功能,Vue CLI 能够灵活地配置和优化构建输出。
3. 区别:
- 开发环境速度: 在开发环境中,Vue CLI 的启动速度相对较慢,因为它需要进行完整的打包过程。
- 构建速度: 由于使用了 Webpack,Vue CLI 在构建速度上可能相对较慢一些,尤其是在大型项目中。
- 依赖关系分析: Webpack 在处理依赖关系时,可能不如 Vite 那样细粒度,因此可能存在一些冗余的模块。
- 插件系统: Vue CLI 使用基于 Webpack 的插件系统,而 Webpack 生态相对成熟,有更多的插件可供选择。
偏原理性问题
1.http缓存
HTTP 缓存指的是通过有效地利用 HTTP 协议头中的各种字段,用于控制如何存储以及从谁处恢复响应结果的机制。这个机制可以大大提高网站的性能,包括加快页面加载速度,减少了不必要的网络带宽消耗等。
HTTP 缓存有两种类型:强缓存和协商缓存。
强缓存: 指的是浏览器直接从缓存中读取资源,而无需向服务器发送请求的情况。强缓存的相关字段有 Expires 和 Cache-Control。当这两个字段同时存在时,Cache-Control 的优先级更高。
Expires 是 HTTP/1.0 的字段,其值为一个绝对时间的 GMT 格式字符串,代表该资源会在何时过期。但由于服务器时间和客户端时间可能存在偏差,所以这个字段在 HTTP/1.1 版本中已经不再主要使用。
Cache-Control 是 HTTP/1.1 的字段,其值具有更多选项,如 max-age (以秒为单位定义资源的有效时间)、no-cache(强制每次都要向服务器确认)、no-store(不允许缓存等。
协商缓存: 如果强缓存失效,浏览器在请求头中携带相应的验证信息(通常是“Last-Modified/If-Modified-Since” 或 “ETag/If-None-Match”),向服务器验证资源的有效性。如果资源没有变化, 服务器返回 304 状态码, 则浏览器从缓存中读取资源; 如果资源有变化,服务器返回新的资源数据与 200 状态码。
Last-Modified 是服务器在返回响应头时,告诉浏览器资源的最后修改时间。If-Modified-Since 是浏览器再次请求这个资源时,告诉服务器资源请求方拥有的资源的最后修改时间。
ETag 是服务器响应请求时,告诉浏览器文件的一个版本标识。服务器可根据文件是否更动来更改 ETag。If-None-Match 是浏览器再次请求时,告诉服务器上一次请求返回的 ETag。服务器会比较该值与当前文件的 ETag,只有不一致时才返回文件内容,否则返回与304状态码。
通过这两种缓存策略的巧妙运用,可以有效地减少网络请求次数,缩短网络请求距离,使得网页加载时间加快,同时也节省了网页的数据请求量。
2.https和http的区别
HTTP 和 HTTPS 主要的区别在于是否使用了安全套接字层(SSL)来进行数据的传输以及通信的双方是否需要身份认证。具体来说它们之间的区别主要体现在以下几个方面:
协议上的区别: HTTP 是基于 TCP/IP 的在网络上通信的最基础的协议之一,全称是超文本传输协议。HTTPS 则是通过 SSL/TLS 来对 HTTP 的数据做了加密处理,全称是超文本传输安全协议。
安全性: HTTP 在传输数据时不进行任何加密,也就是明文传输,因此安全性较差。HTTPS 使用 SSL/TLS 对数据进行加密处理,保护了数据的完整性和隐私性,因此相比 HTTP,HTTPS 更安全。
端口: HTTP 和 HTTPS 使用的端口不同,HTTP 使用的是 80 端口,而 HTTPS 使用的是 443 端口。
搜索引擎优化: 从搜索引擎优化(SEO)的角度考虑,搜索引擎更喜欢 HTTPS,使用 HTTPS 可以带来更好的搜索结果排名。
宽带的需求: 因为 HTTPS 需要在客户端和服务器之间进行数据加密,因此会占用更多的 CPU 资源和内存,相比 HTTP,HTTPS 对服务器和客户端的要求更高。
在绝大多数场景中,我们推荐使用 HTTPS 而不是 HTTP。因为 HTTPS 不仅可以保护网站数据的安全,防止数据在传输过程中被篡改、窃取,也可以提高用户的信任度,从而增加网站的权威性。
3.ssl加密有什么用
SSL(安全套接字层)加密的主要用途是在网络中传输数据时,确保数据的安全性和完整性。以下是SSL加密的主要用途:
**加密数据:**SSL 加密技术能防止数据在传输过程中被非法获取和读取,保护了网络传输的安全性。在数据传输前,SSL会在客户端和服务器之间进行一次握手协商过程,之后的通信数据都会根据协商结果进行加密,过程中即使数据被嗅探,也因为是密文,所以无法进行正确解读。
**身份验证:**SSL可以对通信双方进行身份确认。一般来说,如果一个网站使用了SSL加密,那么它需要从信任的证书颁发机构(CA)获得一个证书,这个证书中包含了该网站的公共密钥及网站信息。因此,当你访问一个使用SSL加密的网站时,你的浏览器会显示一把锁的图标,表示这个网站的身份经过了CA的验证,防止了假冒网站。
**防止数据篡改:**因为SSL采用的是公钥和私钥进行加密和解密的,所以,只有私钥才能解密公钥加密后的数据,避免了数据在传输过程中被第三方修改。
**保护隐私:**在尊重和保护用户隐私的大环境下,任何敏感信息(如信用卡号、身份证号等)都应用SSL进行加密处理,才能进行网络传输。
**增强信誉:**现在许多主流的浏览器都会明显标识出使用SSL的网站,用户访问这些网站会更有安全感,这对于商业网站来说,可以提升用户对其网站的信任度,增强网站的信誉。
因此,无论是为了保护用户的隐私安全,还是为了提升网站的信誉,使用 SSL 加密都是非常重要的。
4.浏览器输入url的变化(没答全)
当你在浏览器的地址栏输入一个 URL 然后按下回车键后,背后会发生一系列的操作,以获取并显示你请求的网页。这个过程大致包括以下步骤:
URL解析: 浏览器首先要分析你输入的 URL,确定你要访问的是哪个网站,以及网站上的哪个页面。
DNS查询: 浏览器接着需要将主机名(即网址的域名部分)解析成服务器的 IP 地址。这通常通过发送一个请求给 DNS 服务器来完成,这是一个网络上的服务,负责将主机名映射到 IP 地址。如果浏览器缓存或者操作系统缓存中有此前查询过的DNS记录,那么此步可以省略。
建立TCP连接: 获得服务器的 IP 地址后,浏览器接着要与服务器建立一个 TCP 连接。一般使用三次握手的方式完成。
发送HTTP请求: 连接建立后,浏览器就可以向服务器发送一个 HTTP 请求了。这个请求通常包含你要获取的页面的路径,以及一些其他的信息,如你的浏览器类型、支持的编码和压缩格式等。
服务器处理请求并返回HTTP响应: 服务器会接收并处理你的请求,然后返回一个 HTTP 响应。响应中包括一个状态码,表示请求是否成功,以及你请求的页面的内容。
浏览器处理: 浏览器接收服务器返回的 HTTP 响应,如果状态码表示成功,那么它会解析并渲染这个页面的 HTML 内容,包括下载并展示所有的 CSS、JavaScript 和图片等资源。
关闭TCP连接: 处理完成后,浏览器和服务器两者之间会关闭 TCP 连接。虽然现代 HTTP/1.1 和 HTTP/2 的协议也支持持久连接,可以在一个 TCP 连接中完成多次 HTTP 请求和响应的交换。
以上就是一个简要的版本。实际上,复杂的网页可能需要多个 HTTP 请求来获取页面的所有部分,而且其中的每一步都可能更加复杂。
5.服务器访问量大,如何优化
服务器访问量大时,一般可能会导致响应速度变慢或者服务器崩溃,这就需要我们去优化服务器以提升其性能。这里列举出一些常见的优化手段:
负载均衡: 当访问量增大时,一个服务器可能无法承受所有的请求,此时可以使用负载均衡技术,将请求分发到不同的服务器上,这样可以大大提升网站的处理能力。
使用缓存: 可以使用各种形式的缓存(例如,内存中的缓存、反向代理服务器的缓存、CDN缓存等),减少对服务器的请求数。
数据库优化: 对数据库进行优化,比如合理的索引设计、读写分离、使用更快的数据存储系统等,以提升数据库查询效率。
代码优化: 进行代码层面的优化,如减少冗余代码,使用更快的算法,避免不必要的计算等。
静态内容和动态内容分离: 将静态内容(像素图片、CSS、JS文件等)和动态内容分开处理,静态内容可以使用 CDN 加速,动态内容通过负载均衡分发到多个服务器处理。
服务器硬件升级: 还可以通过升级服务器硬件,如增加内存、升级 CPU、使用 SSD 硬盘等方式来提升服务器性能。
使用更先进的技术: 如使用 HTTP/2 协议替代 HTTP/1.1,HTTP/2 支持多路复用,对同一个域名所有请求都是通过一个 TCP 连接并发完成,减少了TCP连接的数量,提高了网站加载速度。
记住,优化技术的选择应该根据具体的应用场景和需求来决定。可以先从最容易实施、效果最明显的优化措施开始,逐步进行。
6.niginx跨域是否安全
使用Nginx进行跨域配置本身并不会引发安全问题,但如果配置不恰当,无疑可能引发跨域的安全风险。
所谓跨域,就是指浏览器无法执行某个源(域名、协议、端口任一不同)的脚本访问另一个源的内容。这是由同源策略(Same-Origin Policy)导致的,其目的是为了保护用户信息的安全。
然而,实际上在进行跨域处理的时候,如果随意开放跨域权限,那么很可能会引发一些安全问题,比如 CSRF(跨站请求伪造)攻击。即恶意网站引诱用户去访问,然后后台自动用用户的身份发送攻击请求。
所以,使用Nginx进行跨域处理,需要谨慎配置,且在确保安全性的前提下授予权限。尽量只对需要进行跨域的源进行设置,并且对允许哪些类型的请求进行控制。
例如:
plaintext
location / {
add_header 'Access-Control-Allow-Origin' 'http://trusted-site.com';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
...
}
其中Access-Control-Allow-Origin表示哪个源可以访问,Access-Control-Allow-Methods可控制允许的请求类型等。
总的来说,Nginx跨域在合适、恰当的配置下是相对安全的,但任何技术在使用时都必须保证安全性,避免给攻击者留下可利用的漏洞。
7.既然阻止了跨域,为什么有时候要选择跨域
跨域安全策略(同源策略)确实是为了安全起见而设置的,用于限制页面之间的交互。然而,在现代Web应用中,各个服务可能分布在不同的服务器上,各个页面可能需求获取其他源(域名、协议、端口)的数据或者资源,从而需要进行跨域请求。
举个例子来说,假设你的前端应用运行在 www.example.com,而你的API服务运行在 api.example.com。在这种情况下,当你的前端代码试图访问API服务时,由于跨域限制,请求将不会成功。跨域配置可以帮助缓解这个问题。
所以,从开发的角度来看,虽然Web浏览器默认采用了同源策略,但同时也提供了CORS(跨域资源共享)等跨域方式,以满足开发需要。使用跨域请求,可以使一个网页请求另一个域中的数据。
用途举例:
分布式系统:多个部署在不同服务器的前端及后端同时合作,例如主从架构、服务器集群等。
CDN(内容分发网络):主要分布静态资源,例如图片、视频、CSS和JS等文件,他们通常被存储在各自的服务器上。
第三方服务:如广告、统计服务等通常会被嵌入到各式各样的网页之中。
尽管跨域请求具有一些很好的用途,但开发者需要谨慎使用,正确地配置跨域策略,避免安全风险。跨域并不意味着可以直接取消所有的安全限制,而是在满足必要安全策略的前提下,允许规定范围内跨域的请求。
8.zustand和redux状态库有什么不同
Zustand和Redux都是应用状态管理库,它们都可以用来管理React应用的状态,虽然它们的工作原理以及使用方法有不同。
首先,我们来看一下Redux:
Redux 是一种专门针对JavaScript应用程序的预测状态容器,最常在React和其他流行的UI框架中使用。Redux有严格的"单一状态树"和"纯函数reducer"的概念,这些特性使应用的状态预测性和可维护性增强。
Redux把应用状态存储在一个对象之中,通过定义Action(描述发生了什么)和Reducer(根据Action更新状态)进行状态的读取和更新。
Redux 的主要优点包括预测性和一致性。因为每次状态改变都会通过纯函数(reducers)来完成,所以它会使得状态的管理过程更加稳定和可控。Redux也拥有庞大的社区支持和丰富的中间件。
然后,我们来看一下Zustand:
Zustand 是一个更轻量级的状态管理库,它旨在提供一个最小的、无需boilerplate代码即可工作的API。它的设计灵感来源于React的Hooks API。
Zustand不需要像Redux那样创建Actions和Reducers,你可以直接在任何地方通过调用设置函数(set函数)来改变状态。
Zustand 的主要优点是它的简洁性。它少了很多Redux的概念,所以比Redux更易于学习和使用。
所以:
如果你正在寻找一种稳定、可预见且有强大社区支持的状态管理方案,那么Redux可能是一个很好的选择。
如果你需要一种轻量级且易于上手的状态管理方案,或者你在寻求更接近React Hooks的状态管理方式,那么你可以尝试Zustand。
9.react数据流
React的数据流是单向的(unidirectional),这是由其核心概念“props down, events up”来确保的。“props down”是指父组件通过props把状态传给子组件,而子组件不能直接修改这些props,确保了数据的单向性。"events up"是说子组件通过触发事件通知父组件改变状态,然后父组件改变状态,重新渲染并将新的状态通过props下传给子组件。
这种模式称为"单向数据流"或"一种源真相",所有状态始终由某个特定组件拥有,并且任何状态的更改都有明确定义的和限制的途径。这就大大降低了复杂应用中的bug出现频率,因为每个状态更改都可以对应到特定的逻辑。
用一个典型的例子来说明:假设我们有一个父组件和一个子组件,父组件有一个状态变量state,子组件可以通过props获取到这个state。子组件不能直接修改props中的状态,如果需要修改,应该通过调用父组件提供的函数来实现(事件上报)。父组件接到通知后,改变自己的state,然后再次通过props传给子组件。这样就实现了一个完整的数据流动。
所以React的数据流是单向的,状态总是从上至下(父组件到子组件)传递,事件总是从下至上(子组件到父组件)传递。数据永远都有明确的来源,有助于我们理解应用的运行机制,降低复杂度,同时也使得我们的应用更易于调试和测试。
10.context和状态库有什么不同,怎么选择
React Context和状态库,比如Redux和MobX,主要用来解决的问题是一样的,那就是:如何在应用中共享状态,特别是跨组件、跨层级共享状态。虽然目标相同,但它们的实现方式和适用场景有所不同。
React Context 是React内置的方法,可以让你在组件树中直接传递props,而不需要逐级传递。使用Context,你可以把需要共享的状态存放在顶层组件中,然后其他组件可以直接获取这些状态,而不需要从顶层组件一路传递下来。这样更加方便。
需要注意的是,React Context提供的是非常基础的状态管理功能,你需要自行处理状态的更新逻辑。
状态库(Redux/MobX等) 提供了一套完善的状态管理框架。除了提供状态共享的功能,它们还提供了一系列的状态管理工具,比如Redux的Middleware,以及严格定义的状态更新流程(Action -> Reducer -> Store)等。这些特性使得状态库在处理复杂应用的状态管理时有优势。
选择使用React Context还是状态库,可以根据应用的需求和复杂度来决定:
对于简单的应用,或者只需要基础的状态共享功能,React Context足以满足需求,无需引入额外的库,可以保持应用的轻量级。
对于中大型的应用,特别是那些有复杂的状态管理需求的应用,比如异步数据处理、多层级共享状态等,使用状态库能够更好地组织代码,确保数据流的清晰以及应用的可维护性。
要注意,React Context 和状态库并不是互相排斥的,很多状态库其实就是运用了React Context实现的,比如新版本的Redux就是基于React Context来实现的。
11.Redux和MobX 比较
Redux和MobX都是处理React应用状态的库,但他们有明显的区别。以下是对两者的比较:
**Redux**:
1. Redux是一个预测性状态容器,旨在使你的应用在任何环境下都能以一种一致且可预测的方式运行。
2. Redux强调单一的,只读的状态树。状态是不可变的,每次发生改变都是通过纯函数,叫做reducer,来产生新的状态。
3. Redux通常更适合规模较大的项目,处理复杂的数据流和异步代码更有优势。
4. Redux保证了预测性和一致性,但是这种严格的数据操作也会带来更多的样板代码。
**MobX**:
1. MobX是一个简单、可伸缩的状态管理库。它提供了一种更灵活的方式来管理状态。
2. MobX使用可观察的状态,你可以直接修改状态,并可以通过反应式的方式更新UI。
3. MobX是非常灵活的,基于响应式编程的概念,几乎没有任何限制,可以根据需要进行调整。
4. MobX适合快速开发,小到中型的项目,但是这种灵活性也可能带来难以预测的结果。
哪一个更适合你,完全取决于你的用例、团队技能和个人偏好。如果你喜欢严格的规则和预测性,那么Redux可能是一个不错的选择。如果你需要更快的开发和更大的灵活性,那么MobX可能更适合。
比较两个版本号的大小(split(“.”)😉
实现加载8个图片,每次最多同时加载3个
function loadImage(url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = reject;
img.src = url;
});
}
async function loadImages(urls, max = 3) {
const result = [];
const urlGroups = [];
// 分组
while(urls.length) {
urlGroups.push(urls.splice(0, max));
}
for(let group of urlGroups) {
const promises = group.map(url => loadImage(url));
result.push(...(await Promise.all(promises)));
}
return result;
}
// 测试
const urls = ['img1.jpg', 'img2.jpg', 'img3.jpg', 'img4.jpg', 'img5.jpg', 'img6.jpg', 'img7.jpg', 'img8.jpg'];
loadImages(urls, 3).then(images => {
// ... //所有图片加载完毕后的操作
}).catch(err => {
console.error(err);
});
有哪些hooks
12.hooks状态存储在哪里
React Hooks的状态存储在React内部。当我们使用useState这类的Hooks时,React会在内部为当前的组件维护一个“状态队列”,并为每个调用的useState保留一个位置。
每次组件渲染时,React都会按照useState出现的顺序,依次获取对应的状态。这也就是为什么React的Hook规则中强调不能在条件块或循环内部使用Hook,因为这会使Hook的调用顺序发生变化,导致React无法正确获取对应的状态。
此外,每次重新渲染时,React将创建全新的props和state,全新的函数组件和hooks队列,尽管在视觉上重新渲染的组件与上一次渲染的结果没有区别,但在函数体内部,每一次渲染都是独立的闭包。
这也是Hooks的一个有趣的特性,它允许我们在没有class的情况下,在函数组件内部维护状态,并在组件渲染过程中通过闭包逻辑来使用和修改这个状态。
13.useEffect的副作用指的是什么
在React里,副作用指的是那些与React组件的虚拟DOM渲染相独立的行为。常见的副作用包括异步请求数据、订阅事件、修改DOM、设置定时器等。
useEffect Hook就是用来处理这些副作用的。通过在useEffect的回调函数中书写副作用代码,我们可以告诉React在完成DOM更新后执行我们的副作用函数。
以下是使用useEffect的一个基本示例:
javascript
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// 使用useEffect处理副作用
useEffect(() => {
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
在这个示例中,我们的副作用就是修改文档的标题。每次count状态更新后,React都会在渲染完DOM后执行我们的useEffect函数,从而触发我们的副作用。
14.state是怎么引起react渲染的
在React中,当一个组件的state改变时,React会重新渲染该组件以及所有依赖于这个state的子组件。
React的渲染机制是基于比较新旧state和props是否发生改变来决定是否需要重新渲染的。当我们使用setState或者useState的set函数改变state时,React会在当前渲染周期结束后,执行一个更新,比较新旧state是否有改变。如果有改变,React会根据新的state计算新的虚拟DOM节点,对比旧的虚拟DOM节点找出差异,然后更新真实DOM。
这就是为什么当我们更新state的时候,会触发React的重新渲染。也是React通过state管理UI状态,实现UI与数据的同步更新的机制。
需要注意的是,React可能会为性能考虑,把多个setState调用批处理为一个更新,除非它们发生在不同的事件处理函数中。这也是React中常说的批量更新。
15.nodejs和浏览器js的区别
在实现和使用方式上,Node.js和浏览器中的JavaScript有一些显著的区别,以下是一些主要的区别:
运行环境:最基本的区别是它们的运行环境不一样。浏览器环境的JavaScript主要用于网页交互,DOM操作等,同时受到浏览器安全限制;而Node.js是在服务器端运行的,主要用于文件操作,数据库操作等,没有浏览器环境的安全限制,可以进行更多的操作。
API:在浏览器环境,JavaScript提供了例如BOM(Browser Object Model)和DOM(Document Object Model)等API用于网页交互;而在Node.js环境,JavaScript提供了例如fs,http和events等API用于服务端开发。
模块系统:在浏览器环境中,传统的JavaScript没有模块系统,依赖需要通过全局变量或者脚本顺序来管理(尽管在现代的前端模块化解决方案如AMD,CommonJS,ES6 Modules等的帮助下已经可以实现模块化);而Node.js基于CommonJS规范,具备了模块化管理的能力,通过require和module.exports进行模块的导入和导出。
异步处理:在Node.js中,异步I/O是非常重要的特性,通过回调,Promise,async/await等技术实现非阻塞I/O操作,使得Node.js非常适合处理高并发,I/O密集型的业务;而在浏览器中,虽然也支持异步操作,但并发量通常不会特别高。
社区:Node.js和浏览器用的JavaScript的开发工具和库也会有所不同。例如:Node.js有Express,Koa等服务端框架,而浏览器环境中常有React,Vue等用户界面库或框架。
需要注意的是,由于JavaScript语言本身的发展,以及工具链的不断完善,浏览器环境和Node.js环境下的JavaScript其实越来越接近,很多功能在两者之间已经可以无缝切换了。
16.app webview和小程序webview的区别
App的Webview和小程序的Webview主要有以下几方面的区别:
运行环境:App的Webview是嵌入在原生App中的,可以理解为一个载体,让Web应用可以在原生App中运行。而小程序的Webview也是在原生环境中运行的,但是它不仅仅是一个载体,更像是一种新型的应用程序,具有自己独特的开发模式和API。
实现技术:App的Webview一般加载的是HTML、CSS、JavaScript等标准的Web技术编写的网页,开发者可以使用任何前端框架进行开发。然而,小程序的开发则需要依赖于开放平台提供的开发工具和SDK,开发者需要遵循特定的编程接口和规范进行开发。
权限和能力:App Webview可以通过JSBridge实现JavaScript和原生功能的交互,其能力受限于具体实现。而小程序可以调用平台所提供的丰富的系统能力和API,如地理位置、摄像头、文件系统等,其能力由平台决定。
使用场景:App Webview主要用于在原生App中展示Web内容,并且可以实现原生与Web的交云,增强App的交互性与复杂性。而小程序则是为了满足用户快速获取信息或者服务的需求,特点是“用完即走”,无需安装卸载。
分发和更新:通过App的Webview发布的页面更新,如果涉及到逻辑改动或新功能,可能需要更新APP版本。而小程序可以实现独立更新,不用通过App Store或者Google Play审查,更新迭代速度更快。
总的来说,App Webview和小程序Webview虽然都是在原生环境中运行的Web内容,但是由于其设计理念和运行机制的不同,使得它们在开发方式,权限调用,使用场景等方面有所区别。
17.App的Webview和小程序的Webview有哪些不同?
App的WebView和小程序的WebView在具体实现和功能上有一些主要的区别。以下是一些主要的不同点:
1. 运行环境:App的WebView是嵌入在App中的,可以运行网页并支持HTML5的大部分特性。小程序的WebView则运行在一种特殊的环境中,人们常常将其视为"介于浏览器网页和原生App之间"的产品。
2. 用户交互:通常来说,App的WebView对用户交互的支持相对较弱,因为其主要用于展示网页内容。而小程序的WebView则支持更丰富的用户交互,如转发的功能和操作菜单的定制等。
3. API访问:在App的WebView中,JavaScript只能访问那些浏览器暴露的API,并依赖于特定的JavaScript桥接库(如WebViewJavascriptBridge)来访问原生的API。而在小程序WebView中,独特的小程序JS API集合允许开发者使用诸如文件系统、蓝牙、网络状态等原生特性。
4. 编程模式:在App的WebView中,你可以使用任何你喜欢的Web前端框架(如Vue, React等)。而在小程序WebView中,你需要使用特定的编程模式,例如WXML、WXSS这些由小程序平台提供的技术进行开发。
5. 更新和维护:在一般情况下,App的WebView中的内容更新需要重新发布App版本 ,用户需要在应用市场下载更新才能看到更新的内容。而小程序的更新是即时的,开发者上传版后用户重启小程序即可看到更新内容。
6. 分发方式:App需要下载安装在手机上,而小程序则可以通过搜索、扫一扫、链接等方式打开,无需下载安装。
研发团队在进行技术选型的时候,需要根据自身业务需求和特点,结合以上的区别,来选择使用App WebView还是小程序 WebView。
18.小程序渲染原理
小程序的渲染原理主要涉及到其运行环境和架构设计。小程序运行在微信客户端中,其主要由两个线程组成:主线程和渲染线程。
主线程:主要用于处理JS脚本,包括JS脚本的加载、解析和运行,以及API的调用。
渲染线程:则主要用于生成小程序的界面,包括WXML到DOM节点的转化,WXSS样式的应用,以及页面的布局和渲染。
小程序的渲染原理可分为以下几个步骤:
首先,主线程和渲染线程会加载和解析对应的WXML和WXSS文件。
其次,主线程在解析WXML文件的过程中,会创建对应的JS对象,然后将这些对象通过数据绑定的方式与渲染线程中的DOM节点进行关联。
当主线程中的JS对象发生变化时,主线程会将这些变化通过数据绑定反映到渲染线程中的DOM节点,从而触发DOM的更新和重绘。
渲染线程在接收到DOM节点改变的信息后,会根据WXSS文件中的样式规则来进行页面布局和渲染。此外,渲染线程还可以根据用户的操作(如点击、滑动等)来触发主线程中的事件回调。
最后,经过一系列的计算和渲染,小程序的界面就会呈现在用户面前。
以上就是小程序的渲染原理,总的来说,小程序将JS逻辑和页面渲染分离到两个线程中,这样可以保证JS逻辑的运行不会阻塞页面的渲染,从而提高了小程序的性能。
19.小程序的渲染线程如何处理用户的交互操作?
小程序采用了一种叫做“线程分离”的方法来处理用户的交互操作。这是一个主线程与渲染线程协同工作的结果。具体的处理流程如下:
1. 用户触发了某个交互行为,如点击一个按钮。这个行为首先会被小程序的渲染线程捕获到。
2. 渲染线程捕获到交互行为后,会把事件信息通过一个叫做WeixinJSBridge的桥接库,发送到主线程。
3. 主线程收到来自渲染线程的事件信息后,通过事件的名称和参数,调用预先注册好的对应的JavaScript事件处理函数。
4. 事件处理函数可能会更新一些数据。这些数据的变化会通过数据绑定的方式,反馈到渲染线程中对应的DOM节点。
5. 渲染线程接收到新的数据后,会更新DOM节点的状态,重新渲染页面,完成了整个用户交互的处理过程。
因此,虽然用户的交互行为是在渲染线程中被触发和捕捉到的,但是实际的处理逻辑,包括数据的更新和页面的渲染,都是在主线程中完成的。渲染线程和主线程之间通过特定的桥接机制进行通信,以完成用户交互的处理。
1. 为什么要封装组件?
代码重用:封装好的组件可以在多个项目中重复使用,避免重复造轮子。维护性:组件化开发使得代码结构清晰,易于维护。扩展性:随着业务需求的变化,可以方便地扩展或修改组件。
2. 封装组件的步骤
2.1 确定组件需求 首先,需要明确组件的功能和需求。这通常来源于业务需求或设计稿。
2.2 设计组件结构 设计组件的HTML结构、CSS样式和JavaScript逻辑。确保组件的结构清晰、易于理解。
2.3 编写组件代码 编写组件的模板,定义组件的结构。编写组件的样式,确保组件在不同场景下都能良好地展示。编写组件的行为逻辑,处理用户交互、数据绑定等。
2.4 组件参数化 为了使组件更加灵活和可重用,通常需要将一些配置项作为参数传入组件。例如,可以通过props传递数据,通过slots插入自定义内容等。
2.5 组件事件处理 组件应该提供事件处理机制,以便在组件内部发生某些动作时能够通知父组件或触发其他行为。
2.6 组件测试 编写测试用例,确保组件的功能正常、性能良好。
3. 封装组件的原则
单一职责原则:一个组件只做一件事情,保持功能单一。开放-封闭原则:组件应该对扩展开放,对修改封闭。依赖倒置原则:组件应该依赖抽象,而不是具体实现。接口隔离原则:组件之间的接口应该尽量小且职责单一。里氏替换原则:子类应该能够替换其父类。
4. 组件的复用和扩展
组件复用:通过参数化和插槽机制,使得组件可以在不同场景下复用。组件扩展:通过继承、组合或混入等方式,实现组件的扩展和定制。
5. 组件的维护和文档
组件维护:随着业务需求的变化,需要及时更新和维护组件。组件文档:编写清晰的组件文档,说明组件的使用方法、参数和事件等,方便其他开发者使用。
三、项目的性能优化你有什么好的见解嘛?
笼统的回答:
代码优化:
减少数据库查询:尽量使用批量查询,减少单独查询的次数。缓存:使用缓存来存储经常访问的数据,减少对数据库的访问。异步编程:对于不需要即时返回结果的操作,使用异步编程可以提高程序的响应速度。避免使用高复杂度的算法:尽量使用时间和空间复杂度较低的算法。代码审查:定期进行代码审查,查找并消除性能瓶颈。数据库优化:
索引优化:确保对经常查询的字段建立了索引,并定期审查和优化索引。查询优化:避免使用SELECT *,只选择需要的字段。尽量避免在WHERE子句中使用函数。分区:对于大型表,可以考虑使用分区来提高查询性能。数据库连接池:使用连接池来管理数据库连接,避免频繁地创建和关闭连接。服务器优化:
硬件升级:根据需要,升级服务器的CPU、内存或存储硬件。负载均衡:使用负载均衡技术将请求分发到多个服务器上,以提高系统的吞吐量和响应速度。定期维护:定期清理服务器上的临时文件、日志文件等,保持服务器的良好运行状态。网络优化:
压缩数据:在发送数据前进行压缩,可以减少传输的数据量。CDN:使用CDN来加速静态资源的访问速度。减少HTTP请求:合并CSS和JavaScript文件,减少不必要的HTTP请求。监控和日志:
性能监控:使用性能监控工具来实时监控系统的运行状态,及时发现并解决性能问题。日志分析:定期分析日志文件,查找可能的性能瓶颈和错误。测试和调优:
性能测试:在项目上线前进行性能测试,确保系统满足性能要求。调优:根据性能测试的结果,对系统进行调优,提高系统的性能。
react相关优化
1 . Memo的主要原理和应用如下:
1.1 减少重复计算:当遇到一个函数,其计算结果不依赖于其他任何外部状态或可变数据,而只依赖于其输入参数时,可以使用memo。例如,斐波那契数列的计算就是一个很好的例子。传统的递归方法会进行大量的重复计算,而使用memo可以将已经计算过的结果存储起来,当再次需要时直接返回,避免了重复的计算。1.2 缓存结果:Memo的另一个重要应用是在动态规划中。在动态规划中,问题通常被分解为一系列的子问题,每个子问题的解决方案都被存储起来,以便在解决更大的问题时可以重用这些解决方案。这可以显著提高算法的效率,因为它避免了重复解决相同的子问题。1.3 递归优化:在递归函数中,memo也可以被用来优化性能。当递归函数被调用时,它的结果可以被存储起来,以便在后续的递归调用中直接使用,而不是重新计算。这可以显著减少递归调用的次数,并提高程序的性能。
在前端开发中,特别是在React等函数式组件的框架中,memo也是一种常见的优化手段。React.memo可以对函数式组件进行包装,使其只有在props发生变化时才重新渲染,从而避免不必要的重新渲染,提高性能。
memo的原理是通过存储和重用之前计算过的结果来避免重复的计算和渲染,从而提高程序的性能。在React中,除了使用React.memo进行性能优化之外,还有其他多种优化方案。以下是一些建议的优化手段,附带相应的代码示例:
2. 避免不必要的重新渲染(Should Component Update)
通过实现shouldComponentUpdate方法或使用React.PureComponent,可以避免不必要的组件重新渲染。shouldComponentUpdate方法允许你自定义组件更新的逻辑,而React.PureComponent会自动进行props和state的浅比较。
class MyComponent extends React.PureComponent {
render() {
// 组件代码
}
}
或者,使用React.memo进行函数组件的优化:
const MyComponent = React.memo(props => {
// 组件代码
});
3 . 使用keys优化列表渲染
当渲染列表时,给每个列表项提供一个唯一的key属性可以帮助React识别哪些项发生了变化、被添加或被删除,从而提高渲染性能。
const items = ['Item 1', 'Item 2', 'Item 3'];
return (
<div>
{items.map((item, index) => (
<li key={index}>{item}</li>
))}
</div>
);
4 . 合理使用Context API
使用React的Context API可以避免不必要的props传递,特别是在深层次的组件树中。通过Context,你可以将值深入到组件树的任何位置,而无需手动逐层传递props。
const MyContext = React.createContext();
class MyProvider extends React.Component {
state = {
theme: 'dark'
};
render() {
return (
<MyContext.Provider value={this.state.theme}>
{this.props.children}
</MyContext.Provider>
);
}
}
// 使用Context的组件
class ThemedComponent extends React.Component {
static contextType = MyContext;
render() {
const { theme } = this.context;
return <div className={`themed-component ${theme}`}>Hello World</div>;
}
}
// 在应用中使用Provider
<MyProvider>
<ThemedComponent />
</MyProvider>
5 . 使用懒加载(React.lazy 和 Suspense)
对于大型应用,可以使用React的React.lazy和Suspense实现组件的懒加载,即按需加载组件,这样可以减少应用的初始加载时间。
const OtherComponent = React.lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
// 其他的组件代码...
<React.Suspense fallback={<div>Loading...</div>}>
<OtherComponent />
</React.Suspense>
);
}
6 . 使用代码拆分(Code Splitting)
代码拆分是Webpack等构建工具提供的功能,可以将代码拆分为更小的块,然后异步加载它们。这有助于减少应用的初始加载时间。
在Webpack中,你可以使用import()语法动态导入模块,从而实现代码拆分。
button.addEventListener('click', event => {
// 当按钮被点击时,动态导入模块
import('./dynamicModule.js')
.then(module => {
module.run();
})
.catch(err => {
// 处理加载失败的情况
});
});
7. 优化状态管理
使用像Redux这样的状态管理库时,确保你的reducer函数是纯净的,不产生副作用,并且只在必要时更新状态。此外,使用像reselect这样的库可以帮助你创建记忆化的选择器,避免不必要的计算。
8 . 避免使用内联函数
在渲染方法或组件的props中使用内联函数会导致每次渲染都创建一个新的函数实例,这可能会导致不必要的重新渲染。相反,你可以将函数绑定到组件的实例上,或者使用箭头函数来捕获this的上下文。
深度文章:
1.前端文摘:深入解析浏览器的幕后工作原理 - 梦想天空(山边小溪) - 博客园 (cnblogs.com)
2.浏览器的工作方式 | Articles | web.dev
3.javascript - 从输入URL到页面加载的过程?如何由一道题完善自己的前端知识体系! - 程序生涯 - SegmentFault 思否
4.Default style sheet for HTML 4 (w3.org)
5.javascript - 从输入URL到页面加载的过程?如何由一道题完善自己的前端知识体系! - 程序生涯 - SegmentFault 思否