前端学习(面试收集)
Vue
Vue和React对比
React 和 Vue 有许多相似之处,它们都有使用 Virtual DOM;
提供了响应式(Reactive)和组件化(Composable)的视图组件。
将注意力集中保持在核心库,而将其他功能如路由和全局状态管理交给相关的库。
React 比 Vue 有更丰富的生态系统
都有支持native的方案,React的RN,vue的Wee下
都支持SSR服务端渲染
都支持props进行父子组件间的通信
性能方面:React 和 Vue 在大部分常见场景下都能提供近似的性能。通常 Vue 会有少量优势,因为 Vue 的 Virtual DOM 实现相对更为轻量一些。
不同之处就是:
1. 数据绑定方面,vue实现了数据的双向数据绑定,react数据流动是单向的
2.virtual DOM不一样,vue会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树.
而对于React而言,每当应用的状态被改变时,全部组件都会重新渲染,所以react中会需要shouldComponentUpdate这个生命周期函数方法来进行控制
3.state对象在react应用中不可变的,需要使用setState方法更新状态;
在vue中,state对象不是必须的,数据由data属性在vue对象中管理(如果要操作直接this.xxx)
4.组件写法不一样, React推荐的做法是 JSX , 也就是把HTML和CSS全都写进JavaScript了,即'all in js'; Vue推荐的做法是webpack+vue-loader的单文件组件格式,即html,css,js写在同一个文件
vue2和vue3区别
- vue3 和 vue2 最大的区别就在于vue2使用的是optionsAPI 而 vue3使用的是compositionAPI
OptionsAPI:可以让我们用包含多个选项的对象来描述组件的逻辑,比如data、methods、mounted,选项所定义的属性都会暴露在函数内部的this上,指向当前的组件实例。
CompositionsAPI:我们可以使用导入的API函数来描述组件逻辑,一般和<script setup>搭配使用,setup属性是一个表示,告诉VUE需要在编译时进行一些处理,在<script setup>中的导入和顶层变量或函数都能够在模板中直接使用
vue2的响应式才用的是ES5的API Object.definePropert()
对数据进行劫持 结合 发布订阅模式的方式来实现的。vue3 中使用了 es6 的
Proxy
API 对数据代理。相比于vue2.x,使用proxy的优势如下
defineProperty只能监听某个属性,不能对全对象监听
可以省去for in、闭包等内容来提升效率(直接绑定整个对象即可)
Object.defineProperty 无法监控到数组下标的变化,导致直接通过数组的下标给数组设置值,不能实施响应。
而proxy可以监听数组,不用再去单独的对数组做特异性操作 vue3.x可以检测到数组内部数据的变化vue3中的组件可以有多个根节点
生命周期钩子
Vue2--------------vue3
beforeCreate -> setup()
created -> setup()
beforeMount -> onBeforeMount
mounted -> onMounted
beforeUpdate -> onBeforeUpdate
updated -> onUpdated
beforeDestroy -> onBeforeUnmount
destroyed -> onUnmounted
activated -> onActivated
deactivated -> onDeactivated
说说你对vue的理解
Vue是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建,并提供了一套声明式的、组件化的编程模型,帮助我们高效地开发用户界面。
他的数据传输是用了MVVM模式,M是数据层,V是视图层,VM是调度者。
官方对Vue的评价是 灵活、易用和高效,对于这几点我的看法是:
2. 易用性
- vue提供数据响应式、声明式模板语法等,使我们只需要关注应用的核心业务即可,只要会写js、html和css就能轻松编写vue应用。
3. 灵活性
- 由于是渐进式框架,所以我们可以由浅入深的开发不同大小类型的项目。
- 如果是小应用,我们只需要使用vue核心特性即可完成功能。
- 应用的不断扩大,我们能逐渐引入路由、状态管理、vue-cli等库和工具。
- 不管是应用体积还是习难度都是一个逐渐增加的平稳曲线。
4. 高效性
- 在vue2中引入的虚拟dom和diff算法,使得页面渲染性能表现更好。
- 在vue3中还引入proxy对数据的响应式改进,编译器对静态内容编译的优化,都让性能更好更高效。
SPA 单页应用
单页面应用(SPA--------single page application),一个web项目只有一个页面(即一个HTML文件)刷新页面会请求一个HTML文件,切换页面的时候,并不会发起新的请求一个HTML文件,只是页面内容发生了变化。
Web 页面初始化时加载相应的 HTML、JavaScript 和 CSS。一旦页面加载完成,SPA 不会因为用户的操作而进行页面的重新加载或跳转;取而代之的是利用路由机制实现 HTML 内容的变换,UI 与用户的交互,避免页面的重新加载。
vue.js原理:JS感知URL变化,当URL发生变化后,使用JS动态把当前的页面内容清除掉,再把下一个页面的内容挂载到页面上。此时的路由就不是后端来做了,而是前端来做,判断页面到底显示哪一个组件,再把以前的组件清除掉使用新的组件。就不会每一次跳转都请求HTML文件。
单页应用的优点:
1.分离了前后端的关注点,前端负责页面显示,后端负责数据储存和计算减轻了服务器的压力,不会让前后端的逻辑混淆
2.用户体验好,快,内容的改变不需要重新去加载
3.前端组件化,前端的开发不再以页面为单位,而是采用组件化的思想让代码的结构更加规范,有利于修改
单页应用的缺点:
1.首次加载耗时久,需要加载大量资源
2.对搜索引擎SEO不友好
解决方式:
1.按需加载,避免首屏加载过慢
2.配置好路由信息,通过记录浏览过的历史路由信息解决导航不可以问题
3.用#!代替#,因为谷歌会抓取带有#!的URL。我们可以解决ajax的不被搜索引擎抓取的问题
使用过 Vue SSR 吗?说说 SSR?
vue.js 是构建客户端应用程序的框架 在默认情况下,可以在浏览器输出Vue组件,进行生成DOM和操作DOM。
也可以将同一个组件渲染为 服务端的HTML 字符串 将他们直接发送给服务器,最后将这些静态标记激活为客户端上完全可以交互的应用程序。
SSR大致的意思就是vue在客户端将标签渲染成的整个 html 片段的工作在服务端完成,服务端形成的html 片段直接返回给客户端这个过程就叫做服务端渲染。
Vue单向数据流的理解
单项数据流 就是一个组件单方向的将数据流向它内部组件,也就是 父组件的数据流向子组件,但子组件不能更改父组件提供的数据,除非返回父组件中修改,再重新流向子组件,从而达到数据更新
Vue组件的data为什么要是函数
Vue 怎么重置 data
使用 Object.assign(),vm.$data可以获取当前状态下的data,vm.$options.data 可以获取到组件初始化状态下的 data。
Object.assign(this.$data, this.$options.data())
路由懒加载
在单页应用中,如果没有懒加载,运用 webpack 打包后的文件将会异常的大,造成进入首页时,需要加载的内容过多,延时过长,不利于用户体验,而运用懒加载则可以将页面进行划分,需要的时候加载页面,可以有效的分担首页所承担的加载压力,减少首页加载用时。
原理:vue 异步组件技术:异步加载,vue-router 配置路由 , 使用 vue 的异步组件技术 , 实现按需加载。
组件中写 name 选项有什么作用
① 项目使用 keep-alive 时,可搭配组件的 name 进行缓存过滤。
②DOM 做递归组件时需要调用自身 name
③vue-devtools 调试工具里显示的组件名称是由 vue 中组件 name 决定的
route 和 router
route 是“路由信息对象”,包括 path,params,hash,query,fullPath,matched,name 等路由信息参数。
router 是“路由实例对象”,包括了路由的跳转方法(push、go),钩子函数等。
Vue的修饰符
组件通信
父子组件之间
中央事件总线 bus
使用中央事件总线实际就是创建一个vue实例,利用这个vue实例来传递消息。
使用方式一:
// 定义一个bus文件
import Vue from "vue";
const bus = new Vue();
export default bus;
// 引入bus文件
import bus from "@/bus";
bus.$emit("myEvent", "bus msg")
// 引入bus文件
import bus from "@/bus";
bus.$on("myEvent", data => {console.log(data);
});
使用方式二:
// main.js
import Vue from 'vue'
import App from './App.vue'
Vue.prototype.$bus = new Vue();
new Vue({render: h => h(App),
}).$mount('#app')
// 发送事件
this.$bus.$emit("myEvent", "bus msg");
// 接收事件
this.$bus.$on("myEvent", data => {console.log(data);
})
你在需要的地方写一个 $on,然后在其他地方用 emit,这个 on 的回调就会被调用,有点像在其他地方调用某个组件内部的方法一样。
适用场景:适用于不是特别大的单页面应用跨级跨兄弟组件间通信
computed 、 methods 、 watch
computed 是计算属性,依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值
watch: 适用于一条数据影响多个数据的场景;例如搜索
watch更多的是观察的作用,类似于某些数据的监听回调 ,每当监听的数据变化时都会执行回调进行后续操作
methods: 函数调用,每使用一次,都需要重新加载,不需要缓存时用methods;性能开销较大,
总结
当我们需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用computed 的缓存特性,避免每次获取值时,都要重新计算;
当我们需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许我们执行异步操作 ( 访问一个 API ),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的
Vue为什么是''异步渲染''?
Vue是组件级的更新,一个组件可能涉及的数据非常的多,每次更新都会非常损耗性能,为了节约性能,Vue在更新DOM时是异步执行的。
只要监听到数据的变化,Vue就会开启一个队列,把同一事件循环中发生的所有数据变更都推入这个队列,如果同一个数据变更被多次触发,它也只会被推入队列中一次,这种缓冲行为可以去除重复数据的不必要计算和 DOM操作。
为什么要使用nextTick
当你在 Vue 中更改响应式状态时,最终的 DOM 更新并不是同步生效的,而是由 Vue 将它们缓存在一个队列中,直到下一个“tick”才一起执行。这样是为了确保每个组件无论发生多少状态改变,都仅执行一次更新。
为了在数据变化之后等待 Vue 完成更新 DOM,可以在数据变化之后立即使用 nextTick,该方法的 callback 会在 DOM 更新完成后被调用。
nextTick()
可以在状态改变后立即使用,以等待 DOM 更新完成。你可以传递一个回调函数作为参数,或者 await 返回的 Promise。
v-show 和 v-if
v-show的元素总是会被渲染,对于不显示的元素 也只是在行内设置了display:none的样式
v-if 是真正的条件渲染,他不会去渲染不显示的元素,除非条件为真。
一般情况下v-if的切换开销比较高,而v-show的初始渲染开销比较高
v-if 和 v-for 的优先级
在VUE2中
v-if
比 v-for
的优先级更高。这意味着 v-if
的条件将无法访问到 v-for
作用域内定义的变量别名
Vue的生命周期
beforeCreate
|
在实例初始化之后,数据观测 (data observer) 和
event/watcher 事件配置之前 被调用。
在这个事件中我们 获取不到data数据
data,watcher,methods都不存在这个阶段;只有$route这个对象
因此此阶段就可以根据路由信息进行重定向等操作。
常用于初始化非响应式的变量
|
created |
在实例创建完成后触发,
可以访问data、methods等属性,
但是组件还未被挂载到页面 ,所以不能访问$el,且页面视图未出现,如果请求过多页
面会长时间处于白屏状态;
我们一般在这个函数中进行页面初始化的工作,
例如ajax请求数据来对页面进行初始化
|
beforeMount
|
在组件被挂载到页面之前调用,会找到对应template,
并翻译成render函数
|
mounted |
在组件被挂载到页面之后触发,可以通过DOM
API来获取页面中的DOM元素;
el 被新创建的 vm.$el
替换,并挂载到实例上去,可以进行dom操作
|
beforeUpdate |
在响应式数据更新时调用,发生在虚拟 DOM
打补丁之前,
此时我们可以对一些可能被移除的元素做一些操作,例如
移除事件监听器
|
updated |
组件数据更新之后,发生虚拟DOM重新渲染和打补丁之
后进行调用
避免在这个钩子函数中操作数据,可能陷入死循环
|
beforeDestory |
组件销毁前调用
做一些优化操作,一般在这一步 我们去销毁定时器
解绑全局组件
|
destoryed
|
组件销毁后调用,Vue实例中所有东西都会解除绑定
事件监听器会被移除,所有子实例会被销毁
|
activited
|
keep-alive专属,组件被激活时调用
|
deactivated
|
在被包裹组件停止使用时调用
|
守卫
导航守卫
- 全局前置守卫 在路由跳转前触发
Router.beforeEach((to, form, next)=> { } ) - 全局解析守卫 与 全局前置守卫类似 在路由跳转前触发 区别是 在导航解析之前,同时在所有组件内守卫和路由组件被解析之后调用
Router.beforeResolve((to, form, next)=> { } ) - 全局后置钩子 这个钩子不会接收next 函数 也不改变导航本身
初始化时执行、每次路由切换后执行
Router.afterEach((to, form )=>{ })
路由独享守卫
- beforeEnter :在路由跳转前触发
和 beforeEach 完全相同,如果都设置则在beforeEach之后紧随执行
可以在路由配置上直接定义beforeEnter守卫,和全局前置守卫的方法参数相同 - beforeLeave()
组件独享守卫
- BeforeRouterEnter(to, form, next) => {
next(vm => { 通过vm访问组件实例})
}
在渲染组件的对应路由被确认前调用 在执行当前守卫之前 组件实例还未被创建 所以不能获取组件实例 this 在路由进入前调用 ,你可以通过传一个回调给 next来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数。
是支持给 next传递回调的唯一守卫 - BeforeRouterUpdate(to,from,next){ }
路由改变 但是该组件被复用时调用,例如切换页面但是 该组件被复用 此时可以访问组件实例 this
- BeforeRouterLeave(to, form, next ) {const answer = window.confirm('你还没有保存!')if (answer) {next()} else { next(false) //这样取消
}
在导航离开组件对应的路由时调用,可以访问组件实例this 这个离开守卫通常用来禁止用户在还未保存修改前突然离开。该导航可以通过 next(false) 来取消
总结:
Key的作用和原理
key
这个特殊的 attribute 主要作为 Vue 的虚拟 DOM 算法提示,在比较新旧节点列表时用于识别 vnode。
在没有 key 的情况下,Vue 会尽可能地就地更新/复用相同类型的元素。如果传了 key,则将根据 key 的变化顺序来重新排列元素,并且将始终移除/销毁 key 已经不存在的元素。
同一个父元素下的子元素必须具有唯一的 key。重复的 key 将会导致渲染异常。
- 在v-for中使用key
当我们使用v-for去更新已经渲染过的元素时,vue的行为就是原地复用元素,如果元素的位置改变,Vue就会重新进行渲染,它不能根据元素的顺序更换来渲染元素,做不到很好的复用所以Key的使用是为了提供一个排序的标识,在使用Key的时候Vue会基于 key 的变化重新排列元素顺序,并且会移除 key 不存在的元素。
diff算法主要是做同级比较,它会同时比较标签名和key,如果设置了key,当子节点位置发生变化时,vue 也能很迅速的定位到该节点,无需重新去创建、销毁。 - v-if中使用key
Vue在渲染时会去尽可能的复用元素,而不是从头开始渲染,所以在使用v-if实现元素切换的时候,如果前后含有相同类型的元素 那么这个元素就会被复用,这时候key的作用就是用来标记独立的元素例如 在做不同登录方式的切换的时候,它的input将不会被替换,而只是替换他们的placeholder;这也导致了input内原本被输入的内容在切换后也不会被清除;
虚拟Dom
const vnode = {type: 'div',props: {id: 'hello'},children: [/* 更多 vnode */]
}
Vue如何把组件挂载到页面上?
一个运行时渲染器将会遍历整个虚拟 DOM 树,并据此构建真实的 DOM 树。这个过程被称为挂载 (mount)。
如果我们有两份虚拟 DOM 树,渲染器将会有比较地遍历它们,找出它们之间的区别,并应用这其中的变化到真实的 DOM 上。这个过程被称为更新 (patch),又被称为“比对”(diffing) 或“协调”(reconciliation)。
Diff算法
diff算法的特点是同级对比,深度优先,当数据改变就会触发setter,通过dep.notify()来通知订阅者,订阅者就会调用patch方法来打补丁。
patch方法会用sameVnode()的方法来对比 同层虚拟节点是否为 同一类型 的节点(标签/key/如果是input 时 type是否相同/ 是否定义data/ 是否为注释节点)
如果不是同一类型节点,则直接把整个节点替换为新节点
如果是同一类型节点,则执行patchNode()方法进行比对
①首先先找到真实DOM称为el
当新节点有子节点而老节点没有的时候,则直接把新节点的子节点添加到el
当新节点没有子节点而老节点有的时候,则删除el的子节点
当新节点和老节点都有文本节点,且不相等时,将el中的文本节点设置为新节点中的文本节点
当新老节点都有子节点时,就会调用updateChildren()方法进行比对它们的子节点
updateChildren()的
是首尾指针对比法,新老节点的首尾各有一个指针,进行互相比较,一共有五种情况
1.oldS 与 newS 他会通过sameVnode()的方法来对比,
2.oldE 与 newE
3.oldE 与 new S
4. oldS 与 newE
5.当以上四种都匹配不到时,再把所有旧子节点的 key
做一个映射到旧节点下标的 key -> index
表,然后用新 vnode
的 key
去找出在旧节点中可以复用的位置。
不需要响应式的数据应该怎么处理?
// 方法一:将数据定义在data之外
data () {this.list1 = { xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx }this.list2 = { xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx }this.list3 = { xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx }this.list4 = { xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx }this.list5 = { xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx }return {}}// 方法二:Object.freeze()
data () {return {list1: Object.freeze({xxxxxxxxxxxxxxxxxxxxxxxx}),list2: Object.freeze({xxxxxxxxxxxxxxxxxxxxxxxx}),list3: Object.freeze({xxxxxxxxxxxxxxxxxxxxxxxx}),list4: Object.freeze({xxxxxxxxxxxxxxxxxxxxxxxx}),list5: Object.freeze({xxxxxxxxxxxxxxxxxxxxxxxx}),}}
Vue2.0 响应式数据的原理
整体思路是数据劫持+观察者模式
使用 Object.defineProperty 将属性进行劫持(只会劫持已经存在的属性),当页面使用对应属性时,每个属性都拥有自己的 dep 属性,存放他所依赖的 watcher(依赖收集),当属性变化后会通知自己对应的 watcher 去更新(派发更新)。
数组则是通过重写数组方法来实现。
双向绑定
Vue 2.0 如何检测数组变化
数组考虑性能原因没有用 defineProperty 对数组的每一项进行拦截,而是选择对 7 种数组(push,shift,pop,splice,unshift,sort,reverse)方法进行重写(AOP 切片思想)
所以在 Vue 中修改数组的索引和长度是无法监控到的。需要通过以上 7 种变异方法修改数组才会触发数组对应的 watcher 进行更新
原因:
Vue2 是用 Object.defineProperty 重写 data 的各个属性,只有 data 的属性发生变化才会触发视图更新,数组内部的变化,不会让数组本身变化,数组或对象 就像一个箱子,你往里面放东西,或者换掉里面的东西,箱子本身还是不变的,只是箱子里面变了,所以箱子还是那个箱子,除非你再造一个出来。
但是 vue3 就不会,因为 vue3 是 proxy,他天生就可以监听内部变化。
Proxy 相比于 defineProperty 的优势
Vue3.0 摒弃了 Object.defineProperty,改为基于 Proxy 的观察者机制探索。
首先说一下 Object.defineProperty 的缺点:
①Object.defineProperty 无法监控到数组下标的变化,导致直接通过数组的下标给数组设置值,不能实施响应。
②Object.defineProperty 只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。
Vue2.X 里,是通过递归 + 遍历 data 对象来实现对数据的监控的,如果属性值也是对象那么需要深度遍历,显然如果能劫持一个完整的对象才是更好的选择。
而要取代它的 Proxy 有以下两个优点
可以劫持整个对象,并返回一个新对象。
有多种劫持操作(13 种)
补充:
Proxy 是 ES6 新增的一个属性,翻译过来的意思就是代理,用在这里表示由它来“代理”某些操作。Proxy 让我们能够以简洁易懂的方式控制外部对象的访问,其功能非常类似于设计模式中的代理模式。
Proxy 可以理解为,在目标对象之前设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。
使用 Proxy 的核心优点是可以交由它来处理一些非核心逻辑(如:读取或设置对象的某些属性前记录日志;设置对象的某些属性值前,需要验证;某些属性的访问控制等)。从而可以让对象只需要关注核心逻辑,达到关注点分离,降低对象复杂度等目的。
vue-cli 替我们做了哪些工作
vue-cli 是基于 Vue.js 进行快速开发的完整系统,也可以理解成是很多 npm 包的集合。
vue-cli 完成的功能:
.vue 文件 --> .js 文件
ES6 语法 --> ES5 语法
Sass,Less,Stylus --> CSS
对 jpg,png,font 等静态资源的处理
热更新
定义环境变量,区分 dev 和 production 模式
如果开发者需要补充或修改默认设置,需要在 package.json 同级下新建一个 vue.config.js 文件
keep-alive
- include包含的组件只有匹配的组件会被保存
- exclude排除的组件任何匹配的组件都不会被保存
- max 缓存组件的最⼤值
Vue.extend 作用和原理
官方解释:Vue.extend 使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。
其实就是一个子类构造器 是 Vue 组件的核心 api 实现思路就是使用原型继承的方法返回了 Vue 的子类 并且利用 mergeOptions 把传入组件的 options 和父类的 options 进行了合并
Vue自定义指令
- bind会在指令第一次绑定到函数上时调用,可以做一些初始化的设置,update则是在组件更新时调用,例 如对dom进行一些操作;
- inserted :被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
钩子函数有几个参数,较为常用的是el和binding ;
el存储指令绑定的元素,可以用来直接操作dom,binding里的name,代表指令名,value是指令绑定的值;
Update 有oldvalue是绑定的前一个指令的值; 我们可以在绑定指令的时候给他传值
1. bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
2. inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
3. update:被绑定于元素所在的模板更新时调用,而无论绑定值是否变化。通过比较更新前后的绑定值,可以忽略不必要的模板更新。
4. componentUpdated:被绑定元素所在模板完成一次更新周期时调用。
5. unbind:只调用一次,指令与元素解绑时调用。
export default {bind: function (el, binding) {console.log('bind成功');const options = binding.value;const { className, activeClass,currentIndex } = optionsconst children = el.getElementsByClassName(className)children[currentIndex].className += ` ${activeClass}`// el 自定义指令所在元素 binding name子元素 class},update: function (el, binding) {//binding里有一个oldValueconst options = binding.value;const oldOptions = binding.oldValue;//上一次操作的const { className, activeClass, currentIndex } = optionsconst children = el.getElementsByClassName(className)const { currentIndex: oldCurIndex } = oldOptionschildren[currentIndex].className += ` ${activeClass}`//让上一个变成原本的children[oldCurIndex].className = classNameconsole.log('操作item触发update');}
<template><div class="home"> hi im home<div class="nav-bar" v-nav-active="{ className:'nav-item', //找他的子元素activeClass:'nav-active', //要绑定的类名currentIndex, }"><div @click="change(index)" class="nav-item" v-for="(item, index) initems" :key="index"> {{ item }}</div></div></div>
</template><script>import NavActive from "../directives/navActive";export default {name: "Home",directives: {NavActive,},methods: {change(index) {this.currentIndex = index}},data() {return {currentIndex: 0,items: ["1", "2", "3"],};},components: {},};
</script>
<style lang="scss">.nav-bar {display: flex;margin-top: 20px;}.nav-item {color: white;width: 100px;height: 40px;line-height: 40px;background-color: black;}.nav-active {background-color: white;color: black;border: 1px solid black;box-sizing: border-box;}
</style>
VueX
VueX是一个专门为vue.js 程序开发的状态管理模式 它采用集中式 储存管理 所有引用的组件的状态
VueX的作用
什么情况下去使用VueX
不使用Vuex的坏处
- 代码可维护性下降
- 代码可读性下降
- 耦合度增加
VueX的属性
- state:Vuex是一个用于存放很多对象的仓库,state就是数据的存放地,对应Vue对象里的data,state里存放的数据是响应式的
- getters:用于获得需要计算state后得到的值,实现简单的数据转换;接收state作为第一个参数,可以在多个组件之间复用
- mutations:更改state的值的方法集合
- actions:通常在这发送异步请求;通过调用mutations修改state的值,类似mutations但是不是直接改变状态;利用 context.commit 提交一个 mutation,
- modules:当应用变得非常复杂时,store 对象就有可能变得相当臃肿。为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、避免冲突以便维护
- mutation 是同步更新, $watch 严格模式下会报错,mutation主要是用于修改state的值的方法的集合,可以通过commit调用
- action 是异步操作,使用dispatch调用,之后可以获取数据后调用 mutation 提交最终数据
首屏加载优化
① 把不常改变的库放到 index.html 中,通过 cdn 引入
②vue 路由懒加载
③ 不生成 map 文件,找到 config/index.js 文件,修改为 productionSourcceMap:false
④vue 组件尽量不要全局引入
⑤ 使用更轻量级的工具库
⑥ 开启 gzip 压缩:这个优化是两方面的,前端将文件打包成.gz 文件,然后通过 nginx 的配置,让浏览器直接解析.gz 文件。
⑦ 首页单独做服务端渲染:如果首页真的有瓶颈,可以考虑用 node 单独做服务端渲染,而下面的子页面仍用 spa 单页的方式交互。这里不推荐直接用 nuxt.js 服务端渲染方案,因为这样一来增加了学习成本,二来服务端的维护成本也会上升,有时在本机测试没问题,在服务端跑就有问题,为了省心,还是最大限度的使用静态页面较好。
图片懒加载
IntersectionObserver
它是浏览器用来监听元素是否进入了设备的可视区域之内,而不需要频繁的计算来做这个判断API
IntersectionObserver
支持两个参数:
callback
是当被监听元素的可见性变化时,触发的回调函数options
是一个配置参数,可选,有默认的属性值
IntersectionObserver.observe()
方法向 IntersectionObserver 对象监听的目标集合添加一个元素。一个监听者有一组阈值和一个根, 但是可以监视多个目标元素,以查看这些目标元素可见区域的变化。
调用IntersectionObserver.unobserve()方法可以停止观察元素。
步骤
1.得到所有的Images数组集合
2.创建 obverser = new
IntersectionObserver
(callback)3.Images数组遍历item 通过IntersectionObserver.observer( item )观察元素
4.
Mixin
在日常的开发中,我们经常会遇到在不同的组件中经常会需要用到一些相同或者相似的代码,这些代码的功能相对独立,可以通过 Vue 的 mixin 功能抽离公共的业务逻辑,原理类似“对象的继承”,当组件初始化时会调用 mergeOptions 方法进行合并,采用策略模式针对不同的属性进行合并。当组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”。
与vuex的区别
vuex:用来做状态管理的,里面定义的变量在每个组件中均可以使用和修改,在任一组件中修改此变量的值之后,其他组件中此变量的值也会随之修改
Mixins:可以定义共用的变量,在每个组件中使用,引入组件中之后,各个变量是相互独立的,值的修改在组件中不会相互影响
与公共组件的区别
组件:在父组件中引入组件,相当于在父组件中给出一片独立的空间供子
组件使用: 然后根据props来传值,但本质上两者是相对独立的
Mixins:则是在引入组件之后与组件中的对象和方法进行合并,相当于扩展了父组件的对象与方法,可以理解为形成了一个新的组件
JS
JS的基本数据类型
js有六种基本数据类型 :null undefined number string boolean symbol
JS复杂数据类型
Object:Array,Function、RegExp、Date
总结
基本数据类型和复杂数据类型它们两者的不同主要是在存储的位置不一样。
null 和 undefined
undefined 代表不存在这个值,他是一个变量最原始的状态
null 代表存在这个值 ,但是这个值是空的
什么是DOM 、BOM
ES6的新特性你知道哪些?
如何在ES5使用const
function const ( key , value ){const desc = {value ,writable : false}Object.defineProperty( window , key , desc )
}const ( 'obj' ,{ a : 1 })
obj = {} //重新赋值不生效
闭包
什么是闭包?
一个可以访问其他函数内部变量的函数就是闭包,最常见创建闭包的方法就是在一个函数内部创建另一个函数
闭包的作用?
this对象
箭头函数的特点
Object.is 和 === 的区别
Object.is在严格等于上的基础修复了一些特殊情况下的错误,比如NaN 不等于 NaN
== (全等)和 ===(严等) 的区别
typeof 和 instanceof
typeof 会返回一个值的类型 对于基本数据类型;除了null会返回object,其余都会返回本身类型
Object.prototype.toString.call(检测数据类型最佳方案)
对于 Object.prototype.toString()
方法,会返回一个形如 "[object XXX]"
的字符串。
如果对象的 toString()
方法未被重写,就会返回如上面形式的字符串。
({}).toString(); // => "[object Object]"
Math.toString(); // => "[object Math]"
但是,大多数对象,toString()
方法都是重写了的,这时,需要用 call()来调用
1.判断基本类型:
Object.prototype.toString.call(null);//”[object Null]” Object.prototype.toString.call(undefined);//”[object Undefined]” Object.prototype.toString.call(“abc”);//”[object String]” Object.prototype.toString.call(123);//”[object Number]” Object.prototype.toString.call(true);//”[object Boolean]”
2.判断原生引用类型:
函数类型
Function fn(){console.log(“test”);} Object.prototype.toString.call(fn);//”[object Function]”
日期类型
var date = new Date(); Object.prototype.toString.call(date);//”[object Date]”
数组类型
var arr = [1,2,3]; Object.prototype.toString.call(arr);//”[object Array]”
正则表达式
var reg = /[hbc]at/gi; Object.prototype.toString.call(arr);//”[object Array]”
自定义类型
function Person(name, age) {this.name = name;this.age = age; } var person = new Person("Rose", 18); Object.prototype.toString.call(arr); //”[object Object]”
很明显
这种方法不能准确判断person是Person类的实例,而只能用instanceof 操作符来进行判断,如下所示:
console.log(person instanceof Person);//输出结果为true
3.判断原生JSON对象:
var isNativeJSON = window.JSON && Object.prototype.toString.call(JSON); console.log(isNativeJSON);//输出结果为”[object JSON]”说明JSON是原生的,否则不是;
Promise
什么是promise ?
promise是ES6提出的异步编程解决方案,相对于传统容易陷入回调地狱的异步回调方案来说,promise会让异步操作更加的优雅;
首先Promise他是一个构造函数 需要通过new关键字来生成一个Promise实例对象
他接收一个函数作为参数,函数中的代码会在new Promise的时候立刻执行,作为参数的函数有两个默认的参数 resolve 和 reject,这两个参数是函数标记异步执行的状态
当异步操作完成 我们可以调用resolve函数
当异步操作失败 我们可以调用reject函数
这些标记的状态可以在then和catch中接收;.then代表异步完成的回调,.catch是异步失败的回调
1. Promise他本身是同步执行的,
输出结果为: 1 ,2 (如果是异步,输出结果应该为 2 , 1)
let a = new Promise( (res, rej) => {
console.log(1);
});
console.log(2);
2. promise的回调 .then 和.catch 是异步的
输出结果为 1,2, 3 因为是异步 所以先打印2
let a = new Promise((res, rej) => {console.log(1);res(3)
});
//异步
a.then((res) => {console.log(res);
});
console.log(2);
原型和原型链
1.原型
每个构造函数都有一个prototype属性,这个属性指向一个对象(prototype/实例原型/冰条模具),这个对象内包括该构造函数所有公用的方法和属性,这个对象就是调用该构造函数创建的实例原型,每个原型对象都有一个constructor属性,这个属性会指向关联的构造函数。
通过构造函数实例化出来的实例对象有一个_proto_ 可以访问实例原型,实例对象在创建的时候会默认关联原型,并从原型上继承属性
2.原型链
当我们想去访问实例对象的属性时,实例对象却没有这个属性,JS只能去该实例对象的实例原型上寻找该属性,如果实例原型上依旧找不到,就会去原型的原型上找,直到找到Object为止,这个行为将形成一个链条,该链条就叫做原型链。
bind/call/apply
call和apply方法都是为了改变this的指向,作用相同,传参的方式不同;
call可以接受一个参数列表,而apply只能接受一个参数数组
bind也接收一个参数列表,和call以及apply一样 它的作用是为了改变this的指向,只是该方法会返回一个函数;通过bind实现柯里化
Event Loop
js最大的特点就是单线程 ,在同一时间就只能做同一件事,
这也意味着 任务是需要排队的。只有前一个任务完成了,才能去执行下一个任务。
所以任务也被分为了两种, 同步任务和异步任务(宏任务) /(微任务)
同步任务是在主线程执行,形成一个执行栈,只有前一个任务完成了,才能去执行下一个任务。
而异步任务不进入主线程,而是先在任务队列中放置
只有执行栈中所有同步任务被执行,才会去读取任务队列,把异步任务拉入执行栈执行
这个过程被不断的重复
get和post的区别
1.在应用场景上, get是一个幂等的请求 ,一般用于不会对服务器造成影响的场景,例如请求一个页面;
post是一个非幂等的请求,一般用于会对服务器造成影响的场景;例如注册账号;所以浏览器一般会对get请求缓存,而很少对post请求进行缓存
2.从发送报文的格式来说,Get请求中实体部分为空,而post请求中,报文实体部分一般是向服务器发送的数据;
3.GET请求也可以将参数放在URL中向服务器发送,但是这种方法不是很安全,因为请求的URL会保存
在历史记录中,且浏览器对URL会有长度上的限制;会影响GET请求发送数据的长度;相比之下POST请求更加安全且参数的传递支持更多的数据类型;
深拷贝和浅拷贝
浅拷贝:对于基本数据类型 直接将值赋值给新对象,对于引用数据类型,只复制内存地址而非对象本身,新旧对象共享同一内存,改变其中一个对另一个也有影响
深拷贝:对于基本数据类型 直接将值赋值给新对象,对于引用数据类型,深拷贝会新建一个对象空间 然后拷贝里面的内容,他们指向不同的内存空间 改变其中一个对另一个没有影响
如何实现浅拷贝:
//对象浅拷贝
如何实现深拷贝
2. JSON对象的parse和stringify
function deepClone ( obj ){
let _obj = JSON.stringify ( obj ),
objClone = JSON.parse ( _obj );
return objClone
}
let a = [ 0 , 1 ,[ 2 , 3 ], 4 ],
b = deepClone ( a );
a [ 0 ] = 1 ;
a [ 2 ][ 0 ] = 1 ;
console . log ( a , b );
手写函数
要求手写:转换驼峰的函数、冒泡排序、翻转数组、数组去重、求数组最大值和最小值、forEach、map、filter、reduce的实现、伪数组转换真数组、函数节流和函数防抖、数组扁平化处理
节流函数和防抖函数
1.节流函数:一段时间内只执行一次某个操作,过了这段时间 如果还有操作再继续执行新的操作
2.防抖函数:一段时间内只执行最后一次操作,
总结:防抖函数先清除定时器,再调用回调函数,节流函数先调用回调函数,再清除定时器
防抖函数
function debounce(fn, delay) {// 记录上一次定时器let timer = nullreturn () => {// 每次点击都清除上一个定时器 保证触发的是最后一次clearTimeout(timer)timer = setTimeout(() => {fn.apply(this)}, delay)}
}
let btn = document.querySelector('.btn')
console.log(btn);
btn.onclick = debounce(() => {console.log('debounce',);
}, 200)
节流函数
const box = document.querySelector('.box')
box.addEventListener('drag', throttle(() => {console.log('test');},2000))function throttle(fn, delay) {let timer = nullreturn () => {//如果有定时器 就直接返回if (timer) returntimer = setTimeout(() => {fn()timer = null}, delay)}
}
let btn = document.querySelector('.btn')btn.addEventListener('click', throttle1(function () {console.log(111);
}, 2000))function throttle1(fn, delay) {// 记录上一次触发的时间let lastTime = 0return function () {let nowTime = Date.now()if (nowTime - lastTime > delay) {// 如果现在时间-最后触发时间大于 规定触发时间delay,就调用需要被执行的函数;并且同步一下最后一次调用的时间fn.call(this)// 这里要call回自己否则可能出现this指向的问题lastTime = nowTime}}
}
转换驼峰的函数
let word = 'alice-love-u'
convertCamelCase('-', word)function convertCamelCase(symbol, str) {//根据- 来切割字符串 -> [alice,love,u]let arr = str.split(symbol)arr.map((item, index) => {// 如果是第一个就reurn if(index == 0) return // 截取item的首字母 转换为大写 + 去除第一个字母的item // alice -> A + licearr[index] = item.charAt(0).toUpperCase() + item.slice(1)})// 把[alice,Love,U] -> aliceLoveUlet newStr = arr.join('')return newStr
}
冒泡排序
// 让 [ 5, 4, 3, 2, 1]按从小到大的顺序排列
// 外循环控制轮数(有几个数),内循环控制比较(每个数要换几次)let arr = [5, 4, 3, 2, 1,10]for (let i = 0; i < arr.length - 1; i++) {for (let j = 0; j < arr.length - 1 - i; j++) {let temp = arr[j]if (arr[j] > arr[j + 1]) {arr[j] = arr[j + 1]arr[j + 1] = temp}}
}
console.log(arr);
翻转数组 reverse
let arr = [6,5, 4, 3, 2, 1]
for (let i = 0; i < arr.length / 2; i++) {let temp = arr[i]arr[i] = arr[arr.length - i -1]arr[arr.length - i -1] = temp
}console.log(arr);// [1,2,3,4,5,6]
用reduce 实现forEach
// 1.用reduce 实现 foreachconst arr = [1,2,3,4,5,6]function forEeach(arr) {arr.reduce((pre,cur,index,arr) => {console.log(cur,index);},0)
//要设置初始值为0,否则第一个item不显示;
}forEeach(arr)
map
// 实现map
let double = item => item * 2function Map(arr, handle) {const mapArr = []for (let item of arr) {mapArr.push(handle(item))}console.log(mapArr);return mapArr
}
Map(arr,double)// 用reduce 实现maplet double = item => item * 2
function Map(arr, handle) {const mapArr = []arr.reduce((accumlator,item) {mapArr.push(handle(item))}console.log(mapArr);return mapArr
}
Map(arr,double)
filter
// 实现filter
let getDouble = item => item % 2 === 0
function filter(arr,handle) {let newArr = []for(let item of arr) {if(handle(item)){newArr.push(item)}}console.log(newArr);return newArr
}
小野森森 题
题目1
a 在进行对比的时候 会自动调用toString的方法 每次对比都会+1,
'1' == 1 , 但若是恒等(===) 则不成立
网络协议
解释一下什么是HTTP和HTTPS
HTTP是超文本传输协议(HyperText Transfer Protocol)就是一种发布和接收HTML页面的方法,用于Web浏览器和网络服务器之间传输信息,它默认工作在TCP协议80端口,HTTP协议以明文的方式发送内容,不提供任何方式的加密,所以如果被攻击者截取Web浏览器和网络服务器之间的传输报文,就可以直接读懂其中的讯息,因此HTTP不适合传输一些敏感信息,例如银行卡号以及密码等支付信息;
HTTPS 是超文本传输安全协议(Hypertext Transfer Protocol Secure)是一种透过计算机网络进行安全通信的传输协议;HTTPS经由HTTP进行通信,但是利用SSL/TLS 来加密数据包。
HTTPS开发的主要目的是为了提供对网站服务器的身份认证,保证交换数据的隐私性和完整性;
HTTPS默认工作在TCP协议443端口,它的工作流程是
- TCP三次同步握手
- 客户端检验服务器数字证书
- DH算法协商对称加密算法的密钥,hash算法的密钥
- SSL安全加密隧道协商完成
- 页面以加密方式传输,用协商的对称加密算法和密钥加密,保证数据机密性,用协商的hash算法进行数据完整性的保护,保护数据不被篡改
HTTP和HTTPS的不同?
HTTP是明文协议,数据都是没有加密的,安全性差,HTTPS协议数据传输的过程都是加密的,安全性好,
HTTPS协议需要用到CA(数字证书认证机构)申请证书,一般免费证书较少,所以需要一定的费用
HTTPS的响应速度比HTTP慢,主要HTTP只使用TCP三次握手建立连接,客户端和服务端只需要交换3个包,而HTTPS除了TCP要交换的3个包还有SSL握手需要的9个包,一共是12个包
HTTP和HTTPS使用的连接方式不同,而且端口也不一样,HTTP是在80端口,而HTTPS是在443端口
HTTPS其实就是构建在SSL/TLS之上的HTTP协议,所以比HTTP更耗费服务器资源
HTML
回流和重绘
1.介绍一下回流和重绘
浏览器采用流式布局模型,浏览器会将HTML解析成DOM把CSS解析成CSSOM ,然后把CSSOM和DOM结合生成一个render tree
根据render tree我们能够知道节点的样式 浏览器会从根节点递归调用去计算每个节点的位置,然后把节点绘制到页面上
重绘的概念
当渲染树中的一些元素属性需要更新,但是这些属性只涉及和影响了元素的外观以及风格并不影响布局的情况下,我们称之为重绘
回流的概念
重绘不一定引起回流 但是回流一定会引起重绘
性能上 重绘的性能消耗小于回流
2.常见的引起回流的操作
3.如何减少回流?
1.使用documentFragment的方式来操作DOM
正确写法:先加入文档碎片 再一次性添加
错误写法:一条一条加
2.用transform代替 top
3.不要使用table布局
4.预先定义好class 直接修改className 而不是一条一条的去修改DOM样式
5.避免多层内联样式
6.将需要多次重排的元素 position属性设置为absolute 或fixed,脱离文档流
如何让浏览器不缓存
可以让URL的参数里随机生成一个数字,但是浏览器不缓存会导致页面加载速度变慢,服务器压力增加
浏览器本地缓存
localStorage sessionStorage cookies 的区别
localStorage是一种长期存储的方式,他的有效期是永久,除非用户手动清除,否则存储的信息会一直存在,它可以存放5M大小的数据,仅仅在浏览器中保存,不会和服务器通信,也不会把数据自动发送给服务器
sessionStorage仅在当前会话有效,关闭页面或者浏览器后就会被清除,存储大小一般为5M,数据仅仅在浏览器中保存,不会和服务器通信,也不会把数据自动发送给服务器
cookie的大小为4KB左右,始终会在同源http请求中携带,它的的有效期可以通过expire来设置。
localStoragese:常用于长期登录(+判断用户是否已登录),适合长期保存在本地的数据,而 sessionStorage :敏感账号一次性登录
localStorage & sessionStorage & cookies
localStorage
只要在相同的协议、相同的主机名、相同的端口下,就能读取/修改到同一份localStorage数据。sessionStorage
比localStorage
更严苛一点,除了协议、主机名、端口外,还要求在同一窗口(也就是浏览器的标签页)下
什么是跨域?
4.跨域的解决方案
jsonP、 cors服务器代理
核心是动态添加script标签调用服务器提供的JS脚本,允许用户传递一个callback参数给服务器,服务器在返回数据会将这个callback参数作为函数名来包裹JSON数据,这样客户端就可以随意定制自己的 函数来自动处理返回数据(仅支持GET方法)
!DOCTYPE
用于告知浏览器的解析器用什么模式来解析文档
在标准模式下,浏览器的解析规则都会按照最新标准进行。
在兼容模式下,浏览器会以向后兼容的方式显示,模拟老浏览器的行为,确保一些老的网站能够正常访问。
为什么HTML5只需要写!DOCTYPE 而不需要引入DTD
因为HTML5不再需要基于SGML ,而只是需要通过 !DOCTYPE来规范浏览器行为;
CSS
选择器权重
1.选择器权重只能同级比较,例如再多的 class选择器也抵不过一个id 选择器
!important > 行内选择器 > id > class > 标签选择器 > 通配符 > 继承 > 浏览器默认属性
动画特性可以使用js实现,为什么还要用css?
不占js的主线程,性能更高
postCss是什么 有什么用?
margin 负值
margin合并
1.有内容的div之间的margin会合并为他们之间较大的那个margin
2.有内容的div和空的div之间也会发生margin合并 空的div被忽略
垂直居中/水平居中
垂直居中
方法1.
.block {position: relative;top:50%;/* margin-top:-50px ; */transform: translateY(-50%);height: 100px;width: 100px;background-color: yellow;}2.还有给父元素设置display: flex;align-items: center;3.以及给父元素设置display: grid;给子元素设置align-self: center;4.line-height: center
水平居中
1.text-align:center
2.父元素设置display:flex 子元素设置 justify-content::center
3.父元素设置display:grid 子元素设置 justify-self:center
4.margin: 0 auto
5.子元素:{position: absolute;left: 50%;
transform: translateX(-50%);//或者margin-left -子元素的宽的一半}
BFC block formatting context 块级格式化上下文
什么是BFC
BFC的特性
内部的box会在垂直方向,从顶部一个一个放置
Box垂直方向的距离由margin决定。同属于一个BFC的两个相邻box的margin会发生叠加
形成BFC的常见条件
BFC 的作用?
设置后
三角形
.red {height:0;width:0;border:100px solid;border-color:red transparent transparent red;
}
.beauty{height:0;width:0;border:100px solid;border-color:pink yellow white orange;
}
display:none /opcity 与visibility:hidden的区别
如何进行网站性能优化
js方面
图片方面
css方面
移动方面
cookie方面
content方面
前端学习(面试收集)相关推荐
- 2020前端学习路线收集整理
本人18年毕业,刚毕业自学Java,第一份工作因部门解散,时效半年,其实那时我一直在想往前端发展,恰巧拿到下家offer,第二份工作确实是前端,不过是客户是银行,样式全部统一,与我想象的色彩缤纷的前端 ...
- 前端学习+面试小总结(二)
从过完年开始系统学习相关的知识,已经接近两个月了.这篇文章想系统的回顾一下自己的各种骚操作,并对接下来的路进行一个修正吧. 大致分为几个部分: 1. 我学了什么,学习的过程中有什么可取的点和不可取的点 ...
- 关于前端学习和笔试面试的总结
前沿 以前总是希望在技术论坛和博客能有人关注,最近收到一些小伙伴请教问题的来信和私信,在深感荣幸的同时也深知自己技术和经验的不足,怕会误人子弟,所以现在打算以应届生的身份尽自己的一点绵薄之力给大家一点 ...
- 2020 - 2021 年 Web 前端最新导航 - 前端学习资源分享前端面试资源汇总
前端javascriptvue.jses6typescript 发布于 10月9日 国庆这几天,我收集了大量可以显著提升开发效率的前端导航链接. 这些导航链接对我很有帮助,希望对你也是如此. 这些好用 ...
- GitHub上收集的最全的前端资源汇总(包括前端学习、求职、开发资源)
http://www.imooc.com/article/12645 个人结合github上各位大神分享的资源进行了简单的汇总整理,每一个条目下面都有丰富的资料,是前端学习.工作的好帮手. 项目地址: ...
- 前端学习资料网址收集整理
前端学习资料整理:百度cdn,jquery插件网站收集,html5资料整理等.方便查阅. A:基础知识,行业动态 http://www.51cto.com 51cto http://www.html5 ...
- 【学习笔记】前端开发面试锦集
链接地址:https://microzz.com/2017/02/10/interview/ 前端还是一个年轻的行业,新的行业标准, 框架, 库都不断在更新和新增,正如赫门在2015深JS大会上的&l ...
- web前端学习到什么程度可以面试工作
前端学习想要达到可以找工作的程度还是比较简单的,但是想要通过学习找到高工资的工作可就不容易了,如果只是想要找个普通的工作只要学了前端的基础知识,主流框架实现一定的兼容展示功能,在就是做几个前端 Web ...
- 一些有用的资源分享(工具+电子书+GitHub最全的前端资源汇总仓库(包括前端学习、开发资源、求职面试等))
原地址:https://mp.weixin.qq.com/s/wSN1w2mM6Fh51RDGZYOdIQ 工具类 图片相关工具 TinyPNG:https://tinypng.com/ 免费的在线图 ...
- 前端资源汇集(个人觉得不错的学习资源收集)
前言:此前在"掘金"上看到技术大牛们分享的技术贴,其中涵盖了很多本人所未接触过使用过的前端学习的资源.通过对里面几个自己比较感兴趣的资源网站进行学习之后发现真心不错,因此想与各位跟 ...
最新文章
- matplotlib 制作不等间距直方图
- 简单团队-爬虫豆瓣top250-项目总结
- 前端技术分享:教你玩转vue-router命令视图
- python参数化建模 书_Python 中如何实现参数化测试?
- JAVA绘制图片原理_java开发_图片截取工具实现原理
- 右键新建里面没有word和excel_Excel中为什么修改了新建工作簿的选项,新建以后还是没有生效...
- python元祖组成字典_Python基础之元组和字典
- 斯坦福「AI百年研究」首份报告:2030年的人工智能与生活
- FPGA有哪些优质的带源码的IP开源网站?
- 【热门收藏】iOS开发人员必看的精品资料(100个) ...
- 郁闷,做了很多无用功
- ubuntu火狐浏览器安装flash_player插件
- vue+element实现导入和导出excel
- java 使用mediainfo_使用mediainfo工具统计每个视频文件(媒体文件)播放时长
- 350导热油 shell_导热油320与350的区别,克拉克给你详细解说
- 2018 年最受欢迎的电影,你都看过哪些?爬猫眼电影
- 联奕“云计算”数字校园整体解决方案,让高校云计算不再是“浮云”
- HDU-5813-Elegant Construction-贪心
- 英汉翻译词典软件代码
- ant design pro / umi入门
热门文章
- 【自用】华南师范大学918c++程序设计选填错题
- IIS - 实现HTTPS的主机名绑定(解决IIS7下主机名灰色无法修改问题)
- 一梦江湖获取服务器信息,《一梦江湖》手游官方网站_《楚留香》现已全面升级重制-4月24日维护更新公告,灵犀NPC甜蜜奇遇更新...
- ssm汽车销售系统毕业设计(附源码、运行环境)
- 怎样设置范围使数据透视表能自适应数据源记录的变化
- 手机看html网页内容重叠,html5手机端叠加图片触屏滑动查看特效
- 小程序公众号平台添加服务器,微擎绑定对接微信公众号小程序图文教程
- upload-labs详细教程
- 网络学习云平台计算机基础答案,网络自主学习平台(计算机基础)
- 游戏音乐制作应该注意什么呢?