项目介绍--React
- 该项目是基于React的SPA项目,专注客户价值挖掘的后台管理系统,有登录页面,首页,数据管理面板,资源管理等一系列内容
// 做登录页,首先要进行我们表单的校验,调用Form表单提供的onFinish()方法,携带的参数values就是我们的用户信息,然后再通过dispatch派发action 进行登录操作
// 还需要处理的就是 Token失效问题,我们在某一个页面的时候token失效了然后回退到我们的登陆页面,就需要用到我们的 useHistory 和 useLocation 用来记住从那个页面退回的,做判断有没有退回的路径参数,有就跳转,没有就跳转至首页
`history.replace(location?.state?.from || "/");` 判断是否有返回的 location 中的 from 有即跳转 否则跳转至首页
// 再重新登录之后就直接进到之前退回的页面,这样可以提高用户的体验
const Login = () => {
const dispatch = useDispatch();
const history = useHistory();
const location = useLocation();
// console.log(location); // 获取路由推过来的数据
// 根据Form表单提供的 onFinish 函数的 参数 values 可以得到表单的值
// initialValues 初始化表单的值
const onFinish = async (values) => {
// console.log(values);
// 调用action 触发登录操作
try {
await dispatch(login(values)); // 登录成功
message.success("登录成功");
history.replace(location?.state?.from || "/"); // 判断是否有返回的 location 中的 from 有即跳转 否则跳转至首页
} catch (error) {
// 提示消息
message.error("登录失败");
}
};
cost http = axios.create({
baseURL:
timeout:
})
// 在请求拦截器中统一注入Token
http.interceptors.reauest.use(
(config)=>{
if(state.user.token){
config.header.Authorization = `Bearer ${state.user.token}`
}
}
)
// 在响应拦截器中处理token失效的问题
http.interceptors.response.use(
// 判断后端返回的状态码是否为401
if(e.response.status === 401){
// 本地token清除 redux信息清除 跳转至登录页 还要记住是从那个页面失效退回的,重新登录之后再跳转至那个页面
message.error('登录失效')
store.dispatch(logout())
// 但是这里有个问题 我们无法在非组件环境中使用useHistory hook,拿到路由对象,进行路由跳转操作,所以需要路由提供的Router组件,并自定义history对象,所以就需要一个第三方的包history,然后利用createBrowserHistory 创建自定义的history对象 customHistory
if ( customHistory.location.pathname !== '/login') {
customHistory.push({
pathname: '/login',
state: { from: customHistory.location.pathname }
})
}
}
)
// 封装AuthRoute路由鉴权组件
// 基于Route来封装,Route里面会有一个render属性 返回一个回调,我们在里面判断当前是否登录即token 未登录就用Redirect组件重定向到登录页面 登录了就渲染相关页面组件
const AuthRoute = ({ component: Component, ...rest }) => {
return ( // AuthRoute会接收props参数,要么是Component 要么是children组件
<Route
{...rest}
render={props => {
// 判断是否登录
if (!getToken()) {
// 未登录
return (
<Redirect
to={{
pathname: '/login',
state: { from: props.location.pathname }
}}
/>
)
}
// 登录
return <Component />
}}
/>
)
}
// React.lazy配合React.suspense 来实现路由懒加载
// React.lazy接收一个回调函数中引入我们需要懒加载的组件 然后suspense组件包裹需要懒加载的路由 需要一个必填属性 fallback来指定loading占位内容
import { lazy, Suspense } from 'react'
const Home = lazy(() => import('./Home'))
<Suspense fallback={加载中的提示内容}>
// 懒加载的组件,需要被 Supspense 包裹
<Home />
</Suspense>
// 解决开发时的跨域问题
// 使用第三方包 http-proxy-middleware 来解决 首先在app.use中给代理标识 '/api' ,通过createProxyMiddleware来进行代理配置,配置target:目标服务器地址 axios发来请求 /api/user/profile 浏览器解析地址走cra启动的开发服务器,判断请求地址上是否携带'/api',有的话就走代理,然后重写请求路径,去掉接口中的 /api前缀
const { createProxyMiddleware } = require('http-proxy-middleware')
module.exports = function (app) {
app.use(
// 代理标识
'/api',
// 代理配置
createProxyMiddleware({
// 目标服务器地址
target: 'http://toutiao.itheima.net/v1_0/',
changeOrigin: true,
pathRewrite: {
// 去掉接口中的 /api 前缀
'^/api': ''
}
})
)
}
项目介绍--vue
1、登录、注册模块
先进行页面渲染 flex布局
再(el-ui,from,校验)·········
然后因为这是前后端分类的项目(项目和接口不在同一个域名下)先处理跨域的问题,可以在vue-cli的配置文件vue.config.js中设置代理
// 代理跨域的配置
// 服务器代理过程:
// 1.浏览器访问: http://localhost:8888/api/sys/login
// 2.devServer做了拦截
// 'http://ihrm-java.itheima.net/'+'/api'+'/sys/login' = target路径+拦截路径+接口路径
// http://ihrm-java.itheima.net/api/sys/login
// 3.devServer得到接口结果返回给浏览器
proxy: {
// 当我们的本地的请求 有/api的时候,就会代理我们的请求地址向另外一个服务器发出请求
'/api': {
target: 'http://ihrm-java.itheima.net/', // 跨域请求的地址
// target: 'http://192.168.19.243:3000/',
changeOrigin: true // 只有这个值为true的情况下 才表示开启跨域
}
}
接下来封装登录接口 获取数据,返回数据携带token,本地存储起来 ,vuex共享token 为了需要权限请求时携带,在请求拦截器中统一注入token 然后在vuex的Action(异步)中处理token
2.菜单权限和按钮权限控制
2.1.1 菜单权限
只显示当前用户有权访问的菜单,如果没权限用户通过url强制访问就跳转404
后端返回的权限数据包括 api[]--api访问权限,menus[]--菜单权限数组,points[]--功能权限即按钮权限数组
// 在权限管理页面设置了一个标识,如果用户拥有这个标识,那么该用户就拥有这个路由模块,没这个标识就不能访问
// 总体思路:用户登录--拿到权限标识(在权限设置页面设置)--然后权限标识和路由进行结合,筛选得出权限路由--在全局守卫beforeEach()中通过addRoutes动态添加
// 请求权限数据放在vuex中定义一个user模块,里面有userInfo对象,存放菜单权限数据的数组,利用这个权限数据匹配之前配置的动态路由表,在每一个路由模块中定义了name属性,和权限标识一致,这样标识和name属性能对的上,就说明用户拥有这些权限,
// name和权限标识对应实现:---遍历权限数组,设置key值(key即是权限标识),删选动态路由模块中的name属性是否和key值相等,这样遍历完成之后会得到一个新数组(用户有权访问的路由模块),之后在全局路由守卫beforeEach()中,将这个新路由数组通过addRoutes 和 静态路由合并添加到路由表中,这里要注意在添加动态路由之后必须要next('地址'),不然刷新之后权限就会失效,这样用户的菜单权限就完成了
// store中 子模块想调用另一个子模块的mutation,就需要传入第三个参数{root:true}
context.commit('子模块路径','载荷',{root:true})
2.1.2 功能权限
// 登录之后--获取权限标识points数组,若当前用户拥有这个标识说明有这个功能的权限--比如新增员工(标识:add-employ)
// 在页面中需要一个方法,通过v-if来调用,在这个方法中传入按钮的权限标识,就可以实现按扭根据权限标识来显隐
// 考虑到这个方法会在很多组件中用到,所以我用了minix混入 在src下新建一个minix文件,然后在main.js中引入,用vue.minix(checkPermission)注册
export default{
// 混入对象是组件的选项对象
methhods:{
// 检查权限 有或者无 参数key就是要检查的权限标识
checkPermission(key){
// 去用户信息中找,若有key则认为有标识,没有key则没标识
const { userInfo } = store.state.user
if(userInfo.roles && userInfo.roles.points){
return userInfo.roles.points.find((item)=> item === key)
}
return false // 若没进到if判断 则没有权限
}
}
}
3. filter过滤器
// 比如在查看审批的时候,后端返回给我们的数据是一个数组,里面有审批的状态,但他返回的是一个数字,比如1--为审批,2--已通过,3--审批中,那我们不能将数字渲染到我们的页面上,就需要通过过滤器来删选,
// 我定义了一个filter模块,里面导出过滤器方法,方法接收参数value,就是后端返回的状态数据,循环这个数据让它和我们需要看到的文字一一对应
// 在页面中引入过滤器方法,在插值表达式中使用(过滤器也可以串联使用 | |)
// 有时后端返回给我们的不是可读的时间而是时间戳,所以就需要时间过滤器,formatDate() 函数里面通过new Date 来获取我们的年月日 之后在页面中引入formatDate()
axios请求拦截器和响应拦截器,
一般我们都会封装单独的模块,如util/request.js中
import axios from 'axios'
// 对axios进行配置
const http = axios.create({
baseURL: process.env.REACT_APP_URL,
timeout: 5000
})
// 统一添加token在请求头
http.interceptors.request.use(config => {
// 对config进行修改,每次请求前做的事情
const state = store.getState()
if (state.user.token) {
config.headers.Authorization = `Bearer ${state.user.token}`
}
return config
}, e => Promise.reject(e))
// 返回数据层级太多 res.data.data 后台给的有用的数据
http.interceptors.response.use(res => {
// 取第二层的data,如果没取到还是使用res,将来的await得到的数据就是res.data.data
return res?.data?.data || res
}, e => {
// 处理token失效
if (e.response.status === 401) {
// 本地token清除,redux信息清除 === 做退出
store.dispatch(logout())
// 避免多次请求多次跳转login页面
if (customHistory.location.pathname !== '/login') {
message.error('登录失效')
// 跳转到登录页面
customHistory.push({
pathname: '/login',
state: {
// 当前地址栏上的路由地址
returnUrl: customHistory.location.pathname
}
})
}
}
return Promise.reject(e)
})
export default http
防抖、节流函数
防抖(debounce) ,就是防止抖动,在一定的时间内频繁触发的事件会被重置,避免无意识的发送多次请求,只触发最后一次事件
- 登录,发短信,输入框搜索,浏览器窗口调整大小时,这些场景都需要防抖
// 防抖重在清零 clearTimeout(timer)
function debounce(fn, wait) {
let timer;
return function(){
clearTimeout(timer)
timer = setTimeout(()=>{
fn.apply(this, arguments)
},wait)
}
}
debounce(fn,500)
节流(throttle) ,就是控制事件发生的频率,一定时间内只能触发一次,比如控制为1s发生一次等
- **scroll事件,浏览器播放事件,input框实时搜索等
// 节流重在开关锁 timer=null
function throttle(fn, wait) {
let timer;
return function(){
if(!timer) {
//通过apply() 改变this指向
fn.apply(this, arguments)
timer = setTimeout(()=>{
clearTimeout(timer)
timer = null
},await)
}
}
}
throttle(fn,500)
vue相关面试题
vue组件封装
- 目的是为了提高整个项目的开发效率,将网页抽象成多个相对独立的模块,解决了传统项目开发:效率低,难维护,复用性等问题
- 1.分析业务需求,把页面中可以复用的结构 样式 以及功能单独抽离出来
- 2.具体步骤;创建一个组件,然后使用vue.component方法注册组件,之后使用组件通信来达到数据的使用和修改
minix
- 不同组件中需要用到相同的逻辑代码,这些代码的功能相对独立,就可以通过mixin功能抽离公共的业务逻辑,当组件初始化的时候就调用mergeOptions方法进行合并
- 合并时会遍历父组件和子组件,看那个里面有
vue修饰符有哪些
//事件修饰符
.stop 阻止事件继续传播
.prevent 阻止默认行为
.once 事件将只会触发一次
// v-model 的修饰符
.lazy 可以通过这个修饰符 转变为change事件在同步
.number 将输入的值转化为数值类型
.trim 自动去空格
.sync 修饰符可以实现子组件与父组件的双向绑定,并且可以实现子组件同步修改父组件的值
$set的用法
- set属于data的一个新属性,而vue的原理是在创建实力的时候,遍历data里面的值,监听‘getter’和‘setter’方法,一旦这些值更新了就出发相应的视图更新
- 而set不是vue实例创建时就拥有的属性,所以我们新增这个属性,vue并没有对它的setter和getter方法进行监听,因此无法实现双向绑定,要使用this.$set来进行双向绑定
$nextTick()理解
-
nextTick是Vue提供的一个全局API,Vue在更新DOM时是异步执行的,当数据发生变化,vue会开启一个异步DOM更新队列, -
$nextTick ()传入一个回调函数,等DOM更新完会挨个触发$nextTick()里的函数,统一进行更新 -
使用场景
- 如果我们需要在修改数据之后立即拿到更新后的DOM解构,就可以使用
**vue.nextTick()**,在组件中使用的话通过this.$nextTick()就可以拿到修改后的值
- 如果我们需要在修改数据之后立即拿到更新后的DOM解构,就可以使用
路由守卫
路由守卫是做什么得:简单说路由守卫就是路由跳转过程中的一些钩子函数,这个函数让我们可以操作一些其他的事,类似于组件得声明周期
什么时候用到路由守卫:
有哪些路由守卫
全局路由守卫:
- beforeEach(to,from,next) 前置全局守卫,在路由跳转前触发
- beforeResolve(to,from, next)
- afterEach(to,from) 后置全局守卫,路由跳转后触发
- 单个路由配置时的路由守卫:beforeEnter(to,from,next)
- 组件守卫,指在组件内执行的钩子函数
- beforeRouteEnter(to,from, next)
- beforeRouteUpdate(to,from, next)
- beforeRouteLeave(to,from, next)
keep-alive缓存组件
-
Vue.js内部将DOM节点抽象成了一个个的VNode节点,keep-alive组件的缓存也是基于VNode节点的而不是直接存储DOM结构。它将满足条件(pruneCache与pruneCache)的组件在cache对象中缓存起来,在需要重新渲染的时候再将vnode节点从cache对象中取出并渲染。 -
方式一:缓存部分页面 通过
v-if- 首先需要在路由配置当中添加
meta属性,设置keepAlive是否为true,控制缓存的页面放到keeep-alive包裹的router-view中还是单独的router-view中
- 首先需要在路由配置当中添加
// 最外边组件,根组件
const App = {
name: 'App',
template: `<div>
<!-- 使用keep-alive缓存页面 -->
<keep-alive>
<!-- 需要缓存的页面放这里 -->
<router-view v-if="$route.meta.keepAlive"></router-view>
</keep-alive>
<!-- 不需要缓存的页面放这里 -->
<router-view v-if="!$route.meta.keepAlive"></router-view>
</div>`
}
// 路由配置
const router = new VueRouter({
routes: [
{
path: '/', component: Layout, children: [
// 需要缓存的路由,添加原信息,keepAlive为true
{ path: '', component: Home, meta: { keepAlive: true } },
{ path: '/my', component: My }
],
// 需要缓存的路由,添加原信息,keepAlive为true
meta: { keepAlive: true }
},
{
path: '/login', component: Login
}
]
})
-
方式二:通过给
keep-alive设置include属性控制哪些页面需要做缓存,<keep-alive :include="cachedViews"></keep-alive>cachedViews:['组件name']接收一个需要做缓存的页面name属性的数组
// 最外边组件,根组件
const App = {
name: 'App',
template: `<div>
<!-- 使用keep-alive缓存页面,通过include缓存部分页面 -->
<keep-alive :include="cachedViews">
<router-view></router-view>
</keep-alive>
</div>`,
data() {
return {
// 指定要缓存的组件name数组
cachedViews: ['Layout']
}
}
}
vue路由传参
-
params传参(url中显示参数)
- path 路径传参
- 父路由组件上使用 向子路由组件传递参数
- 子路由通过 this.$route.params.num的形式来获取父路由传递过来的参数
- params(不显示参数),通过路由的别名name进行传值
-
Query实现路由传参
- 父路由组件上使用向子路由传递参数
- 子路由通过this.$route.query.num的形式来接收
插槽的使用
- 默认插槽
// 直接写slot
<slot></slot>
- 具名插槽
- 方式1--通过slot="after" after为插槽名称
<el-button slot="after">新增</el-button>
- 方式2--通过 <template v-slot:after>
<template v-slot:after>*
<el-button type="primary" size="small">新增权限</el-button>
</template>
- 方式3--通过 <template #after>
<template #after>
<el-button type="primary" size="small">新增</el-button>
</template>`
- 作用域插槽
// 推荐和template标签连用 因为template不产生DOM元素
<template slot-scope="data">{{ data }}</template>
- 具名作用域插槽
// 注:data为接收的数据 若是对象可以解构
// 具名插槽和作用域插槽连用 原始语法
<template slot="插槽名称" slot-scope="data">{{ data }}</template>
// 新语法 标准写法
<template v-slot:"插槽名称"="data">{{ data }}</template>
// 简化写法 用#代替
<template #插槽名称="data">{{ data }}</template>
父子组件生命周期顺序
// 加载渲染过程
父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted
// 更新过程
父beforeUpdate->子beforeUpdate->子updated->父updated
//销毁过程
父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
vuex的理解,有哪些属性
- State定义公共状态数据,可以在这里设置默认的初始状态
// 获取到vuex中的store对象实例
this.$store
//辅助函数--mapState 将store中的数据映射到computed中 属于一种方便方法
computed:{
...mapState(['count'])
}
- Getter 通过组件从Store中获取数据,mapGetters辅助函数是将store中的getter映射到局部计算属性
//有时需要从state中派生出一些状态,这些状态是依赖state的,此时会用到getters - Mutation 是唯一可更改store中状态的方法,并且是同步的
// state数据的修改只能通过mutations,并且mutations
mapMutations
// 可以将mutations中的方法导入到methods中
import { mapMutations } from 'vuex'
methods: {
...mapMutations(['addCount'])
}
- Action 进行异步操作,通过mutation更改状态
// actions 来执行异步操作,修改数据也需要调用mutations方法
// 获取异步的数据 context表示当前的store的实例,可以通过context.commit 来提交mutations
mapActions
methods: {
...mapActions(['getAsyncCount'])
}
- modules 模块化,把所有的状态都放在state中,当项目变得越来越大的时候,vuex会变得越来越难以维护
namespsced
//默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的——这样使得多个模块能够对同一 mutation 或 action 作出响应。
// namespaced 就相当于给模块上了一道锁 保证了内部模块的高封闭性
computed和watch的区别
-
computed计算属性- computed多对一,一个数据受多个依赖数据影响(商品结算功能)
computed计算属性是依赖已有的变量来计算一个目标变量,大多数情况都是多个变量凑在一起计算出一个变量computed具有缓存机制,当computed中的函数依赖的属性没有发生改变,调用这个函数的话就从缓存中获取结果computed不支持异步操作,当computed中有异步操作时无效,无法监听到数据的变化
-
watch侦听属性- 不支持缓存,
watch监听的是已经在data中定义的变量,当该变量变化时,会触发watch中的方法 - 支持异步操作
watch监听的函数接收两个参数,第一个是最新的值(newValue),第二个是输入之前的值(oldValue)- watch 一对多,一个数据影响多个数据(搜索功能),
- 不支持缓存,
-
即能用computed实现又可以用watch监听来实现的功能,优先推荐computed,相比watch更高效
.sync修饰符
.sync修饰符 // 例如父亲有1000元 儿子需要使用这些钱
//子组件 通过 'update:money' 事件名去触发响应
<button @click="$emit('update:money', money-100)">
// 父组件 通过 $event 来接收经过子组件修改后的值
<Child :money="total" v-on:update:money="total = $event"/>
//经过 .sync 修饰符简化后
<Child :money.sync="total"/>
做过哪些性能优化
// 编码阶段
1.不需要响应式的数据不要放到data中(可以使用Object.freeze())冻结数据
2.v-if和v-show区分场景使用
3.v-for遍历的时候必须给key值,避免和v-if连用
4.路由进行懒加载,
5.图片懒加载
6.第三方包采用import按需引入
7.做SPA页面的时候适当采用keep-alive缓存组件
8.大数据列表和表格性能优化--虚拟列表/虚拟表格
9.防止内存泄漏在组件销毁后把全局变量和全局事件销毁
10.防抖和节流函数的运用
11.服务端渲染SSP 预渲染
12.打包的时候将一些体积大的第三方包添加externals排除打包,采用CDN引入的方式(需要根据环境变量来判断是否是生产环境,是就排除否则不排除)
判断数据类型
// 判断数据类型
typeof // 基本数据类型
instanceof // 判断引用数据类型 根据protoType来判断的
constructor //每一个实例对象都可通过constructor 来访问它的构造函数 如 '5'.__proto__.constructor === String // true
Object.prototype.toString.call() // Object.prototype.toString方法返回对象的类型字符串,因此可用来判断一个值的类型。如:Object.prototype.toString.call('5') //[object String]
ES6新增了哪些属性
// let const 块级作用域
// 箭头函数
// promise
// 扩展运算
// 对象结构赋值
// 模块化
// 模板字符串
// 函数默认参数
this指向问题
//this 的指向问题
全局范围内this指向window对象
函数中 this指向最后调用他的那个对象
构造函数中,this指向new出来的那个新对象
// 相同点都是用来改变this指向
bind() // 会立即返回一个新函数
call() // 第一个参数是指定的this值,第二个是接收若干个参数列表
apply() //第一个参数是指定的this值,第二个是一个数组
javascript实现继承的方式
- 以原型链(protoType)的方式来实现继承,缺点:在包含有引用类型的数据时,会被所有的实例对象共享,容易造成修改的混乱。
- 借用构造函数(constructor)的方式来继承,缺点:无法实现函数方法的复用
- 组合继承,将原型链和借用构造函数组合起来,解决了以上两种方法的弊端
- 寄生式继承
- 寄生式组合继承
数组方法总结
//splice()和slice()的区别
slice() 不改变原数组,接收两个参数(start,end),可以用来做字符串的截取类似于字符串方法substring()和subStr(),也可以浅拷贝
splice() 会改变原数组,向数组中添加或删除项目,接收三个参数(index(添加或删除的起始位置),要删除的数量(如果为0,不删除),第三个可选参数(向数组中添加的元素))
// 改变原数组的方法
splice() // 添加或者删除数组
sort() // 数组排序
push() // 向数组末尾添加元素 返回数组的长度
pop() // 删除数组中的最后一个元素 返回这个元素
shift() // 删除数组的第一个元素 返回这个元素的值
unshift() // 向数组首位添加元素 返回数组的长度
reverse() // 颠倒数组中元素的顺序
// 不改变原数组的方法
slice() // 浅拷贝数组元素
join() // 数组转字符串
toString()/toLocaleString() // 数组转字符串
indexOf() // 查找数组是否存在某个元素 返回下标
includes() //查找数组是否包含某个元素 返回布尔值
// 遍历数组的方法
every 检测数组所有元素是否都满足条件
some 检测数组中是否有满足判断条件的元素
map 返回新数组
filter 返回新数组
reduce 累加器 返回一个值
Array.from(Array(100),()=>{}) // 快速生成一个100个元素的数组列表
// 数组去重
- 利用数组方法**splice()删除元素**
- 创建一个新数组,判断新数组中是否存在该元素,如果不存在就将该元素添加到新数组中
- 借助**indexOf()方法**判断此元素在该数组中首次出现的位置下标与循环的下标是否相等
- 利用数组中的**filter()方法**
- `var arr1 = new Set(arr) Array.from(arr1)`
- `var newArr = ([...new Set(arr)])`
// 数组转字符串
- 数组到字符串
- join() // 将数组中的所有元素通过指定的分割符分隔之后,放入一个字符串
- Array.toString()
- toLocalString()
- 字符串到数组
- split() // 把一个字符串以特定字符分隔成字符串数组后返回 接收两个参数
- 扩展运算符[...]
- Array.from()
map和set有什么区别,WeakMap和WeakSet有什么区别
Set
// Set本身是一个构造函数,可以接收一个数组,作为参数来初始化Set
// Set中的值可以是任意类型的,但是不能重复,所以可用来数组去重
// Set中的两个对象永远是不相等的
// 在向Set中添加值得时候,不会发生类型转换 例如:"5"和5是两个值
WeakSet
// WeakSet是构造函数,可接受一个值为对象的数组,来初始化WeakSet
// WeakSet的值只能是对象类型
// WeakSet没有size属性不能被遍历
// WeakSet中得值也不能重复
Map
// Map是键值对得集合,可以是任意数据类型
// Map通过set添加成员,通过get方法来获取成员
// Map也是构造函数,也可以接收一个数组作为参数
WeakMap
// WeakMap也是通过set添加成员,通过get方法来获取成员
// WeakMap也是构造函数,也可以接收一个数组作为参数
// WeakMap只接受对象作为键名
// WeakMap没有size属性不能被遍历
对象的深浅拷贝
-
浅拷贝就是创建一个新对象,如果是基本数据类型,拷贝的就是基本类型的值,若是引用类型那拷贝的就是栈内存地址,新旧对象还是共享同一块内存地址,修改新对象会影响到原对象
- Object.assign()
- lodash库中的.clone方法
- ES6的扩展运算符
- Array.prototype.concat()
- Array.prototype.slice()
-
深拷贝是将一个对象从内存中完整的复制一份出来,从堆内存中开辟一个新的区域存放对象,新旧对象不会共享内存,修改新对象不会影响旧对象
- JSON.parse(JSON.stringify())
- lodash函数库中的.cloneDeep方法
- $.extend()方法==>jquary提供的
- 递归 (里面判断是否是个 对象,是的话再次调用这个函数,不是就push到新对象里面
js中new做了哪些事
- 创建了一个新对象
- 继承原型链,即将新对象的
__proto__指向构造函数的prototype对象 - 改变this的指向,让this指向这个新对象
- 执行构造函数中的代码(为这个新对象添加属性)
- 返回这个新的对像
H5新增了哪些新特性
- form表单新增 ---- input输入框的type类型
email 验证邮箱格式
number 只能输入数字
date 日期
search 搜索框
// 新增属性
placeholder 提示信息
autofocus 自动获取焦点
required 必填项
max 最大值
min 最小值
maxlength 最大长度
minlength 最小长度
// 新增语义化标签
header 头部
footer 底部
nav 导航
aside 侧边栏
article 文章
// 新增了视频 音频标签
video --- autoplay muted controls loop
audio --- autoplay muted controls loop
// 新增元素
canvas 画布 将跨域的图片渲染到canvas画布上面就形成了canvas污染,此时无法获取该canvas的数据
//(Canvas 渲染第三方图片请求不受 CORS 限制)
// 在页面创建一个canvas标签之后,首先要使用getContext()获取canvas的上下文执行环境 参数只支持2d
//context.fill() 填充当前的图像
program 进度条
// 新增本地存储
localStorage
sessionStorage
css3新增特性
rgba --- 颜色
border-shadow --- 边框阴影
text-shadow --- 文字阴影
border-radius --- 圆角
animation --- 动画
display:flex --- 弹性盒模型
transform --- 转化
display和visible的区别是什么
-
display:none
- 浏览器不会渲染display属性为none的元素,不占据空间
- 无法进行DOM事件监听
- 动态改变此属性时会引起重排,性能较差
- 不会被子元素继承,毕竟子类也不会被渲染
- transition不支持display
-
visibility:hidden
- 元素被隐藏,但是会被浏览器渲染,所以占据空间
- 无法进行DOM事件监听
- 动态改变此属性会引起重绘,性能较高
- 会被子元素继承,子元素可以通过设置visibility:visible,来取消隐藏
- transition:visibility 会立即显示,隐藏时会延迟
-
opacity:0
- 透明度为0,元素隐藏,占据空间
- 可以进行DOM事件监听
- 不会触发重绘,性能较高
适配相关 响应式布局
css@media媒体查询
- 原理就是给不同的设备定义不同的样式来达到自适应效果
- 缺点就是 浏览器大小改变的时候,需要改变的样式太多,很繁琐
百分比( % )
- 子元素的宽高 padding margin 等,都相对于直接父元素
- 缺点 计算困难
rem
- rem 单位就是相对于根元素html的font-size的大小来决定的
- 缺陷 必须通过js来动态的控制根元素font-size的大小
VW 和 Vh 来实现自适应
- vw相当于 浏览器视口宽度的百分之一
- vh 相当于 浏览器视口高度的百分之一
移动/pc端适配
-
css媒体查询 @media
-
使用rem 单位(相对于根元素html的字体大小的单位)
-
flexible 适配方案
-
viewport 适配方案(vw:是 viewport width 的简写)
- 1vw 等于 window.innerWidth 的 1%
- 1vh 等于 window.innerHeihgt 的 1%;
- 可以使用插件
postcss-px-to-viewport
-
在 html 头部设置 mata 标签,同时不允许用户手动缩放
flex布局属性
flex-direction // 改变主轴方向 值:row(水平) column(垂直)
flex-wrap //用来换行
flex-flow // 将以上两种结合
justify-content //定义子元素在主轴上的对齐方式 值:(flex-start-默认起点开始 flex-end-终点对齐 center-居中对齐 space-between-两端对齐 space-around-子元素左右间距相等)
align-items //定义子元素侧轴对齐(start end center)
align-content //多行显示下有效
// 复合属性
//可以使用flex属性来同时设置flex-grow(子元素的放大比例)、flex-shrink(子元素的缩小比例)、flex-basis(跟width属性一致)这3个属性
flex: grow shrink basis;
// flex 属性的默认值为 "0 1 auto"
// flex:1 等价于 "flex:1 1 auto" 就是说flex取值只有一个数时,表示只设置了flex-grow 这个属性的取值
谈谈BFC是什么
-
BFC全称
block Formatting Context,即块级格式化上下文 -
BFC是一个完全独立的(空间)布局环境,让空间之内的子元素不会影响到外面的布局
-
BFC的规则
-
BFC就是一个块级元素,块级元素会在垂直方向一个接一个的排列BFC就是页面中的一个隔离的独立容器,容器里的标签不会影响到外部标签垂直方向的距离由margin决定, 属于同一个
BFC的两个相邻的标签外边距会发生重叠计算
BFC的高度时,浮动元素也参与计算
-
-
创建BFC
- 根元素,即HTML元素
- float的值不为none
- position为absolute或fixed
- display的值为inline-block、table-cell、table-caption
- overflow的值不为visible
-
BFC使用场景
- 去除边距重叠现象
- 清除浮动(让父元素的高度包含子浮动元素)
- 避免某元素被浮动元素覆盖
- 避免多列布局由于宽度计算四舍五入而自动换行
清除浮动
- 额外标签法,在最后一个浮动标签后,新加一个标签,设置
clear:both - 给父级元素添加
overflow:hidden - 使用
after伪元素清除浮动 - 使用
before和after双伪元素清除浮动
预编译less和sass
- 可以将常用的样式标记成变量,以便后续复用,方便维护
$highlight-color: #f40;
$basic-border: 1px solid black;
#app {
background-color: $highlight-color;
border: $basic-border;
}
- &父选择器
// 当我们想针对某个特定元素进行设置时
$highlight-color: #f90;
$basic-border: 1px solid black;
#app{
background-color: $highlight-color;
border:$basic-border;
.container{
font-size:30px;
}
a{
color:blue;
&:hover{
color: red;
}
}
}
- 可以模块化
我们可以将需要的变量定义在一个新的js文件中,需要使用时直接引入就行
- scss的minix
//可以自定义一些scss方法,相当于的封装了样式方法,将需要复用的样式封装起来,便于复用
@minix
scss和less区别
- 首先变量符号不一样,Less是@,Scss是$
- 编译环境不一样,scss是在服务端处理的,less是需要引入less.js输出css到浏览器
- Scss支持条件语句,可以使用if{}else{},for{}循环等,Less不支持
常用git指令
git reflog 查看所有的提交记录
git log 查看当前版本以前的提交记录
git remote add origin 地址 将本地仓库与某个远程仓库关联起来
git remote remove origin 将已关联的远程仓库删除(然后可添加新的远程仓库)
git push -u origin master或其他分支名 将分支 推送到 远程仓库(第一次,之后可直接git push
git push origin --delete 远程分支名 删除远程分支
git diff 文件名 查看文件与仓库中最近一个版本的差异
git diff 版本号1:文件名 版本号2:文件名 查看两个版本库中某个文件的差异
git cherry-pick 拣选
// 版本回退
git reset -- (soft | mixed | hard)
--hard 回退全部
--mixed 回退部分
--soft 只回退 HEAD
git reset --hard HEAD 回退到上个版本
git reset -- hard HEAD~3 回退到前三次提交之前
将后端返回的列表数据转为树形结构数据
// 将列表数据转化为树形数据 用到递归算法 => 自身调用自身 => 尤为重要的一点就是自身调用自身的条件一定不能一样(否则会陷入死循环)
// 遍历树形数据 重点就是要 找到一个遍历的开始数据(头儿)
//(list,rootValue) list代表后端返回的列表数据 rootValue代表数据标识 就是区分有无子节点的pid
function tranListToTreeDate(list,rootValue) {
var arr = [] //定义空数组 接收遍历list返回的数组
// 遍历 list
list.forEach(item=>{
//做判断 当前的项的id是否等于 rootValue 相等则意味着当前项有子节点
if(item.id === rootValue) {
// 找到之后再去 item 项下面找是否有子节点 就是再次调用该方法(自己调用自己)
// 之后会返回一个包含子节点的新数组
const children = tranListToTreeDate(list,rootVlaue) // 当前的rootValue为item.id
if(children.length){
// 如果children的长度大于0 说明找到了子节点
item.children = children
}
arr.push(item) // 将遍历得到的内容追加到数组中
}
})
// 返回这个数组
return arr
}
前端实现超大图片上传
实现流程如下:
1.用户通过input框选择照片
2.使用FileReader进行图片预览
3.将图片绘制到canvas画布上
4.使用canvas画布的能力实现图片压缩
5.将压缩后的Base64(DataUrl)格式的数据转换成Blob对象进行上传
1.使用FileReader进行图片预览
// FileReader 对象是用来读取File对象或Blob对象的。File对象就是<input type="file">获取到的对象,而Blob(二进制)对象就是原始的二进制数据
//FileReader.onload:处理load事件。即该钩子在读取操作完成时触发,通过该钩子函数可以完成例如读取完图片后进行预览的操作,或读取完图片后对图片内容进行二次处理等操作
//FileReader.readAsDataURL:读取方法,并且读取完成后,result属性将返回 Data URL 格式(Base64 编码)的字符串,代表图片内容。
2.将图片绘制到canvas画布上
//先使用CanvasRenderingContext2D.drawImage()方法将选中的图片文件在画布上绘制出来,
//再使用Canvas.toDataURL()将画布上的图片信息转换成base64(DataURL)格式的数据。
Canvas.toDataURL(mimeType, quality) // 接收两个参数
mimeType(可选):String;表示需要转换的图像的mimeType类型。默认值是image/png,还可以是image/jpeg,甚至image/webp
quailty(可选):Number;quality表示转换的图片质量。范围是0到1。此参数要想有效,图片的mimeType需要是image/jpeg或者image/webp,其他mimeType值无效。默认压缩质量是0.92。
//前端图片压缩的核心方法就在quailty方法上
const canvas = document.createElement('canvas');
const quality = 0.2; //设置压缩比例
canvas.toDataURL(file.type, quality)
webpack打包工具
-
webpack 本质是一个第三方的模块包
- 支持所有类型的文件打包
- 支持将less/sass => css
- 还可以将ES6/7/8 高版本的js语法 进行降级处理 => Es5
- 压缩代码 提高网站加载速度
-
打包流程:执行webpack打包命令 => 浏览配置文件,得到配置文件的参数 => 找到入口 => 构建依赖关系 => 编译各个模块文件 => 输出到出口文件
-
模块打包,将不同模块的文件打包整合到一起,利用这个我们在开发的时候就可以根据自己的业务自由的划分文件模块,这样保证了项目结构的清晰增加可读性
-
编译兼容,通过webpack的loader机制,将
.less,.vue,.jsx这些文件进行编译转换为js文件 -
能力扩展,通过
webpack的Plugin机制就可以实现按需加载,代码压缩这些功能 -
webpack-loader就是起了文件转换的作用 loader运行在打包文件之前(loader为在模块加载时的预处理文件)- 因为
webpack打包成果就是一份js代码,实际上webpack内部也只能处理JS模块代码,打包过程中会默认将所有的文件当作js文件来处理,所以loader存在的意义就是将这些非js文件代码转换
- 因为
-
webpack-Plugin起功能扩展的作用,plugins在整个编译周期都起作用,作用在webpack本身上的,而且plugin的话不局限在打包,在资源的加载上面它的功能更加丰富,比如webpack提供的插件html-webpack-plugin就是针对html打包和拷贝的插件