null
或 undefined
的情况上提供了更为便利的解决方案,同时在需要为变量提供默认值的场景中展现了出色的灵活性。这篇文章将深入研究这两个新特性,并详细探讨它们在Vue3中的项目,通过实例和最佳实践为你提供清晰而实用的指导,帮助更好地运用这些语法糖来简化和增强Vue项目代码。可选链允许在访问对象属性时判断属性是否为 null
或 undefined
,如果是则不会导致错误。在Vue3的模板中,你可以通过以下方式使用可选链:
1 | <template> |
在上述代码中,user?.name
表达式如果 user
为 null
或 undefined
,将不会导致错误,而是会显示为空。同时,user?.address?.city
则展示了如何链式使用可选链。
双问号用于提供默认值的逻辑空合并操作符。当左侧的值为 null
或 undefined
时,它会返回右侧的默认值。在Vue3中的应用如下:
1 | <template> |
在上述代码中,someVariable ?? 'Default Value'
表达式如果 someVariable
为 null
或 undefined
,将会返回 'Default Value'
。
除了在模板中的直接应用,可选链和双问号也可以在计算属性中灵活使用。例如:
1 | <template> |
在上述代码中,formattedUserName
计算属性展示了如何结合可选链和双问号,以更安全地获取用户名称并提供默认值。
在Vue3项目中,你不需要额外安装Babel插件 @babel/plugin-proposal-optional-chaining
和 @babel/plugin-proposal-nullish-coalescing-operator
来使用可选链 (?.
) 和双问号 (??
)。这是因为Vue CLI默认已经为Vue3项目配置了Babel插件以支持这些语法。
Vue CLI使用的默认的Babel配置中包含了支持可选链和空值合并操作符的插件,因此你可以直接在Vue3项目中使用这些语法而无需额外的配置。
如果你使用的是Vue2项目或者在其他环境中,你可能需要手动安装并配置这些插件。但对于Vue3项目,通常不需要额外的配置即可使用这些新特性。
可选链和双问号是Vue3中引入的强大特性,它们使得处理空值和提供默认值变得更加简单和清晰。在模板和计算属性中合理地使用这些特性,可以提高代码的可读性和健壮性。在Vue3项目中,你可以充分利用这些新的JavaScript语法,更轻松地处理对象属性的边缘情况。
]]>-g
参数进行全局安装,因为这是用于同时安装Vue2
和Vue3
的命令。保持两个版本共存的情况下,我选择了局部安装Vue3
。1 | npm install @vue/cli #官方默认的就是vue3了 |
装完了之后在这个文件夹的路径下的终端执行vue,提示的是vue2.9.6的版本,以为我之前vue2是全局安装的。
接下来我找到了位于vue3/node_modules/.bin/vue的文件。如果你无法看到隐藏文件,可以按住Command + Shift + .
来显示隐藏的文件和文件夹。然后,我将该文件从 ‘vue’ 改为 ‘vue3’。
然后进入终端,输入
1 | vim ~/.bash_profile |
输入i
,进行编辑,添加上export PATH=$PATH:[你的.bin文件夹路径]
,
例如export PATH=$PATH:/Users/baosheng/vue3/node_modules/.bin
,按ESC退出编辑模式,然后:wq
保存退出。最后输入
1 | source ~/.bash_profile |
在任何路径下的终端输入vue --version
,有版本信息,测试以前的vue2的项目还是可以正常运行的。
在任何路径下的终端输入vue3 --version
,有版本信息
1 | vue create my-vue2-project |
使用上述命令创建一个新的Vue2项目。
1 | vue3 create my-vue3-project |
使用上述命令创建一个新的Vue3项目。
]]>Pinia
是一个为Vue提供状态管理的库。它专注于提供简洁、强大、可扩展的状态管理解决方案。Pinia
是Vue3官方支持的状态管理库,同时也兼容Vue2。Pinia
的设计目标是在开发大型应用时提供更好的性能和开发体验。它采用了一些现代化的状态管理理念,并通过一些独特的特性使得开发者更容易组织和维护项目的状态管理。
Pinia中文官网地址:https://pinia.web3doc.top/
pinia
符合直觉,易于学习。pinia
是轻量级状态管理工具,大小只有1KB.pinia
模块化设计,方便拆分。pinia
没有mutations
,直接在actions
中操作state
,通过this.xxx
访问响应的状态,尽管可以直接操作state
,但是还是推荐在actions
中操作,保证状态不被意外的改变。store
的action
被调度为常规的函数调用,而不是使用dispatch
方法或者是MapAction
辅助函数,这是在Vuex
中很常见的。store
。Vue devtools
、SSR
、webpack
代码拆分。在Vue3项目中,首先需要安装Pinia
。使用以下命令:
1 | npm install pinia |
或者使用 yarn:
1 | yarn add pinia |
安装完Pinia
后,接下来就是使用了。在使用的初始步骤中,我们需要在项目中引Pinia
。
首先在main.js
文件中引入
vue3写法:
1 | import { createPinia } from 'pinia' |
vue2写法:
1 | import { PiniaVuePlugin } from 'pinia' |
我们将继续使用Vue3来详细介绍Pinia
的使用方法。在导入Pinia
时,我们需要使用Vue的hook
,并确保调用它。一旦调用完成,状态将以插件的形式存在于项目中,最后的步骤是在项目中使用这些状态。经过上述步骤的编写,我们成功地在Vue3
项目中导入和配置了Pinia
,为后续的状态管理操作做好了准备。
1 | import { createApp } from 'vue' |
首先,在根目录下创建一个 store
文件夹,这个结构对于熟悉Vuex的开发者来说应该很熟悉,毕竟Pinia
的设计就是为了替代Vuex
。创建完 store
文件夹后,在其中新建一个 JavaScript
或TypeScript
文件,命名为 index.js
或index.ts
,为了方便大家直接理解,我这里创建的index.js
文件。
defineStore
有三个参数:第一个是state
,第二个是getters
,第三个是actions
。
state
和之前我们vuex
里面的写法是不一样的,在vuex
里面呢,state
是一个对象,但是在pinia
中,state
必须是一个箭头函数,然后在返回一个对象。getters
模块呢,类似于计算属性,是有缓存的,主要是帮助我们修饰一些值。actions
呢,类似于 methods,帮助我们做一些同步的或者是异步的操作,提交state
之类的。index.js文件:
1 | import { defineStore } from "pinia" |
store_name.js文件:
1 | export const enum Names { |
到这个地方为止,其实我们已经可以使用pinia
了,我们写一个页面使用一下pinia
的值。
1 | <template> |
修改Pinia
的值有多种方式,我们逐一介绍。实际上,修改Pinia
中的值就是修改其状态(state)的值。在上个案例中,我们创建了一个状态(state),其中包含了两个值,一个是 name
,另一个是 age
。今天我们的任务是实现修改Pinia
中状态的值。有许多常见的修改值的方式,我们将逐一进行讲解,一共有五种。
这种方式简单粗暴,直接修改即可,在vuex
里面是坚决不允许这样子直接操作state
数据的,但是小菠萝是可以操作。比如,上个案例,我们来实现点击按钮的时候实现age
加1
操作。
1 | <template> |
在我们实例化const userInfo = useInfoStore()
这个state
的时候,其实这个userInfo
中,有一个方法,就是patch
函数,它可以帮助我们批量修改。比如点击按钮,同时修改name
和age
的值,直接上代码:
1 | <template> |
和方式二相同都是使用patch
函数来实现修改,但是有区别,方式二是在patch
函数中传入修改的对象值,但是这种方式传入的是一个函数,其作用可以进行逻辑操作,比如说判断之类的。
1 | <template> |
这种方式不是很常用,尽管它也可以修改state
的值,但是这种方式有一个很大的弊端,它是直接替换掉state
。比如上面的state
里面有两个值,一个是name
,一个是age
,如果我们只想修改age
的值,那么我们必须把age
和name
的值都写上才可以,如果只写age
,那么name
的值就没有了。
1 | <template> |
这个方式我们需要借助actions
来实现,所以说我们需要去store
文件夹下的index.js
文件中写一个action
。
1 | import { defineStore } from "pinia"; |
写完action
就可以使用action
的方式修改age
的值了。只需要使用实例去调用刚才写的action
函数就行了。
1 | <template> |
action
是可以传参的,修改一下index.js
文件编写的action
函数,让它接受一个参数再赋值。
1 | actions: { |
页面代码修改:
1 | // 方式五 |
上面的案例,实例化const userInfo = useInfoStore()
了以后呢,这个userInfo
是可以继续解构操作。但是有个问题,就是解构后的数据,是不具备响应式的,即我们修改了state
的值,页面数据不会跟着变化,因为解构后的不是响应式数据。官方提供了一个方法,可以把解构后的数据转换为响应式的数据。就是 storeToRefs
,使用这个方法把我们解构的对象包裹一下就可以了,这个方法其实就是toRefs
类型.
1 | <template> |
actions
具备处理同步和异步操作的能力。同步操作相对较为简单,上面我们已经通过一个同步的 action
修改了 state
。
先模拟一个异步函数,比如常见的登录。
1 | const User = { |
然后我们在actions
中就可以调用这个异步的操作了,在 actions
处理异步的时候呢,一般是与 async
和 await
连用。
index.js文件:
1 | import { defineStore } from "pinia" |
login.vue页面代码:
1 | <template> |
getters
类似于 vue
里面的计算属性,可以对已有的数据进行修饰,在pinia
中有两种写法。
1 | getters: { |
getter
也是可以像 actions
一样相互调用
1 | getters: { |
pinia
里有很多的 API,下面将介绍几种常见的API
这个 $reset
可以将 state
的数据初始到初始值,比如有一个数据,点击按钮改变了,然后可以通过这个 API ,将数据恢复到初始状态值。
1 | <template> |
$subscribe
使用来监听的,监听 state
数据的变化,只要 state
里面的数据发生了变化,就会自动走这个函数。
1 | <template> |
action
一调用就会被触发,它里面有一个参数 args
,这个args
是 actions
传进来的参数。
1 | // $onAction |
上面的案例只传了一个参数,就是一个工厂函数,其实他还有第二个参数true
,传true
的意思是当这个组件销毁了,这个onAction
还可以继续保活。
1 | // $onAction |
不止 $onAction
可以传第二个参数,$subscribe
也有第二个参数,只不过subscribe
的参数是一个对象,对象里面设置的是detached
为true
效果和onAction
是一样的,当然还有其它的参数,和 watch
是类似的。
1 | userInfo.$subscribe((args, state) => { |
总体来说,Pinia
是一个为 Vue
开发者提供了更灵活、更强大的状态管理工具,尤其在大型应用的状态组织和维护方面具有优势。另外Pinia
还有一些关键特点:
Vue 3 官方支持: Pinia 是 Vue.js 团队官方支持的状态管理库,因此能够充分发挥与 Vue 3 的集成优势。
适用于 Vue 2 和 Vue 3: Pinia 不仅兼容 Vue 3,还可以在 Vue 2 项目中使用,这对于逐步迁移项目或在不同版本间共享代码的项目非常有用。
Typescript 支持: Pinia 提供了完整的 TypeScript 支持,使得开发者在使用 TypeScript 编写代码时能够获得更好的类型提示和开发体验。
零依赖: Pinia 设计为零依赖,不依赖 Vuex 或其他状态管理库。这使得 Pinia 在体积和性能上都更加轻量。
响应式: Pinia 使用 Vue 3 的响应式系统,确保状态的变化能够正确地反映在视图上。
插件体系: Pinia 提供了插件体系,可以轻松扩展其功能。这使得开发者可以根据具体需求引入额外的功能或定制化行为。
Vue3
作为Vue2
的下一个主要版本,不仅在性能上进行了优化,还引入了一些强大的新特性。然而,与Vue2
相比,Vue3
并非仅仅是一个简单的升级,而是一个经过重新设计的框架,注重了性能、可维护性和可扩展性。Vue3
带来了许多强大的新特性,如Composition API
、createRenderer
和Teleport
等,以及对性能的全面优化。尽管在学习曲线上可能需要一些时间,但这些变化使得Vue
更加现代、灵活,为前端开发者提供了更好的开发体验。vue2
通过Object.definedProperty()
的get()
和set()
来做数据劫持、结合和发布订阅者模式来实现,Object.definedProperty()
会遍历每一个属性。vue3
通过Proxy
代理的方式实现。Proxy
的优势:不需要像Object.definedProperty()
的那样遍历每一个属性,有一定的性能提升proxy
可以理解为在目标对象之前架设一层“拦截”,外界对该对象的访问都必须通过这一层拦截。这个拦截可以对外界的访问进行过滤和改写。Object.definedProperty()
要通过遍历的方式监听每一个属性。利用proxy
则不需要遍历,会自动监听所有属性,有利于性能的提升.patch
对象,用来存储两个节点不同的地方。patch
记录的消息去更新dom
Vue
的patch
是即时的,并不是打包所有修改最后一起操作DOM
,也就是在vue
中边记录变更新。(React
则是将更新放入队列后集中处理)。patchFlags
,是一种优化的标识。patchFlags
发生变化的节点,进行识图更新。而对于patchFlags
没有变化的元素作静态标记,在渲染的时候直接复用。虚拟DOM上增加patchFlag
字段。我们借助Vue3 Template Explorer来看
1 | <div id="app"> |
渲染函数如下所示:
1 | import { createElementVNode as _createElementVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from vue |
注意第3个_createElementVNode
的第4个参数即patchFlag
字段类型。
字段类型情况:1
代表节点为动态文本节点,那在diff
过程中,只需比对文本对容,无需关注class、style
等。除此之外,发现所有的静态节点(HOISTED
为-1
),都保存为一个变量进行静态提升,可在重新渲染时直接引用,无需重新创建。
1 | // patchFlags 字段类型列举 |
Vue3
的cacheHandler
可在第一次渲染后缓存我们的事件。相比于Vue2
无需每次渲染都传递一个新函数。加一个click
事件。
1 | <div id="app"> |
渲染函数如下所示:
1 | import { createElementVNode as _createElementVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from vue |
观察以上渲染函数,会发现click
事件节点为静态节点(HOISTED
为-1
),即不需要每次重新渲染。
Tree-shaking
是一种模块打包中的概念,常见于webpack
、rollup
等工具。它的目标是移除JavaScript
上下文中未被引用的代码,以减小最终打包出的文件大小。该过程主要依赖于import
和export
语句,通过检测代码模块是否被导出、导入,并且是否被JavaScript
文件使用来实现。
以一个例子说明,当使用tree-shaking
时,只有那些真正被其他文件引用的模块会被保留在最终的打包文件中,未被引用的代码将被消除,从而优化了项目的性能和加载速度。
以nextTick
为例,对比Vue2
中的全局API
暴露在Vue
实例上的情况,即使未使用nextTick
,它仍然存在于Vue
实例中,无法通过tree-shaking
进行消除。这是因为在Vue2
中,全局API
是直接挂载在Vue
构造函数上,而tree-shaking
对全局对象的属性无法有效地进行检测和消除。这意味着即使你的应用中并未使用nextTick
,它仍会被打包到最终的输出中,造成一定的性能损耗。
在Vue3
中,为了解决这个问题,Vue
的一些全局API
被重新设计,使其更适合tree-shaking
。这种变化使得未使用的全局API
在打包时能够被有效地消除,减小最终打包文件的体积。这是Vue3
中对性能优化的一项改进之一。
1 | import Vue from 'vue'; |
Vue3
中针对全局和内部的API进行了重构,并考虑到tree-shaking
的支持。因此,全局API
现在只能作为ES
模块构建的命名导出进行访问。
1 | import { nextTick } from 'vue'; // 显式导入 |
通过这一更改,只要模块绑定器支持tree-shaking
,则Vue
应用程序中未使用的api
将从最终的捆绑包中消除,获得最佳文件大小。
transition
、v-model
等标签或者指令被命名导出。只有在程序真正使用才会被捆绑打包。Vue3
将所有运行功能打包也只有约22.5kb,比Vue2
轻量很多。比如对于一些第三方插件,vue2
中通常使用prototype
原型来挂载到vue
对象中:
1 | import Vue from 'vue' |
vue3
中提供了一个名为globalProperties
的全局属性配置,可以代替vue2
中的prototype
:
1 | app.config.globalProperties.$http = Axios |
使用示例:
1 | import { getCurrentInstance } from 'vue' |
在Vue3
中,组件现在支持有多个根节点
1 | <template> |
Vue3
提供Suspense
组件,允许程序在等待异步组件加载完成前渲染兜底的内容,如loading
,使用户的体验更平滑。使用它,需在模板中声明,并包括两个命名插槽:default
和fallback
。Suspense
确保加载完异步内容时显示默认插槽,并将fallback
插槽用作加载状态。
1 | <tempalte> |
在List
组件(有可能是异步组件,也有可能是组件内部处理逻辑或查找操作过多导致加载过慢等)未加载完成前,显示Loading…(即fallback
插槽内容),加载完成时显示自身(即default
插槽内容)。
Teleport
是一种能够将我们的模板移动到DOM
中Vue app
之外的其他位置的技术,就有点像哆啦A梦的“任意门”。vue2
中,像modals
,toast
等这样的元素,如果我们嵌套在Vue
的某个组件内部,那么处理嵌套组件的定位、z-index
和样式就会变得很困难。Teleport
,我们可以在组件的逻辑位置写模板代码,然后在Vue
应用范围之外渲染它。1 | <button @click="showToast" class="btn">打开 toast</button> |
createRenderer
,能够构建自定义渲染器,能够将vue
的开发模型扩展到其他平台。canvas
画布上。createRenderer
的基本使用1 | import { createRenderer } from '@vue/runtime-core' |
vue2
中v-for
的优先级高于v-if
,可以放在一起使用,但是不建议这么做,会带来性能上的浪费vue3
中v-if
的优先级高于v-for
,一起使用会报错。可以通过在外部添加一个标签,将v-for
移到外层匿名插槽
1 | <!--子组件--> |
具名插槽
1 | <!--子组件--> |
作用域插槽:父组件模板的所有东西都会在父级作用域内编译;子组件模板的所有东西都会在子级作用域内编译。不过,我们可以在父组件中使用slot-scope
特性从子组件获取数据
1 | <!--子组件--> |
匿名插槽:和在vue2
中一样
具名插槽:
1 | <!--子组件--> |
作用域插槽:
1 | <!--子组件--> |
总结:
vue2
使用slot=''
,vue3
使用v-slot:''
vue2
中在父组件中使用slot-scope="data"
从子组件获取数据,vue3
中在父组件中使用#data
或者#default="{data}"
获取1 | /deep/ .类名{} |
1 | :deep (.类名{}) |
Vue2
是选项API(Options API),一个逻辑会散乱在文件不同位置(data
、props
、computed
、watch
、生命周期钩子等),导致代码的可读性变差。当需要修改某个逻辑时,需要上下来回跳转文件位置。
Vue3
组合式API(Composition API)则很好地解决了这个问题,可将同一逻辑的内容写到一起,增强了代码的可读性、内聚性,其还提供了较为完美的逻辑复用性方案。
1 | <template> |
所有的对象和方法都需要return
才能使用,太繁琐,除了旧项目,可以用这种方式体验Vue3
的新特性以外,不建议了解这种方式。
1 | <template> |
注意: <script setup>
本质上是第二种写法的语法糖,掌握了这种写法,其实第二种写法也基本上就会了。
1 | <template> |
总结:
选项式Api
是将data
和methods
包括后面的watch
、computed
等分开管理,而组合式Api
则是将相关逻辑放到了一起(类似于原生js开发)。
setup语法糖
则可以让变量方法不用再写return
,后面的组件甚至是自定义指令也可以在我们的template
中自动获得。
1 | <script> |
1 | <script setup> |
在选项式api
中,data
函数中的数据都具有响应式,页面会随着data
中的数据变化而变化,而组合式api
中不存在data
函数,所以为了解决这个问题Vue3
引入了ref
和reactive
函数来将使得变量成为响应式的数据。
使用ref
的时候在js中取值的时候需要加上.value
。
reactive
更推荐去定义复杂的数据类型ref
更推荐定义基本类型。
下表包含:Vue2
和Vue3
生命周期的差异
Vue2(选项式API) | Vue3(setup) | 描述 |
---|---|---|
beforeCreate | - | 实例创建前 |
created | - | 实例创建后 |
beforeMount | onBeforeMount | DOM挂载前调用 |
mounted | onMounted | DOM挂载完成调用 |
beforeUpdate | onBeforeUpdate | 数据更新之前被调用 |
updated | onUpdated | 数据更新之后被调用 |
beforeDestroy | onBeforeUnmount | 组件销毁前调用 |
destroyed | onUnmounted | 组件销毁完成调用 |
Vue3
里,除了将两个destroy
相关的钩子,改成了unmount
,剩下的需要注意的,就是在<script setup>
中,不能使用beforeCreate
和created
两个钩子。
如果你熟悉相关的生命周期,只需要记得在setup
里,用on
开头,加上大写首字母就行。
1 | <template> |
1 | <template> |
总结:
从上面可以看出Vue3
中的组合式API
采用hook函数
引入生命周期;其实不止生命周期采用hook函数
引入,像watch
、computed
、路由守卫
等都是采用hook函数
实现.
Vue3
中的生命周期相对于Vue2
做了一些调整,命名上发生了一些变化并且移除了beforeCreate
和created
,因为setup
是围绕beforeCreate
和created
生命周期钩子运行的,所以不再需要它们。
声明事件方法,我们只需要在script
标签里,创建一个方法对象即可。剩下的在Vue2
里是怎么写的,Vue3
是同样的写法。
1 | <template> |
1 | <template> |
1 | <template> |
1 | <template> |
这一部分,我们需要注意一下了,Vue3
中watch
有两种写法。一种是直接使用watch
,还有一种是使用watchEffect
。
两种写法的区别是: watch
需要你明确指定依赖的变量,才能做到监听效果,而watchEffect
会根据你使用的变量,自动的实现监听效果。
1 | <template> |
1 | <template> |
1 | <template> |
1 | <template> |
computed
和watch
所依赖的数据必须是响应式的。Vue3
引入了watchEffect
,watchEffect
相当于将watch
的依赖源和回调函数合并,当任何你有用到的响应式依赖更新时,该回调函数便会重新执行。不同于watch
的是watchEffect
的回调函数会被立即执行,即({ immediate: true }
)
watch介绍watch
用于观察一个或多个响应式引用或计算属性,并在它们更改时执行一个函数
watch参数
immediate
: 是否立即执行回调函数。deep
: 是否深度观察(适用于对象或数组)。flush
: 控制回调何时执行(pre,post,sync
)watchEffect介绍watchEffect
用于立即执行一个函数,并响应该函数内部所依赖的所有响应式引用或计算属性。
主要区别
watchEffect
自动侦测函数内所用到的所有响应式引用,而watch
需要你明确指定要观察的引用。watchEffect
创建时会立即执行一次,而watch
默认不会,除非设置了immediate
选项。watch
回调提供新值和旧值,而watchEffect
不提供。watch
可以观察多个源,但watchEffect
观察函数内的所有响应式引用。相关点
使用场景
使用watch:
使用 watchEffect:
v-model
在vue2
中是双向绑定的语法糖。这里不讨论它在input
标签的使用;只是看一下它和sync
在组件中的使用
我们都知道Vue
中的props
是单向向下绑定的;每次父组件更新时,子组件中的所有props
都会刷新为最新的值;但是如果在子组件中修改props
,Vue
会向你发出一个警告(无法在子组件修改父组件传递的值);可能是为了防止子组件无意间修改了父组件的状态,来避免应用的数据流变得混乱难以理解。
但是可以在父组件使用子组件的标签上声明一个监听事件,子组件想要修改props
的值时使用$emit
触发事件并传入新的值,让父组件进行修改。为了方便vue
就使用了v-model
和sync
语法糖。
1 | <!--父组件---> |
1 | <!--父组件---> |
vue3
中移除了sync
的写法,取而代之的式v-model:event
的形式。其v-model:changePval="msg"
或者:changePval.sync="msg"
的完整写法为 :msg="msg" @update:changePval="msg=$event"
。所以子组件需要发送update:changePval
事件进行修改父组件的值
vue3
和vue2
路由常用功能只是写法上有些区别
1 | <template> |
1 | <template> |
用beforeRouteEnter
作为路由守卫的示例是因为它在setup语法糖
中是无法使用的;大家都知道setup
中组件实例已经创建,是能够获取到组件实例的。而beforeRouteEnter
是再进入路由前触发的,此时组件还未创建,所以是无法setup
中的;如果想在setup语法糖
中使用则需要再写一个setup语法糖
的script
如下:
1 | <template> |
Vue
中组件通信方式有很多,其中选项式API
和组合式API
实现起来会有很多差异,这里将介绍如下组件通信方式:
方式 | Vue2 | Vue3 |
---|---|---|
父传子 | props | props |
子传父 | $emit | emits |
父传子 | $attrs | attrs |
子传父 | $listeners | 无(合并到 attrs方式) |
父传子 | provide | provide |
子传父 | inject | inject |
子组件访问父组件 | $parent | 无 |
父组件访问子组件 | $children | 无 |
父组件访问子组件 | $ref | expose&ref |
兄弟传值 | EventBus | mitt |
声明props
我们可以用defineProps()
,具体写法,我们看代码。
1 | <!--父组件--> |
1 | <!---父组件--> |
1 | <!--父组件--> |
注意
props中数据流是单项的,即子组件不可改变父组件传来的值
在组合式API中,如果想在子组件中用其它变量接收props的值时需要使用toRef将props中的属性转为响应式。
1 | <script setup> |
子组件可以通过emit
发布一个事件并传递一些参数,父组件通过v-on
进行这个事件的监听
1 | <!--父组件--> |
1 | <!--父组件--> |
1 | <!--父组件--> |
子组件使用$attrs
可以获得父组件除了props
传递的属性和特性绑定属性 (class
和style
)之外的所有属性。
子组件使用$listeners
可以获得父组件(不含.native
修饰器的)所有v-on
事件监听器,在Vue3
中已经不再使用;但是Vue3
中的attrs
不仅可以获得父组件传来的属性也可以获得父组件v-on
事件监听器
1 | <!--父组件--> |
1 | <!--父组件--> |
1 | <!--父组件--> |
注意
provide
:是一个对象,或者是一个返回对象的函数。里面包含要给子孙后代属性
inject
:一个字符串数组,或者是一个对象。获取父组件或更高层次的组件provide
的值,既在任何后代组件都可以通过inject
获得
1 | <!---父组件--> |
1 | <!--父组件--> |
1 | <!--父组件--> |
注意
provide/inject
一般在深层组件嵌套中使用合适。一般在组件开发中用的居多。$refs
可以直接获取元素属性,同时也可以直接获取子组件实例
1 | <!--父组件--> |
1 | <!--父组件--> |
1 | <!--父组件--> |
注意
通过ref获取子组件实例必须在页面挂载完成后才能获取。
在使用setup语法糖时候,子组件必须元素或方法暴露出去父组件才能获取到。
兄弟组件通信可以通过一个事件中心EventBus
实现,既新建一个Vue
实例来进行事件的监听,触发和销毁。
在Vue3
中没有了EventBus
兄弟组件通信,但是现在有了一个替代的方案mitt.js
,原理还是EventBus
新建bus.js
文件:
1 | import Vue from 'vue' |
组件1
和组件2
的代码:
1 | <!--组件1--> |
首先安装mitt
npm i mitt -S
然后像Vue2
中bus.js
一样新建mitt.js
文件
mitt.js
文件:
1 | import mitt from 'mitt' |
组件1
和组件2
代码文件:
1 | <!--组件1--> |
1 | <!--组件1--> |
<script setup>
编写组件data
需要注意的地方,整个data
这一部分的内容,你只需要记住下面这一点:
以前在data
中创建的属性,现在全都用ref()
声明,在template
中直接用,在script
中记得加.value
。
1 | <template> |
1 | <template> |
Vue
的核心理念之一是响应式数据绑定,它使得数据的变化能够自动反映在用户界面上,反之亦然。v-model
和.sync
指令正是为了简化这一过程而设计的,让开发者能够更轻松地处理组件之间的数据流动,所以v-model
和.sync
都是用于实现双向数据绑定的指令和修饰符起到了至关重要的意义。v-model
是Vue
提供的一个语法糖,旨在简化表单元素的双向绑定。在内部,v-model
实际上使用了:value
和@input
这两个指令的组合形式。主要用于表单元素(如输入框、选择框等),它建立了数据模型和表单元素之间的双向绑定关系。通过v-model
,用户输入的值直接更新到绑定的数据模型,同时数据模型变化时,也会自动更新到表单元素上显示。
示例:
1 | <template> |
同时,它还能用于在子组件中与父组件进行数据交互。当在一个子组件上使用 v-model
指令时,Vue
会自动为你创建一个名为 value
的prop
和一个名为 input
的事件,这使得子组件可以像处理本地数据一样处理来自父组件的数据。
示例:
1 | <!---父组件--> |
在上面的例子中,v-model="parentData"
实际上是语法糖,它展开为 :value="parentData" @input="parentData = $event"
。
.sync
是一个修饰符,主要用于在自定义组件中实现父子组件之间的双向数据绑定。通过将一个对象传递给子组件,.sync
允许子组件修改父组件的数据。在子组件中,通过触发update
事件并传递新的值,实现了父子组件之间的数据同步。通常用于复杂组件通信的场景。
示例:
1 | <!-- 父组件 --> |
在上面的例子中,:syncedData.sync="parentData"
允许子组件修改parentData
,并通过@input="$emit('update:childData', $event.target.value)"
将变化通知给父组件。
v-model
主要应用于原生表单元素,如输入框、选择框等,用于实现用户与数据的交互。它是用于实现表单输入绑定的常用方式。
.sync
主要应用于自定义组件之间的数据传递和状态管理,可以实现父子组件之间的双向数据流。通过.sync
,可以将父组件的数据传递给子组件,并且子组件可以修改这个数据并将修改的结果传递回父组件。
v-model
是一个固定的语法糖,在实现双向绑定时相对而言较为简便。但是它只能用于特定的表单元素。
.sync
则相对更加灵活,可以用于任意自定义组件的双向数据绑定。它通过约定的方式提供了一种一致的语法,使得在父子组件之间传递和同步数据更加直观和易于理解。
了解v-model
和.sync
的使用方式和区别,可以根据具体的需求和场景选择合适的方式来实现数据的双向绑定,并根据实际情况确定使用哪种方式更为合适。
在Vue3
中,v-model
和.sync
在使用方式上有一些变化。
Vue3
中,v-model
的用法基本保持不变,仍然是用于实现表单元素的双向绑定。Vue2
不同的是,Vue3
中的v-model
默认情况下会将事件和属性绑定到modelValue
上,而不是像Vue2
中的value
属性。这是因为Vue3
使用了 Composition API
中的model
API。Vue3
中,v-model
可以绑定到多个表单元素上,但是每个表单元素都需要借助model
选项来定义对应的属性和事件。1 | <template> |
在上述示例中,使用了同时使用了:ca
和:ipt
修饰符,可以分别用于捕获change
和input
事件,同时更新value1
的值。
需要注意的是,在Vue3
中,与Vue2
不同的是,v-model
默认会使用modelValue
作为属性名和事件名,而不再使用value
。可以通过配置modelConfig
来自定义属性和事件名.
Vue3
中,v-model
的修饰符是可以组合使用的,例如:ca
和:ipt
。modelConfig
可以自定义属性和事件名,提供了更大的灵活性和可定制性。Vue3
中,.sync
修饰符已被废弃,官方不再推荐使用。示例:
1 | <!-- 父组件 --> |
为了实现这一目标,我们可以首先使用npm全局安装一个叫做 “n” 的模块。
1 | sudo npm install -g n |
使用n
加版本号就可以安装其他版本
1 | sudo n 14.2.0 |
成功会显示成这样
安装其他版本node 直接n
后加版本号
再使用n
,通过上下键选择需要用的node版本,选择后回车即可,如图
安装最新版本
1 | n latest |
安装稳定版本
1 | n stable |
删除某个版本
1 | n rm x.x.x |
1 | sudo n |
可以查看所有已安装的node版本,可以根据上下和回车选择要使用的版本。
1 | sudo n 14.21.3 |
1 | sudo n rm 14.21.3 |
1 | n use 14.21.3 index.js |
1 | node -v |
WebSocket是一种双向通信协议,它允许服务器和客户端之间进行实时的数据交换。相比传统的HTTP请求,WebSocket的优势在于它能够建立持久性连接,避免了频繁的握手和连接建立过程。这使得WebSocket非常适合实时聊天应用、在线游戏、实时地图以及其他需要实时通信的应用程序。
要在Nginx中配置WebSocket支持,您需要进行以下步骤:
安装Nginx: 如果您还没有安装Nginx,首先需要安装Nginx。您可以使用适合您操作系统的包管理器来安装Nginx。
编辑Nginx配置文件: 打开Nginx的配置文件,通常位于/etc/nginx/nginx.conf
或/etc/nginx/sites-available/
目录中。您可以使用文本编辑器进行编辑。
添加WebSocket支持: 在Nginx配置文件中,找到您希望添加WebSocket支持的位置,通常是一个server
块。然后添加以下配置:
1 | location /websocket/ { |
这里,/websocket
是WebSocket的端点路径,backend_server
是后端WebSocket服务器的地址。
重新加载Nginx: 保存配置文件后,使用以下命令重新加载Nginx以使配置生效:
1 | sudo service nginx reload |
或者
1 | sudo systemctl reload nginx |
现在,您的Nginx服务器将能够代理WebSocket请求。
为了验证WebSocket连接是否已经成功配置,您可以使用WebSocket客户端工具或JavaScript来创建一个WebSocket连接并测试它。以下是一个使用JavaScript的示例:
1 | var socket = new WebSocket("ws://your-nginx-server/websocket"); |
将your-nginx-server
替换为您的Nginx服务器的地址。如果一切配置正确,您应该能够建立WebSocket连接并接收消息。
使用如上连接,如果所有的连接仅仅为 “ws” 协议的请求是没有问题的,但是如果要及支持 http 请求又支持 ws 请求上述配置就不起作用了。
通过nginx官方关于WebSocket的配置得知,可以自定义变量。故配置如下,就可以做到既支持 ws 请求,又支持 http请求。
1 | http { |
当配置Nginx支持WebSocket时,务必注意安全性。确保您的WebSocket服务器受到适当的安全保护,并使用HTTPS来加密WebSocket连接以保护数据的传输。同时,限制对WebSocket端点的访问,以防止滥用。
总之,通过配置Nginx支持WebSocket,您可以更好地支持实时通信需求,并为您的应用程序提供更好的性能和可扩展性。遵循上述步骤,确保WebSocket连接的负载均衡和安全性,将有助于您构建出色的实时Web应用程序。
]]>在 vue 中提供了⼀套为数据驱动视图更为⽅便的操作,这些操作被称为指令系统,我们看到的v-
开头的⾏内属性,都是指令,不同的指令可以完成或实现不同的功能除了核⼼功能默认内置的指令(v-model
和v-show
),Vue
也允许注册⾃定义指令指令使⽤的⼏种⽅式:
1 | //会实例化⼀个指令,但这个指令没有参数 |
注册⼀个⾃定义指令有全局注册与局部注册,全局注册主要是通过Vue.directive
⽅法进⾏注册,Vue.directive
第⼀个参数是指令的名字(不需要写上v-
前缀),第⼆个参数可以是对象数据,也可以是⼀个指令函数.
1 | // 注册⼀个全局⾃定义指令 `v-focus` |
局部注册通过在组件options
选项中设置directive
属性
1 | directives: { |
然后你可以在模板中任何元素上使⽤新的v-focus
property,如下:
1 | <input v-focus /> |
⾃定义指令也像组件那样存在钩⼦函数:
VNode
更新时调⽤,但是可能发⽣在其⼦VNode
更新之前。指令的值可能发⽣了改变,也可能没有。但是你可以通过⽐较更新前后的值来忽略不必要的模板更新VNode
及其⼦VNode
全部更新后调⽤所有的钩⼦函数的参数都有以下:
DOM
property
:v-
前缀。v-my-directive="1 + 1"
中,绑定值为2
。update
和componentUpdated
钩⼦中可v-my-directive="1 + 1"
中,表达"1 + 1"
。v-my-directive:foo
中,参数为foo
。v-my-directive.foo.bar
中,修饰符{ foo: true, bar: true }
Vue
编译⽣成的虚拟节点update
和componentUpdated
钩⼦中可⽤除了
el
之外,其它参数都应该是只读的,切勿进⾏修改。如果需要在钩⼦之间共享数据,建议通过元素的dataset
来进⾏
举个例⼦:
1 | <div v-demo="{ color: 'white', text: 'hello!' }"></div> |
使⽤⾃定义指令可以满⾜我们⽇常⼀些场景,这⾥给出⼏个⾃定义指令的案例:
表单防⽌重复提交这种情况设置⼀个v-throttle
⾃定义指令来实现
举个例⼦:
1 | // 1.设置v-throttle⾃定义指令 |
设置⼀个v-lazy
⾃定义指令完成图⽚懒加载
1 | const LazyLoad = { |
1 | import { Message } from 'ant-design-vue'; |
关于⾃定义指令还有很多应⽤场景,如:拖拽指令、⻚⾯⽔印、权限校验等等应⽤场景.
]]>首先,让我们简要了解一下CORS的工作原理。CORS是一种安全策略,用于防止恶意网站访问其他域名下的资源。浏览器通过实施同源策略来限制来自不同源的HTTP请求。然而,在某些情况下,你需要允许跨域请求,这就是CORS发挥作用的地方。CORS通过HTTP响应头字段来配置,其中一些重要的字段包括:
Access-Control-Allow-Origin
:指定哪些域名允许访问资源。Access-Control-Allow-Methods
:指定允许的HTTP请求方法。Access-Control-Allow-Headers
:指定允许的HTTP请求头。Access-Control-Allow-Credentials
:指定是否允许发送身份验证信息(如Cookie)。Access-Control-Expose-Headers
:指定哪些HTTP响应头可以在客户端访问。首先,让我们看看如何配置Nginx以允许来自所有域名的请求访问资源。以下是一个示例配置:
1 | server { |
这将允许来自任何域名的请求访问资源,并允许发送身份验证信息。
如果你只想允许特定域名的请求访问资源,可以将Access-Control-Allow-Origin
字段设置为特定域名或域名列表,如下所示:
1 | add_header 'Access-Control-Allow-Origin' 'https://example.com, https://anotherdomain.com'; |
这将只允许example.com
和anotherdomain.com
的请求跨域访问资源。
你还可以根据需要在Nginx的location
块级别配置CORS。例如,如果你只想允许某个特定路径下的资源进行跨域访问,可以像这样配置:
1 | location /auth-service { |
这将允许/auth-service
路径下的资源进行跨域访问。
除了配置CORS,Nginx还可以作为代理服务器来实现跨域资源传输。这在你需要将跨域请求代理到另一个域名下的服务器时非常有用。以下是一个示例配置:
1 | location /api { |
在这个配置中,任何来自/api
路径的请求都会被代理到http://backend-server
,因为服务器跟服务器之间的通信不存在跨域的问题。
在vue的开发中,一般都是使用的axios请求框架来请求后端接口数据,而axios提供了request拦截器,所以我们的思路是从拦截器里面去添加接口的header。
下面是request.js公共请求方法文件:
1 | import axios from 'axios' |
这样,在每个请求后端的接口的时候都会自动相应的跨域的header设置了。
使用Nginx配置CORS和代理是确保不同域名下的应用程序能够相互通信的关键步骤。通过适当配置CORS相关的HTTP响应头字段,以及使用Nginx作为代理服务器,你可以控制允许访问的域名,允许的HTTP方法和头信息,同时实现资源的跨域传输。这些配置可以增加你的Web应用程序的灵活性和互操作性,为用户提供更好的体验。无论是构建跨域API还是处理跨域请求,Nginx是一个强大的工具,能够帮助你达到目标。
]]>在Web开发和服务器管理的旅程中,403 Forbidden错误是一个常见的绊脚石。当你努力设置Nginx以提供网站或应用程序的访问时,可能会遇到这个令人沮丧的错误。本文将深入研究403 Forbidden错误的各种原因,以及如何针对每种情况采取措施来解决问题。我们的目标是帮助你更全面地理解这个问题,并为你提供具体的解决方案,让你能够更轻松地克服Nginx配置中的障碍。
403 Forbidden错误是HTTP状态码之一,表示服务器拒绝了客户端的请求。这意味着客户端没有权限访问请求的资源。通常,这是由于以下几个常见原因导致的:
权限问题:客户端请求的资源需要特定权限才能访问,而客户端没有这些权限。这可能是由于文件或目录的访问权限设置不正确引起的。
由于启动用户和nginx工作用户不一致所致:查看nginx的启动用户,发现是nginx,而为是用root启动的。
目录索引问题:如果请求的资源是一个目录,但没有提供index文件(如index.html),Nginx可能会拒绝列出目录内容。如果没有合适的index文件,也会导致403错误。
Nginx配置错误:Nginx的配置文件中可能存在错误,例如location
块或root
指令的设置不正确。这可能会导致Nginx无法正确处理请求。
访问控制列表(ACL):Nginx的配置文件中可能包含访问控制列表或allow/deny
指令,它们可能被配置为拒绝访问请求。
SELinux或AppArmor等安全模块:如果服务器上启用了SELinux、AppArmor或其他安全模块,它们可能会限制Nginx的访问权限,导致403错误。
URL地址错误:客户端请求的URL地址可能不正确,包括端口号和路径等部分。
现在让我们逐一解决这些问题,以便更好地理解和解决403 Forbidden错误。
权限问题通常是403 Forbidden错误的主要原因之一。Nginx服务器需要具有读取请求的文件或目录的权限。请按照以下步骤检查和解决权限问题:
检查文件和目录权限:
使用ls -l
命令查看文件和目录的权限。确保具有访问所需资源的权限。
1 | ls -l /home/www |
更改权限:
如果权限不正确,可以使用chmod
命令更改它们。以下是设置权限的示例:
1 | chmod -R 777 /home/www/* |
这将确保目录具有可执行权限,文件具有适当的读取权限。
查看nginx的启动用户,发现是nginx,而为是用root启动的
1 | [root@localhost /]# ps aux|grep nginx |
将nginx.config的user改为和启动用户一致,命令:vi /etc/nginx.conf
1 | user root; #就是这里 |
也可以使用如下命令直接查看
1 | [root@localhost nginx]# ps aux|grep nginx |
如果请求的资源是一个目录,但没有提供index文件,可以采取以下措施解决问题:
创建index文件:
创建一个index文件(例如index.html或index.htm),确保它存在于目录中,并包含所需的内容。
启用自动目录索引:
在Nginx配置中添加index
指令,以允许列出目录内容。请注意,在生产环境中不要使用这个选项,因为它可能泄露目录结构。
1 | server { |
如果在/home/www/下面没有index.html的时候,直接文件,会报403 forbidden。
Nginx的配置文件可能包含错误,导致403 Forbidden错误。确保Nginx的配置正确无误,特别关注以下部分:
服务器块(Server Block):
检查server
块中的配置,确保server_name
、root
、index
等指令正确设置。
1 | server { |
location块:
检查location
块中的配置,确保路径和访问控制设置正确。
如果Nginx的配置文件包含访问控制列表(ACL)或allow/deny
指令,确保它们没有被设置为拒绝访问。以下是一个示例配置:
1 | location /assets { |
确保你的配置允许访问请求的资源,或者根据需要进行相应的修改。
如果服务器上启用了SELinux、AppArmor或其他安全模块,它们可能会限制Nginx的访问权限。你可以禁用这些模块,或者配置相应的规则来允许Nginx访问资源。也就是SELinux设置为开启状态(enabled)的原因。
查看当前selinux的状态:
1 | /usr/sbin/sestatus |
将SELINUX=enforcing 修改为 SELINUX=disabled 状态:
1 | vi /etc/selinux/config |
重启生效,reboot
1 | reboot |
请注意,禁用SELinux可能会降低服务器的安全性,因此谨慎使用。更好的方法是配置SELinux规则以允许Nginx访问资源。
确保客户端请求的URL地址是正确的,包括域名、端口号和路径等部分。如果URL地址不正确,将会导致Nginx无法找到请求的资源。
检查Nginx的错误日志文件以获取更多信息,该文件通常位于/var/log/nginx/error.log
或Nginx配置中指定的位置。错误日志可以提供关于403错误的详细信息,帮助你更好地诊断问题。
Nginx 403 Forbidden错误通常是由权限、配置或其他访问限制问题引起的。通过仔细检查和解决上述问题,你可以更好地理解和处理这个常见的错误,确保Nginx服务器能够正常提供资源,同时保护服务器的安全性。无论是在开发、测试还是生产环境中,解决403错误都是维护Nginx服务器的重要一步。希望本文提供的解决方法对你有所帮助。
]]>toFixed
方法是一个常用于将数字转换为字符串并限制小数部分精度的JavaScript内置方法。然而,尽管它在许多情况下非常实用,但也存在一些潜在问题,尤其是在处理浮点数时。这些问题的根源在于JavaScript使用的是 IEEE 754 浮点数表示法,这种表示法并不总能够完全准确地表示所有可能的小数值。这可能导致toFixed
在某些情况下生成不符合预期的结果。前端数值精度问题指的是在 JavaScript 中处理数值时可能会引发的精度损失或不准确性。这是因为 JavaScript 使用标准的 IEEE 754 浮点数表示法,这种表示法无法精确表示所有的小数。例如,让我们来看一个简单的示例:
1 | var result = 0.1 + 0.2; |
在这个示例中,我们期望得到 0.3,但由于浮点数表示的限制,结果变成了一个接近 0.3 的值,但不完全相等。这种情况可能会导致不准确的计算结果,特别是在处理金融数据或其他需要高精度的应用中。
最近项目中遇到了一个数值精度的挑战,特别是在使用 toFixed()
方法时出现了一些令人困惑的结果。举个例子,当我尝试对 2.55 使用 toFixed(1)
时,期望得到的结果应该是 2.5,但实际上却是 2.5。为了解决这个问题,我开始进行一些研究,特别关注了 toFixed()
方法的工作原理。
在我的调研中,我发现了一些有趣的事情。首先,网上有一种说法是 toFixed()
方法使用的是银行家算法,这个算法涉及到四舍五入,但有五种情况来处理舍入,而只有四种情况用于舍弃小数位,因此 5 需要根据具体情况来决定是舍去还是进位。然而,我后来发现这并不是真正的原因。
实际上,问题的根本在于计算机在处理小数时使用了二进制表示法。计算机存储数据时,整数的存储通常没有问题,因为整数可以完全转换为二进制表示。但是,小数的二进制表示可能会引发精度问题。以 0.1 为例,它的二进制表示是一个无限循环的小数,类似于 0.00011001100110011…,但由于计算机存储的限制,它只能存储有限的位数。
因此,当我们使用 toFixed()
方法时,计算机会对这个无限循环的二进制小数进行近似处理,这就是为什么我们会看到结果不精确的原因。这个近似处理是计算机内部为了满足有限存储能力而做出的权衡。
所以,要解决这个问题,我们需要明白在使用 toFixed()
或其他数值操作时,可能会遇到的精度限制,并采取适当的方法来处理。在一些关键的场景中,可能需要考虑使用更高精度的数学库,以确保精确性和可靠性。这个经验教训也提醒我们在前端开发中要特别小心处理数值精度,特别是在金融领域或其他需要高精度计算的应用中。
如下图:
现在,你可能已经更清楚为什么在将 2.55 保留一位小数时结果是 2.5 而不是 2.6 了。这是因为计算机在进行二进制数值存储时会涉及进位和舍去操作。
这也解释了为什么在某些情况下,例如 0.1 + 0.2 并不等于 0.3。原因在于 0.1 和 0.2 本身就是不精确的二进制表示,当它们相加时,存在微小的误差。计算机无法确定你是否希望得到精确的 0.3 结果,因此它保持了这些微小误差。然而,同样不精确的数字在加法操作中可能会出现正确的结果,比如 0.2 和 0.3 相加。
这是为什么呢?因为计算存储的时候有进有啥,一个进一个舍两个相加就抵消了。
这个现象反映了在计算机中处理浮点数时,需要小心处理精度问题,尤其是在需要高度精确性的应用中。对于精确计算需求,我们可能需要考虑使用专门的数学库或采用其他策略来处理数值精度问题。
如下图:
为了解决前端数值精度问题,我们可以采取一系列方法和技术。下面我们将详细介绍其中一些方法,并提供相应的示例代码。
toFixed
方法具体的方法是将字符串与整数进行运算,因为整数的存储在计算机中是精确的,既然 Number.prototype.toFixed() 的舍入方法并不是我们需要的,那么我们可以直接将其重写成符合的即可。
1 | Number.prototype.toFixed=function (d) { // d是小数保留的位数 |
或者
1 | export function toFixed(num, d = 2) { // num是需要转换的数值,d是小数保留的位数 |
在处理需要高精度的数学运算时,可以考虑使用第三方库,例如BigNumber.js
。这些库提供了更高精度的数字表示和数学运算,使你能够更可靠地处理精确计算。
推荐的第三方库,自行查阅:
math.js
big.js
bignumber.js
decimal.js
下面以BigNumber.js为例:
1 | // 使用 BigNumber.js 示例 |
另一种解决数值精度问题的方法是尽量避免累加小数。在某些情况下,你可以将小数转换为整数,并在最后再将结果转回小数。这可以减少精度问题的发生。
1 | function addDecimals(num1, num2) { |
这个示例中,我们将小数转换为整数,执行累加操作,然后再将结果还原为小数。
在一些场景中,你可能需要多次累加或减小小数,这可能导致累积的精度问题。在这种情况下,你可以使用一个累加器来跟踪结果,并将小数累加到整数上。
1 | function accumulateDecimals(numbers) { |
这个示例中,我们使用一个累加器accumulator
来追踪结果,并在最后将其转换回小数。
在处理百分比和比率时,也要格外小心。将它们转换为小数,确保正确的精度,并在需要时进行逆向转换。
1 | function calculatePercentage(decimal, precision = 2) { |
这个示例中,我们编写了两个函数,一个用于计算百分比,另一个用于将百分比转换回小数。
JavaScript 的Math
库提供了一些有用的方法来处理数值精度问题。例如,Math.round()
可以用于四舍五入,Math.floor()
可以用于向下取整,Math.ceil()
可以用于向上取整,以及Math.max()
和Math.min()
可以用于比较数值。
1 | function roundToDecimal(number, precision) { |
Number.isFinite()
Number.isFinite()
方法用于检查给定的数值是否有限(finite)。一个有限数是一个不是 Infinity 或 -Infinity 的数。
1 | const num = 42; |
在上述示例中,我们首先声明一个数值 num
,然后使用 Number.isFinite()
来检查它是否是有限数。如果是有限数,则输出 “Number is finite.”,否则输出 “Number is not finite.”。
这个方法对于确保数值不是无穷大或负无穷大非常有用,因为无穷大的数值可能会引发不准确的计算或其他问题。
Number.isNaN()
Number.isNaN()
方法用于检查给定的数值是否是 NaN(Not-a-Number)。NaN 表示一个非法的或不可表示的数值。
1 | const num = NaN; |
在上述示例中,我们声明一个数值 num
,然后使用 Number.isNaN()
来检查它是否是 NaN。如果是 NaN,则输出 “Number is NaN.”,否则输出 “Number is not NaN.”。
这个方法对于在处理数值时检测非法或不可表示的数值非常有用,以避免对它们进行不合适的操作。
Number.parseFloat()
Number.parseFloat()
方法用于将字符串解析为浮点数,并返回解析后的浮点数。
1 | const str = "3.14"; |
在上述示例中,我们有一个包含浮点数的字符串 str
,然后使用 Number.parseFloat()
将它解析为浮点数。这个方法非常有用,因为它能够处理字符串中的小数点,使得解析后的结果与原始数值保持一致。
这些 ES6 新特性方法 Number.isFinite()
、Number.isNaN()
和 Number.parseFloat()
提供了更严格和更精确的数值处理工具。它们有助于准确地检查数值的有限性和 NaN 状态,同时还能够正确地解析包含小数点的字符串,以保持数值的精确性。这些方法可以用于许多前端开发场景,特别是在处理用户输入或从外部源获取的数据时,以确保数据的正确性和可靠性。
数值精度问题是前端开发中一个常见的挑战,但我们可以采取多种方法来解决它。无论是使用内置方法如 toFixed
,还是使用第三方库如 BigNumber.js
,或者编写自定义函数来处理精度,都可以根据具体的需求选择合适的方法。重要的是要了解数值精度问题,并在开发过程中采取适当的措施,以确保数值计算的准确性和可靠性。希望这篇文章对你理解和解决前端数值精度问题有所帮助。
本文将深入研究如何解决这个问题,特别关注了Nginx作为一个非常流行的代理服务器和反向代理服务器的情况。我们将详细介绍在Nginx中配置的步骤,以便获取代理服务器IP和客户端真实IP地址。同时,我们还将探讨为什么这个过程至关重要,以及它如何影响性能优化、安全性和用户隐私保护。
通常情况下,当请求通过代理服务器时,服务器会看到代理服务器的IP地址而不是最终客户端的IP地址。这是因为代理服务器在请求传递过程中会修改请求头。这可能不是问题,但在某些情况下,你可能需要知道请求的真实来源:
访问控制:某些资源可能只允许特定IP地址或IP地址范围访问。在这种情况下,你需要获取客户端的真实IP地址以进行适当的访问控制。
安全审计:在安全审计过程中,确定请求的来源非常重要。获取真实IP地址有助于识别潜在的恶意活动或入侵尝试。
性能优化:反向代理服务器和负载均衡器通常使用Nginx来管理流量。获取真实IP地址允许Nginx将请求正确路由到后端服务器,从而提高性能和可用性。
日志分析:准确的访问日志对于分析用户行为和流量模式非常重要。如果你看到代理服务器的IP地址,分析将不准确。
RealIP 模块是 Nginx 的一个官方模块,用于获取客户端的真实 IP 地址。当 Nginx 用作反向代理服务器时,请求会首先经过代理服务器,然后再转发给后端服务器。这会导致后端服务器看到的 IP 地址是代理服务器的地址,而不是客户端的真实 IP 地址。RealIP 模块的主要作用是帮助你解决这个问题,确保后端服务器可以正确地识别客户端的真实 IP 地址。
以下是 RealIP 模块的介绍、安装方法和使用法:
模块名称:ngx_http_realip_module
作用:获取客户端的真实 IP 地址,而不是代理服务器的 IP 地址。
适用场景:适用于使用 Nginx 作为反向代理服务器时,需要传递客户端真实 IP 地址给后端服务器的情况。这对于访问控制、安全审计、日志记录和性能优化非常有用。
实现原理:RealIP 模块通过修改 Nginx 变量 $remote_addr
的值来实现。默认情况下,$remote_addr
包含代理服务器的 IP 地址,但启用 RealIP 模块后,它将包含客户端的真实 IP 地址。
1 | --with-http_realip_module |
1 | ./configure --prefix=/usr/local/nginx --user=nginx --group=nginx --with-http_stub_status_module --with-http_realip_module |
1 | cp -r ./objs/nginx /usr/local/nginx/sbin/ |
要使用 RealIP 模块,你需要在 Nginx 配置文件中进行以下步骤:
加载 RealIP 模块:首先,确保 Nginx 已经编译并加载了 RealIP 模块。通常情况下,RealIP 模块在 Nginx 默认的编译选项中是包含的。
配置 RealIP 模块:在 Nginx 配置文件中,你需要配置 RealIP 模块的指令。以下是一些常用的配置指令:
real_ip_header
:指定从请求中读取客户端真实 IP 地址的头字段,默认是 X-Real-IP
。你可以根据实际情况修改它。
set_real_ip_from
:指定允许传递真实 IP 地址的代理服务器 IP 地址或 CIDR 范围。只有这些 IP 地址的请求才会被视为受信任的代理服务器,真实 IP 地址才会被提取。
real_ip_recursive
:用于处理多层代理的情况。如果请求经过多个代理服务器,启用此选项会递归查找 real_ip_header
中的真实 IP 地址。
示例配置:以下是一个简单的示例配置,用于启用 RealIP 模块并配置它:
1 | http { |
启用了 RealIP 模块后,Nginx 将会正确地提取客户端的真实 IP 地址,并在后端服务器收到请求时将其包含在请求中。
总之,RealIP 模块是 Nginx 中一个非常有用的模块,用于确保后端服务器可以获取到客户端的真实 IP 地址。这对于实现访问控制、安全审计、性能优化和日志记录等任务非常重要。要使用 RealIP 模块,你需要在 Nginx 配置文件中配置相应的指令,以确保客户端的真实 IP 地址被正确识别。
set_real_ip_from
指令要配置Nginx以获取代理服务器IP地址,我们使用 set_real_ip_from
指令。这个指令告诉Nginx,哪些IP地址是信任的代理服务器。只有这些IP地址的请求将被视为来自代理服务器。
1 | http { |
如果有多个代理服务器,你可以在同一行上添加它们,用空格分隔:
1 | http { |
以下是一个示例配置,用于获取代理服务器的IP地址:
1 | http { |
这个配置告诉Nginx只接受来自IP地址为 192.168.1.100
的代理服务器的请求。
在默认情况下,Nginx将请求视为来自代理服务器的,因此在访问日志中,你将看到代理服务器的IP地址,而不是客户端的真实IP地址。要解决这个问题,我们需要告诉Nginx查找哪个HTTP头字段以获取客户端的真实IP地址。
real_ip_header
指令通常,这个头字段被称为 X-Forwarded-For
。要配置Nginx使用这个头字段,我们使用 real_ip_header
指令。
1 | http { |
这个配置告诉Nginx查找 X-Forwarded-For
头字段,以获取客户端的真实IP地址。
localtion
获取客户端的IP地址不仅可以通过proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
获取到,也可以通过proxy_set_header X-real-ip $remote_addr;
获得。
1 | location /api/ { |
1 | log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for" "$upstream_addr"'; |
如果你的网络架构中有多层代理服务器,可能需要启用递归以处理多层代理。递归允许Nginx查找多个 X-Forwarded-For
头字段,并选择最后一个非空的IP地址。
1 | http { |
这对于复杂的代理结构非常有用。
以下是一个示例配置,用于获取客户端真实IP地址:
1 | http { |
这个配置将告诉Nginx从 X-Forwarded-For
头字段中提取客户端的真实IP地址,并在访问日志中记录它。
虽然获取客户端真实IP地址对于许多应用程序非常重要,但要小心配置以防止滥用。通过 set_real_ip_from
指令,你可以限制哪些IP地址被视为代理服务器。只允许受信任的代理服务器的IP地址。
在配置Nginx时,确保只信任受信任的代理服务器发送的头字段。不受信任的代理服务器可能会伪造 X-Forwarded-For
头字段,从而导致安全问题。
遵循Nginx的安全最佳实践,确保你的Nginx服务器是最新的,并且安装了最新的安全补丁。此外,监控Nginx的访问日志以检测异常活动。
通过获取客户端真实IP地址,你可以更精确地跟踪谁访问了你的网站或应用程序。这对于分析访问模式和用户行为非常有用。
在安全审计过程中,确定请求的来源至关重要。获取真实IP地址有助于识别潜在的恶意活动或入侵尝试。
反向代理服务器和负载均衡器通常使用Nginx来管理流量。获取真实IP地址允许Nginx将请求正确路由到后端服务器,从而提高性能和可用性。
通过获取客户端真实IP地址,你可以更容易地识别和阻止来自特定IP地址的滥用或攻击。
确保你的代理服务器已正确配置以传递 X-Forwarded-For
头字段。检查代理服务器的设置以确保它们不会修改或删除这个头字段。
检查代理服务器的配置,确保它正确传递头字段,并且没有其他问题。
请再次检查Nginx配置文件,确保 real_ip_header
指令已正确设置为代理服务器发送的头字段名称。
如果服务器上启用了防火墙或安全组,请确保它们不会阻止来自代理服务器的流量。
如果有多个代理服务器,确保每个代理服务器都正确传递 X-Forwarded-For
头字段。你可以使用 real_ip_recursive on;
来处理多层代理。
获取真实IP地址对于网络管理、安全性和性能优化非常重要。Nginx提供了强大的工具来帮助你获取代理服务器IP地址以及客户端真实IP地址。通过正确配置 set_real_ip_from
和 real_ip_header
指令,以及遵循安全最佳实践,你可以确保你的应用程序能够准确地识别请求的来源。
无论是用于访问控制、安全审计、性能优化还是日志分析,获取真实IP地址都是关键的。因此,深入了解如何配置Nginx以获取这些IP地址是非常有价值的技能。
]]>Token 和 Session 分别代表了不同的方法来管理用户的状态和身份。Session 依赖于服务器端的会话管理,而 Token 更注重客户端的身份验证和状态维护。这两种方法各有优势和适用场景,根据您的项目需求和性能要求,选择正确的方法至关重要。
在本文中,我们将深入研究 Token 和 Session 的区别,以及它们在前端开发中的实际应用。我们将提供详细的示例代码,旨在帮助初学者理解这些概念,并将它们应用到自己的项目中。无论您是正在学习前端开发的新手还是寻求进一步了解身份验证和授权的开发人员,本文都将为您提供有价值的信息。通过深入了解 Token 和 Session,您将能够更好地保护用户数据,确保应用程序的安全性,以及提供出色的用户体验。让我们开始探索这两个关键的前端开发概念吧。
Session 是一种服务器端的会话管理机制,用于跟踪用户的状态和身份验证。当用户首次访问网站时,服务器会为其创建一个唯一的会话标识,通常是一个长随机字符串。这个会话标识存储在服务器上,并且通常会在客户端的 Cookie 中存储一个与之关联的 Session ID。
在会话期间,服务器会将用户的相关信息存储在 Session 中,例如用户ID、用户名、权限等等。这些数据可以在用户访问不同页面时共享和使用。Session 通常在用户注销或一段时间后被销毁,以确保安全性。
Token 是一种轻量级的身份验证和授权机制,它不依赖于服务器端的状态存储。通常,Token 是一个包含用户信息和其他元数据的 JSON 数据结构,它被签名和加密以确保安全性。Token 通常在用户登录成功后生成,并存储在客户端,通常存储在 LocalStorage 或 SessionStorage 中。
与 Session 不同,Token 不需要在服务器端存储用户状态,这使得它们适用于构建分布式和可伸缩的应用程序。Token 通常具有较短的有效期,并在过期后需要刷新或重新生成。
现在,让我们深入研究 Token 和 Session 之间的区别。
最大的区别之一是 Session 需要服务器端来维护状态信息,而 Token 是无状态的。这意味着服务器不需要存储关于每个用户的会话数据,从而使系统更容易扩展和维护。
Token 通常比 Session 更安全。Token 可以包含签名和加密信息,以防止被篡改。此外,使用 Token 的应用程序可以更容易地实施跨域资源共享(CORS)策略,因为 Token 可以在请求头中发送,并且不需要在服务器端进行复杂的跨域配置。
由于 Session 依赖于服务器端的状态存储,因此在构建高度可伸缩的应用程序时可能会遇到性能问题。相反,Token 可以轻松地扩展到多个服务器节点,因为它们不依赖于共享的服务器状态。
Token 更适合用于跨不同平台的应用程序,因为它们可以存储在客户端的 LocalStorage 或 SessionStorage 中,并且不依赖于特定的浏览器或操作系统。
现在,让我们看看如何在前端应用中使用 Token 和 Session。
要在前端中使用 Session,首先需要确保服务器端已正确配置 Session 管理。然后,可以通过 Cookie 或其他方式将 Session ID 存储在客户端,以便将其包含在每个请求中。以下是一个简单的示例:
1 | // 前端代码 |
要在前端中使用 Token,首先需要实现用户的身份验证系统,通常是通过用户名和密码登录,然后服务器会返回一个 Token。Token 通常存储在 LocalStorage 或 SessionStorage 中,以便将其包含在每个请求中。以下是一个示例:
1 | // 前端代码 |
全性
Token 和 Session 是前端开发中的关键概念,用于处理用户身份验证和访问控制。理解它们之间的区别以及如何在应用程序中使用它们对于构建安全、可伸缩和高性能的应用程序至关重要。选择 Token 还是 Session 取决于您的应用程序需求,但在任何情况下,都要牢记实施安全性措施和最佳实践,以保护用户数据和确保应用程序的可靠性。
]]>在深入研究 Proxy 的功能之前,让我们首先了解一下 Proxy 是什么。
Proxy 是 ES6 中的一个内置对象,它允许我们创建一个代理对象,用于拦截和自定义对目标对象的操作。这意味着我们可以捕获和控制对象上的各种操作,包括属性的读取、写入、删除,以及方法的调用等。Proxy 的出现为我们提供了一种灵活和强大的方式来操作和扩展对象的行为。
在开始深入探讨 Proxy 的功能之前,让我们看一下 Proxy 的基本语法。
1 | const target = {}; // 目标对象 |
上面的代码创建了一个简单的代理对象 proxy
,它会拦截对 target
对象的操作,并根据需要进行自定义处理。在接下来的部分,我们将探讨 Proxy 可以实现的各种功能。
Proxy 最常见的用途之一是拦截对象属性的读取和写入操作。通过在 get
和 set
拦截器中定义自定义行为,我们可以实现各种有趣的功能。
1 | const target = { |
在上面的示例中,我们拦截了属性 age
的读取操作,将其返回值修改为 “Secret!”。这种方式可以用于隐藏敏感信息或根据条件返回不同的值。
1 | const target = { |
在上面的示例中,我们拦截了属性 age
的写入操作,检查了属性值是否为负数,如果是负数,则抛出错误。这种方式可以用于数据验证和保护属性的完整性。
除了读取和写入属性,我们还可以拦截属性的删除操作。
1 | const target = { |
在上面的示例中,我们拦截了属性 age
的删除操作,如果尝试删除 age
属性,则抛出错误。这种方式可以用于保护重要属性不被删除。
Proxy 不仅可以拦截属性的操作,还可以拦截方法的调用操作。这为我们提供了一种方式来实现函数式编程的概念,例如柯里化(Cur
rying)和函数组合(Function Composition)。
1 | function add(a, b) { |
在上面的示例中,我们拦截了 add
方法的调用操作,并在方法执行后将结果乘以 2。这种方式可以用于对方法的结果进行变换或增强。
Proxy 还可以用于代理数组操作,例如拦截数组元素的读取、写入、添加和删除操作。
1 | const target = [1, 2, 3]; |
在上面的示例中,我们拦截了数组元素的读取和写入操作,并在数组长度读取时返回长度的两倍。此外,我们还拦截了添加元素的操作,如果尝试添加元素,则抛出错误。
Proxy 还可以用于数据验证和保护。通过拦截属性的读取、写入和删除操作,我们可以确保数据的完整性和一致性。
1 | const user = { |
在上面的示例中,我们拦截了属性 password
的读取操作,并抛出错误以禁止访问密码。同时,我们还拦截了属性 username
的写入操作,并在尝试设置非字符串的用户名时抛出错误。
1 | const data = { |
在上面的示例中,我们拦截了对敏感信息属性的读取、写入和删除操作,并在所有情况下抛出错误,以确保数据的安全性和保护。
Proxy 为响应式编程提供了强大的支持。通过拦截属性的读取和写入操作,我们可以创建可观察对象(Observable Objects),这些对象可以通知观察者对象(Observer Objects)状态的变化。
1 | function createObservable(data) { |
在上面的示例中,我们创建了一个可观察对象 user
,当访问属性时会自动添加观察者,并在属性发生变化时通知观察者。这种方式非常适合构建响应式界面和状态管理系统。
Proxy 允许我们在运行时动态创建代理对象,这意味着我们可以根据需要为不同的对象创建不同的代理。
1 | function createDynamicProxy(target) { |
在上面的示例中,我们创建了两个不同的代理对象 proxy1
和 proxy2
,它们分别代理了不同的目标对象 obj1
和 obj2
。这种方式使得我们可以根据需要创建灵活的代理对象,以实现各种功能。
柯里化是一种函数式编程的概念,它将接受多个参数的函数转换成一系列接受一个参数的函数。Proxy 可以用于实现函数柯里化。
1 | function curry(fn) { |
在上面的示例中,我们创建了一个函数 curry
,它使用 Proxy 拦截函数的调用操作,并根据参数的数量返回新的函数或最终的结果。这种方式使得我们可以轻松地实现函数柯里化。
函数组合是函数式编程的另一个重要概念,它允许将多个函数组合成一个新的函数。Proxy 也可以用于实现函数组合。
1 | function compose(...fns) { |
在上面的示例中,我们创建了一个函数 compose
,它使用 Proxy 拦截函数的调用操作,并将一系列函数按照从右到左的顺序组合起来。这种方式使得我们可以轻松地实现函数组合。
除了拦截属性的读取和写入,Proxy 还可以拦截对象的遍历和枚举操作。
1 | const target = { |
在上面的示例中,我们拦截了对象的遍历和枚举操作,只允许遍历和枚举 name
属性,同时对所有属性的读取都返回了一个问候语。
Proxy 可以用于创建虚拟对象,这些对象在访问属性时可以动态生成属性值。
1 | const virtualObject = new Proxy({}, { |
在上面的示例中,我们创建了一个虚拟对象
virtualObject
,它在访问任何属性时都会返回一个动态生成的属性值。这种方式可以用于创建临时对象或模拟对象。
Proxy 可以用于实现观察者模式,其中观察者可以监听目标对象的变化并作出响应。
1 | class Observable { |
在上面的示例中,我们创建了一个可观察对象 data
,它使用 Proxy 来拦截属性的写入操作,并在属性发生变化时通知观察者。
Proxy 是 JavaScript 中一个强大的特性,它为我们提供了一种全新的方式来操作和控制对象的行为。通过拦截属性的读取、写入、删除,以及方法的调用,Proxy 可以实现各种有趣的功能,包括数据验证、响应式编程、函数柯里化、函数组合、观察者模式等等。在前端开发中,Proxy 可以用于构建更灵活、更强大的应用程序,提高开发效率和代码质量。在日常工作中,不妨尝试使用 Proxy 来解决一些复杂的问题,探索前端开发的新境界。
]]>在这篇文章中,我们将带您深入探讨如何通过 CSS 来实现九宫格布局,为您提供了多种不同的方法以及相应的示例代码。无论您是刚刚入门前端开发,还是有一定经验的开发者,本文都将为您提供宝贵的知识,让您能够更好地掌握九宫格布局的技巧和诀窍。
我们将从基础的 Flexbox 布局,到强大的 CSS Grid 布局,甚至到使用表格布局和绝对定位的高级技巧,一一为您呈现。每种方法都将详细解释其优点和限制,并提供实际的示例,以便您能够根据项目的需求选择最合适的布局方式。
无论您是在构建响应式网站、移动应用还是桌面应用,都会受益于掌握这些九宫格布局技巧。在接下来的内容中,我们将引导您逐步理解和应用这些方法,让您的布局工作更加高效和出色。
九宫格布局是将页面分为3x3的网格,通常用于创建网站的整体布局。每个格子可以容纳不同类型的内容,如导航菜单、内容区域、侧边栏等。这种布局方式有助于使页面看起来整洁有序,同时提供了很好的可扩展性。
Flexbox(弹性盒子布局)是一种强大的 CSS 布局模型,特别适合创建九宫格布局。以下是一个简单的示例,展示了如何使用 Flexbox 实现基本的九宫格布局:
1 |
|
在这个示例中,我们使用了 Flexbox 的 display: flex
属性来创建一个弹性容器 .grid-container
,并使用 flex-wrap
让内容自动换行。每个九宫格 .grid-item
的宽度和高度都设置为32%,这样它们将平均分布在父容器内。通过调整这些百分比值,您可以控制每个格子的大小。
优点:
缺点:
使用场景:
CSS Grid 布局也是实现九宫格布局的强大工具。以下是一个示例,展示了如何使用 CSS Grid 实现九宫格布局:
1 |
|
在这个示例中,我们使用了 Grid 布局的 display: grid
属性来创建一个网格容器 .grid-container
,并使用 grid-template-columns
定义了每列的大小。repeat(3, 1fr)
表示我们有3列,每列的宽度平均分配。通过调整列的数量和宽度,您可以轻松地控制九宫格的布局。
优点:
缺点:
使用场景:
表格布局是一种经典的布局方法,也适用于九宫格。您可以使用 <table>
元素和表格单元格来创建九宫格,如下所示:
1 |
|
这种方法使用了 <table>
、<tr>
和 <td>
元素来创建九宫格布局,每个单元格具有相等的宽度和高度。您可以根据需要调整单元格的样式和边框。
优点:
缺点:
使用场景:
您还可以使用绝对定位来创建九宫格布局,将每个格子绝对定位到父容器中的特定位置。以下是一个示例:
1 |
|
这种方法使用了绝对定位来精确地控制每个格子的位置。每个 .grid-item
元素都有一个独特的类名,通过调整 top
和 left
属性来定位它们。
优点:
缺点:
使用场景:
如果您不想手动编写每个格子的 HTML,您还可以使用 CSS 网格生成来动态生成九宫格。以下是一个示例:
1 |
|
在这个示例中,我们使用了伪元素来生成格子,通过设置 grid-template-columns
和 grid-auto-rows
,可以自动创建九宫格。这种方法更具动态性,适合大规模的布局需求。
优点:
缺点:
使用场景:
您可以使用浮动属性将九宫格中的项目浮动到所需的位置。这是一种传统的布局方法,适用于旧版浏览器的兼容性要求。
1 |
|
在这个示例中,每个格子都设置了 float: left;
来使它们浮动在左侧。overflow: hidden;
用于清除浮动,以确保父容器包含所有子项。
优点:
缺点:
使用场景:
如果您需要更复杂的布局,您还可以使用JavaScript来生成九宫格。以下是一个示例:
1 |
|
这个示例中,我们使用JavaScript动态创建了九个格子,并将它们附加到了父容器 .grid-container
中。
优点:
缺点:
使用场景:
通过使用以上方法,您可以轻松实现九宫格布局,这是创建各种网页布局的基础。根据项目需求和个人喜好,您可以选择适合您的布局方式。希望这些示例代码能够帮助您更好地理解如何使用 CSS 来创建九宫格布局,为您的网页提供更加精美的外观和良好的用户体验。
]]>typeof
运算符、instanceof
运算符、constructor
属性以及Object.prototype.toString.call()
方法。我们将详细探讨它们的工作原理、使用情况以及在实际应用中需要注意的细节和限制。typeof
运算符是最简单的数据类型检测工具之一。它返回一个字符串,表示给定表达式的数据类型。
1 | console.log(typeof 42); // "number" |
需要注意的是,typeof null
返回"object"
,这是一个历史上的错误,但在实际使用中我们可以通过额外的判断来区分null和对象。
虽然typeof
运算符在大多数情况下都可以很好地工作,但它有一些限制和细节需要注意:
注意事项:
typeof
是一种快速且简单的方式来检测数据类型,但对于复杂的数据结构,如自定义对象,可能不够准确。null
和普通对象,都返回"object"
,这是历史遗留问题。优点:
typeof
适用于基本数据类型的检测,如数字、字符串、布尔值等。缺点:
null
和普通对象。instanceof
运算符用于检测对象是否是某个构造函数的实例。它的语法如下:
1 | object instanceof constructor |
其中,object
是要检测的对象,constructor
是构造函数。如果object
是constructor
的一个实例,instanceof
将返回true
,否则返回false
。
1 | const date = new Date(); |
尽管instanceof
运算符可以用于检测对象的构造函数,但它也有一些限制和细节需要注意:
注意事项:
instanceof
通常用于检测对象是否是特定构造函数的实例,适用于自定义对象的检测。优点:
缺点:
每个JavaScript对象都有一个constructor
属性,该属性指向创建该对象的构造函数。因此,我们可以使用constructor
属性来检测对象的数据类型。
1 | console.log((42).constructor === Number); // true |
与instanceof
类似,constructor
属性也有一些限制和细节需要考虑:
注意事项:
constructor
属性,但如果对象的constructor
属性被修改,检测结果可能失效。优点:
测自定义对象的类型。
缺点:
constructor
属性可能被修改的影响。Object.prototype.toString.call()
方法是一种更可靠的方式来检测JavaScript数据类型。它返回一个字符串,包含了对象的内部标识,可以用来确定对象的数据类型。
1 | console.log(Object.prototype.toString.call(42)); // "[object Number]" |
使用Object.prototype.toString.call()
方法的优点是,它可以准确地检测所有数据类型,包括自定义对象和基本数据类型。此外,它不会受到多个执行环境的影响,因为它使用的是当前执行环境的全局对象。
通常,为了获得更准确的数据类型检测结果,我们可以综合使用typeof
和Object.prototype.toString.call()
方法。
1 | function getDataType(value) { |
这种综合方式能够更精确地检测各种数据类型,既考虑了基本数据类型,也考虑了自定义对象。
综合而言,选择合适的数据类型检测方式取决于具体的使用场景。在实际编码中,根据需要权衡各种方式的优点和缺点,以确保数据类型检测的准确性和效率。不同的方式可以在不同的情况下发挥作用,因此熟练掌握这些方式将有助于编写更健壮的JavaScript代码。
]]>在这篇文章中,我们将迈出一步,探讨两种常见的样式导入方式:<link>
元素和 @import 指令。我们将不仅仅是浅尝辄止,而是深入挖掘它们的本质。我们将会深入研究它们的语法,理解它们的实际用途,评估它们的各自优势和劣势,以及最重要的是,我们将提供一些建议,帮助你在不同情境下做出明智的选择,确定何时应该使用其中的哪一种方法。
<link>
元素<link>
元素是HTML标签,用于在HTML文档中引入外部资源,最常见的用途之一是链接外部CSS样式表。
1 | <link rel="stylesheet" type="text/css" href="styles.css"> |
rel
属性定义关系,通常设置为 stylesheet
表示这是一个样式表。type
属性定义链接的资源类型,通常设置为 text/css
。href
属性指定外部CSS文件的路径。并行加载:<link>
元素使浏览器能够并行加载多个外部资源,不会阻止页面渲染。这意味着页面可以同时加载CSS和其他资源,提高性能和加载速度。
适用于所有情况:<link>
元素适用于几乎所有情况,无论是在页面的 <head>
部分还是在页面的底部引入CSS文件。
可缓存:浏览器可以缓存通过 <link>
导入的CSS文件,以便在用户再次访问网站时加载得更快。
额外的HTTP请求:每个 <link>
元素都会生成一个额外的HTTP请求,如果有许多CSS文件,可能会导致性能问题。
不适合动态加载:如果需要根据某些条件动态加载CSS,<link>
元素不太适用,因为它通常在页面加载时生效。
@import
指令@import
是CSS中的一种规则,用于在CSS文件内部导入其他CSS样式表。它允许在CSS文件内部引入其他CSS文件。
1 | @import url("styles.css"); |
url()
函数用于指定外部CSS文件的路径。组织性强:@import
允许你在CSS文件内部组织样式,将相关的样式放在一起,有助于代码的可维护性。
按需加载:可以在需要的时候使用 @import
导入样式,而不是一开始就全部加载。这对于动态加载和条件加载非常有用。
阻塞渲染:与 <link>
不同,@import
会阻止页面的渲染,因为它必须在样式加载完毕后才能继续加载页面内容。
性能差:由于 @import
阻塞页面渲染,可能导致性能问题,特别是在移动设备上。
不可缓存:一些老旧的浏览器可能不会缓存通过 @import
导入的样式表。
<link>
元素时:@import
指令时:无论你选择使用 <link>
元素还是 @import
指令,以下是一些最佳实践:
合并CSS文件:尽量减少HTTP请求,将多个CSS文件合并成一个,以提高性能。
压缩CSS:使用压缩工具来减小CSS文件的大小,提高加载速度。
使用CDN:使用内容分发网络(CDN)来提供CSS文件,减少服务器负载并加速文件加载。
避免嵌套太深:不要嵌套太多的 @import
,因为它们可能导致较慢的加载时间。
按需加载:根据页面的需要,按需加载CSS,减少不必要的加载。
无论你选择使用 <link>
元素还是 @import
指令,导入样式表都是前端开发中不可或缺的部分。理解它们的优势和劣势以及何时使用哪种方法,将有助于你更好地管理和优化你的网页样式。在实际项目中,根据具体情况选择最适合你的导入方式,并遵循最佳实践来提高性能和可维护性。无论你是初学者还是经验丰富的开发者,掌握这些技巧都将为你的前端开发工作带来便利。
最简单的文件下载方法是通过使用<a>
标签的download
属性。这允许我们在页面上创建一个链接,当用户点击时,会提示下载文件。
实现思路:
href
和download
属性的<a>
标签。href
属性指定文件的URL,download
属性指定下载时的文件名。1 | <template> |
在上面的示例中,我们创建了一个简单的文件下载链接。用户点击链接时,浏览器会提示他们下载文件。
如果文件需要从后端服务器获取,可以使用Vue来触发后端API以获取文件,并将其提供给用户。
实现思路:
1 | <template> |
在上面的示例中,我们使用了Vue的$axios
来请求后端API,并将响应的数据转换成Blob对象。然后,我们创建一个临时的链接元素,设置其href
属性为Blob的URL,并触发点击事件来下载文件。
Vue还可以利用第三方库来实现文件下载,其中一种常见的库是file-saver
。这个库可以轻松地在浏览器中触发文件下载。
实现思路:
file-saver
库。file-saver
库的saveAs
函数触发文件下载。首先,安装file-saver
库:
1 | npm install file-saver --save |
然后,在Vue组件中使用它:
1 | <template> |
在上面的示例中,我们导入了file-saver
库的saveAs
函数,然后在下载文件的方法中使用它。这种方式使文件下载更加简单和可维护。
在某些情况下,你可能需要显示文件下载的进度条,以便用户了解下载进度。你可以使用Vue的vue-progressbar
库等来实现这一目标。
实现思路:
vue-progressbar
库。vue-progressbar
库来显示下载进度。首先,安装vue-progressbar
库:
1 | npm install vue-progressbar --save |
然后,在Vue应用中使用它:
1 | <template> |
在上面的示例中,我们使用了vue-progressbar
库来显示下载进度。首先,在Vue组件中导入并注册VueProgressBar
组件。然后,在下载文件的方法中,我们通过onDownloadProgress
事件监听下载进度,并将进度传递给进度条组件。
axios
库下载文件除了前面提到的方法,你还可以使用axios
库来下载文件。axios
是一个强大的HTTP请求库,它允许你更灵活地处理文件下载请求。
实现思路:
axios
发送GET请求以获取文件的二进制数据。首先,确保你已经安装了axios
库:
1 | npm install axios --save |
然后,在Vue组件中使用它:
1 | <template> |
这种方式使用了axios
库的更多配置选项,例如指定responseType
为blob
来获取二进制数据。其余的部分与前面的示例类似。
fetch
API下载文件你还可以使用fetch
API来下载文件。fetch
是JavaScript的内置方法,可以用于发出HTTP请求。
实现思路:
fetch
发送GET请求以获取文件的二进制数据。1 | <template> |
在这个示例中,我们使用了fetch
API来获取文件的二进制数据,然后将其转换为Blob对象,并触发下载。这是一种不需要额外依赖的纯JavaScript方法。
Vue.js
扩展插件如果你需要在整个Vue应用中实现文件下载,你可以考虑使用Vue.js的扩展插件,例如vue-file-download
。这种方法使文件下载更加模块化和可重用。
实现思路:
首先,安装vue-file-download
插件:
1 | npm install vue-file-download --save |
然后,在Vue应用中使用它:
1 | <template> |
在这个示例中,我们使用了v-file-download
指令来触发文件下载,该指令由vue-file-download
插件提供。通过配置指令的参数,你可以指定下载的URL和文件名。
你可以将文件数据转换为Blob对象,然后创建一个Blob URL来触发文件下载。这个方法适用于不需要向后端请求文件的情况。
实现思路:
href
属性为Blob URL,触发下载。1 | <template> |
在这个示例中,我们将文件数据转换为Blob对象,然后创建Blob URL。然后,我们创建一个链接元素,将其href
属性设置为Blob URL,触发文件下载。
iframe
进行下载另一种下载文件的方法是使用隐藏的iframe
元素。这个方法适用于需要向后端请求文件的情况,并且可以在不刷新页面的情况下进行下载。
实现思路:
iframe
元素。iframe
的src
属性设置为文件下载的URL。1 | <template> |
在这个示例中,我们创建了一个隐藏的iframe
元素,并将其src
属性设置为文件下载的URL。当用户点击下载按钮时,文件将在iframe
中下载,而不会导致页面刷新。
blob-util
库blob-util
是一个JavaScript库,可以简化Blob数据的处理和操作。你可以使用它来下载文件。
实现思路:
blob-util
库将文件数据转换为Blob对象。首先,安装blob-util
库:
1 | npm install blob-util --save |
然后,在Vue组件中使用它:
1 | <template> |
在这个示例中,我们使用了blob-util
库来将文件数据转换为Blob对象。然后,我们创建了一个下载链接,将其href
属性设置为Blob的URL,触发文件下载。
这些是Vue实现文件下载的10种常见方式。你可以根据你的具体需求选择适合你的方法,然后根据示例代码进一步定制和扩展。无论你选择哪种方法,Vue都提供了强大的工具来实现文件下载功能。希望这篇文章对你有所帮助!
]]>虚拟 DOM 是一个抽象概念,它是以 JavaScript 对象的形式表示整个页面的 DOM 结构。在 Vue.js 中,我们通常使用 render
函数或模板来定义组件的虚拟 DOM。
1 | render() { |
在这个例子中,h
函数(或 createElement
)用于创建虚拟 DOM,其第一个参数是要创建的元素标签,第二个参数是一个包含子元素的数组。这个虚拟 DOM 表示了一个包含一个段落和一个按钮的 div
元素。
虚拟 DOM 的一个重要特性是它是“虚拟”的,不直接与浏览器的 DOM 交互。相反,它仅仅是一个 JavaScript 对象,用于描述页面的状态和结构。
虚拟 DOM 的出现是为了解决直接操作真实 DOM 带来的性能问题。在传统的前端开发中,频繁地更新真实 DOM 是一项昂贵的操作,它可能导致页面的重排(Reflow)和重绘(Repaint),从而影响用户体验。
虚拟 DOM 的出现解决了这个问题,它通过以下方式提高了页面的性能:
批量更新:虚拟 DOM 可以批量处理多次状态变化,然后一次性更新真实 DOM,减少了重排和重绘的次数。
快速比对:虚拟 DOM 可以在更新前后比较差异,只更新发生了变化的部分,而不必更新整个页面。
跨平台兼容性:虚拟 DOM 是框架的一部分,可以在不同的平台上运行,如浏览器、移动端、服务器等。
在 Vue 3 中,虚拟 DOM 到真实 DOM 的转换是一个经过精心设计和高度优化的过程。它主要涉及以下几个关键步骤:
当一个组件初始化或数据发生变化时,Vue 3 会使用 h
函数(或 createElement
)来创建一个新的虚拟 DOM 树。这个虚拟 DOM 包含了组件的状态、事件监听器以及渲染函数。
1 | // 创建虚拟 DOM |
这个新的虚拟 DOM 表示了页面的最新状态。
Vue 3 会将新的虚拟 DOM 与之前渲染的虚拟 DOM 进行比较,找到它们之间的差异。这个过程是虚拟 DOM 技术的核心,通常被称为 Diff 算法。
Diff 算法的结果是一组差异补丁,用于描述新旧虚拟 DOM 之间的
差异。这些差异补丁包括了需要删除、更新或添加的节点,以及相应的操作。
最后,Vue 3 会将生成的差异补丁应用到真实 DOM 上。只有真正需要更新的部分会被修改,这使得页面更新更加高效。
下面,我们将详细研究每个步骤以及其在源码中的实现。
在 Vue 3 中,h
函数(或 createElement
)用于创建虚拟 DOM。这个函数接受多个参数,包括元素的标签名、属性、子元素等,然后返回一个虚拟 DOM 节点。
1 | const vnode = h('div', { class: 'container' }, [ |
在上面的示例中,我们使用 h
函数创建了一个虚拟 DOM,表示了一个 div
元素包含一个段落和一个按钮。
Diff 算法是 Vue 3 中虚拟 DOM 技术的核心。它的作用是比较新的虚拟 DOM 和之前的虚拟 DOM,找出它们之间的差异。这个过程通常被称为“Diffing”。
Diff 算法的实现是一个复杂的话题,通常涉及虚拟 DOM 树的递归遍历,以及对节点的比较和更新。Vue 3 使用了一种高效的 Diff 算法,可以快速找到差异,而不必对整个虚拟 DOM 树进行比较。
在 Diff 过程中,Vue 3 会执行以下操作:
差异补丁是一个包含了需要对真实 DOM 进行操作的一组指令。这些指令包括了删除、更新、添加等操作,以及相应的目标节点。
在 Vue 3 中,差异补丁的生成是一个重要的步骤,它决定了如何有效地更新真实 DOM。
1 | // 一个简化的差异补丁对象示例 |
上面的示例展示了一个简化的差异补丁对象,它包括了删除、更新和添加操作。这些操作会告诉 Vue 3 如何处理真实 DOM。
最后一步是将生成的差异补丁应用到真实 DOM 上。Vue 3 会遍历差异补丁,执行相应的操作,从而更新页面。
1 | // 应用差异补丁 |
上面的代码演示了如何应用差异补丁。根据差异补丁的类型,我们执行相应的操作,例如删除、更新或添加节点。
Vue 3 中的虚拟 DOM 到真实 DOM 的转换过程是一个复杂而精妙的技术。它通过创建虚拟 DOM、比较新旧虚拟 DOM、生成差异补丁以及应用差异补丁的步骤,实现了高效的页面渲染和更新。这一技术使得前端开发者可以专注于页面的逻辑和交互,而无需担心性能问题。
通过深入理解这些过程和 Vue 3 的源码,我们可以更好地掌握前端开发中虚拟 DOM 技术的运作方式。这有助于我们优化应用程序,提高性能,并更好地理解现代前端框架的内部工作原理。
希望本文能够帮助你更深入地理解 Vue 3 中虚拟 DOM 的转换过程,以及其在前端开发中的重要性。继续探索这一领域,将使你成为一名更出色的前端开发者。
]]>CSS选择器是用于选择文档中的元素以应用样式的重要工具。CSS选择器有许多不同的类型,每种类型都有自己的用途。以下是一些常见的CSS选择器:
元素选择器是最基本的选择器之一,它根据HTML元素的名称来选择元素。例如,要选择所有段落元素(<p>
),可以使用以下选择器:
1 | p { |
类选择器允许您根据元素的class
属性选择元素。这是一种非常有用的选择器,可以用于选择多个元素并为它们应用相同的样式。例如,要选择所有带有btn
类的按钮元素,可以使用以下选择器:
1 | .btn { |
ID选择器通过元素的id
属性来选择元素。每个页面上的ID应该是唯一的,因此ID选择器通常用于选择特定的元素。例如,要选择一个具有header
ID的元素,可以使用以下选择器:
1 | #header { |
后代选择器允许您选择元素的后代元素。它使用空格分隔元素名称,例如,要选择<ul>
元素内的所有<li>
元素,可以使用以下选择器:
1 | ul li { |
子元素选择器选择元素的直接子元素。它使用>
符号分隔元素名称。例如,要选择<ul>
元素的直接子元素<li>
,可以使用以下选择器:
1 | ul > li { |
伪类选择器用于选择元素的特殊状态或位置。例如,:hover
伪类选择器用于选择鼠标悬停在元素上的状态:
1 | a:hover { |
伪元素选择器用于选择元素的特殊部分,如::before
和::after
伪元素用于在元素前后插入内容:
1 | p::before { |
属性选择器允许您根据元素的属性值来选择元素。例如,[type="text"]
选择所有type
属性为”text”的元素:
1 | [type="text"] { |
相邻兄弟选择器选择与指定元素相邻的兄弟元素。例如,h2 + p
选择<h2>
元素后紧接着的<p>
元素:
1 | h2 + p { |
通用选择器选择页面上的所有元素。它使用*
符号表示:
1 | * { |
以上是一些常见的CSS选择器,它们可以根据需要进行组合和嵌套,以选择目标元素并为其应用样式。
在CSS中,有一些属性是可以被子元素继承的,这意味着
父元素上设置的值会自动应用到子元素上。这些可继承的属性包括:
color
color
属性用于设置文本颜色,它可以被子元素继承。例如:
1 | p { |
子元素的文本也将具有蓝色颜色,除非子元素上有自己的color
设置。
font-family
font-family
属性用于设置字体系列,它可以被子元素继承。例如:
1 | body { |
子元素的文本将继承font-family
的值。
font-size
font-size
属性用于设置字体大小,它可以被子元素继承。例如:
1 | h1 { |
子元素的文本将继承font-size
的值。
font-weight
font-weight
属性用于设置字体粗细,它可以被子元素继承。例如:
1 | strong { |
子元素的文本将继承font-weight
的值。
line-height
line-height
属性用于设置行高,它可以被子元素继承。例如:
1 | p { |
子元素的文本将继承line-height
的值。
text-align
text-align
属性用于设置文本对齐方式,它可以被子元素继承。例如:
1 | div { |
子元素中的文本将继承text-align
的值。
text-decoration
text-decoration
属性用于设置文本修饰,如下划线、删除线等,它可以被子元素继承。例如:
1 | a { |
子元素中的链接文本将继承text-decoration
的值。
text-transform
text-transform
属性用于设置文本转换,如大写、小写等,它可以被子元素继承。例如:
1 | span { |
子元素中的文本将继承text-transform
的值。
visibility
visibility
属性用于设置元素的可见性,它可以被子元素继承。例如:
1 | .parent { |
子元素也将不可见。
cursor
cursor
属性用于设置鼠标光标的样式,它可以被子元素继承。例如:
1 | .button { |
子元素中的文本也将具有相同的鼠标光标样式。
需要注意的是,并非所有属性都可以继承,例如width
、height
、margin
等通常不会被子元素继承。可以通过CSS规范或浏览器文档查看属性是否可继承。继承属性可以在父元素上设置,以影响其子元素的样式。
CSS选择器和属性继承是前端开发中的基础知识,深入理解它们可以帮助我们更好地掌握CSS,实现所需的样式效果。选择合适的选择器并了解哪些属性可以被子元素继承是编写干净、高效CSS代码的关键。
希望本文能够帮助您更好地理解CSS选择器和属性继承,提高前端开发技能。如果您有任何问题或需要进一步的指导,请随时咨询。愿您的前端之路越发光辉!
]]>