Fork me on GitHub

vue笔记

模板中需要用方法/复杂逻辑:里就用计算属性computed:{} 类似方法,在模板中调>用时不需要加括号

模板里若需要调用方法则要加括号,用计算属性更优

  • 安装yarn npm i -g yarn
  • 安装vue-Cli npm i -g @vue/cli or yarn global add @vue/cli 创建项目 vue create my-app
  • 运行项目npm run serve or yarn run

vue3+ts


js,ts变量名和值名字一样直接省略写法:name:name->name

箭头函数vscode快捷写法:dfi


创建项目

1.Vue-CLi

vue create 项目名

2.Vite

基于create-vue 创建出来的项目默认没有初始化git仓库

  • npm create vue@latest

  • npm init vue@latest

以上两种方法创建的项目相同

创建vite项目

  • ```npm create vite@latest``

图形化界面创建

vue ui


状态管理工具

1.Vue3 -> Piania(good),Vuex

2.Vue2 -> Vuex


语法检查工具

ESLint

  • 其他:获取id:uuid,nanoid

组合式api命名组件的两种方式:

1
2
3
4
5
6
7
8
9
10
11
1.命名组件:2个script
<script lang='ts'>
export default(){
name:'Person'//name='Person'命名组件
data(){
}
}
</script>

<script lang='ts' setup>
</script>
1
2
3
4
5
6
2.命名组件:
需先安装插件`npm i vite-plugin-vue-setup-extend -D`
再到vite.config.ts引用
<script lang='ts' setup name='Person'>//name='Person'命名组件

</script>

项目结构

1
2
3
4
5
6
7
//main.ts
import './assets/main.css'

import { createApp } from 'vue'
import App from './App.vue' //根组件App

createApp(App).mount('#app') //创建App并挂载到index.html的id为app的元素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//根目录的index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<!-- 引入main.ts -->
<script type="module" src="/src/main.ts"></script>
</body>
</html>

ref和reactive-响应式数据

import {ref,reactive} from 'vue'

  • ref:适用于基本类型和对象。

    • let name = ref('Coffee') let person = ref({name:'Coffee',age:22}) let arr = ['a',{name:'Coffee',age:22}]
    • 在模板直接使用{{arr[1]}}
    • 改数据:在方法中需要先.value再赋值name.value person.value.name arr.value[1].name
  • reactive:适用于对象和数组。

    • let person = reactive({name:'Coffee',age:22})。获取属性值直接对象.属性名

toRefs和toRef

  • 将reactive定义的对象的属性转为ref

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    //toRefs
    let person = reactive({
    name:'Coffee',
    age:22
    })

    let {name,age} = toRefs(person)
    /*let person = reactive({
    name:'Coffee', ->ref
    age:22 ->ref
    })*/person仍为reactive

    console.log(name.value,age.value)
    console.log(person.name,person.age)

    //toRef
    let {name,age} = toRef(person,'name')
    console.log(name.value)
    console.log(person.name)

v-for为什么加:key

例:用删除操作删除复选框,如果不加:key,则会出错。:key用于标识每个循环的元素,防止诸如删除出错等问题。


computed计算属性:

  • 计算属性也是属性且是只读的。默认为ref响应式数据
  • 计算属性有缓存,只计算一次,缓存中数据变化时才再次计算。而方法每次调用都计算一次。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let a = ref('a')
let b = ref('b')

let c = computed(()=>{
return a.value + b.value //回调函数
})

//通过getter和setter获取和更改计算属性
let c = computed(()=>{
get(){
return a.value + b.value //回调函数
}
set(val){
a.value = val
b.value = val
}
})

Watch监视

参数:

watch(被监视数据,(newValue,oldValue)=>{ },配置对象)

  • ref:

    • 监视ref定义的基本类型数据:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      import {ref,watch} from 'vue'

      let a = ref(0)

      //watch()调用时有返回值,调用停止监视
      const stopWatch = watch(a,(newValue,oldValue)=>{
      console.log(newValue,oldValue)
      if(条件){
      stopWatch() //结束监视
      }
      })
    • 监视ref定义的对象数据:监听对象属性时:deep:true

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      import {ref,watch} from 'vue'

      let person = ref({
      name:'Coffee',
      age:22
      })

      //监视整个对象的变化,对象本身变成新的对象才监视
      watch(person,(newValue,oldValue)={
      console.log(newValue,oldValue)
      })

      //监视对象的属性和对象本身,手动开启深度监听
      //添加配置对象{deep:true,immediate:true}
      //修改属性时,对象地址不变属性值变,oldValue仍指向原来的对象,故newValue=oldValue
      watch(person,(newValue,oldValue)={
      console.log(newValue,oldValue)
      },{deep:true,immediate:true})
    • 监视ref定义的对象的某个属性:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      let person = ref({
      name:'Coffee',
      age:22
      })

      watch(() => person.name,(newValue,oldValue)={
      console.log(newValue,oldValue)
      })

      //监视多个属性
      watch([() => person.name,() => person.age],(newValue,oldValue)={
      console.log(newValue,oldValue)
      })
  • reactive

    • 监视reactive定义的对象类型数据,默认开启深度监视(隐式创建):

      1
      2
      3
      4
      5
      6
      7
      8
      9
      let person = reactive({
      name:'Coffee',
      age:22
      })

      //监视属性变化和对象本身变化
      watch(person,(newValue,oldValue)=>{
      console.log(newValue,oldValue)
      })
    • 监视监视reactive定义的对象类型数据的属性:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      let person = reactive({
      name:'Coffee',
      age:22,
      car:{
      c1:'gti',
      c2:'a4l'
      }
      })

      //监视的对象的属性为基本类型
      watch(()=>person.name,(newValue,oldValue)=>{
      console.log(newValue,oldValue)
      })

      //1.监视的对象的属性为对象,监视car本身而不监视car的属性变化:
      watch(()=>person.car,(newValue,oldValue)=>{
      console.log(newValue,oldValue)
      })

      //2.监视的对象的属性为对象,监视car的属性变化,需要手动开启深度监视
      watch(()=>person.car,(newValue,oldValue)=>{
      console.log(newValue,oldValue)
      },{deep:true})

      //监视多个属性
      watch([() => person.name,() => person.age,person.car],(newValue,oldValue)=>{
      console.log(newValue,oldValue)
      },{deep:true})

      tip:无论是ref还是reactive定义的对象:要监视对象的某个属性,watch里要用回调函数监视,

      watch(()=>person.car,(newValue,oldValue)=>{},{})


html标签中的ref:

1
2
3
4
5
6
7
template中:
<h1 ref='tag'></h1>

ts/js中:
let tag = ref()

可以通过tag变量名标识h1和获取h1

限制对象属性的名字和类型

定义接口

创建文件夹:types->index.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
//index.ts
//限制对象Obj的属性
export interface ObjInter{
id:string,
age:number,
sex?:string //加?表示为可选项
}

//用export暴露出去

//限制数组的元素为对象类型的属性
export type Objs = Array<ObjInter>
export type Objs = ObjInter[]

引用:

import {type ObjInter,type Objs} from '@/types'

使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let obj:ObjInter = {id:'1',age:1}
let objs:Array<ObjInter> = [
{id:'1',age:1},
{id:'2',age:2}
]

let objs:Objs = [
{id:'1',age:1},
{id:'2',age:2}
]

//<泛型>
let objs = reactive<Objs>([
{id:'1',age:1},
{id:'2',age:2}
])

defineProps

父组件传

子组件收:

1
2
3
4
5
6
7
8
9
10
import {defineProps,withDefault} from 'vue'//宏函数可以不引入

defineProps(['a','b'])

//限制类型
defineProps<{a:Objs}>()

withDefault(defineProps<{a?:Objs}>(),{
a:()=>[{默认值}]
})//'?'表可选项,

defineExpose

vue3子组件的数据只有暴露之后父组件才能获取到

1
defineExpose('a','b')//a,b为子组件数据名,暴露后父组件可以获得

组件生命周期

1. vue2组合式api

生命周期 钩子

  • 创建 (创建前beforeCreate(),创建完毕created())

  • 挂载 (挂载前beforeMounte(),挂载完毕mounted())

  • 更新 (更新前beforeUpdate(),更新完毕updated())

  • 销毁 (销毁前beforeDestory(),销毁完毕destoryed())

钩子:生命周期函数

2. vue3选项式api

生命周期

1
import {onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmounte,onUnmounted,onBeforeDestory,onDestoryed} from 'vue'
  • 创建:
    • setup。setup时自动return,setup时创建完毕。
  • 挂载:子组件挂载到父组件,父组件再挂载到
    • 挂载前:onBeforeMount( ( ) => { } )
    • 挂载完毕:onMounted( ( ) => { } )
  • 更新:
    • 更新前:onBeforeUpdate( ( ) => { } )
    • 更新完毕:onUpdated( ( ) => { } )
  • 卸载:v-show()
    • 卸载前:onBeforeUnmounte( ( ) => { } )
    • 卸载完毕:onUnmounted( ( ) => { } )

Hooks:useXxx.ts命名文件的为hooks

创建hooks文件夹,hooks->useName.ts

1
2
3
4
5
//useName.ts
export default function(){

return {name,age}
}
1
2
3
4
5
6
7
//调用useName.ts的vue文件
<script setup lang='ts'>
import useName from '@hooks/useName'

//调用
const {name,age} = useName()
</script>

组件通信

  • 父传子:

    • props:父组件直接传给子组件。父组件传<Son name='coffee' />,子组件接收defineProps(['name'])
  • 子传父:

    • props:通过传递函数给子组件,子组件将数据传给函数,父组件接收到数据
      • 父组件定义一个方法f(value)
      • 父组件将该方法传给子组件<Son :func='f' />
      • 子组件接收方法defineProps(['func'])
      • 子组件调用方法func(params)
    • 自定义事件:
      • 父组件定义方法getSon(value:string)
      • 父组件给子组件绑定自定义事件<Son @send-from-son='getSon' /> 自定义事件的事件名命名规范:a-b-c,v-on对大小写不敏感
      • 子组件接收自定义事件const emit = defineEmits(['sendFromSon'])
      • 子组件调用事件,<button @click='emit('sendFromSon',name)'></button>

v-bind

v-bind传对象,对象里面是key:value

v-bind='{a:'1',b:'2'}' ==传递==>a:'1',b:'2'


宏函数

define开头

钩子

use开头或者on开头


forin遍历

forin遍历根据索引遍历:

1
2
3
for(index in arr){
console.log(arr[index])
}

当索引值是key时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<Child1 ref='c1' />
<Child2 ref='c2' />
<button @click='getAllChilds($refs)'></button>

---

getAllChilds(refs:any){
console.log(refs)
/* key:value
refs:{
c1:Child1,
c2:Child2
}
*/

//同样地,遍历refs
for(key in refs){
console.log(refs[key])
}
}

插槽

  • 数据源在父组件,用默认插槽具名插槽给子组件插标签

  • 数据源在子组件,用作用域插槽。处理过程:子组件先给插槽传数据,插槽再将数据传给父组件中的子组件标签,从而实现数据访问

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //父组件
    <template>
    <Child>
    <template #insert="params"> //#insert="params"等价于v-slot:insert="params"
    <ul>
    <li v-for="games111 in params.games111" :key="games111.id">
    {{game111.name}}
    <li>
    </ul>
    <template>
    </Child>
    </template>
    1
    2
    3
    4
    5
    6
    //子组件,数据在子组件
    <template>
    <div>
    <slot name="insert" :games111="game" x="1" y="2"> //作用域插槽子组件传数据给插槽,数据包装成一个对象
    </div>
    </template>

路由传参

/api/category?a=1&b=2&c=3

?表示要开始传参了

&用来分隔参数

prams传参::路由中的表示占位符


全局api(vue2)转移到应用对象(vue3)

  1. vue2:Vue.use(),Vue.mount()
  2. vue3: const app = createApp(App) app.use(),app.mount()

防抖和节流及其底层实现

防抖

单位时间内,频繁触发事件,只执行最后一次。

触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间。应用场景:手机号、邮箱验证、搜索框输入。

实现:

利用lodash的 debounced(防抖动)函数。

_.debounce(func, [wait=0], [options=]) 等待时间:[wait=0]单位是多少毫秒,先等待再执行

1
btn.addEventListener('mousemove',_.debounce(fn,500))

其底层是用定时器setTimeout实现的,每次执行前先判断有没有定时器,有则清除定时器。

  1. 声明定时器变量
  2. 每次事件触发前判断有没有存在定时器,有则清除
  3. 开启定时器,存入到定时器变量
  4. 定时器里调用传入的函数
1
2
3
4
5
6
7
8
9
10
11
function debounce(fn,t){
let timer = null
return function(){
if(timer) clearTimeout(timer) //若存在定时器,则删除
timer = setTimeout(function(){
fn()
},t)
}
}

btn.addEventListener('mousemove',debounce(fn,500))

一直移动鼠标,停下来后,等500毫秒才执行。

节流

单位事件内(周期),频繁触发事件,只执行一次。

高频事件触发,但在n秒内只会执行一次。应用场景:window对象的页面尺寸缩放resize、滚动条滚动scroll事件,文字输入,mousemove,射击游戏的mousedown,keydown

例子,射击游戏频繁点击鼠标,为了节流,规定单位事件内点击的n次鼠标只执行一次操作。

实现:

利用lodash的_.throttle(func, [wait=0], [options=]),wait=n毫秒内,只会执行一次操作。

底层实现:

  1. 声明一个定时器变量

  2. 每次事件触发前先判断是否存在定时器,如果有则不开启新定时器

  3. 如果没有定时器则开启定时器,存入定时器变量

    - 定时器里面调用执行的函数
    
    - 定时器里面要把定时器清空
    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function throttle(fn,t){
let timer = null
return function(){
//如果不存在定时器,则创建新定时器
if(!timer){
timer = setTimeout(function(){
fn()
//清空定时器
timer = null
},t)
}
}
}

btn.addEventListener('mousemove',debounce(fn,500))

一直移动鼠标,单位时间500毫秒内无论执行多少次,只执行一次。

前端面试题

一、CSS

1.说一下CSS的盒模型。

盒模型的属性:

width、height、padding、border、margin(margin:上 右 下 左)

在HTML页面中的所有元素都可以看成是一个盒子
盒子的组成:内容content、内边距padding、边框border、外边距margin
盒模型的类型:

  1. 标准盒模型
    margin + border + padding + content,宽高不包含内边距和边框

  2. IE盒模型
    margin + content(border + padding),宽高包含内边距和边框

    控制盒模型的模式:box-sizing:content-box(默认值,标准盒模型)、box-sizing:border-box(IE盒模型);

2.CSS选择器的优先级?

​ CSS的特性:继承性、层叠性、优先级
​ 优先级:写CSS样式的时候,会给同一个元素添加多个样式,此时谁的权重高就显示谁的样式
​ 标签、类/伪类/属性、全局选择器、行内样式、id、!important
!important > 行内样式 > id > 类/伪类/属性 > 标签 > 全局选择器

3.隐藏元素的方法有哪些,特点是?

  1. display:none;元素在页面上消失,不占据空间
  2. opacity:0;​设置了元素的透明度为0,元素不可见,占据空间位置
  3. visibility:hidden;让元素消失,一种不可见的状态,占据空间位置
  4. 利用缩放:transform: scale(0);
  5. 利用定位:position:absolute; + z-index:-1;
  6. clip-path?

4.px和rem的区别是什么?

​ px是像素,显示器上给我们呈现画面的像素,每个像素的大小是一样,绝对单位长度;
​ rem,相对单位,相对于html根节点的font-size的值,直接给html节点的font-size:62.5%;
​ 1rem = 10px; (16px*62.5%=10px)

5.重绘重排有什么区别?

重排(回流):对DOM元素的大小、位置进行修改后,浏览器需要重新计算元素的几何属性
重绘:对DOM元素的样式进行修改,比如color,浏览器不需要重新计算元素的几何属性,重新绘制元素的新样式 。

页面发生变化时:重排和回流(重排)

  • 重绘:确定好元素的位置和大小后,页面元素样式发生变化,重新绘制已经显示的元素。

  • 回流(重排):确定好元素的位置和大小后,元素的大小位置发生变化,重新计算元素的位置和大小。盒模型在页面上的位置和大小

    回流比重绘的代价更高,因为回流会涉及到整个渲染树的重新计算布局

6.让一个元素水平垂直居中的方式有哪些?

1.定位+margin(需要知道子元素的宽高)

​ a.absolute + 负margin

1
2
3
<div class="wp">
<div class="box size">123123</div>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* 公共代码 */
.wp {
border: 1px solid red;
width: 300px;
height: 300px;
}

.box {
background: green;
}

.box.size{
width: 100px;
height: 100px;
}
/* 公共代码 */
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* 此处引用上面的公共代码 */
/* 此处引用上面的公共代码 */

/* 定位代码 */
.wp {
position: relative;
}
.box {
position: absolute;;
top: 50%;
left: 50%;
margin-left: -50px;
margin-top: -50px;
}

​ b.absolute + margin auto

这种方式也要求居中元素的宽高必须固定

1
2
3
<div class="wp">
<div class="box size">123123</div>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* 此处引用上面的公共代码 */
/* 此处引用上面的公共代码 */

/* 定位代码 */
.wp {
position: relative;
}
.box {
position: absolute;;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
}

这种方法兼容性也很好,缺点是需要知道子元素的宽高

​ c.absolute + calc

这种方式也要求居中元素的宽高必须固定,所以我们为box增加size类

1
2
3
4
<div class="wp">
<div class="box size">123123</div>
</div>

1
2
3
4
5
6
7
8
9
10
11
12
/* 此处引用上面的公共代码 */
/* 此处引用上面的公共代码 */

/* 定位代码 */
.wp {
position: relative;
}
.box {
position: absolute;;
top: calc(50% - 50px);
left: calc(50% - 50px);
}

2.定位+transform

还是绝对定位,但这个方法不需要子元素固定宽高,所以不再需要size类了

1
2
3
<div class="wp">
<div class="box">123123</div>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
/* 此处引用上面的公共代码 */
/* 此处引用上面的公共代码 */

/* 定位代码 */
.wp {
position: relative;
}
.box {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%); //将元素移动自身大小的百分之几,translateX,translateY
}

3.flex布局

1
2
3
<div class="wp">
<div class="box">123123</div>
</div>
1
2
3
4
5
.wp{
display: flex;
justify-content: center;
align-item: center;
}

4.grid布局

1
2
3
<div class="wp">
<div class="box">123123</div>
</div>
1
2
3
4
5
6
7
.wp {
display: grid;
}
.box {
align-self: center;
justify-self: center;
}

5.table布局

曾经table被用来做页面布局,现在没人这么做了,但table也能够实现水平垂直居中,但是会增加很多冗余代码

1
2
3
4
5
6
7
8
9
<table>
<tbody>
<tr>
<td class="wp">
<div class="box">123123</div>
</td>
</tr>
</tbody>
</table>

tabel单元格中的内容天然就是垂直居中的,只要添加一个水平居中属性就好了

1
2
3
4
5
6
.wp {
text-align: center;
}
.box {
display: inline-block;
}

6.css-table属性

css新增的table属性,可以让我们把普通元素,变为table元素的现实效果,通过这个特性也可以实现水平垂直居中

1
2
3
<div class="wp">
<div class="box">123123</div>
</div>

下面通过css属性,可以让div显示的和table一样

1
2
3
4
5
6
7
8
.wp {
display: table-cell;
text-align: center;
vertical-align: middle;
}
.box {
display: inline-block;
}

这种方法和table一样的原理,但却没有那么多冗余代码,兼容性也还不错

7.lineheight

利用行内元素居中属性也可以做到水平垂直居中

1
2
3
<div class="wp">
<div class="box">123123</div>
</div>

把box设置为行内元素,通过text-align就可以做到水平居中,但很多同学可能不知道通过通过vertical-align也可以在垂直方向做到居中,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* 此处引用上面的公共代码 */
/* 此处引用上面的公共代码 */

/* 定位代码 */
.wp {
line-height: 300px;
text-align: center;
font-size: 0px;
}
.box {
font-size: 16px;
display: inline-block;
vertical-align: middle;
line-height: initial;
text-align: left; /* 修正文字 */
}

7.CSS的哪些属性哪些可以继承?哪些不可以继承?

​ CSS的三大特性:继承、层叠、优先级
​ 子元素可以继承父类元素的样式
​ 1.字体的一些属性:font
​ 2.文本的一些属性:line-height
​ 3.元素的可见性:visibility:hidden
​ 4.表格布局的属性:border-spacing
​ 5.列表的属性:list-style
​ 6.页面样式属性:page
​ 7.声音的样式属性

8.有没有用过预处理器?

​ 预处理语言增加了变量、函数、混入等强大的功能
​ SASS LESS

9. 如何画一条0.5px线

1
2
3
4
.line{
border-top:1px solid black;
transform:scaleY(0.5);
}

10.定位

  1. 相对定位,相对于元素原来的位置,不会脱离文档流

  2. 绝对定位,相对于最近的已定位父元素,如果元素没有已定位的父元素,那么它的位置相对于<html>脱离文档流

  3. 固定定位,参考它的视口,脱离文档流

  4. 粘性定位,参考离它最近的一个拥有“滚动机制”的祖先元素,不会脱离文档流

11.消除浮动

浮动:在文本和其他元素直接可以利用浮动控制其位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<style>
img
{
float:right;
}
</style>
<body>
<p>在下面的段落中,我们添加了一个 <b>float:right</b> 的图片。导致图片将会浮动在段落的右边。</p>
<p>
<img src="logocss.gif" width="95" height="84" />
这是一些文本。这是一些文本。这是一些文本。
这是一些文本。这是一些文本。这是一些文本。
这是一些文本。这是一些文本。这是一些文本。
这是一些文本。这是一些文本。这是一些文本。
</p>
</body>
  1. clear:both clear属性指定元素两侧不能出现浮动元素,添加一个空的块级元素,给其clear:both属性。

    在浮动元素下加

  2. 添加伪元素 ,在父元素内的最后添加一个伪元素。

​ 使用伪元素clearfix

1
2
3
4
5
6
7
8
9
10
.clearfix::after{
content:'';
display:block;
clear:both;
}

<div class="clearfix">
<div class="box1">
...
</div>

display:inline/block/inline-block:设置为行内/内联,块级,行内块级元素

  1. 将父元素设置为flex布局。

  2. 为父元素设置overflow:hidden/auto/scoll

  3. 将父元素也设置为浮动。

12.自适应布局

界面布局的类型
界面的布局都是使用css写的,布局分为5种:固定布局,流式布局,弹性布局,自适应布局,响应式布局。

固定布局:就是静态布局,换句话说,就是界面是死的,一般是px单位。

流式布局:顾名思义,流动,像水一样,会根据界面的变化而变化,但是字体不会变化,一般是百分比的单位。

弹性布局:具有弹性,像橡皮筋一样,弹来弹去,可大可小,一般是rem,em单位

自适应布局:自适应,就是根据不同的设备,进行布局,一种屏幕一个布局,主要是使用媒体查询,主要用到尺寸值有百分比、auto等

响应式布局:最强大,一套代码,可以实现所有设备的大小的变化,包括手机端和电脑端,一般都是综合使用上面的。

13.网格布局

1
2
3
display:grid;
grid-template-columns:repeat(auto-fill,minmax(200px,1fr)); //repeat(3,1fr);3列
gap: 30px;

二、JavaScript

1.JS由哪三部分组成?

  1. ECMAScript:JS的核心内容,描述了语言的基础语法,比如var,for,数据类型(数组、字符串),
  2. 文档对象模型(DOM):DOM把整个HTML页面规划为元素构成的文档
  3. 浏览器对象模型(BOM):对浏览器窗口进行访问和操作

2.JS有哪些内置对象?

String Boolean Number Array Object Function Math Date RegExp...
Math
    abs() sqrt() max() min() random()
Date
    new Date() getYear() 
Array
String
    concat() length  slice() split()

3.操作数组的方法有哪些?

push() pop() sort() splice() unshift() shift() reverse() concat() split(),join()=>数组和字符串的转换
map() filter() every() some() reduce() isArray() findIndex() find()
哪些方法会改变原数组?
    push() pop() unshift() shift() sort() reverse() splice()

4.JS对数据类的检测方式有哪些?

  1. 基本数据类型:typeof

    1
    typeof 5 -> "number" , typeof null -> "object"
  2. 引用数据类型:instanceof

    1
    p instanceof Person -> true
  3. XXX.constructor – 均可

    1
    (5).constructor === Number -> true,"text".constructor === String,p.constructor === Person->true`
  4. Object.prototype.toString.call() -> “[object Object/Array/Function/String/Number/Date…]”

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    Object.prototype.toString.call(1) 						// '[object Number]'
    Object.prototype.toString.call('hello') // '[object String]'
    Object.prototype.toString.call(true) // '[object Boolean]'
    Object.prototype.toString.call() // '[object Undefined]'
    Object.prototype.toString.call(null) // '[object Null]'
    Object.prototype.toString.call(Symbol()) // '[object Symbol]'
    Object.prototype.toString.call(2172141653n) // '[object BigInt]'
    Object.prototype.toString.call([]) // '[object Array]'
    Object.prototype.toString.call({}) // '[object Object]'
    Object.prototype.toString.call(function(){}) // '[object Function]'
    Object.prototype.toString.call(new Date()) // '[object Date]'
    Object.prototype.toString.call(new Set()) // '[object Set]'
    Object.prototype.toString.call(new Map()) // '[object Map]'
    Object.prototype.toString.call(/^a/g) // '[object RegExp]'
    1. 针对数组:Array.isArray()

5.说一下闭包,闭包有什么特点?

什么是闭包?函数嵌套函数,内部函数被外部函数返回并保存下来时,就会产生闭包。
特点:可以重复利用变量,并且这个变量不会污染全局的一种机制;这个变量是一直保存再内存中,不会被垃圾回收机制回收
缺点:闭包较多的时候,会消耗内存,导致页面的性能下降,在IE浏览器中才会导致内存泄漏
使用场景:防抖,节流,函数嵌套函数避免全局污染的时候

6.前端的内存泄漏怎么理解?

JS里已经分配内存地址的对象,但是由于长时间没有释放或者没办法清除,造成长期占用内存的现象,会让内存资源大幅浪费,最终导致运行速度慢,甚至崩溃的情况。
垃圾回收机制
因素:未清空的定时器、过度的闭包、未销毁的事件监听器、循环引用、全局变量未清除、一些引用元素没有被清除。

7.事件委托是什么?

又叫事件代理,原理就是利用了事件冒泡的机制来实现,也就是说把子元素的事件绑定到了父元素的身上。
如果子元素阻止了事件冒泡,那么委托也就不成立。阻止事件冒泡:event.stopPropagation()。
addEventListener('click',函数名,true/false) 默认是false(事件冒泡),true(事件捕获)
好处:提高性能,减少事件的绑定,也就减少了内存的占用。

8.基本数据类型和引用数据类型的区别?

基本数据类型:String Number Boolean undefined null Symbol
    基本数据类型保存在栈内存当中,保存的就是一个具体的值
引用数据类型(复杂数据类型):Object Function Array
    保存在堆内存当中,声明一个引用类型的变量,它保存的是引用类型数据的地址
    假如声明两个引用类型同时指向了一个地址的时候,修改其中一个那么另外一个也会改变

9.说一下原型链。

1
2
3
4
5
6
7
原型链是一种搜索和继承机制,当查找对象的某个属性时,首先先从对象身上找,如果没有则从对象的原型上找,如果还是没有,就到Object.prototype的身上找,如果还是没有则说明该对象没有这个属性。

原型就是一个普通对象,它是为构造函数的实例共享属性和方法;所有实例中引用的原型都是同一个对象。
使用`Object.prototype.属性名/方法名`可以把方法挂在原型上,内存值保存一份。
__proto__可以理解为指针,实例对象中的属性,__proto__指向了构造函数的原型对象(prototype)。

原型链是 JavaScript 中用于实现继承和属性查找的一种机制。每个对象都有一个原型对象(prototype),通过原型对象可以实现属性和方法的继承。当访问一个对象的属性或方法时,如果对象本身不存在这个属性或方法,JavaScript 引擎会沿着原型链向上查找,直到找到相应的属性或方法或者到达原型链的顶端(Object.prototype)为止。

10.new操作符具体做了什么?

1.先创建一个空对象
2.把空对象和构造函数通过原型链进行链接
3.把构造函数的this绑定到新的空对象身上
4.根据构建函数返回的类型判断,如果是值类型,则返回对象,如果是引用类型,就要返回这个引用类型

***
1. 创建一个空对象。
2. 将空对象的原型链接(__proto__)到构造函数的原型对象。这样新对象就可以访问构造函数的原型对象中定义的属性和方法。
3. 将构造函数的作用域赋给空对象。这样新对象就可以通过this关键字来引用构造函数中的属性和方法。
4. 执行构造函数中的代码。构造函数中的代码将用于初始化新对象的属性。
5. 如果构造函数中没有返回其他对象,那么new操作符将返回新创建的对象实例。否则,返回构造函数中返回的对象。

11.JS是如何实现继承的?

1.原型链继承
2.借用构造函数继承
3.组合式继承
4.ES6的class类继承

12.JS的设计原理是什么?

JS引擎 运行上下文 调用栈 事件循环 回调

13.JS中关于this指向的问题

1. 全局对象中的this指向
指向的是window
2. 全局作用域或者普通函数中的this
指向全局window
3. this永远指向最后调用它的那个对象
在不是箭头函数的情况下
4. new 关键词改变了this的指向
5. apply,call,bind
可以改变this指向,不是箭头函数
6. 箭头函数中的this
它的指向在定义的时候就已经确定了
箭头函数它没有this,看外层是否有函数,有就是外层函数的this,没有就是window
7. 匿名函数中的this
永远指向了window,匿名函数的执行环境具有全局性,因此this指向window

14.script标签里的async和defer有什么区别?

当没有async和defer这两个属性的时候,浏览器会立刻加载并执行指定的脚本
async:
加载和渲染后面元素的过程将和script的加载和执行并行进行(异步)
defer:
加载和渲染后面元素的过程将和script的加载并行进行(异步),但是它的执行事件要等所有元素解析完成之后才会执行

15.setTimeout最小执行时间是多少?

HTML5规定的内容:

  • setTimeout最小执行时间是4ms
  • setInterval最小执行时间是10ms

16.ES6和ES5有什么区别?

1
2
3
JS的组成:ECMAScript BOM  DOM
ES5:ECMAScript5,2009年ECMAScript的第五次修订,ECMAScript2009
ES6:ECMAScript6,2015年ECMAScript的第六次修订,ECMAScript2015,是JS的下一个版本标准

17.ES6的新特性有哪些?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
1.  新增块级作用域(let,const)
不存在变量提升
存在暂时性死区的问题
块级作用域的内容
不能在同一个作用域内重复声明
2. 新增了定义类的语法糖(class)
3. 新增了一种基本数据类型(symbol)
4. 新增了解构赋值
从数组或者对象中取值,然后给变量赋值
5. 新增了函数参数的默认值
6. 给数组新增了API
7. 对象和数组新增了扩展运算符
8. Promise
解决回调地狱的问题。
自身有all,reject,resolve,race方法
原型上有then,catch
把异步操作队列化
三种状态:`pending初始状态,fulfilled操作成功,rejected操作失败`
状态:pending -> fulfilled;pending -> rejected 一旦发生,状态就会凝固,不会再变
async await
同步代码做异步的操作,两者必须搭配使用
async表明函数内有异步操作,调用函数会返回promise
await是组成async的表达式,结果是取决于它等待的内容,如果是promise那就是promise的结果,如果是普通函数就进行链式调用
await后的promise如果是reject状态,那么整个async函数都会中断,后面的代码不执行

9. 新增了模块化(import,export)
10. 新增了set和map数据结构
set就是不重复
map的key的类型不受限制
11. 新增了generator
12. 新增了箭头函数
不能作为构造函数使用,不能用new
箭头函数就没有原型
箭头函数没有arguments
箭头函数不能用call,apply,bind去改变this的执行
this指向外层第一个函数的this

18.call,aply,bind三者有什么区别?

都是改变this指向和函数的调用,call和apply的功能类似,只是传参的方法不同
call方法传的是一个参数列表
apply传递的是一个数组
bind传参后不会立刻执行,会返回一个改变了this指向的函数,这个函数还是可以传参的,bind()()
call方法的性能要比apply好一些,所以call用的更多一点

19.用递归的时候有没有遇到什么问题?

如果一个函数内可以调用函数本身,那么这个就是递归函数
函数内部调用自己
特别注意:写递归必须要有退出条件return

20.如何实现一个深拷贝?

深拷贝就是完全拷贝一份新的对象,会在堆内存中开辟新的空间,拷贝的对象被修改后,原对象不受影响
主要针对的是引用数据类型

  1. lodash的_.deepClone实现

  2. JSON.parse(JSON.stringify())

  3. Object.assign(obj1,obj2),将obj2拷贝到obj1

  4. 利用递归函数实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    function deepClone(obj) {
    if (obj === null) return obj;
    if (obj instanceof Date) return new Date(obj);
    if (obj instanceof RegExp) return new RegExp(obj);
    if (typeof obj !== "object") return obj;
    // 创建一个和obj一样的新对象cloneObj,obj.constructor()获取obj的构造函数
    let cloneObj = new obj.constructor();
    for (let key in obj) {
    // 用于检查对象 obj 是否具有名为 key 的属性。如果对象 obj 具有名为 key 的属性,并且该属性是对象自身的属性,而不是继承自对象的原型链,那么这个方法返回 true,否则返回 false。
    if (obj.hasOwnProperty(key)) {
    // 实现一个递归拷贝
    cloneObj[key] = deepClone(obj[key]);
    }
    }
    return cloneObj;
    }

21.说一下事件循环。(查)

在事件循环中,当主线程执行完当前的同步任务后,会检查事件队列中是否有待处理的事件。如果有,主线程会取出事件并执行对应的回调函数。这个循环的过程被称为事件循环(Event Loop),它由主线程任务队列两部分组成。主线程负责执行同步任务,而异步任务则通过任务队列进行处理。

事件循环流程如下:

  1. 主线程读取JavaScript代码,形成相应的堆和执行栈。
  2. 当主线程遇到异步任务时,将其委托给对应的异步进程(如Web API)处理。
  3. 异步任务完成后,将相应的回调函数推入任务队列。
  4. 主线程执行完同步任务后,检查任务队列,如果有任务,则按照先进先出的原则将任务推入主线程执行。
  5. 重复执行以上步骤,形成事件循环。

JS是一个单线程的脚本语言
主线程 执行栈 任务队列 宏任务 微任务
主线程先执行同步任务,然后才去执行任务队列里的任务,如果在执行宏任务之前有微任务,那么要先执行微任务。全部执行完之后等待主线程的调用,调用完之后再去任务队列中查看是否有异步任务,这样一个循环往复的过程就是事件循环!

主线程同步任务>任务队列(异步)>微任务>宏任务

22.ajax是什么?怎么实现的?

创建交互式网页应用的网页开发技术
在不重新加载整个网页的前提下,与服务器交换数据并更新部分内容
通过XmlHttpRequest对象向服务器发送异步请求,然后从服务器拿到数据,最后通过JS操作DOM更新页面
1.创建XmlHttpRequest对象 xhr
2.通过xhr对象里的open()方法和服务器建立连接
3.构建请求所需的数据,并通过xhr对象的send()发送给服务器
4.通过xhr对象的onreadystatechanges事件监听服务器和你的通信状态
5.接收并处理服务器响应的数据结果
6.把处理的数据更新到HTML页面上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function loadData() {
// 创建 XMLHttpRequest 对象
const xhr = new XMLHttpRequest();

// 配置请求,指定请求方法和URL
xhr.open("GET", "https://jsonplaceholder.typicode.com/posts/1", true);

// 定义请求完成后的回调函数
xhr.onreadystatechange = () => {
// 请求完成且响应状态为 200 表示成功
if (xhr.readyState === 4 && xhr.status === 200) {
// 解析服务器响应的 JSON 数据
const data = JSON.parse(xhr.responseText);

// 更新页面上的内容
document.getElementById("result").innerHTML = "Title: " + data.title;
}
};

// 发送请求
xhr.send();
}

readyState:

0:请求未初始化
1:服务器连接已建立
2:请求已接收
3:请求处理中
4:请求已完成,且响应已就绪

status:

200:”OK”
404:未找到页面

返回结果:

response属性来获取从服务器返回的响应数据。

responseText属性用于以字符串形式返回响应的文本数据

responseXML属性(如果响应的内容是XML格式)则返回响应的XML文档。

23.get和post有什么区别?

1.get一般是获取数据,post一般是提交数据
2.get参数会放在url/query上,post是放在body中
3.get请求刷新服务器或退回是没有影响的,post请求退回时会重新提交数据
4.get请求时会被缓存,post请求不会被缓存
5.get请求会被保存在浏览器历史记录中,post不会
6.get请求只能进行url编码,post请求支持多种编码方式(json,xml,form-data表单数据)

post请求为什么有2次?

第一次发送请求头响应100,再次响应返回200,成功

先发起一个OPTIONS请求,然后再发送实际的 POST 请求。

24. promise的内部原理是什么?它的优缺点是什么?

Promise对象,封装了一个异步操作并且还可以获取成功或失败的结果
Promise主要就是解决回调地狱的问题,之前如果异步任务比较多,同时他们之间有相互依赖的关系,就只能使用回调函数处理,这样就容易形成回调地狱,代码的可读性差,可维护性也很差
有三种状态:pending初始状态 fulfilled成功状态 rejected失败状态
状态改变只会有两种情况,
pending -> fulfilled; pending -> rejected 一旦发生,状态就会凝固,不会再变
首先就是我们无法取消promise,一旦创建它就会立即执行,不能中途取消
如果不设置回调,promise内部抛出的测u哦呜就无法反馈到外面
若当前处于pending状态时,无法得知目前在哪个阶段。
原理:
构造一个Promise实例,实例需要传递函数的参数,这个函数有两个形参,分别都是函数类型,一个是resolve一个是reject
promise上还有then方法,这个方法就是来指定状态改变时的确定操作,resolve是执行第一个函数,reject是执行第二个函数

25.promise和async await的区别是什么?

1.都是处理异步请求的方式
2.promise是ES6,async await 是ES7的语法
3.async await是基于promise实现的,他和promise都是非阻塞性
优缺点:
1.promise是返回对象我们要用then,catch方法去处理和捕获异常,并且书写方式是链式,容易造成代码重叠,不好维护,async await 是通过try catch进行捕获异常
2.async await最大的优点就是能让代码看起来像同步一样,只要遇到await就会立刻返回结果,然后再执行后面的操作
promise.then()的方式返回,会出现请求还没返回,就执行了后面的操作

26.浏览器的存储方式有哪些?

1.cookies
H5标准前的本地存储方式
兼容性好,请求头自带cookie
存储量小,资源浪费,使用麻烦(封装)
2.localstorage
H5加入的以键值对为标准的方式
操作方便,永久存储,兼容性较好
保存值的类型被限定,浏览器在隐私模式下不可读取,不能被爬虫
3.sessionstorage
当前页面关闭后就会立刻清理,会话级别的存储方式
4.indexedDB
H5标准的存储方式,他是以键值对进行存储,可以快速读取,适合WEB场景

27.token存在sessionstorage还是loaclstorage?

token:验证身份的令牌,一般就是用户通过账号密码登录后,服务端把这些凭证通过加密等一系列操作后得到的字符串
1.存loaclstorage里,后期每次请求接口都需要把它当作一个字段传给后台
2.存cookie中,会自动发送,缺点就是不能跨域
如果存在localstorage中,容易被XSS攻击,但是如果做好了对应的措施,那么是利大于弊
如果存在cookie中会有CSRF攻击

28.token的登录流程。

1.客户端用账号密码请求登录
2.服务端收到请求后,需要去验证账号密码
3.验证成功之后,服务端会签发一个token,把这个token发送给客户端
4.客户端收到token后保存起来,可以放在cookie也可以是localstorage
5.客户端每次向服务端发送请求资源的时候,都需要携带这个token
6.服务端收到请求,接着去验证客户端里的token,验证成功才会返回客户端请求的数据

29.页面渲染的过程是怎样的?

  1. DNS解析

  2. 建立TCP连接

  3. 客户端发送HTTP请求

  4. 服务器处理请求

  5. 渲染页面

    • 浏览器会获取HTML和CSS的资源

    • 然后把HTML解析成DOM树

    • 再把CSS解析成CSSOM

    • 把DOM和CSSOM合并为渲染树

    • 布局

    • 渲染树的每个节点渲染到屏幕上(绘制)

    • 页面发生变化时:重排和回流(重排)

      • 重绘:页面元素样式发生变化,重新绘制已经显示的元素,(计算好盒模型的位置、大小之后)
      • 回流(重排):页面元素大小位置,页面重新计算元素的位置和大小。盒模型在页面上的位置和大小
        • 回流比重绘的代价更高,因为回流会涉及到整个渲染树的重新计算布局
  6. 断开TCP连接

30.DOM树和渲染树有什么区别?

DOM树是和HTML标签一一对应的,包括head和隐藏元素
渲染树是不包含head和隐藏元素的,是实际在页面上显示的

31.精灵图和base64的区别是什么?

精灵图:把多张小图整合到一张大图上,利用定位的一些属性把小图显示在页面上,当访问页面可以减少请求,提高加载速度
base64:传输8Bit字节代码的编码方式,把原本二进制形式转为64个字符的单位,最后组成字符串
base64是会和html css一起下载到浏览器中,减少请求,减少跨域问题,但是一些低版本不支持,若base64体积比原图片大,不利于css的加载。

32.svg格式了解多少?

可缩放矢量图,基于XML语法格式的图像格式,其他图像是基于像素的,SVG是属于对图像形状的描述,本质是文本文件,体积小,并且不管放大多少倍都不会失真
1.SVG可直接插入页面中,成为DOM一部分,然后用JS或CSS进行操作

    <svg></svg>

​ 2.SVG可作为文件被引入

​ 3.SVG可以转为base64引入页面

33.了解过JWT吗?

​ JSON Web Token(JWT是token的一种)通过JSON形式作为在web应用中的令牌,可以在各方之间安全的把信息作为JSON对象传输
​ 信息传输、授权
​ JWT的认证流程
​ 1.前端把账号密码发送给后端的接口
​ 2.后端核对账号密码成功后,把用户id等其他信息作为JWT负载,把它和头部分别进行base64编码拼接后签名,形成一个JWT(token)。
​ 3.前端每次请求时都会把JWT放在HTTP请求头的Authorization字段内
​ 4.后端检查是否存在,如果存在就验证JWT的有效性(签名是否正确,token是否过期)
​ 5.验证通过后后端使用JWT中包含的用户信息进行其他的操作,并返回对应结果
​ 简洁、包含性、因为Token是JSON加密的形式保存在客户端,所以JWT是跨语言的,原则上是任何web形式都支持。

34.npm的底层环境是什么?

​ ‌npm的底层环境是JavaScript运行时环境Node.js。‌ npm是Node.js的默认包管理器,用于管理Node.js项目的依赖关系。它允许开发者安装、更新和卸载Node.js包,从而简化了JavaScript库和框架的管理过程。npm的底层环境依赖于Node.js提供的执行环境,这使得npm能够执行包安装、测试、打包等一系列操作。

​ node package manager,nodejs的包管理和分发工具,已经成为分发node模块的标准,是JS的运行环境。
​ npm的组成:网站、注册表(registry)、命令行工具(CLI)

35.HTTP协议规定的协议头和请求头有什么?

​ 1.请求头信息:
​ 1.Accept:指定客户端能够接受的媒体类型,如“text/html”、“application/json”等。
​ 2.Host:指定请求的服务器的域名和端口号
​ 3.Referer:指明请求来源的页面URL,用于服务器分析请求来源。浏览器告诉服务器我是从哪里来的(防盗链)
​ 4.User-Agent:用户设备信息、浏览器类型、版本信息
​ 5.Date:客户端请求时间
​ 6.Connection:指定连接方式,如“keep-alive”表示持久连接,或“close”表示关闭连接。
​ 7.Cookie:存储客户端的会话信息,如登录状态等。
​ 8.X-Request-With:请求方式

​ 9.Authorization:用于身份验证,token

​ 10.Content-Type:指明请求体的媒体类型,如“application/x-www-formurlencoded”、“multipart/form-data”等。

​ 2.响应头信息:

​ access-control-allow-origin:指定哪些网站可以跨域源资源共享

​ Content-Type:响应体的媒体类型

​ Connection:指定连接方式,如“keep-alive”表示持久连接,或“close”表示关闭连接。

​ Date:服务端响应数据时间

​ server:服务器类型信息

​ Location:指明用于重定向的 URI

​ Refresh:控制定时重定向

https:

1.客户端发起https请求
2.服务器配置证书并发送给客户端
3.客户端收到证书后进行验证,如果验证通过就生成对称密钥,并用证书里的公钥加密这个对称密钥
4.客户端将加密后的对称密钥发送给服务器
5.服务器接收到客户端发来的对称密钥后,用自己之前的私钥进行非对称解密,解密之后得到客户端的密钥,然
后用客户端的对称密钥对返回的数据进行加密,这样传输的数据都是密文。
6.服务器将加密后的密文发送给客户端
7.客户端收到密文后用自己的对称密钥进行解密

36.说一下浏览器的缓存策略。(查)

​ 强缓存(本地缓存)、协商缓存(弱缓存)

强缓存:当访问URL的时候,不会向服务器发送请求,直接从缓存中读取资源,但是会返回200的状态码。

协商缓存:强缓存失效后,浏览器携带缓存标识向服务器发送请求,由服务器根据缓存标识来决定是否使用缓存的过程。

​ 强缓:不发起请求,直接使用缓存里的内容,浏览器把JS,CSS,image等存到内存中,下次用户访问直接从内存中取,提高性能
​ 协缓:需要向后台发请求,通过判断(需要询问服务器缓存是否过期)来决定是否使用协商缓存,如果请求内容没有变化,则返回304,浏览器就用缓存里的内容
​ 强缓存的触发:
​ HTTP1.0:时间戳响应标头
​ HTTP1.1:Cache-Control响应标头
​ 协商缓存触发:
​ HTTP1.0:请求头:if-modified-since 响应头:last-modified
​ HTTP1.1:请求头:if-none-match 响应头:Etag

37.说一下什么是“同源策略”?(查)

​ http:// www. aaa.com:8080/index/vue.js
​ 协议 子域名 主域名 端口号 资源
​ 同源策略是浏览器的核心,如果没有这个策略就会遭受网络攻击
​ 主要指的就是协议+域名+端口号三者一致,若其中一个不一样则不是同源,会产生跨域
​ 三个允许跨域加载资源的标签:img link script
​ 跨域是可以发送请求,后端也会正常返回结果,只不过这个结果被浏览器拦截了!
​ JSONP
​ CORS
​ websocket
​ 反向代理

38.防抖和节流是什么?

​ 都是应对页面中频繁触发事件的优化方案

  • 防抖:触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间。应用场景:手机号、邮箱验证、搜索框输入。
  • 节流:单位事件内(周期),频繁触发事件,只执行一次。应用场景:scroll事件

39.解释一下什么是json?

​ JSON是一种纯字符串形式的数据,它本身不提供任何方法,适合在网络中进行传输
​ JSON数据存储在.json文件中,也可以把JSON数据以字符串的形式保存在数据库、Cookise中
​ JS提供了JSON.parse() JSON.stringify()
​ 什么时候使用json:定义接口;序列化;生成token;配置文件package.json

40.当数据没有请求过来的时候,该怎么做?

​ 可以在渲染数据的地方给一些默认的值
​ if判断语句

41.有没有做过无感登录?***

1.解决方案
无感登录实际上就是无感刷新 token。

1.在响应器中拦截,判断 token 返回过期后,调用刷新 token 的接口。

2.后端返回过期时间,前端判断 token 的过期时间,去调用刷新 token 的接口。

3.写一个定时器,定时刷新 token。

我们常用的第一种方法实现无感登录。

2.实现流程
1.登录流程

    用户输入用户名和密码进行登录。

    后端验证用户信息,如果验证通过,返回一个 token 和 refresh_token。
    前端将 token 和 refresh_token 保存在本地(例如 localStorage)。

2.拦截响应

    在前端,我们可以在响应拦截器中处理返回的响应。
    如果返回的状态码为401(未授权),说明 token 已经过期。

3.刷新 token

    使用本地保存的 refresh_token 来请求新的 token。
    请求时可以携带当前 token 的过期时间,以便后端可以更精确地控制新 token 的过期时间。

4.替换 token

    一旦新的 token 返回,前端将其保存,并替换本地旧的 token。

5.处理请求

    前端将新的 token 放入未完成的请求中,并重新发送请求。

6.错误处理

    如果 refresh_token 也过期了,那么需要重新登录。清除所有过期的 token,然后重新执行登录流程。

三种方法:

  1. 在响应器中拦截到401,判断token返回过期后,调用刷新token的接口

  2. 后端返回过期时间,前端判断token的过期时间,去调用刷新token的接口

  3. 前端写定时器,定时刷新token接口

    方法1流程:

    1. 登录成功后保存token 和 refresh_token
    2. 在响应拦截器中对401状态码引入刷新token的api方法调用
    3. 替换保存本地新的token
    4. 把错误对象里的token替换
    5. 再次发送未完成的请求
    6. 如果refresh_token过期了,判断是否过期,过期了就清除所有token重新登录

42.大文件上传是怎么做的?

分片上传:
​ 1.把需要上传的文件切割成相同大小的数据块;

1
2
3
4
5
6
7
// 切片函数,返回一个包含所有切片的数组result,chunkSize为切片大小
function createChunks(file,chunkSize){
const result = [];
forlet i=0;i < file.size;i += chunkSize)
result.push(file.slice(i,i + chunkSize));
return result;
}

​ 2.把各个数据块上传,客户端记录切片的上传状态,只需要上传未成功的切片;
​ 3.发送完成后,服务端会判断数据上传的完整性,如果完整,那么就会把数据库合并成原始文件;
断点续传:
​ 服务端返回,从哪里开始

​ 1. 客户端上传文件时,记录已上传的切片位置

​ 2. 下次上传时,根据记录的位置,继续上传

43.var const let区别

  1. const let 是块级作用域,var没有块级作用域,var只有函数和全局作用域
  2. var存在变量提升,const和let不存在变量声明提升。变量声明提升:变量提升是将变量声明提升到它所在作用域的最开始的部分,先调用再声明定义。
  3. const let是ES6提出的,var是ES5
  4. const 声明的是常量,常量不能被修改,let和var声明的是变量,可以被修改
  5. const在声明时必须赋值,而let和var不需要
  6. let 和const不能重复声明同一个值:如 let a=1 ; let a =2 这样是不被允许的,但var可以,最后一个var声明的值会覆盖之前的 如:var b =1 ;var b =2 console.log(b) 结果为2

44.==和===的区别

  • ‘’==’’:类型不同,会尝试进行类型转换,将它们转换为同一类型后再进行比较值。

  • ‘’===’’:比较类型和值是否完全相同

45.定时器

1
2
3
4
5
6
7
8
9
10
11
for(var i = 1;i<5;i++){

setTimeOut(function(){

console.log(i)

},i*1000)

}

//结果:5 5 5 5

46.数组去重

  1. 用Set去重

    1
    2
    const readImgs = ref([1,2,3,4,1,2,3,4])
    readImgs.value = [...new Set(readImgs.value)]
1
2
3
4
5
6
7
8
9
function unique (arr) {
//Array.from()可以从类数组对象或可迭代对象(如字符串、Set、Map、NodeList等)创建一个新的数组实例
return Array.from(new Set(arr))
}

var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];

console.log(unique(arr))
//[1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {}, {}]
  1. splice 去重,不需要排序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function unique(arr){            
for(var i=0; i<arr.length; i++){ //第i个和第j=i+1个后面逐一比较,若相等则去掉第j个
for(var j=i+1; j<arr.length; j++){
if(arr[i]===arr[j]){
arr.splice(j,1);
j--; //删除第二个后j--,然后循环结束又j++,相当于原位重新判断新的'第二个'
}
}
}
return arr;
}

var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];

console.log(unique(arr))
//[1, "true", 15, false, undefined, NaN, NaN, "NaN", "a", {…}, {…}] //NaN和{}没有去重,两个null直接消失了
  1. sort(),第一个必不重复,第二个开始循环,和前面一个进行比较,如果不同则加入当前元素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function unique(arr) {
if (!Array.isArray(arr)) {
console.log('type error!')
return;
}
arr = arr.sort() //排序
var arrry= [arr[0]]; //第一个肯定不会重复
for (var i = 1; i < arr.length; i++) { //第二个开始,和前面一个进行比较,如果不同则加入当前元素
if (arr[i] !== arr[i-1]) {
arrry.push(arr[i]);
}
}
return arrry;
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
// [0, 1, 15, "NaN", NaN, NaN, {…}, {…}, "a", false, null, true, "true", undefined] //NaN、{}没有去重
  1. 利用indexOf()

arr.indexOf(item) 用于查找数组中指定元素第一次出现的位置索引的方法。如果未找到,则返回 -1

arr.indexOf(item, 0) 用于在数组 arr 中从索引 0 开始查找元素 item 第一次出现的位置。如果未找到,则返回 `-1

​ 创建一个新数组存,根据indexOf新数组没有就加入

1
2
3
4
5
6
const handleRemoveRepeat = (arr) => {
let repeatArr = [];
for (let i = 0,len = arr.length ; i < len; i++)
if (repeatArr.indexOf(arr[i]) === -1) repeatArr.push(arr[i])
return repeatArr;
}
  1. 利用 filter

​ 第一次出现就返回。当前元素在原始数组中的第一个索引==当前索引值,说明第一次出现。

1
2
3
4
5
6
7
8
9
const unique = (arr) => {
arr.filter((item, index, arr) => {
//条件:当前元素在原始数组中的第一个索引==当前索引值
arr.indexOf(item, 0) === index;
})
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
//[1, "true", true, 15, false, undefined, null, "NaN", 0, "a", {…}, {…}]
6. 利用includes

​ 创建一个新数组存,如果新数组没有则加入

1
2
3
4
5
6
const handleRemoveRepeat = (arr) => {
let repeatArr = [];
for (let i = 0,len = arr.length ; i < len; i++)
if (!repeatArr.includes(arr[i])) repeatArr.push(arr[i])
return repeatArr;
}

47.怎么判断对象是否为空

  1. 使用Object.keys()方法:这个方法返回一个数组,包含对象自身的所有可枚举属性。通过判断返回的数组长度是否为0,可以确定对象是否为空。例如,Object.keys(obj).length === 0 如果为真,则对象为空。

  2. 使用JSON.stringify()方法:将对象转换为JSON字符串,然后比较转换后的字符串是否等于'{}'。这种方法简单易行,但需要注意的是,JSON.stringify()只能序列化对象的可枚举自有属性,如果有不可枚举或继承属性,结果可能不准确。JSON.stringify(obj) === '{}'

  3. 使用obj.hasOwnProperty()方法:该方法用来检查一个对象是否拥有特定的自身属性,而不是从原型链上继承的属性。通过检查对象是否具有特定的属性来判断其是否为空。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    function isEmpty(obj) {
    for (var prop in obj) {
    if (obj.hasOwnProperty(prop)) {
    return false;
    }
    }
    return true;
    }

    // 示例使用:
    var obj1 = {};
    console.log(isEmpty(obj1)); // 输出:true,因为 obj1 是空对象

    var obj2 = { key: 'value' };
    console.log(isEmpty(obj2)); // 输出:false,因为 obj2 有自身属性
  4. 使用Object.getOwnPropertyNames()方法:获取对象中的属性名并存到一个数组中(同1),通过判断数组的长度来判断对象是否为空。这个方法可以检测到不可枚举的属性(原型上的属性)。Object.getOwnPropertyNames(obj).length === 0

  5. 使用for..in循环:通过遍历对象的属性,如果在循环体内没有找到任何属性,则可以认为对象为空。这种方法可以通过定义一个函数来实现,如果在循环中找到属性则返回false,否则返回true表示对象为空。

48.https工作流程

  1. 客户端发起https请求

  2. 服务器配置证书并发送给客户端

  3. 客户端收到证书后进行验证,如果验证通过就生成对称密钥,并用证书里的公钥加密这个对称密钥

  4. 客户端将加密后的对称密钥发送给服务器

  5. 服务器接收到客户端发来的对称密钥后,用自己之前的私钥进行非对称解密,解密之后得到客户端的密钥,然

    后用客户端的对称密钥对返回的数据进行加密,这样传输的数据都是密文。

  6. 服务器将加密后的密文发送给客户端

  7. 客户端收到密文后用自己的对称密钥进行解密

服务器证书→客户端生成对称密钥并用服务器证书的公钥加密→发送给服务器→服务器的私钥进行非对称解密得到客户端的密钥→加密数据→发送给客户端→客户端进行对称解密

三、HTML5CSS3

1.语义化的理解。

​ 在写HTML页面结构时所用的标签有意义
​ 头部用head 主体用main 底部用foot…
​ 怎么判断页面是否语义化了?
​ 把CSS去掉,如果能够清晰的看出来页面结构,显示内容较为正常
为什么要选择语义化?
​ 1.让HTML结构更加清晰明了
​ 2.方便团队协作,利于开发
​ 3.有利于爬虫和SEO,有利于搜索引擎检索和抓取
​ 4.能够让浏览器更好的去解析代码
​ 5.给用户带来良好的体验

2.H5C3有哪些新特性?

​ H5的新特性:
​ 1.语义化的标签
​ 2.新增音频视频
​ 3.画布canvas
​ 4.数据存储localstorage sessionstorage
​ 5.增加了表单控件 email url search…
​ 6.拖拽释放API
​ CSS3的新特性:
​ 1.新增选择器:属性选择器、伪类选择器、伪元素选择器
​ 2.增加了媒体查询
​ 3.文字阴影
​ 4.边框
​ 5.盒子模型box-sizing
​ 6.渐变
​ 7.过度
​ 8.自定义动画
​ 9.背景的属性
​ 10.2D和3D

3.rem是如何做适配的?

​ rem是相对长度,相对于根元素(html)的font-size属性来计算大小,通常来做移动端的适配
​ rem是根据根元素font-size计算值的倍数
​ 比如html上的font-size:16px,给div设置宽为1.5rem,1.2rem = 16px*1.2 = 19.2px.

4.解决了哪些移动端的兼容问题?

​ 1.当设置样式overflow:scroll/auto时,IOS上的滑动会卡顿
​ -webkit-overflow-scrolling:touch;
​ 2.在安卓环境下placeholder文字设置行高时会偏上
​ input有placeholder属性的时候不要设置行高
​ 3.移动端字体小于12px时异常显示
​ 应该先把在整体放大一倍,然后再用transform进行缩小
​ 4.ios下input按钮设置了disabled属性为true显示异常
​ input[typy=button]{
​ opcity:1
​ }
​ 5.安卓手机下取消语音输入按钮
​ input::-webkit-input-speech-button{
​ display:none
​ }
​ 6.IOS下取消input输入框在输入引文首字母默认大写

​ 7.禁用IOS和安卓用户选中文字
​ 添加全局CSS样式:-webkit-user-select:none
​ 8.禁止IOS弹出各种窗口
​ -webkit-touch-callout:none
​ 9.禁止IOS识别长串数字为电话
​ 添加meta属性

四、Vue

1.v-if和v-show的区别?

​ 都可以控制元素的显示和隐藏
​ 1.控制手段:v-show时控制元素的display值来让元素显示和隐藏,DOM元素依旧还在;v-if显示隐藏时把DOM元素整个添加和删除;
​ 2.编译过程:v-if有一个局部编译/卸载的过程,切换这个过程中会适当的销毁和重建内部的事件监听和子组件;v-show只是简单的css切换;
​ 3.编译条件:v-if才是真正的条件渲染,它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建;v-show从false变成true的时候不会触发组件的生命周期,v-if会触发生命周期;
​ 4.v-if有更高的切换消耗;v-show有更高的初始渲染消耗;

如果需要在条件切换频繁的情况下,可以使用v-show来避免频繁的创建和销毁组件或元素,提高性能。

如果需要在条件切换较少的情况下,可以使用v-if来在条件为假时减少不必要的渲染,节省内存。

2.如何理解MVVM的?

​ 是Model-View-ViewModel的缩写。前端开发的架构模式
​ M:模型,对应的就是data的数据
​ V:视图,用户界面,DOM
​ VM:视图模型:Vue的实例对象,连接View和Model的桥梁
​ 核心是提供对View和ViewModel的双向数据绑定,当数据改变的时候,ViewModel能监听到数据的变化,自动更新视图,当用户操作视图的时候,ViewModel也可以监听到视图的变化,然后通知数据进行改动,这就实现了双向数据绑定
​ ViewModel通过双向绑定把View和Model连接起来,他们之间的同步是自动的,不需要认为干涉,所以我们只需要关注业务逻辑即可,不需要操作DOM,同时也不需要关注数据的状态问题,因为她是由MVVM统一管理

3.v-for中的key值的作用是什么?

​ key属性是DOM元素的唯一标识
​ 作用:
​ 1.提高虚拟DOM的更新
​ 2.若不设置key,可能会触发一些bug。key属性可以避免数据混乱的情况出现 (如果元素中包含了有临时数据的元素,如果不用key就会产生数据混乱)
​ 3.为了触发过渡效果

4.说一下你对vue生命周期的理解。

​ 组件从创建到销毁的过程就是它的生命周期
创建
​ beforeCreat
​ 在这个阶段属性和方法都不能使用
​ created
​ 这里时实例创建完成之后,在这里完成了数据监测,可以使用数据,修改数据,不会触发updated,也不会更新视图
​ 挂载
​ beforeMount
​ 完成了模板的编译,虚拟DOM也完成创建,即将渲染,修改数据,不会触发updated
​ Mounted
​ 把编译好的模板挂载到页面,这里可以发送异步请求也可以访问DOM节点
​ 更新
​ beforeUpdate
​ 组件数据更新之前使用,数据是新的,页面上的数据时旧的,组件即将更新,准备渲染,可以改数据
​ updated
​ render重新做了渲染,这时数据和页面都是新的,避免在此更新数据
销毁
​ beforeDestroy
​ 实例销毁前,在这里实例还可以用,可以清楚定时器等等
​ destroyed
​ 组件已经被销毁了,全部都销毁
​ 使用了keep-alive时多出两个周期:
​ activited
​ 组件激活时
​ deactivited
​ 组件被销毁时

异常

​ errorCaptured

beforeCreate ——–> setup(()=>{})
created ——–> setup(()=>{})
beforeMount ——–> onBeforeMount(()=>{})
mounted ——–> onMounted(()=>{})
beforeUpdate ——–> onBeforeUpdate(()=>{})
updated ——–> onUpdated(()=>{})
beforeDestroy ——–> onBeforeUnmount(()=>{})
destroyed ——–> onUnmounted(()=>{})
activated ——–> onActivated(()=>{})
deactivated ——–> onDeactivated(()=>{})
errorCaptured ——–> onErrorCaptured(()=>{})

5.在created和mounted去请求数据,有什么区别?

​ created:在渲染前调用,通常先初始化属性,然后做渲染
​ mounted:在模板渲染完成后,一般都是初始化页面后,在对元素节点进行操作
在这里请求数据可能会出现闪屏的问题,created里不会
​ 一般用created比较多
​ 1.请求的数据对DOM有影响,那么使用created
​ 2.如果请求的数据对DOM无关,可以放在mounted

6.vue中的修饰符有哪些?

​ 1.事件修饰符
​ .stop 阻止冒泡
​ .prevent 阻止默认行为
​ .capture 内部元素触发的事件先在次处理
​ .self 只有在event.target是当前元素时触发
​ .once 事件只会触发一次
​ .passive 立即触发默认行为
​ .native 把当前元素作为原生标签看待
​ 2.按键修饰符
​ .keyup 键盘抬起
​ .keydown 键盘按下
​ 3.系统修饰符
​ .ctrl
​ .alt
​ .meta
​ 4.鼠标修饰符
​ .left 鼠标左键
​ .right 鼠标右键
​ .middle 鼠标中键
​ 5.表单修饰符
​ .lazy 等输入完之后再显示
​ .trim 删除内容前后的空格
​ .number 输入是数字或转为数字

7.elementui是怎么做表单验证的?

​ 1.在表单中加rules属性,然后再data里写校验规则
​ 2.内部添加规则
​ 3.自定义函数校验

8.vue如何进行组件通信,组件传参?

​ 1.父传子
​ props
​ 父组件使用自定义属性,然后子组件使用props
​ $ref
​ 引用信息会注册在父组件的$refs对象上
​ 2.子传父
​ $emit
​ 子组件绑定自定义事件,触发执行后,传给父组件,父组件需要用事件监听来接收参数
​ 3.兄弟传
​ new一个新的vue实例,用on和emit来对数据进行传输
​ 4.vuex传值

​ 5.任意组件间:provide/inject

9.keep-alive是什么?怎么使用?

​ Vue的一个内置组件,包裹组件的时候,会缓存不活跃的组件实例,并不是销毁他们
​ 作用:把组件切换的状态保存在内存里,防止重复渲染DOM节点,减少加载时间和性能消耗,提高用户体验

使用了keep-alive时多出两个周期:
activited:组件激活时
deactivited:组件被销毁时

10.axios是怎么做封装的?

​ 下载 创建实例 接着封装请求,响应拦截器 抛出 最后封装接口

11.vue路由时怎么传参的?

​ 1.params传参
​ this.$router.push({name:’index’,params:{id:item.id}})
​ this.$route.params.id
​ 2.路由属性传参
​ this.$router.push({name:’/index/${item.id}’})
​ 路由配置 { path:’/index:id’ }
​ 3.query传参(可以解决页面刷新参数丢失的问题)
​ this.$router.push({
​ name:’index’,
​ query:{id:item.id}
​ })

12.vue路由的hash模式和history模式有什么区别?***

hash模式适合单页面应用

1. URL 结构

  • history: 使用实际的 URL 路径,例如 “/about”。
  • hash: 在 URL 的末尾使用哈希 (#) 符号,例如 “/#about”。

2. 浏览器历史

  • history: 会修改浏览器历史记录,允许用户使用后退和前进按钮在页面之间导航。
  • hash: 不会修改浏览器历史记录,用户使用后退和前进按钮时会停留在同一页面。

3. 刷新行为

  • history: 刷新页面会导致一个新的请求,服务器将渲染整个页面。
  • hash: 刷新页面不会触发服务器请求,浏览器只重新加载当前页面。

4. 搜索引擎友好性

  • history: 对搜索引擎友好,因为 URL 中包含有意义的信息。
  • hash: 对搜索引擎不友好,因为哈希部分不会被搜索引擎识别。

5. 兼容性

  • history: 需要 HTML5 History API 支持,在大多数现代浏览器中都有。
  • hash: 兼容性好,可以在所有支持 JavaScript 的浏览器中使用。

​ 1.hash的路由地址上有#号,history模式没有
​ 2.在做回车刷新的时候,hash模式会加载对应页面,history会报错404
​ 3.hash模式支持低版本浏览器,history不支持,因为是H5新增的API
​ 4.hash不会重新加载页面,单页面应用必备
​ 5.history有历史记录,H5新增了pushState和replaceState()去修改历史记录,并不会立刻发送请求
​ 6.history需要后台配置

13.路由拦截是怎么实现的?

​ 路由拦截 axios拦截
​ 需要在路由配置中添加一个字段,它是用于判断路由是否需要拦截

{
            name:'index',
            path:'/index',
            component:Index,
            meta:{
                requirtAuth:true
            }
        }
        router.beforeEach((to,from,next) => {
            if(to.meta.requirtAuth){
                if( store.satte.token ){
                    next()
                }else{
                    }
    }
})

14.说一下vue的动态路由。

​ 要在路由配置里设置meat属性,扩展权限相关的字段,在路由导航守卫router.beforeEach里通过判断这个权限标识,实现路由的动态增加和跳转。
​ 根据用户登录的账号,返回用户角色
​ 前端再根据角色,跟路由表的meta.role进行匹配
​ 把匹配搭配的路由形成可访问的路由

15.如何解决刷新后二次加载路由?

​ 1.window.location.reload()
​ 2.matcher
​ const router = createRouter()
​ export function resetRouter(){
​ const newRouter = creatRouter()
​ router.matcher = newRouter.matcher
​ }

16.vuex刷新数据会丢失吗?怎么解决?

​ vuex肯定会重新获取数据,页面也会丢失数据
​ 1.把数据直接保存在浏览器缓存里(cookie localstorage sessionstorage)
​ 2.页面刷新的时候,再次请求数据,达到可以动态更新的方法
​ 监听浏览器的刷新书简,在刷新前把数据保存到sessionstorage里,刷新后请求数据,请求到了用vuex,如果没有那就用sessionstorage里的数据

17.computed和watch的区别?

  1. computed是计算属性,其依赖data的数据;watch是监听,监听data中的数据变化。

  2. 原理不同

    computed第一次加载时就监听;watch默认第一次加载时不监听。immediate设置成true时,第一次加载才会监听

  3. 是否支持缓存
    computed支持缓存,当其依赖的属性的值发生变化时,计算属性会重新计算,反之,则使用缓存中的属性值;

    watch不支持缓存,当对应属性发生变化的时候,响应执行。

  4. 是否支持异步
    computed不支持异步,当computed内有异步操作时是无法监听数据变化的;
    watch支持异步操作

  5. 使用场景不同
    computed擅长处理的场景是:多个数据影响一个数据 —-购物车商品结算。
    watch擅长处理的场景是:一个数据变化影响多个数据。—-搜索框。

18.vuex在什么场景会去使用?属性有哪些?

​ state 存储变量
​ getters state的计算属性
​ mutations 提交更新数据的方法
​ actions 和mutations差不多,他是提交mutations来修改数据,可以包括异步操作
​ modules 模块化vuex
​ 使用场景:
​ 用户的个人信息、购物车模块、订单模块

mutaion和action的区别:

action的功能和mutation是类似的,都是去变更store里的state,不过action和mutation有两点不同:

  1. action主要处理的是异步的操作,mutation必须同步执行,而action就不受这样的限制,也就是说action中我们既可以处理同步,也可以处理异步的操作

  2. action改变状态,最后是通过提交mutation

19.vue的双向数据绑定原理是什么?***

Vue2的双向数据绑定原理:‌通过Object.defineProperty()方法实现数据劫持,‌为每个属性定义gettersetter方法,‌从而监听数据的变化。‌当数据变化时,‌setter方法会被触发,‌进而通知视图更新。‌同时,‌Vue2使用发布-订阅模式来管理组件之间的通信,‌确保所有组件都能接收到更新的通知并及时响应变化。‌

Vue3则采用Proxy对象来实现响应式系统,‌Proxy允许拦截并定义JavaScript对象的默认行为,‌从而对数据的读取和更新进行拦截,‌并在数据变化时自动触发依赖关系的更新操作,‌实现双向数据绑定。‌这种方式比Vue2的数据劫持更加灵活和强大。‌

​ 通过数据劫持和发布订阅者模式来实现,同时利用Object.defineProperty()劫持各个属性的setter和getter,
​ 在数据发生改变的时候发布消息给订阅者,触发对应的监听回调渲染视图,也就是说数据和视图时同步的,数据发生改变,视图跟着发生改变,视图改变,数据也会发生改变。
​ 第一步:需要observer的数据对象进行递归遍历,包括子属性对象的属性,都加上setter和getter
​ 第二步:compile模板解析指令,把模板中的变量替换成数据,然后初始化渲染视图,同时把每个指令对应的节点绑定上更新函数,添加订阅者,如果数据变化,收到通知,更新视图
​ 第三步:Watcher订阅者是Observer和Compile之间的通信桥梁,作用:
​ 1.在自身实例化的时候忘订阅器内添加自己
​ 2.自身要有一个update()方法
​ 3.等待属性变动时,调用自身的update方法,触发compile这种的回调
​ 第四步:MVVM作为数据绑定的入口,整合了observer、compile和watcher三者,通过observer来监听自己的数据变化,通过compile解析模板指令,最后利用watcher把observer和compile联系起来,最终达到数据更新视图更新,视图更新数据更新的效果

v-model:

<input v-model="message" placeholder="edit me">等价于<input :value="message" @input="message = $event.target.value">

<check v-model="message">:value @change

20.了解diff算法和虚拟DOM吗?

​ 虚拟DOM,描述元素和元素之间的关系,创建一个JS对象
​ 如果组件内有响应的数据,数据发生改变的时候,render函数会生成一个新的虚拟DOM,这个新的虚拟DOM会和旧的虚拟DOM进行比对,找到需要修改的虚拟DOM内容,然后去对应的真实DOM中修改
​ diff算法就是虚拟DOM的比对时用的,返回一个patch对象,这个对象的作用就是存储两个节点不同的地方,最后用patch里记录的信息进行更新真实DOM
​ 步骤:
​ 1.JS对象表示真实的DOM结构,要生成一个虚拟DOM,再用虚拟DOM构建一个真实DOM树,渲染到页面
​ 2.状态改变生成新的虚拟DOM,跟就得虚拟DOM进行比对,这个比对的过程就是DIFF算法,利用patch记录差异
​ 3.把记录的差异用在第一个虚拟DOM生成的真实DOM上,视图就更新了。

21.vue和jquery的区别是什么?

​ 1.原理不同
​ vue就是数据绑定;jq是先获取dom再处理
​ 2.着重点不同
​ vue是数据驱动,jq是着重于页面
​ 3.操作不同
​ 4.未来发展不同

22.vuex的响应式处理。

​ vuex是vue的状态管理工具
​ vue中可以直接触发methods中的方法,vuex是不可以的。未来处理异步,当触发事件的时候,会通过dispatch来访问actions中的方法,actions中的commit会触发mutations中的方法从而修改state里的值,通过getter把数据更新到视图
​ Vue.use(vuex),调用install方法,通过applyMixin(vue)在任意组件内执行this.$store就可以访问到store对象。
​ vuex的state是响应式的,借助的就是vue的data,把state存到vue实例组件的data中

23.vue中遍历全局的方法有哪些?

​ 1.普通遍历,对象.forEach()
​ arr.forEach(function(item,index,arr){
​ console.log(item,index)
​ })
​ 2.对元素统一操作 对象.map()
​ var newarr = arr.map(function(item){
​ return item+1
​ })
​ 3.查找符合条件的元素 对象.filter()
​ arr.filter(function(item){
​ if(item > 2){
​ return false
​ }else{
​ return true
​ }
​ })
​ 4.查询符合条件的元素,返回索引 对象.findindex()
​ arr.finindex(function(item){
​ if(item>1){
​ return true
​ }else{
​ return false
​ }
​ })
​ 对象.evening() 遇到不符合的对象会停止
​ 对象.some() 找到符合条件的元素就停止

24.如何搭建脚手架?

​ 下载:node cnpm webpack vue-cli
​ 创建项目:
​ 1.找到对应的文件,然后利用node指令创建(cmd)
​ 2.vue init webpack xxxx
​ 3.回车项目描述
​ 4.作者回车
​ 5.选择vue build
​ 6.回车
​ 7.输入n
​ 8.不按照yarn
​ 9.输入npm run dev

25.如何封装一个组件?

​ 1.使用Vue.extend()创建一个组件
​ 2.使用Vue.components()方法注册组件
​ 3.如果子组件需要数据,可以在props中接收定义
​ 4.子组件修改好数据,要把数据传递给父组件,可以用emit()方法
​ 原则:
​ 把功能拆开
​ 尽量让组件原子化,一个组件做一件事情
​ 容器组件管数据,展示组件管视图

26.封装一个可复用的组件,需要满足什么条件?

​ 1.低耦合,组件之间的依赖越小越好
​ 2.最好从父级传入信息,不要在公共组件中请求数据
​ 3.传入的数据要进行校验
​ 4.处理事件的方法写在父组件中

27.vue的过滤器怎么使用?

​ vue的特性,用来对文本进行格式化处理
​ 使用它的两个地方,一个是插值表达式,一个是v-bind
​ 分类:
​ 1.全局过滤器
​ Vue.filter(‘add’,function(v){
​ return v < 10 ? ‘0’ + v : v
​ })

        <div>{{33 | add}}</div>

​ 2.本地过滤器
​ 和methods同级
​ filter:{
​ add:function(v){
​ return v < 10 ? ‘0’ + v : v
​ }
​ }

28.vue中如何做强制刷新?

​ 1.localtion.reload()
​ 2.this.$router.go(0)
​ 3.provide和inject

29.vue3和vue2有哪些区别?

1.双向数据绑定的原理不同,vue2:defineProperty,vue3:proxy
​ 2.是否支持碎片
​ 3.API不同
4.定义数据变量方法不同
5.生命周期的不同
​ 6.传值不同
​ 7.指令和插槽不同
8.main.js不同

30.vue的性能优化怎么做?

​ 1.编码优化

  • 不要把所有数据都放在data中
  • v-for时给每个元素绑定事件用事件代理
  • keep-alive缓存组件
  • 尽可能拆分组件,提高复用性、维护性
  • key值要保证唯一
  • 合理使用路由懒加载,异步组件
  • 数据持久化存储的使用尽量用防抖、节流优化

​ 2.加载优化
​ 按需加载
​ 内容懒加载
​ 图片懒加载
​ 3.用户体验
​ 骨架屏
​ 4.SEO优化
​ 预渲染
​ 服务端渲染ssr
​ 5.打包优化
​ CDN形式加载第三方模块
​ 多线程打包
​ 抽离公共文件
​ 6.缓存和压缩
​ 客户端缓存、服务端缓存
​ 服务端Gzip压缩

31.首屏优化该如何去做?

​ 1.使用路由懒加载
​ 2.非首屏组件使用异步组件
​ 3.首屏不中要的组件延迟加载
​ 4.静态资源放在CDN上
​ 5.减少首屏上JS、CSS等资源文件的大小
​ 6.使用服务端渲染
​ 7.简历减少DOM的数量和层级
​ 8.使用精灵图请求
​ 9.做一些loading
​ 10.开启Gzip压缩
​ 11.图片懒加载

32.vue3的性能为什么比vue2好?

​ 1.diff算法的优化
​ 2.静态提升
​ 3.事件侦听缓存

33.vue3为什么使用proxy?***

​ 1.proxy可以代理整个对象,defineproperty(vue2)只代理对象上的某个属性,不需要遍历了,效率更高。
​ 2.proxy对代理对象的监听更加丰富
​ 3.proxy代理对象会生成新的对象,不会修改被代理对象本身
​ 4.proxy不兼容ie浏览器

34.说一下你对组件的理解。

​ 可以重复使用的vue实例,独一无二的组件名称
​ 可以抽离单独的公共模块
​ 提高代码的复用率

35.你是如何规划项目文件的?

​ public
​ 图标、index.html、img
​ src
​ api
​ assets
​ components
​ 按分类再次划分子目录
​ plugins
​ router
​ static
​ styles
​ utils
​ views
​ App.vue
​ main.js
​ package.json
​ vue.config.js

36.是否使用过nuxt.js?

​ 是基于vue的应用框架,关注的是渲染,可以开发服务端渲染应用的配置
​ SSR:服务端渲染
​ 好处:
​ SSR生成的是有内容的HTML页面,有利于搜索引擎的搜索
​ 优化了首屏加载时间
​ SEO:优化搜索引擎
​ SPA的应用不利于搜索引擎SEO的操作

37.SEO如何优化?

​ 1.SSR
​ 2.预渲染 prerender-spa-plugin

38. 什么是跨域?跨域的原因?解决方法

  • 当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域指的是在网页端,发起一个跨越不同域名(协议、端口号)的HTTP请求的过程

  • 跨域问题是指由于浏览器的同源策略(协议、域名、端口)导致的不同域之间的通信限制。主要是防止 csrf 攻击

  • 前端解决方法(vue)配置代理

1
2
3
4
5
6
7
8
9
10
11
12
13
//vite.config.js
export default defineConfig({
plugins:[vue()],
server:{
proxy:{
"/api":{
target:"http://localhost:80", //转到"http://localhost:80"
changeOrigin:true,
rewrite:(path) => path.replace(/^\/api/,"") //删除替换'/api'
}
}
}
})

39. nextTick()

作用:在下一次DOM节点更新完后执行其指定的回调

什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行。

1
2
3
this.$nextTick(function(){

})

40.vuex pinia区别

  1. Vuex选项式api,pinia组合式api,更灵活

  2. 状态管理方式

    Vuex采用全局单例模式,通过一个store对象来管理所有的状态,组件通过store对象来获取和修改状态。而Pinia采用了分离模式,即每个组件都拥有自己的store实例,通过在组件中创建store实例来管理状态。

  3. 数据修改方式

    Vuex通过mutations来修改状态,而Pinia没有mutations,它只有state、getters和actions(同步、异步)来使用它来修改state数据。

  4. Vuex支持模块化,通过modules配置来组织状态。而Pinia没有modules配置,每一个独立的仓库都是definStore生成出来的

  5. Vuex对TypeScript的支持不完整,而Pinia做到了完整的TypeScript支持,这使其与TypeScript更加兼容和友好,提供了更清晰的类型定义。

41.数据(事件)改变之后发起请求,请求放在哪个监听属性比较合适,watch、computed,为什么

为什么选择watch而不是computed

  1. watch的用途
    watch用于观察和响应Vue实例上数据的变动。当指定的数据变化时,你可以执行异步操作或开销较大的操作,比如发送网络请求。watch非常适合执行副作用或异步操作,这些操作不应该在computed属性中执行,因为computed属性应该是同步的,并且基于它们的依赖进行缓存。
  2. computed的用途
    computed计算属性主要被用作声明式的描述一些依赖其它响应式属性的值。它们是基于它们的依赖进行缓存的,只有当依赖项发生改变时,computed属性才会重新计算。由于它们是基于响应式依赖进行缓存的,所以它们适合执行同步操作,并且不需要进行副作用(如网络请求)或执行开销较大的操作。
  3. 异步操作和副作用
    如前所述,当数据变化需要执行异步操作(如API请求)时,使用watch是更合适的选择。这是因为watch允许你定义当被观察的数据变化时应该执行的回调函数,这个回调函数可以是异步的,比如发送HTTP请求。而computed属性则不适合执行异步操作,因为它们的设计初衷是同步的,并且基于依赖的缓存机制。

五、其他

1.TCP三次握手,为什么要握手三次,两次不行

  1. 第一次握手:客户端向服务器发出连接请求报文

  2. 第二次握手:TCP服务器收到请求报文后,如果同意连接,则发出确认报文。

  3. 第三次握手:TCP客户进程收到确认后,还要向服务器给出确认。

已失效的连接请求报文段。

  • client发送了第一个连接的请求报文,但是由于网络不好,这个请求没有立即到达服务端,而是在某个网络节点中滞留了,直到某个时间才到达server。
  • 本来这已经是一个失效的报文,但是server端接收到这个请求报文后,还是会想client发出确认的报文,表示同意连接。
  • 假如不采用三次握手,那么只要server发出确认,新的建立就连接了,但其实这个请求是失效的请求,client是不会理睬server的确认信息,也不会向服务端发送确认的请求
  • 但是server认为新的连接已经建立起来了,并一直等待client发来数据,这样,server的很多资源就没白白浪费掉了

采用三次握手就是为了防止这种情况的发生,server会因为收不到确认的报文,就知道client并没有建立连接。这就是三次握手的作用。

客户端发生请求报文,但是这个请求报文由于某些原因失效了。

服务端接收到失效的请求报文,还会向客户端发生确认的报文。

如果没有第三次握手,服务端端发送的确认报文不会被客户端理睬。

这时服务端认为连接已经建立了,一直等待客户端发生请求数据,这样服务端的资源就会白白浪费了。

2.冒泡排序

1
2
3
4
5
6
7
8
9
let arr = [5, 8, 6, 3, 0, 45, 7, -5, 78, 32]
for (let i = 0; i <= arr.length - 1; i++) {//外层循环是从0开始
for (let j = 0; j< arr.length-i-1; j++) {//内层循环是从arr.length-1开始
if (arr[j] > arr[j + 1]) {//如果逆序,则交换
[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];//交换
}
}
}
console.log(arr);//排序后的数组

六、Uni-APP

1.uni-app有没有做过分包?

​ 优化小程序的下载和启动速度
​ 小程序启动默认下载主包并启动页面,当用户进入分包时,才会下载对应的分包,下载完进行展示

七、Weabpack

1.webpack打包和不打包的区别?

​ 1.运行效率
​ 2.对基础的支持不够

2.webpack是怎么打包的,babel是做什么的?

​ webpack会把js css image看作一个模块,用import/require引入
​ 找到入口文件,通过入口文件找到关联的依赖文件,把他们打包到一起
​ 把bundle文件,拆分成多个小的文件,异步按需加载所需要的文件
​ 如果一个被多个文件引用,打包时只会生成一个文件
​ 如果引用的文件没有调用,不会打包,如果引入的变量和方法没有调用也不会打包
​ 对于多个入口文件,加入引入了相同的代码,可以用插件把他抽离到公共文件中

八、Git

1.git如何合并、拉取代码?

​ 拉取代码 git pull ‘仓库地址’
​ 查看状态 git status
​ 提交到本地缓存区 git add .
​ 提交本地仓库 git commit -m ‘修改描述’
​ 提交到远程仓库 git push ‘仓库地址’ master
​ 创建分支 git branch -b xxx
​ 合并分支 git merge ‘合并分支的名字’

2.git如何解决冲突问题?

​ 1.两个分支中修改了同一个文件
​ 2.两个分支中修改了同一个文件的名字
​ 1.解决:当前分支上,直接修改代码 add commit
​ 2.解决:在本地当前分支上,修改冲突代码 add commit push

3.git stash

场景一:当前分支A,BUG分支B

  1. git stash,先存入栈
  2. git checkout B,切换到BUG分支B
  3. git checkout A,修完BUG切回分支A
  4. git stash pop,取回

场景二:代码开发完准备提交

  1. git stash

  2. git pull 仓库别名

  3. git stash pop

  4. 本地处理可能出现的冲突

  5. git commit -m "xxx" + git push 仓库别名

  6. 存(入栈)

    git stash

    git stash save '注释'

  7. 取(出栈)

git stash pop

git stash apply

  1. 清除

git stash drop + <栈索引>清除某条记录

git stash clear清空整个栈

  1. 查看

git stash list查看整个栈的所有记录

git stash show查看某条记录的具体信息

MyBatis笔记

一、MyBatis简介
1 、MyBatis历史
2 、MyBatis特性
3 、MyBatis下载
4 、和其它持久化层技术对比
二、搭建MyBatis
1 、开发环境
2 、创建maven工程
a>打包方式:jar
b>引入依赖
3 、创建MyBatis的核心配置文件
4 、创建mapper接口
5 、创建MyBatis的映射文件
6 、通过junit测试功能
7 、加入log4j日志功能
a>加入依赖
b>加入log4j的配置文件
三、核心配置文件详解
四、MyBatis的增删改查
五、MyBatis获取参数值的两种方式(重点)

1 、单个字面量类型的参数
2 、多个字面量类型的参数
3 、map集合类型的参数
4 、实体类类型的参数
5 、使用@Param标识参数
六、MyBatis的各种查询功能
1 、查询一个实体类对象
2 、查询一个list集合
3 、查询单个数据
4 、查询一条数据为map集合
5 、查询多条数据为map集合
方式一:
方式二:
七、特殊SQL的执行
1 、模糊查询
2 、批量删除
3 、动态设置表名
4 、添加功能获取自增的主键
八、自定义映射resultMap
1 、resultMap处理字段和属性的映射关系
2 、多对一映射处理
a>级联方式处理映射关系
b>使用association处理映射关系
c>分步查询
3 、一对多映射处理
a>collection
b>分步查询
九、动态SQL
1 、if
2 、where
3 、trim
4 、choose、when、otherwise
5 、foreach
6 、SQL片段
十、MyBatis的缓存

1 、MyBatis的一级缓存
2 、MyBatis的二级缓存
3 、二级缓存的相关配置
4 、MyBatis缓存查询的顺序
5 、整合第三方缓存EHCache
a>添加依赖
b>各jar包功能
c>创建EHCache的配置文件ehcache.xml
d>设置二级缓存的类型
e>加入logback日志
f>EHCache配置文件说明
十一、MyBatis的逆向工程
1 、创建逆向工程的步骤
a>添加依赖和插件
b>创建MyBatis的核心配置文件
c>创建逆向工程的配置文件
d>执行MBG插件的generate目标
2 、QBC查询
十二、分页插件
1 、分页插件使用步骤
a>添加依赖
b>配置分页插件
2 、分页插件的使用

一、MyBatis简介

1 、MyBatis历史

MyBatis最初是Apache的一个开源项目 iBatis , 2010年 6 月这个项目由Apache Software Foundation迁

移到了Google Code。随着开发团队转投Google Code旗下, iBatis3.x正式更名为MyBatis。代码于

2013 年 11 月迁移到Github。

iBatis一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。 iBatis提供的持久层框架

包括SQL Maps和Data Access Objects(DAO)。

2 、MyBatis特性

1 ) MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架

2 ) MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集

3 ) MyBatis可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO(Plain Old Java

Objects,普通的Java对象)映射成数据库中的记录

4 ) MyBatis 是一个 半自动的ORM(Object Relation Mapping)框架

3 、MyBatis下载

MyBatis下载地址:https://github.com/mybatis/mybatis-

4 、和其它持久化层技术对比

JDBC

SQL 夹杂在Java代码中耦合度高,导致硬编码内伤

维护不易且实际开发需求中 SQL 有变化,频繁修改的情况多见

代码冗长,开发效率低

Hibernate 和 JPA

操作简便,开发效率高

程序中的长难复杂 SQL 需要绕过框架

内部自动生产的 SQL,不容易做特殊优化

基于全映射的全自动框架,大量字段的 POJO 进行部分映射时比较困难。

反射操作太多,导致数据库性能下降

MyBatis

轻量级,性能出色

SQL 和 Java 编码分开,功能边界清晰。Java代码专注业务、SQL语句专注数据

开发效率稍逊于HIbernate,但是完全能够接受

二、搭建MyBatis

1 、开发环境

IDE:idea 2019.

构建工具:maven 3.5.

MySQL版本:MySQL 5.

MyBatis版本:MyBatis 3.5.

2 、创建maven工程

a>打包方式:jar

b>引入依赖

3 、创建MyBatis的核心配置文件

习惯上命名为mybatis-config.xml,这个文件名仅仅只是建议,并非强制要求。将来整合Spring

之后,这个配置文件可以省略,所以大家操作时可以直接复制、粘贴。

核心配置文件主要用于配置连接数据库的环境以及MyBatis的全局配置信息

1
2
3
4
5
6
7
<dependencies>
<!-- Mybatis核心 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
1
2
3
4
5
6
7
<!-- junit测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
1
2
3
4
5
6
7
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.3</version>
</dependency>
</dependencies>

核心配置文件存放的位置是src/main/resources目录下

4 、创建mapper接口

MyBatis中的mapper接口相当于以前的dao。但是区别在于,mapper仅仅是接口,我们不需要

提供实现类。

5 、创建MyBatis的映射文件

相关概念: ORM ( O bject R elationship M apping)对象关系映射。

对象:Java的实体类对象

关系:关系型数据库

映射:二者之间的对应关系

1
2
3
4
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<configuration>
<!--设置连接数据库的环境-->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url"
value="jdbc:mysql://localhost:3306/MyBatis"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!--引入映射文件-->
<mappers>
<mapper resource="mappers/UserMapper.xml"/>
</mappers>
</configuration>
1
public interface UserMapper {
/**
* 添加用户信息
*/
1
int insertUser();
}

Java概念 数据库概念

类 表

属性 字段/列

对象 记录/行

1 、映射文件的命名规则:

表所对应的实体类的类名+Mapper.xml

例如:表t_user,映射的实体类为User,所对应的映射文件为UserMapper.xml

因此一个映射文件对应一个实体类,对应一张表的操作

MyBatis映射文件用于编写SQL,访问以及操作表中的数据

MyBatis映射文件存放的位置是src/main/resources/mappers目录下

2 、MyBatis中可以面向接口操作数据,要保证两个一致:

a>mapper接口的全类名和映射文件的命名空间(namespace)保持一致

b>mapper接口中方法的方法名和映射文件中编写SQL的标签的id属性保持一致

6 、通过junit测试功能

1
2
3
4
5
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.mybatis.mapper.UserMapper">
1
2
3
4
<!--int insertUser();-->
<insert id="insertUser">
insert into t_user values(null,'张三','123',23,'女')
</insert>
1
</mapper>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//读取MyBatis的核心配置文件
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
//创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new
SqlSessionFactoryBuilder();
//通过核心配置文件所对应的字节输入流创建工厂类SqlSessionFactory,生产SqlSession对象
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
//创建SqlSession对象,此时通过SqlSession对象所操作的sql都必须手动提交或回滚事务
//SqlSession sqlSession = sqlSessionFactory.openSession();
//创建SqlSession对象,此时通过SqlSession对象所操作的sql都会自动提交
SqlSession sqlSession = sqlSessionFactory.openSession(true);
//通过代理模式创建UserMapper接口的代理实现类对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//调用UserMapper接口中的方法,就可以根据UserMapper的全类名匹配元素文件,通过调用的方法名匹配
映射文件中的SQL标签,并执行标签中的SQL语句
int result = userMapper.insertUser();
//sqlSession.commit();

SqlSession:代表Java程序和 数据库 之间的 会话 。(HttpSession是Java程序和浏览器之间的

会话)

SqlSessionFactory:是“生产”SqlSession的“工厂”。

工厂模式:如果创建某一个对象,使用的过程基本固定,那么我们就可以把创建这个对象的

相关代码封装到一个“工厂类”中,以后都使用这个工厂类来“生产”我们需要的对象。

7 、加入log4j日志功能

a>加入依赖

b>加入log4j的配置文件

log4j的配置文件名为log4j.xml,存放的位置是src/main/resources目录下

日志的级别

FATAL(致命)>ERROR(错误)>WARN(警告)>INFO(信息)>DEBUG(调试)

从左到右打印的内容越来越详细

1
System.out.println("结果:"+result);
1
2
3
4
5
6
<!-- log4j日志 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
1
2
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
1
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
<param name="Encoding" value="UTF-8" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS}
%m (%F:%L) \n" />
</layout>
</appender>
<logger name="java.sql">
<level value="debug" />
</logger>
<logger name="org.apache.ibatis">
<level value="info" />
</logger>
<root>
<level value="debug" />
<appender-ref ref="STDOUT" />
</root>
</log4j:configuration>

三、核心配置文件详解

核心配置文件中的标签必须按照固定的顺序:

properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,reflectorF

actory?,plugins?,environments?,databaseIdProvider?,mappers?

1
2
3
4
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//MyBatis.org//DTD Config 3.0//EN"
"http://MyBatis.org/dtd/MyBatis-3-config.dtd">
1
<configuration>
1
2
<!--引入properties文件,此时就可以${属性名}的方式访问属性值-->
<properties resource="jdbc.properties"></properties>
1
2
3
4
5
6
<settings>
<!--将表中字段的下划线自动转换为驼峰-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!--开启延迟加载-->
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<typeAliases>
<!--
typeAlias:设置某个具体的类型的别名
属性:
type:需要设置别名的类型的全类名
alias:设置此类型的别名,若不设置此属性,该类型拥有默认的别名,即类名且不区分大小

若设置此属性,此时该类型的别名只能使用alias所设置的值
-->
<!--<typeAlias type="com.atguigu.mybatis.bean.User"></typeAlias>-->
<!--<typeAlias type="com.atguigu.mybatis.bean.User" alias="abc">
</typeAlias>-->
<!--以包为单位,设置改包下所有的类型都拥有默认的别名,即类名且不区分大小写-->
<package name="com.atguigu.mybatis.bean"/>
</typeAliases>
<!–
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
environments:设置多个连接数据库的环境
属性:
default:设置默认使用的环境的id
-->
<environments default="mysql_test">
<!--
environment:设置具体的连接数据库的环境信息
属性:
id:设置环境的唯一标识,可通过environments标签中的default设置某一个环境的id,
表示默认使用的环境
-->
<environment id="mysql_test">
<!--
transactionManager:设置事务管理方式
属性:

四、MyBatis的增删改查

1 、添加

2 、删除

3 、修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
type:设置事务管理方式,type="JDBC|MANAGED"
type="JDBC":设置当前环境的事务管理都必须手动处理
type="MANAGED":设置事务被管理,例如spring中的AOP
-->
<transactionManager type="JDBC"/>
<!--
dataSource:设置数据源
属性:
type:设置数据源的类型,type="POOLED|UNPOOLED|JNDI"
type="POOLED":使用数据库连接池,即会将创建的连接进行缓存,下次使用可以从
缓存中直接获取,不需要重新创建
type="UNPOOLED":不使用数据库连接池,即每次使用连接都需要重新创建
type="JNDI":调用上下文中的数据源
-->
<dataSource type="POOLED">
<!--设置驱动类的全类名-->
<property name="driver" value="${jdbc.driver}"/>
<!--设置连接数据库的连接地址-->
<property name="url" value="${jdbc.url}"/>
<!--设置连接数据库的用户名-->
<property name="username" value="${jdbc.username}"/>
<!--设置连接数据库的密码-->
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<!--引入映射文件-->
<mappers>
<mapper resource="UserMapper.xml"/>
<!--
以包为单位,将包下所有的映射文件引入核心配置文件
注意:此方式必须保证mapper接口和mapper映射文件必须在相同的包下
-->
<package name="com.atguigu.mybatis.mapper"/>
</mappers>
</configuration>
1
2
3
4
<!--int insertUser();-->
<insert id="insertUser">
insert into t_user values(null,'admin','123456',23,'男')
</insert>
1
2
3
4
<!--int deleteUser();-->
<delete id="deleteUser">
delete from t_user where id = 7
</delete>

4 、查询一个实体类对象

5 、查询集合

注意:

1 、查询的标签select必须设置属性resultType或resultMap,用于设置实体类和数据库表的映射

关系

resultType:自动映射,用于属性名和表中字段名一致的情况

resultMap:自定义映射,用于一对多或多对一或字段名和属性名不一致的情况

2 、当查询的数据为多条时,不能使用实体类作为返回值,只能使用集合,否则会抛出异常

TooManyResultsException;但是若查询的数据只有一条,可以使用实体类或集合作为返回值

五、MyBatis获取参数值的两种方式(重点)

MyBatis获取参数值的两种方式: ${} 和 #{}

${}的本质就是字符串拼接,#{}的本质就是占位符赋值

${}使用字符串拼接的方式拼接sql,若为字符串类型或日期类型的字段进行赋值时,需要手动加单引

号;但是#{}使用占位符赋值的方式拼接sql,此时为字符串类型或日期类型的字段进行赋值时,可以自

动添加单引号

1 、单个字面量类型的参数

若mapper接口中的方法参数为单个的字面量类型

此时可以使用${}和#{}以任意的名称获取参数的值,注意${}需要手动加单引号

2 、多个字面量类型的参数

若mapper接口中的方法参数为多个时

此时MyBatis会自动将这些参数放在一个map集合中,以arg0,arg1…为键,以参数为值;以

param1,param2…为键,以参数为值;因此只需要通过${}和#{}访问map集合的键就可以获取相对应的

值,注意${}需要手动加单引号

1
2
3
4
<!--int updateUser();-->
<update id="updateUser">
update t_user set username='ybc',password='123' where id = 6
</update>
1
2
3
4
<!--User getUserById();-->
<select id="getUserById" resultType="com.atguigu.mybatis.bean.User">
select * from t_user where id = 2
</select>
1
2
3
4
<!--List<User> getUserList();-->
<select id="getUserList" resultType="com.atguigu.mybatis.bean.User">
select * from t_user
</select>

3 、map集合类型的参数

若mapper接口中的方法需要的参数为多个时,此时可以手动创建map集合,将这些数据放在map中

只需要通过${}和#{}访问map集合的键就可以获取相对应的值,注意${}需要手动加单引号

4 、实体类类型的参数

若mapper接口中的方法参数为实体类对象时

此时可以使用${}和#{},通过访问实体类对象中的属性名获取属性值,注意${}需要手动加单引号

5 、使用@Param标识参数

可以通过@Param注解标识mapper接口中的方法参数

此时,会将这些参数放在map集合中,以@Param注解的value属性值为键,以参数为值;以

param1,param2…为键,以参数为值;只需要通过${}和#{}访问map集合的键就可以获取相对应的值,

注意${}需要手动加单引号

六、MyBatis的各种查询功能

1 、查询一个实体类对象

2 、查询一个list集合

3 、查询单个数据

/**
1
2
3
4
5
* 根据用户id查询用户信息
* @param id
* @return
*/
User getUserById(@Param("id") int id);
1
2
3
4
<!--User getUserById(@Param("id") int id);-->
<select id="getUserById" resultType="User">
select * from t_user where id = #{id}
</select>
/**
* 查询所有用户信息
1
2
3
* @return
*/
List<User> getUserList();
1
2
3
4
<!--List<User> getUserList();-->
<select id="getUserList" resultType="User">
select * from t_user
</select>

4 、查询一条数据为map集合

5 、查询多条数据为map集合

方式一:

方式二:

/**
* 查询用户的总记录数
1
2
3
4
5
6
7
* @return
* 在MyBatis中,对于Java中常用的类型都设置了类型别名
* 例如:java.lang.Integer-->int|integer
* 例如:int-->_int|_integer
* 例如:Map-->map,List-->list
*/
int getCount();
1
2
3
4
<!--int getCount();-->
<select id="getCount" resultType="_integer">
select count(id) from t_user
</select>
/**
1
2
3
4
5
* 根据用户id查询用户信息为map集合
* @param id
* @return
*/
Map<String, Object> getUserToMap(@Param("id") int id);
1
2
3
4
5
<!--Map<String, Object> getUserToMap(@Param("id") int id);-->
<select id="getUserToMap" resultType="map">
select * from t_user where id = #{id}
</select>
<!--结果:{password=123456, sex=男, id=1, age=23, username=admin}-->
/**
1
2
3
4
5
6
* 查询所有用户信息为map集合
* @return
* 将表中的数据以map集合的方式查询,一条数据对应一个map;若有多条数据,就会产生多个map集合,此
时可以将这些map放在一个list集合中获取
*/
List<Map<String, Object>> getAllUserToMap();
1
2
3
4
<!--Map<String, Object> getAllUserToMap();-->
<select id="getAllUserToMap" resultType="map">
select * from t_user
</select>

七、特殊SQL的执行

1 、模糊查询

2 、批量删除

/**
1
2
3
4
5
6
7
8
* 查询所有用户信息为map集合
* @return
* 将表中的数据以map集合的方式查询,一条数据对应一个map;若有多条数据,就会产生多个map集合,并
且最终要以一个map的方式返回数据,此时需要通过@MapKey注解设置map集合的键,值是每条数据所对应的
map集合
*/
@MapKey("id")
Map<String, Object> getAllUserToMap();
1
2
3
4
5
6
7
8
9
10
11
12
<!--Map<String, Object> getAllUserToMap();-->
<select id="getAllUserToMap" resultType="map">
select * from t_user
</select>
结果:
<!--
{
1={password=123456, sex=男, id=1, age=23, username=admin},
2={password=123456, sex=男, id=2, age=23, username=张三},
3={password=123456, sex=男, id=3, age=23, username=张三}
}
-->
/**
* 测试模糊查询
1
2
3
4
* @param mohu
* @return
*/
List<User> testMohu(@Param("mohu") String mohu);
1
2
3
4
5
6
<!--List<User> testMohu(@Param("mohu") String mohu);-->
<select id="testMohu" resultType="User">
<!--select * from t_user where username like '%${mohu}%'-->
<!--select * from t_user where username like concat('%',#{mohu},'%')-->
select * from t_user where username like "%"#{mohu}"%"
</select>
/**
* 批量删除
1
2
3
4
* @param ids
* @return
*/
int deleteMore(@Param("ids") String ids);

3 、动态设置表名

4 、添加功能获取自增的主键

t_clazz(clazz_id,clazz_name)

t_student(student_id,student_name,clazz_id)

1 、添加班级信息

2 、获取新添加的班级的id

3 、为班级分配学生,即将某学的班级id修改为新添加的班级的id

八、自定义映射resultMap

1 、resultMap处理字段和属性的映射关系

若字段名和实体类中的属性名不一致,则可以通过resultMap设置自定义映射

1
2
3
4
<!--int deleteMore(@Param("ids") String ids);-->
<delete id="deleteMore">
delete from t_user where id in (${ids})
</delete>
/**
* 动态设置表名,查询所有的用户信息
1
2
3
4
* @param tableName
* @return
*/
List<User> getAllUser(@Param("tableName") String tableName);
1
2
3
4
<!--List<User> getAllUser(@Param("tableName") String tableName);-->
<select id="getAllUser" resultType="User">
select * from ${tableName}
</select>
/**
* 添加用户信息
1
2
3
4
5
6
7
* @param user
* @return
* useGeneratedKeys:设置使用自增的主键
* keyProperty:因为增删改有统一的返回值是受影响的行数,因此只能将获取的自增的主键放在传输的参
数user对象的某个属性中
*/
int insertUser(User user);
1
2
3
4
<!--int insertUser(User user);-->
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
insert into t_user values(null,#{username},#{password},#{age},#{sex})
</insert>

若字段名和实体类中的属性名不一致,但是字段名符合数据库的规则(使用_),实体类中的属性

名符合Java的规则(使用驼峰)

此时也可通过以下两种方式处理字段名和实体类中的属性的映射关系

a>可以通过为字段起别名的方式,保证和实体类中的属性名保持一致

b>可以在MyBatis的核心配置文件中设置一个全局配置信息mapUnderscoreToCamelCase,可

以在查询表中数据时,自动将_类型的字段名转换为驼峰

例如:字段名user_name,设置了mapUnderscoreToCamelCase,此时字段名就会转换为

userName

2 、多对一映射处理

查询员工信息以及员工所对应的部门信息

a>级联方式处理映射关系

<!–
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
resultMap:设置自定义映射
属性:
id:表示自定义映射的唯一标识
type:查询的数据要映射的实体类的类型
子标签:
id:设置主键的映射关系
result:设置普通字段的映射关系
association:设置多对一的映射关系
collection:设置一对多的映射关系
属性:
property:设置映射关系中实体类中的属性名
column:设置映射关系中表中的字段名
-->
<resultMap id="userMap" type="User">
<id property="id" column="id"></id>
<result property="userName" column="user_name"></result>
<result property="password" column="password"></result>
<result property="age" column="age"></result>
<result property="sex" column="sex"></result>
</resultMap>
<!--List<User> testMohu(@Param("mohu") String mohu);-->
<select id="testMohu" resultMap="userMap">
<!--select * from t_user where username like '%${mohu}%'-->
select id,user_name,password,age,sex from t_user where user_name like
concat('%',#{mohu},'%')
</select>

b>使用association处理映射关系

c>分步查询

1 )查询员工信息

1
2
3
4
5
6
7
8
9
10
11
12
13
<resultMap id="empDeptMap" type="Emp">
<id column="eid" property="eid"></id>
<result column="ename" property="ename"></result>
<result column="age" property="age"></result>
<result column="sex" property="sex"></result>
<result column="did" property="dept.did"></result>
<result column="dname" property="dept.dname"></result>
</resultMap>
<!--Emp getEmpAndDeptByEid(@Param("eid") int eid);-->
<select id="getEmpAndDeptByEid" resultMap="empDeptMap">
select emp.*,dept.* from t_emp emp left join t_dept dept on emp.did =
dept.did where emp.eid = #{eid}
</select>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<resultMap id="empDeptMap" type="Emp">
<id column="eid" property="eid"></id>
<result column="ename" property="ename"></result>
<result column="age" property="age"></result>
<result column="sex" property="sex"></result>
<association property="dept" javaType="Dept">
<id column="did" property="did"></id>
<result column="dname" property="dname"></result>
</association>
</resultMap>
<!--Emp getEmpAndDeptByEid(@Param("eid") int eid);-->
<select id="getEmpAndDeptByEid" resultMap="empDeptMap">
select emp.*,dept.* from t_emp emp left join t_dept dept on emp.did =
dept.did where emp.eid = #{eid}
</select>
/**
* 通过分步查询查询员工信息
1
2
3
4
* @param eid
* @return
*/
Emp getEmpByStep(@Param("eid") int eid);
1
2
3
4
5
6
7
8
9
<resultMap id="empDeptStepMap" type="Emp">
<id column="eid" property="eid"></id>
<result column="ename" property="ename"></result>
<result column="age" property="age"></result>
<result column="sex" property="sex"></result>
<!--
select:设置分步查询,查询某个属性的值的sql的标识(namespace.sqlId)
column:将sql以及查询结果中的某个字段设置为分步查询的条件
-->

2 )根据员工所对应的部门id查询部门信息

3 、一对多映射处理

a>collection

b>分步查询

1
2
3
4
5
6
7
8
<association property="dept"
select="com.atguigu.MyBatis.mapper.DeptMapper.getEmpDeptByStep" column="did">
</association>
</resultMap>
<!--Emp getEmpByStep(@Param("eid") int eid);-->
<select id="getEmpByStep" resultMap="empDeptStepMap">
select * from t_emp where eid = #{eid}
</select>
/**
1
2
3
4
5
* 分步查询的第二步:根据员工所对应的did查询部门信息
* @param did
* @return
*/
Dept getEmpDeptByStep(@Param("did") int did);
1
2
3
4
<!--Dept getEmpDeptByStep(@Param("did") int did);-->
<select id="getEmpDeptByStep" resultType="Dept">
select * from t_dept where did = #{did}
</select>
/**
1
2
3
4
5
* 根据部门id查新部门以及部门中的员工信息
* @param did
* @return
*/
Dept getDeptEmpByDid(@Param("did") int did);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<resultMap id="deptEmpMap" type="Dept">
<id property="did" column="did"></id>
<result property="dname" column="dname"></result>
<!--
ofType:设置collection标签所处理的集合属性中存储数据的类型
-->
<collection property="emps" ofType="Emp">
<id property="eid" column="eid"></id>
<result property="ename" column="ename"></result>
<result property="age" column="age"></result>
<result property="sex" column="sex"></result>
</collection>
</resultMap>
<!--Dept getDeptEmpByDid(@Param("did") int did);-->
<select id="getDeptEmpByDid" resultMap="deptEmpMap">
select dept.*,emp.* from t_dept dept left join t_emp emp on dept.did =
emp.did where dept.did = #{did}
</select>

1 )查询部门信息

2 )根据部门id查询部门中的所有员工

分步查询的优点:可以实现延迟加载,但是必须在核心配置文件中设置全局配置信息:

lazyLoadingEnabled:延迟加载的全局开关。当开启时,所有关联对象都会延迟加载

aggressiveLazyLoading:当开启时,任何方法的调用都会加载该对象的所有属性。 否则,每个

属性会按需加载

此时就可以实现按需加载,获取的数据是什么,就只会执行相应的sql。此时可通过association和

collection中的fetchType属性设置当前的分步查询是否使用延迟加载,fetchType=”lazy(延迟加

载)|eager(立即加载)”

九、动态SQL

Mybatis框架的动态SQL技术是一种根据特定条件动态拼装SQL语句的功能,它存在的意义是为了解决

拼接SQL语句字符串时的痛点问题。

1 、if

/**
* 分步查询部门和部门中的员工
1
2
3
4
* @param did
* @return
*/
Dept getDeptByStep(@Param("did") int did);
1
2
3
4
5
6
7
8
9
10
11
<resultMap id="deptEmpStep" type="Dept">
<id property="did" column="did"></id>
<result property="dname" column="dname"></result>
<collection property="emps" fetchType="eager"
select="com.atguigu.MyBatis.mapper.EmpMapper.getEmpListByDid" column="did">
</collection>
</resultMap>
<!--Dept getDeptByStep(@Param("did") int did);-->
<select id="getDeptByStep" resultMap="deptEmpStep">
select * from t_dept where did = #{did}
</select>
/**
1
2
3
4
5
* 根据部门id查询员工信息
* @param did
* @return
*/
List<Emp> getEmpListByDid(@Param("did") int did);
1
2
3
4
<!--List<Emp> getEmpListByDid(@Param("did") int did);-->
<select id="getEmpListByDid" resultType="Emp">
select * from t_emp where did = #{did}
</select>

if标签可通过test属性的表达式进行判断,若表达式的结果为true,则标签中的内容会执行;反之标签中

的内容不会执行

2 、where

where和if一般结合使用:

a>若where标签中的if条件都不满足,则where标签没有任何功能,即不会添加where关键字

b>若where标签中的if条件满足,则where标签会自动添加where关键字,并将条件最前方多余的

and去掉

注意:where标签不能去掉条件最后多余的and

3 、trim

1
2
3
4
5
6
7
8
9
10
11
12
13
<!--List<Emp> getEmpListByMoreTJ(Emp emp);-->
<select id="getEmpListByMoreTJ" resultType="Emp">
select * from t_emp where 1=
<if test="ename != '' and ename != null">
and ename = #{ename}
</if>
<if test="age != '' and age != null">
and age = #{age}
</if>
<if test="sex != '' and sex != null">
and sex = #{sex}
</if>
</select>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<select id="getEmpListByMoreTJ2" resultType="Emp">
select * from t_emp
<where>
<if test="ename != '' and ename != null">
ename = #{ename}
</if>
<if test="age != '' and age != null">
and age = #{age}
</if>
<if test="sex != '' and sex != null">
and sex = #{sex}
</if>
</where>
</select>
1
2
3
4
5
6
7
8
9
10
<select id="getEmpListByMoreTJ" resultType="Emp">
select * from t_emp
<trim prefix="where" suffixOverrides="and">
<if test="ename != '' and ename != null">
ename = #{ename} and
</if>
<if test="age != '' and age != null">
age = #{age} and
</if>
<if test="sex != '' and sex != null">

trim用于去掉或添加标签中的内容

常用属性:

prefix:在trim标签中的内容的前面添加某些内容

prefixOverrides:在trim标签中的内容的前面去掉某些内容

suffix:在trim标签中的内容的后面添加某些内容

suffixOverrides:在trim标签中的内容的后面去掉某些内容

4 、choose、when、otherwise

choose、when、otherwise相当于if…else if..else

5 、foreach

1
2
3
4
sex = #{sex}
</if>
</trim>
</select>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!--List<Emp> getEmpListByChoose(Emp emp);-->
<select id="getEmpListByChoose" resultType="Emp">
select <include refid="empColumns"></include> from t_emp
<where>
<choose>
<when test="ename != '' and ename != null">
ename = #{ename}
</when>
<when test="age != '' and age != null">
age = #{age}
</when>
<when test="sex != '' and sex != null">
sex = #{sex}
</when>
<when test="email != '' and email != null">
email = #{email}
</when>
</choose>
</where>
</select>
1
2
3
4
5
6
7
8
9
10
11
12
13
<!--int insertMoreEmp(List<Emp> emps);-->
<insert id="insertMoreEmp">
insert into t_emp values
<foreach collection="emps" item="emp" separator=",">
(null,#{emp.ename},#{emp.age},#{emp.sex},#{emp.email},null)
</foreach>
</insert>
<!--int deleteMoreByArray(int[] eids);-->
<delete id="deleteMoreByArray">
delete from t_emp where
<foreach collection="eids" item="eid" separator="or">
eid = #{eid}
</foreach>

属性:

collection:设置要循环的数组或集合

item:表示集合或数组中的每一个数据

separator:设置循环体之间的分隔符

open:设置foreach标签中的内容的开始符

close:设置foreach标签中的内容的结束符

6 、SQL片段

sql片段,可以记录一段公共sql片段,在使用的地方通过include标签进行引入

十、MyBatis的缓存

1 、MyBatis的一级缓存

一级缓存是SqlSession级别的,通过同一个SqlSession查询的数据会被缓存,下次查询相同的数据,就

会从缓存中直接获取,不会从数据库重新访问

使一级缓存失效的四种情况:

1) 不同的SqlSession对应不同的一级缓存

2) 同一个SqlSession但是查询条件不同

3) 同一个SqlSession两次查询期间执行了任何一次增删改操作

4) 同一个SqlSession两次查询期间手动清空了缓存

2 、MyBatis的二级缓存

二级缓存是SqlSessionFactory级别,通过同一个SqlSessionFactory创建的SqlSession查询的结果会被

缓存;此后若再次执行相同的查询语句,结果就会从缓存中获取

二级缓存开启的条件:

a>在核心配置文件中,设置全局配置属性cacheEnabled=”true”,默认为true,不需要设置

b>在映射文件中设置标签

1
2
3
4
5
6
7
8
</delete>
<!--int deleteMoreByArray(int[] eids);-->
<delete id="deleteMoreByArray">
delete from t_emp where eid in
<foreach collection="eids" item="eid" separator="," open="(" close=")">
#{eid}
</foreach>
</delete>
1
2
3
4
<sql id="empColumns">
eid,ename,age,sex,did
</sql>
select <include refid="empColumns"></include> from t_emp

c>二级缓存必须在SqlSession关闭或提交之后有效

d>查询的数据所转换的实体类类型必须实现序列化的接口

使二级缓存失效的情况:

两次查询之间执行了任意的增删改,会使一级和二级缓存同时失效

3 、二级缓存的相关配置

在mapper配置文件中添加的cache标签可以设置一些属性:

eviction属性:缓存回收策略

LRU(Least Recently Used) – 最近最少使用的:移除最长时间不被使用的对象。

FIFO(First in First out) – 先进先出:按对象进入缓存的顺序来移除它们。

SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。

WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。

默认的是 LRU。

flushInterval属性:刷新间隔,单位毫秒

默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新

size属性:引用数目,正整数

代表缓存最多可以存储多少个对象,太大容易导致内存溢出

readOnly属性:只读,true/false

true:只读缓存;会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了

很重要的性能优势。

false:读写缓存;会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是

false。

4 、MyBatis缓存查询的顺序

先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用。

如果二级缓存没有命中,再查询一级缓存

如果一级缓存也没有命中,则查询数据库

SqlSession关闭之后,一级缓存中的数据会写入二级缓存

5 、整合第三方缓存EHCache

a>添加依赖

jar包名称 作用

mybatis-ehcache Mybatis和EHCache的整合包

ehcache EHCache核心包

slf4j-api SLF4J日志门面包

logback-classic 支持SLF4J门面接口的一个具体实现

b>各jar包功能

c>创建EHCache的配置文件ehcache.xml

d>设置二级缓存的类型

e>加入logback日志

存在SLF4J时,作为简易日志的log4j将失效,此时我们需要借助SLF4J的具体实现logback来打印日志。

创建logback的配置文件logback.xml

1
2
3
4
5
6
7
8
9
10
11
12
<!-- Mybatis EHCache整合包 -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.1</version>
</dependency>
<!-- slf4j日志门面的一个具体实现 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
1
2
3
4
5
<?xml version="1.0" encoding="utf-8" ?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<!-- 磁盘保存路径 -->
<diskStore path="D:\atguigu\ehcache"/>
1
2
3
4
5
6
7
8
9
10
11
<defaultCache
maxElementsInMemory="1000"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="true"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>
1
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

f>EHCache配置文件说明

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">
<!-- 指定日志输出的位置 -->
<appender name="STDOUT"
class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!-- 日志输出的格式 -->
<!-- 按照顺序分别是:时间、日志级别、线程名称、打印日志的类、日志主体内容、换行 -
->
<pattern>[%d{HH:mm:ss.SSS}] [%-5level] [%thread] [%logger]
[%msg]%n</pattern>
</encoder>
</appender>
1
2
3
4
<root level="DEBUG">
<!-- 指定打印日志的appender,这里通过“STDOUT”引用了前面配置的appender -->
<appender-ref ref="STDOUT" />
</root>
1
<logger name="com.atguigu.crowd.mapper" level="DEBUG"/>
1
</configuration>

属性名

作用

maxElementsInMemory 是 在内存中缓存的element的最大数目

maxElementsOnDisk 是

在磁盘上缓存的element的最大数目,若是 0 表示无

穷大

eternal 是

设定缓存的elements是否永远不过期。 如果为

true,则缓存的数据始终有效, 如果为false那么还

要根据timeToIdleSeconds、timeToLiveSeconds

判断

overflowToDisk 是

设定当内存缓存溢出的时候是否将过期的element

缓存到磁盘上

timeToIdleSeconds 否

当缓存在EhCache中的数据前后两次访问的时间超

过timeToIdleSeconds的属性取值时, 这些数据便

会删除,默认值是0,也就是可闲置时间无穷大

timeToLiveSeconds 否

缓存element的有效生命期,默认是0.,也就是

element存活时间无穷大

diskSpoolBufferSizeMB 否

DiskStore(磁盘缓存)的缓存区大小。默认是

30MB。每个Cache都应该有自己的一个缓冲区

diskPersistent 否

在VM重启的时候是否启用磁盘保存EhCache中的数

据,默认是false。

diskExpiryThreadIntervalSeconds 否

磁盘缓存的清理线程运行间隔,默认是 120 秒。每

个120s, 相应的线程会进行一次EhCache中数据的

清理工作

memoryStoreEvictionPolicy 否

当内存缓存达到最大,有新的element加入的时

候, 移除缓存中element的策略。 默认是LRU(最

近最少使用),可选的有LFU(最不常使用)和

FIFO(先进先出)

十一、MyBatis的逆向工程

正向工程:先创建Java实体类,由框架负责根据实体类生成数据库表。Hibernate是支持正向工程

的。

逆向工程:先创建数据库表,由框架负责根据数据库表,反向生成如下资源:

Java实体类

Mapper接口

Mapper映射文件

1 、创建逆向工程的步骤

a>添加依赖和插件

b>创建MyBatis的核心配置文件

c>创建逆向工程的配置文件

文件名必须是:generatorConfig.xml

1
2
3
4
5
6
7
8
<!-- 依赖MyBatis核心包 -->
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
</dependencies>
1
2
<!-- 控制Maven在构建过程中相关配置 -->
<build>
1
<plugins>
1
2
3
4
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.0</version>
1
<dependencies>
1
2
3
4
5
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.2</version>
</dependency>
1
2
3
4
5
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.2</version>
</dependency>
1
2
3
4
5
6
7
8
9
10
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.8</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
1
2
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration

d>执行MBG插件的generate目标

效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<!--
targetRuntime: 执行生成的逆向工程的版本
MyBatis3Simple: 生成基本的CRUD(清新简洁版)
MyBatis3: 生成带条件的CRUD(奢华尊享版)
-->
<context id="DB2Tables" targetRuntime="MyBatis3Simple">
<!-- 数据库的连接信息 -->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/mybatis"
userId="root"
password="123456">
</jdbcConnection>
<!-- javaBean的生成策略-->
<javaModelGenerator targetPackage="com.atguigu.mybatis.bean"
targetProject=".\src\main\java">
<property name="enableSubPackages" value="true" />
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!-- SQL映射文件的生成策略 -->
<sqlMapGenerator targetPackage="com.atguigu.mybatis.mapper"
targetProject=".\src\main\resources">
<property name="enableSubPackages" value="true" />
</sqlMapGenerator>
<!-- Mapper接口的生成策略 -->
<javaClientGenerator type="XMLMAPPER"
targetPackage="com.atguigu.mybatis.mapper" targetProject=".\src\main\java">
<property name="enableSubPackages" value="true" />
</javaClientGenerator>
<!-- 逆向分析的表 -->
<!-- tableName设置为*号,可以对应所有表,此时不写domainObjectName -->
<!-- domainObjectName属性指定生成出来的实体类的类名 -->
<table tableName="t_emp" domainObjectName="Emp"/>
<table tableName="t_dept" domainObjectName="Dept"/>
</context>
</generatorConfiguration>

2 、QBC查询

十二、分页插件

1 、分页插件使用步骤

a>添加依赖

1
2
3
4
5
6
7
8
@Test
public void testMBG() throws IOException {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSession sqlSession = new
SqlSessionFactoryBuilder().build(is).openSession(true);
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
EmpExample empExample = new EmpExample();
//创建条件对象,通过andXXX方法为SQL添加查询添加,每个条件之间是and关系
1
2
3
4
5
6
7
8
9
empExample.createCriteria().andEnameLike("a").andAgeGreaterThan( 20 ).andDidIsNot
Null();
//将之前添加的条件通过or拼接其他条件
empExample.or().andSexEqualTo("男");
List<Emp> list = mapper.selectByExample(empExample);
for (Emp emp : list) {
System.out.println(emp);
}
}

b>配置分页插件

在MyBatis的核心配置文件中配置插件

2 、分页插件的使用

a>在查询功能之前使用PageHelper.startPage(int pageNum, int pageSize)开启分页功能

pageNum:当前页的页码

pageSize:每页显示的条数

b>在查询获取list集合之后,使用PageInfo pageInfo = new PageInfo<>(List list, int

list:分页之后的数据

c>分页相关数据

PageInfo{

pageNum=8, pageSize=4, size=2, startRow=29, endRow=30, total=30, pages=8,

list=Page{count=true, pageNum=8, pageSize=4, startRow=28, endRow=32, total=30,

pages=8, reasonable=false, pageSizeZero=false},

prePage=7, nextPage=0, isFirstPage=false, isLastPage=true, hasPreviousPage=true,

hasNextPage=false, navigatePages=5, navigateFirstPage4, navigateLastPage8,

}

常用数据:

pageNum:当前页的页码

pageSize:每页显示的条数

size:当前页显示的真实条数

total:总记录数

pages:总页数

prePage:上一页的页码

nextPage:下一页的页码

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>
1
2
3
4
<plugins>
<!--设置分页插件-->
<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>

isFirstPage/isLastPage:是否为第一页/最后一页

hasPreviousPage/hasNextPage:是否存在上一页/下一页

MyBatis插入中文时乱码

问题解决:使用Mybatis向数据库插入数据时,中文数据乱码?


  • 首先,检查数据库设置是否为UTF-8,如果是数据库设置编码问题,则改正。

  • 如果问题依旧无法解决,则向Mybatis的核心配置文件mybatis-config.xml中的数据库环境配置中的url添加以下信息:

    1
    ?useUnicode=true&amp;characterEncoding=utf-8&amp;

    如:

    1
    <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&amp;characterEncoding=utf-8&amp;"/>

问题解决:mybatis插入数据中文乱码

问题解决:使用Mybatis向数据库插入数据时,中文数据乱码?


  • 首先,检查数据库设置是否为UTF-8,如果是数据库设置编码问题,则改正。

  • 如果问题依旧无法解决,则向Mybatis的核心配置文件mybatis-config.xml中的数据库环境配置中的url添加以下信息:

    1
    ?useUnicode=true&amp;characterEncoding=utf-8&amp;

    如:

    1
    <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&amp;characterEncoding=utf-8&amp;"/>
  • SpringBoot中:

    1
    spring.datasource.url=jdbc:mysql://localhost:3306/shop?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
    
    

软件工程导论概念

第1章 软件工程学概述

软件危机计算机软件的开发和维护过程中所遇到的一系列严重问题。

软件危机的典型表现:

  1. 对软件开发成本和进度的估计常常很不准确;

  2. 用户对完成的软件系统不满意的现象经常发生;

  3. 软件产品的质量往往靠不住;

  4. 软件常常是不可维护的;

  5. 软件通常没有适当的文档资料;

  6. 软件成本在计算机系统总成本中所占的比例逐年上升;

  7. 软件开发生产率提高的速度跟不上计算机应用的发展趋势。

产生软件危机的原因

  1. 软件本身特点造成;对于计算机系统来说,软件是逻辑部件,软件开发过程没有统一的、公认的方法论和规范指导,造成软件维护困难。

  2. 软件开发与维护的方法不正确。对软件产品缺乏正确认识,没有真正理解软件产品是一个完整的配置组成。造成开发中制定计划盲目、编程草率,不考虑维护工作的必要性。

主要表现:

  1. 忽视软件需求分析;

  2. 认为软件开发就是写程序并使之运行;

  3. 轻视软件维护;

解决软件危机的途径

  1. 推广使用在实践中总结出来的开发软件的成功技术和方法,并研究探索更有效的技术和方法;

  2. 开发和使用更好的软件工具

  3. 良好的组织管理措施。

软件工程的介绍

早期:软件工程就是为了经济地获得可靠的且能在实际机器上有效地运行的软件,而建立和使用完善的工程原理。

1993年IEEE:软件工程是

  1. 把系统的、规范的、可度量的途径应用于软件开发、运行和维护过程;
  2. 研究(1)中提到的途径。

软件工程的本质特性:

  1. 软件工程关注于大型程序的构造;

  2. 软件工程的中心课题是控制复杂性;

  3. 软件经常变化;

  4. 开发软件的效率非常重要;

  5. 和谐地合作是软件开发的关键;

  6. 软件必须有效地支持它的用户;

  7. 在软件工程领域中是由具有一种文化背景的人替具有另一种文化背景的人创造产品。

软件工程的基本原理

  1. 用分阶段的生命周期计划严格管理;

  2. 坚持进行阶段评审;

  3. 实行严格的产品控制;

  4. 采用现代程序设计技术;

  5. 结果能清楚地审查;

  6. 开发小组的人员应该少而精;

  7. 承认不断改进软件工程实践的必要性。

软件工程方法学

概念:通常把在软件生命周期全过程中使用的一整套技术方法的集合称为方法学(Methodology),也称为范型(Paradigm)。

软件工程方法学的三要素:方法、工具和过程

1. 传统方法学

也称为生命周期方法学或结构化范型。

结构化方法(Structure Method)有:

  1. 结构化设计方法(SD);

  2. 结构化分析方法(SA);

  3. 结构化分析与设计技术(SADT)

  4. JACKSON方法

  5. WARNIER方法

2. 面向对象方法学

把数据和对数据的操作紧密结合起来的方法,模拟人类认识世界解决问题的方法和过程。

面向对象的方法=对象(属性与服务的封装)+分类+继承+用消息通信

软件生命周期

指软件从提出到最终被淘汰的这个存在期。

  1. 软件定义;

​ A.问题定义 B.可行性研究 C.需求分析

  1. 软件开发;

​ D.总体设计 E.详细设计

​ F.编码和单元测试 G.综合测试

  1. 运行维护。

软件生命周期各个阶段:

1.问题定义;

2.可行性研究;

3.需求分析;

4.总体设计(概要设计);

5.详细设计;

6.编码与单元测试;

7.综合测试;

8.维护。

问题定义报告的内容包括:

  1. 软件项目标题;

  2. 软件目标;

  3. 软件用户对象;

  4. 软件规模。

软件过程

为了获得高质量软件所需要完成的一系列任务的框架,它规定了完成各项任务的工作步骤。

使用资源将输入转化为输出的活动所构成的系统。

瀑布模型:

  1. 阶段间具有顺序性和依赖性

  2. 推迟实现的观点

  3. 质量保证的观点

优点:采用规范的方法;严格规定每个阶段提交的文档;要求每个阶段交出的产品必须经过验证。

快速原型模型:

优点:不带反馈环,基本上是线性顺序进行。

增量模型:

优点:能较短时间内提交可完成部分工作的产品;可以使用户有充裕的时间学习和适应新产品。

第2章 可行性研究

可行性研究的目的是:用最小的代价在尽可能短的时间内确定问题是否有解,以及是否值得去解。

可行性研究的内容:

  1. 技术可行性:使用现有的技术能否实现这个系统

  2. 经济可行性:系统的经济效益能否超过它的开发成本

  3. 操作可行性:操作可行性评价系统运行后会引起的各方面变化

  4. 社会可行性

可行性研究的步骤

  1. 复查系统规模和目标;

  2. 研究目前正在使用的系统;

  3. 导出新系统的高层逻辑模型(数据流图、数据字典);

  4. 重新定义问题;

  5. 导出和评价供选择的解法(物理解决方案);

  6. 推荐行动方案;

  7. 草拟开发计划;

  8. 书写文档提交审查。

数据字典

数据字典:对数据流图中包含的所有元素的定义的集合;

可行性研究阶段,数据流图与数据字典共同构成系统的逻辑模型。

数据字典应该对下列元素进行定义:

  1. 数据流;

  2. 数据元素(数据流分量);

  3. 数据存储;

  4. 处理。

成本估计

  1. 代码行技术:软件成本 = 每行代码的平均成本×估计的源代码总行数

  2. 任务分解技术

  3. 自动估计成本技术

第3章 需求分析

需求分析的任务

确定对系统的综合要求

  1. 功能需求

  2. 性能需求

  3. 可靠性和可用性需求

  4. 出错处理需求

  5. 接口需求

  6. 约束

  7. 逆向需求

  8. 将来可能提出的要求

与用户沟通获取需求的方法

访谈:

  1. 正式访谈:系统分析员提出事先准备好的问题。

  2. 非正式访谈:提出一些用户可以自由回答的开放性问题,鼓励被访者说出自己的想法。

面向数据流自顶向下求精

简易的应用规格说明技术

快速建立软件原型

分析建模与规格说明

分析建模

  1. 实体联系图
  2. 数据流图
  3. 状态转换图

数据规范化

  1. 第一范式
  2. 第二范式
  3. 第三范式

第5章 总体设计

设计过程

  1. 设想供选择的方案

  2. 选择合理的方案

​ 对每个合理的方案要提供:

  A.系统流程图
  
  B.组成系统的物理元素清单
  
   C.成本/效益分析
  
  D.实现这个系统的进度计划
  1. 推荐最佳方案

  2. 功能分解

  3. 设计软件结构

  4. 数据库设计

  5. 制定测试计划

  6. 书写文档

    A.系统说明

    B. 用户手册

    C.测试计划

    D.详细的实现计划

​ E.数据库设计结果

  1. 审查和复审

设计原理

模块化

抽象

逐步求精

信息隐蔽和局部化

模块独立:

模块的独立性很重要,因为:

  1. 有效的模块化的软件比较容易开发出来;

  2. 独立的模块比较容易测试和维护。

模块独立程度可以由两个定性标准度量:耦合与内聚。

耦合

耦合:指软件结构内不同模块彼此之间相互依赖(连接)的紧密程度。

模块的偶合分四类:

  1. 数据耦合

​ 两个模块之间只是通过参数交换信息,而且交换的信息仅仅是数据。

 数据耦合是**最低程度**的耦合。 
  1. 控制耦合

​ 两个模块之间所交换的信息包含控制信息。

 控制耦合是中等程度的耦合。
  1. 公用耦合

​ 两个或多个模块通过一个公共区相互作用时的耦合。

​ 公共区可以是:全程数据区、共享通信区、内存公共覆盖区、任何介质上的文件、物理设备等。

​ 软件结构中存在大量的公用耦合时会给诊断错误带来困难。

  1. 内容耦合

​ 一个模块与另一个模块的内容直接发生联系。

​ 内容耦合对维护会带来严重的困难。

​ 内容耦合是最高程度的耦合,应该避免采用。

应追求尽可能松散耦合,这样模块间的联系就越小,模块的独立性就越强,对模块的测试、维护就越容易。

尽量使用数据耦合,少用控制耦合,限制公用耦合,完全不用内容偶合

内聚

内聚:一个模块内部各个元素彼此结合的紧密程度。

  1. 功能内聚 :功能内聚是最高程度的内聚

  2. 顺序内聚

    高内聚


  3. 通信内聚

  4. 过程内聚

    中内聚


  5. 时间内聚

  6. 逻辑内聚

  7. 偶然内聚:偶然内聚是最差的一种内聚。

    地内聚

力求做到高内聚,尽量少用中内聚,不用低内聚。

启发式规则

  1. 改进软件结构提高模块独立性

  2. 模块规模应该适中

  3. 深度、宽度、扇出和扇入都应适当

    深度:软件结构中控制的层数;

    宽度:软件结构内同一个层次上的模块总数的最大值;

    扇出:一个模块直接控制(调用)其它模块的数目;

    扇入:一个模块被其它模块调用的数目

  4. 模块的作用域应该在控制域之内

​ 作用域:受该模块内一个判定影响的所有模块的集合。

​ 控制域:模块本身以及所有从属于它的模块的集合

  1. 力争降低模块接口的复杂度

  2. 设计单入口、单出口的模块

  3. 模块功能应该可以预测

面向数据流的设计方法

数据流可以分为两种类型:

  1. 变换型数据流
  2. 事务型数据流

第6章 详细设计

目标:确定如何具体实现所要求的系统。

结构程序设计

结构程序设计: 一种设计程序的技术,它采用自顶向下逐步求精的设计方法和单入口单出口的控制结构。 一种设计程序的技术,它采用自顶向下逐步求精的设计方法和单入口单出口的控制结构。

使用结构程序设计技术的好处:

  1. 提高软件开发工程的成功率和生产率;

  2. 系统有清晰的层次结构,容易阅读理解;

  3. 单入口单出口的控制结构,容易诊断纠正;

  4. 模块化可以使得软件可以重用;

  5. 程序逻辑结构清晰,有利于程序正确性证明。

经典的结构程序设计:只允许使用顺序、IF_THEN_ELSE选择和DO_WHILE循环;

扩展的结构程序设计:除了三种基本控制结构,还使用DO_CASEDO_UNTIL循环;

修正的结构程序设计:除了三种基本控制结构和两种扩充结构,还使用BREAK等结构。

人机界面设计

设计问题

  1. 系统响应时间;

  2. 用户帮助;

  3. 出错信息处理;

  4. 命令交互

人机界面设计指南

  1. 一般交互指南;

  2. 信息显示指南;

  3. 数据输入指南

过程设计的工具

程序流程图

程序流程图的缺点

  1. 程序流程图本质上不是逐步求精的好工具,它诱使程序员过早地考虑程序的控制流程,而不去考虑程序的全局结构。

  2. 程序流程图中用箭头代表控制流,因此程序员不受任何约束,可以完全不顾结构程序设计的精神,随意转移控制。

  3. 程序流程图不易表示数据结构。

盒图(N-S图)

PAD图

判定表

判定树

过程设计语言

面向数据结构的设计方法

Jackson图

改进的Jackson图

程序复杂度的定量度量

McCabe方法:

  1. 流图:仅描绘程序的控制流程

  2. 计算环形复杂度的方法

    a.环形复杂度 V(G)等于流图中的区域数;

    b.环形复杂度 V(G)=E-N+2,其中E是流图中边的条数,N是结点数;

    c.环形复杂度 V(G)=P+1,其中P为流图中判定结点的数目。

  3. 环形复杂度的用途

    对测试难度的一种定量度量,也能对软件最终的可靠性给出某种预测。

Halstead方法:

根据程序中运算符和操作数的总数来度量程序复杂度。

N = N1 + N2

其中:N定义为程序长度;

​ N1为程序中运算符出现的总次数;

​ N2为操作数出现的总次数。

Halstead给出预测程序长度的公式为:

H = n1log2n1 + n2log2n2

其中:H定义为程序预测长度;

​ n1为程序中使用的不同运算符(包括关键字)的个数;

​ n2为程序中使用的不同操作数(变量和常量)的个数。

程序的预测长度H和实际程序长度N非常接近。

Halstead还给出了预测程序中包含错误的个数的公式:

E = N log2(n1+n2) / 3000

第7章 实现

编码和测试统称为实现。

编码:把软件设计结果翻译成程序。

测试:检测程序并改正错误的过程。

编码

选择程序设计语言

  1. 汇编语言;

  2. 高级语言。

从应用特点看,高级语言可分为:

  1. 基础语言
  2. 结构化语言
  3. 专用语言

选择一种编程语言的理论标准:

  1. 有理想的模块化机制;

  2. 可读性好的控制结构和数据结构;

  3. 便于调试和提高软件可靠性;

  4. 编译程序发现程序错误的能力强;

  5. 有良好的独立编译机制。

主要的实用标准:

  1. 系统用户要求

  2. 可以使用的编译程序

  3. 可以得到的软件工具

  4. 工程规模

  5. 程序员知识

  6. 软件可移植性要求

  7. 软件的应用领域

编码风格:

1.程序内部的文档

2.数据说明

3.语句构造

4.输入/输出

5.效率

​ A程序运行时间

​ B.存储器效率

​ C.输入/输出效率

软件测试基础

软件测试的目标

有关测试的一些规则:

  1. 测试是为了发现程序中的错误而执行程序的过程;

  2. 好的测试方案是极可能发现迄今为止尚未发现的错误的测试方案;

  3. 成功的测试是发现了至今为止尚未发现的错误的测试。

软件测试准则

  1. 所有测试都应该能追溯到用户需求;

  2. 应该远在测试前就制定出测试计划;

  3. 把Pareto原理应用到软件测试中;

  4. 应该从“小规模”测试开始,并逐步进行“大规模”测试;

  5. 穷举测试是不可能的;

  6. 为了达到最佳测试效果,应该由独立的第三方从事测试工作。

测试方法

黑盒测试:如果已经知道软件应该具有的功能,可以通过测试来检验是否每个功能都能正常使用,这种测试称黑盒测试。也称功能测试。

白盒测试:也称结构测试。如果知道软件内部工作过程,可以通过测试来检验软件内部动作是否按照规格说明书的规定正常进行,这种测试称为白盒测试。

软件测试的步骤

  1. 模块测试

  2. 子系统测试

  3. 系统测试

  4. 验收测试

  5. 平行运行

单元测试

最小的单元-模块

测试重点

  1. 测试重点
  2. 模块接口
  3. 局部数据结构
  4. 重要的执行路径
  5. 出错处理通路
  6. 边界条件

代码审查

审查小组:

  1. 组长;

  2. 程序的设计者;

  3. 程序的编写者;

  4. 程序的测试者。

计算机测试

  1. 驱动程序:相当于一个“主程序”,用来把测试数据传送给被测试的模块,并打印有关结果。

  2. 存根程序:用来代替被测试模块所调用的模块,相当于“虚拟子程序”。

集成测试

集成测试是组装软件的系统化技术,它将经过单元测试的模块联系在一起进行测试。

由模块组装成程序时有两种方法:

  1. 非渐增式测试方法:先分别测试每个模块,再把所有模块按设计要求放在一起结合成所要的程序。

  2. 渐增式测试方法:每次增加一个待测试模块,把它同已经测试好的那些模块结合起来进行测试,反复进行直到完成所有模块测试的方法。

使用渐增式测试方法把模块结合到软件系统中去时,有自顶向下和自底向上两种集成方法。

一、自顶向下集成

自顶向下集成是一种递增的装配软件结构的方法,这种方法应用非常广泛。它需要存根程序,但是不需要驱动程序。

这种方法的思想是:从主控模块(主程序)开始,沿软件的控制层次向下移动,逐渐把各个模块结合起来。

在自顶向下结合方法中,如何将所有模块组装到软件结构中,又有两种方法:

  1. 深度优先策略

  2. 宽度优先策略

二、自底向上集成

自底向上集成方法是从软件结构最底层模块开始进行组装和测试,它与自顶向下结合方法相反,需要驱动程序,不需要存根程序。

改进:

  1. 改进的自顶向下测试方法;

  2. 混合法。

回归测试

指重新执行已经做过的部分测试。

回归测试用于保证由于调试或其他原因引起的程序变化,不会导致额外错误的测试活动。


确认测试

确认测试的范围

  1. 也称为验收测试,目标是验证软件的有效性。
  2. 如果软件的功能和性能符合用户的期待,软件就是有效的。
  3. 软件规格说明书是进行确认测试的基础。

确认测试的主要特点及内容有:

  1. 某些已经测试过的纯粹技术性的测试项可能不需要再次测试,而对用户特别感兴趣的功能或性能,可能需要增加一些测试;

  2. 通常确认测试主要使用实际生产中的数据来进行测试;

  3. 确认测试必须有用户的积极参与,甚至以用户为主,可能需要进行一些与用户使用步骤有关的测试。

确认测试一般使用黑盒测试法。

软件配置复查:

目的:保证软件配置的所有成分都齐全,质量符合要求,文档与程序完全一致,而且已经编好目录。

Alpha和Beta测试

Alpha测试:用户在开发者的场所进行测试,并且在开发者的指导下进行,测试在受控环境中进行,开发者记录发现的错误和问题;

Beta测试:用户在一个或多个客户场所进行测试,不受开发者控制,测试者记录发现的问题和错误,定期将问题报告发送给开发者。


白盒测试技术

逻辑覆盖

  1. 语句覆盖:设计的测试用例能使程序中每条语句至少执行一次。

  2. 判定覆盖:选取足够的测试用例,使得程序中每个判断的可能结果都至少执行一次,也就是说使程序的每个判断分支至少通过一次。

  3. 条件覆盖:选择足够的测试用例,使得程序中每个判定表达式的每个条件都取到各种可能的结果。

  4. 判定/条件覆盖:选取足够的测试用例使得同时满足判定覆盖和条件覆盖的要求。

  5. 条件组合覆盖:选取足够的测试用例,使得每个判定表达式中条件的各种可能的组合都至少出现一次。

  6. 点覆盖:选取足够多的测试用例,使得程序执行路径至少经过程序图中每个节点一次。

  7. 边覆盖:选取足够多的测试用例,使得程序执行路径至少经过程序图中每条边一次。

  8. 路径覆盖:选取足够多的测试用例,使得程序的每条可能路径都至少执行一次。


黑盒测试技术

等价类划分是一种黑盒测试技术。

用等价类划分设计测试用例时,主要分两步:划分等价类、确定测试用例。

边界值分析测试法属黑盒测试。

错误推测法在很大程度上靠直觉和经验进行。


调试

调试是在测试发现错误之后排除错误的过程。

调试途径:

  1. 蛮干法

  2. 回溯法

  3. 原因排除法


软件可靠性

软件可靠性:是程序在给定的时间间隔内,按照规格说明书的规定成功地运行的概率。

软件可用性:程序在给定的时间点,按照规格说明书的规定,成功地运行的概率。

可靠性和可用性的区别:可靠性是在0到t时间间隔内,系统没有失效的概率。而可用性是在t时刻,系统是正常运行的概率。

平均维修时间MTTR是修复一个故障平均需要用的时间,取决于维护人员的技术水平和对系统熟悉程度。

平均无故障时间MTTF是系统按照规格说明书规定成功地运行的平均时间,取决于系统中潜伏的错误数量。

估计错误总数ET的方法

  1. 植入故障法

  2. 分别测试法


第8章 维护

软件维护是软件生命周期的最后一个阶段。

任务:维护软件的正常运行,不断改进软件的性能和质量,为软件的进一步推广应用和更新替换做积极工作。

软件交付使用 :

  1. 软件验收测试以后,就标志着软件设计开发阶段的结束。
  2. 而软件交付用户使用,才真正标志漫长的维护阶段的开始。

软件交付使用的方式

  1. 直接方式

​ 优点:转换简单,费用最省。

​ 缺点:风险大

  1. 并行方式

​ 优点:

  A. 可以对系统进行全面测试,减少了新系统失灵带来的风险,因为旧系统也仍然存在;
  
  B.用户也能够有一段熟悉新系统的时间。

​ 缺点:

 所需费用较高,双系统要投入更多的人力财力。
  1. 逐步方式

软件维护的定义:在软件已经交付使用后,为了改正错误或满足新的需要而进行修改的过程。

软件维护的原因

  1. 改正在特定使用条件下暴露出来的一些潜在程序错误或设计缺陷;

  2. 因在软件使用过程中数据环境发生变化(如所要处理的数据发生变化)或处理环境发生变化(如硬件或软件操作系统等发生变化),需要修改软件,以适应这种变化;

  3. 用户和数据处理人员在使用时常提出改进现有功能、增加新功能、以及改善总体性能的要求,为满足这些要求,需要修改软件。

软件维护的类型

  1. 改正性维护

  2. 适应性维护

  3. 完善性维护

  4. 预防性维护

完善性维护占软件维护工作的大部分。


软件维护的特点

结构化维护与非结构化维护的差别:

  1. 非结构化维护

​ 软件配置的唯一成分是代码,维护从评价程序代码开始,对软件结构、数据结构、系统接口、设计约束等常产生误解,不能进行回归 测试,维护代价大。

  1. 结构化维护

    有完整的软件配置,维护从评价设计文档开始,确定软件结构、性能和接口特点,现修改设计,接着修改代码,再进行回归测试。

软件维护的典型问题:

  1. 如果维护时只有程序代码而没有注释说明,维护起来就相当困难;

  2. 由于软件维护阶段时间长,软件开发人员经常流动,所以在维护时,不可能所有的维护工作都依靠原来的开发人员。这会使得维护工作量增加;

  3. 软件没有足够的文档资料,或者程序修改后与文档资料不一致;

  4. 绝大多数软件在设计时没有考虑将来的修改,所以建议采用功能独立的模块化设计原则,增加软件的可维护性;

  5. 软件维护被许多人视为一种毫无吸引力的工作,因为维护工作常常受到挫折。


软件维护过程

  1. 维护组织

  2. 维护报告

​ 根据软件问题报告(维护要求),作出的软件修改报告包含的信息主要有:

​ 1)满足维护要求表中提出的要求所需要的工作量;

​ 2)维护要求的性质;

​ 3)这项要求的优先次序;

​ 4)与修改有关的事后数据(如测试数据等)。

  1. 维护的事件流

  2. 保存维护记录

​ 程序标识源语句数;机器指令数;使用的程序设计语言;程序安装的日期;自安装以来程序运行次数;

  1. 评价维护活动

​ 1)每次程序运行平均失效的次数;

​ 2)用于每一类维护活动的总人时数;

​ 3)平均每个程序、每种维护类型所做的程序变动数;

​ 4)维护过程中增加或删除一个源语句平均花费的人时数;

​ 5)维护每种语言平均花费的人时数;

​ 6)一张维护要求表的平均周转时间;

​ 7)不同维护类型所占的百分比。


软件的可维护性

决定软件可维护性的因素

  1. 可理解性

  2. 可测试性

  3. 可修改性

  4. 可移植性

  5. 可重用性

文档:影响软件可维护性的决定因素

  1. 用户文档:主要描述系统功能和使用方法,并不关心这些功能是怎样实现的。

  2. 系统文档:描述系统设计、实现和测试等各方法的内容。

  3. 用户文档

​ 1)功能描述;

​ 2)安装文档;

​ 3)使用手册;

​ 4)参考手册;

​ 5)操作员指南;

  1. 系统文档

可维护性复审

测试结束时进行正式的可维护性复审,称为配置复审,目的是:保证软件配置的所有成分是完整的、一致的和可理解的。


第9章 面向对象方法学引论

面向对象方法学概述

面向对象方法的优点:

  1. 与人们习惯的思维方法一致;

  2. 稳定性好;

  3. 可重用性好;

  4. 较易开发大型软件产品;

  5. 可维护性好。

对象的特点:

  1. 以数据为中心;

  2. 对象是主动的;

  3. 实现了数据封装;

  4. 本质上具有并行性;

  5. 模块独立性好。

对象是具有相同状态的一组操作的集合。

类就是对具有相同数据和相同操作的一组相似对象的定义

方法,是对象所能执行的操作。

消息就是用来请求对象执行某个处理或回答某些信息的要求。

属性,是类中定义的数据。

封装就是信息隐藏,通过封装对外界隐藏了对象的实现细节。

继承,是指能够直接获得已有的性质和特征,而不必重复定义它们。

多态性,指子类对象可以象父类对象那样使用,同样的消息既可以发送给父类对象,也可以发送给子类对象。

重载:1. 函数重载 2. 运算符重载


面向对象建模

  1. 对象模型:描述系统的数据结构;

  2. 动态模型:描述系统的控制结构;

  3. 功能模型:描述系统的功能。

对象模型是最基本、最重要的。

泛化(继承):

1)普通泛化


第11章 面向对象设计

面向对象设计的准则

  1. 模块化:对象就是模块。它把数据结构和操作(方法)紧密地结合在一起构成模块。

  2. 抽象:类实际上是一种抽象数据类型

  3. 信息隐蔽:信息隐蔽通过对象的封装性实现

  4. 弱耦合

​ 对象间的耦合有两大类:a.交互耦合 b.继承偶合

  1. 强内聚

  2. 可重用


启发规则

1. 设计结果应该清晰易懂;

​ 影响的主要因素:1.用词一致;2.使用已有的协议;3.减少消息模式的数目;4.避免模糊的定义。

2.一般—特殊结构的深度应适当

3. 设计简单的类设计小而简单的类,便于开发和管理;

注意几点:

​ 1.避免包含过多的属性;

​ 2.有明确的定义;

​ 3.尽量简化对象之间的合作关系;

​ 4.不要提供太多服务。

4.使用简单的协议

5.使用简单的服务

6.把设计变动减至最小


软件重用

重用的三个层次:

​ 1)知识重用;

​ 2)方法和标准的重用;

​ 3)软件成分的重用。

软件成分的重用级别:

1)代码重用

​ a. 源代码剪贴;

​ b. 源代码包含;

​ c. 继承;

2)设计结果重用

3)分析结果重用

典型的可重用软件成分

1)项目计划; 2)成本计划;

3)体系结构; 4)需求模型和规格说明;

5)设计; 6)源代码;

7)用户文档和技术文档;8)用户界面;

9)数据; 10)测试用例。


设计问题域子系统

1. 调整需求

2. 重用已有的类

3. 组合问题域的类

4. 增添基类以定义公共函数集合

5. 调整继承层次


设计人机交互子系统

设计人机交互子系统的策略:

  1. 分类用户;

  2. 描述用户;

  3. 设计命令层次;

  4. 设计人机交互类。


设计数据管理子系统

选择数据存储管理模式

  1. 文件管理系统

  2. 关系数据库管理系统

  3. 面向对象数据库管理系统


面向对象语言的优点:

  1. 一致的表示方法
  2. 可重用性
  3. 可维护性

选择面向对象语言时应考虑的技术特点:

  1. 支持类与对象概念的机制

  2. 实现整体-部分(聚集)结构的机制

  3. 实现一般-特殊(泛化)结构的机制

  4. 实现属性和服务的机制

  5. 类型检查

  6. 类库

  7. 效率

  8. 持久保存对象

  9. 参数化类

  10. 开发环境

选择面向对象语言应考虑的因素:

  1. 将来能否占主导地位

  2. 可重用性

  3. 类库和开发环境

  4. 其他因素


程序设计风格

  1. 提高可重用性
  2. 提高可扩充性
  3. 提高健壮性

面向对象的集成测试

两种不同的测试策略:

  1. 基于线程的测试:将响应系统的一个输入或一个事件所需要的哪些类集成起来测试。

  2. 基于使用的测试先测试独立类,再测试使用独立类的下一层次的类(依赖类),重复直至完毕。

测试类的方法:

  1. 随机测试

  2. 划分测试

  3. 基于故障的测试

集成测试方法:

1. 多类测试

2. 从动态模型导出测试用例

最大子段和问题的多算法实现

最大子段和问题的多算法实现

1.BF

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <stdio.h>

void maxSubarrayBruteforce(int arr[], int n) {
int maxSum = INT_MIN;
int startIndex = 0;
int endIndex = 0;

for (int i = 0; i < n; i++) { //n种可能
int currentSum = 0;
for (int j = i; j < n; j++) { //每种可能每次从j=i开始
currentSum += arr[j];
if (currentSum > maxSum) { //每次加法后存储最大值
maxSum = currentSum;
startIndex = i; //求到最大后存储当前开始和结束标签
endIndex = j;
}
}
}

printf("最大子段和: %d\n", maxSum);
printf("最大子段: ");
for (int k = startIndex; k <= endIndex; k++) {
printf("%d ", arr[k]);
}
printf("\n");
}

int main() {
int arr[] = {-2, 1, -3, 4, -1, 2, 1, -5, 4};
int n = sizeof(arr) / sizeof(arr[0]);

maxSubarrayBruteforce(arr, n);

return 0;
}
/*
暴力解法的思路是枚举所有可能的子段,计算它们的和,然后找出最大的和。

*/

2.D&C

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include <stdio.h>
#include <limits.h>

// 求三个数中的最大值
int max(int a, int b, int c) {
if (a >= b && a >= c)
return a;
else if (b >= a && b >= c)
return b;
else
return c;
}

// 在跨越中点的情况下找到最大子段和
int maxCrossingSum(int arr[], int low, int mid, int high) {
int leftSum = INT_MIN;
int sum = 0;
for (int i = mid; i >= low; i--) {
sum += arr[i];
if (sum > leftSum)
leftSum = sum;
}

int rightSum = INT_MIN;
sum = 0;
for (int i = mid + 1; i <= high; i++) {
sum += arr[i];
if (sum > rightSum)
rightSum = sum;
}

return leftSum + rightSum;
}

// 递归函数,找到数组的最大子段和
int maxSubarraySum(int arr[], int low, int high) {
if (low == high)
return arr[low];

int mid = (low + high) / 2;

// 递归求解左右子数组的最大子段和
int leftMax = maxSubarraySum(arr, low, mid);
int rightMax = maxSubarraySum(arr, mid + 1, high);
int crossMax = maxCrossingSum(arr, low, mid, high);

// 返回左右子数组最大子段和以及跨越中点的最大子段和中的最大值
return max(leftMax, rightMax, crossMax);
}

int main() {
int arr[] = {-2, 1, -3, 4, -1, 2, 1, -5, 4};
int n = sizeof(arr) / sizeof(arr[0]);

int result = maxSubarraySum(arr, 0, n - 1);

printf("最大子段和: %d\n", result);

return 0;
}

/*
最大子段和问题也可以使用分治法来解决。
分治法的基本思想是将问题划分为较小的子问题,
然后递归地解决这些子问题,
最后将子问题的解合并得到原问题的解。
*/

3.DP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <stdio.h>

int max(int a, int b) {
return (a > b) ? a : b;
}

int maxSubarraySum(int arr[], int n) {
int maxEndingHere = arr[0];
int maxSoFar = arr[0];

for (int i = 1; i < n; i++) {
// 在当前位置选择要么以前一个位置的子数组结束,要么从当前位置重新开始
maxEndingHere = max(arr[i], maxEndingHere + arr[i]);
//最后一个maxEndingHere可能比上一个小了,故不能直接返回maxEndingHere,而要设置一个maxSoFar存当前最大值

// 更新到目前为止的最大子段和
maxSoFar = max(maxSoFar, maxEndingHere);
}

return maxSoFar;
}

int main() {
int arr[] = {-2, 1, -3, 4, -1, 2, 1, -5, 4};
int n = sizeof(arr) / sizeof(arr[0]);

int result = maxSubarraySum(arr, n);

printf("最大子段和: %d\n", result);

return 0;
}

分治法

1.ChessCovering 问题(棋盘覆盖)

易知,覆盖任意一个2^k×2^k的特殊棋盘,用到的骨牌数恰好为(4^K-1)/3。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include <stdio.h>
#include <stdlib.h>
#define N 8 //2^k×2^k
int Board[N][N];//定义一个棋盘
int tile = 1; //表示骨牌的型号
void ChessBoard(int r, int c, int fr, int fc, int size);
//r表示棋盘左上角行号,c表示棋盘左上角列号,fr表示特殊方格所在行号,fc表示特殊方格所在列号,size棋盘规格

/*原理:为无特殊方格的小棋盘(分之后的)添加特殊方格*/

int main() {
//将棋盘各方格初始化为0
for (int i = 0; i < N; i++)
for (int j = 0; j < N; j++)
Board[i][j] = 0;

ChessBoard(0, 0, 2, 2, N);

//输出棋盘
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++)
printf("%d\t", Board[i][j]);
printf("\n");
}
system("PAUSE"); //因编译器原因,防止运行结果闪退
}

void ChessBoard(int r, int c, int fr, int fc, int size) {
if (size == 1)
return;
int t = tile++; //本列用相同数字表示骨牌
int s = size / 2;

if (fr < r + s && fc < c + s) //特殊方格在此小棋盘内
ChessBoard(r, c, fr, fc, s);
else { //特殊方格不在此棋盘
Board[r + s - 1][c + s - 1] = t; //用t号骨牌覆盖右下角
ChessBoard(r, c, r + s - 1, c + s - 1, s); //继续覆盖其余方格
}
//后面三个if-else类似于第一个
if (fr >= r + s && fc < c + s) //特殊方格在此小棋盘内(第三象限),人为选定默认认为在
ChessBoard(r + s, c, fr, fc, s); //棋盘左上角坐标为(r+s,c)
else { //特殊方格不在此小棋盘内
Board[r + s][c + s - 1] = t; //选定(r+s,c+s-1)为特殊方块,用t号骨牌覆盖右上角
ChessBoard(r + s, c, r + s, c + s - 1, s); //继续覆盖其余方格
}

if (fr < r + s && fc >= c + s) //特殊方格在此小棋盘内(第一象限),人为选定默认认为在
ChessBoard(r, c + s, fr, fc, s);
else { //特殊方格不在此小棋盘内
Board[r + s - 1][c + s] = t; //用t号骨牌覆盖左下角
ChessBoard(r, c + s, r + s - 1, c + s, s);
}

if (fr >= r + s && fc >= c + s) //特殊方格在此小棋盘内(第四象限),人为选定默认认为在
ChessBoard(r + s, c + s, fr, fc, s);
else { //特殊方格不在此小棋盘内
Board[r + s][c + s] = t; //用t号骨牌覆盖左上角
ChessBoard(r + s, c + s, r + s, c + s, s);
}

}

2.最接近点对问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <float.h>

// 定义一个二维点的结构体
struct Point {
int x, y;
};

// 比较函数,用于根据x坐标对点进行排序
int compareX(const void* a, const void* b) {
return ((struct Point*)a)->x - ((struct Point*)b)->x;
}

// 比较函数,用于根据y坐标对点进行排序
int compareY(const void* a, const void* b) {
return ((struct Point*)a)->y - ((struct Point*)b)->y;
}

// 计算两点之间的距离
float dist(struct Point p1, struct Point p2) {
return sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y));
}

// 计算最小距离的函数
float min(float x, float y) {
return (x < y) ? x : y;
}

// 在给定点集中找到最小距离的函数,左右区计算最近距离
float bruteForce(struct Point P[], int n) {
float min_distance = FLT_MAX;
for (int i = 0; i < n; ++i) {
for (int j = i + 1; j < n; ++j) {
if (dist(P[i], P[j]) < min_distance) {
min_distance = dist(P[i], P[j]);
}
}
}
return min_distance;
}

// 使用分治法找到最小距离的函数
float closestUtil(struct Point Px[], struct Point Py[], int n) {
if (n <= 3) { //分为每个区3个,再BF算最小距离
return bruteForce(Px, n);
}

// 找到中间点
int mid = n / 2;
struct Point midPoint = Px[mid];

// 分别在左右子集中递归查找最小距离
float dl = closestUtil(Px, Py, mid);
float dr = closestUtil(Px + mid, Py + mid, n - mid);

// 取两个子集中的最小距离
float d = min(dl, dr);

// 构建y坐标在(d - 2d, d + 2d)范围内的点集
struct Point strip[n];
int j = 0;
for (int i = 0; i < n; i++) {
if (abs(Py[i].x - midPoint.x) < d) {
strip[j] = Py[i];
j++;
}
}

// 找到strip中的最小距离
float minStrip = bruteForce(strip, j);

// 返回最小距离
return min(d, minStrip);
}



// 主函数,计算最小距离
float closest(struct Point P[], int n) {
struct Point Px[n];
struct Point Py[n];
for (int i = 0; i < n; i++) {
Px[i] = P[i];
Py[i] = P[i];
}

// 按x坐标排序
qsort(Px, n, sizeof(struct Point), compareX);

// 按y坐标排序
qsort(Py, n, sizeof(struct Point), compareY);

// 使用分治法计算最小距离
return closestUtil(Px, Py, n);
}

// 示例用法
int main() {
struct Point P[] = {
{8,-15},{9,-29},{29,-28},{-30,-13},{-3,45},{-33,-12},{-7,35},{47,-45},{43,-10},{24,-6},
{47,23},{18,-23},{-37,16},{-45,-1},{1,-43},{-42,16},{-16,-50},{-39,35},{-48,38},{-26,-28},
{49,31},{42,-36},{-50,-33},{41,36},{-13,14},{4,5},{32,-19},{0,17},{-35,15},{18,-5},
{14,46},{0,23},{-7,28},{-45,-28},{46,10},{5,-31},{42,5},{-28,-22},{36,-13},{-24,-39},
{-12,-8},{42,-47},{29,6},{30,26},{30,6},{-14,-39},{-49,16},{39,-20},{-35,28},{-33,13}
};

int n = sizeof(P) / sizeof(P[0]);

printf("最接近点的距离是 %f\n", closest(P, n));

return 0;

}

动态规划

1.Trangle Problem(数字三角形问题)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include <stdio.h>

#define ROWS 6
#define MAX_COLS 6

// 计算最小路径和并填充值表和路表
int minimumTotal(int triangle[ROWS][MAX_COLS], int valueTable[ROWS][MAX_COLS], int pathTable[ROWS][MAX_COLS]) {
// 初始化最后一行
for (int j = 0; j < MAX_COLS; j++) {
valueTable[ROWS - 1][j] = triangle[ROWS - 1][j];
}

// 从倒数第二层开始向上计算
for (int i = ROWS - 2; i >= 0; i--) {
for (int j = 0; j <= i; j++) {
// 当前位置的最小路径和等于当前位置的值加上下一层相邻位置的最小路径和的较小值
valueTable[i][j] = triangle[i][j] + ((valueTable[i + 1][j] < valueTable[i + 1][j + 1]) ? valueTable[i + 1][j] : valueTable[i + 1][j + 1]);

// 记录路径信息
pathTable[i][j] = (valueTable[i + 1][j] < valueTable[i + 1][j + 1]) ? j : j + 1;
}
}

// 返回最终结果,自底向上
return valueTable[0][0];
}

// 打印最短路径
void printShortestPath(int triangle[ROWS][MAX_COLS], int pathTable[ROWS][MAX_COLS]) {
int row = 0;
printf("最短路径: %d", triangle[0][0]);

for (int i = 1; i < ROWS; i++) {
int col = pathTable[i - 1][row];
printf(" -> %d", triangle[i][col]);
row = col;
}
}

int main() {
int triangle[ROWS][MAX_COLS] = {
{2},
{3, 4},
{6, 5, 7},
{4, 1, 8, 3},
{4, 9, 12, 15, 11},
{12, 4, 6, 1, 9, 2}
};

int valueTable[ROWS][MAX_COLS];
int pathTable[ROWS][MAX_COLS];

int result = minimumTotal(triangle, valueTable, pathTable);
printf("最小路径和: %d\n", result);

printShortestPath(triangle, pathTable);

return 0;
}

2.LCS Promblem(最长公共子序列)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <stdio.h>

// 计算最长公共子序列的长度
int lcs(char X[], char Y[], int m, int n) {
int dp[m + 1][n + 1];

//初始化状态表
for(int i=0;i<=m;i++)
for(int j=0;j<=n;j++)
dp[i][j] = 0;

// 填充动态规划表
for (int i = 0; i <= m; i++) {
for (int j = 0; j <= n; j++) {
if (i == 0 || j == 0)
dp[i][j] = 0;
else if (X[i - 1] == Y[j - 1])//最后一个相等
dp[i][j] = dp[i - 1][j - 1] + 1;
else
dp[i][j] = (dp[i - 1][j] > dp[i][j - 1]) ? dp[i - 1][j] : dp[i][j - 1];
}
}

return dp[m][n];
}

int main() {
char X[] = "ABCBDABAABCDBABDDCBABDBCA";
char Y[] = "BDCABAABDBCBDABDAAADCABC";

int m = sizeof(X) / sizeof(X[0]) - 1;
int n = sizeof(Y) / sizeof(Y[0]) - 1;

int result = lcs(X, Y, m, n);

printf("最长公共子序列的长度: %d\n", result);

return 0;
}
  • Copyrights © 2022-2024 CoffeeLin
  • Visitors: | Views:

请我喝杯咖啡吧~

支付宝
微信