web(World Wide Web)
即全球广域网,也称为万维网,它是一种基于超文本和HTTP的、全球性的、动态交互的、跨平台的分
布式图形信息系统。是建立在Internet上的一种网络服务,为浏览者在Internet上查找和浏览信息提供了图
形化的、易于访问的直观界面,其中的文档及超级链接将Internet上的信息节点组织成一个互为关联的网状
结构。
//////////////////
// this.setState //
//////////////////
异步:
1. react 本身的事件
2. 在 生命周期里 setState 都是异步的
同步:
1. 原生事件
2. setTimetou setTimeInteval
3. axios 回调里
///////////////////////
// index 可以当 key //
///////////////////////
* 怎么解决这个问题: shortid 或者 唯一的 id
* 怎么实现一个类似的 shortid
getId = () => Math.random()
shortId 封装: 数据中人为添加id: shortId
///////////
// 路由 //
///////////
* 文件拆出 所有路由拆出一个文件
* Switch组件,确保路由只匹配一个页面
* link 跳转 navlink 带class属性的跳转
* 路由传参 query 和 动态路由
query 直接拼接 使用 location.search获取,获取可以直接qs.parse()解析
动态路由 路由后设置动态变量 使用 this.props.match.params.动态变量 获取
/////////////////////////
/// redux状态管理 ////
////////////////////////
* 流程 view->action->reduce->渲染 (只有一个store,单一数据源)
* redux 与 react-redux
redux(状态管理) -- react-redux(react版本状态管理)
* 使用流程:
{ createStore (创建store) , combineReducers (合并reducer) } from redux
{ Provider } from react-redux
cons defaultState = { } //state默认值
function list ( state = defaultState , action ){
//处理数据逻辑
switch ( ) {
case '处理逻辑名'://case值唯一,匹配时会全局查找
return { ...state( 用于备份state,防止冲突 ) , name: action.payload };
default :
break ;
}
}
var myReducers = combineReducers ({ list })
const action = {
type: '选择要操作的逻辑',
payload: '转递参数'
}
var store = createStore ( myReducers )
store.dispatch (action)//调用action
<Provider store = { store }>
包裹项目
</Provider>
* 组件内使用redux
import { connect } from 'react-redux'
import { xxx } from '/action/xxx'
17之前
connect(
state => { value: state.reducer( .value ) },
{ xxx (引入的action) }
)( 组件 )
17之后
@connect( state =>({
value: state.reducer( .value )
}), {
xxx ( 引入的reducer )
}
) 组件
* redux-promise 插件
action 中只能写同步代码
使action中,可以直接使用axios请求,并直接返回请求到的数据
const action = {
type: '选择要操作的逻辑',
payload: axios.get( url )
}
//先当于在action中,直接取到 axios.get( url ).then( res=> {}) 中的res
//////////////
// flex 布局 //
//////////////
* 使用 flex: 1; 时,为防止溢出导致布局变化,在布局方向上添加
width: 1px 或 height: 1px
////////////////////
//// 数据持久化 ////
////////////////////
* import { persistStore } from 'redux-persist'
import { PersistGate } from 'redux-persist/lib/integration/react'
///////// index.js配置 //////////
//////// reducer持久化需要配置 ////////
* import { persistReducer } from 'redux-persist'
import storage from 'redux-persist/lib/storage'
import sessionStorage from 'redux-persist/lib/storage/session'
import autoMergeLevel2 from 'redux-persist/lib/stateReconciler/autoMergeLevel2'
import doData from './reducers/doData' // reducer
//需要对哪些 state 持久化
const rootPersistConfig = {
key: 'root',
storage: sessionStorage,//storage 类型
stateReconciler:autoMergeLevel2, // 层级
whiteList: ['doData'] //白名单 想让谁持久,就放谁
}
//抽离一个需要持久化的公共 reducer
const myPersistReducer = persistReducer(
rootPersistConfig,
doData
)
const store = createStore(
myPersistReducer,
composeEnhancers(
applyMiddleware(promise)
)
)
<Provider store = { store }>
<PersistGate loading = { null } persistor={ PersistStore( store ) }>
包裹项目
</Provider>
</Provider>
* redux-thunk -- 以函数的形式写action
import { redux-thunk } from 'redux-thunk'
export function getxxx () {
return dispatch => {
dispatch({
type: ' getList ',
payload: ' 参数 '
})
}
}
applyMiddleware( redux-thunk ) //注意将中间件添入
////////////////
/////node /////
////////////////
* npm view 插件 versions //查看插件版本
/////////////////
/// antd ///
/////////////////
* Form 表单回填 ( antd 版本3 )
Form.create({
mapPropsToFields( props ) {
return {
username: Form.createFormField({
value: '65464',
}),
password: Form.createFormField({
value: '98798797',
})
}
}
})( 组件名 )
////////////////////////
/// React 生命周期 ////
///////////////////////
* 16.4
1. 加载阶段: constructor( ),render( ),componentDidMount( )
2. 更新阶段: componentWillReceiveProps( ),shouldComponentUpdate( ),render( ),
componentWillUpdate( ),componentDidUpdate( )
3. 卸载阶段: componentWillUnmount( )
* 16.4 之后:
加载阶段: consructor( ),getDerivedStateFromProps( ),render( ),componentDidMount( )
更新阶段: getDerivedStateFromProps( nextProps, prevState ),shouldComponentUpdate( ),render( ),
getSnapshotBeforeUpdate( prevProps, prevState ),componentDidUpdate( )
卸载阶段: componentWillUnmount( )
注意: getSnapshotBeforeUpdate( prevProps, prevState ),
getDerivedStateFromProps( nextProps, prevState ) //两者都必须有返回值
state或者props改变就触发
比较props的值是否有改变
///////////////
/// es6 ///
///////////////
* includes():返回布尔值,表示是否找到了参数字符串。
startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。
endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。
* this : 代表当前执行的对象
* constructor(){ } //构造过程 new 的过程也是构造
// 创建一个空对象{ }
// 执行构造器
// 把所有的 this 替换成 { }
// 最终返回 空对象
* var xxx = ( ) => { } // 被定义之后,this产生并固定不变
* 箭头函数 与 普通函数 -- 区别
//箭头函数 没有 arguments
//箭头函数 不能当构造器使用
//箭头函数 没有自己的this
//箭头函数 this 是在定义的时候就不变了
* 数组扩展方法
map() // 返回新数组,长度与原数组长度相同
filter() // 返回新数组,返回符合条件的数据
reduce() //两个参数,第一个参数是回调函数,第二个参数为默认第一项值
find() //返回真 结束循环 并把当前值返回
findIndex() //返回真 结束循环 并把当前值的下标返回
for of // 数组中值的循环
* 对象扩展方法
keys() //遍历key值返回为一个数组
entries(){} //将对象变为二维数组 每一对key和值变为一个数组
is
深浅拷贝
Object.assign() , { ...obj } //浅拷贝
JSON.parse( JSON.stringify( obj ) ) //可以实现深拷贝存在bug
// 过滤undefined
// 如果对象中有function,拷贝后的对象会丢失这个function
// 如果对象中存在循环引用的情况也无法正确实现深拷贝
递归 , lodash //深拷贝
* Symbol 变量
// 具有唯一性 多用于object的key
* Promise
//解决回调地狱
//三种状态: 进行中 已成功 已失败
const promise = new Promise(( resolve, reject ) => {
resolve( '成功' )
reject( '失败' )
})
//resolve() 回调函数(成功)
//reject() 回调函数(失败)
链式操作 // 等待上一个promise改变状态后再执行下一个
promise
.then(res => {
console.log( res ) // 打印resolve()中的 '成功'
})
.catch(err => {
console.log( err ) // 打印reject()中的 '成功'
})
Promise.all ([promise, promise2]).then( res => {
console.log(res)// 所有promise返回resolve时,才执行
//失败时,只返回失败项的状态
})
Promise.race ([promise, promise2]).then( res => {
console.log(res)// 此状态为最先改变的promise的状态
})
Promise.resolve( 任意类型 ) 快速创建一个成功的promise 参数为then打印的参数值
Promise.reject( 任意类型 ) 快速创建一个失败的promise 参数为catch打印的参数值
* Generator
const promise = new Promise(( resolve, reject ) => {
resolve( '成功' )
reject( '失败' )
})
function* fn() {
yield promise
yield 'hello'
}
const f = fn()
console.log( f.next() )//返回{ value:'xxx', done: false }
console.log( f.next() )//返回{ value:'xxx', done: false }
console.log( f.next() )//返回{ value:'undefined', done: true } done为true,结束
f.next(参数)//参数为上一个yield的返回值
* 宏任务 - 微任务
宏任务: setTimeout setInterval
微任务: resolve reject await
同步任务
同步任务 > 微任务 > 宏任务 //优先级
* event loop 事件循环机制
1.先执行同步任务
2.微任务调用栈[resolve(1), resolve(2), resolve(3)] await
3.宏任务: setTimeout setInterval I/O(异步操作) DOM渲染
* async
含义:相对于Promise链式操作与Generator的优化,使得异步操作更加方便,返回值为
Promise对象
内部与await联合使用,await后面的任务会等到await代码执行完之后再执行
await 接收任意类型 通常接收异步方法
const data = await promise() //用变量接收值,等同于执行一个then,若状态为失败,
则需用.catch()接收返回,此时data接收失败的值,并且后面的await不会执行
推荐写法 try{
data = await promise()
} catch(e) {
console.log()
}
* async 原理
async === Generator + 自动执行器
function spawn (genF) {
return new Promise(resolve => {
const gen = genF()
function step (nextF) {
let next = nextF()
if (next.done) {
return resolve(next.value)
}
step(() => gen.next(next.value))
}
step(() => gen.next(undefined))
})
}
* 类
class Person {
constructor(username, sex) {//构造器 constructor
this.name = username
this.sex = sex
}
}
constructor 构造过程
1. 创建一个空对象{ }
2. 执行构造器
3. 把所有的 this 替换成 { }
4. 最终返回 空对象
继承
class Animals {
static eye = 'eye'//静态属性 (静态也可以继承)
moues = 'moues'
}
class Person extends Animals {
constructor() {
super()//继承的子类是没有this的,需要通过super方法将父级的this拿过来
}
}
///////////////
///// git //////
///////////////
* svn, git:版本管理工具 github:面向个人'
* 配置git//新机器必备
git config --global user.email"用户登录邮箱"
git config --global user.name"用户名"
1. 创建项目
git init//项目托管给git
git add .
git commit -m "说明性注释"//创建提交
2. 提交项目
git commit -m "说明性注释"//创建提交
git push -u origin master//提交分支到gitHub(远程)
3.配置公钥私钥
ssh-keygen -t rsa -C '用户登录邮箱'//创建公钥
cat ···/.ssh/id_rsa.pub//打开公钥(钥匙文件所在文件夹下打开)
gitHUB页面中setting设置公钥//将文件中的密钥复制到gitHub
4.查看修改与提交 (常用)
git status//查看git那些文件修改
git add (文件名/.)//保存文件修改到本地
git commit -m "说明性注释"//创建提交
git status//再次查看文件是否无修改
git push origin master (master是分支名)//往远程提交分支文件
* 常用分支指令
git log//查看日志
git checkout -b 本地分支名 origin/master//创建分支
git checkout 分支名//切换分支
git branch -v//查看本地分支
git branch -a//查看远程分支
git config --list//查看账号信息
* 开发常用
git stash / git stash save '说明性注释'//暂存修改(文件回复到修改前)
git stash list//查看缓存列表
git stash apply / apply stash@{0}//回到最近(或指定)存储
git stash clear//清除缓存
git diff <file>//查看当前修改文件
git clone (gitHub地址)//复制拉取文件
git pull origin master(远程分支)//同步远程项目
git fetch//拉取远程分支同步到本地
git reset --hard hash值//撤回提交
git push -f origin master//强制提交
git checkout .//撤销修改
* 合并分支
git add (文件名/.)//保存文件修改到本地
git commit -m "说明性注释"//创建提交
git checkout 分支名//切换分支
git merge 需要合并的分支名//将其他分支合并到当前分支
///////////////////////
//react 项目创建流程//
///////////////////////
1.npx create-react-app name
2.配置文件夹,路由,页面,组件等
3.配置router文件夹,包含 index.js(主路由文件) 与 assembly.js(抛出路由组件)
4.删除不必要的文件,index.js中删除App相关,并引入主路由 router/index.js
5.配置conifg-overrides.js(项目配置文件/webpack配置文件)
替换package.json中的scripts配置,并安装配置相关依赖
"scripts": {
"start": "react-app-rewired start --open",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-app-rewired eject"
},"scripts": {
"start": "react-app-rewired start --open",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-app-rewired eject"
},
6.安装less@3.11.1 与 less-loader@5.0.0,更改index.js引入index.css为index.less
/////////////
// Hook //
////////////
* class组件达到瓶颈,复用组件逻辑复杂(使用高阶函数),hook的主要用途就是复用状态逻辑
* Hook(是函数、没有生命周期)
* Hook常用钩子
1.useState:
const = [ val, setVal ] = useState(默认值)//定义useState
setVal()//设置useState中的值(异步)
setVal( pre => { return xxx } )//拿到上一次的值
const = [ val, setVal ] = useState( ( ) => {
初始值有复杂逻辑时写法
return xxx
})
2.useEffect:
useEffect( () => {
//逻辑代码
return () => { 卸载阶段 }
}, [ 依赖项 ] )//依赖项中为监听值,没有监听值只执行一次,相当于DidMount
//依赖多个值,只要一个改变,就触发,监听后相当于更新期
* 注意事项:
Hook 只能写在函数组件
Hook 只能写在顶层,不能写在 if for中,hook按顺序解析;Hook的调用顺序在每次渲染中都是相同的
Hook 自定义hook,以use开头或者以函数组件的形式
* createRef 与 useRef
const myRef = React.createRef( )//每次都返回一个新对象
console.log( myRef.current )//不可以加属性,只有current属性
<div ref={ myRef }></div>
const ref = useRef ( 初始值 )//只执行一次,可记录上一次的值,始终返回同一个对象
console.log( ref.current )//不一定只有current属性
* memo//返回一个新组件 机制与PureComponent相似,执行时做比较优化
const A = memo(function(){
return(
<div>AAA</div>
)
})
<A />
useMemo( 回调, [ 依赖 ]) => 返回值取决于return//多用于缓存,函数体立刻执行
useCallback( 回调, [ 依赖 ]) => 返回新函数 //比较函数,相同就缓存通常与memo连用,函数体需要调用执行
redux-react-hook//配合useReducer使用的插件
/////////
// vue //
/////////
* 安装 npm i @vue-cli
vue create hello-world
* 配置文件夹,路由,页面,组件等
npm i element-ui -S
npm install babel-plugin-component -D//按需引入
* data() {
return {
//项目中的数据
}
}
* methods: {
//项目函数
}
* watch: {
data中的现有数据 : {
监听触发的函数
}
}
* computed: {//缓存,同时可以监听多个值
data中的现有数据 : {
监听触发的函数
}
}
* filters: {//过滤值
filter(price) {
return `$${price}`
}
}
{{ price | filter(price) }}
* components: {
引入的组件
}
父传子
父:<A :price="price"/>
子:props: [ 'price' ],
子传父
子:methods: {
onclick(){
this$emits('aaa(触发事件名)',{
返回值
})
}
}
<div @click='onclick'></div>
父:methods: {
onAAA(obj){
console.log(obj)
}
}
<A @aaa(触发事件名)='onAAA'></div>
* slot
<A>aaa</A>//拿到在父组件中,子组件的公共部分
子:
<slot/>
* keep-alive //缓存,避免加载浪费性能
<keep-alive></keep-alive>
activated(){}//激活
deactivated(){}//暂停
* router
this.$router.push('/ ')//跳转
this.$router.push({name: ''})
const routes = [
{
path: '/',
name: 'Home',
component: Home,
},
{
path: '/about',
name: 'About',
component: () => import('../views/about/About.vue')
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router
* vuex
store/index.js//配置store
state: {//默认值
code: 1,
data: []
},
mutations: {//同步修改 只能在mutations中修改state 触发 commit
//同步代码不能写异步
setCode ( state, action ) {
console.log()
state = action
}
setDataList ( state, action ) {
state.data = action
}
},
actions: {//异步操作 触发 dispatch
getDataList ({ commit }) {
const data = axios.get('url')
commit( 'setDataList', data )
commit( 'home/setDataList', data, {root: true})//调用别的方法
}
},
modules: {
//引入拆出的模块
}
页面组件
import { mapState, mapMutations, mapActions } from 'vuex'//引入store中的配置
methods: {
...mapMutations([
'setCode',//在Mutations中提取方执行方法
]),
...mapMutations('home',[
'setCount',//在拆出部分中提取方执行方法actions同理
]),
...mapActions([
'getDataList',//在Mutations中提取方执行方法
]),
onclick() {
this.setCode(传值)//使用提取出来的方法处理数据
}
}
computed: {//在computed中取store的state值
...mapState(['code', 'data']),
...mapState('home',[ 'count' ]),//引入拆出模块的state
...mapState('home',{
homeCount: 'count',//重名时,别名方法
})
}
///////////////
///溢出省略///
///////////////
* 单行溢出省略
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
* 多行溢出省略
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 2;//行数
-webkit-box-orient: vertical;
* 中间截取省略
overflow: hidden;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;//行数
text-indent: -40px;//缩进
/////////////
//面试常问//
/////////////
* z-index//需要position才能生效
0,0 点//相对于设置position最近的父元素
//如果没有position父元素,以document定位
//relative的0,0点相对于父元素计算
* 粘性定位
position: sticky;
* 事件注册
addEventListener( ' ', function( ){ }, false )
//事件;触发函数;在哪个阶段触发( false,冒泡;true,捕获 )
* 事件传递方式
捕获阶段 -> 目标阶段 -> 冒泡阶段
* 跨域
(浏览器)同源策略:协议,域名,端口,只要一项不同就会跨域
JSONP CORS后台设置请求头 设置代理 http-proxy-middleware
上线后项目不是node,使用nginx反向代理
* 为什么请求要放在componentDidMoutn()
this.setState() 需要在DOM渲染完之后才能执行
// await 微任务 promise
// 4 1 3 6 8 2 7 5//执行顺序
async function async1() {
console.log(1)
await async2() // reoslve()
console.log(2)
}
async function async2() {
console.log(3)
}
console.log(4)
setTimeout( function () {
console.log(5)
}, 0)
async1();
new Promise(function ( resolve ) {
console.log(6)
resolve();
}).then( function () {
console.log(7)
})
console.log(8)
* viewport//主要用于移动设备
width = device-width//宽度为设配屏幕的宽度
initial-scale = 1.0//初始缩放比例
minimum-scale = 0.5//最小缩放比例
maximum-scale = 2.0//最大缩放比例
user-scalable = yes//用户是否可以调整缩放
* 浏览器内核
chrome: Blink
FireFox: Gecko
Opera: Blink
Safari: Webkit
* 路由权限
pt: 登录 首页
vip : 登录 首页 详情
sipv: 登录 首页 详情 用户列表
1. 本地存 { pt: [], vip: [], sivp: [] }
2. 登录后台返回权限 通过权限 拿到这个用户能访问的路由数组
3. 全局路由拦截 [].includes(pathname)
* HTML5
canvas svg video audio localStorage sessionStorage webSocket pushState replaceState
* 清除浮动的2中方法
//内浮动
:after
:before{ clear: both }
* 新增伪类
:after :before :checked :focus :nth-child :last-child :first-child
* 手动动画最小间隔
1/60*1000ms = 16.7ms
* css3动画
transform ( 通过事件触发 )
animation ( 不通过事件触发 )
* 如何使用CSS实现硬件加速, GPU渲染
transfrom: translate3D(100, 0, 0): 写上Z轴 会使用伪 GPU渲染
will-change: 绝对的GPU渲染
* websoket
跟后台进行通信 创建长连接
const ws = new Websocket(url)
ws -> httpwss -> https
# 四个事件
open 连接建立时触发
message客户端服务端数据时触发
error通信发生错误时触发
close 连接关闭时触发
# 两个方法
ws.send()
ws.close()
* js继承
继承概念:通过某种方式让一个对象可以访问到另一个对象的属性和方法,把这种方式称之为继承
作用:获取更多的拓展,减少代码的冗余等。
### 原型链继承
function Person(){
this.name = '邓紫棋'
}
Person.prototype.getName = function(){
console.log(this.name)
}
function Child(){
}
Child.prototype = new Person()
var child1 = new Child()
child1.getName()// 邓紫棋
缺点:
通过原型来实现继承时,原型会变成另一个类型的实例,原先的实例属性变成了现在的原型属性,该原型的引
用类型属性会被所有的实例共享。
在创建子类型的实例时,没有办法在不影响所有对象实例的情 况下给超类型的构造函数中传递参数
###借用构造函数继承
// 借用构造函数继承(经典继承)
function Person(){
this.colors = ['red', 'blue', 'green']
}
function Child(){
Person.call(this)
}
var child1 = new Child()
child1.colors.push('yellow')
console.log(child1.colors)// ["red", "blue", "green", "yellow"]
var child2 = new Child()
console.log(child2.colors)// ["red", "blue", "green"]
优点:
可以向超类传递参数
解决了原型中包含引用类型值被所有实例共享的问题
缺点:
方法都在构造函数中定义,函数复用无从谈起,超类型原型中定义的方法对于子类型而言都是不可见的。
###组合继承(借用构造函数继承+原型链继承)
function Parent (name) {
this.name = name
this.colors = ['red', 'blue', 'green']
}
Parent.prototype.getName = function () {
console.log(this.name)
}
function Child (name, age) {
Parent.call(this, name)
this.age = age
}
Child.prototype = new Parent()
var child1 = new Child('kevin', '18')
console.log(child1)//Parent { name: 'kevin', colors: [ 'red', 'blue', 'green' ], age: '18' }
优点:
可以向超类传递参数
每个实例都有自己的属性
实现了函数复用
缺点:
无论什么情况下,都会调用两次超类型构造函数:一次是在创建
子类型原型的时候,另一次是在子类型构造函数内部
###原型式继承
function Wonman(name){
let instance = new People()
instance.name = name || 'wangxiaoxia'
return instance
}
let wonmanObj = new Wonman()
缺点:同原型链实现继承一样,包含引用类型值的属性会被所有实例共享。
###寄生式继承
function createAnother(original){
var clone = Object.create(original) //通过调用函数创建一个新对象
clone.sayHi = function(){ //以某种方式来增强这个对象
alert("Hello")
}
return clone //返回这个对象
}
var person = {
name: "Bob",
friends: ["Shelby", "Court", "Van"]
}
var anotherPerson = createAnother(person)
anotherPerson.sayHi()
缺点:
使用寄生式继承来为对象添加函数,会由于不能做到函数复用而效率低下。同原型链实现继承一样,包含
引用类型值的属性会被所有实例共享。
###寄生组合式继承
function Parent(name,play){
this.name=name;
this.play=play;
}
function Child(name,play,age){
Parent.call(this,name,play);
this.age=age;
}
// 隔离了父类和子类的构造函数,父类的添加到了__proto__属性上
Child.prototype=Object.create(Parent.prototype);
Child.prototype.constructor=Child
let child=new Child("张三","玩",20);
let child2=new Child("李四","吃",10)
console.log(child.constructor)
优点:
只调用了一次超类构造函数,效率更高。避免在 SuberType.prototype上面创建不必要的、多余的属
性,与其同时,原型链还能保持不变。
* web前端性能优化
## 页面内容
1.减少HTTP
· 通过 webpack 合并JS CSS文件
· 使用 CSS Sprite 合并图片
· 使用 Base64 行内图片
· 用 icon 替换图标
2. 避免重定向
· URL末尾添加 / 例如 http://www.baidu.com/
3. 缓存Ajax请求
· 设置 Cache-Control (相对过期时间)
4. 减少页面元素数量
## CSS
1. react vue 可以实现css样式按需加载, 减少每次加载CSS的大小
2. 压缩CSS
3. 使用CSS3属性 例如动画使用 transform transiton animtion
## Javascript
1. JS代码尽量放在页面底部, 避免阻塞页面加载
2. 使用外部的CSS 可以缓存CSS
3. webpack 压缩 js代码
## 图片
1. CSS Sprite 合并图片
2. Webp 图片
3. 不在HTML中缩放图片
4. 非 webp 图片都要压缩后再使用
5. 使用 Base64 内嵌图片
## react || vue
· 路由懒加载
· vue 组件异步加载
## 服务器
1. 使用 CDN(内容分发网络CDN是一组分散在不同地理位置的web服务器)
2. 添加 Cache-Control 缓存头
3. 启用 Gzip 压缩 (图片不要gzip压缩)
4. Ajax 尽可能使用 get方法, post方法会多一次请求
5. 避免 空图片标签
## 移动端
1. 保证所有组件都小于25K
* 链表
单向链表:单向链表包含两个域一个是信息域一个是指针域
双向链表:每个节点有2个指针域一个是指向前一个节点另一个则指向后一个节点
循环链表:循环链表就是首节点和末节点被连接在一起,循环链表中第一个节点之前就是最后一个节点
数组和链表的区别:
链表是链式的存储结构数组是顺序的存储结构
链表通过指针来连接元素数组则是把所有元素按次序依次存储
链表的插入删除元素相对数组较为简单但是寻找某个元素较为困难
数组寻找某个元素较为简单但插入与删除比较复杂
自我理解:
数组便于查询和修改但是不方便新增和删除
链表适合新增和删除但是不适合查询
* 防抖 节流
所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,
则会重新计算函数执行时间。
import _ from 'lodash'
const id = useRef()
const fn = () => {
clearTimeout( id.current )
id.current = setTimeout(( ) => {
console.log()
}, 1000)
}
<input onKeyDown={ fn }/>
import _ from 'lodash'
const id = useRef()
const fn = () => {
console.log()
}
<input onKeyDown={_.debounce(fn, 1000)} />
所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数
import _ from 'lodash'
const id = useRef()
const fn = () => {
if( !id.current ) {
id.current = setTimeout(( ) => {
id.current = null
console.log()
}, 1000)
}
console.log()
}
<input onKeyDown={_.debounce(fn, 1000)} />
resize//窗口变化时触发(事件)
window.addEventListener('scroll', _.throttle(evt => {
document.querySelector('p').innerHTML =
document.querySelector('body').scrollTop || document.querySelector('html').scrollTop
}, 200), false)
* 判断引用数据类型
Object. prototype. toString.call (对象)
* 事件代理
currentTarget(获取注册事件的节点)
* http状态码
200:表示客户端发来的请求在服务器端被正确处理
201:请求成功并且服务器创建了新的资源。
202:接受请求但没创建资源
203:返回另一资源的请求
301:永久性重定向,表示资源已被分配了新的url
302:临时重定向,表示资源临时分配了新的url
303:表示资源存在着另一个url,应使用get方法获取资源
304:表示服务器允许访问资源,但因发生请求未满足条件的情况
400:请求报文存在语法错误
401:表示发送的请求需要通过HTTP认证的认证信息
403:表示对请求资源的访问被服务器拒绝
404:表示在服务器上没有找到请求的资源
500:表示服务器端在执行请求时发送了错误
501:表示服务器不支持当前请求所需要的的某个功能
503:表示服务器暂时除余超负载或正在停机维护
* 从输入URL 到页面展现
根据地址栏输入的地址向DNS(Domain Name System)查询IP
通过IP向服务器发起TCP连接
向服务器发起请求
服务器返回请求内容
浏览器开始解析渲染页面并显示
关闭连接
# 通过DNS解析获得对应的IP地址
1. 浏览器缓存——浏览器会缓存DNS记录一段时间(2分钟到30分钟)
2. 系统缓存——如何浏览器缓存中没有,浏览器会做一个系统系统调用,查找系统缓存中的记录,常
见的hosts文件
3. 路由缓存——如果系统缓存也没有需要的记录,会向本地路由器发送一条DNS查询请求,一般会有
自己的dns缓存
4. ISP DNS缓存——如果本地路由器没有再查看本地网络提供商(移动啦、电信啦)的DNS服务器,
一般都能找到相应的缓存记录
# 浏览器向服务器发送一个HTTP请求
1. 首先通过三次握手建立TCP连接,
2. 浏览器通过TCP连接向服务器发送一个http请求
# 服务器发出重定向响应
1. 在拿到ip地址后, 浏览器会向对应的web服务器(Nginx,Apache...)发起TCP连接请求,通过三次握手,建立
TCP连接
2. 建立TCP连接后, 浏览器向web服务器发送Http请求
# 服务器返回请求内容
服务器在接收到请求后,解析用户请求,知道了要调度那些资源文件,再通过相应的资源文件,处理用户的请
求和参数,并调用数据库信息,最后讲结果通过web服务器返回给浏览器.
# 浏览器开始解析渲染页面并显示
html 渲染过程
# 关闭连接
在这次数据传输完成后,为了避免服务器与客户端双方的资源占用和损耗,会经过四次挥手,关闭TCP连接.
* 浏览器缓存
强缓存
Expires: 2020.12.12 8.0 一个绝对时间的 GMT 格式的时间字符串,代表缓存资源的过期时间
Cache-Control: 2小时 2小时之内你重新请求了一个接口 从你请求这个接口开始 重新计算2小时
判断缓存资源的最⼤⽣命周期,它的值单位为秒
协商缓存
If-Modified-Since 过期 2020.12.12 8.0
ajax('1.php') -> 后台可以拿到过期时间 后台判断 不给返回新数据 300, 有数据200
通过⽐较两个时间来判断资源在两次请求期间是否有过修改
Last-Modified (值为资源最后更新时间,随服务器response返回)
* RESF规范 实现一个接口执行多个操作
* vip权限
封装权限路由表(数组)
跳转获取pathname,通过后台的权限,使用includes查看是否包含该pathname,
* 数组合并4种方法
arr.concat( [] )
arr.push( ...[] )//返回长度,合成数组为arr
[ ...arr, ...arr1 ]
arr.push.apply(arr, [])//返回长度,合成数组为arr
* jekenis 上线
提交代码到 测试 featur/dev
提交代码到 测试 featur/pre
提交代码到 测试 featur/master
* 打包 npm run build
合并 js css
react less 转译 js css
build
1.FTP
2.给后台
* 前端数据映射: //解决公共组件 渲染后台 不同接口 返回不同数据的问题
1. 首先定义一套自己的数据格式
2. 把后台接口给的数据转化成我们自己定义的数据格式
3. 公共组件都适用这套自己定义的数据格式渲染数据
////////////////
////问公司/////
////////////////
* 技术栈--详细了解(包括状态管理工具,插件等)
* 假如有幸加入公司,会负责哪些项目
* 公司内部是否有技术分享
/////人事/////
* 接下来找工作的想法:
1. 北京的公司
2. 稳定可以长期干下去的公司
3. 技术氛围好的公司
* 你找工作主要看中哪些东西
1. 技术提升
2. 薪资是否可以
3. 公司氛围
////////////////////
//react插件、方法//
////////////////////
* React传送门
ReactDOM.createPortal( fn(), document.querySelector('') )//渲染到root外面
{ children }//通过 children 展示弹框内容
* export const { Provider, Consumer } = createContext()
<Provider value='wxh'></Provider>
* react-router-config
import { renderRoutes } from 'react-router-config'
const Root = ({ route }) => (
<div>
<h1>Root</h1>
{renderRoutes(route.routes)}
</div>
)
const routes = [
{
component: Root,
routes:[
{
path: '/home/:id',//动态路由
component: Home,
routes:[
{
path: '/home/user',
component: Hook
}
]
},
{
path: '/hook',
component: Hook
},
{
path: '/hook2',
component: Hook2
},
]
}
]
* import { useHistory, useRouteMatch } from 'react-router-dom'
子组件中引用
let match = useRouteMatch('/home/:id')
let history = useHistory()
* import Masonry from 'masonry-layout' // 瀑布流
* import InfiniteScroll from 'react-infinite-scroller' //下拉加载
state = {
hasMore: true, // 是否开启下拉加载
data: [
{ title: '小白' },
{ title: '小白' },
{ title: '小白' },
{ title: '小白' },
{ title: '小白' },
{ title: '小白' },
{ title: '小白' },
], // 接受我每次的数据
count: 0,
width: '',
}
advanceWidth = () => {//瀑布流配置
new Masonry(document.querySelector('.content'), {// new Masonry(节点, 配置)
itemSelector: '.d', // 要布局的网格元素
fitWidth: true, // 设置网格容器宽度等于网格宽度
gutter: 20,
columnWidth: '.d',
originLeft: true,
})
}
loadMoreData = page => {//下拉加载配置
const { data, count } = this.state
if (count && page > Math.ceil(count / 10)) return false
axios.post('https://api.baxiaobu.com/index.php/home/v5/getuser', { data: { page, limit: 10 } })
.then(res => {
this.setState({
data: [...data, ...data],
count: res.count,
})
this.advanceWidth()
})
.catch(err => console.log(err))
}
<div className="box">
<InfiniteScroll//下拉加载部分
initialLoad={false} // 不让它进入直接加载
pageStart={1} // 设置初始化请求的页数
loadMore={this.loadMoreData} // 监听的ajax请求
hasMore={true} // 是否继续监听滚动事件 true 监听 | false 不再监听
useWindow={true} // 不监听 window 滚动条 如果你要监听 window 外层不能有任何节点
>
<div className="content">
{
this.state.data.map((value, key) => (//瀑布流部分
<div key={key} className="d xxx">
{value.title}
</div>
))
}
</div>
</InfiniteScroll>
</div>
.box {
box-sizing: border-box;
margin: 0 auto;
width: 100%;
height: 100%;
overflow-x: hidden;
overflow-y: auto;
box-sizing: border-box;
border: 5px #0f0 solid;
.content {
width: 100%;
margin: 0 auto;
border: 5px #00F solid;
}
.xxx {
width: 200px;
margin: 0 0 20px 0;
border: 5px #f00 solid;
}
.xxx:nth-child(2n-1) {
height: 200px;
}
.xxx:nth-child(2n) {
height: 300px;
}
}
// columnWidth: 200,
// itemSelector: '.grid-item' // 要布局的网格元素
// gutter: 10 // 网格间水平方向边距,垂直方向边距使用css的margin-bottom设置
// percentPosition: true // 使用columnWidth对应元素的百分比尺寸
// stamp:'.grid-stamp' // 网格中的固定元素,不会因重新布局改变位置,移动元素填充到固定元素下方
// fitWidth: true // 设置网格容器宽度等于网格宽度,这样配合css的auto margin实现居中显示
// originLeft: true // 默认true网格左对齐,设为false变为右对齐
// originTop: true // 默认true网格对齐顶部,设为false对齐底部
// containerStyle: { position: 'relative' } // 设置容器样式
// transitionDuration: '0.8s' // 改变位置或变为显示后,重布局变换的持续时间,时间格式为css的时间格式
// stagger: '0.03s' // 重布局时网格并不是一起变换的,排在后面的网格比前一个延迟开始,该项设置延迟时间
// resize: false // 改变窗口大小将不会影响布局
// initLayout: true // 初始化布局,设未true可手动初试化布局
* npm i imagesloaded -S//等图片加载
imagesOnload = () => {//等待图片加载
// 初始化你要监听哪个节点下的图片
const elLoad = imagesLoaded('.content')
// always 页面图片全部加载完 不管有没有加载失败的图片
elLoad.on('always', (instance, image) => {
// 图片加载后执行的方法
// 拿第一次的数据
this.advanceWidth() // 初始化瀑布流
})
}
* 图片瀑布流
1. 元素设置 absolute
2. 获取浏览器宽度 和 卡片的宽度, 浏览器的宽度 / 卡片宽度 = 有几列
const imglist = [img, img, img, img, ...]
const arr = [
[{ url: '图片路径', position: {top: 100px, left: 40px} }], // 100px
[img, img], // 80 + 20 = 100
[img, img], // 60 + 70 = 130
[img], // 120
]
arr.forEach(v => {
v.forEach(v2 => {
v2.img + v2.img
})
})
// 2 80 + 20 = 100
arr[1].push(imglist[5])
arr.map(v => {
v.map(v2 => {
<img src={v2.url} style={{top: v2.position.top, left: v2.position.left}}
})
})
3. onresize
获取浏览器宽度 和 卡片的宽度, 浏览器的宽度 / 卡片宽度 = 有几列
const imglist = [img, img, img, img, ...]
const arr = [
[{ url: '图片路径', position: {top: 100px, left: 40px} }], // 100px
[img, img], // 80 + 20 = 100
[img, img], // 60 + 70 = 130
]
///////////////
//// dva ////
//////////////
* dva === redux-sage + roadhog
npm i dva-cli -g//安装dva-cli
dva new name//创建dva应用
* .webpackrc.js//webpackrc改为js文件
export default {//配置webpackrc.js文件
publicPath: '/',
extraBabelPlugins: [
['import', { 'libraryName': 'antd', 'libraryDirectory': 'es', 'style': 'css' }],
],
alias: {
'@': `${__dirname}/src`,
'@@': `${__dirname}/src/components`
},
proxy: {
'/aps': {
target: 'https://api.baxiaobu.com',
changeOrigin: true,
pathRewrite: {
'^/aps': '',
}
},
'/api': {
target: 'https://blogs.zdldove.top',
changeOrigin: true,
pathRewrite: {
'^/api': '',
}
},
}
}
* 在routes中创建assembly.js、router.js同react一样配置路由
在router.js中引入异步加载路由
import dynamic from 'dva/dynamic'
function RouterConfig({ history, app }) {//注意引入app
const Home = dynamic({
app,
models: () => [//引入model,数组形式
import('@/models/home')
],
component: () => import('@/pages/home'),//引入组件
})
return (
<Router history={history}>
<Switch>
<Route path="/" exact component={Home} />
</Switch>
</Router>
)
}
export default RouterConfig
* model
export default {
namespace: 'home',
state: {
data: []
},
subscriptions: {
setup({ dispatch, history }) { // eslint-disable-line
},
},
effects: {//处理异步
*fetch({ payload }, { call, put, select }) { //select拿到上一次的数据,里面也是函数
const xx = yield call(() => {做请求})//call中是函数,相当于一个await
yield put({//put === dispatch
type: '',
payload: ''
})
},
},
reducers: {//修改state
home/setName (state, { payload }) {
return { ...state, data: payload }
},
},
}
组件中使用connect连接组件
connect(
state => {
return { dataName: state.home.data }
}
)(Home)
this.props.dispatch({//通过props.dispatch调用
type: 'home/setName',
payload: 'xla'
})
* subscriptinos: {//订阅 初始化数据
xxx ({ history, dispatch }) {
history.listen(({ pathname }) => {
const regexp = pathToRegexp( '/home' ).test(pathname)
})
}
}
//pathToRegexp将路由自动转为正则
model中路由跳转
router from 'umi/router'
put( routerRedux.push('/xxx') )
/////////////////
///typescript///
////////////////
* npx create-react-app tsDemo --typescript//ts react项目搭建
tsconfig.json 配置
{
"compilerOptions": {
"target": "es5", // 指定 ECMAScript 版本
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"outDir": "lib",
"allowJs": true, // 允许编译 JavaScript 文件
"skipLibCheck": true,
// 禁用命名空间引用 (import * as fs from "fs") 启用 CJS/AMD/UMD 风格引用 (import fs from "fs")
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react",
"declaration": true
},
"include": [
"src/**/*"
],
"exclude": ["node_modules", "build"] // *** 不进行类型检查的文件 ***
}
* 声明变量
let/const/var xxx : 数据类型 = xxx
let/const/var arr : 数据类型[ ] = [ ]//数组
let/const/var arr : (number, string) [ ] = [ ]//数组联合类型
let/const/var arr : any = xxx//任意类型
let/const/var arr : number | boolean | null = xxx//联合类型(允许值为其中一种类型)
let xxx: [number, boolean] = [1, true]
enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat}//枚举 值只能为string或number
function name (xxx: string, xxx:number ): void {
//void: 表示一个函数没有任何一个返回值
}
function name (xxx: string, xxx:string ): string
function name (xxx: number, xxx:number ): number
function name (xxx: any, xxx:any ): any {//函数重载
}
* class类
class Person {
name: string
age: number
constructor(n: string, a: number){//需初始化
this.name = n
this.age = a
}
}
Person('wxh', 20)
* interface//接口不可实例化可继承(仅可继承一个, 接口可实现多个)
interface A {
可设置对象、函数等接口(规定接口中有哪些变量或属性)
}
implements//实现接口
interface 可以继承类,但是constructor,静态方法、属性不会继承
//接口只能定义规则,但抽象类可以设置公共的属性和方法
* abstract//抽象类不可实例化
abstract Person {
name: string
constructor(n: string){//需初始化
this.name = n
}
}
* 泛型
泛型函数
function fun<T> (opt: T): T {//传入什么类型,返回什么类型
return opt
}
console.log(fun<string>('123'))
console.log(fun('123'))
function fun2<T> (opt: T[]): T[] {//传入什么类型,返回什么类型
return opt
}
console.log(fun<string>(['1', '2', '3']))
泛型类
class Person<T> {
private count: T[]
constructor(arr: T[]) {
this.count = arr
}
minRun(): T {
let minValue = this.count[0]
this.count.forEach(v => {
if(v < minValue) {
minValue = v
}
})
return minValue
}
}
const person = new Person<number>([1, 2, 3])
person.minRun()// 1
泛型接口
interface Kind<T> {
(arg: T, n: T): T
}
const fn: Kind<number> = function<t> (arg: T, n: T): T{
return arg
}
fn(123, 456)
////////////
// mobx //
///////////
* npm i mobx -S//安装mobx插件
* import { observable } from 'mobx'//可观察数据(监听)
//4.0以前
const num = observable.box(11)
num.get()//获取值
num.set()//修改值
//引用数据类型
const arr = observable([1, 2, 3])
arr[2] = 4
const obj = observable({name: 'wxh'})
obj.name = 'wbl'
//避免下标越界访问数组
* import { computed, } from 'mobx'//监听数据变化
class kuuga {
observable str = 'wxh'
@computed get com() {//使用数据时触发
console.log('获取')
}
set com(){//更改数据时触发
console.log('更改')
}
}
const Kuuga = new kuuga()
console.log(Kuuga.com)//触发get com()
Kuuga.str = 'hdr'//触发set com()
* autorun 当任意可观察数据修改时触发
* when(() => {
return boolean
},() => {})
* @action xxx() {}//能将多次autorun合并为一次
@action.bound xxx() {}//效果相当于bind(),用于改变指向
/////////////
//设计模式//
/////////////
* 发布-订阅模式
var saleOffices = {//发布-订阅模式
clientList: [],
listen (fn){ //订阅函数
this.clientList.push(fn)
},
trigger(price, square) { //发布函数
this.clientList.forEach(fn => {
fn(price, square)
})
}
}
saleOffices.listen((price, square) => {
console.log('王小红'+price, square)
})
saleOffices.listen((price, square) => {
console.log('好多肉'+price, square)
})
saleOffices.listen((price, square) => {
console.log('沈年年'+price, square)
})
saleOffices.trigger(8000, 120)
* 传参判断版
var saleOffices = {
clientList: {},
listen (kind, fn){ //订阅函数 kind表示订阅分类,函数表示订阅后要做什么
if(!this.clientList[kind]){
this.clientList[kind] = []//判断是否存在该订阅类型
}
this.clientList[kind].push(fn)//存在就添加
},
trigger(kind, price, square) { //发布函数
this.clientList[kind].forEach(fn => {//遍历该类型的所有方法
fn(price, square)
})
}
}
saleOffices.listen('square120' ,(price, square) => {//订阅时传入订阅类型和执行函数
console.log('王小红'+price, square)
})
saleOffices.listen('square120', (price, square) => {
console.log('好多肉'+price, square)
})
saleOffices.listen('square130', (price, square) => {
console.log('沈年年'+price, square)
})
saleOffices.trigger('square120', 8000, 120)
/////////////////
// 函数柯里化 //
////////////////
function fun() {
const arr = []
return opt => {
if (opt) {
arr.push(opt)
} else {
let sum = 0
arr.forEach(v => {
sum += v
})
return sum
}
}
}
const fn = fun()
fn(10)
fn(20)
fn(30)
fn(40)
console.log(fn())
////////////////////////////
//pushState replaceState//
////////////////////////////
state: 可通过 history.state读取
title: 可选参数,暂时没有用,建议传个短标题
url: 改变后的 url 地址 /abc
let { history } = window//window中的history
export default function Home (props) {
const onClick = () => {
var _wr = function(type) {
var orig = history[type]
return function() {
var rv = orig.apply(this, arguments)
var e = new Event(type)
e.arguments = arguments
window.dispatchEvent(e)
return rv
}
}
history.pushState = _wr('pushState')
history.replaceState = _wr('replaceState')
// 监听 history.pushState
window.addEventListener('pushState', function(e) {
if ('page1') {
}
// history.state 直接拿 pushState 第一个参数
console.log(history.state, 2)
})
history.pushState({ page: 1 }, 'title1', 'page1')
history.replaceState({page: 2}, "title 3", 'page2')
}
return (
<div className="pages-home">
<Button onClick={onClick}>点我</Button>
</div>
)
}
//////////////////////////
//redux - promise原理//
/////////////////////////
// 判断一个变量是不是 promise
import isPromise from 'is-promise'
// 是不是 FSA
// FSA: 定义 action 标准 { type, payload, error, meta }
// 必须有 type,
// 可能有 payload, error, meta
import { isFSA } from 'flux-standard-action'
// redux-promise
export default function promiseMiddleware(_ref) {
// next === 下一个中间件 或者 dispatch
// 首先返回一个函数 接收一个参数 这个参数是下一个中间件 或者 dispatch
return function (next) {
return function (action) {
// 判断是不是标准的 FSA
// 标准的 FSA 只包含4个属性 type payload error meta
if (!isFSA(action)) {
/**
* 判断是不是promise,
* 如果是则执行,只会处理resolve的值,
* 反之交给下一个中间件
*/
return isPromise(action) ? action.then(dispatch) : next(action);
}
// 是标准的FSA, 判断是不是一个promise
return isPromise(action.payload)
/**
* 1.promise的时候,执行then,同时捕获异常,
* 在处理这两种情况以后,会分别添加另外一个约束error
* 这我们需要在reducer里面还需要判断error的值,
* 做不同的处理
*
* 2. 如果不是promise则交给下一个中间件
* */
// promise
// action.payload === axios.get('http://www.baidu.com')
? action.payload
// result 就是我们请求接口的数据
.then(result => {
_ref.dispatch({ ...action, payload: result })
})
.catch(error => {
_ref.dispatch({ ...action, payload: error, error: true });
return Promise.reject(error);
})
: next(action);
}
};
}
///////////////////
///// BFC ///////
//////////////////
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=0.5" />
<meta name="description" content="Web site created using create-react-app" />
<title>React App</title>
<style>
.d0 {
border: 10px #F00 solid;
width: 200px;
/* float: left; */
/* position: absolute; */
/* display: inline-block; */
/* display: table-cell; */
overflow: hidden;
}
.d1 {
background: #9F9;
height: 100px;
width: 100%;
float: left;
}
*{
margin: 0;
padding: 0;
}
p {
color: #FFF;
background: rgb(214, 125, 9);
width: 200px;
line-height: 100px;
text-align:center;
margin: 0 0 30px 0;
}
.div {
overflow: hidden;
border: 1px #F00 solid;
}
div p {
margin: 30px 0 0 0;
}
</style>
</head>
<body>
<div id="root"></div>
<div class="d0">
<div class="d1"></div>
</div>
<p>看看我的 margin是多少</p>
<div class="div">
<p>看看我的 margin是多少</p>
</div>
</body>
</html>
BFC: 块格式化上下文
变成BFC:
浮动元素 (元素的 float 不是 none)
绝对定位元素 (元素具有 position 为 absolute 或 fixed)
内联块 (元素具有 display: inline-block)
表格单元格 (元素具有 display: table-cell,HTML表格单元格默认属性)
具有overflow 且值不是 visible 的块元素
BFC特性:
使 BFC 内部浮动元素不会到处乱跑;
和浮动元素产生边界。
<script>
</script>
///////////////
/// 修饰器 ///
//////////////
//target === MyTestableClass
function testable(isTestable) {
return function fun(target) {
target.isTestable = isTestable
}
}
//装饰class用
@testable('wxh')
class MyTestableClass {
}
console.log(MyTestableClass.isTestable)
// MyTestableClass.isTestable
@testable(false)
class Myclass {}
console.log(Myclass.isTestable)
// Myclass.isTestable
//////////////////
//postMessage//
//////////////////
//跨域传值
* 父路由
<iframe src='http://localhost:3001' name='a1' />
document.onclick = function() {
window.frames['a1'].postMessage('狼来了', 'http://localhost:3001')
}
window.addEventListener('message', function(e) {
console.log(e.origin, 1)
console.log(e.data, 2)
})
* 子路由
window.addEventListener('message', function (e) {
console.log(e.origin, 1)
console.log(e.data, 2)//传递的内容
window.top.postMessage('没来', 'http://localhost:3000')
})
/////////////
/// Umi ////
////////////
npm create @umijs/umi-app
npm i
.umirc.ts//配置路由及其他
pages//存放路由文件
web(World Wide Web) 即全球广域网,也称为万维网,它是一种基于超文本和HTTP的、全球性的、动态交互的、跨平台的分 布式图形信息系统。是建立在Internet上的一种网络服务,为浏览者在Internet上查找和浏览信息提供了图 形化的、易于访问的直观界面,其中的文档及超级链接将Internet上的信息节点组织成一个互为关联的网状 结构。 //////////////////// this.setState ////////////////////异步: 1. react 本身的事件 2. 在 生命周期里 setState 都是异步的同步: 1. 原生事件 2. setTimetou setTimeInteval 3. axios 回调里
///////////////////////// index 可以当 key ///////////////////////// * 怎么解决这个问题: shortid 或者 唯一的 id * 怎么实现一个类似的 shortid getId = () => Math.random() shortId 封装: 数据中人为添加id: shortId
///////////// 路由 ///////////// * 文件拆出 所有路由拆出一个文件 * Switch组件,确保路由只匹配一个页面 * link 跳转 navlink 带class属性的跳转 * 路由传参 query 和 动态路由 query 直接拼接 使用 location.search获取,获取可以直接qs.parse()解析 动态路由 路由后设置动态变量 使用 this.props.match.params.动态变量 获取
//////////////////////////// redux状态管理 //////////////////////////// * 流程 view->action->reduce->渲染 (只有一个store,单一数据源)
* redux 与 react-redux redux(状态管理) -- react-redux(react版本状态管理)
* 使用流程: { createStore (创建store) , combineReducers (合并reducer) } from redux { Provider } from react-redux cons defaultState = { } //state默认值 function list ( state = defaultState , action ){ //处理数据逻辑 switch ( ) { case '处理逻辑名'://case值唯一,匹配时会全局查找 return { ...state( 用于备份state,防止冲突 ) , name: action.payload }; default : break ; } }
var myReducers = combineReducers ({ list })
const action = { type: '选择要操作的逻辑', payload: '转递参数' }
var store = createStore ( myReducers ) store.dispatch (action)//调用action
<Provider store = { store }> 包裹项目 </Provider>
* 组件内使用redux import { connect } from 'react-redux' import { xxx } from '/action/xxx' 17之前 connect( state => { value: state.reducer( .value ) },{ xxx (引入的action) } )( 组件 ) 17之后 @connect( state =>({ value: state.reducer( .value ) }), { xxx ( 引入的reducer ) } ) 组件
* redux-promise 插件 action 中只能写同步代码 使action中,可以直接使用axios请求,并直接返回请求到的数据 const action = { type: '选择要操作的逻辑', payload: axios.get( url ) } //先当于在action中,直接取到 axios.get( url ).then( res=> {}) 中的res
//////////////// flex 布局 ////////////////
* 使用 flex: 1; 时,为防止溢出导致布局变化,在布局方向上添加 width: 1px 或 height: 1px
//////////////////////// 数据持久化 ////////////////////////
* import { persistStore } from 'redux-persist' import { PersistGate } from 'redux-persist/lib/integration/react' ///////// index.js配置 //////////
//////// reducer持久化需要配置 //////// * import { persistReducer } from 'redux-persist' import storage from 'redux-persist/lib/storage' import sessionStorage from 'redux-persist/lib/storage/session' import autoMergeLevel2 from 'redux-persist/lib/stateReconciler/autoMergeLevel2' import doData from './reducers/doData' // reducer
//需要对哪些 state 持久化 const rootPersistConfig = { key: 'root', storage: sessionStorage,//storage 类型 stateReconciler:autoMergeLevel2, // 层级 whiteList: ['doData'] //白名单 想让谁持久,就放谁 }
//抽离一个需要持久化的公共 reducer const myPersistReducer = persistReducer( rootPersistConfig, doData )
const store = createStore( myPersistReducer, composeEnhancers( applyMiddleware(promise) ) )
<Provider store = { store }> <PersistGate loading = { null } persistor={ PersistStore( store ) }> 包裹项目 </Provider> </Provider>
* redux-thunk -- 以函数的形式写action import { redux-thunk } from 'redux-thunk'
export function getxxx () { return dispatch => { dispatch({ type: ' getList ',payload: ' 参数 ' }) } }
applyMiddleware( redux-thunk ) //注意将中间件添入
/////////////////////node /////////////////////
* npm view 插件 versions //查看插件版本
//////////////////// antd ////////////////////
* Form 表单回填 ( antd 版本3 )
Form.create({ mapPropsToFields( props ) { return { username: Form.createFormField({ value: '65464', }), password: Form.createFormField({ value: '98798797', }) } } })( 组件名 )
/////////////////////////// React 生命周期 /////////////////////////// * 16.4 1. 加载阶段: constructor( ),render( ),componentDidMount( ) 2. 更新阶段: componentWillReceiveProps( ),shouldComponentUpdate( ),render( ), componentWillUpdate( ),componentDidUpdate( ) 3. 卸载阶段: componentWillUnmount( )
* 16.4 之后: 加载阶段: consructor( ),getDerivedStateFromProps( ),render( ),componentDidMount( ) 更新阶段: getDerivedStateFromProps( nextProps, prevState ),shouldComponentUpdate( ),render( ), getSnapshotBeforeUpdate( prevProps, prevState ),componentDidUpdate( ) 卸载阶段: componentWillUnmount( )
注意: getSnapshotBeforeUpdate( prevProps, prevState ), getDerivedStateFromProps( nextProps, prevState ) //两者都必须有返回值 state或者props改变就触发 比较props的值是否有改变
////////////////// es6 //////////////////
* includes():返回布尔值,表示是否找到了参数字符串。 startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。 endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。
* this : 代表当前执行的对象
* constructor(){ } //构造过程 new 的过程也是构造 // 创建一个空对象{ } // 执行构造器 // 把所有的 this 替换成 { } // 最终返回 空对象
* var xxx = ( ) => { } // 被定义之后,this产生并固定不变
* 箭头函数 与 普通函数 -- 区别 //箭头函数 没有 arguments //箭头函数 不能当构造器使用 //箭头函数 没有自己的this //箭头函数 this 是在定义的时候就不变了
* 数组扩展方法 map() // 返回新数组,长度与原数组长度相同 filter() // 返回新数组,返回符合条件的数据 reduce() //两个参数,第一个参数是回调函数,第二个参数为默认第一项值 find() //返回真 结束循环 并把当前值返回 findIndex() //返回真 结束循环 并把当前值的下标返回 for of // 数组中值的循环
* 对象扩展方法 keys() //遍历key值返回为一个数组 entries(){} //将对象变为二维数组 每一对key和值变为一个数组 is 深浅拷贝 Object.assign() , { ...obj } //浅拷贝 JSON.parse( JSON.stringify( obj ) ) //可以实现深拷贝存在bug // 过滤undefined // 如果对象中有function,拷贝后的对象会丢失这个function // 如果对象中存在循环引用的情况也无法正确实现深拷贝 递归 , lodash //深拷贝
* Symbol 变量 // 具有唯一性 多用于object的key
* Promise //解决回调地狱 //三种状态: 进行中 已成功 已失败
const promise = new Promise(( resolve, reject ) => { resolve( '成功' ) reject( '失败' ) }) //resolve() 回调函数(成功) //reject() 回调函数(失败)
链式操作 // 等待上一个promise改变状态后再执行下一个 promise .then(res => { console.log( res ) // 打印resolve()中的 '成功' }) .catch(err => { console.log( err ) // 打印reject()中的 '成功' })
Promise.all ([promise, promise2]).then( res => { console.log(res)// 所有promise返回resolve时,才执行//失败时,只返回失败项的状态 }) Promise.race ([promise, promise2]).then( res => { console.log(res)// 此状态为最先改变的promise的状态 })
Promise.resolve( 任意类型 ) 快速创建一个成功的promise 参数为then打印的参数值 Promise.reject( 任意类型 ) 快速创建一个失败的promise 参数为catch打印的参数值
* Generator const promise = new Promise(( resolve, reject ) => { resolve( '成功' ) reject( '失败' ) }) function* fn() { yield promise yield 'hello' } const f = fn() console.log( f.next() )//返回{ value:'xxx', done: false } console.log( f.next() )//返回{ value:'xxx', done: false } console.log( f.next() )//返回{ value:'undefined', done: true } done为true,结束 f.next(参数)//参数为上一个yield的返回值
* 宏任务 - 微任务 宏任务: setTimeout setInterval 微任务: resolve reject await 同步任务
同步任务 > 微任务 > 宏任务 //优先级
* event loop 事件循环机制 1.先执行同步任务 2.微任务调用栈[resolve(1), resolve(2), resolve(3)] await 3.宏任务: setTimeout setInterval I/O(异步操作) DOM渲染
* async 含义:相对于Promise链式操作与Generator的优化,使得异步操作更加方便,返回值为 Promise对象 内部与await联合使用,await后面的任务会等到await代码执行完之后再执行 await 接收任意类型 通常接收异步方法 const data = await promise() //用变量接收值,等同于执行一个then,若状态为失败, 则需用.catch()接收返回,此时data接收失败的值,并且后面的await不会执行 推荐写法 try{ data = await promise() } catch(e) { console.log() }
* async 原理 async === Generator + 自动执行器 function spawn (genF) { return new Promise(resolve => { const gen = genF() function step (nextF) { let next = nextF() if (next.done) { return resolve(next.value) } step(() => gen.next(next.value)) } step(() => gen.next(undefined)) }) }
* 类 class Person { constructor(username, sex) {//构造器 constructor this.name = username this.sex = sex } }
constructor 构造过程 1. 创建一个空对象{ } 2. 执行构造器 3. 把所有的 this 替换成 { } 4. 最终返回 空对象
继承 class Animals { static eye = 'eye'//静态属性 (静态也可以继承) moues = 'moues' } class Person extends Animals { constructor() { super()//继承的子类是没有this的,需要通过super方法将父级的this拿过来 } }
//////////////////// git /////////////////////
* svn, git:版本管理工具 github:面向个人'
* 配置git//新机器必备 git config --global user.email"用户登录邮箱" git config --global user.name"用户名"
1. 创建项目 git init//项目托管给git git add . git commit -m "说明性注释"//创建提交
2. 提交项目 git commit -m "说明性注释"//创建提交 git push -u origin master//提交分支到gitHub(远程)
3.配置公钥私钥 ssh-keygen -t rsa -C '用户登录邮箱'//创建公钥 cat ···/.ssh/id_rsa.pub//打开公钥(钥匙文件所在文件夹下打开) gitHUB页面中setting设置公钥//将文件中的密钥复制到gitHub
4.查看修改与提交 (常用) git status//查看git那些文件修改 git add (文件名/.)//保存文件修改到本地 git commit -m "说明性注释"//创建提交 git status//再次查看文件是否无修改 git push origin master (master是分支名)//往远程提交分支文件
* 常用分支指令 git log//查看日志 git checkout -b 本地分支名 origin/master//创建分支 git checkout 分支名//切换分支 git branch -v//查看本地分支 git branch -a//查看远程分支 git config --list//查看账号信息
* 开发常用 git stash / git stash save '说明性注释'//暂存修改(文件回复到修改前) git stash list//查看缓存列表 git stash apply / apply stash@{0}//回到最近(或指定)存储 git stash clear//清除缓存 git diff <file>//查看当前修改文件 git clone (gitHub地址)//复制拉取文件 git pull origin master(远程分支)//同步远程项目 git fetch//拉取远程分支同步到本地 git reset --hard hash值//撤回提交 git push -f origin master//强制提交 git checkout .//撤销修改
* 合并分支 git add (文件名/.)//保存文件修改到本地 git commit -m "说明性注释"//创建提交 git checkout 分支名//切换分支 git merge 需要合并的分支名//将其他分支合并到当前分支 /////////////////////////react 项目创建流程/////////////////////////
1.npx create-react-app name 2.配置文件夹,路由,页面,组件等 3.配置router文件夹,包含 index.js(主路由文件) 与 assembly.js(抛出路由组件) 4.删除不必要的文件,index.js中删除App相关,并引入主路由 router/index.js 5.配置conifg-overrides.js(项目配置文件/webpack配置文件) 替换package.json中的scripts配置,并安装配置相关依赖 "scripts": { "start": "react-app-rewired start --open", "build": "react-app-rewired build", "test": "react-app-rewired test", "eject": "react-app-rewired eject" },"scripts": { "start": "react-app-rewired start --open", "build": "react-app-rewired build", "test": "react-app-rewired test", "eject": "react-app-rewired eject" }, 6.安装less@3.11.1 与 less-loader@5.0.0,更改index.js引入index.css为index.less
/////////////// Hook //////////////
* class组件达到瓶颈,复用组件逻辑复杂(使用高阶函数),hook的主要用途就是复用状态逻辑
* Hook(是函数、没有生命周期)
* Hook常用钩子 1.useState: const = [ val, setVal ] = useState(默认值)//定义useState setVal()//设置useState中的值(异步) setVal( pre => { return xxx } )//拿到上一次的值 const = [ val, setVal ] = useState( ( ) => { 初始值有复杂逻辑时写法 return xxx }) 2.useEffect: useEffect( () => {
//逻辑代码 return () => { 卸载阶段 }
}, [ 依赖项 ] )//依赖项中为监听值,没有监听值只执行一次,相当于DidMount//依赖多个值,只要一个改变,就触发,监听后相当于更新期
* 注意事项: Hook 只能写在函数组件 Hook 只能写在顶层,不能写在 if for中,hook按顺序解析;Hook的调用顺序在每次渲染中都是相同的 Hook 自定义hook,以use开头或者以函数组件的形式
* createRef 与 useRef const myRef = React.createRef( )//每次都返回一个新对象 console.log( myRef.current )//不可以加属性,只有current属性 <div ref={ myRef }></div>
const ref = useRef ( 初始值 )//只执行一次,可记录上一次的值,始终返回同一个对象 console.log( ref.current )//不一定只有current属性
* memo//返回一个新组件 机制与PureComponent相似,执行时做比较优化 const A = memo(function(){ return( <div>AAA</div> ) }) <A /> useMemo( 回调, [ 依赖 ]) => 返回值取决于return//多用于缓存,函数体立刻执行 useCallback( 回调, [ 依赖 ]) => 返回新函数 //比较函数,相同就缓存通常与memo连用,函数体需要调用执行
redux-react-hook//配合useReducer使用的插件
/////////// vue ///////////
* 安装 npm i @vue-cli vue create hello-world
* 配置文件夹,路由,页面,组件等 npm i element-ui -S npm install babel-plugin-component -D//按需引入
* data() { return { //项目中的数据 } }
* methods: { //项目函数 }
* watch: { data中的现有数据 : { 监听触发的函数 } }
* computed: {//缓存,同时可以监听多个值 data中的现有数据 : { 监听触发的函数 } }
* filters: {//过滤值 filter(price) { return `$${price}` } } {{ price | filter(price) }}
* components: { 引入的组件 } 父传子 父:<A :price="price"/> 子:props: [ 'price' ],
子传父 子:methods: { onclick(){ this$emits('aaa(触发事件名)',{ 返回值 }) } } <div @click='onclick'></div> 父:methods: { onAAA(obj){ console.log(obj) } } <A @aaa(触发事件名)='onAAA'></div>
* slot <A>aaa</A>//拿到在父组件中,子组件的公共部分 子: <slot/>
* keep-alive //缓存,避免加载浪费性能 <keep-alive></keep-alive> activated(){}//激活 deactivated(){}//暂停
* router this.$router.push('/ ')//跳转 this.$router.push({name: ''}) const routes = [ { path: '/', name: 'Home', component: Home, }, { path: '/about', name: 'About', component: () => import('../views/about/About.vue') } ] const router = new VueRouter({ mode: 'history', base: process.env.BASE_URL, routes }) export default router
* vuex store/index.js//配置store state: {//默认值 code: 1, data: [] }, mutations: {//同步修改 只能在mutations中修改state 触发 commit //同步代码不能写异步 setCode ( state, action ) { console.log() state = action } setDataList ( state, action ) { state.data = action } }, actions: {//异步操作 触发 dispatch getDataList ({ commit }) { const data = axios.get('url') commit( 'setDataList', data ) commit( 'home/setDataList', data, {root: true})//调用别的方法 } }, modules: { //引入拆出的模块 }
页面组件 import { mapState, mapMutations, mapActions } from 'vuex'//引入store中的配置 methods: { ...mapMutations([ 'setCode',//在Mutations中提取方执行方法 ]), ...mapMutations('home',[ 'setCount',//在拆出部分中提取方执行方法actions同理 ]), ...mapActions([ 'getDataList',//在Mutations中提取方执行方法 ]), onclick() { this.setCode(传值)//使用提取出来的方法处理数据 } } computed: {//在computed中取store的state值 ...mapState(['code', 'data']), ...mapState('home',[ 'count' ]),//引入拆出模块的state ...mapState('home',{ homeCount: 'count',//重名时,别名方法 }) }
//////////////////溢出省略////////////////// * 单行溢出省略 overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
* 多行溢出省略 overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2;//行数 -webkit-box-orient: vertical;
* 中间截取省略 overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;//行数 text-indent: -40px;//缩进
///////////////面试常问///////////////
* z-index//需要position才能生效 0,0 点//相对于设置position最近的父元素//如果没有position父元素,以document定位//relative的0,0点相对于父元素计算
* 粘性定位 position: sticky;
* 事件注册 addEventListener( ' ', function( ){ }, false ) //事件;触发函数;在哪个阶段触发( false,冒泡;true,捕获 )
* 事件传递方式 捕获阶段 -> 目标阶段 -> 冒泡阶段
* 跨域 (浏览器)同源策略:协议,域名,端口,只要一项不同就会跨域 JSONP CORS后台设置请求头 设置代理 http-proxy-middleware 上线后项目不是node,使用nginx反向代理 * 为什么请求要放在componentDidMoutn() this.setState() 需要在DOM渲染完之后才能执行
// await 微任务 promise // 4 1 3 6 8 2 7 5//执行顺序 async function async1() { console.log(1) await async2() // reoslve() console.log(2) } async function async2() { console.log(3) } console.log(4) setTimeout( function () { console.log(5) }, 0) async1(); new Promise(function ( resolve ) { console.log(6) resolve(); }).then( function () { console.log(7) }) console.log(8)
* viewport//主要用于移动设备 width = device-width//宽度为设配屏幕的宽度 initial-scale = 1.0//初始缩放比例 minimum-scale = 0.5//最小缩放比例 maximum-scale = 2.0//最大缩放比例 user-scalable = yes//用户是否可以调整缩放
* 浏览器内核 chrome: Blink FireFox: Gecko Opera: Blink Safari: Webkit
* 路由权限 pt: 登录 首页 vip : 登录 首页 详情 sipv: 登录 首页 详情 用户列表
1. 本地存 { pt: [], vip: [], sivp: [] } 2. 登录后台返回权限 通过权限 拿到这个用户能访问的路由数组 3. 全局路由拦截 [].includes(pathname)
* HTML5 canvas svg video audio localStorage sessionStorage webSocket pushState replaceState
* 清除浮动的2中方法 //内浮动 :after :before{ clear: both }
* 新增伪类 :after :before :checked :focus :nth-child :last-child :first-child
* 手动动画最小间隔 1/60*1000ms = 16.7ms
* css3动画 transform ( 通过事件触发 ) animation ( 不通过事件触发 )
* 如何使用CSS实现硬件加速, GPU渲染 transfrom: translate3D(100, 0, 0): 写上Z轴 会使用伪 GPU渲染 will-change: 绝对的GPU渲染
* websoket 跟后台进行通信 创建长连接 const ws = new Websocket(url) ws -> httpwss -> https # 四个事件 open 连接建立时触发 message客户端服务端数据时触发 error通信发生错误时触发 close 连接关闭时触发 # 两个方法 ws.send() ws.close()
* js继承 继承概念:通过某种方式让一个对象可以访问到另一个对象的属性和方法,把这种方式称之为继承 作用:获取更多的拓展,减少代码的冗余等。
### 原型链继承 function Person(){ this.name = '邓紫棋' } Person.prototype.getName = function(){ console.log(this.name) } function Child(){ } Child.prototype = new Person() var child1 = new Child() child1.getName()// 邓紫棋 缺点: 通过原型来实现继承时,原型会变成另一个类型的实例,原先的实例属性变成了现在的原型属性,该原型的引 用类型属性会被所有的实例共享。 在创建子类型的实例时,没有办法在不影响所有对象实例的情 况下给超类型的构造函数中传递参数
###借用构造函数继承 // 借用构造函数继承(经典继承) function Person(){ this.colors = ['red', 'blue', 'green'] } function Child(){ Person.call(this) } var child1 = new Child() child1.colors.push('yellow') console.log(child1.colors)// ["red", "blue", "green", "yellow"] var child2 = new Child() console.log(child2.colors)// ["red", "blue", "green"] 优点: 可以向超类传递参数 解决了原型中包含引用类型值被所有实例共享的问题 缺点: 方法都在构造函数中定义,函数复用无从谈起,超类型原型中定义的方法对于子类型而言都是不可见的。
###组合继承(借用构造函数继承+原型链继承) function Parent (name) { this.name = name this.colors = ['red', 'blue', 'green'] } Parent.prototype.getName = function () { console.log(this.name) } function Child (name, age) { Parent.call(this, name) this.age = age } Child.prototype = new Parent() var child1 = new Child('kevin', '18') console.log(child1)//Parent { name: 'kevin', colors: [ 'red', 'blue', 'green' ], age: '18' } 优点: 可以向超类传递参数 每个实例都有自己的属性 实现了函数复用 缺点: 无论什么情况下,都会调用两次超类型构造函数:一次是在创建 子类型原型的时候,另一次是在子类型构造函数内部
###原型式继承 function Wonman(name){ let instance = new People() instance.name = name || 'wangxiaoxia' return instance } let wonmanObj = new Wonman() 缺点:同原型链实现继承一样,包含引用类型值的属性会被所有实例共享。
###寄生式继承 function createAnother(original){ var clone = Object.create(original) //通过调用函数创建一个新对象 clone.sayHi = function(){ //以某种方式来增强这个对象 alert("Hello") } return clone //返回这个对象 } var person = { name: "Bob", friends: ["Shelby", "Court", "Van"] } var anotherPerson = createAnother(person) anotherPerson.sayHi() 缺点: 使用寄生式继承来为对象添加函数,会由于不能做到函数复用而效率低下。同原型链实现继承一样,包含 引用类型值的属性会被所有实例共享。
###寄生组合式继承 function Parent(name,play){ this.name=name; this.play=play; } function Child(name,play,age){ Parent.call(this,name,play); this.age=age; } // 隔离了父类和子类的构造函数,父类的添加到了__proto__属性上 Child.prototype=Object.create(Parent.prototype); Child.prototype.constructor=Child let child=new Child("张三","玩",20); let child2=new Child("李四","吃",10) console.log(child.constructor) 优点: 只调用了一次超类构造函数,效率更高。避免在 SuberType.prototype上面创建不必要的、多余的属 性,与其同时,原型链还能保持不变。
* web前端性能优化 ## 页面内容 1.减少HTTP · 通过 webpack 合并JS CSS文件 · 使用 CSS Sprite 合并图片 · 使用 Base64 行内图片 · 用 icon 替换图标 2. 避免重定向 · URL末尾添加 / 例如 http://www.baidu.com/ 3. 缓存Ajax请求 · 设置 Cache-Control (相对过期时间) 4. 减少页面元素数量 ## CSS 1. react vue 可以实现css样式按需加载, 减少每次加载CSS的大小 2. 压缩CSS 3. 使用CSS3属性 例如动画使用 transform transiton animtion ## Javascript 1. JS代码尽量放在页面底部, 避免阻塞页面加载 2. 使用外部的CSS 可以缓存CSS 3. webpack 压缩 js代码 ## 图片 1. CSS Sprite 合并图片 2. Webp 图片 3. 不在HTML中缩放图片 4. 非 webp 图片都要压缩后再使用 5. 使用 Base64 内嵌图片 ## react || vue · 路由懒加载 · vue 组件异步加载 ## 服务器 1. 使用 CDN(内容分发网络CDN是一组分散在不同地理位置的web服务器) 2. 添加 Cache-Control 缓存头 3. 启用 Gzip 压缩 (图片不要gzip压缩) 4. Ajax 尽可能使用 get方法, post方法会多一次请求 5. 避免 空图片标签 ## 移动端 1. 保证所有组件都小于25K
* 链表 单向链表:单向链表包含两个域一个是信息域一个是指针域 双向链表:每个节点有2个指针域一个是指向前一个节点另一个则指向后一个节点 循环链表:循环链表就是首节点和末节点被连接在一起,循环链表中第一个节点之前就是最后一个节点 数组和链表的区别: 链表是链式的存储结构数组是顺序的存储结构 链表通过指针来连接元素数组则是把所有元素按次序依次存储 链表的插入删除元素相对数组较为简单但是寻找某个元素较为困难 数组寻找某个元素较为简单但插入与删除比较复杂 自我理解: 数组便于查询和修改但是不方便新增和删除 链表适合新增和删除但是不适合查询
* 防抖 节流 所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件, 则会重新计算函数执行时间。 import _ from 'lodash' const id = useRef() const fn = () => { clearTimeout( id.current ) id.current = setTimeout(( ) => {console.log() }, 1000) } <input onKeyDown={ fn }/>
import _ from 'lodash' const id = useRef() const fn = () => { console.log() } <input onKeyDown={_.debounce(fn, 1000)} />
所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数 import _ from 'lodash' const id = useRef() const fn = () => { if( !id.current ) { id.current = setTimeout(( ) => { id.current = null console.log() }, 1000) } console.log() } <input onKeyDown={_.debounce(fn, 1000)} />
resize//窗口变化时触发(事件)
window.addEventListener('scroll', _.throttle(evt => { document.querySelector('p').innerHTML = document.querySelector('body').scrollTop || document.querySelector('html').scrollTop }, 200), false)
* 判断引用数据类型 Object. prototype. toString.call (对象)
* 事件代理 currentTarget(获取注册事件的节点)
* http状态码 200:表示客户端发来的请求在服务器端被正确处理 201:请求成功并且服务器创建了新的资源。 202:接受请求但没创建资源 203:返回另一资源的请求
301:永久性重定向,表示资源已被分配了新的url 302:临时重定向,表示资源临时分配了新的url 303:表示资源存在着另一个url,应使用get方法获取资源 304:表示服务器允许访问资源,但因发生请求未满足条件的情况
400:请求报文存在语法错误 401:表示发送的请求需要通过HTTP认证的认证信息 403:表示对请求资源的访问被服务器拒绝 404:表示在服务器上没有找到请求的资源
500:表示服务器端在执行请求时发送了错误 501:表示服务器不支持当前请求所需要的的某个功能 503:表示服务器暂时除余超负载或正在停机维护
* 从输入URL 到页面展现 根据地址栏输入的地址向DNS(Domain Name System)查询IP 通过IP向服务器发起TCP连接 向服务器发起请求 服务器返回请求内容 浏览器开始解析渲染页面并显示 关闭连接 # 通过DNS解析获得对应的IP地址 1. 浏览器缓存——浏览器会缓存DNS记录一段时间(2分钟到30分钟) 2. 系统缓存——如何浏览器缓存中没有,浏览器会做一个系统系统调用,查找系统缓存中的记录,常 见的hosts文件 3. 路由缓存——如果系统缓存也没有需要的记录,会向本地路由器发送一条DNS查询请求,一般会有 自己的dns缓存 4. ISP DNS缓存——如果本地路由器没有再查看本地网络提供商(移动啦、电信啦)的DNS服务器, 一般都能找到相应的缓存记录 # 浏览器向服务器发送一个HTTP请求 1. 首先通过三次握手建立TCP连接, 2. 浏览器通过TCP连接向服务器发送一个http请求 # 服务器发出重定向响应 1. 在拿到ip地址后, 浏览器会向对应的web服务器(Nginx,Apache...)发起TCP连接请求,通过三次握手,建立 TCP连接 2. 建立TCP连接后, 浏览器向web服务器发送Http请求 # 服务器返回请求内容 服务器在接收到请求后,解析用户请求,知道了要调度那些资源文件,再通过相应的资源文件,处理用户的请 求和参数,并调用数据库信息,最后讲结果通过web服务器返回给浏览器. # 浏览器开始解析渲染页面并显示 html 渲染过程 # 关闭连接 在这次数据传输完成后,为了避免服务器与客户端双方的资源占用和损耗,会经过四次挥手,关闭TCP连接.
* 浏览器缓存 强缓存 Expires: 2020.12.12 8.0 一个绝对时间的 GMT 格式的时间字符串,代表缓存资源的过期时间 Cache-Control: 2小时 2小时之内你重新请求了一个接口 从你请求这个接口开始 重新计算2小时 判断缓存资源的最⼤⽣命周期,它的值单位为秒 协商缓存 If-Modified-Since 过期 2020.12.12 8.0 ajax('1.php') -> 后台可以拿到过期时间 后台判断 不给返回新数据 300, 有数据200 通过⽐较两个时间来判断资源在两次请求期间是否有过修改 Last-Modified (值为资源最后更新时间,随服务器response返回)
* RESF规范 实现一个接口执行多个操作
* vip权限 封装权限路由表(数组) 跳转获取pathname,通过后台的权限,使用includes查看是否包含该pathname,
* 数组合并4种方法 arr.concat( [] ) arr.push( ...[] )//返回长度,合成数组为arr [ ...arr, ...arr1 ] arr.push.apply(arr, [])//返回长度,合成数组为arr
* jekenis 上线 提交代码到 测试 featur/dev 提交代码到 测试 featur/pre 提交代码到 测试 featur/master
* 打包 npm run build 合并 js css react less 转译 js css build 1.FTP 2.给后台
* 前端数据映射: //解决公共组件 渲染后台 不同接口 返回不同数据的问题 1. 首先定义一套自己的数据格式 2. 把后台接口给的数据转化成我们自己定义的数据格式 3. 公共组件都适用这套自己定义的数据格式渲染数据
////////////////////问公司/////////////////////
* 技术栈--详细了解(包括状态管理工具,插件等)
* 假如有幸加入公司,会负责哪些项目
* 公司内部是否有技术分享
/////人事/////
* 接下来找工作的想法: 1. 北京的公司 2. 稳定可以长期干下去的公司 3. 技术氛围好的公司
* 你找工作主要看中哪些东西 1. 技术提升 2. 薪资是否可以 3. 公司氛围
//////////////////////react插件、方法//////////////////////
* React传送门 ReactDOM.createPortal( fn(), document.querySelector('') )//渲染到root外面 { children }//通过 children 展示弹框内容
* export const { Provider, Consumer } = createContext() <Provider value='wxh'></Provider>
* react-router-config import { renderRoutes } from 'react-router-config' const Root = ({ route }) => ( <div> <h1>Root</h1> {renderRoutes(route.routes)} </div> )
const routes = [ { component: Root, routes:[ { path: '/home/:id',//动态路由 component: Home, routes:[ { path: '/home/user', component: Hook } ] }, { path: '/hook', component: Hook }, { path: '/hook2', component: Hook2 }, ] } ]
* import { useHistory, useRouteMatch } from 'react-router-dom' 子组件中引用 let match = useRouteMatch('/home/:id') let history = useHistory()
* import Masonry from 'masonry-layout' // 瀑布流 * import InfiniteScroll from 'react-infinite-scroller' //下拉加载 state = { hasMore: true, // 是否开启下拉加载 data: [ { title: '小白' }, { title: '小白' }, { title: '小白' }, { title: '小白' }, { title: '小白' }, { title: '小白' }, { title: '小白' }, ], // 接受我每次的数据 count: 0, width: '', }
advanceWidth = () => {//瀑布流配置 new Masonry(document.querySelector('.content'), {// new Masonry(节点, 配置) itemSelector: '.d', // 要布局的网格元素 fitWidth: true, // 设置网格容器宽度等于网格宽度 gutter: 20, columnWidth: '.d', originLeft: true, }) }
loadMoreData = page => {//下拉加载配置 const { data, count } = this.state if (count && page > Math.ceil(count / 10)) return false axios.post('https://api.baxiaobu.com/index.php/home/v5/getuser', { data: { page, limit: 10 } }) .then(res => { this.setState({ data: [...data, ...data], count: res.count, }) this.advanceWidth() }) .catch(err => console.log(err)) }
<div className="box"> <InfiniteScroll//下拉加载部分 initialLoad={false} // 不让它进入直接加载 pageStart={1} // 设置初始化请求的页数 loadMore={this.loadMoreData} // 监听的ajax请求 hasMore={true} // 是否继续监听滚动事件 true 监听 | false 不再监听 useWindow={true} // 不监听 window 滚动条 如果你要监听 window 外层不能有任何节点 > <div className="content"> { this.state.data.map((value, key) => (//瀑布流部分 <div key={key} className="d xxx"> {value.title} </div> )) } </div> </InfiniteScroll> </div>
.box { box-sizing: border-box; margin: 0 auto; width: 100%; height: 100%; overflow-x: hidden; overflow-y: auto; box-sizing: border-box; border: 5px #0f0 solid; .content { width: 100%; margin: 0 auto; border: 5px #00F solid; } .xxx { width: 200px; margin: 0 0 20px 0; border: 5px #f00 solid; } .xxx:nth-child(2n-1) { height: 200px; } .xxx:nth-child(2n) { height: 300px; } }
// columnWidth: 200, // itemSelector: '.grid-item' // 要布局的网格元素 // gutter: 10 // 网格间水平方向边距,垂直方向边距使用css的margin-bottom设置 // percentPosition: true // 使用columnWidth对应元素的百分比尺寸 // stamp:'.grid-stamp' // 网格中的固定元素,不会因重新布局改变位置,移动元素填充到固定元素下方 // fitWidth: true // 设置网格容器宽度等于网格宽度,这样配合css的auto margin实现居中显示 // originLeft: true // 默认true网格左对齐,设为false变为右对齐 // originTop: true // 默认true网格对齐顶部,设为false对齐底部 // containerStyle: { position: 'relative' } // 设置容器样式 // transitionDuration: '0.8s' // 改变位置或变为显示后,重布局变换的持续时间,时间格式为css的时间格式 // stagger: '0.03s' // 重布局时网格并不是一起变换的,排在后面的网格比前一个延迟开始,该项设置延迟时间 // resize: false // 改变窗口大小将不会影响布局 // initLayout: true // 初始化布局,设未true可手动初试化布局
* npm i imagesloaded -S//等图片加载
imagesOnload = () => {//等待图片加载 // 初始化你要监听哪个节点下的图片 const elLoad = imagesLoaded('.content') // always 页面图片全部加载完 不管有没有加载失败的图片 elLoad.on('always', (instance, image) => { // 图片加载后执行的方法 // 拿第一次的数据 this.advanceWidth() // 初始化瀑布流 }) }
* 图片瀑布流 1. 元素设置 absolute 2. 获取浏览器宽度 和 卡片的宽度, 浏览器的宽度 / 卡片宽度 = 有几列 const imglist = [img, img, img, img, ...] const arr = [ [{ url: '图片路径', position: {top: 100px, left: 40px} }], // 100px [img, img], // 80 + 20 = 100 [img, img], // 60 + 70 = 130 [img], // 120 ] arr.forEach(v => { v.forEach(v2 => { v2.img + v2.img }) }) // 2 80 + 20 = 100 arr[1].push(imglist[5])
arr.map(v => { v.map(v2 => { <img src={v2.url} style={{top: v2.position.top, left: v2.position.left}} }) }) 3. onresize 获取浏览器宽度 和 卡片的宽度, 浏览器的宽度 / 卡片宽度 = 有几列 const imglist = [img, img, img, img, ...] const arr = [ [{ url: '图片路径', position: {top: 100px, left: 40px} }], // 100px [img, img], // 80 + 20 = 100 [img, img], // 60 + 70 = 130 ]
/////////////////// dva //////////////////
* dva === redux-sage + roadhog npm i dva-cli -g//安装dva-cli dva new name//创建dva应用
* .webpackrc.js//webpackrc改为js文件 export default {//配置webpackrc.js文件 publicPath: '/', extraBabelPlugins: [ ['import', { 'libraryName': 'antd', 'libraryDirectory': 'es', 'style': 'css' }], ], alias: { '@': `${__dirname}/src`, '@@': `${__dirname}/src/components` }, proxy: { '/aps': { target: 'https://api.baxiaobu.com', changeOrigin: true, pathRewrite: { '^/aps': '', } }, '/api': { target: 'https://blogs.zdldove.top', changeOrigin: true, pathRewrite: { '^/api': '', } }, } }
* 在routes中创建assembly.js、router.js同react一样配置路由 在router.js中引入异步加载路由 import dynamic from 'dva/dynamic'
function RouterConfig({ history, app }) {//注意引入app const Home = dynamic({ app, models: () => [//引入model,数组形式 import('@/models/home') ], component: () => import('@/pages/home'),//引入组件 }) return ( <Router history={history}> <Switch> <Route path="/" exact component={Home} /> </Switch> </Router> ) }
export default RouterConfig
* model export default { namespace: 'home',
state: { data: [] },
subscriptions: { setup({ dispatch, history }) { // eslint-disable-line }, },
effects: {//处理异步 *fetch({ payload }, { call, put, select }) { //select拿到上一次的数据,里面也是函数 const xx = yield call(() => {做请求})//call中是函数,相当于一个await yield put({//put === dispatch type: '', payload: '' }) }, },
reducers: {//修改state home/setName (state, { payload }) { return { ...state, data: payload } }, }, } 组件中使用connect连接组件 connect( state => { return { dataName: state.home.data } } )(Home)
this.props.dispatch({//通过props.dispatch调用 type: 'home/setName', payload: 'xla' })
* subscriptinos: {//订阅 初始化数据 xxx ({ history, dispatch }) { history.listen(({ pathname }) => {const regexp = pathToRegexp( '/home' ).test(pathname) }) } } //pathToRegexp将路由自动转为正则
model中路由跳转 router from 'umi/router' put( routerRedux.push('/xxx') )
////////////////////typescript///////////////////
* npx create-react-app tsDemo --typescript//ts react项目搭建 tsconfig.json 配置 { "compilerOptions": { "target": "es5", // 指定 ECMAScript 版本 "lib": [ "dom", "dom.iterable", "esnext" ], "outDir": "lib", "allowJs": true, // 允许编译 JavaScript 文件 "skipLibCheck": true, // 禁用命名空间引用 (import * as fs from "fs") 启用 CJS/AMD/UMD 风格引用 (import fs from "fs") "esModuleInterop": true, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, "module": "esnext", "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react", "declaration": true }, "include": [ "src/**/*" ], "exclude": ["node_modules", "build"] // *** 不进行类型检查的文件 *** }
* 声明变量 let/const/var xxx : 数据类型 = xxx let/const/var arr : 数据类型[ ] = [ ]//数组 let/const/var arr : (number, string) [ ] = [ ]//数组联合类型 let/const/var arr : any = xxx//任意类型 let/const/var arr : number | boolean | null = xxx//联合类型(允许值为其中一种类型)
let xxx: [number, boolean] = [1, true]
enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat}//枚举 值只能为string或number
function name (xxx: string, xxx:number ): void { //void: 表示一个函数没有任何一个返回值 }
function name (xxx: string, xxx:string ): string function name (xxx: number, xxx:number ): number function name (xxx: any, xxx:any ): any {//函数重载
}
* class类 class Person { name: string age: number constructor(n: string, a: number){//需初始化 this.name = n this.age = a } } Person('wxh', 20)
* interface//接口不可实例化可继承(仅可继承一个, 接口可实现多个) interface A { 可设置对象、函数等接口(规定接口中有哪些变量或属性) } implements//实现接口 interface 可以继承类,但是constructor,静态方法、属性不会继承
//接口只能定义规则,但抽象类可以设置公共的属性和方法
* abstract//抽象类不可实例化 abstract Person { name: string constructor(n: string){//需初始化 this.name = n } }
* 泛型 泛型函数 function fun<T> (opt: T): T {//传入什么类型,返回什么类型 return opt } console.log(fun<string>('123')) console.log(fun('123'))
function fun2<T> (opt: T[]): T[] {//传入什么类型,返回什么类型 return opt } console.log(fun<string>(['1', '2', '3'])) 泛型类 class Person<T> { private count: T[] constructor(arr: T[]) { this.count = arr } minRun(): T { let minValue = this.count[0] this.count.forEach(v => { if(v < minValue) { minValue = v } }) return minValue } } const person = new Person<number>([1, 2, 3]) person.minRun()// 1 泛型接口 interface Kind<T> { (arg: T, n: T): T } const fn: Kind<number> = function<t> (arg: T, n: T): T{ return arg } fn(123, 456)
////////////// mobx ///////////// * npm i mobx -S//安装mobx插件
* import { observable } from 'mobx'//可观察数据(监听) //4.0以前 const num = observable.box(11) num.get()//获取值 num.set()//修改值
//引用数据类型 const arr = observable([1, 2, 3]) arr[2] = 4 const obj = observable({name: 'wxh'}) obj.name = 'wbl' //避免下标越界访问数组
* import { computed, } from 'mobx'//监听数据变化 class kuuga { observable str = 'wxh' @computed get com() {//使用数据时触发 console.log('获取') } set com(){//更改数据时触发 console.log('更改') } } const Kuuga = new kuuga() console.log(Kuuga.com)//触发get com() Kuuga.str = 'hdr'//触发set com()
* autorun 当任意可观察数据修改时触发
* when(() => { return boolean },() => {})
* @action xxx() {}//能将多次autorun合并为一次 @action.bound xxx() {}//效果相当于bind(),用于改变指向
///////////////设计模式///////////////
* 发布-订阅模式 var saleOffices = {//发布-订阅模式 clientList: [],
listen (fn){ //订阅函数 this.clientList.push(fn) },
trigger(price, square) { //发布函数 this.clientList.forEach(fn => { fn(price, square) }) } }
saleOffices.listen((price, square) => { console.log('王小红'+price, square) })
saleOffices.listen((price, square) => { console.log('好多肉'+price, square) })
saleOffices.listen((price, square) => { console.log('沈年年'+price, square) })
saleOffices.trigger(8000, 120)
* 传参判断版 var saleOffices = { clientList: {},
listen (kind, fn){ //订阅函数 kind表示订阅分类,函数表示订阅后要做什么 if(!this.clientList[kind]){ this.clientList[kind] = []//判断是否存在该订阅类型 } this.clientList[kind].push(fn)//存在就添加 },
trigger(kind, price, square) { //发布函数 this.clientList[kind].forEach(fn => {//遍历该类型的所有方法 fn(price, square) }) } }
saleOffices.listen('square120' ,(price, square) => {//订阅时传入订阅类型和执行函数 console.log('王小红'+price, square) })
saleOffices.listen('square120', (price, square) => { console.log('好多肉'+price, square) })
saleOffices.listen('square130', (price, square) => { console.log('沈年年'+price, square) })
saleOffices.trigger('square120', 8000, 120)
/////////////////// 函数柯里化 //////////////////
function fun() { const arr = []
return opt => { if (opt) { arr.push(opt) } else { let sum = 0 arr.forEach(v => { sum += v }) return sum } }}
const fn = fun()
fn(10)fn(20)fn(30)fn(40)console.log(fn())
//////////////////////////////pushState replaceState////////////////////////////// state: 可通过 history.state读取 title: 可选参数,暂时没有用,建议传个短标题 url: 改变后的 url 地址 /abc
let { history } = window//window中的history export default function Home (props) { const onClick = () => { var _wr = function(type) { var orig = history[type] return function() { var rv = orig.apply(this, arguments) var e = new Event(type) e.arguments = arguments window.dispatchEvent(e) return rv } } history.pushState = _wr('pushState') history.replaceState = _wr('replaceState') // 监听 history.pushState window.addEventListener('pushState', function(e) { if ('page1') { } // history.state 直接拿 pushState 第一个参数 console.log(history.state, 2) })
history.pushState({ page: 1 }, 'title1', 'page1') history.replaceState({page: 2}, "title 3", 'page2') }
return ( <div className="pages-home"> <Button onClick={onClick}>点我</Button> </div> ) }
////////////////////////////redux - promise原理///////////////////////////
// 判断一个变量是不是 promiseimport isPromise from 'is-promise'// 是不是 FSA// FSA: 定义 action 标准 { type, payload, error, meta }// 必须有 type, // 可能有 payload, error, metaimport { isFSA } from 'flux-standard-action'
// redux-promiseexport default function promiseMiddleware(_ref) { // next === 下一个中间件 或者 dispatch // 首先返回一个函数 接收一个参数 这个参数是下一个中间件 或者 dispatch return function (next) { return function (action) { // 判断是不是标准的 FSA // 标准的 FSA 只包含4个属性 type payload error meta if (!isFSA(action)) { /** * 判断是不是promise, * 如果是则执行,只会处理resolve的值, * 反之交给下一个中间件 */ return isPromise(action) ? action.then(dispatch) : next(action); }
// 是标准的FSA, 判断是不是一个promise return isPromise(action.payload) /** * 1.promise的时候,执行then,同时捕获异常, * 在处理这两种情况以后,会分别添加另外一个约束error * 这我们需要在reducer里面还需要判断error的值, * 做不同的处理 * * 2. 如果不是promise则交给下一个中间件 * */ // promise // action.payload === axios.get('http://www.baidu.com') ? action.payload // result 就是我们请求接口的数据 .then(result => { _ref.dispatch({ ...action, payload: result }) }) .catch(error => { _ref.dispatch({ ...action, payload: error, error: true }); return Promise.reject(error); }) : next(action); } };}
//////////////////////// BFC /////////////////////////
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=0.5" /><meta name="description" content="Web site created using create-react-app" /><title>React App</title><style>.d0 { border: 10px #F00 solid; width: 200px; /* float: left; */ /* position: absolute; */ /* display: inline-block; */ /* display: table-cell; */ overflow: hidden;}.d1 { background: #9F9; height: 100px; width: 100%; float: left;}*{ margin: 0; padding: 0;}p { color: #FFF; background: rgb(214, 125, 9); width: 200px; line-height: 100px; text-align:center; margin: 0 0 30px 0;}.div { overflow: hidden; border: 1px #F00 solid;}div p { margin: 30px 0 0 0;}</style>
</head><body><div id="root"></div><div class="d0"> <div class="d1"></div></div>
<p>看看我的 margin是多少</p><div class="div"> <p>看看我的 margin是多少</p></div></body></html>
BFC: 块格式化上下文
变成BFC: 浮动元素 (元素的 float 不是 none) 绝对定位元素 (元素具有 position 为 absolute 或 fixed) 内联块 (元素具有 display: inline-block) 表格单元格 (元素具有 display: table-cell,HTML表格单元格默认属性) 具有overflow 且值不是 visible 的块元素
BFC特性: 使 BFC 内部浮动元素不会到处乱跑; 和浮动元素产生边界。
<script></script>
////////////////// 修饰器 /////////////////
//target === MyTestableClassfunction testable(isTestable) { return function fun(target) { target.isTestable = isTestable }}
//装饰class用@testable('wxh')class MyTestableClass {
}
console.log(MyTestableClass.isTestable)// MyTestableClass.isTestable
@testable(false)class Myclass {}
console.log(Myclass.isTestable)// Myclass.isTestable
////////////////////postMessage//////////////////// //跨域传值 * 父路由 <iframe src='http://localhost:3001' name='a1' /> document.onclick = function() { window.frames['a1'].postMessage('狼来了', 'http://localhost:3001') } window.addEventListener('message', function(e) { console.log(e.origin, 1) console.log(e.data, 2) })
* 子路由 window.addEventListener('message', function (e) { console.log(e.origin, 1) console.log(e.data, 2)//传递的内容 window.top.postMessage('没来', 'http://localhost:3000') })
//////////////// Umi //////////////// npm create @umijs/umi-app npm i
.umirc.ts//配置路由及其他 pages//存放路由文件
浙公网安备 33010602011771号