```
::: warning
动态绑定同名属性时的覆盖问题:
* Vue2:固定绑定的属性总是覆盖动态绑定的属性
* \*Vue3:从左至右,后绑定的覆盖前绑定的属性
:::
## 6.2.7 `v-model`
`v-model`双向数据绑定 一般只用于表单控件input、select、textarea、components
```html
```
::: tip
`v-model`会忽略所有表单元素的value、checked、selected
:::
## 6.2.8 `v-on`
`v-on`事件绑定
```html
```
支持的事件详见:[事件](/frontend/javascript/event)
## 6.2.9 `v-for`
`in`遍历key,在遍历对象时使用。
```html
- {{index}}-{{item.subject}}-{{item.score}}
```
`of`遍历value,在遍历数组时使用。
```html
- {{item}}-{{key}}-{{index}}
```
`in`/`of`在Vue的v-for中使用效果并无差别。
:::warning
1. `v-for`渲染的元素列表顺序改变时,采取“就地更新”策略,`Vue`不会随之移动DOM元素的顺序,而是就地更新每个元素,确保它们在原本指定的索引位置上渲染
2. `v-for`支持的监听数组变化的方法:`push()`,`pop()`,`shift()`,`unshift()`,`splice()`,`sort()`,`reverse()`
3. `v-for`与`v-if`一同使用时,`v-for`优先级高,因此每次循环都会重复执行`v-if`
4. \*Vue3中`key`不再是必须的,Vue会自动生成唯一的`key`
5. \*Vue3中`v-if`总是优先于`v-for`生效,这意味着`v-if`的条件将无法访问到`v-for`作用域内定义的变量别名。
6. `:key="index"`将项目的索引与项目的唯一项绑定以避免因为就地复用引起项目混乱。\
[key的作用详解](https://www.zhihu.com/question/61064119/answer/766607894)
7. \*Vue3中当容器是`
`时`key`应该放在``上
:::
## 6.2.10 `v-once`、`v-memo`
* `v-once`:只渲染一次,不随数据更新而重新渲染。与`Object.freeze()`方法类似。
```html
{{msg}}
```
* `v-memo`:(` v-memo="[valueA, valueB]"`)只有当`valueA`和`valueB`都保持不变,则整个子树不会重新渲染
```html
ID: {{ item.id }} - selected: {{ item.id === selected }}
...more child nodes
```
::: tip
当搭配`v-for`使用`v-memo`,确保两者都绑定在同一个元素上。
:::
## 6.2.11 `:class`
```html
变量
对象
三目运算符
数组
```
如果你的组件有多个根元素,你将需要指定哪个根元素来接收这个class。你可以通过组件的 `$attrs`来实现指定:
```html
Hi!
This is a child component
```
```html
```
渲染结果:
```html
Hi!
This is a child component
```
## 6.2.12 `:style`
```html
变量
对象
三目运算符
数组
```
* 多重值
常用语浏览器兼容问题,Vue会渲染数组中最后一个被浏览器支持的值
```html
```
## 6.2.13 动态参数
```html
attributeName是一个变量
attributeName是一个变量
eventName是一个变量
```
## 6.2.14 自定义指令 `directive`
* 局部注册:
```js
Vue.directive('myfocus', {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el) {
// 聚焦元素
el.focus()
}
})
```
* 局部注册:
```js
export default {
el:'app',
directives:{
myfocus:{
// inserted 当被绑定的元素插入到 DOM 中时
inserted (el,binding) {
el.focus() // 获取焦点
}
}
},
}
```
```html
```
### 动态指令参数:
```html
```
```js
export default {
el:'app',
directives:{
color:{
// inserted 当被绑定的元素插入到 DOM 中时
inserted (el,binding) {
el.style.color = binding.value.color //设置字体颜色
el.innerHTML = binding.value.text //设置字体颜色
}
}
},
}
```
### 相关钩子函数
`inserted`:被绑定元素插入父节点时调用(仅保证父节点存在,但不一定已被插入文档中)。\
`bind`:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。\
`update`:所在组件的`VNode`更新时调用,但是可能发生在其子`VNode`更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新。\
`componentUpdated`:指令所在组件的 VNode 及其子 VNode 全部更新后调用。\
`unbind`:只调用一次,指令与元素解绑时调用。
### 相关参数:
`el`:指令所绑定的元素,可以用来直接操作DOM。\
`binding`:一个对象,包含以下属性:
> `name`:指令名,不包括 v- 前缀\
> `value`:指令的绑定值,例如:v-my-directive="1+1" 中,绑定值为2\
> `oldValue`:指令绑定的前一个值,仅在`update`和`componentUpdated`钩子中可用。无论值是否改变都可用\
> `expression`:字符串形式的指令表达式。例如v-my-directive="1+1"中,表达式为"1+1"\
> `arg`:传给指令的参数,可选。例如v-my-directive:foo中,参数为"foo"\
> `modifiers`:一个包含修饰符的对象。例如:v-my-directive.foo.bar中,修饰符对象为{ foo: true, bar: true }\
> `vnodeVue`:编译生成的虚拟节点\
> `oldVnode`:上一个虚拟节点,仅在`update`和`componentUpdated`钩子中可用
### \*Vue3语法
#### 相关钩子函数的变化:
`created`:新增!在元素的 attribute 或事件监听器被应用之前调用\
bind → `beforeMount`:在元素被插入到 DOM 前调用\
inserted → `mounted`:在绑定元素的父组件及他自己的所有子节点都挂载完成后调用\
`beforeUpdate`:新增!在元素本身被更新之前调用,与组件的生命周期钩子十分相似\
`update` →:移除!该钩子与 updated 有太多相似之处。请改用 `updated`\
componentUpdated → `updated`:在绑定元素的父组件及他自己的所有子节点都更新后调用
`beforeUnmount`:新增!与组件的生命周期钩子类似,它将在元素被卸载之前调用\
unbind -> `unmounted`:绑定元素的父组件卸载后调用
### 相关参数的变化:
`el`:指令所绑定的元素,可以用来直接操作DOM\
`binding`:一个对象,包含以下属性:
> `name`:指令名,不包括 v- 前缀\
> `value`:指令的绑定值,例如:v-my-directive="1+1" 中,绑定值为2\
> `oldValue`:指令绑定的前一个值,仅在`beforeUpdate`和`updated`钩子中可用。无论值是否改变都可用\
> `arg`:传给指令的参数,可选。例如v-my-directive:foo中,参数为"foo"\
> `modifiers`:一个包含修饰符的对象。例如:v-my-directive.foo.bar中,修饰符对象为{ foo: true, bar: true }\
> `instance`:使用该指令的组件实例\
> `dir`:指令的定义对象
`vnode`:代表绑定元素的底层 VNode\
`prevNode`:之前的渲染中代表指令所绑定元素的 VNode。仅在 `beforeUpdate` 和 `updated` 钩子中可用。
```html
```
```js
export default {
el:'app',
directives:{
color:{
// mounted 当被绑定的元素插入到 DOM 中时
mounted (el, binding, vnode) {
el.style.color = binding.value.color //设置字体颜色
el.innerHTML = binding.value.text //设置字体颜色
}
}
},
}
```
::: tip
1. 只有当所需功能只能通过直接的 DOM 操作来实现时,才应该使用自定义指令。其他情况下应该尽可能地使用 v-bind 这样的内置指令来声明式地使用模板,这样更高效,也对服务端渲染更友好
2. 除了 el 外,其他参数都是只读的,不要更改它们。若你需要在不同的钩子间共享信息,推荐通过元素的 dataset attribute 实现
:::
## 6.2.15 数据修饰符
### `lazy`
在change时而非input时更新
```html
```
### `number`
将用户的输入值转为数值类型
```html
```
### `trim`
过滤用户输入的首尾空白字符
```html
```
## 6.2.16 事件修饰符
### `once`
只执行一次
```html
```
### `stop`
阻止冒泡
```html
```
### `capture`
冒泡顺序,设置capture的最先冒泡
```html
1
2
3
```
### `self`
只有设置了`self`的才能触发事件,既阻止捕获又阻止冒泡
```html
1
2
3
```
### `prevent`
阻止默认事件
```html
```
### `passive`
执行默认方法
```html
```
### `native`
监听组件子组件元素的原生事件\*Vue3中移除
```html
```
```html
```
### `sync`
子组件修改父组件的
```html
```
* \*Vue3语法
```html
```
子组件修改父组件的show值
```js
this.$emit('update:show',false)
```
### 按键修饰符
按下按键时执行
```html
```
* 按键编码
`keyCode`已被主流浏览器废弃,因此vue提供如下按键码别名:
`.enter`/`.tab`/`.delete`(捕获“删除”和“退格”键)/`.esc`/`.space `/`.up` /`.down` /`.left`/`.right`/`.ctrl`/`.alt`/`.shift`/`.meta`
你还可以通过全局`config.keyCodes`(Vue2)对象自定义按键修饰符别名:
### `exact`
组合修饰符\
允许精确的使用组合修饰符触发的事件。
```html
```
:::tip
* 浏览器只有等内核线程执行到事件监听器对应的JavaScript代码时,才能知道内部是否会调用preventDefault函数来阻止事件的默认行为,因此使用`prevent`提前告知浏览器。一般用于监听`@scoll`/`@touchmove`
* 串联使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。例如:`v-on:click.prevent.self`会阻止所有的点击,而 `v-on:click.self.prevent`只会阻止对元素自身的点击。
:::
---
---
url: /frontend/vue/component.md
---
# 6.3 组件系统
## 6.3.1 组件
### 组件名
* kebab-case\
使用kebab-case(短横线分隔命名)定义组件时,你也必须在引用这个自定义元素时使用kebab-case\
例如:`
`
* PascalCase
当使用PascalCase(首字母大写命名)定义一个组件时,引用时`kebab-case`/`PascalCase`都可以。
### 组件的作用域
* 全局注册\
可以用在任何新创建的Vue根实例
* 局部注册\
只能用在当前组件内,且不能用在其子组件中。
### 组件的实例
组件挂载后才能访问模板引用
::: code-group
```vue [Vue2]
```
```vue [Vue3]
```
```vue [Vue3.5]
```
:::
::: tip
1. 如果子组件使用的是选项式 API,被引用的组件实例和该子组件的 `this` 完全一致,这意味着父组件对子组件的每一个属性和方法都有完全的访问权。
2. \*Vue3:使用了`
> ```
:::
### 单文件组件
一个Vue单文件组件 (SFC),通常使用`*.vue`作为文件扩展名,它是一种使用了类似HTML语法的自定义文件格式,用于定义Vue组件。一个Vue单文件组件在语法上是兼容HTML的
每一个 `*.vue` 文件都由三种顶层语言块构成:``、`
```
### 模块系统
* 将每个组件放置在其各自的文件中。
* 通过`import`/`require`引入组件。(`ES6 Module`详见[7.16.20](/frontend/javascript/ECMAScript#_3-16-20-es6-module)
`CommonJS`详见[7.1.7](/other/node/introduction#_7-1-7-模块系统))
* 全局注册的行为必须在根Vue实例(通过 new Vue)创建之前发生
* 局部注册:
::: code-group
```vue [Vue2]
```
```vue [Vue3 setup]
```
```vue [Vue3 setup()]
```
:::
## 6.3.2 全局组件
1. 第一步声明
```js
new Vue({el:`#app`})
```
2. 第二步全局注册挂载
```js
Vue.component("Vheader",{template:`我是模板
`});
//Vue.component(组件名称 template:`模板(需要一个根元素,否则只能渲染第一个元素)`)
```
\*Vue3语法
```js
import { createApp } from 'vue'
const app = createApp({})
app.component(
// 注册的名字
'MyComponent',
// 组件的实现
{
/* ... */
}
)
```
3. 第三步使用
```html
```
## 6.3.3 局部组件
1. 第一步声明
```js
new Vue({el:"#app"});
```
2. 第二步局部注册挂载,仅用于#app下局部使用
```js
new Vue({
el:"#app",
components: {
Vheader:{template:`子组件
`}//3.组件描述
},
});
```
3. 第三步使用组件
```html
```
::: warning
1. 如果子组件在外部描述需要先声明,然后再父组件中注册挂载
2. 子组件的`data`必须是一个函数,否则在复用组件时,将共用一个`data`
:::
## 6.3.4 动态组件、异步组件、异步导入、与组件缓存
### 动态组件
```html
```
```js
// 组件名称组成的数组或一个组件的选项对象
data:{
index:0,
currentTabComponent:[
template1,template2,template3
],
methods:{
change(){
this.index++
this.currentTabComponent[this.index%3]
}
}
```
### 异步组件
```js
Vue.component('async-example', function (resolve, reject) {
setTimeout(function () {
// 向 `resolve` 回调传递组件定义
resolve({
template: 'I am async!
'
})
}, 1000)
})
```
* 异步引用模块化组件
```html
```
```js
import Home from "./components/home.vue";
import News from "./components/news.vue";
export default {
name:"App",
data(){
return{
//默认值显示Home,通过修改currentComponent异步加载不同的组件
currentComponent:Home ,
}
},
components:{
Home,
News
},
```
### 异步导入组件
\*通过Vue3新增的`defineAsyncComponent`方法异步引入组件
```js
import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent(
() =>
new Promise((resolve, reject) => {
resolve({
template: 'I am async!
'
})
})
)
```
* 全局异步导入:
```js
app.component('MyComponent', defineAsyncComponent(() =>
import('./components/MyComponent.vue')
))
```
* 在局部注册的时候直接导入
```js
import { defineAsyncComponent } from 'vue'
export default {
components: {
AdminPage: defineAsyncComponent(() =>
import('./components/AdminPageComponent.vue')
)
}
}
```
* 配合动态组件异步导入:
```js
import { defineAsyncComponent } from 'vue'
const Home = defineAsyncComponent(() => import('@/components/home.vue'))
const News = defineAsyncComponent(() => import('@/components/News.vue'))
export default {
name:"App",
data(){
return{
//默认值显示Home,通过修改currentComponent异步加载不同的组件
currentComponent:Home ,
}
},
components:{
Home,
News
},
```
* 加载与错误状态:
```js
const AsyncComp = defineAsyncComponent({
// 加载函数
loader: () => import('./Foo.vue'),
// 加载异步组件时使用的组件
loadingComponent: LoadingComponent,
// 展示加载组件前的延迟时间,默认为 200ms
delay: 200,
// 加载失败后展示的组件
errorComponent: ErrorComponent,
// 如果提供了一个 timeout 时间限制,并超时了
// 也会显示这里配置的报错组件,默认值是:Infinity
timeout: 3000
})
```
* 搭配 `Suspense` 使用
### `keep-alive`组件缓存
参数:
> `include`:(捕获需要缓存的)字符串、数组或正则表达式。只有名称匹配的组件会被缓存。\
> `exclude`:(捕获不需要缓存的)字符串、数组或正则表达式。任何名称匹配的组件都不会被缓存。\
> `max`:数字。最多可以缓存多少组件实例。
```html
```
::: warning
1. `keep-alive`要求被切换到的组件都有自己`name`
2. `keep-alive`与`router-view`结合使用时,`include`优先匹配组件的`name`所以组件的`name`必须与路由的`name`完全一致
3. `exclude`与`include`t同时使用时,`exclude`的优先级会更高
4. 在 3.2.34 或以上的版本中,使用 `
```
* 子组件
```html
{{inputName}}
```
::: tip props
1. props是单向下行绑定,父组件更新回传给子组件,反之不行
2. Prop可以验证,并设置默认值。如果验证失败开发环境回产生一个控制台警告
```js
props: {
// Number类型验证
propA: Number,
// 多个类型验证
propB: [String, Number],
// 必填的String类型
propC: {
type: String,
required: true
},
// 带有默认值的Number类型
propD: {
type: Number,
default: 100
},
// 带有默认值的Object
propE: {
type: Object,
// 对象或数组默认值必须从一个工厂函数获取
default: function () {
return { message: 'hello' }
}
},
// 自定义验证函数
propF: {
validator: function (value) {
// 这个值必须匹配下列字符串中的一个
return ['success', 'warning', 'danger'].indexOf(value) !== -1
}
}
}
```
3. 使用一个对象绑定多个 prop
```vue
```
4. 非`Prop`的`Attribute`是指不需要接收直接挂在到子组件根节点的属性,常见的有`class`、`style`、`id`
```html
```
禁用`Attribute`继承
```vue
```
```html
```
:::
\*Vue3写法
::: code-group
```vue [Vue3 setup]
{{ title }}
```
```vue [Vue3 setup()]
```
:::
* `withDefaults`使用`TS`声明类型
```js
export interface Props {
msg?: string
labels?: string[]
}
const props = withDefaults(defineProps(), {
msg: 'hello',
labels: () => ['one', 'two']
})
```
### 父传孙`attrs`
* 父组件
```html
```
* 子组件
```html
```
* 孙组件
```html
{{inputName}}
```
### 子传父`emit`
* 子组件
```html
{{childValue}}
```
* 父组件
```html
{{name}}
```
\*Vue3写法
::: code-group
```vue [Vue3 setup]
```
```vue [Vue3 setup()]
```
:::
### 子传父`listeners`
\*Vue3中移除
### 兄弟传值$emit与$on
* 组件A
```html
{{elementValue}}
```
* 组件B
```html
B组件:
{{name}}
```
### 依赖注入(`provide`、`inject`)
::: code-group
```vue [Vue2]
```
```vue [Vue3]
```
:::
::: warning 依赖注入是非响应式的
\*Vue3:`inject`和`provide` 需要在 `setup()` 内同步调用
\*Vue3:如果需要在注入方组件中更改数据,需要给提供方给注入放一个变更数据的方法:
````
```vue
````
:::
* 注入默认值
```js
inject('message', '这是默认值')
inject('key', () => new ExpensiveClass())
```
\*Vue3使用 Symbol 作注入名
但如果你正在构建大型的应用,包含非常多的依赖提供,或者你正在编写提供给其他开发者使用的组件库,建议最好使用 Symbol 来作为注入名以避免潜在的冲突。
详见[3.16.9 Symbol](/frontend/javascript/ECMAScript#_3-16-9-symbol)
```js
// keys.js
export const myInjectionKey = Symbol() as InjectionKey
```
```js
// 在供给方组件中
import { myInjectionKey } from './keys.js'
export default {
provide() {
return {
[myInjectionKey]: {
/* 要提供的数据 */
}
}
}
}
```
```js
// 注入方组件
import { myInjectionKey } from './keys.js'
export default {
inject: {
injected: { from: myInjectionKey }
}
}
```
## 6.3.6 组件其他方法
### 在组件上使用`v-model`
```js
```
子组件`props`必须绑定名为`value`的变量。并在`input`事件中通过`$emit`返回给父组件
```js
Vue.component('ChildComponent ', {
props: ['value'],
template: `
`
})
```
* 使用`model`选项
允许一个自定义组件在使用v-model时定制prop和event命名
```js
Vue.component('ChildComponent ', {
model: {
prop: 'searchText',
event: 'change'
},
props: {
// 这将允许 `value` 属性用于其他用途
value: String,
// 使用searchText代替value作为model的prop
searchText: {
type: Number,
default: 0
}
},
// ...
})
```
* \*Vue3中语法
#### 有参数的v-model
```html
```
#### 无参数的v-model
`prop`和`event`命名更改为`modelValue`和`update:modelValue`
```html
```
```js
// ChildComponent.vue
export default {
props: {
modelValue: String // Vue2是`value:String`
},
emits: ['update:modelValue'],
methods: {
changePageTitle(title) {
this.$emit('update:modelValue', title) // Vue2是 `this.$emit('input', title)`
}
}
}
```
`defineModel`方法
```html
```
```js
// ChildComponent.vue
var title = defineModel()
```
### 混入(mixin)
将任意组件通过mixin包装成混入对象,插入另一个组件,
```js
// 定义一个混入对象
var mixin = {
created: function () {
console.log('混入对象的钩子被调用')
}
}
// 使用混入对象
new Vue({
mixins: [mixin],
created: function () {
console.log('组件钩子被调用')
}
})
```
* 使用`Vue.extend`进行混入
```js
// 定义一个混入对象
var myMixin = {
created: function () {
this.hello()
},
methods: {
hello: function () {
console.log('hello from mixin!')
}
}
}
// 定义一个使用混入对象的组件
var Component = Vue.extend({
mixins: [myMixin]
})
var component = new Component() // => "hello from mixin!"
```
:::tip
1. 当组件和混入对象含有同名选项时,`methods`、`components`和`directives`,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对。
2. \*Vue3中推荐使用组合式函数\组合式Api方式
:::
* 全局混入
```js
Vue.mixin({
created: function () {
console.log('全局混入')
}
})
```
* 自定义选项合并策略
`Vue.config.optionMergeStrategies`自定义合并策略
```js
const merge = Vue.config.optionMergeStrategies.computed
Vue.config.optionMergeStrategies.vuex = function (toVal, fromVal) {
if (!toVal) return fromVal
if (!fromVal) return toVal
return {
getters: merge(toVal.getters, fromVal.getters),
state: merge(toVal.state, fromVal.state),
actions: merge(toVal.actions, fromVal.actions)
}
}
```
### 函数式组件(渲染函数render)
```html
```
* `render`函数,
> 有一个参数`createElement`函数\
> 返回`createElement`创建的VNode虚拟节点
* `createElement`函数
> 第一个参数`HTML`标签字符串\
> 第二个参数`HTML`标签的属性对象
>
> > `class`样式\
> > `style`样式\
> > `attrs`属性\
> > `on`事件\
> > `props`接收参数\
> > `domProps`传递参数\
> > `on`事件\
> > `nativeOn`原生事件\
> > `directives`自定义指令
> 第三个参数子虚拟节点数组,返回VNode虚拟节点
* `props`接收上级传来的参数
* 元素位置限制
某些HTML元素对于放在其中的元素类型有限制,例如` `,``,`- `,`
`,``
```html
```
完整示例
```js
Vue.component('example', {
render: function (createElement) {
return createElement(
'h' + this.level, //h1,h2...标签
{ // 属性
style: {
border: '1px solid #ccc',
':hover': { // 设置伪类
background: 'yellow'
},
}
},
[ // 添加子节点
createElement(
// 标签
'input',
// 属性
{ //类名
class:{
'demo':true
},
// html属性
attrs: {
name: this.$slots.default.headingId
},
// 组件的props
props:{
myData: 'message',
// 绑定自定义事件
'focus':()=>{}
},
// Dom属性
domProps:{
innerHtml:'baz'
},
style: {
border: '1px solid #ccc',
fontSize:'10px'
},
// 添加事件
on: {
click: () => {
},
mouseover: () => {
}
},
// 监听原生事件
nativeOn:{
click: ()=>{}
},
// 自定义指令
directives:[
{
name:'myDIrective',
value:2,
expression:' 1+1',
arg:'foo',
modifiers:{
bar:true
}
}
]
},
this.$slots.default // 子节点
)
]
)
},
// 接收传给example组件的的数据
props: {
level: {
type: Number,
required: true
}
}
)}
```
* \*Vue3写法
```vue
```
::: tip 实现v-model
```js
return h('div', [
h('Input', {
props: {
// 回显
value: data
},
on: {
// 监听input 动态修改data参数
input: (event) => {
data = event
}
}
})
])
```
:::
#### JSX
```html
```
### 模板扩展
* `inline-template`内联模板\
内联模板不会把子组件的内容分发渲染到父组件中而是需要在父组件中实现其内容的渲染
```html
These are compiled as the component's own template.
Not parent's transclusion content.
```
::: warning
1. 内联模板的作用域是子组件自身。
2. \*Vue3中移除
:::
* `X-Template`
```html
```
### 控制更新
由于`JavaScript`的限制,`Vue`不能检测数组和对象的变化。
因此在变更数组和对象的时候可以使用以下两种方法触发视图更新:
1. `$forceUpdate`强制视图更新。(只会影响实例本身和插入插槽内容的子组件,而不是所有子组件)
```js
this.$forceUpdate();
```
2. 通过`$set`方法修改数组或对象触发视图更新。
```js
this.$set(target,key,value); // target = Object | Array
```
### \*Vue3透传属性
1. 当一个组件以单个元素为根作渲染时,透传的属性会自动被添加到根元素上
```html
```
2. 如果一个子组件的根元素已经有了 `class` 或 `style` 属性,它会和从父组件上继承的值合并。
3. `v-on`监听器也会有透传显像
4. 禁用透传
* 如果你使用了 `
```
### \*Vue3组合式函数(Hooks)
组合式函数(`Hooks`) 是一个利用 Vue 组合式 API 来封装和复用有状态逻辑的函数。
* 鼠标跟踪实例:
```js
// mouse.js
import { ref, onMounted, onUnmounted } from 'vue'
// 按照惯例,组合式函数名以“use”开头
export function useMouse() {
// 被组合式函数封装和管理的状态
const x = ref(0)
const y = ref(0)
// 组合式函数可以随时更改其状态。
function update(event) {
x.value = event.pageX
y.value = event.pageY
}
// 一个组合式函数也可以挂靠在所属组件的生命周期上
// 来启动和卸载副作用
onMounted(() => window.addEventListener('mousemove', update))
onUnmounted(() => window.removeEventListener('mousemove', update))
// 通过返回值暴露所管理的状态
return { x, y }
}
```
```vue
Mouse position is at: {{ x }}, {{ y }}
```
* 选项式Api中使用组合式函数
```js
import { useMouse } from './mouse.js'
import { useFetch } from './fetch.js'
export default {
setup() {
const { x, y } = useMouse()
const { data, error } = useFetch('...')
return { x, y, data, error }
},
mounted() {
// setup() 暴露的属性可以在通过 `this` 访问到
console.log(this.x)
}
// ...其他选项
}
```
::: tip 组合式函数 和 Mixin 的对比
* Vue 2 的用户可能会对 mixins 选项比较熟悉。它也让我们能够把组件逻辑提取到可复用的单元里。然而 mixins 有三个主要的短板:
1. 不清晰的数据来源:当使用了多个 mixin 时,实例上的数据属性来自哪个 mixin 变得不清晰,这使追溯实现和理解组件行为变得困难。这也是我们推荐在组合式函数中使用 ref + 解构模式的理由:让属性的来源在消费组件时一目了然。
2. 命名空间冲突:多个来自不同作者的 mixin 可能会注册相同的属性名,造成命名冲突。若使用组合式函数,你可以通过在解构变量时对变量进行重命名来避免相同的键名。
3. 隐式的跨 mixin 交流:多个 mixin 需要依赖共享的属性名来进行相互作用,这使得它们隐性地耦合在一起。而一个组合式函数的返回值可以作为另一个组合式函数的参数被传入,像普通函数那样。
基于上述理由,我们不再推荐在 Vue 3 中继续使用 mixin。保留该功能只是为了项目迁移的需求和照顾熟悉它的用户。
:::
### ``
``可以将一个组件内部的一部分模板“传送”到该组件的 DOM 结构外层的位置去。
参数:
> `to`:指定传送的目标,可以是一个 CSS 选择器字符串,也可以是一个 DOM 元素对象\
> `disabled`:禁用传送\
> `max`:数字。最多可以缓存多少组件实例。
```html
Hello from the modal!
```
::: tip
`` 挂载时,传送的 to 目标必须已经存在于 DOM 中。理想情况下,这应该是整个 Vue 应用 DOM 树外部的一个元素。如果目标元素也是由 Vue 渲染的,你需要确保在挂载 `` 之前先挂载该元素。
:::
* 多个 `` 共享同一个目标
```html
A
B
```
渲染结果
```html
```
### ``
``用来在组件树中协调对异步依赖的处理。它让我们可以在组件树上层等待下层的多个嵌套异步依赖项解析完成,并可以在等待时渲染一个加载状态。
* 异步依赖
---
---
url: /frontend/vue/filter.md
---
# 6.4 过滤器
过滤器只改变展示的形式,不改变原数据,并通过return返回处理后的数据。
* 全局过滤器
```js
Vue.filter("formatData",function (olddata) {
return newdata
})
```
* 局部过滤器
```js
filters: {
formatData: function (olddata) {
return newdata
}
}
```
* 使用
```html
{{olddata | newdata}}
```
::: warning
\*Vue3中移除,建议使用计算属性或方法代替
:::
---
---
url: /frontend/vue/slot.md
---
# 6.5 插槽
## 6.5.1 匿名插槽
* 定义:
```html
```
* 使用:
```html
登陆
```
## 6.5.2 具名插槽
* 定义:
```html
默认值
```
* 使用:
```html
```
## 6.5.3 作用域插槽
父组件通过插槽prop调用子组件的数据
* 定义:
```html
{{ userName.lastName }}
```
```js
data() {
return {
userName: {
firstName: "jaqi",
},
};
},
```
* 使用:
```html
{{ slotProps.user.firstName }}
```
* 解构插槽Prop:
```html
{{ name.firstName }}
```
---
---
url: /frontend/vue/methods.md
---
# 6.6 属性方法
## 6.6.1 methods
在vue项目中定义的函数需要放在`methods`方法里。
```js
methods:{
loadData(){}
}
```
## 6.6.2 computed
`computed`计算属性:将复杂的逻辑从模板中提取出来便于维护。并根据数据的改变重新进行逻辑处理。如果数据没有改变就不会重新处理。
```html
Reversed message: "{{ reversedMessage }}"
```
```js
computed:{
// 对reversedMessage进行逻辑处理
reversedMessage: function () {
return this.message.split('').reverse().join('')
}
}
```
#### 可写计算属性
计算属性默认仅能通过计算函数得出结果。当你尝试修改一个计算属性需要如下方法
```js
computed: {
fullName: {
// getter
get() {
return this.firstName + ' ' + this.lastName
},
// setter
set(newValue) {
// 注意:我们这里使用的是解构赋值语法
[this.firstName, this.lastName] = newValue.split(' ')
}
}
}
```
::: warning
1. 不要在计算函数中做异步请求或者更改DOM
2. 避免直接修改计算属性值
:::
## 6.6.3 watch
`watch`监听属性:监听数据的改变
```js
// 监听data属性的改版
watch: {
data(val, oldVal) {
}
}
```
* 深度监听
```js
// 监听data属性的改版
watch: {
docData: {
handler(val, oldVal) {
},
deep: true, //深度监听
immediate: true //对初始值进行监听
}
}
```
* 监听子属性
```js
watch: {
// 只能是简单的路径,不支持表达式
'data.rows': {
handler(val, oldVal) {
},
immediate: true
}
},
```
* 函数写法
```js
var unwatch = this.$watch('data.rows', function (val, oldVal) {
unwatch()// 取消监听
},
{ deep: true, //深度监听
immediate: true //对初始值进行监听
}
```
* \*Vue3新增的`flush`属性
默认情况下,用户创建的侦听器回调,都会在Vue组件更新之前被调用。这意味着你在侦听器回调中访问的DOM将是被Vue更新之前的状态,如果想在侦听器回调中能访问被Vue更新之后的DOM。就要使用`flush`属性
```js
watch: {
docData: {
handler(val, oldVal) {
},
flush: 'post'
}
}
```
## 6.6.3 三者的区别
1. `methods`方法不会自执行。
2. `computed`方法有缓存,如果监听的数据没有改变,不会重新计算。适合复杂的逻辑处理
3. `watch`擅长处理一个数据影响多个数据、`computed`擅长处理一个数据受多个数据影响
```js
new Vue({
el: '#app',
data: {
fullName: '',
firstName: 'jaqi',
lastdName: 'l'
},
//一个数据(fullName)受(firstName、lastdName)两个数据的影响
computed:{
this.fullName = firstName + this.lastdName
},
//如果使用watch就需要对每个改变的数据进行监听,非常繁琐
watch: {
firstName: function (newValue) {
this.fullName = firstName + this.lastdName
},
lastdName: function (newValue) {
this.fullName = firstName + this.lastdName
},
},
})
```
* [【Vue】谈Vue的依赖追踪系统 ——搞懂methods watch和compute的区别和联系](https://www.cnblogs.com/penghuwan/p/7194133.html)
* [vue computed正确使用方式](https://zhuanlan.zhihu.com/p/72541791)
---
---
url: /frontend/vue/lifecycle.md
---
# 6.7 生命周期
## 6.7.1 生命周期
创建前/后、载入前/后、更新前/后、销毁前/销毁后。

| 事件 | API | \*Vue3API |
| -------- | -------- | -------- |
| 实例挂载之前 | `beforeCreate` | `setup()` |
| 实例创建之后 | `created` | - |
| 数据挂载之前 | `beforeMount` | `onBeforeMount` |
| 数据挂载之后 | `mounted` | `onMounted` |
| 数据更新之前 | `beforeUpdate` | `onBeforeUpdate` |
| 数据更新之后 | `updated` | `onUpdated` |
| 缓存组件被激活 | `activated` | `onActivated` |
| 缓存组件被卸载 | `deactivated` | `onDeactivated` |
| 实例销毁之前 | `beforeDestroy` | `onBeforeUnmount` |
| 实例销毁之后 | `destroyed` | `onUnmounted` |
| 实例错误 | `errorHandler` | `app.config.errorHandler` |
| 后代组件错误 | `errorCaptured` | `onErrorCaptured` |
| 当组件读取到响应式数据时 | - | `onRenderTracked`(仅开发模式) |
| 当响应式数据改变触发组件渲染时 | - | `onRenderTriggered`(仅开发模式) |
| 在组件实例在服务器上被渲染之前 | - | `onServerPrefetch`(SSR) |
## 6.7.2 使用方法
::: code-group
```vue [Vue2]
```
```vue [Vue3 setup]
```
```vue [Vue3 setup()]
```
:::
## 6.7.3 生命周期事件
::: code-group
```html [Vue2]
```
```html [Vue3]
```
::: code-group
---
---
url: /frontend/vue/router.md
---
# 6.8 Vue Router
## 6.8.1 路由基础
* 安装
```zsh
npm install vue-router
```
* 全局引用\
main.js
::: code-group
```js [Vue Router 3.x]
import VueRouter from 'vue-router'
Vue.use(VueRouter)
// 或
new Vue({
render: h => h(App),
router,
}).$mount('#app')
```
```js [Vue Router 4.x]
import { createRouter, createWebHistory } from 'vue-router'
// 还有 createWebHashHistory 和 createMemoryHistory
createRouter({
history: createWebHistory(),
routes: [],
})
```
:::
#### js方法:
```js
router.push('/index')
```
#### 标签方法:
```html
首页
```
```js
methods: {
go() {
this.$router.push('/index')
},
goBack(){
this.$router.go(-1)// 后退一步记录,等同于 history.back()
},
goForward(){
this.$router.go(1) // 在浏览器记录中前进一步,等同于 history.forward()
}
}
```
### 动态路由
* query方法(查询路由)
#### 编程式:
```js
this.$router.push({ path: "/index", query: data })// 发送 -> /index
this.$route.query // 接收
```
#### 声明式:
```html
新闻
```
* params方法(命名路由)
#### 编程式:
```js
this.$router.push({ name: "index", params: data })// 发送 -> /index?data
tthis.$route.params // 接收
```
#### 声明式:
```html
新闻
```
* 路径传参
#### 编程式:
```js
this.$router.push({ path: "/index/:id"})
```
#### 声明式:
```html
首页
```
:::tip
1. query要用(path)路径传参,params要用(name)命名路由传参。
2. 通过命名路由(params)传递的参数,刷新页面参数会丢失。
3. 发送是`$router`,接收使是`$route`
:::
### 路由捕获
```js
path: '*' // 会匹配所有路径,通常用于404错误
path: '/user-*' // 会匹配以 `/user-` 开头的任意路径
```
* 在参数中自定义正则:
```js
path: '/:orderId(\\d+)' // 仅匹配数字
path: '/:chapters(\\d+)*' // 匹配 /, /1, /1/2, /1/2/3, 等
```
### 路由嵌套
```js
routes: [
{ path: '/user/:id', component: User,
children: [
{
// 当 /user/:id/profile 匹配成功,
// UserProfile 会被渲染在 User 的 中
path: 'profile',
component: UserProfile
},
{
// 当 /user/:id/posts 匹配成功
// UserPosts 会被渲染在 User 的 中
path: 'posts',
children: []
}
]
}
]
```
:::tip
以`/`开头的嵌套路径会被当作根路径
:::
### 命名视图与嵌套命名视图
* 命名视图
```html
```
```js
const router = createRouter({
history: createWebHashHistory(),
routes: [
{
path: '/',
components: {
default: Home,
// LeftSidebar: LeftSidebar 的缩写
LeftSidebar,
// 它们与 `` 上的 `name` 属性匹配
RightSidebar,
},
},
],
})
```
* 嵌套命名视图
```html
User Settings
```
```js
{
path: '/settings',
// 你也可以在顶级路由就配置命名视图
component: UserSettings,
children: [{
path: 'emails',
component: UserEmailsSubscriptions
}, {
path: 'profile',
components: {
default: UserProfile,
helper: UserProfilePreview
}
}]
}
```
### 重定向与别名
#### 从/a重定向到/b:
```js
routes: [
{ path: '/a', redirect: '/b' }
]
```
#### 重定向另一个命名路由
```js
routes: [
{ path: '/a', redirect: { name: 'foo' }}
]
```
#### 重定向一个方法
```js
routes: [
{ path: '/a', redirect: to => {
// 方法接收 目标路由 作为参数
// return 重定向的 字符串路径/路径对象
}}
]
```
#### 别名
/a的别名是/b,意味着,当用户访问/b 时,URL会保持为 /b,但是路由匹配则为/a,就像用户访问 /a 一样。
```js
routes: [
{ path: '/a', component: A, alias: '/b' }
]
```
### 路由组件传参
使组件只能在某些特定的URL上使用
#### 取代与$route的耦合:
```js
const User = {
template: 'User {{ $route.params.id }}
'
}
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: User }
]
})
```
#### 通过props解耦:
```js
const User = {
props: ['id'],
template: 'User {{ id }}
'
}
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: User, props: true }, // 当props=true,route.params将会被设置为组件属性
// 对于包含命名视图的路由,必须分别为每个命名视图添加 `props` 选项:
{
path: '/user/:id',
components: { default: User, sidebar: Sidebar },
props: { default: true, sidebar: false }
}
]
})
```
### 响应路由参数的变化
当使用带有参数的路由时需要注意的是相同的组件实例将被重复使用。因为两个路由都渲染同个组件,比起销毁再创建,复用则显得更加高效。不过,这也意味着组件的生命周期钩子不会被调用。
要对同一个组件中参数的变化做出响应的话,可以使用一下方法:
* `wacth`监听
```js
// 写法一:
watch:{
$router(to,from){
console.log(to.path)
}
},
// 写法二:
watch: {
$route: {
handler: function (val, oldVal){
console.log(val);
},
// 深度观察监听
deep: true
}
},
```
* `beforeRouteUpdate`拦截
```js
async beforeRouteUpdate (to, from, next) {
his.userData = await fetchUser(to.params.id)
},
```
### 替换历史栈
#### 编程式:
```js
router.push({ path: '/home', replace: true })
// 相当于
router.replace({ path: '/home' })
```
#### 声明式:
```html
新闻
```
### 历史模式
* Hash模式
通过`createWebHashHistory()`创建
```js
import { createRouter, createWebHashHistory } from 'vue-router'
const router = createRouter({
history: createWebHashHistory(),
routes: [
//...
],
})
```
:::tip 什么是Hash模式
这里的hash是指url尾巴后的#号及后面的字符。这里的#和css里的#是一个意思。hash也称作锚点,本身是用来做页面定位的,她可以使对应id的元素显示在可是区域内。由于hash值变化不会导致浏览器向服务器发出请求,而且hash改变会触发hashchange事件,浏览器的进后退也能对其进行控制,所以人们在 html5的history出现前,基本都是使用 hash 来实现前端路由的。Hash模式兼容到IE8,使用Hash模式做路由则不能使用锚点功能。
:::
* history(HTML5)模式
通过`createWebHistory()`创建
```js
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes: [
//...
],
})
```
:::tip 什么是history(HTML5)模式
HTML5规范提供了history.pushState和history.replaceState来进行路由控制。通过这两个方法可以改变url且不向服务器发送请求。同时不会像hash有一个#,更加的美观。但是history路由需要服务器的支持,并且需将所有的路由重定向倒根页面。history(HTML5)模式兼容到IE10。history(HTML5)模式可以传递体积更大的数据包。
:::
## 6.8.2 导航卫士
#### 局部卫士:
`beforeRouteEnter`进入当前路由前\
`beforeRouteUpdate`当前路由更新前\
`beforeRouteLeave`离开当前路由前
#### 全局卫士:
在main.js或router.js配置\
`beforeEach`进入路由时\
`beforeResolve`路由解析后\
`afterEach`进入路由后,不接受next参数
#### 独享卫士:
在router.js配置\
`beforeEnter`进入路由配置时
#### 参数:
`from`从哪个路由进入\
`to`去哪个路由\
`next`组件渲染完毕后回调
#### 触发顺序:
`beforeEach`→`beforeEnter`→`beforeRouteEnter`→`beforeRouteUpdate`→`beforeRouteLeave`
→`beforeResolve`→`afterEach`→`beforeRouteEnter`的回调函数
#### 案例
1. 守卫模块(作用于VueRouter):
```js
routes:[{
path:"/Blog",
component:Blog,
meta:{ // 路由元信息
auth:true// 表示用户访问该组件是否需要登陆
}]
```
2. 登录模块(作用于登录模块):
```js
handleLogin(){ //保存用户名和密码
localStorage.setItem("username",{name:this.uname,psw:this.psw});
this.$router.push("/blog")}//给VueRouter添加一个历史记录 目的是渲染blog
}
```
3. 守卫判断:
```js
router.beforeEach((to,from,next)=>{
if (to.meta.auth) {//判断有没有用户名和密码
if (localStorage.getItem("username")) {//有用户名说明已经登录
next();
}else{//没有登录 渲染登录组件
next({path:"/login"});
};
}else{
next();
};
});
```
4. 注销模块(作用域实例):
```js
clearOut(){
localStorage.removeItem("username"); // 移除本地用户名密码
this.$router.push("/login")
}//给VueRouter添加一个历史记录 目的是渲染blog
```
---
---
url: /frontend/vue/async.md
---
# 6.9 异步
## 6.9.1 nextTick
Vue在更新DOM时是异步执行的。当数据发生变化,Vue将开启一个异步更新队列,视图需要等队列中所有数据变化完成之后,再统一进行更新。
为了在更新DOM后能立即拿的修改后的数据就需要`nextTick`方法。`nextTick`会在DOM更新结束后立即执行。
::: code-group
```js [Vue2]
created(){
this.$nextTick(() => {})
}
```
```js [Vue3]
import { nextTick } from 'vue'
created(){
nextTick(() => {})
}
```
:::
:::tip
在Vue生命周期的`created()`钩子函数进行的DOM操作一定要放在`Vue.nextTick()`的回调函数中.
:::
## 7.9.2 Promise详见[3.14.2](/frontend/javascript/sync_asyn#_3-14-2promise)
## 7.9.3 async/await详见[3.14.3](/frontend/javascript/sync_asyn#_3-14-3async-await)
---
---
url: /other/node/introduction.md
---
# 7.1 node.js概述
`Node.js`是`JavaScript`服务器端运行时环境,`Node.js` 建立在 `Google Chrome V8 JavaScript` 引擎之上,不依赖第三方应用(`Apache`),可以基于`Api`自己实现。\
`Node.js`应用在单个进程中运行,在其标准库中提供了一组异步 `I/O` 原语,以防止 JavaScript 代码阻塞。
* 优势:前端入手、性能高、利于与前端代码整合。
* 劣势:安全和稳定性没有传统语言高。
`node.js`一般做中间层(更安全):
> 客户端-------->服务器端客户端------>node.js-----服务器
* 主流`JavaScript`运行时对比
| 功能 | `Node` | `Deno` | `Bun` |
| ------------------------ | ------------------------- | --------------- | ----------------------- |
| 底层语言 | C++ | Rust | Zig |
| JS引擎 | V8(Google) | V8(Google) | JavaScriptCore(Apple) |
| 版本号 | 20.5.1 | 1.36 | 0.7.3 |
| Web API | ✅(正在支持中) | ✅ | ✅ |
| TypeScript/JSX | ❌ | ✅ | ✅ |
| Bundle | ❌ | ✅(准备放弃) | ✅ |
| Compile(可执行文件) | ✅(实验性质,正在支持中) | ✅ | ✅ |
| 本地ESM | ✅ | ✅ | ✅ |
| 远程ESM(URL导入) | ❌ | ✅ | ❌ |
| CommonJS | ✅ | ✅ | ✅ |
| package.json | ✅ | ✅(部分支持) | ✅ |
| .npmrc | ✅ | ❌(可能会支持) | ❌(好像不准备支持) |
| 安全校验(权限) | ✅(正在支持中) | ✅ | ❌ |
| 代码校验、格式化 | ❌ | ✅ | ❌ |
| 内置测试库 | ✅ | ✅ | ✅ |
| 宏 | ❌ | ❌ | ✅ |
| 插件(转换其它类型文件) | ❌ | ❌ | ✅ |
[华山论剑:Node.js、Deno与将要发布1.0的Bun](https://zhuanlan.zhihu.com/p/651166037)
## 7.1.1 安装:
下载安装包双击安装即可。[node.js官网](https://nodejs.org/en/)
## 7.1.2 卸载:
* window:
1. 从卸载程序卸载程序和功能
2. `C:\Program Files (x86)\Nodejs`
3. `C:\Program Files\Nodejs`
4. `C:\Users\{User}\AppData\Roaming\npm(或%appdata%\npm)`
5. `C:\Users\{User}\AppData\Roaming\npm-cache(或%appdata%\npm-cache)`
6. 重启,检查环节变量是否还有node、npm
* MacOS:
1. 按`shift+command+G`,打开前往文件夹的窗口,分别输入下列目录
> `/usr/local/lib`,删除node和node\_modules相关的文件和文件夹\
> `/usr/local/include`,删除node和 node\_modules 相关的文件和文件夹
> `/usr/local/bin`,删除node可执行文件
2. 如果使用`brew`安装的还需要执行`brew uninstall node`
3. 个人文件夹下面的所有的local、lib以及include文件夹,删除node和node\_modules相关的文件和文件夹
4. 可能还需要如下指令
> `sudo rm /usr/local/bin/npm`\
> `sudo rm /usr/local/share/man/man1/node.1`\
> `sudo rm /usr/local/lib/dtrace/node.d`\
> `sudo rm -rf ~/.npm`\
> `sudo rm -rf ~/.node-gyp`\
> `sudo rm /opt/local/bin/node`\
> `sudo rm /opt/local/include/node`\
> `sudo rm -rf /opt/local/lib/node_modules`
## 7.1.3 运行与使用
* `node -v`查看node版本
* `npm -v`查看npm版本
* `node server.js`运行谁就node谁
## 7.1.4 包管理工具(npm)
NPM是随同NodeJS一起安装的包管理工具。
* `npm init`项目初始化(生成package.json文件)
* `npm init-y`快速初始化(生成package.json文件)
* `npm install`、`npm i`、 `npm ci`安装
:::tip 安装命令的修饰符
`-g`:全局安装\
`-D`:安装开发依赖(devDependencies)\
`-S`:安装生产依赖(dependencies)\
:::
:::tip npm install、npm i 与 npm ci 的区别
`npm install`:\
根据package文件安装依赖,根据package-lock确定版本,如果没有package-lock则生成package-lock\
`npm i`:\
根据package文件安装依赖,同时依据`^`或`~`升级相关依赖,并更新package-lock文件,不会生成npm-debug.log文件,卸载时需要使用`npm uninstall i`命令。\
`npm ci`:\
根据package文件安装依赖,根据package-lock确定版本没有则报错,不会更新依赖,也不会更新package-lock文件。\
:::
:::tip `npm` 与 `npx` 的区别
`npm`:`Node` 包管理器,在执行的时候必须本地安装依赖,无法执行远程包\
`npx`:`Node` 包执行器,在执行的时候不安装依赖,因此可以执行远程包\
:::
* `npm unistall`卸载
* `npm get registry`:查看安装源
* `npm install 包 -registry=https://`:指定安装源
* `npm install --userconfig .npmrc`:根据指定的.npmrc文件安装
* `npm install el-components https://gitee.com/jaqi/component.git`:从本地或远程地址安装
* `npm config set registry http://`:修改安装源
> `npm install -g cnpm --registry=https://registry.npm.taobao.org`:全局安装cnpm
* `npm login`:登录
> `npm login --auth-type=legacy`:npm >= 9
* `npm publish`:发布npm包
> `npm publish --registry=`:发布到指定仓库
* `npm list`:查看当前项目安装的依赖的模块
* `npm list --global`:查看全局安装的依赖模块
* `npm update 包名`:更新包
* `npm view(v/info/show) 包`:查看包详情
* `npm config get userconfig`:查看当前使用的 .npmrc 配置文件路径
* `npm config get prefix`:获取 .npmrc 全局配置文件路径 $PREFIX
* `which npm`:npm 安装路径
* `npm –help`:帮助
#### `package.json`文件
```js
{
"name": "jaqi.note",//项目名
"version": "0.1.0",//项目版本
"author":"jaqi.l"//作者
"private": true, //项目是否私有
"scripts": {
// serve 模式配置
// --open 在服务器启动时打开浏览器
// --copy 在服务器启动时将 URL 复制到剪切版
// --mode 指定环境模式 (默认值:development)
// --host 指定 host (默认值:0.0.0.0)
// --port 指定 port (默认值:8080)
// --https 使用 https (默认值:false)
// build 模式配置
// --mode 指定环境模式 (默认值:production)
// --dest 指定输出目录 (默认值:dist)
// --modern 面向现代浏览器带自动回退地构建应用
// --target app | lib | wc | wc-async (默认值:app)
// --name 库或 Web Components 模式下的名字 (默认值:package.json 中的 "name" 字段或入口文件名)
// --no-clean 在构建项目之前不清除目标目录
// --report 生成 report.html 以帮助分析包内容
// --report-json 生成 report.json 以帮助分析包内容
// --watch 监听文件变化
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": { // 生产依赖
"vue": "2.6.11",//使用2.6.11版本
"vue-template-compiler": "~2.6.11",//使用2.6.x版本
"vuex": "^3.2.0", //使用3.x.x版本
"jquery": "latest" //使用latest版本
},
"devDependencies": { // 开发依赖
},
"eslintConfig": { // ESLint 规则配置
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "babel-eslint"
},
"rules": {
"no-debugger": "off",//debugger 错误提示
"no-console": "off", //console 错误提示
}
},
"browserslist": [ // 项目的目标浏览器范围
"> 1%", // 兼容使用率大于1%的版本
"last 2 versions", // 兼容最近的2个版本
"not dead"
]
}
```
详见:[package.json指南](https://dev.nodejs.cn/learn/the-package-json-guide/)
#### `.npmrc`文件
```npmrc
# 设置 npm 包的下载地址
registry = https://registry.npm.taobao.org/:_authToken=yourAuthToken
#设置以 @test 开头的包的下载地址
@test:registry = https://registry.npm.taobao.org/:_authToken=yourAuthToken
#是否授权
always-auth=true
```
### pnpm
`node >16.13`内置pnpm
* 开启实验性功能`corepack`
```zsh
corepack enable
```
* 更新最新的 pnpm
```zsh
corepack prepare pnpm@ --activate
```
#### 常用命令
* `pnpm istall`安装
* `pnpm install -- no-frozen-lockfile`安装(等效`npm ci`)
* `pnpm uninstall`卸载
## 7.1.5 使用`nvm`、`nvmw`安装并管理`node.js`
#### 安装:
* window:
[nvmw镜像仓库](https://github.com/coreybutler/nvm-windows/releases)
1. 下载最新的安装包。
2. 安装:安装的目录不能有中文和空格。
3. 修改安装源:打开根目录的settings.txt文件,最后面添加上下面的源地址
> `node_mirror: https://npm.taobao.org/mirrors/node/`\
> `npm_mirror: https://npm.taobao.org/mirrors/npm/`
* MacOS:
[nvm镜像地址](https://github.com/nvm-sh/nvm#install-script)
1. 复制最新的下载指令
2. 在终端执行复制的镜像下载指令:`curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash`
3. 复制环境变量代码
::: tip
通过homebrew安装nvm
1. 首先安装homebrew
```zsh
/bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)"
```
2. 安装nvm
```zsh
brew install nvm
```
:::
```js
// 案例:
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
```
4. 创建启动脚本
```bash
# 创建文件
touch .bash_profile
# 打开创建的文件
open -e .bash_profile
# 将启动命令放入文件
```
5. 使脚本开机自启
```bash
# 切换到 ~ 目录下
$ cd ~
# 编辑 .zshrc 文件,若是没有则自动建立
$ vi .zshrc
# 在最后一行写入下面内容
source ~/.bash_profile
```
::: tip 安装oh-my-zsh
* 安装oh-my-zsh
```zsh
curl sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
# 国内镜像
git clone https://gitee.com/mirrors/oh-my-zsh.git ~/.oh-my-zsh
cp ~/.oh-my-zsh/templates/zshrc.zsh-template ~/.zshrc
```
* 确保以下文件都有环境变量代码(环境变量代码如上nvm)\
`~/.bash_profile`、`~/.zshrc`、`~/.profile`
* 设置为默认的`Shell`
```zsh
chsh -s $(which zsh)
```
* 依次重载配置文件
```zsh
source ~/.bash_profile
source ~/.zshrc
source ~/.profile
```
:::
::: tip raw.githubusercontent.com host
[search host](https://site.ip138.com/raw.githubusercontent.com/)\
199.232.68.133 raw.githubusercontent.com
* `.zshrc`推荐配置
```zsh
export ZSH="$HOME/.oh-my-zsh"
ZSH_THEME="robbyrussell"
plugins=(
git
zsh-syntax-highlighting
)
source $ZSH/oh-my-zsh.sh
# nvm
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
# pnpm
export PNPM_HOME="/Users/jaqi.l/Library/pnpm"
export PATH="$PNPM_HOME:$PATH"
# pyenv
export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init -)"
```
:::
#### 卸载:
* window:同node.js卸载一样
* MacOS:
1. `cd ~`
2. `rm -rf .nvm`
#### 使用:
* `nvm list` 列出已经安装的node版本,可以简写为nvm ls命令
* `nvm on` 启用node版本管理
* `nvm off` 禁用node版本,并不会卸载任何东西
* `nvm uninstall version` 卸载指定版本的node
* `nvm install version` 安装指定版本的node
* `nvm install latest` 安装最新版本
* `nvm use version` 使用指定版本的node
* `nvm alias default version` 给指定版本添加别名
* `nvm run version index.js` 使用指定版本运行node
* `nvm exec version npm install` 使用指定版本运行npm
## 7.1.6 使用`nrm`安装并管理`npm`
* 安装
```zsh
npm install -g nrm
```
* 查看可选源,星号代表当前使用源
```zsh
nrm ls
```
* 查看当前使用的源
```zsh
nrm current
```
* 切换源
```zsh
nrm use
```
* 添加源
```zsh
nrm add
```
* 测试源速度
```zsh
nrm test
```
* 删除源
```zsh
nrm del
```
## 7.1.7 模块系统
一个js文件就是一个模块,通常放在node\_modules文件夹中。
node.js使用`CommonJS`模块规范,`CommonJS`加载的是一个对象该对象只有在脚本运行完才会生成。(自 `Node.js` v12 起支持ESM规范[3.16.20 Module](/frontend/javascript/ECMAScript#_3-16-20-es6-module) )
* 模块分类:
1. 系统核心模块(`http`、`fs`、`url`...)
2. 第三方模块
3. 自定义模块
* 模块的引用
```js
const http = require("http");// 系统模块或第三方模块(node_modules下的模块)
const mod = require("./mod");// 自定义模块(引用时需要写相对路径)
```
* 自定义模块的导出
1. 每个模块都有`module`对象
2. `module`对象有一个`exports`对象
3. 需要导出的成员需要挂载到`module.exports`对象中,`exports`是它的别名
4. 导出单个成员`module.exports == xxx`,不能使用别名的方式
```js
// 单个导出
module.exports.fn = fn
module.exports = funciton fn(){}
// 多个导出 接收时是一个对象
exports.fn = fn
exports.a = 10
exports.b = 20
module.exports = {fn,a:10,b:20}
```
---
---
url: /other/node/modules.md
---
# 7.2 系统核心模块
## 7.2.1 创建服务器;响应请求(`http`)
请求参数的属性
> `req.url`:返回请求路径\
> `req.method`:返回请求方式\
> `req.headers`:返回请求头\
> `req.on`:添加请求的监听事件
>
> > `req.on("data",callback)`监听每次请求来的数据\
> > `req.on("end",callback)`监听数据接收结束事件\
> > `req.on("error",callback)`监听请求接收错误事件
响应参数的属性
> `res.writeHead`:设置响应头(响应的的参数)\
> `res.setHeader`:设置响应头( 参数1:状态码,参数2:响应的的参数)\
> `res.write`:设置响应内容\
> `res.end`: 结束本次请求
```js
const http = require("http");// 引用http模块
//创建一个响应请求服务 一旦有请求过来 函数就执行
let server = http.createServer(function(req,res) { // req请求 res响应
// 处理请求
let url = req.url // 请求路径 可以获取get请求的参数
let arr = []
// 监听请求来data post请求是分段请求的,因此需要监听数据
req.on("data",function(buffer){arr.push(buffer)}) // 监听数据,把每次请求的数据存起来
req.on("end",function(){
let buffer = Buffer.concat(arr)
console.log(buffer.toString())
}) // 监听数据接收结束事件 并把二进制数据转换成字符串
// 设置响应
res.writeHead(200, {'Content-Type': 'text/plain'}); // 状态码
res.write(url);
res.write("hello
");
res.end(); // 结束本次请求 结束后不能再发响应
});
server.listen(8000);//监听等待客户端的连接
```
::: tip
* `res.writeHead`与`res.setHeader`区别
> 1. `res.writeHead`必须在`res.end`之前调用
> 2. 如果两者同时存在(没必要),要先写`res.setHeader`,后写`res.writeHead`,且`res.writeHead`优先
* 处理跨域问题`Access-Control-Allow-Origin`,
> 1. `res.setHeader("access-control-allow-origin","*")` 允许全部
> 2. `res.writeHead(200, {"access-control-allow-origin","www.jaqi.com"})`\
> :::
## 7.2.2 文件系统模块(`fs`)
文件的读取修改创建
`fs`的属性
> `fs.readFile`:读取文件(浪费服务器资源)\
> `fs.readFileSync`:同步读取文件\
> `fs.writeFile`:读取文件\
> `fs.writeFileSync`:同步读取文件\
> `fs.createReadStream`:流式操作(创建一个读取流)\
> `fs.createWriteStream`:流式操作(创建一个写入流)\
> `fs.existsSync`:路径是否存在\
> `pipe()`流式操作(执行流,通过管道对文件进行操作)
```js
const http = require("http");
const fs = require("fs");// 引用fs模块
// 修改文件并读取
let server = http.createServer(function(req,res) {
fs.writeFileSync("./1.txt","新的内容",function(err){ // 1.txt文件写入新的内容
if(err){
console.log(err)
}
})
// 根据req.url动态读取文件
fs.readFile(`./${req.url}`,function(err,data){ // 第二个参数 data返回字符串、buffer:返回二进制数据
if(err){
res.write(JSON.stringify({error:404,msg:err}))
}else{
res.write(data);
res.end();
}
})
});
server.listen(8000);
```
`fs.readFile`浪费服务器内存、资源使用不均匀,推荐使用`流式操作`(读一个发一个)
* `流式操作`
```js
const fs = require("fs");// 引用fs模块
let readFile = fs.createReadStream("a.txt") // 创建一个读取流
let writeFile = fs.createWriteStream("a_copy.txt") // 创建一个写入流
readFile.on("error",(err)=>{
console.log()
})
writeFile.on("finish",()=>{
console.log("finish")
})
readFile.pipe(writeFile) // 通过管道对文件进行操作
```
## 7.2.4 文件压缩模块(`zlib`)
```js
const fs = require("fs");// 引用fs模块
const zlib = require("zlib");// 引用zlib模块
let readFile = fs.createReadStream("a.txt") // 创建一个读取流
let GzipFile = zlib.createGzip() // 创建压缩流
let writeFile = fs.createWriteStream("a.txt.gz") // 创建一个写入流
readFile.pipe(GzipFile).pipe(writeFile) // 执行流 压缩文件并写入
```
## 7.2.5 查询字符串模块(`querystring`)
`querystring`的属性
> `querystring.parse`:默认根据`&`、`=`将字符串转成对象\
> `querystring.stringify`:默认根据`&`、`=`将对象转成字符串
```js
const querystring = require("querystring");// 引用querystring模块
let obj = querystring.parse('foo=bar&baz=qux&baz=quux&corge')
// obj = { foo: 'bar', baz: ['qux', 'quux'], corge: '' }
let str = querystring.stringify(obj)
// str = 'foo=bar&baz=qux&baz=quux&corge'
```
### 7.2.6 缓冲器(`Buffer`)
`Buffer`的属性
> `Buffer.alloc`:返回一个指定大小的Buffer实例\
> `Buffer.from`:根据传入的参数创建一个Buffer实例\
> `Buffer.toJSON`: 将Buffer转换为JSON对象\
> `Buffer.toString`编码转换
>
> > `Buffer.toString('ascii')`:ASCII编码,仅支持7位\
> > `Buffer.toString('Buffer.utf8')`: 多字节编码的Unicode\
> > `Buffer.toString('Buffer.utf16le')`: 2或4个字节的Unicode\
> > `Buffer.toString('Buffer.ucs2')`: utf16le的别名\
> > `Buffer.toString('Buffer.base64')`: Base64编码\
> > `Buffer.toString('Buffer.latin1')`: 一种把Buffer编码成一字节编码的字符串的方式\
> > `Buffer.toString('Buffer.binary')`: latin1的别名\
> > `Buffer.toString('Buffer.hex')`: 将每个字节编码为两个十六进制字符
```js
const querystring = require("querystring");// 引用querystring模块
let obj = querystring.parse('foo=bar&baz=qux&baz=quux&corge')
// obj = { foo: 'bar', baz: ['qux', 'quux'], corge: '' }
let str = querystring.stringify(obj)
// str = 'foo=bar&baz=qux&baz=quux&corge'
```
## 7.2.7 请求路径处理方法(`url`)
`url`的属性
> `url.parse`:将一个url的字符串解析成对像\
> `url.format`:将一个url的对像解析成字符串\
> `url.resolve`:将一个url解析成"from/to"格式的字符串
```js
const http = require("http");
const fs = require("url");// 引用url模块
// 修改文件并读取
let server = http.createServer(function(req,res) {
url.parse(req.url)
res.write(data);
res.end();
});
server.listen(8000);
```
## 7.2.8 文件路径处理方法(`path`)与魔术变量(`__dirname`)
`path`的属性
> `path.dirname`:返回文件的目录,不包括文件\
> `path.extname`:返回文件的扩展名\
> `path.basename`:返回文件的文件名(包括扩展名)\
> `path.resolve`:分析并拼接路径
```js
const path = require("path")
let pathUrl = "/a/b/c/1.html";
path.dirname(pathUrl);// /a/b/c
path.extname(pathUrl);// .html
path.basename(pathUrl);// 1.html
/*
/root/a/b -> /root/a/b
../c -> /root/a/c
d -> /root/a/c/d
.. -> /root/a/c
e -> /root/a/c/e
(.当前目录 .. 上级 ...上上级)
*/
path.resolve("/root/a/b","../c","d","..","e");
path.resolve(__dirname,"www"); // 魔术变量将绝对路径替换成www
```
## 7.2.9 断言(`assert`)
`assert`的属性
> `assert`:判断表达式正确与否\
> `assert.equal`:比较两个值相不相等,相当于`==`(不支持数组、json)\
> `assert.deepEqual`:比较两个表达式相不相等,相当于`==`\
> `assert.deepStrictEqual`:比较两个表达式相不相等,相当于`===`
```js
const assert = require("assert");
assert(10>19,"达式3") // 如果表达式错误则返回达式3
assert.equal(1,1)// 比较两个值相不相等(不支持数组、json)
assert.deepEqual(1,"1","达式3"); // 如果两个表达式不相等则返回表达式3,相当于==
assert.deepStrictEqual(1,"1","达式3") // 如果两个表达式不相等则返回表达式3,相当于===
```
---
---
url: /other/node/otherModules.md
---
# 7.3 第三方模块
## 7.3.1 调用MySQL数据库(`mysql`)
安装:`npm install mysql -S`
方法:
`createConnection`:创建一个与数据库的链接\
`createPool`:创建一个与数据库的链接池(与`createConnection`相同,用于处理多个链接的情况)\
`query`:创建一个SQL语句
```js
const mysql = require("mysql"); // 引入mysql模块
// 创建一个与数据库的链接
let db = mysql.createPool({
host: "jaqi.myDataBase.com",// 数据库地址
ports:"3306",
user: "jaqi", // 用户名
password: "123456", // 密码
database: "myDataBase" // 库名
})
// 执行SQL结构化查询语句
// 插入
db.query("INSERT INTO myDataBase.student (name, psw) VALUES ('张三', '654321')",(err,data)=>{
if(err){
console.error(err);
}else{
console.log(data);
}
})
//查询
db.query('SELECT * FROM `myDataBase`.`student` WHERE id<=200',(err,data)=>{
if(err){
console.error(err);
}else{
console.log(JSON.stringify(data));
}
})
```
`MySQL`详见[8.2 MySQL](/other/database/mySQL)
## 7.3.2 服务端渲染(ssr)模块
---
---
url: /other/node/frame.md
---
# 7.4 node.js 框架
## 7.4.1 Express
安装:`npm install express`
```js
const express = require('express') // 引用express框架
const bodyParser = require('body-parser') // 引用body-parser模块
const app = express() // 挂在框架
// 处理get请求
app.get('/', function (req, res,next) {
res.send(res.query) // 发送数据到客服端,相当于于`res.write`和`res.end`的集合。
})
// 处理post请求
app.use(bodyParser.json()); // 解析 application/json
app.post('/', function (req, res,next) {
res.send(req.bodyParser) // 发送数据到客服端,相当于于`res.write`和`res.end`的集合。
})
app.listen(3000)
```
#### 路由
`get(url,fn)`:处理get请求\
`post(url,fn)`:处理post请求\
`use(url,fn)`:无论什么请求都处理\
回调函数`fn`的参数:
> 请求参数的属性
>
> > `res.query`:获取`query`类型的参数
> 响应参数的属性
> > `res.send`:发送数据到客服端,相当于于`res.write`和`res.end`的集合
> 回调函数的属性(`next`)
#### 系统中间件
1. `express.static`静态文件托管
```js
app.use('/',express.static("./static/"))
```
#### 第三方中间件
1. `body-parser`:解析JSON、Raw、文本、URL-encoded格式的请求体
> `bodyParser.json()`:解析JSON格式\
> `bodyParser.raw()`:解析二进制格式\
> `bodyParser.text()`:解析文本格式\
> `bodyParser.urlencoded()`:解析文本格式
2. `multer`解析multipart/form-data格式的请求体(上传文件)
3. `cookie-parser`
4. `cookie-session`
## 7.4.2 Koa
v1:Generator
v2:Generator和async
v3:Generator和async
安装:`npm install koa -S`
```js
const Koa = require("koa");
let server = new Koa() // 创建koa对象
server.listen(80);
```
server属性:
> `listen`:监听端口\
> `context`:设置全局上下文。可以通过ctx调用
#### 路由中间件`koa-router`
`koa`不自带路由,需要单独安装`koa-router`
router属性:
> `get(url,fn)`:处理get请求\
> `post(url,fn)`:处理post请求\
> `all(url,fn)`:无论什么请求都处理\
> `use(url,fn)`:挂载中间件
ctx的属性:
> `body`:响应内容\
> `cookies`:cookies
>
> > `cookies.set`:设置cookies
> > `cookies.get`:获取cookies
安装:`npm install koa-router`
```js
const Koa = require("koa");
const Router = require("koa-router");
let server = new Koa()
server.listen(80);
let router = new Router(); // 创建router对象
server.use(router.routes() ) // 挂在router
server.keys = []; //秘钥
router.get("/aa",(ctx,next)=>{
ctx.cookies.set("user","jaqi.l",{signed:true}); // 设置cookies,signed启动秘钥验证
ctx.body = "aaa"; // 设置响应内容
})
```
* 嵌套路由
```js
const Koa = require("koa");
const Router = require("koa-router");
let server = new Koa()
server.listen(80);
let router = new Router();
let company = new Router();
company.get("/:name",(ctx,next)=>{
console.log(ctx.params);
ctx.body = "我是公司"+ctx.params.name
})
let admin = new Router();
admin.get("/query",(ctx,next)=>{
ctx.body = "我是管理员"+ctx.query.name
})
let userRouter = new Router();
userRouter.use("/company",company.routes())
userRouter.use("/admin",admin.routes())
router.use("/user",userRouter.routes())
/*
/user/company/:name
/user/admin/:name
*/
server.use(router.routes())
```
#### 文件解析中间件`koa-better-body`
安装:`npm install koa-better-body`
```js
const Koa = require("koa");
const body = require("koa-better-body");
let server = new Koa()
server.listen(80);
server.use(body({
uploadDir:"./upload" // 文件上传目录
}))
server.use(async ctx=>{
ctx.request.fields
});
```
#### 静态文件服务中间件`koa-static`
安装:`npm install koa-static`
```js
const Koa = require("koa");
const Router = require("koa-router");
const static = require("koa-static");
let server = new Koa();
server.listen(80);
let router = new Router();
router.all(/(\.css)$)/i,static("./static", {
maxage: 24*60*60*1000 // css文件缓存的过期时间
}))
server.use(router.routes());
server.use(static("./static", {
index: "index.html",//默认读取的文件
maxage: 24*60*60*1000 // 文件缓存的过期时间
}));
```
#### session服务中间件`koa-session`
安装:`npm install koa-session -S`
```js
const Koa = require("koa");
const Router = require("koa-router");
const session = require("koa-session");
let server = new Koa();
server.listen(80);
let router = new Router();
server.keys = []; //秘钥
server.use(session({
maxAge:20*60*1000,// 过期时间
renew:true // 自动续签
},server));
server.use(async ctx=>{
if(ctx.session.view){
ctx.session.view = 0;
}
ctx.session.view++;
ctx.body = `欢迎第${ctx.session.view}次访问`
})
```
#### mysql服务中间件`mysql`、`co-mysql`
安装:`npm install mysql co-mysql -S`
```js
const Koa = require("koa");
const mysql = require("mysql");
const co = require("co-mysql");
// 创建一个与数据库的链接
let con = mysql.createdPool({
host: "jaqi.myDataBase.com",// 数据库地址
ports:"3306",
user: "jaqi", // 用户名
password: "123456", // 密码
database: "myDataBase" // 库名
})
let server = new Koa();
server.listen(80);
```
#### 服务端渲染(ssr)中间件`koa-ejs`
安装:`npm install koa-ejs -S`
```js
const Koa = require("koa");
const render = require("koa-ejs");
const path = require("path");
let server = new Koa();
render(server,{
root:path.resolve(__dirname,"./template"),//模板文件的所在文件夹
layout:false, // 目标文件,false 自动在文件夹下找
viewExt:"ejs",// 模板的扩展名
cache:false,// 缓存
debug:false,// debug
});
server.use(async ctx=>{
// 使用template目录下的index模板,并传递数据给模板渲染
await ctx.render("index",{
name:"jaqi",
sex:"men"
})
});
server.listen(80);
```
## 7.4.3 egg
## 7.4.4 nest
---
---
url: /other/database/introduction.md
---
# 8.1 数据库概述
* 数据库分类
> 文件型:`SQLite``WebSQL`、`IndexedDB`
> 关系型:`Access`、`MySQL`、`Oracle`
> 分布式:`MongoDB`
> 非关系型(NoSQL):`Redis`、`MemCache`
* `SQL`操作关系型数据库的语言
---
---
url: /frontend/miniprogram/introduction.md
---
# 8.1 概览
[微信开放文档](https://developers.weixin.qq.com/miniprogram/dev/framework/)
## 小程序代码构成
> `.json`后缀的JSON配置文件
>
> > `app.json`小程序的全局配置文件\
> > `project.config.json`工具配置文件\
> > `page.json`页面配置文件\
> > `.wxml`后缀的WXML模板文件\
> > 标签必须闭合、大小写敏感\
> > `.wxss`后缀的WXSS样式文件\
> > `app.wxss`公共样式配置文件\
> > `.js`后缀的JS脚本逻辑文件\
> > `app.js`入口逻辑文件\
> > `.wxs`后缀的WeiXinScript脚本逻辑文件
## 小程序版本发布流程
预览版→开发版→体验版→审核版→正式版
---
---
url: /other/database/mySQL.md
---
# 8.2 MySQL
## 8.2.1 安装部署
* 下载安装
[下载官方的yum资源包](https://dev.mysql.com/downloads/repo/yum/)
```zsh
yum -y install mysql80-community-release-el7-6.noarch.rpm
rpm --import https://repo.mysql.com/RPM-GPG-KEY-mysql-2022
yum -y install mysql-community-server
```
* 启动和停止
```zsh
systemctl start mysqld
systemctl status mysqld
systemctl restart mysqld
systemctl stop mysqld
```
* 连接数据库
```zsh
mysql [-h 127.0.0.1] [-p 3306] -u root -p
```
* 初始化配置
1. 安全模式启动
```zsh
vim /etc/my.cnf
# 在my.cnf的[mysqld]下添加:`skip-grant-tables`
```
2. 设置root账号初始密码
```zsh
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY'123456';
```
3. 注释掉`my.cnf`中的安全启动命令
4. 创建数据库
```zsh
show databases; # 查看已有的
create database jaqi_db # 创建名为jaqi_db的数据库
```
5. 创建一个远程访问账号
```zsh
CREATE USER user@'%' IDENTIFIED BY '123456';
# 不使用mysql8.0协议
ALTER USER 'user'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
```
6. 给admin用户设置jaqi\_bd的权限
```zsh
# GRANT ALL PRIVILEGES ON [数据库名].[表明] TO '[用户名]'@'[连接地址]' WITH GRANT OPTION;
grant all privileges on `jaqi_db`.* to 'admin'@'%' with grant option;
flush privileges # 刷新权限表
select host,user from mysql.user; # 查看用户表
```
## 8.2.2 概述
## 8.2.3 库与表的创建
库的创建:
`CREATE DATABASE <库> CHARACTER SET <字符集> COLLATE <排序规则>;`
```SQL
CREATE DATABASE `myDataBase` CHARACTER SET 'utf8' COLLATE 'utf8_unicode_ci';
```
表的创建:
`CREATE TABLE <表.库> (表头);`
```SQL
CREATE TABLE `myDataBase`.`student`
( `id` int NOT NULL, `name` varchar(255) NULL, `psw` varchar(255) NULL, PRIMARY KEY (`id`));
```
## 8.2.4 SQL结构化查询语句
* `INSERT`插入数据
`INSERT INTO <表.库> (key1,key2,key3...) VALUES (value1,value2,value3...);`
```SQL
INSERT INTO `myDataBase`.`student` (id,name,password) VALUES ('0422','jaqi','psw123456');
```
##### 插入多条
```SQL
INSERT INTO `myDataBase`.`student` (name) VALUES ('jaqi1') ,('jaqi2');
```
* `DELETE`删除数据
` DELETE FROM <表.库> WHERE <条件>;`
```SQL
DELETE FROM `myDataBase`.`student` WHERE id='0422';
```
* `UPDATE`修改数据
`UPDATE <表.库> SET key1=value1,key2=value2... WHERE <条件>`
```SQL
UPDATE `myDataBase`.`student` SET name=`jaqi.l`,password=`123456psw` WHERE id='0422'
```
* `SELECT`查询数据
`SELECT FROM <表.库> WHERE <条件> ORDER BY <排序key> LIMIT <起始位置,条数>`
```SQL
SELECT * FROM `bdm314524665_db`.`jaqi` WHERE `id` > '0' LIMIT 0,1000
```
::: tip
1. 如果SQL语句中的`key`与`MySQL`系统关键字重复,需要使用\`\`反引号包裹起来。
:::
---
---
url: /frontend/miniprogram/wxml.md
---
# 8.2 wxml特性
* 支持的标签、组件:详见[视图容器|微信开放文档](https://developers.weixin.qq.com/miniprogram/dev/component/)
* 支持的属性:id、class、style、hidden、data-*、bind*/catch\*
* 数据绑定:详见[数据绑定|微信开放文档](https://developers.weixin.qq.com/miniprogram/dev/reference/wxml/data.html)
```html
{{"hello" + message}}
```
* 列表渲染:详见[列表渲染|微信开放文档](https://developers.weixin.qq.com/miniprogram/dev/reference/wxml/list.html)
```html
{{index}}:{{item.name}}
```
\*条件渲染:详见[条件渲染|微信开放文档](https://developers.weixin.qq.com/miniprogram/dev/reference/wxml/conditional.html)
```html
大于五
大于二小于五
其他
```
* 模板:详见[模板|微信开放文档](https://developers.weixin.qq.com/miniprogram/dev/reference/wxml/conditional.html)
```html
{{index}}: {{msg}}
Time: {{time}}
```
引用:详见[引用|微信开放文档](https://developers.weixin.qq.com/miniprogram/dev/reference/wxml/import.html)
`import`:只能引入根节点的template
`include`:可以引入template内的全部template
```html
{{text}}
```
---
---
url: /other/database/MongoDB.md
---
# 8.3 MongoDB
---
---
url: /frontend/miniprogram/wxss.md
---
# 8.3 wxss特性
* rpx单位:设备像素(dp设备的物理像素)、CSS像素、PPI/DPI(每英寸像素数)、DPR(设备像素与CSS像素的比)、rpx(规定屏幕宽度750px,根据DPR进行计算自动适配)
* 外链样式导入:
```css
@import './assets.wxss'
```
* 选择器:id选择器、类选择器、标签选择器、after/before伪类
* 选择器优先级:important最高、行内1000、id选择器100、类选择器10、标签选择器
---
---
url: /other/database/sqlite.md
---
# 8.4 SQLite
---
---
url: /frontend/miniprogram/wxs.md
---
# 8.4 wxs特性
JS运行环境:ios→JavaScriptCore、android→X5内核、IED→nwjs
WXS:运行环境不依赖于运行时的基础库版本,可以在所有版本的小程序中运行
```html
var msg = "hello world";
module.exports.message = msg;
{{m1.message}}
```
WXS数据类型:number、string、boolean、object、arrary、function、date、regexp
:::tip
wxs不支持try/catch
:::
---
---
url: /other/database/webSQL.md
---
# 8.5 WebSQL
---
---
url: /frontend/miniprogram/runtime.md
---
# 8.5 运行机制
* 运行机制:视图层→逻辑层→CDM(查看是否有更新)→网络请求
* 生命周期
## 应用生命周期:
* **onLaunch(Object object)**:小程序初始化完成时触发,全局只触发一次。参数也可以使用 `wx.getLaunchOptionsSync` 获取。
* **onShow(Object object)**:小程序启动,或从后台进入前台显示时触发。也可以使用 `wx.onAppShow` 绑定监听。
* **onHide()**:小程序从前台进入后台时触发。也可以使用 `wx.onAppHide` 绑定监听。
* **onError(String error)**:小程序发生脚本错误或 API 调用报错时触发。也可以使用 `wx.onError` 绑定监听。
* **onPageNotFound(Object object)**:小程序要打开的页面不存在时触发。也可以使用 `wx.onPageNotFound` 绑定监听。
* **onUnhandledRejection(Object object)**:小程序有未处理的 Promise 拒绝时触发。也可以使用 `wx.onUnhandledRejection` 绑定监听。
* **onThemeChange(Object object)**:系统切换主题时触发。也可以使用 `wx.onThemeChange` 绑定监听。
**示例代码:**
```js
App({
onLaunch (options) {
// 小程序初始化完成时触发,全局只触发一次
console.log('小程序启动', options)
},
onShow (options) {
// 小程序启动,或从后台进入前台显示时触发
console.log('小程序显示', options)
},
onHide () {
// 小程序从前台进入后台时触发
console.log('小程序隐藏')
},
onError (msg) {
// 小程序发生脚本错误或 API 调用报错时触发
console.log('小程序错误', msg)
},
onPageNotFound (res) {
// 小程序要打开的页面不存在时触发
wx.redirectTo({
url: 'pages/index/index'
})
},
onUnhandledRejection (res) {
// 小程序有未处理的 Promise 拒绝时触发
console.log('未处理的 Promise 拒绝', res)
},
onThemeChange (res) {
// 系统切换主题时触发
console.log('主题变化', res.theme)
},
globalData: 'I am global data'
})
```
## 页面生命周期:
* **onLoad(Object object)**:页面加载时触发。一个页面只会调用一次,可以在 onLoad 的参数中获取打开当前页面路径中的参数。
* **onShow()**:页面显示时触发。每次打开页面都会调用一次。
* **onReady()**:页面初次渲染完成时触发。一个页面只会调用一次,代表页面已经准备妥当,可以和视图层进行交互。
* **onHide()**:页面隐藏时触发。当 `navigateTo` 或底部 `tab` 切换时调用。
* **onUnload()**:页面卸载时触发。当 `redirectTo` 或 `navigateBack` 的时候调用。
* **onRouteDone()**:页面路由完成时触发。页面路由动画完成时触发,此时页面已经完全显示。
**示例代码:**
```js
Page({
data: {
title: '页面标题'
},
onLoad(options) {
// 页面加载时触发,一个页面只会调用一次
console.log('页面加载', options)
// 获取页面参数
if (options.id) {
this.setData({
id: options.id
})
}
},
onShow() {
// 页面显示时触发,每次打开页面都会调用一次
console.log('页面显示')
},
onReady() {
// 页面初次渲染完成时触发,一个页面只会调用一次
console.log('页面渲染完成')
// 此时可以安全地操作DOM
},
onHide() {
// 页面隐藏时触发,当navigateTo或底部tab切换时调用
console.log('页面隐藏')
},
onUnload() {
// 页面卸载时触发,当redirectTo或navigateBack的时候调用
console.log('页面卸载')
// 清理定时器、事件监听等
},
onRouteDone() {
// 页面路由完成时触发,页面路由动画完成时触发
console.log('页面路由完成')
// 此时页面已经完全显示,可以执行一些需要等待动画完成的操作
}
})
```
:::tip
1. `5s`后挂起,`30min`后自动删除进程。
2. [原生与uni-app小程序应用、页面与组件的完整生命周期](https://blog.csdn.net/ljq547152849/article/details/130881632?spm=1001.2014.3001.5502)
:::
## 页面事件处理函数:
* **onPullDownRefresh()**:监听用户下拉刷新事件。需要在 `app.json` 的 `window` 选项中开启 `enablePullDownRefresh`。
* **onReachBottom()**:监听用户上拉触底事件。
* **onPageScroll(Object object)**:监听用户滚动页面事件。
* **onAddToFavorites(Object object)**:监听用户点击添加到收藏事件。
* **onShareAppMessage(Object object)**:监听用户点击页面内分享事件。
* **onShareTimeline()**:监听用户点击分享到朋友圈事件。
* **onResize(Object object)**:监听用户窗口尺寸变化事件。
* **onTabItemTap(Object object)**:监听用户点击 `tab` 事件。
* **onSaveExitState()**:监听用户保存退出状态事件。
## 组件事件处理函数:
小程序页面除了生命周期函数外,还可以定义组件事件处理函数,用于响应用户的各种交互操作。
---
---
url: /other/database/indexedDB.md
---
# 8.6 IndexedDB
---
---
url: /frontend/miniprogram/router.md
---
# 8.6 路由
## 8.6.1 路由方法
### wx.navigateTo(Object object)
保留当前页面,跳转到应用内的某个页面。不能跳转到 tabBar 页面。
**参数:**
* `url` (string) - 需要跳转的应用内非 tabBar 的页面的路径,路径后可以带参数
* `success` (function) - 接口调用成功的回调函数
* `fail` (function) - 接口调用失败的回调函数
* `complete` (function) - 接口调用结束的回调函数
**示例:**
```js
wx.navigateTo({
url: '/pages/detail/detail?id=1&name=test',
success(res) {
console.log('跳转成功')
},
fail(err) {
console.log('跳转失败', err)
}
})
```
### wx.redirectTo(Object object)
关闭当前页面,跳转到应用内的某个页面。不能跳转到 tabBar 页面。
**参数:**
* `url` (string) - 需要跳转的应用内非 tabBar 的页面的路径,路径后可以带参数
* `success` (function) - 接口调用成功的回调函数
* `fail` (function) - 接口调用失败的回调函数
* `complete` (function) - 接口调用结束的回调函数
**示例:**
```js
wx.redirectTo({
url: '/pages/login/login'
})
```
### wx.reLaunch(Object object)
关闭所有页面,打开到应用内的某个页面。
**参数:**
* `url` (string) - 需要跳转的应用内页面路径,路径后可以带参数
* `success` (function) - 接口调用成功的回调函数
* `fail` (function) - 接口调用失败的回调函数
* `complete` (function) - 接口调用结束的回调函数
**示例:**
```js
wx.reLaunch({
url: '/pages/index/index'
})
```
### wx.switchTab(Object object)
跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面。
**参数:**
* `url` (string) - 需要跳转的 tabBar 页面的路径,路径后不能带参数
* `success` (function) - 接口调用成功的回调函数
* `fail` (function) - 接口调用失败的回调函数
* `complete` (function) - 接口调用结束的回调函数
**示例:**
```js
// app.json 配置
{
"tabBar": {
"list": [{
"pagePath": "pages/index/index",
"text": "首页"
}, {
"pagePath": "pages/profile/profile",
"text": "我的"
}]
}
}
// 跳转到 tabBar 页面
wx.switchTab({
url: '/pages/index/index'
})
```
### wx.navigateBack(Object object)
关闭当前页面,返回上一页面或多级页面。
**参数:**
* `delta` (number) - 返回的页面数,如果 delta 大于现有页面数,则返回到首页
* `success` (function) - 接口调用成功的回调函数
* `fail` (function) - 接口调用失败的回调函数
* `complete` (function) - 接口调用结束的回调函数
**示例:**
```js
// 返回上一页
wx.navigateBack({
delta: 1
})
// 返回多级页面
wx.navigateBack({
delta: 2
})
```
### wx.rewriteRoute(Object object)
重写当前页面路由,用于自定义路由逻辑。
**参数:**
* `route` (string) - 重写的路由路径
* `success` (function) - 接口调用成功的回调函数
* `fail` (function) - 接口调用失败的回调函数
* `complete` (function) - 接口调用结束的回调函数
**示例:**
```js
// 重写当前页面路由
wx.rewriteRoute({
route: '/pages/custom/custom',
success(res) {
console.log('路由重写成功')
},
fail(err) {
console.log('路由重写失败', err)
}
})
```
**使用场景:**
* 动态修改页面路由
* 自定义路由逻辑
* 路由拦截和处理
## 8.6.2 页面参数传递
### 传递参数
```js
// 页面跳转时传递参数
wx.navigateTo({
url: '/pages/detail/detail?id=1&name=test&type=product'
})
```
### 接收参数
```js
// 在目标页面的 onLoad 中接收参数
Page({
onLoad(options) {
console.log(options.id) // "1"
console.log(options.name) // "test"
console.log(options.type) // "product"
}
})
```
## 8.6.3 `getCurrentPages`页面栈管理
### 获取当前页面栈
```js
const pages = getCurrentPages()
const currentPage = pages[pages.length - 1] // 当前页面
const prevPage = pages[pages.length - 2] // 上一个页面
```
### 页面栈限制
* 小程序最多只能打开10个页面
* 当页面栈达到10层时,再调用 `wx.navigateTo` 会失败
### 触发上一页方法
通过 `getCurrentPages` 可以获取页面栈中的页面实例,从而调用上一页的方法或修改上一页的数据。
```js
// 获取页面栈
const pages = getCurrentPages()
const currentPage = pages[pages.length - 1] // 当前页面
const prevPage = pages[pages.length - 2] // 上一个页面
// 调用上一页的方法
if (prevPage && prevPage.updateData) {
prevPage.updateData('来自当前页面的数据')
}
```
## 8.6.4 路由事件
### 应用路由事件
**wx.onBeforeAppRoute(function listener)**
* **功能**:监听应用路由即将发生事件
* **参数**:`listener` - 路由事件的监听函数
* **回调参数**:`res` 对象包含路由相关信息
**wx.offBeforeAppRoute(function listener)**
* **功能**:取消监听应用路由即将发生事件
* **参数**:`listener` - 要取消的监听函数
**wx.onAppRoute(function listener)**
* **功能**:监听应用路由事件
* **参数**:`listener` - 路由事件的监听函数
* **回调参数**:`res` 对象包含路由相关信息
**wx.offAppRoute(function listener)**
* **功能**:取消监听应用路由事件
* **参数**:`listener` - 要取消的监听函数
**wx.onAppRouteDone(function listener)**
* **功能**:监听应用路由完成事件
* **参数**:`listener` - 路由事件的监听函数
* **回调参数**:`res` 对象包含路由相关信息
**wx.offAppRouteDone(function listener)**
* **功能**:取消监听应用路由完成事件
* **参数**:`listener` - 要取消的监听函数
### 页面路由事件
**wx.onBeforePageUnload(function listener)**
* **功能**:监听路由事件引起现有页面实例销毁时,页面实例销毁前的事件监听
* **参数**:`listener` - 路由事件的监听函数
* **回调参数**:`res` 对象包含 `path`(页面路径)、`routeEventId`(路由事件id)、`page`(页面实例)
**wx.offBeforePageUnload(function listener)**
* **功能**:取消监听页面即将卸载事件
* **参数**:`listener` - 要取消的监听函数
**wx.onBeforePageLoad(function listener)**
* **功能**:监听页面即将加载事件
* **参数**:`listener` - 路由事件的监听函数
* **回调参数**:`res` 对象包含 `path`(页面路径)、`routeEventId`(路由事件id)
**wx.offBeforePageLoad(function listener)**
* **功能**:取消监听页面即将加载事件
* **参数**:`listener` - 要取消的监听函数
**wx.onAfterPageLoad(function listener)**
* **功能**:监听页面加载完成事件
* **参数**:`listener` - 路由事件的监听函数
* **回调参数**:`res` 对象包含 `path`(页面路径)、`routeEventId`(路由事件id)
**wx.offAfterPageLoad(function listener)**
* **功能**:取消监听页面加载完成事件
* **参数**:`listener` - 要取消的监听函数
**wx.onAfterPageUnload(function listener)**
* **功能**:监听页面卸载完成事件
* **参数**:`listener` - 路由事件的监听函数
* **回调参数**:`res` 对象包含 `path`(页面路径)、`routeEventId`(路由事件id)
**wx.offAfterPageUnload(function listener)**
* **功能**:取消监听页面卸载完成事件
* **参数**:`listener` - 要取消的监听函数
## 8.6.5 EventChannel 页面间通信
EventChannel 是页面间通信的桥梁,用于在页面跳转时传递数据和事件。
### 创建 EventChannel
```js
// 页面跳转时创建 EventChannel
wx.navigateTo({
url: '/pages/detail/detail',
events: {
// 监听目标页面发送的事件
acceptDataFromOpenerPage: function(data) {
console.log('接收到目标页面数据', data)
},
someEvent: function(data) {
console.log('接收到事件', data)
}
},
success: function(res) {
// 通过 eventChannel 向被打开页面传送数据
res.eventChannel.emit('acceptDataFromOpenerPage', { data: 'test' })
}
})
```
### 在目标页面使用 EventChannel
```js
// 在目标页面 pages/detail/detail.js 中
Page({
onLoad: function(options) {
const eventChannel = this.getOpenerEventChannel()
// 监听上一页面发送的数据
eventChannel.on('acceptDataFromOpenerPage', (data) => {
console.log('接收到上一页面数据', data)
this.setData({
receivedData: data.data
})
})
// 向上一页面发送数据
eventChannel.emit('acceptDataFromOpenerPage', { data: '来自详情页的数据' })
}
})
```
### EventChannel 方法
* **emit(eventName, data)** - 向指定页面发送事件和数据
* **on(eventName, callback)** - 监听指定事件
* **once(eventName, callback)** - 监听指定事件,只触发一次
* **off(eventName, callback)** - 取消监听指定事件
## 8.6.6 自定义路由
```js
// 定义自定义效果,从右侧推入
const slideRouteBuilder = (customRouteContext) => {
const { primaryAnimation } = customRouteContext
const handlePrimaryAnimation = () => {
'worklet'
const transX = windowWidth * (1 - primaryAnimation.value)
return {
transform: `translateX(${transX}px)`,
}
}
return {
handlePrimaryAnimation
}
}
wx.router.addRouteBuilder('slide', slideRouteBuilder)
// 使用自定义路由
wx.navigateTo({
url: 'xxx',
routeType: 'slide'
})
```
---
---
url: /frontend/miniprogram/event.md
---
# 8.7 事件
## 8.7.1 事件类型
### 触摸事件
* **touchstart** - 手指触摸开始
* **touchmove** - 手指触摸后移动
* **touchend** - 手指触摸结束
* **touchcancel** - 手指触摸动作被打断,如来电提醒,弹窗等
### 点击事件
* **tap** - 手指触摸后马上离开
* **longpress** - 手指触摸后,超过350ms再离开,如果指定了事件回调函数并触发了这个事件,tap事件将不被触发
* **longtap** - 手指触摸后,超过350ms再离开(推荐使用longpress事件代替)
## 8.7.2 事件绑定
### 事件绑定语法
```html
点击我
点击我
点击我
```
### 事件绑定方式
* **bind** - 事件绑定不会阻止冒泡阶段中事件继续传播
* **catch** - 事件绑定后,会阻止事件向上冒泡
## 8.7.3 事件对象
### 事件对象属性
| 属性 | 类型 | 说明 |
|------|------|------|
| type | String | 事件类型 |
| timeStamp | Number | 页面打开到触发事件所经过的毫秒数 |
| target | Object | 触发事件的源组件 |
| currentTarget | Object | 事件绑定的当前组件 |
| dataset | Object | 事件源组件上由data-开头的自定义属性组成的集合 |
| mark | Object | 事件触发时,事件冒泡路径上所有的mark会被合并 |
| touches | Array | 触摸事件,当前停留在屏幕中的触摸点信息的数组 |
| changedTouches | Array | 触摸事件,当前变化的触摸点信息的数组 |
| detail | Object | 自定义事件所携带的数据 |
### target 和 currentTarget
* **target** - 触发事件的源组件
* **currentTarget** - 事件绑定的当前组件
**示例:**
```html
内部
```
```js
Page({
handleOuterTap(e) {
console.log('outer tap')
console.log('target:', e.target.id) // "inner"
console.log('currentTarget:', e.currentTarget.id) // "outer"
},
handleInnerTap(e) {
console.log('inner tap')
console.log('target:', e.target.id) // "inner"
console.log('currentTarget:', e.currentTarget.id) // "inner"
}
})
```
## 8.7.4 自定义数据
### dataset 属性
在组件节点中可以附加一些自定义数据,以 `data-` 开头:
```html
DataSet Test
```
```js
Page({
bindViewTap(e) {
console.log(e.currentTarget.dataset.alphaBeta) // 1 (连字符转驼峰)
console.log(e.currentTarget.dataset.alphabeta) // 2 (大写转小写)
}
})
```
### mark 属性
在基础库版本 2.7.1 以上,可以使用 `mark` 来识别具体触发事件的 target 节点:
```html
```
```js
Page({
bindViewTap(e) {
console.log(e.mark.myMark) // "last"
console.log(e.mark.anotherMark) // "leaf"
},
bindButtonTap(e) {
console.log(e.mark.myMark) // "last"
console.log(e.mark.anotherMark) // "leaf"
}
})
```
## 8.7.5 触摸事件详解
### Touch 对象属性
| 属性 | 类型 | 说明 |
|------|------|------|
| identifier | Number | 触摸点的标识符 |
| pageX, pageY | Number | 距离文档左上角的距离 |
| clientX, clientY | Number | 距离页面可显示区域左上角距离 |
### CanvasTouch 对象属性
| 属性 | 类型 | 说明 |
|------|------|------|
| identifier | Number | 触摸点的标识符 |
| x, y | Number | 距离 Canvas 左上角的距离 |
### touches 和 changedTouches
* **touches** - 当前停留在屏幕上的触摸点
* **changedTouches** - 有变化的触摸点(touchstart、touchmove、touchend、touchcancel)
---
---
url: /frontend/miniprogram/component.md
---
# 8.8 组件
## 8.8.1 视图容器
### view
**功能**:视图容器,相当于HTML中的div元素
**属性**:
* `hover-class` (string) - 指定按下去的样式类,当hover-class="none"时,没有点击态效果
* `hover-stop-propagation` (boolean) - 指定是否阻止本节点的祖先节点出现点击态
* `hover-stay-time` (number) - 按住后多久出现点击态,单位毫秒
### scroll-view
**功能**:可滚动视图区域,用于区域滚动
**属性**:
* `scroll-x` (boolean) - 允许横向滚动
* `scroll-y` (boolean) - 允许纵向滚动
* `upper-threshold` (number) - 距顶部/左边多远时,触发scrolltoupper事件
* `lower-threshold` (number) - 距底部/右边多远时,触发scrolltolower事件
* `scroll-top` (number) - 设置竖向滚动条位置
* `scroll-left` (number) - 设置横向滚动条位置
* `scroll-into-view` (string) - 值应为某子元素id,则滚动到该元素
* `scroll-with-animation` (boolean) - 在设置滚动条位置时使用动画过渡
* `enable-back-to-top` (boolean) - iOS点击顶部状态栏、安卓双击标题栏时,滚动条返回顶部
* `refresher-enabled` (boolean) - 开启自定义下拉刷新
* `refresher-threshold` (number) - 设置自定义下拉刷新阈值
* `refresher-default-style` (string) - 设置自定义下拉刷新默认样式
* `refresher-background` (string) - 设置自定义下拉刷新区域背景颜色
* `refresher-triggered` (boolean) - 设置当前下拉刷新状态
**事件**:
* `scrolltoupper` - 滚动到顶部/左边,会触发scrolltoupper事件
* `scrolltolower` - 滚动到底部/右边,会触发scrolltolower事件
* `scroll` - 滚动时触发,`event.detail = {scrollLeft, scrollTop, scrollHeight, scrollWidth, deltaX, deltaY}`
**示例**:
```html
{{item.name}}
```
### match-media
**功能**:匹配检测组件,用于响应式布局
**属性**:
* `min-width` (number) - 页面最小宽度(px)
* `max-width` (number) - 页面最大宽度(px)
* `min-height` (number) - 页面最小高度(px)
* `max-height` (number) - 页面最大高度(px)
* `orientation` (string) - 屏幕方向,可选值:portrait、landscape
**示例**:
```html
当页面宽度在 300 ~ 600 px 之间时展示这里
横屏时显示
```
### movable-area 和 movable-view
**功能**:可移动区域和可移动视图组件
**movable-area 属性**:
* `scale-area` (boolean) - 当里面的movable-view设置为支持双指缩放时,设置此值可将缩放手势生效区域缩小为设置的movable-area区域
**movable-view 属性**:
* `direction` (string) - 移动方向,可选值:all、vertical、horizontal、none
* `inertia` (boolean) - 是否带有惯性
* `out-of-bounds` (boolean) - 是否允许超出边界
* `x` (number) - 定义x轴方向的偏移,如果x的值不在可移动范围内,会自动移动到可移动范围
* `y` (number) - 定义y轴方向的偏移,如果y的值不在可移动范围内,会自动移动到可移动范围
* `damping` (number) - 阻尼系数,用于控制x或y改变时的动画和过界回弹的动画,值越大移动越快
* `friction` (number) - 摩擦系数,用于控制惯性滑动的动画,值越大摩擦力越大,滑动越快停止
* `disabled` (boolean) - 是否禁用
* `scale` (boolean) - 是否支持双指缩放
* `scale-min` (number) - 定义缩放倍数最小值
* `scale-max` (number) - 定义缩放倍数最大值
* `scale-value` (number) - 定义缩放倍数,取值范围为 0.5-10
* `animation` (boolean) - 是否使用动画
**事件**:
* `change` - 拖动过程中触发的事件
* `scale` - 缩放过程中触发的事件
* `htouchmove` - 初次手指触摸后移动为横向的移动时触发的事件
* `vtouchmove` - 初次手指触摸后移动为纵向的移动时触发的事件
**示例**:
```html
可移动内容
```
### page-container
**功能**:页面容器,当用户进行返回操作时,关闭该容器而不关闭页面
**属性**:
* `show` (boolean) - 是否显示容器
* `overlay` (boolean) - 是否显示遮罩层
* `position` (string) - 容器出现的位置,可选值:bottom、center、top
* `round` (boolean) - 是否显示圆角
* `close-on-slide-down` (boolean) - 是否在下滑时关闭容器
* `close-on-overlay-click` (boolean) - 是否在点击遮罩层时关闭容器
* `safe-area-inset-bottom` (boolean) - 是否开启底部安全区适配
* `safe-area-inset-top` (boolean) - 是否开启顶部安全区适配
**事件**:
* `enter` - 容器进入时触发
* `leave` - 容器离开时触发
* `after-enter` - 容器进入动画结束时触发
* `after-leave` - 容器离开动画结束时触发
**示例**:
```html
页面容器内容
```
### root-portal
**功能**:用于将组件从页面脱离出来,类似于Vue3的Teleport方法
**属性**:
* `target` (string) - 指定挂载的节点,可选值为一个选择器
**示例**:
```html
脱离页面的内容
```
### swiper 和 swiper-item
**功能**:滑块视图容器,用于轮播图等场景
**swiper 属性**:
* `indicator-dots` (boolean) - 是否显示面板指示点
* `indicator-color` (string) - 指示点颜色
* `indicator-active-color` (string) - 当前选中的指示点颜色
* `autoplay` (boolean) - 是否自动切换
* `current` (number) - 当前所在滑块的index
* `interval` (number) - 自动切换时间间隔
* `duration` (number) - 滑动动画时长
* `circular` (boolean) - 是否采用衔接滑动
* `vertical` (boolean) - 滑动方向是否为纵向
* `previous-margin` (string) - 前边距,可用于露出前一个swiper-item
* `next-margin` (string) - 后边距,可用于露出后一个swiper-item
* `display-multiple-items` (number) - 同时显示的滑块数量
* `skip-hidden-item-layout` (boolean) - 是否跳过未显示的滑块布局
* `easing-function` (string) - 指定swiper切换动画类型
**swiper-item 属性**:
* `item-id` (string) - 该swiper-item的标识符
**事件**:
* `change` - current改变时会触发change事件
* `transition` - swiper-item的位置发生改变时会触发transition事件
* `animationfinish` - 动画结束时会触发animationfinish事件
**示例**:
```html
```
## 8.8.2 基础内容
### icon
**功能**:图标组件,用于显示各种图标
**属性**:
* `type` (string) - 图标类型,有效值:success, success\_no\_circle, info, warn, waiting, cancel, download, search, clear
* `size` (number) - 图标大小,单位px,默认值:23
* `color` (string) - 图标颜色,同CSS的color
**示例**:
```html
```
### text
**功能**:文本组件,用于显示文本内容
**属性**:
* `space` (string) - 显示连续空格,可选值:ensp、emsp、nbsp
* `decode` (boolean) - 是否解码,默认值:false
* `user-select` (boolean) - 文本是否可选,默认值:false
**示例**:
```html
这是一段可选择的文本,支持空格和解码
```
### rich-text
**功能**:富文本组件,用于显示富文本内容
**属性**:
* `nodes` (array/string) - 节点列表/HTML String,支持Array和String类型
* `space` (string) - 显示连续空格,可选值:ensp、emsp、nbsp
* `user-select` (boolean) - 富文本是否可选,默认值:false
**nodes 属性支持两种类型**:
1. **String 类型**:节点字符串
2. **Array 类型**:节点数组
### progress
**功能**:进度条组件,用于显示操作进度
**属性**:
* `percent` (number) - 百分比0~100,默认值:0
* `show-info` (boolean) - 在进度条右侧显示百分比,默认值:false
* `stroke-width` (number) - 进度条线的宽度,单位px,默认值:6
* `color` (string) - 进度条颜色(请使用activeColor),默认值:#09BB07
* `activeColor` (string) - 已选择的进度条的颜色
* `backgroundColor` (string) - 未选择的进度条的颜色
* `active` (boolean) - 进度条从左往右的动画,默认值:false
* `active-mode` (string) - backwards: 动画从头播;forwards: 动画从上次结束点接着播,默认值:backwards
* `duration` (number) - 进度增加1%所需毫秒数,默认值:30
**事件**:
* `bindactiveend` - 动画完成事件
**示例**:
```html
```
### selection
**功能**:选择器组件,用于文本选择
**属性**:
* `text` (string) - 要选择的文本内容
* `start` (number) - 选择起始位置,从0开始
* `end` (number) - 选择结束位置,从0开始
* `color` (string) - 选中文本的背景色,默认值:#007aff
* `background-color` (string) - 选中文本的背景色,默认值:#007aff
**事件**:
* `bindselect` - 选择文本时触发,`event.detail = {text, start, end}`
**示例**:
```html
这是一段可以被选择的文本内容
```
## 8.8.3 表单组件
### button
**功能**:按钮组件,用于触发操作
**属性**:
* `size` (string) - 按钮大小,可选值:default、mini,默认值:default
* `type` (string) - 按钮样式类型,可选值:primary、default、warn,默认值:default
* `plain` (boolean) - 按钮是否镂空,背景色透明,默认值:false
* `disabled` (boolean) - 是否禁用,默认值:false
* `loading` (boolean) - 名称前是否带 loading 图标,默认值:false
* `form-type` (string) - 用于 form 组件,点击分别会触发 form 的 submit/reset 事件
* `open-type` (string) - 微信开放能力,可选值:contact、share、getPhoneNumber、getUserInfo、launchApp、openSetting、feedback、chooseAvatar、getRealtimePhoneNumber、agreeprivacyauthorization、chooseAddress、subscribe、lifestyle、launchFacialRecognitionVerify、openBluetoothAdapter、getAuthorize、onWatchPrivacyPolicy、chooseInvoiceTitle、subscribeMessage、liveActivity
* `hover-class` (string) - 指定按钮按下去的样式类,当hover-class="none"时,没有点击态效果
* `hover-stop-propagation` (boolean) - 指定是否阻止本节点的祖先节点出现点击态
* `hover-stay-time` (number) - 按住后多久出现点击态,单位毫秒
* `lang` (string) - 指定返回用户信息的语言,zh\_CN 简体中文,zh\_TW 繁体中文,en 英文
* `session-from` (string) - 会话内消息卡片标题,open-type="contact"时有效
* `send-message-title` (string) - 会话内消息卡片标题,open-type="contact"时有效
* `send-message-path` (string) - 会话内消息卡片点击跳转小程序路径,open-type="contact"时有效
* `send-message-img` (string) - 会话内消息卡片图片,open-type="contact"时有效
* `app-parameter` (string) - 打开 APP 时,向 APP 传递的参数,open-type=launchApp时有效
* `show-message-card` (boolean) - 显示会话内消息卡片,open-type="contact"时有效
* `bindgetphonenumber` (eventhandle) - 手机号快速验证回调,open-type=getPhoneNumber时有效
* `bindgetrealtimephonenumber` (eventhandle) - 手机号实时验证回调,open-type=getRealtimePhoneNumber时有效
* `binderror` (eventhandle) - 当使用开放能力时,发生错误的回调
* `bindopensetting` (eventhandle) - 在打开授权设置页后回调,open-type=openSetting时有效
* `bindlaunchapp` (eventhandle) - 打开 APP 成功的回调,open-type=launchApp时有效
* `bindchooseavatar` (eventhandle) - 获取用户头像回调,open-type=chooseAvatar时有效
* `bindagreeprivacyauthorization` (eventhandle) - 用户同意隐私协议事件回调
**示例**:
```html
```
### checkbox
**功能**:多选项目组件
**属性**:
* `value` (string) - checkbox标识,选中时触发checkbox-group的 change 事件,并携带 checkbox 的 value
* `disabled` (boolean) - 是否禁用,默认值:false
* `checked` (boolean) - 当前是否选中,默认值:false
* `color` (string) - checkbox的颜色,同css的color
**示例**:
```html
选项1
选项2
```
### checkbox-group
**功能**:多项选择器,内部由多个checkbox组成
**属性**:
* `bindchange` (eventhandle) - checkbox-group中选中项发生改变时触发 change 事件
**示例**:
```html
选项1
选项2
选项3
```
### form
**功能**:表单组件,用于将组件内的用户输入的switch input checkbox slider radio textarea 提交
**属性**:
* `report-submit` (boolean) - 是否返回 formId 用于发送模板消息,默认值:false
* `report-submit-timeout` (number) - 等待一段时间(毫秒)后,自动提交表单,默认值:0
* `bindsubmit` (eventhandle) - 携带 form 中的数据触发 submit 事件
* `bindreset` (eventhandle) - 表单重置时会触发 reset 事件
**示例**:
```html
```
### input
**功能**:输入框组件
**属性**:
* `value` (string) - 输入框的初始内容
* `type` (string) - input 的类型,可选值:text、number、idcard、digit、safe-password、nickname
* `password` (boolean) - 是否是密码类型,默认值:false
* `placeholder` (string) - 输入框为空时占位符
* `placeholder-style` (string) - 指定 placeholder 的样式
* `placeholder-class` (string) - 指定 placeholder 的样式类
* `disabled` (boolean) - 是否禁用,默认值:false
* `maxlength` (number) - 最大输入长度,设置为 -1 的时候不限制最大长度
* `cursor-spacing` (number) - 指定光标与键盘的距离,单位 px
* `auto-focus` (boolean) - 自动聚焦,拉起键盘,默认值:false
* `focus` (boolean) - 获取焦点,默认值:false
* `confirm-type` (string) - 设置键盘右下角按钮的文字,可选值:send、search、next、go、done
* `confirm-hold` (boolean) - 点击键盘右下角按钮时是否保持键盘不收起,默认值:false
* `cursor` (number) - 指定focus时的光标位置
* `selection-start` (number) - 光标起始位置,自动聚集时有效,需与selection-end搭配使用
* `selection-end` (number) - 光标结束位置,自动聚集时有效,需与selection-start搭配使用
* `adjust-position` (boolean) - 键盘弹起时,是否自动上推页面,默认值:true
* `hold-keyboard` (boolean) - 键盘弹起时,是否自动上推页面,默认值:false
* `bindinput` (eventhandle) - 键盘输入时触发
* `bindfocus` (eventhandle) - 输入框聚焦时触发
* `bindblur` (eventhandle) - 输入框失去焦点时触发
* `bindconfirm` (eventhandle) - 点击键盘完成时触发
* `bindkeyboardheightchange` (eventhandle) - 键盘高度发生变化的时候触发
**示例**:
```html
```
### label
**功能**:用来改进表单组件的可用性
**属性**:
* `for` (string) - 绑定控件的 id
**示例**:
```html
```
### picker
**功能**:从底部弹起的滚动选择器
**属性**:
* `mode` (string) - 选择器类型,可选值:selector、multiSelector、time、date、region
* `range` (array) - mode为 selector 或 multiSelector 时,range 有效
* `range-key` (string) - 当 range 是一个 Object Array 时,通过 range-key 来指定 Object 中 key 的值作为选择器显示内容
* `value` (number) - value 的值表示选择了 range 中的第几个(下标从 0 开始)
* `start` (string) - 有效值 start 表示有效日期范围的开始
* `end` (string) - 有效值 end 表示有效日期范围的结束
* `fields` (string) - 有效值 year、month、day,表示选择器的粒度
* `bindchange` (eventhandle) - value 改变时触发 change 事件
* `bindcancel` (eventhandle) - 取消选择时触发
**示例**:
```html
当前选择:{{array[index]}}
当前时间:{{time}}
当前日期:{{date}}
```
### picker-view
**功能**:嵌入页面的滚动选择器
**属性**:
* `value` (array) - 数组中的数字依次表示 picker-view 内的 picker-view-column 选择的第几项(下标从 0 开始),数字大于 picker-view-column 可选项长度时,选择最后一项
* `indicator-style` (string) - 设置选择器中间选中框的样式
* `indicator-class` (string) - 设置选择器中间选中框的类名
* `mask-style` (string) - 设置蒙层的样式
* `mask-class` (string) - 设置蒙层的类名
* `bindchange` (eventhandle) - 当滚动选择,value 改变时触发 change 事件
**示例**:
```html
{{item}}年
{{item}}月
{{item}}日
```
### radio
**功能**:单选项目组件
**属性**:
* `value` (string) - radio 标识。当该radio 选中时,radio-group 的 change 事件会携带radio的value
* `checked` (boolean) - 当前是否选中,默认值:false
* `disabled` (boolean) - 是否禁用,默认值:false
* `color` (string) - radio的颜色,同css的color
**示例**:
```html
选项1
选项2
```
### radio-group
**功能**:单项选择器,内部由多个 radio 组成
**属性**:
* `bindchange` (eventhandle) - radio-group 中的选中项发生变化时触发 change 事件
**示例**:
```html
选项1
选项2
选项3
```
### slider
**功能**:滑动选择器组件
**属性**:
* `min` (number) - 最小值,默认值:0
* `max` (number) - 最大值,默认值:100
* `step` (number) - 步长,取值必须大于 0,并且可被(max - min)整除,默认值:1
* `disabled` (boolean) - 是否禁用,默认值:false
* `value` (number) - 当前取值,默认值:0
* `activeColor` (string) - 已选择的颜色,默认值:#1aad19
* `backgroundColor` (string) - 背景条的颜色,默认值:#e9e9e9
* `block-size` (number) - 滑块的大小,取值范围为 12 - 28,默认值:28
* `block-color` (string) - 滑块的颜色,默认值:#ffffff
* `show-value` (boolean) - 是否显示当前 value,默认值:false
* `bindchange` (eventhandle) - 完成一次拖动后触发的事件
* `bindchanging` (eventhandle) - 拖动过程中触发的事件
**示例**:
```html
```
### switch
**功能**:开关选择器组件
**属性**:
* `checked` (boolean) - 是否选中,默认值:false
* `disabled` (boolean) - 是否禁用,默认值:false
* `type` (string) - 样式,有效值:switch、checkbox,默认值:switch
* `color` (string) - switch 的颜色,同 css 的 color
* `bindchange` (eventhandle) - checked 改变时触发 change 事件
**示例**:
```html
```
### textarea
**功能**:多行输入框组件
**属性**:
* `value` (string) - 输入框的内容
* `placeholder` (string) - 输入框为空时占位符
* `placeholder-style` (string) - 指定 placeholder 的样式
* `placeholder-class` (string) - 指定 placeholder 的样式类
* `disabled` (boolean) - 是否禁用,默认值:false
* `maxlength` (number) - 最大输入长度,设置为 -1 的时候不限制最大长度
* `auto-height` (boolean) - 是否自动增高,设置auto-height时,style.height不生效,默认值:false
* `fixed` (boolean) - 如果 textarea 是在一个 position:fixed 的区域,需要显示指定属性 fixed 为 true,默认值:false
* `cursor-spacing` (number) - 指定光标与键盘的距离,单位 px
* `cursor` (number) - 指定focus时的光标位置
* `show-confirm-bar` (boolean) - 是否显示键盘上方工具栏,默认值:true
* `selection-start` (number) - 光标起始位置,自动聚集时有效,需与selection-end搭配使用
* `selection-end` (number) - 光标结束位置,自动聚集时有效,需与selection-start搭配使用
* `adjust-position` (boolean) - 键盘弹起时,是否自动上推页面,默认值:true
* `hold-keyboard` (boolean) - 键盘弹起时,是否自动上推页面,默认值:false
* `bindfocus` (eventhandle) - 输入框聚焦时触发
* `bindblur` (eventhandle) - 输入框失去焦点时触发
* `bindlinechange` (eventhandle) - 输入框行数变化时调用
* `bindinput` (eventhandle) - 当键盘输入时,触发 input 事件
* `bindconfirm` (eventhandle) - 点击键盘完成时触发
* `bindkeyboardheightchange` (eventhandle) - 键盘高度发生变化的时候触发
**示例**:
```html
```
### keyboard-accessory
**功能**:键盘辅助组件,用于在键盘上方显示自定义内容
**属性**:
* `bindtap` (eventhandle) - 点击键盘辅助组件时触发
* `bindlongpress` (eventhandle) - 长按键盘辅助组件时触发
**示例**:
```html
常用短语
表情
@用户
```
**使用场景**:
* 在输入框上方显示常用短语
* 显示表情选择器
* 显示@用户功能
* 其他键盘相关的快捷操作
### editor
**功能**:富文本编辑器组件,用于编辑富文本内容
**属性**:
* `read-only` (boolean) - 设置编辑器为只读,默认值:false
* `placeholder` (string) - 提示信息
* `show-img-size` (boolean) - 点击图片时显示图片大小控件,默认值:false
* `show-img-toolbar` (boolean) - 点击图片时显示工具栏控件,默认值:false
* `show-img-resize` (boolean) - 点击图片时显示修改尺寸控件,默认值:false
* `bindready` (eventhandle) - 编辑器初始化完成时触发
* `bindinput` (eventhandle) - 编辑器内容改变时触发
* `bindfocus` (eventhandle) - 编辑器获得焦点时触发
* `bindblur` (eventhandle) - 编辑器失去焦点时触发
* `bindstatuschange` (eventhandle) - 通过 context 方法改变编辑器内样式时触发
**方法**:
* `EditorContext.setContents(Object object)` - 初始化编辑器内容
* `EditorContext.getContents(Object object)` - 获取编辑器内容
* `EditorContext.clear(Object object)` - 清空编辑器内容
* `EditorContext.removeFormat(Object object)` - 清除当前选区的样式
* `EditorContext.format(String name, String value)` - 设置当前选区的样式
* `EditorContext.insertText(Object object)` - 在当前光标位置插入文本
* `EditorContext.insertImage(Object object)` - 在当前光标位置插入图片
* `EditorContext.scrollIntoView()` - 使编辑器光标滚动到可视区域
### editor-portal
**功能**:渲染 editor 组件的自定义区块,相关接口 EditorCtx.insertCustomBlock
**属性**:
* `key` (string) - 自定义区块的 blockId,必填
**示例**:
```html
```
## 8.8.4 导航
### navigator
**功能**:页面链接组件,用于页面跳转
**属性**:
* `target` (string) - 在哪个目标上发生跳转,默认值:self
* `self` - 当前小程序
* `miniProgram` - 其它小程序
* `url` (string) - 当前小程序内的跳转链接
* `open-type` (string) - 跳转方式,默认值:navigate
* `navigate` - 对应 `wx.navigateTo` 或 `wx.navigateToMiniProgram` 的功能
* `redirect` - 对应 `wx.redirectTo` 的功能
* `switchTab` - 对应 `wx.switchTab` 的功能
* `reLaunch` - 对应 `wx.reLaunch` 的功能
* `navigateBack` - 对应 `wx.navigateBack` 或 `wx.navigateBackMiniProgram` 的功能
* `exit` - 退出小程序,target="miniProgram"时生效
* `version` (string) - 当target="miniProgram"且open-type="navigate"时有效,要打开的小程序版本,默认值:release
* `develop` - 开发版
* `trial` - 体验版
* `release` - 正式版
* `short-link` (string) - 当target="miniProgram"时有效,当传递该参数后,可以不传 app-id 和 path
* `app-id` (string) - 当target="miniProgram"时有效,要打开的小程序 appId
* `path` (string) - 当target="miniProgram"时有效,打开的页面路径,如果为空则打开首页
* `extra-data` (object) - 当target="miniProgram"时有效,需要传递给目标小程序的数据,目标小程序可在 App.onLaunch(),App.onShow() 中获取到这份数据
* `delta` (number) - 当open-type="navigateBack"时有效,返回的页面数,如果 delta 大于现有页面数,则返回到首页
* `hover-class` (string) - 指定点击时的样式类,当hover-class="none"时,没有点击态效果,默认值:navigator-hover
* `hover-stop-propagation` (boolean) - 指定是否阻止本节点的祖先节点出现点击态,默认值:false
* `hover-start-time` (number) - 按住后多久出现点击态,单位毫秒,默认值:50
* `hover-stay-time` (number) - 手指松开后点击态保留时间,单位毫秒,默认值:600
* `bindsuccess` (string) - 当target="miniProgram"且open-type="navigate/navigateBack"时有效,跳转小程序成功
* `bindfail` (string) - 当target="miniProgram"且open-type="navigate/navigateBack"时有效,跳转小程序失败
* `bindcomplete` (string) - 当target="miniProgram"且open-type="navigate/navigateBack"时有效,跳转小程序完成
**使用限制**:
1. 需要用户确认跳转:从 `2.3.0` 版本开始,在跳转至其他小程序前,将统一增加弹窗,询问是否跳转,用户确认后才可以跳转其他小程序
2. 从2020年4月24日起,跳转其他小程序将不再受数量限制
**注意事项**:
* `navigator` 在 Skyline 下视为文本节点,只能嵌套文本节点(如 text),不能嵌套 view、button 等普通节点
* 新增 `span` 组件用于内联文本和图片
* `navigator-hover` 默认为 `{background-color: rgba(0, 0, 0, 0.1); opacity: 0.7;}`,navigator 的子节点背景色应为透明色
* `app-id` 必须是有效的微信小程序 appId,格式为 wx 开头的字符串
* `path` 路径必须以 `/` 开头,如果不传或为空,则打开目标小程序的首页
* `extra-data` 传递的数据会在目标小程序的 `App.onLaunch()` 和 `App.onShow()` 中通过 `options.referrerInfo.extraData` 获取
* `delta` 用于 `navigateBack` 时指定返回的页面层数,如果 delta 大于现有页面数,则返回到首页
**示例**:
```html
跳转到新页面
在当前页打开
切换 Tab
打开绑定的小程序
使用短链接跳转
返回上一页
返回多级页面
打开其他小程序并传递数据
```
### functional-page-navigator
**功能**:用于跳转插件功能页的组件
**属性**:
* `version` (string) - 跳转的小程序版本,默认值:release
* `name` (string) - 要跳转的功能页 name
* `args` (object) - 功能页参数,参数格式与具体功能页相关
* `bindsuccess` (eventhandle) - 功能页调用成功
* `bindfail` (eventhandle) - 功能页调用失败
**示例**:
```html
登录功能页
```
## 8.8.5 媒体组件
### camera
**功能**:系统相机组件,支持拍照和扫码功能
**属性**:
* `mode` (string) - 应用模式,只在初始化时有效,不能动态变更,默认值:normal
* `normal` - 相机模式
* `scanCode` - 扫码模式
* `resolution` (string) - 分辨率,不支持动态修改,默认值:medium
* `low` - 低分辨率
* `medium` - 中分辨率
* `high` - 高分辨率
* `device-position` (string) - 摄像头朝向,默认值:back
* `front` - 前置摄像头
* `back` - 后置摄像头
* `flash` (string) - 闪光灯,默认值:auto
* `auto` - 自动
* `on` - 打开
* `off` - 关闭
* `torch` - 常亮(2.8.0+)
* `frame-size` (string) - 指定期望的相机帧数据尺寸,默认值:medium
* `small` - 小尺寸帧数据
* `medium` - 中尺寸帧数据
* `large` - 大尺寸帧数据
**事件**:
* `bindstop` - 摄像头在非正常终止时触发,如退出后台等情况
* `binderror` - 用户不允许使用摄像头时触发
* `bindinitdone` - 相机初始化完成时触发,`e.detail = {maxZoom}`
* `bindscancode` - 在扫码识别成功时触发,仅在 `mode="scanCode"` 时生效
**注意事项**:
* 同一页面只能插入一个 `camera` 组件
* 需要用户授权 `scope.camera`
* 扫码二维码功能需升级微信客户端至6.7.3
* 2.10.0起 `initdone` 事件返回 `maxZoom`,最大变焦范围
* 原生组件使用限制
* `onCameraFrame` 接口根据 `frame-size` 返回不同尺寸的原始帧数据,与 Camera 组件展示的图像不同,其实际像素值由系统决定
### channel-live
**功能**:小程序内嵌视频号直播组件,展示视频号直播状态和封面,并无弹窗跳转至视频号
**属性**:
* `feed-id` (string) - 视频 feedId,必填
* `finder-user-name` (string) - 视频号 id,以"sph"开头的id,可在视频号助手获取,必填
**注意事项**:
* 基础库 2.29.0 开始支持,低版本需做兼容处理
* 使用该组件打开的视频号视频需要与小程序的主体一致
* 视频号必须与当前小程序相同主体
### channel-video
**功能**:小程序内嵌视频号视频组件,用于播放视频号视频内容
**属性**:
* `feed-id` (string) - 仅视频号视频与小程序同主体时生效,若内嵌非同主体视频,请使用 feed-token
* `finder-user-name` (string) - 视频号 id,以"sph"开头的id,可在视频号助手获取,视频号必须与当前小程序相同主体
* `feed-token` (string) - 仅内嵌小程序非同主体视频号视频时使用,获取方式参考[feed-token 指引](https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/channels-activity#feed-token)
* `autoplay` (string) - 是否自动播放,仅视频号视频与小程序同主体时支持设置为 true
* `loop` (boolean) - 是否循环播放,默认值:false
* `muted` (boolean) - 是否静音播放,默认值:false
* `object-fit` (string) - 当视频大小与 video 容器大小不一致时,视频的表现形式,默认值:contain
* `contain` - 包含
* `fill` - 填充
* `cover` - 覆盖
**事件**:
* `binderror` - 视频播放出错时触发
**注意事项**:
* 基础库 2.31.1 开始支持 feed-token 和 autoplay 属性
* 暂不支持纯图片视频号内容
* 视频号必须与当前小程序相同主体
### image
**功能**:图片组件,支持 JPG、PNG、SVG、WEBP、GIF 等格式,2.3.0 起支持云文件ID
**属性**:
* `src` (string) - 图片资源地址
* `mode` (string) - 图片裁剪、缩放的模式,默认值:scaleToFill
* `scaleToFill` - 缩放模式,不保持纵横比缩放图片,使图片的宽高完全拉伸至填满 image 元素
* `aspectFit` - 缩放模式,保持纵横比缩放图片,使图片的长边能完全显示出来
* `aspectFill` - 缩放模式,保持纵横比缩放图片,只保证图片的短边能完全显示出来
* `widthFix` - 缩放模式,宽度不变,高度自动变化,保持原图宽高比不变
* `heightFix` - 缩放模式,高度不变,宽度自动变化,保持原图宽高比不变(2.10.3+)
* `top` - 裁剪模式,不缩放图片,只显示图片的顶部区域(仅 WebView 支持)
* `bottom` - 裁剪模式,不缩放图片,只显示图片的底部区域(仅 WebView 支持)
* `center` - 裁剪模式,不缩放图片,只显示图片的中间区域(仅 WebView 支持)
* `left` - 裁剪模式,不缩放图片,只显示图片的左边区域(仅 WebView 支持)
* `right` - 裁剪模式,不缩放图片,只显示图片的右边区域(仅 WebView 支持)
* `top left` - 裁剪模式,不缩放图片,只显示图片的左上边区域(仅 WebView 支持)
* `top right` - 裁剪模式,不缩放图片,只显示图片的右上边区域(仅 WebView 支持)
* `bottom left` - 裁剪模式,不缩放图片,只显示图片的左下边区域(仅 WebView 支持)
* `bottom right` - 裁剪模式,不缩放图片,只显示图片的右下边区域(仅 WebView 支持)
* `show-menu-by-longpress` (boolean) - 长按图片显示发送给朋友、收藏、保存图片、搜一搜、打开名片/前往群聊/打开小程序的菜单,默认值:false
* `binderror` (eventhandle) - 当错误发生时触发,`event.detail = {errMsg}`
* `bindload` (eventhandle) - 当图片载入完毕时触发,`event.detail = {height, width}`
**Skyline 特有属性**:
* `fade-in` (boolean) - 是否渐显,默认值:false
**WebView 特有属性**:
* `webp` (boolean) - 默认不解析 webP 格式,只支持网络资源,默认值:false
* `lazy-load` (boolean) - 图片懒加载,在即将进入一定范围(上下三屏)时才开始加载,Skyline 默认懒加载,默认值:false
* `forceHttps` (boolean) - 自动将 http 链接替换为 https 链接,默认值:false
**支持长按识别的码**:
* 小程序码
* 微信个人码(2.18.0+)
* 企业微信个人码(2.18.0+)
* 普通群码(2.18.0+)
* 互通群码(2.18.0+)
* 公众号二维码(2.18.0+)
**注意事项**:
* image 组件默认宽度 320px、高度 240px
* image 组件进行缩放时,计算出来的宽高可能带有小数,在不同 webview 内核下渲染可能会被抹去小数部分
* 使用 svg 格式且 mode=scaleToFill 时,WebView 会居中(除非 svg 里加上 preserveAspectRatio="none"),Skyline 则会撑满
* svg 格式不支持百分比单位
* svg 格式不支持 `
```
webpack.config.js文件:
```js
const VueLoaderPlugin = require("vue-loader/lib/plugin");
plugins:[new VueLoaderPlugin()]
```
### 7.3.11 自定义插件
#### 引用方法一:
webpack.cofig.js文件:
```js
module: {
rules:[{
test: /\.js$/,
use:[path.resolve('./src/loader/myloader')],}
}]
```
#### 引用方法二:
将自定义插件的文件放入node\_modules文件中
webpack.cofig.js文件:
```js
module: {
rules:[{
test: /\.js$/,
use:['myloader']
}]}
```
#### 引用方法三:
webpack.cofig.js文件:
```js
resolveLoader:{
modules:[path.resolve("./src/loader"),path.resolve("./node_modules")],
}
module: {
rules:[{
test: /\.js$/,
use:['myloader']
}]
}
```
### 7.3.12 常用插件
## 7.4 常用plugins指南
### 7.4.1 热更新webpack-dev-server
插件:`npm i webpack-dev-server -g`
* webpack.cofig.js文件
```js
plugins: [
new webpack.HotModuleReplacementPlugin()
],
devServer:{
contentBase:'./dist',
hot:true
}
```
在package.json中设置
```js
"scripts": {
"dev":"webpack-dev-server --open"
}
```
* 远程热更新webpack-dev-middleware
### 7.4.2 html文件的编译、压缩Html-webpack-plugin
插件:`npm i html-webpack-plugin --save-dev`
作用:根据配置的html产生一个引入打包后文件的html
配置:
webpack.cofig.js文件:
```js
const Htmlwebpackplugin = require("html-webpack-plugin");//引入插件
plugins: [
new Htmlwebpackplugin({
template: "./src/index.html", //模板
filename: "index.html", // 打包出来的文件名
inject: 'body', // 路径放置位置 head/body
title: 'this is index', //打包后的htmltitle
minify: { //html设置压缩参数
html5:true,
collapseWhitespace: true,
preserveLineBreaks:false,
minifyCSS:true,
minifyJS:true,
removeComments: true,
},
chunks: ['index'],// 指定chunks
excludeChunks: [],//排除chunks
}),
]
```
Index.html文件:
```html
<%= htmlWebpackPlugin.options.title%> //获取htmlWebpackPlugin下的title的数据
```
### 7.4.3 多页面打包html-webpack-plugin、glob
一个页面对应一个`entry`、一个`html-webpack-plugin`\
优势:利于SEO\
缺点:每次新增或删除页面都需要配置webpack,利用`glob-sync`可以解决这个问题\
`npm i glob -D`
```js
'use strict'
const path = require('path')
const glob = require('glob')
const htmlWebpackPlugin = require('html-webpack-plugin');
const setMPA = () => {
const entry = {};
const htmlWebpackPlugins = []
const entryFiles = glob.sync(path.join(__dirname, './src/*/index.js'))
Object.keys(entryFiles).map((index) => {
const entryFile = entryFiles[index];
const match = entryFile.match(/src\/(.*)\/index\.js/);
const pageName = match && match[1];
entry[pageName] = entryFile;
console.log(pageName,1111,entryFile)
htmlWebpackPlugins.push(
new htmlWebpackPlugin({
template: path.join(__dirname,`src/${pageName}/index.html`), //模板
filename: `${pageName}.html`, // 打包出来的文件名
inject: 'body', // 路径放置位置 head/body
minify: { //html设置压缩参数
chunks: [pageName],// 指定chunks
html5: true,
collapseWhitespace: true,
preserveLineBreaks: false,
minifyCSS: true,
minifyJS: true,
removeComments: true,
},
}),
);
})
return {
entry,
htmlWebpackPlugins
}
}
const {entry,htmlWebpackPlugins} = setMPA();
module.exports = {
entry:entry,
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name]_[hash:8].js'
},
mode: 'production',
plugins: [].concat(htmlWebpackPlugins)
}
```
### 7.4.4 js文件压缩terser-webpack-plugin
`uglifyjs-webpack-plugin:webpack`4.x已内置,不支持ES6语法,不建议新项目使用。
`terser-webpack-plugin`5.x已内置,4.x需要单独安装。
插件:`npm i terser-webpack-plugin --save-dev`
```js
const TerserPlugin = require("terser-webpack-plugin");
module.exports = {
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
test: /\.js(\?.*)?$/i, // 通过正则匹配要压缩的文件
include: /\/includes/, // 指定要压缩的文件
exclude: /\/excludes/, // 排除文件
}),
],
},
};
```
### 7.4.5 css文件压缩optimize-css-assets-webpack-plugin/cssnano
插件:`npm i optimize-css-assets-webpack-plugin --save-dev`/`npm i cssnano --save-dev`
```js
const Htmlwebpackplugin = require("optimize-css-assets-webpack-plugin");
const Htmlwebpackplugin = require("cssnano");
module.exports={
entry:{
app:'./src/app.js',
search:'./src/search.js'
},
output:{
filename:'[name][chunkhash:8].js',
path:__dirname+'./dist'
},
Plugins:[
new OptimizeCssAssetsPlugin({
assetNameRegExp:/\.css$/g,
cssProcessor:require('cssnano')
})
]
}
```
### 7.4.6 自动清理构建目录clean-webpack-plugin
插件:`npm i clean-webpack-plugin -D`
```js
const Htmlwebpackplugin = require("clean-webpack-plugin");
module.exports={
entry:{
app:'./src/app.js',
search:'./src/search.js'
},
output:{
filename:'[name][chunkhash:8].js',
path:__dirname+'./dist'
},
Plugins:[
new CleanWebpackPlugin()
]
}
```
也可以使用`rimraf`插件清理构建目录
### 7.4.7 source map
作用:通过source map定位到源代码。
source map关键字:
> `eval`:使用`eval`包裹模块代码。
> `source map`产生.map文件
> `cheap`不包含代码的所在列信息
> `inline`将.map做为DataURI嵌入,不单独生成.map文件
> `module`包含loader的sourcemap
> source map类型:
```js
const HtmlWebpackPlugin = require('html-webpack-plugin'); // 通过npm安装
const webpack = require('webpack'); // 用于访问内置插件
module.exports = {
entry: {
app: './src/index.js',
},
output:{
path:path.resolve(__dirname,'dist'),
filename:'[name].js'
},
mode:'production',
module: {
rules: [
{ test: /\.js$/, use: 'babel-loader' }
]
},
devtool:'eval' // eval\source-map\inline-source-map\cheap-source-map
};
```
:::warning
线上环境排查问题时可以开启,否则要关闭source map,否则会暴露源代码。
:::
### 7.4.8 基础库的分离html-webpack-externals-plugin/SplitChunksPlugin
* `html-webpack-externals-plugin`
插件:`npm i html-webpack-externals-plugin -D`
```js
const HtmlWebpackExternalsPlugin = require('html-webpack-externals-plugin')
module.exports={
Plugins:[
new HtmlWebpackExternalsPlugin({
externals:[
{
module:'react',
entry:'dist/jquery.min.js', //可以是本地也可以是cdn文件
global:'React',
},
{
module:'react-dom',
entry:'dist/jquery.min.js',//可以是本地也可以是cdn文件
global:'ReactDom',
}
]
})
]
}
```
* `SplitChunksPlugin`
```js
module.exports={
optimization: {
splitChunks: {
chunks: 'async', //三选一:"initial" 初始化,"all"(默认就是all),"async"(动态加载)
minSize: 30000, // 形成一个新代码块最小的体积,只有 >= minSize 的bundle会被拆分出来
maxSize: 0, //拆分之前最大的数值,默认为0,即不做限制
minChunks: 1, //引入次数,如果为2 那么一个资源最少被引用两次才可以被拆分出来
maxAsyncRequests: 5,// 按需加载的最大并行请求数
maxInitialRequests: 3, // 一个入口最大并行请求数
automaticNameDelimiter: '~', // 文件名的连接符
name: true,
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
}
}
```
## 7.5 进阶用法
### 7.5.1 构建配置抽离成npm包
意义:统一构建脚本,开发者无需在关注构建配置。合理的抽离拆分,编辑维护管理。
通过多个配置文件管理不同环境的配置文件。
> `webpack.base.js`:基础包
> `webpack.dev.js`开发环境包
> `webpack.prod.js`生成环境包
> `webpack.ssr.js`ssr环境包
> 合并配置包`webpack-merge`
```js
const merge = require('webpack-merge');
const baseConfig = require('./webpack.base');.
const prodConfig = require('./webpack.prod');
module.exports = merge(baseConfig,prodConfig)
```
### 7.5.2 构建测试
* 冒烟测试
指提交测试之前开发人员进行的预测试。主要排查基本功能是否可用。
> 构建是否成功。
> 构建目录是否有js、css、html等文件输出。
> 单元测试工具:`mocha`+`chai`
1. 安装:`npm i mocha chai -D`
2. 创建test目录,增加test.js测试文件
3. 在package.json中设置
```js
"scripts": {
"test":"node_modules/mocha/bin/_mocha"
}
```
4. 执行测试`npm run test`
* 持续集成
代码在合并主干之前,根据测试用例进行自动化测试,主要有一个测试用例失败,就不能集成。
优点:快速发现错误,防止分支大幅偏离主干。
持续集成工具:`Travis CI`
1. https://travis-ci.org/ 使用github账号登陆
2. 在github创建项目、将项目同步到travis-ci。
3. 项目的根目录下创建.travis.yml,内容如下:
```js
language:node_js // 语音
sudo:false // 管理员权限
cache:
app:true // 是否需要缓存
directories:
-node_modules
node_js:stable // node版本
install:
- npm install -D // 安装构建依赖
- cd /test/template-project
- npm install -D // 安装模板项目依赖
script:
- npm test
```
4. 提交代码,travis-ci会自动根据测试用例进行测试。
### 7.5.3 构建优化
* 构建统计`stats`(颗粒度比较粗,不容易看出问题)
在package.json中使用`stats`
```js
"scripts": {
"build:stats":"webpack--env production --json > stats.json"
}
```
* 构建速度分析`speed-measure-webpack-plugin`
`npm i speed-measure-webpack-plugin`
```js
const speedMeasureWebpackPlugin = require('speed-measure-webpack-plugin');
const smp = new speedMeasureWebpackPlugin();
module.exports= smp.wrap({
entry:{
app:'./src/app.js',
search:'./src/search.js'
},
output:{
filename:'[name][chunkhash:8].js',
path:__dirname+'./dist'
},
})
```
* 构建体积分析`webpack-bundle-analyzer`
`npm i webpack-bundle-analyzer -D`
```js
const {BundleAnalyzerPlugin} = require('webpack-bundle-analyzer');
const smp = new speedMeasureWebpackPlugin();
module.exports={
Plugins:[
new BundleAnalyzerPlugin()
]
}
```
* 速度优化
1. 使用高版本的webpack和node.js
2. 多进程多实例构建`thread-loader`(webpack 4.x内置)/`paraller-webpack`/`HappyPack`(不维护了)
> `thread-loader`:
>
> ```js
> module.exports = {
> module: {
> rules: [
> { test: /\.js$/, use: 'thread-loader' }
> ]
> }
> }
> ```
3. 多进程多实例并行压缩`parallel-uglify-plugin`/`uglifyjs-webpack-plugin`(不支持压缩ES6语法)/`terser-webpack-plugin`(webpack 4.x内置)
> `terser-webpack-plugin`:详见[7.8.4](/frontend/webpack#_7-4-4-js文件压缩terser-webpack-plugin)
4. 分包`html-webpack-externals-plugin`(对业务包不友善)/`DLLPlugin`预编译资源模块(将基础包和业务包打包成一个文件)
> `html-webpack-externals-plugin`:详见[7.4.8](/frontend/webpack#_7-4-8-基础库的分离html-webpack-externals-plugin-splitchunksplugin)
> `DLLPlugin`:
> webpack.config.dll.js文件:
>
> ```js
> const webpack = require('webpack')
> module.exports = {
> entry:{
> library:[
> 'react',
> `react-dom`
> ]
> },
> output:{
> filename:'[name]-[chunkhash].dll.js',
> path:path.join(__dirname,'build/library'),
> library:'[name]'
> }
> plugins:[
> new webpack.DLLPlugin({
> name:'[name]_[hash]',
> path:path.join(__dirname,'build/library/[name].json'),
> })
> ]
> }
> ```
>
> webpack.config.js文件:
>
> ```js
> module.exports = {
> plugins:[
> new webpack.DLLReferencePlugin({
> manifest:require('./build/library/library.json'),
> })
> ]
> }
> ```
5. 缓存:提升二次构建速度`babel-loader`/`terser-webpack-plugin`/`cache-loader`或`hard-source-webpack-plugin`
> `babel-loader`:
>
> ```js
> module.exports = {
> module: {
> rules: [
> { test: /\.js$/, use: 'babel-loader?cacheDirectory=true' }
> ]
> }
> }
> ```
>
> `terser-webpack-plugin`:
>
> ```js
> module.exports = {
> optimization: {
> minimize: true,
> minimizer: [
> new TerserPlugin({
> test: /\.js(\?.*)?$/i, // 通过正则匹配要压缩的文件
> include: /\/includes/, // 指定要压缩的文件
> exclude: /\/excludes/, // 排除文件
> paraller:true, // 开启多进程多实例并行压缩
> cache:true // 开启缓存
> }),
> ],
> },
> };
> ```
>
> 缓存文件存放在`node_modules/.cache`
6. 缩小构建目标
> 利用`alias`优化路径,减少文件查找。
* 体积优化
1. 图片压缩基于node库的`imagemin`或tinypng API
> `imagemin`优点:定制配置丰富,可以引入第三方插件(例如:pngquant),可以处理多种格式。
>
> ```js
> rules: [{
> test: /\.(gif|png|jpe?g|svg)$/i,
> use: [
> 'file-loader',
> {
> loader: 'image-webpack-loader',
> options: {
> mozjpeg: { //jpeg格式
> progressive: true,
> },
> optipng: { //png格式
> enabled: false,
> },
> pngquant: { //png格式
> quality: [0.65, 0.90],
> speed: 4
> },
> gifsicle: { //gif格式
> interlaced: false,
> },
> webp: { //webp格式
> quality: 75
> }
> }
> },
> ],
> }]
> ```
2. 摇树优化tree shaking详见[7.2.9](/frontend/webpack#_7-2-9-摇树优化tree-shaking)
3. 动态Polyfill服务
> polyfill服务原理:识别浏览器User Agent下发不同的polyfill。[polyfill在线识别服务](https://polyfill.io/v3/polyfill.min.js)
4. ScopeHoisting[7.4.10](/frontend/webpack#_7-4-10-scopehoisting)
---
---
url: /other/dataStructure_algorithm.md
---
## 10.1 基础数据结构
### 10.1.1 数组(Array)
数组是可以在内存中连续存储多个元素的结构,在内存中的分配也是连续的

#### 优点:
1. 按照索引查询元素速度快
2. 按照索引遍历数组方便
#### 缺点:
1. 数组的大小固定后就无法扩容了
2. 数组只能存储一种类型的数据
3. 添加,删除的操作慢,因为要移动其他的元素
#### 适用场景:
频繁查询,很少插入或删除的情况,且对存储空间要求不大
### 10.1.2 链表(Linked List)
链表是物理存储单元上非连续的、非顺序的存储结构,数据元素的逻辑顺序是通过链表的指针地址实现,每个元素包含两个结点,一个是存储元素的数据域 (内存空间),另一个是指向下一个结点地址的指针域。根据指针的指向,链表能形成不同的结构,例如单链表,双向链表,循环链表等

#### 优点:
1. 链表是很常用的一种数据结构,不需要初始化容量,可以任意加减元素
2. 添加或者删除元素时只需要改变前后两个元素结点的指针域指向地址即可,所以添加,删除很快
#### 缺点:
1. 因为含有大量的指针域,占用空间较大
2. 查找元素需要遍历链表来查找,非常耗时(使用`跳表`优化查询速度)
#### 适用场景:
频繁插入或删除,很少查询的情况,且对存储空间要求不大
### 10.1.3 栈(Stack)
栈,也叫堆栈,是一种特殊的线性表,仅能在线性表的一端操作,栈顶允许操作,栈底不允许操作。 栈的特点是:先进后出,或者说是后进先出,从栈顶放入元素的操作叫入栈,取出元素叫出栈

#### 优点:
1. 提供后进先出的存取方式
#### 缺点:
1. 存取其他项都很慢
#### 适用场景
递归功能方面的场景,例如斐波那契数列
### 10.1.4 队列(Queue)
队列与栈一样,也是一种线性表,不同的是,队列可以在一端添加元素,在另一端取出元素,也就是:先进先出。从一端放入元素的操作称为入队,取出元素为出队。

#### 优点:
1. 提供先进先出的存取方式
#### 缺点:
1. 存取其他项都很慢
#### 适用场景
多线程阻塞队列管理
### 10.1.5 树(Tree)
树是一种数据结构,它是由n(n>=1)个有限节点组成一个具有层次关系的集合。把它叫做 “树” 是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。
* 它具有以下的特点:
1. 每个节点有零个或多个子节点
2. 没有父节点的节点称为根节点
3. 每一个非根节点有且只有一个父节点
4. 除了根节点外,每个子节点可以分为多个不相交的子树

* 二叉树特点:
1. 每个结点最多有两颗子树,结点的度最大为2
2. 左子树和右子树是有顺序的,次序不能颠倒
3. 即使某结点只有一个子树,也要区分左右子树
* 二叉搜索树特点:
1. 左子树上的所有节点值均小于它的根节点的值
2. 右子树上的所有节点值均大于它的根节点的值
3. 左右子树也分别符合以上两点
#### 优点:
1. 查找,插入,删除都快
2. 既有链表的好处,也有数组的好处
#### 缺点:
#### 适用场景:
二叉树既有链表的好处,也有数组的好处,是两者的优化方案,在处理大批量的动态数据方面非常有用。
### 10.1.6 哈希表(Hash table)
哈希表,也叫散列表,是根据关键码和值 (key和value) 直接进行访问的数据结构,通过key和value来映射到集合中的一个位置,这样就可以很快找到集合中的对应元素(哈希冲突:如果key值重复则通常用链表存储相同key值对应的value)。

#### 优点:
1. 如果关键字已知则存取速度极快
#### 缺点:
1. 删除慢,如果不知道关键则存取很慢,对存储空间使用不充分
#### 适用场景:
Map、Set
### 10.1.7 堆(Heap)
堆是一种比较特殊的数据结构,可以被看做一棵树的数组对象。
* 具有以下的性质:
1. 堆中某个节点的值总是不大于或不小于其父节点的值;
2. 堆总是一棵完全二叉树。
3. 将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。常见的堆有二叉堆、斐波那契堆等。
将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。常见的堆有二叉堆、斐波那契堆等。
堆的定义如下:n个元素的序列{k1,k2,ki,…,kn}当且仅当满足下关系时,称之为堆。
(ki <= k2i,ki <= k2i+1)或者(ki >= k2i,ki >= k2i+1), (i = 1,2,3,4…n/2),满足前者的表达式的成为小顶堆,满足后者表达式的为大顶堆,这两者的结构图可以用完全二叉树排列出来,示例图如下:

#### 优点:
1. 插入,删除块,对最大数据的项存取很快
#### 缺点:
1. 对其他数据项存取很慢
#### 适用场景:
因为堆有序的特点,一般用来做数组中的排序,称为堆排序。
### 10.1.8 图(Graph)
图是由结点的有穷集合V和边的集合E组成。其中,为了与树形结构加以区别,在图结构中常常将结点称为顶点,边是顶点的有序偶对,若两个顶点之间存在一条边,就表示这两个顶点具有相邻关系。

图是一种比较复杂的数据结构,在存储数据上有着比较复杂和高效的算法,分别有邻接矩阵 、邻接表、十字链表、邻接多重表、边集数组等存储结构,这里不做展开,读者有兴趣可以自己学习深入。
#### 优点:
#### 缺点:
#### 适用场景:
## 10.2 时间复杂度与空间复杂度
时间:是指执行当前算法所消耗的时间
空间:是指执行当前算法需要占用多少内存空间
负责度:是指算法随着执行次数的增长,所花费的时间、空间的增长趋势
### 10.2.1 时间复杂度
#### 公式:`T(n) = O(f(n))`
> `f(n)`:表示每行代码执行次数之和
> 去掉常数项和系数、只保留复杂度最大项
#### 常见的时间复杂度量级有:
* 常数阶`O(1)`
无论代码执行了多少行,只要是没有循环等复杂结构,那这个代码的时间复杂度就都是O(1)
```js
int i = 1;
int j = 2;
++i;
j++;
int m = i + j;
```
* 对数阶`O(logN)`
```js
int i = 1;
while(i `f(n)`:表示每行代码占用的空间大小之和
> 去掉常数项和系数、只保留复杂度最大项
#### 常见的时间复杂度量级有:
* 常数阶`O(1)`
只要不会因为算法里的执行,导致额外的空间增长,就算是一万行,空间复杂度也是`O(1)`
```js
function foo(){
let n = 1
let b = n * 100
if(b === 100){
console.log("开始吃糖")
}
console.log("我吃了1颗糖")
console.log("我吃了2颗糖")
......
console.log("我吃了10000颗糖")
}
```
* 线性阶`O(n)`
```js
function foo(n){
let arr = []
for( let i = 1; i < n; i++ ) {
arr[i] = i
}
}
```
* 平方阶`O(n²)`
O(n²) 这种空间复杂度一般出现在比如二维数组,或是矩阵的情况
```js
let arr = [
[1,2,3,4,5],
[1,2,3,4,5],
[1,2,3,4,5]
]
```
* 阶乘`O(n!)`
## 10.3 算法
### 10.3.1 排序
### 10.3.2 暴力枚举
### 10.3.3 图、树、线性表
### 10.3.4 分治法
### 10.3.5 递归
### 10.3.6 动态规划
### 10.3.7 贪心
---
---
url: /other/designPattern.md
---
设计模式:在面向对象软件设计过程中针对特定问题的简洁而优雅的解决方案
## 11.1 单列模式
单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点
```js
var namespace1 = {
a: function(){
alert (1);
},
b: function(){
alert (2);
}
};
```
## 11.2 策略模式
策略模式:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换
```js
var strategies = {
"S": function( salary ){
return salary * 4;
},
"A": function( salary ){
return salary * 3;
},
"B": function( salary ){
r eturn salary * 2;
}
};
var calculateBonus = function( level, salary ){
return strategies[ level ]( salary );
};
console.log( calculateBonus( 'S', 20000 ) ); // 输出:80000
console.log( calculateBonus( 'A', 10000 ) ); // 输出:30000
```
* 通过高阶函数实现:
```js
var S = function( salary ){
return salary * 4;
};
var A = function( salary ){
return salary * 3;
};
var B = function( salary ){
r eturn salary * 2;
};
var calculateBonus = function( func, salary ){
return func( salary );
};
console.log(calculateBonus( S, 20000 ) ); // 输出:80000
console.log(calculateBonus( A, 10000 ) ); // 输出:30000
```
## 11.3 代理模式
代理模式:为一个对象提供一个代用品或占位符,以便控制对它的访问
1. 虚拟代理:通过虚拟代理实现图片预加载
```js
// 虚拟代理实现图片预加载 图片没加载之前通过本地的图片站位
var myImage = (function(){
// 1. myImage函数自执行:创建dom元素
var imgNode = document.createElement( 'img' );
document.body.appendChild( imgNode );
return {
setSrc: function( src ){
// 5./7.将传来的src放入dom
imgNode.src = src;
}
}
})();
var proxyImage = (function(){
// 2.proxyImage函数自执行:创建图片对象,绑定方法
var img = new Image;
img.onload = function(){
// 6.监听到图片加载完成,调用setSrc方法,将设置的图片传给myImage方法
myImage.setSrc( this.src );
}
return {
setSrc: function( src ){
// 3.用户调用setSrc方法,将默认图片传给myImage方法
myImage.setSrc( 'file://Users/svenzeng/Desktop/loading.gif' );
// 4. img形成闭包,给img设置src
img.src = src;
}
}
})();
proxyImage.setSrc( 'http://imgcache.qq.com/music/photo/k/000GGDys0yA0Nk.jpg' );
```
2. 缓存代理:通过缓存代理合并http请求,将一段时间内的连续请求合并
```js
var synchronousFile = function( id ){
console.log( '开始上传文件,id 为: ' + id );
};
var proxySynchronousFile = (function(){
// 1.初始化
var cache = [], // 保存一段时间内需要同步的 ID
timer; // 定时器
return function( id ){
// 3. 调用proxySynchronousFile的方法 将id插入待处理的集合
cache.push( id );
if ( timer ){ // 保证不会覆盖已经启动的定时器
return;
}
timer = setTimeout(function(){
synchronousFile( cache.join( ',' ) ); // 2 秒后向上传方法提交ID的合集
clearTimeout( timer ); // 清空定时器
timer = null;
cache.length = 0; // 清空 ID 集合
}, 2000 );
}
})();
// 2.连续快速调用proxySynchronousFile方法
var checkbox = document.getElementsByTagName( 'input' );
for ( var i = 0, c; c = checkbox[ i++ ]; ){
c.onclick = function(){
if ( this.checked === true ){
proxySynchronousFile( this.id );
}
}
};
```
3. 保护代理:用于对象应该有不同访问权限的情况
4. 防火墙代理理:控制网络资源的访问
5. 远程代理
6. 智能引用代理
7. 写时复制代理
## 11.4 迭代器模式
迭代器模式:是指定义一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示
(迭代器模式无非就是循环访问聚合对象中的各个元素)
1. 内部迭代器
```js
var each = function( ary, callback ){
for ( var i = 0, l = ary.length; i < l; i++ ){
callback.call( ary[i], i, ary[ i ] ); // 把下标和元素当作参数传给 callback 函数
}
};
each( [ 1, 2, 3 ], function( i, n ){
alert ( [ i, n ] );
});
```
2. 外部迭代器
```js
var Iterator = function( obj ){
var current = 0;
var next = function(){
current += 1;
};
var isDone = function(){
return current >= obj.length;
};
var getCurrItem = function(){
return obj[ current ];
};
return {
next: next,
isDone: isDone,
getCurrItem: getCurrItem
}
};
var iterator1 = Iterator( [ 1, 2, 3 ] );
iterator1.next() // 迭代下一个元素
iterator1.getCurrItem() // 当前元素
iterator1.isDone() // 迭代是否结束
```
## 11.5 发布-订阅模式(观察者模式)
发布-订阅模式:定义一个对象,当这个对象的状态发生改变时,所有依赖于它的对象都将得到通知
发布-订阅模式是最常用的模式之一,无论是`MVC`还是`MVVM`,都少不了发布—订阅模式的参与,而且`JavaScript`本身也是一门基于事件驱动的语言
* 定义一个发布-订阅模式模型
```js
var event = {
clientList: [], // 所有订阅的客户
// 订阅
listen: function( name,key, fn ){ // 登记订阅的客户姓名、通讯方式fn(回调函数)和所订阅信息key
if ( !this.clientList[ key ] ){
this.clientList[ key ] = [];
}
this.clientList[ key ].push( {name,fn} ); // 根据订阅的消息将客户分类
},
// 取消订阅
remove: function( name,key, fn ){ // 取消用户订阅
var list = this.clientList[ key ];
if ( !fns ){ // 如果消息key没有被人订阅过 直接跳过
return false;
}
if ( !name ){ // 如果没有告诉具体的人,则删除消息key下的所有订阅人
list && ( list.length = 0 );
}else{
for ( var l = list.length - 1; l >=0; l-- ){ // 反向遍历订阅的回调函数列表
if ( list[ l ].name === name ){
list.splice( l, 1 ); // 删除订阅记录
fn.apply( this, ['取消成功'] ); // 通过fn给客户发送取消订阅成功的消息
}
}
}
},
// 发布消息
trigger: function(){ // 为订阅的客户发送消息
var [key,msg] = arguments
list = this.clientList[ key ]; // 取出不同消息的订阅客户
if ( !list || list.length === 0 ){ // 如果该消息没有客户订阅 则不发布
return false;
}
for( var i = 0, item; item = list[ i++ ]; ){
item.fn.apply( this, arguments ); // 通过fn给客户发送消息
}
}
};
// 为发布-订阅模式模型的使用者 安装注册相关方法
var installEvent = function( obj ){
for ( var i in event ){
obj[ i ] = event[ i ];
}
};
```
* 使用发布-订阅模式模型 订阅、取消订阅和发送消息
```js
// 定义一个使用者 新闻中心
var NewsCenter = {};
// 安装发布订阅模型
installEvent( NewsCenter );
// 客户在新闻中心订阅消息
NewsCenter.listen( '小明','科技新闻', function( msg ){ // 小明
console.log( msg[0] + ':' + msg[1] ); // 科技新闻=十条
});
NewsCenter.listen('小王', '娱乐新闻', function( msg ){ // 小王
console.log( msg[0] + ':' + msg[1] ); // 科技新闻=二十条
});
NewsCenter.listen('小张', '体育新闻', function( msg ){ // 小张
console.log( msg[0] + ':' + msg[1] );// 没有该消息 不会发布
});
// 取消订阅
NewsCenter.remove('小王', '娱乐新闻', function( msg ){ // 小王取消订阅
console.log(msg); // 科技新闻=十条
});
// 新闻中心发布消息
NewsCenter.trigger( '科技新闻', '十条' );
NewsCenter.trigger( '娱乐新闻', '二十条' );
```
::: tip
* 推模型与拉模型:\
推模型:发布的状态和发布的内容同时告诉订阅者\
拉模型:只将发布的状态通知给订阅者。发布的内容需要订阅者使用其他方法再获取\
:::
## 11.6 命令模式
命令模式:命令是对命令的封装,每一个命令都是一个操作,请求方发出请求,接收方接收请求,并执行操作
```html
输入小球移动后的位置:
```
```js
// 操作小球往复运动
var ball = document.getElementById( 'ball' );// 要运动的小球元素
var pos = document.getElementById( 'pos' ); // 滚动的距离
var moveBtn = document.getElementById( 'moveBtn' ); // 开始运动
var cancelBtn = document.getElementById( 'cancelBtn' ); // 回到原点
var MoveCommand = function( receiver, pos ){
this.receiver = receiver;
this.pos = pos;
this.oldPos = null;
};
MoveCommand.prototype.execute = function(){
// 向左运动pos的长度
this.receiver.start( 'left', this.pos, 1000, 'strongEaseOut' );
// 记录小球开始移动前的位置
this.oldPos = this.receiver.dom.getBoundingClientRect()[ this.receiver.propertyName ];
};
MoveCommand.prototype.undo = function(){
// 回到小球移动前记录的位置
this.receiver.start( 'left', this.oldPos, 1000, 'strongEaseOut' );
};
var moveCommand;
moveBtn.onclick = function(){
var animate = new Animate( ball );
moveCommand = new MoveCommand( animate, pos.value );
moveCommand.execute();
};
cancelBtn.onclick = function(){
moveCommand.undo(); // 撤销命令
};
```
## 11.7 组合模式
组合模式:将对象组合成树形结构以表示“部分-整体”的层次结构,组合模式使得用户对单个对象和组合对象的使用具有一致性
```js
// 扫描文件夹
/******************************* Folder ******************************/
var Folder = function( name ){
this.name = name;
this.files = [];
};
Folder.prototype.add = function( file ){
this.files.push( file );
};
Folder.prototype.scan = function(){
console.log( '开始扫描文件夹: ' + this.name );
for ( var i = 0, file, files = this.files; file = files[ i++ ]; ){
file.scan();
}
};
/******************************* File ******************************/
var File = function( name ){
this.name = name;
};
File.prototype.add = function(){
throw new Error( '文件下面不能再添加文件' );
};
File.prototype.scan = function(){
console.log( '开始扫描文件: ' + this.name );
};
var folder = new Folder( '学习资料' );
var folder1 = new Folder( 'JavaScript' );
var folder2 = new Folder ( 'jQuery' );
var file1 = new File( 'JavaScript 设计模式与开发实践' );
var file2 = new File( '精通 jQuery' );
var file3 = new File( '重构与模式' )
folder1.add( file1 );
folder2.add( file2 );
folder.add( folder1 );
folder.add( folder2 );
folder.add( file3 );
```
## 11.8 模板方法模式
模板方法模式:模板方法模式是一种只需使用继承就可以实现的非常简单的模式
```js
var Coffee = function(){};
Coffee.prototype.boilWater = function(){
console.log( '把水煮沸' );
};
Coffee.prototype.brewCoffeeGriends = function(){
console.log( '用沸水冲泡咖啡' );
};
Coffee.prototype.pourInCup = function(){
console.log( '把咖啡倒进杯子' );
};
Coffee.prototype.addSugarAndMilk = function(){
console.log( '加糖和牛奶' );
};
Coffee.prototype.init = function(){
this.boilWater();
this.brewCoffeeGriends();
this.pourInCup();
this.addSugarAndMilk();
};
var coffee = new Coffee();
coffee.init();
```
## 11.9 享元模式
享元模式:用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式
* 优点:大大减少对象的创建,降低系统的内存,使效率提高
* 缺点:提高了系统的复杂度,需要分离出外部状态和内部状态,而且外部状态具有固有化的性质,不应该随着内部状态的变化而变化,否则会造成系统的混乱
* 使用场景:
> 1. 内部状态存储于对象内部
> 2. 内部状态可以被一些对象共享
> 3. 内部状态独立于具体的场景,通常不会改变
> 4. 外部状态取决于具体的场景,并根据场景而变化,外部状态不能被共享
```js
// 有50种男式内衣和50种女士内衣,需要50个男模特50个女模特分别传上衣服牌子
var Model = function( sex, underwear){
this.sex = sex;
this.underwear= underwear;
};
Model.prototype.takePhoto = function(){
console.log( 'sex= ' + this.sex + ' underwear=' + this.underwear);
};
for ( var i = 1; i <= 50; i++ ){
var maleModel = new Model( 'male', 'underwear' + i );
maleModel.takePhoto();
};
for ( var j = 1; j <= 50; j++ ){
var femaleModel= new Model( 'female', 'underwear' + j );
femaleModel.takePhoto();
};
/* 要得到一张照片,每次都需要传入sex和underwear参数,
如上所述,现在一共有50种男内衣和50种女内衣,所以一共会产生100个对象*/
```
* 通过享元模式简化
```js
var Model = function( sex ){
this.sex = sex;
};
Model.prototype.takePhoto = function(){
console.log( 'sex= ' + this.sex + ' underwear=' + this.underwear);
};
// 分别创建一个男模特对象和一个女模特对象:
var maleModel = new Model( 'male' ),
femaleModel = new Model( 'female' );
// 给男模特依次穿上所有的男装,并进行拍照:
for ( var i = 1; i <= 50; i++ ){
maleModel.underwear = 'underwear' + i;
maleModel.takePhoto();
};
// 同样,给女模特依次穿上所有的女装,并进行拍照:
for ( var j = 1; j <= 50; j++ ){
femaleModel.underwear = 'underwear' + j;
femaleModel.takePhoto();
};
/* 性别是内部状态,内衣是外部状态,通过区分这两种状态,大大减少了系统中的对象数量
可以看到,改进之后的代码,只需要两个对象便完成了同样的功能 */
```
## 11.10 职责链模式
职责链模式:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止,请求发送者只需要知道链中的第一个节点,从而弱化了发送者和一组接收者之间的强联系。
```js
/*
orderType:订单类型,1的时候是500元定金用户,2是200元定金用户,3是普通购买用户
pay:用户是否已经支付定金,定金用户如果一直没有支付定金,只能降级进入普通购买模式
stock:表示普通用户可购买的手机库存,定金的用户不受此限制
*/
var order = function( orderType, pay, stock ){
if ( orderType === 1 ){ // 500 元定金购买模式
if ( pay === true ){ // 已支付定金
console.log( '500 元定金预购, 得到 100 优惠券' );
}else{ // 未支付定金,降级到普通购买模式
if ( stock > 0 ){ // 用于普通购买的手机还有库存
console.log( '普通购买, 无优惠券' );
}else{
console.log( '手机库存不足' );
}
}
}else if ( orderType === 2 ){ // 200 元定金购买模式
if ( pay === true ){
console.log( '200 元定金预购, 得到 50 优惠券' );
}else{
if ( stock > 0 ){
console.log( '普通购买, 无优惠券' );
}else{
console.log( '手机库存不足' );
}
}
}else if ( orderType === 3 ){
if ( stock > 0 ){
console.log( '普通购买, 无优惠券' );
}else{
console.log( '手机库存不足' );
}
}
};
order( 1 , true, 500); // 输出: 500 元定金预购, 得到 100 优惠券
```
* 通过职责链模式重构
```js
var order500 = function( orderType, pay, stock ){
if ( orderType === 1 && pay === true ){
console.log( '500 元定金预购,得到 100 优惠券' );
}else{
return 'nextSuccessor'; // 我不知道下一个节点是谁,反正把请求往后面传递
}
};
var order200 = function( orderType, pay, stock ){
if ( orderType === 2 && pay === true ){
console.log( '200 元定金预购,得到 50 优惠券' );
}else{
return 'nextSuccessor'; // 我不知道下一个节点是谁,反正把请求往后面传递
}
};
var orderNormal = function( orderType, pay, stock ){
if ( stock > 0 ){
console.log( '普通购买,无优惠券' );
}else{
console.log( '手机库存不足' );
}
};
// 把函数包装进职责链节点
var Chain = function( fn ){
this.fn = fn;
this.successor = null;
};
// Chain.prototype.setNextSuccessor 指定在链中的下一个节点
Chain.prototype.setNextSuccessor = function( successor ){
return this.successor = successor;
};
// Chain.prototype.passRequest 传递请求给某个节点
Chain.prototype.passRequest = function(){
var ret = this.fn.apply( this, arguments );
if ( ret === 'nextSuccessor' ){
return this.successor && this.successor.passRequest.apply( this.successor, arguments );
}
return ret;
};
// 把3个订单函数分别包装成职责链的节点
var chainOrder500 = new Chain( order500 );
var chainOrder200 = new Chain( order200 );
var chainOrderNormal = new Chain( orderNormal );
// 指定节点在职责链中的顺序
chainOrder500.setNextSuccessor( chainOrder200 );
chainOrder200.setNextSuccessor( chainOrderNormal );
// 把请求传递给第一个节点
chainOrder500.passRequest( 1, true, 500 ); // 输出:500 元定金预购,得到 100 优惠券
chainOrder500.passRequest( 2, true, 500 ); // 输出:200 元定金预购,得到 50 优惠券
chainOrder500.passRequest( 3, true, 500 ); // 输出:普通购买,无优惠券
chainOrder500.passRequest( 1, false, 0 ); // 输出:手机库存不足
```
## 11.11 中介者模式
中介者模式:就是解除对象与对象之间的紧耦合关系。增加一个中介者对象后,所有的相关对象都通过中介者对象来通信,而不是互相引用,所以当一个对象发生改变时,只需要通知中介者对象即可。中介者使各对象之间耦合松散,而且可以独立地改变它们之间的交互。中介者模式使网状的多对多关系变成了相对简单的一对多关系(迪米特法则)

缺点:因为对象之间交互的复杂性,转移成了中介者对象的复杂性,使得中介者对象经常是巨大的。中介者对象自身往往就是一个难以维护的对象
```js
/*
模拟两个队伍的游戏对战,一方角色全部死亡则另一方胜利
*/
// 玩家的构造函数
function Player( name, teamColor ){
this.name = name; // 角色名字
this.teamColor = teamColor; // 队伍颜色
this.state = 'alive'; // 玩家生存状态
};
// 队伍胜利方法
Player.prototype.win = function(){
console.log( this.name + ' won ' );
};
// 队伍失败方法
Player.prototype.lose = function(){
console.log( this.name +' lost' );
};
// 玩家死亡
Player.prototype.die = function(){
this.state = 'dead';
playerDirector.reciveMessage( 'playerDead', this ); // 给中介者发送消息,玩家死亡
};
// 玩家离开
Player.prototype.remove = function(){
playerDirector.reciveMessage( 'removePlayer', this ); // 给中介者发送消息,移除一个玩家
};
// 玩家切换队伍
Player.prototype.changeTeam = function( color ){
playerDirector.reciveMessage( 'changeTeam', this, color ); // 给中介者发送消息,玩家换队
};
// 创建玩家对象
var playerFactory = function( name, teamColor ){
var newPlayer = new Player( name, teamColor ); // 创造一个新的玩家对象
playerDirector.reciveMessage( 'addPlayer', newPlayer ); // 给中介者发送消息,新增玩家
return newPlayer;
};
/* 中介对象
在playerDirector中开放一些接收消息的接口,各player可以直接调用该接口来给playerDirector发送消息,
player只需传递一个参数给playerDirector,这个参数的目的是使playerDirector可以识别发送者。
同样,playerDirector接收到消息之后会将处理结果反馈给其他player
*/
var playerDirector= ( function(){
var players = {}, // 保存所有玩家
operations = {}; // 中介者可以执行的操作
// 新增一个玩家
operations.addPlayer = function( player ){
var teamColor = player.teamColor; // 玩家的队伍颜色
players[ teamColor ] = players[ teamColor ] || []; // 如果该颜色的玩家还没有成立队伍,则新成立一个队伍
players[ teamColor ].push( player ); // 添加玩家进队伍
};
// 移除一个玩家
operations.removePlayer = function( player ){
var teamColor = player.teamColor, // 玩家的队伍颜色
teamPlayers = players[ teamColor ] || []; // 该队伍所有成员
for ( var i = teamPlayers.length - 1; i >= 0; i-- ){ // 遍历删除
if ( teamPlayers[ i ] === player ){
teamPlayers.splice( i, 1 );
}
}
// 移除的玩家相当于死亡
operations.playerDead(player)
};
// 玩家换队
operations.changeTeam = function( player, newTeamColor ){ // 玩家换队
operations.removePlayer( player ); // 从原队伍中删除
player.teamColor = newTeamColor; // 改变队伍颜色
operations.addPlayer( player ); // 增加到新队伍中
};
// 玩家死亡
operations.playerDead = function( player ){
var teamColor = player.teamColor,
teamPlayers = players[ teamColor ]; // 玩家所在队伍
var all_dead = true;
for ( var i = 0, player; player = teamPlayers[ i++ ]; ){
if ( player.state !== 'dead' ){
all_dead = false;
break;
}
}
if ( all_dead === true ){ // 全部死亡
for ( var i = 0, player; player = teamPlayers[ i++ ]; ){
player.lose(); // 本队所有玩家 lose
}
for ( var color in players ){
if ( color !== teamColor ){
var teamPlayers = players[ color ]; // 其他队伍的玩家
for ( var i = 0, player; player = teamPlayers[ i++ ]; ){
player.win(); // 其他队伍所有玩家 win
}
}
}
}
};
var reciveMessage = function(){
var message = Array.prototype.shift.call( arguments ); // arguments 的第一个参数为消息名称
operations[ message ].apply( this, arguments );
};
return {
reciveMessage: reciveMessage
}
})();
// 测试结果
// 红队:
var player1 = playerFactory( '皮蛋', 'red' ),
player2 = playerFactory( '小乖', 'red' ),
player3 = playerFactory( '宝宝', 'red' ),
player4 = playerFactory( '小强', 'red' );
// 蓝队:
var player5 = playerFactory( '黑妞', 'blue' ),
player6 = playerFactory( '葱头', 'blue' ),
player7 = playerFactory( '胖墩', 'blue' ),
player8 = playerFactory( '海盗', 'blue' );
player1.die();
player2.die();
player3.die();
player4.changeTeam();
```
```js
/*
根据手机的颜色、内存和用户的购买数量判断是否可以购买
*/
var goods = { // 手机库存
"red|32G": 3,
"red|16G": 0,
"blue|32G": 1,
"blue|16G": 6
};
var mediator = (function(){
var colorSelect = document.getElementById( 'colorSelect' ),
memorySelect = document.getElementById( 'memorySelect' ),
numberInput = document.getElementById( 'numberInput' ),
colorInfo = document.getElementById( 'colorInfo' ),
memoryInfo = document.getElementById( 'memoryInfo' ),
numberInfo = document.getElementById( 'numberInfo' ),
nextBtn = document.getElementById( 'nextBtn' );
return {
changed: function( obj ){
var color = colorSelect.value, // 颜色
memory = memorySelect.value,// 内存
number = numberInput.value, // 数量
stock = goods[ color + '|' + memory ]; // 颜色和内存对应的手机库存数量
if ( obj === colorSelect ){ // 如果改变的是选择颜色下拉框
colorInfo.innerHTML = color;
}else if ( obj === memorySelect ){
memoryInfo.innerHTML = memory;
}else if ( obj === numberInput ){
numberInfo.innerHTML = number;
}
if ( !color ){
nextBtn.disabled = true;
nextBtn.innerHTML = '请选择手机颜色';
return;
}
if ( !memory ){
nextBtn.disabled = true;
nextBtn.innerHTML = '请选择内存大小';
return;
}
if ( ( ( number - 0 ) | 0 ) !== number - 0 ){ // 输入购买数量是否为正整数
nextBtn.disabled = true;
nextBtn.innerHTML = '请输入正确的购买数量';
return;
}
nextBtn.disabled = false;
nextBtn.innerHTML = '放入购物车';
}
}
})();
// 修改颜色
colorSelect.onchange = function(){
mediator.changed( this );
};
// 修改内存
memorySelect.onchange = function(){
mediator.changed( this );
};
// 购买数量
numberInput.oninput = function(){
mediator.changed( this );
};
```
## 11.12 修饰者模式
装饰者模式:装饰者模式可以动态地给某个对象添加一些额外的职责,而不会影响从这个类中派生的其他对象(相当于式将一个对象嵌入另一个对象之中)
优点:使用继承的方式一方面会导致超类和子类之间存在强耦合性,其次在完成一些功能复用时,有可能创建出大量的子类,使子类的数量呈爆炸性增。跟继承相比,装饰者是一种更轻便灵活的做法,这是一种“即用即付”的方式
```js
// 通过保存原引用的方式改写某个函数
var a = function(){
alert (1);
}
var _a = a;
a = function(){
_a(); //
alert (2);
}
a();
```
* AOP装饰函数:基于原型(prototype)
```js
// 新增函数在原函数 前执行
Function.prototype.before = function (beforefn) {
var __self = this; // 保存原函数的引用
return function () { // 返回包含了原函数和新函数的"代理"函数
beforefn.apply(this, arguments); // 执行新函数,且保证 this 不被劫持,新函数接受的参数
// 也会被原封不动地传入原函数,新函数在原函数之前执行
return __self.apply(this, arguments); // 执行原函数并返回原函数的执行结果
// 并且保证 this 不被劫持
}
}
// 新增函数在原函数 后执行
Function.prototype.after = function (afterfn) {
var __self = this; // 保存原函数的引用
return function () { // 返回包含了原函数和新函数的"代理"函数
var ret = __self.apply(this, arguments); // 执行原函数并记录原函数的执行结果
afterfn.apply(this, arguments); // 执行新函数,且保证 this 不被劫持,新函数接受的参数
return ret; // 返回原函数的执行结果
}
};
document.getElementById = document.getElementById.before(function () {
console.log('before');
});
document.getElementById = document.getElementById.after(function () {
console.log('after');
});
var button = document.getElementById('button');
console.log(button);
```
* AOP装饰函数:基于回调函数(callback)
```js
var before = function (fn, beforefn) {
return function () {
beforefn.apply(this, arguments);
return fn.apply(this, arguments);
}
}
var after = function (fn, afterfn) {
return function () {
var ret = fn.apply(this, arguments);
afterfn.apply(this, arguments);
return ret
}
}
// 原函数
function a() {
console.log('a')
}
a = before(a, function () { console.log('before') });
a = after(a, function () { console.log('after') });
a(); /// before a after
```
::: tip AOP模式
AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,是OOP(面向对象编程)的补充和完善。
简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
:::
::: tip 代理模式和装饰者模式的区别
代理模式是为其他对象提供一种代理以控制对这个对象的访问。装饰者模式是动态地给一个对象添加一些额外的职责。代理模式是行为的扩展,装饰者模式是对象的扩展。
:::
## 11.13 状态模式
状态模式:当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类
使用场景:
> 1. 一个对象的行为取决于它的状态,并且它必须在运行时根据状态来改变他的行为。
> 2. 代码中包含了大量与对象状态有关的条件语句时。
常规方法—文件上传
```js
window.external.upload = function (state) {
console.log(state); // 可能为 sign、uploading、done、error
};
var plugin = (function () {
var plugin = document.createElement('embed');
plugin.style.display = 'none';
plugin.type = 'application/txftn-webkit';
plugin.sign = function () {
console.log('开始文件扫描');
}
plugin.pause = function () {
console.log('暂停文件上传');
};
plugin.uploading = function () {
console.log('开始文件上传');
};
plugin.del = function () {
console.log('删除文件上传');
}
plugin.done = function () {
console.log('文件上传完成');
}
document.body.appendChild(plugin);
return plugin;
})();
var Upload = function (fileName) {
this.plugin = plugin;
this.fileName = fileName;
this.button1 = null;
this.button2 = null;
this.state = 'sign'; // 设置初始状态为 waiting
};
Upload.prototype.init = function () {
var that = this;
this.dom = document.createElement('div');
this.dom.innerHTML =
'文件名称:' + this.fileName + '\
\
';
document.body.appendChild(this.dom);
this.button1 = this.dom.querySelector('[data-action="button1"]'); // 第一个按钮
this.button2 = this.dom.querySelector('[data-action="button2"]'); // 第二个按钮
this.bindEvent();
};
Upload.prototype.bindEvent = function () {
var self = this;
this.button1.onclick = function () {
if (self.state === 'sign') { // 扫描状态下,任何操作无效
console.log('扫描中,点击无效...');
} else if (self.state === 'uploading') { // 上传中,点击切换到暂停
self.changeState('pause');
} else if (self.state === 'pause') { // 暂停中,点击切换到上传中
self.changeState('uploading');
} else if (self.state === 'done') {
console.log('文件已完成上传, 点击无效');
} else if (self.state === 'error') {
console.log('文件上传失败, 点击无效');
}
};
this.button2.onclick = function () {
if (self.state === 'done' || self.state === 'error'
|| self.state === 'pause') {
// 上传完成、上传失败和暂停状态下可以删除
self.changeState('del');
} else if (self.state === 'sign') {
console.log('文件正在扫描中,不能删除');
} else if (self.state === 'uploading') {
console.log('文件正在上传中,不能删除');
}
};
};
Upload.prototype.changeState = function (state) {
switch (state) {
case 'sign':
this.plugin.sign();
this.button1.innerHTML = '扫描中,任何操作无效';
break;
case 'uploading':
this.plugin.uploading();
this.button1.innerHTML = '正在上传,点击暂停';
break;
case 'pause':
this.plugin.pause();
this.button1.innerHTML = '已暂停,点击继续上传';
break;
case 'done':
this.plugin.done();
this.button1.innerHTML = '上传完成';
break;
case 'error':
this.button1.innerHTML = '上传失败';
break;
case 'del':
this.plugin.del();
this.dom.parentNode.removeChild(this.dom);
console.log('删除完成');
break;
}
this.state = state;
};
var uploadObj = new Upload('JavaScript 设计模式与开发实践');
uploadObj.init();
window.external.upload = function (state) { // 插件调用 JavaScript 的方法
uploadObj.changeState(state);
};
window.external.upload('sign'); // 文件开始扫描
setTimeout(function () {
window.external.upload('uploading'); // 1 秒后开始上传
}, 1000);
setTimeout(function () {
window.external.upload('done'); // 5 秒后上传完成
}, 5000);
```
状态模式重构文件上传程序
```js
window.external.upload = function( state ){
console.log( state ); // 可能为 sign、uploading、done、error
};
var plugin = (function(){
var plugin = document.createElement( 'embed' );
plugin.style.display = 'none';
plugin.type = 'application/txftn-webkit';
plugin.sign = function(){
console.log( '开始文件扫描' );
}
plugin.pause = function(){
console.log( '暂停文件上传' );
};
plugin.uploading = function(){
console.log( '开始文件上传' );
};
plugin.del = function(){
console.log( '删除文件上传' );
}
plugin.done = function(){
console.log( '文件上传完成' );
}
document.body.appendChild( plugin );
return plugin;
})();
```
## 11.14 适配器模式
适配器模式:就是使两个不兼容的接口通过适配器进行兼容。
```js
// 当我们向 googleMap 和 baiduMap 都发出“显示”请求时两者提供的方法不一致
var googleMap = {
show: function(){
console.log( '开始渲染谷歌地图' );
}
};
var baiduMap = {
display: function(){
console.log( '开始渲染百度地图' );
}
};
// 百度需要调用display方法,因此为百度增加一个适配器
var baiduMapAdapter = {
show: function(){
return baiduMap.display();
}
};
// 统一使用show方法使 googleMap 和 baiduMap兼容
var renderMap = function( map ){
if ( map.show instanceof Function ){
map.show();
}
};
```
---
---
url: /other/freeMarker.md
---
FreeMarker是一款模板语言:即一种基于模板和要改变的数据,并用来生成输出文本的通用工具。
## 6.1 `assign`定义变量
```ftl
<#assign userName="jaqi">
```
## 6.2 `${...}`输出变量
```html
${userName}
${user!}
${user!"jaqi"}
```
## 6.3 控制语句
#### `if`
```ftl
<#if userName="jaqi">
<#elseif userName="lijiaqi">
<#else>
#if>
```
#### `switch`
```ftl
<#switch value>
<#case refValue1>
...
<#break>
<#case refValue2>
...
<#break>
...
<#case refValueN>
...
<#break>
<#default>
...
#switch>
```
## 6.4 `list`遍历
```ftl
<#list goodsList as item>
${item_index+1} 商品名称: ${item.name} 价格:${item.price}
<#sep>
<#sep>
#list>
```
::: tip
1. `循环体+_index`返回索引
2. `<#sep>`不在第一项之前或最后一项之后显示的内容。
:::
* 根据name排序
```ftl
<#assign userNameArr=[{"name":1},{"name":6},{"name":4}]>
<#list userNameArr?sort_by(["name"])?reverse as user>
${user.name} // 6 4 1
#list>
```
## 6.5 `include`引用模板
```ftl
<#include "head.html">
```
## 6.6 常用属性
* `size`返回数组长度
```ftl
${(userName?size)}
```
* `length`返回字符串长度
```ftl
${(userName?length)}
```
* `??`判断变量是否存在,不判断变量的值。
```ftl
${userName??}
```
* `!!`忽略list中的空值
```ftl
<#list lists!! as list>
#list>
```
* `has_content`判断变量是否为空
```ftl
${userName?has_content}
```
* `?c`Number类型转字符串
```ftl
${Num?c}
```
* 比较运算符
大于:`gt`\小于:`lt`\大于等于:`gte`小于等于:`lte`
---
---
url: /other/git.md
---
## 初始化
* `init` 初始化仓库
```zsh
git init # 在当前目录新建一个Git代码库
git init [projectName] # 新建一个目录,将其初始化为Git代码库
```
* `config` 仓库配置
```zsh
git config --list # 显示当前仓库的Git配置
git config -e [--global] # 编辑全局的Git配置
git config [--global] user.name "[name]" # 设置用户名
git config [--global] user.email "[email address]" # 设置邮箱7
```
* `SSH Key`
```zsh
ssh-keygen -t rsa -C "[email address]" # 创建 SSH Key
vi ~/.ssh/id_rsa.pub # 查看 SSH Key
```
## 多密钥情况
* .ssh/config文件中配置多个密钥
```
# 个人账户
Host jaqi
HostName gitee.com
IdentityFile ~/.ssh/id_rsa_jaqi
User git
# 工作账户
Host work
HostName codeup.com
Port 22
IdentityFile ~/.ssh/id_rsa_work
User git
```
* 使用
```zsh
git@jaqi:xxx.git
git@work:xxx.git
```
## `clone` 克隆代码
从当前地址的`master`分支克隆的到本地`ProjectName`文件夹
```zsh
git clone https://gitHub.com/ ProjectName -b master
```
## `remote` 设置远程仓库地址
```zsh
git remote -v # 查看全部远程仓库
git remote add origin https://gitHub.com/ master # 增加远程仓库origin并设置其远程地址
git remote set-url origin https://gitHub.com/ # 修改远程仓库origin的地址
git remote rm origin # 删除origin仓库
```
## `status` 查看工作区、暂存区、本地仓库的状态
```zsh
git status
```
## `add` 添加的暂存区
```zsh
git add . # 添加所有modified(修改)、new(新增)的文件,不包括delete(删除)的文件
git add -u # 添加所有modified(修改)、delete(删除)的文件,不包括new(新增)的文件
git add -A # 添加当前文件夹下的所有modified(修改)、new(新增),delete(删除)的文件
git add -f dist # 添加dist文件到暂存区
git add -p # 添加所有文件并根据变化分次提交
```
## `rm` 将文件从暂存区和工作区中删除
```zsh
git rm runoob.txt # 从暂存区和工作区中删除 runoob.txt 文件
git rm -f runoob.txt # 强制删除以追踪的暂存区和工作区中的runoob.txt 文件
git rm --cached runoob.txt # 从追踪清单和的暂存区中删除,仍保留在工作区
git rm –r * # 从暂存区和工作区中递归删除该目录下全部文件
```
## `mv` 移动或重命名一个文件、目录或软连接
```zsh
git mv README README.md # 改名README ->README.md
```
## `diff` 比较暂存区和工作区的差异
```zsh
git diff # 显示暂存区和工作区的差异
git diff --cached [file] # 显示暂存区和上一个commit的差异
git diff HEAD # 显示工作区与当前分支最新commit之间的差异
git diff [first-branch]...[second-branch] # 显示两次提交之间的差异
git diff --shortstat "@{0 day ago}" # 显示今天你写了多少行代码
```
## `commit` 提交说明
```zsh
git commit -m"提交的情况说明"
git commit -am"提交的情况说明" # 相当于 add . + commit
git commit -v # 查看要提交的信息
git commit --amend # 修改刚刚设置的commit
# 使用一次新的commit,替代上一次提交
# 如果代码没有任何新变化,则用来改写上一次commit的提交信息
git commit --amend -m [message]
git commit --amend [file1] [file2] ... # 重做上一次commit,并包括指定文件的新变化
```
## `fetch` 拉取分支
```zsh
git fetch # 拉取远程所以分支的最新代码
git fetch https://gitHub.com/ master # 拉取当前地址的master分支到本地
git fetch origin master # 拉取origin仓库master分支到本地
```
## `merge` 合并分支
```zsh
git merge dev # 合并本地dev分支,并创建新的提交commit
git merge origin/dev # 合并远程origin仓库的dev分支,并创建新的提交commit
git merge --squash dev # 合并本地dev分支,并将所有提交commit合并为一个commit
git merge --abort # 取消本次合并
```
## `rebase` 合并分支
```zsh
git rebase master # 将本地分支的提交commit 合并到master后面
git rebase –abort # 取消本次合并
```
:::warning
1. 不要在公共分支使用`rebase`,因为往后放的这些`commit`都是新的,这样其他从这个公共分支拉出去的人,都需要再`rebase`,相当于你`rebase`东西进来,就都是新的`commit`了
:::
| 特性 | git merge | git merge --squash | git rebase |
| -------- | ------------------------------ | ---------------------------------- | -------------------------------------------- |
| 历史记录 | 保留完整的历史记录和分支结构 | 将多个提交压缩成一个提交 | 重写历史,保持线性 |
| 合并提交 | 生成一个合并提交 | 不生成合并提交,只创建一个新的提交 | 不生成合并提交 |
| 冲突处理 | 解决合并点的冲突 | 解决合并时的冲突 | 解决每个提交的冲突 |
| 适用场景 | 解决合并点的冲突 | 解决合并时的冲突 | 解决每个提交的冲突 |
| 优点 | 清晰显示合并历史,解决冲突清晰 | 提交历史简洁,减少噪音 | 保持线性历史,日志记录简洁 |
| 缺点 | 可能产生多次合并提交,历史复杂 | 失去详细的提交历史 | 需要处理每个提交的冲突,公共分支上使用有风险 |
## `pull` 拉取并合并分支
```zsh
git pull # 拉取当前分支并与当前分支合并
git pull origin dev # 拉取远程dev分支,并与当前分支合并
git push origin --delete dev # 直接删除远程dev分支,谨慎使用!
```
::: tip\
`git pull` = `git fetch` + `git merge` 或 `git rebase`
1. `git config pull.rebase false`此时:`git pull`=`git fetch`+`git merge`
2. `git config pull.rebase true`此时:`git pull`=`git fetch`+`git rebase`
3. `git config pull.ff only`会将本地分支移到远程分支的最新提交上,而不会创建新的合并提交或执行变基操作\
:::
## `push` 提交分支
```zsh
git pull origin dev # 提交到远程dev分支的代码
git push [remote] --force # 强行推送当前分支到远程仓库,即使有冲突
git push [remote] --all # 推送所有分支到远程仓库
```
## `checkout` 切换分支
```zsh
git checkout -b dev master # 在本地基于master创建dev分支,并切换到dev分支,提交后远程才会生效
git checkout dev # 切换到dev分支
git checkout index.js # 撤销工作区中index.js文件,对暂存区的文件无效
git checkout . # 撤销工作区中所有文件的修改,对暂存区的文件无效
git checkout - # 切换到上一个分支
```
从工作区恢复某个文件或目录
```zsh
git checkout 1.js
```
:::warning
如果远程有新增分支,在切换分支之前,需要先`fetch`拉取一下全部分支
:::
## `branch` 查看分支
```zsh
git branch # 查看本地全部分支,*为当前分支
git branch -r # 查看远程全部分支,*为当前分支
git branch -a # 查看全部分支,*为当前分支
git branch dev # 在本地创建dev分支,提交后远程才会生效。注意与checkout -b的区别
git branch -d dev # 删除本地分支
git branch -d -r dev # 删除远程分支,删除后还需推送到服务器
git branch origin:dev # 删除后推送至服务器
git branch -m [oldname] [newname] # 重命名本地分支,如果oldname已存在于远程分支,那么会新建一个newname兵提交,且不会影响oldname分支
git branch --track [branch] [origin/remote-branch] # 根据上游分支,创建一个新的分支,不切换到新分支
git branch --set-upstream-to=[origin/remote-branch] [branch] # 修改分支的追踪分支
```
:::warning
1. 如果远程有新增分支,在查看远程分支之前,需要先`fetch`拉去一下全部分支
:::
## `log` 查看版本历史
```zsh
git log
git log --stat # 显示commit历史,以及每次commit发生变更的文件
git log -S [keyword] # 搜索提交历史,根据关键词
git log -p [file] #显示指定文件相关的每一次diff
# 显示某个文件的版本历史,包括文件改名
git log --follow [file]
git whatchanged [file]
```
## `blame` 查看指定文件修改记录
```zsh
git blame [file]
```
## `reflog` 查看提交记录
```zsh
git reflog
```
## `reset` 撤销本地提交
```zsh
git reset --hard [commit] # 撤销到指定的commit版本
git reset --hard HEAD^ # 撤销,并切换到最新的一次提交
$ git reset --keep [commit] # 重置当前HEAD为指定commit,但保持暂存区和工作区不变
git reset HEAD^ # 保留当前代码,撤销到git add 之前
git push origin HEAD --force
git reset HEAD [file] # 撤销当前文件的提交
```
## `revert`回滚远程代码
```zsh
git revert [commit] # 保留当前版本,创建一个新的commit并回滚到指定的commit版本,需要填写commit
```
## `stash` 本地代码暂存
```zsh
git stash save "[暂存名称]" # 暂存
git stash list # 查看暂存列表
git stash pop # 取回暂存(栈是先进后出)
git stash apply # 取回指定暂存(不会删除已取出的)
git stash drop [暂存名称] # 删除指定暂存
git reset --hard # 取消取回
git stash clear # 删除全部暂存
git stash show # 查看暂存列表中最新保存的stash和当前目录的差异
```
## `tag` 标签
```zsh
git tag # 查看标签
git tag [标签名] [commit] # 为指定commitID创建标签
git tag -d [标签名] # 删除标签
git push --tags # 推送标签
git checkout [标签名] # 切换标签
```
## `describe` 生成可读性更强的版本号
```zsh
git describe # 基于最近的 tag,生成当前 commit 的描述(如 v1.0-2-g2414721)
git describe --tags # 同上,显示所有 tag(包括轻量标签)
git describe --abbrev=0 # 只显示最近的 tag,不显示 commit 信息
git describe --always # 即使没有 tag,也显示 commit 的简短 hash
git describe --long # 显示完整的 tag-commit 距离信息
```
> 通常用于生成更易读的版本号,常见于自动化发布脚本。
## `rev-parse` 解析和转换 Git 对象
```zsh
git rev-parse HEAD # 显示当前分支最新 commit 的 hash 值
git rev-parse --short HEAD # 显示短 hash
git rev-parse --abbrev-ref HEAD # 显示当前分支名
git rev-parse --show-toplevel # 显示仓库的根目录绝对路径
git rev-parse origin/master # 显示远程分支的 commit hash
```
> 常用于脚本中获取分支名、commit hash、仓库路径等信息。
## `switch` 切换分支
`git switch` 用于切换分支,是对 `git checkout` 分支切换功能的专门细化,语义更清晰。
```zsh
git switch dev # 切换到已有的 dev 分支
git switch -c feature/login # 新建并切换到 feature/login 分支(等价于 git checkout -b)
git switch - # 切换到上一个分支
```
**区别:**
* `git switch` 只用于分支切换,不会用于文件还原,避免误操作。
* `git checkout` 既能切分支又能还原文件,语义不清晰,容易误用。
## `restore` 恢复或撤销文件的更改
`git restore` 用于还原工作区或暂存区的文件内容,是对 `git checkout` 文件还原功能的专门细化。
```zsh
git restore index.js # 撤销工作区 index.js 的修改(还原为暂存区内容)
git restore --staged index.js # 撤销暂存区 index.js 的修改(还原为 HEAD 内容)
git restore . # 撤销所有文件的修改
```
**区别:**
* `git restore` 只用于文件内容的恢复,不涉及分支切换。
* `git checkout` 既能切分支又能还原文件,命令不直观。
***
**对比总结:**
| 操作 | 旧命令(`checkout`/`reset`) | 新命令(`switch`/`restore`) |
| -------------- | ---------------------------- | ---------------------------- |
| 切换分支 | git checkout dev | git switch dev |
| 新建并切分支 | git checkout -b feature | git switch -c feature |
| 撤销工作区文件 | git checkout -- file | git restore file |
| 撤销暂存区文件 | git reset HEAD file | git restore --staged file |
## `subtree` 子树合并
`git subtree` 是一种将一个仓库作为另一个仓库的子目录进行管理的方法,相比 `submodule`,它更简单,不需要额外的配置文件,子项目的代码直接集成到主仓库中。
### 核心参数说明
| 参数 | 说明 | 是否必需 |
|------|------|----------|
| `--prefix=` | 指定子项目在主仓库中的存放路径,如 `library` | **是** |
| `--squash` | 将所有提交压缩为一个单独的提交 | 否 |
### 1. 添加子树
```bash
# master 是要拉取的分支名
git subtree add --prefix=library git@gitee.com:jaqi/submodules-1.git master
# 如果需要指定特定的提交
git subtree add --prefix=library git@gitee.com:jaqi/submodules-1.git
```
**参数说明**:
* `--prefix=library`:将远程仓库内容克隆到主仓库的 `library` 目录下
* 如果目录不存在,Git 会自动创建
### 2. 从远程更新子树
```bash
# 从远程仓库拉取最新更改并合并到子树
git subtree pull --prefix=library git@gitee.com:jaqi/submodules-1.git master
# 使用 --squash 将所有提交压缩为一个
git subtree pull --prefix=library git@gitee.com:jaqi/submodules-1.git master --squash
```
::: warning **注意事项**:
* 如果是从主应用拆分子树,需要删除子树并提交,再重新添加子树才能更新
:::
### 3. 向远程推送子树更改
```bash
# 将子树的更改推送到远程仓库
git subtree push --prefix=library git@gitee.com:jaqi/submodules-1.git master
```
::: warning **注意事项**:
* `library` 有变更,且已经的待提交状态(已执行`-am`)
:::
### 4. 查看子树信息
```bash
# 查看子树的提交历史
git log --oneline --all -- library
# 查看子树的远程配置
git config --get-regexp subtree
```
### 5. 拆分子树(将子目录分离为独立仓库)
```bash
# 步骤1:将子目录拆分为独立分支(这会创建一个新的本地分支)
git subtree split --prefix=submodules-new --branch=submodules-new-branch
# 步骤2:从独立分支推送到新的远程仓库master分支上
git push git@gitee.com:jaqi/submodules-new.git submodules-new-branch:master
```
::: warning **注意事项**:
1. 被拆分的目录内容已经提交到本地仓库
:::
**参数说明**:
* `--prefix=submodules-new`:指定要拆分的子目录路径
* `--branch=submodules-new-branch`:将拆分出的内容创建为本地新分支(分支名可自定义,避免与主分支冲突)
* `submodules-new-branch:master`:将本地 `submodules-new-branch` 分支推送到远程仓库的 `master` 分支
**重要提示**:
* `git subtree split` 只是创建一个**新的本地分支**,不会自动切换过去
* 如果直接使用 `master` 作为分支名,可能会与主仓库的 master 分支冲突,建议使用不同的分支名
## `submodule` 子模块管理
`git submodule` 命令用于管理包含其他 Git 仓库的项目,适用于大型项目或需要集成外部库的场景。通过子模块,你可以将外部库作为项目的一部分管理,而不必直接合并到主仓库中
### 1. 初始化子模块
```bash
# 根据 `.gitmodules` 配置,初始化所有子模块,但不会下载内容,
git submodule init
```
### 2. 根据子模块的引用,拉取子模块的内容
```bash
# 从子模块的远程仓库拉取内容,只是拉取,不会更新子项目的提交
# 通常在初始化项目后使用
git submodule update
# 相当于 git submodule init + git submodule update
git submodule update --init --recursive
```
### 3. 更新子模块的引用,并拉取
```bash
# 递归更新所有子模块(包括子模块的子模块)并拉取最新更改
# --recursive 递归更新,--remote 从远程仓库拉取最新更改
git submodule update --recursive --remote
```
### 4. 添加子模块
```bash
# 将指定仓库作为子模块添加到当前仓库
# 是子模块仓库地址, 是子模块在主仓库中的路径(可选)
git submodule add []
# 示例
git submodule add https://github.com/example/jaqi.git submodule-1
# 示例 添指定分支的子模块
git submodule add -b master https://github.com/example/jaqi.git submodule-1
```
### 5. 移除子模块
```bash
# 从 .git/config 文件中移除子模块
git submodule deinit
# 从主仓库中删除子模块引用
git rm
# 删除子模块的本地缓存
rm -rf .git/modules/
```
* 说明:需要三个步骤完成子模块的完全移除
### 6. 列出子模块
```bash
# 列出当前仓库中的所有子模块及其状态
git submodule
```
* 说明:显示子模块的提交哈希、路径等信息
### 7. 检查子模块状态
```bash
# 显示子模块的当前状态
git submodule status
```
* 说明:包括当前提交哈希、路径以及是否有未提交的更改
### 克隆包含子模块的项目
当克隆一个包含子模块的项目时,需要初始化并更新子模块:
如果 子模块领先于主模块引用的子模块提交,该方法不会更新主模块的子模块引用,需要手动更新。
```bash
# repo-url 是主模块的 git 地址
git clone
cd
git submodule init
git submodule update
# 或者使用一步命令
git clone --recurse-submodules
```
### 在主模块中修改子模块的内容
主项目(父仓库)中存储的是子模块的 特定提交哈希(commit ID) ,而不是对子模块分支的直接引用
> 在主项目中无法直接修改子模块的内容 :
>
> * 当你克隆包含子模块的项目时,子模块目录会处于“分离头指针(detached HEAD)”状态,指向主项目中记> 录的特定提交。
> * 在这种状态下,你可以查看子模块的内容,但任何修改都不会自动关联到子模块的任何分支。
> 关键原因 :
>
> * 主项目仅记录子模块的特定提交,而不跟踪子模块的分支变化。
> * 若不切换到子模块的分支就进行修改,这些修改将处于“游离”状态,无法被正确提交到子模块的版本历史中。
> * 修改子模块后,必须同时更新主项目中对子模块的引用提交,否则其他克隆该项目的开发者将无法获取到最新的子模块修改。
```bash
# 1. 进入子模块目录
cd
# 2. 切换到子模块的目标分支(如main或master)
git switch
# 3. 进行必要的修改
git add .
git commit -m "Update submodule"
# 4. 推送修改到子模块的远程仓库
git push
# 5. 返回主项目,更新对子模块的引用
cd ..
git add
git commit -m "Update submodule reference"
git push
```
### `.gitmodules` 配置文件
`.gitmodules` 是一个 Git 配置文件,用于存储项目中子模块的映射信息。当你添加子模块时,Git 会自动创建或更新这个文件。该文件会被版本控制,与项目一起提交和推送,确保其他开发者克隆项目时能正确获取子模块。
#### 文件结构与格式
`.gitmodules` 文件采用标准的 Git 配置文件格式,由多个子模块配置块组成。每个子模块配置块包含子模块的名称、路径和仓库 URL 等信息。
**基本格式示例:**
```ini
[submodule "DbConnector"]
path = DbConnector
url = https://github.com/example/DbConnector.git
[submodule "Utils"]
path = lib/utils
url = https://github.com/example/Utils.git
branch = main
```
#### 主要配置项说明
| 配置项 | 说明 | 是否必需 |
| -------------------- | ------------------------------------------- | -------- |
| `submodule` 后的名称 | 子模块的唯一标识符,通常与子模块仓库名相同 | 必需 |
| `path` | 子模块在主仓库中的相对路径 | 必需 |
| `url` | 子模块的远程仓库地址 | 必需 |
| `branch` | 指定跟踪的分支(默认为 `master` 或 `main`) | 可选 |
| `ignore` | 忽略子模块状态的方式(默认为 `none`) | 可选 |
| `update` | 更新子模块的策略(默认为 `checkout`) | 可选 |
#### 手动编辑 `.gitmodules` 文件
在某些情况下,你可能需要手动编辑 `.gitmodules` 文件:
1. **修改子模块的 URL**:当子模块仓库地址变更时
2. **调整子模块的路径**:当需要移动子模块在项目中的位置时
3. **添加额外的配置项**:如指定跟踪的分支
**编辑后需要执行的操作:**
```bash
# 使修改生效
git submodule sync
# 更新子模块
git submodule update --init --recursive
```
#### 配置示例详解
**1. 基本配置**
```ini
[submodule "vue"]
path = src/vue
url = https://github.com/vuejs/vue.git
```
* 说明:添加 Vue.js 作为子模块,放置在 `src/vue` 目录下
**2. 指定分支和更新策略**
```ini
[submodule "react"]
path = lib/react
url = https://github.com/facebook/react.git
branch = main
update = rebase
ignore = dirty
```
* 说明:
* 跟踪 `main` 分支
* 更新时使用 `rebase` 策略
* 忽略子模块中的未提交更改
#### 注意事项
* `.gitmodules` 文件会被版本控制,修改后需要提交和推送
* 其他开发者克隆仓库后,Git 会根据 `.gitmodules` 文件中的信息拉取子模块
* 如果需要为本地修改子模块 URL(例如使用 SSH 而非 HTTPS),可以使用 `git config` 命令在本地覆盖配置:
```bash
git config submodule.<子模块名>.url <新的 URL>
```
这种本地修改不会影响 `.gitmodules` 文件
### 注意事项
* 子模块的 `.gitmodules` 文件会被版本控制,包含子模块的 URL 和路径映射
* 子模块在主仓库中被视为一个特定的提交,而不是跟踪其内容变化
* 推送主仓库时不会自动推送子模块的更改,需要单独推送
### `subtree` 与 `submodule` 的区别
| 特性 | `git subtree` | `git submodule` |
|------|--------------|----------------|
| **存储方式** | 子项目代码直接合并到主仓库 | 子项目作为独立仓库引用 |
| **配置文件** | 不需要额外配置文件 | 需要 `.gitmodules` 配置文件 |
| **克隆复杂度** | 直接克隆,无需额外步骤 | 需要 `git submodule init/update` |
| **提交历史** | 子项目提交融入主仓库历史 | 子项目有独立的提交历史 |
| **更新方式** | `git subtree pull` | `git submodule update --remote` |
| **推送方式** | `git subtree push` | 需要进入子模块单独推送 |
| **学习曲线** | 相对简单 | 相对复杂 |
| **适用场景** | 简单集成、不需要频繁同步 | 需要保持子项目独立性、复杂依赖 |
| **克隆速度** | 较慢(包含所有历史) | 较快(只包含引用) |
| **磁盘空间** | 较大(完整历史) | 较小(只有引用) |
## `.gitignore`
> ##### 常用的规则
>
> > mtk 忽略当前目录下整个mtk文件夹\
> > \*.zip 忽略所有.zip格式的文件\
> > mtk/doc 忽略当前目录下mtk文件里的doc文件
>
> ##### 配置语法:
>
> > 以斜杠/开头表示目录;\
> > 以星号\*通配多个字符;\
> > 以问号?通配单个字符\
> > 以方括号\[]包含单个字符的匹配列表;\
> > 以叹号!表示不忽略(跟踪)匹配到的文件或目录;
::: tip
`cd`切换目录
`pwd`显示当前工作目录
`ls`显示当前目录所以文件
`ls -a`显示所有文件包括隐藏的
`mkdir`创建目录(文件夹)
`↑`查看上一条cmd命令
`↓`查看下一条cmd命令
`tab`字段补全命令
`-d`delete:删除
`-D`delete+force强制
`-f`force:强制
`-m`move:移动或重命名
`-M`move+force的快捷键
`-r`remote:远程
`-a`all全部
`!`强制
`q`退出
`w`保存
`wq`保存并退出
`wq!`强制保存并退出.
:::
[Git在线演示](https://learngitbranching.js.org/?locale=zh_CN)\
[Git命令大全](https://gitee.com/all-about-git)
## `.gitkeep`
`.gitkeep` 是一个空的文件,用于在 Git 仓库中保持目录的存在。当你创建一个空的目录时,Git 不会跟踪这个目录,除非你在这个目录中添加一个 `.gitkeep` 文件。
```bash
touch .gitkeep
```
* 说明:当你创建一个空的目录时,Git 不会跟踪这个目录,除非你在这个目录中添加一个 `.gitkeep` 文件。
* 当你删除这个文件时,Git 会开始跟踪这个目录。
* 当你添加这个文件时,Git 会开始跟踪这个目录。
## 与svn命令的对比
| 作用 | git | svn |
| ------------------------ | ---------------- | ------------------------ |
| 版本库初始化 | `git init` | `svn create` |
| 拉取 | `git clone` | `svn co(checkout)` |
| 添加暂存区 | `git add` | `svn add` |
| 本地快照 | `git commit` | `svn commit`(直接提交) |
| 更新 | `git pull` | `svn update` |
| 提交 | `git push` | |
| 查看暂存区 | `git status` | `svn status` |
| 查看分支 | `git branch` | `svn cp` |
| 删除分支 | `git branch -d ` | `svn rm` |
| 合并分支 | `git merge` | `svn merge` |
| 比较暂存区和工作区的差异 | `git diff` | `svn diff` |
| 切换分支 | `git checkout` | `svn switch` |
---
---
url: /proverbs.md
---
---
---
url: /frontend/microFrontends.md
---
## 10.1 微前端概述
微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 `web` 应用的技术手段及方法策略
微前端的核心目标是将巨石应用拆解成若干可以自治的松耦合微应用,这样才能确保微应用真正具备独立开发、独立运行的能力
#### 多页面应用、单页面应用、与为微服务应用
1. 多页面应用(`MPA`):一个应用即一个页面。优点是彼此独立互不依赖。缺点是:不便于交流和资源共享,切换会造成浏览器重刷
2. 单页面应用(`SPA`):一个应用多个页面。优点是页面直接便于交流和资源共享,切换不刷新浏览器只加载局部代码。缺点是:彼此独立相互依赖
3. 微前端:合集了`MPA` 与 `SPA` 框架优势,即保证应用独立互不依赖,又便于应用之间交流和资源共享。
#### 特征
1. 与技术栈无关,主框架不限制接入应用的技术栈,微应用具备完全自主权
2. 独立开发、独立部署,微应用仓库独立,前后端可独立开发,部署完成后主框架自动完成同步更新
3. 增量升级,在面对各种复杂场景时,我们通常很难对一个已经存在的系统做全量的技术栈升级或重构,而微前端是一种非常好的实施渐进式重构的手段和策略
4. 独立运行时,每个微应用之间状态隔离,运行时状态不共享
#### 微前端的类型
1. 单实例:即同一时刻,只有一个微应用被展示,微应用具备一个完整的应用生命周期。通常基于 `url` 的变化来做微应用的切换
2. 多实例:同一时刻可展示多个微应用。通常使用 `Web Components` 方案来做微应用封装,微应用更像是一个业务组件而不是应用
## 10.2 微前端框架 `single-spa`
`single-spa`是单实例微前端框架
### 原理
`single-spa`仅仅是一个微应用生命周期的调度者

1. 路由原理:通过 `pushState` 与 `onpopstate` 操作和监听 `url` 实现微应用的切换
2. 接入方式:基于 `HTML Entry`
3. 代码隔离:基于快照沙箱、代理沙箱
::: tip
1. `pushState`与`popState`详见[3.10.3](/frontend/javascript/BOM#_3-10-3-window%E5%AF%B9%E8%B1%A1%E5%B1%9E%E6%80%A7)
2. `HTML Entry` & `JS Entry`
* `JS Entry`
> 原理:所有的资源(css、图片)都打包成一个 `js` 文件,主应用引用微应用打包出的 `js`文件\
> 缺点:打包出来的文件体积庞大,资源加载慢微应用发布后,主应用需要重新打包
* `HTML Entry`
> 原理:微应用按照原来的方式打包出一个 `html`、主应用加载微应用打包出来的 `html` 文件,并将这个 `html` 节点放在主应用的容器中\
> 缺点:打包出来的文件体积庞大,资源加载慢微应用发布后,主应用需要重新打包\
> 
> :::
### `iframe` 与 `single-spa`
* `iframe` 的弊端
1. 每次进来都要加载,状态不能保留
2. `DOM` 结构不共享。(比如微应用里有一个 `Modal`,显示的时候只能在那一小块地方展示,不能全屏展示)
特性
3. 无法跟随浏览器前进后退
4. 天生的硬隔离,无法与主应用进行资源共享,交流也很困难
* `single-spa`的优势
1. 切换路由就是切换页面组件,组件的挂载和卸载非常快
2. 单页应用共享 `DOM`
3. 前端控制路由,想前就前,想后就后
4. `React` 通信有 `Redux`,`Vue` 通信有 `Vuex` ,可与 `App` 组件进行交流、资源共享
## 10.3 `single-spa` 的实现库 `qiankun`
* 蚂蚁金融科技基于 `single-spa` 的微前端实现库
### 10.3.1 安装与使用
#### 主应用
* 初始化主应用
::: code-group
```zsh [Vue/Cli]
npm install -g @vue/cli
vue create main-app
npm install vue-router -S
```
```zsh [Vite]
pnpm install vite
pnpm create vite
pnpm install vue-router -S
```
:::
* 安装 `qiankun`
```zsh
pnpm install qiankun -S
# 或者
npm install qiankun -S
```
* 注册主应用
```js
// mainApp.js
import { registerMicroApps } from "qiankun";
import config from "./subApps"; // 微应用配置项
const { subApps } = config;
export function registerApps() {
try {
registerMicroApps(subApps, {
beforeLoad: [
app => {
console.log("before load", app);
}
],
beforeMount: [
app => {
console.log("before mount", app);
}
],
afterUnmount: [
app => {
console.log("before unmount", app);
}
]
});
} catch (err) {
console.log(err);
}
}
```
* 配置微应用
```js
// subApps.js
export default {
subApps: [
{
name: "vue-app", // 微应用名称,跟package.json一致
entry: "//localhost:7071", // 微应用入口,本地环境下指定端口
container: "#container", // 挂载微应用的dom
activeRule: "/vue-app", // 路由匹配规则
props: {} // 主应用与微应用通信传值
},
{
name: "react-app", // 微应用名称,跟package.json一致
entry: "//localhost:7072", // 微应用入口,本地环境下指定端口
container: "#container", // 挂载微应用的dom
activeRule: "/react-app", // 路由匹配规则
props: {} // 主应用与微应用通信传值
}
]
};
```
* 挂载主应用
```vue
```
* 配置微应用路由
```js
// rc/router/index.js
import { createRouter, createWebHistory } from "vue-router";
const routes = [
{
path: "",
redirect: { name: "app" },
meta: { title: "App" },
children: [
{
// history模式需要通配所有路由,详见vue-router文档
path: "/vue-app/:pathMatch(.*)*",
name: "vue-app",
meta: {},
component: () => import("@/views/layout/index.vue")
},
{
// history模式需要通配所有路由,详见vue-router文档
path: "/react-app/:pathMatch(.*)*",
name: "react-app",
meta: {},
component: () => import("@/views/layout/index.vue")
}
]
}
];
const router = createRouter({
history: createWebHistory(),
routes
});
export default router;
```
* 基于 UI 复用需求的微应用的拆分方案

## 10.4 参考文档
[single-spa官方文档](https://zh-hans.single-spa.js.org/docs/getting-started-overview)
[qiankun官方文档](https://qiankun.umijs.org/zh/guide)
[微前端解决方案的演进](https://zhuanlan.zhihu.com/p/78362028)
[qiankun在中后台系统实践](https://zhuanlan.zhihu.com/p/259209543?utm_id=0)
[single-spa原理与实践](https://mp.weixin.qq.com/s?__biz=MzA3NTk4NjQ1OQ==\&mid=2247484245\&idx=1\&sn=9ee91018578e6189f3b11a4d688228c5)
[qiankun-vue 后项目demo](https://github.com/gongshun/qiankun-vue-element-admin)
---
---
url: /other/seo.md
---
## 4.1.1 基本概念
权重:页面重要程度\
收录:量被百度收录的页面数\
蜘蛛:爬取工具\
索引量:参与排名的页面\
指数:关键词搜索情况
快照:搜索引擎上保存的被收录的网页的纯文本备份
## 4.1.1 TDK优化
T = title标题关键字描述\
D = description描述\
K = keywords关键字
* 百度分词原理:
1. 基于空格
2. 百度词典
3. 用户行为(大数据)
4. 大于四个字符才分
* title编写规则:
1. 标题不要过长最多30/32字符。
2. 标题不重复(内部竞争)
3. 第一个分词权重最高
* keywords编写规则:
2. 关键词不宜太多推荐2-3个最近,且与内容相符
3. 关键词不重复(内部竞争)
4. 第一个关键词权重最高
* description编写规则:
1. 提供更多相近的关键词
2. 长度200字符内
3. 不参与权重计算
## 4.1.2 网站结构优化
* 页面结构类型:
1. 扁平结构:权重趋向于首页
2. 树状结构:权重趋向于栏目
* 域名权重:
1. 主域名qq.com权重最高
2. 二级域名weixin.qq.com
3. 二级目录www.qq.com/bbs/ 其次
* URL:
使用静态(伪静态)URL:不带有`?`、`%`、`=`、`&`、`php`、`asp`的URL,也就是URL不能带有任何参数
* 网页标签:
1. 使用语义化标签
2. 使用H1...H6标签,依次使用,不要跳用。
3. `a`标签填写`alt`属性
4. `img`标签填写`title`属性
5. 少用`iframe`
* `robots.txt`
```txt
# 网页抓取工具的名称。*表示允许全部
User-agent: *
# 不应抓取的目录或网页
Disallow: /nogooglebot/
# 允许抓取的目录或网页 / 允许爬墙全部页面
Allow: /
# 生成并填写网站地图
Sitemap: http://www.wangxiaokai.vip/sitemap.xml
```
## 4.1.3 百度检索技巧
1. 收录条数:`site:网址`
2. 外链:`Domain:网址`
3. 网址中包含X:`Inurl:X`
4. 标题中包含X:`Intitle:X`
5. 相关的网页类型PDF:`Filetype:pdf`