基础知识
在我开始使用C#开发web开始,大多都是使用Razor模版开发html页面。很少会想java这样将前后端分离,之前进入过一家公司,在本地也算是一家大的联网公司了。他们的软件最开始是使用C#开发的,后面有一部分开始使用java开发,这样公司里面就招聘前端,java,C#。C#的开发人员就是前后端需求都是连在一起,java的需求就是拆分开,他们只做后端。其实我们也可以前后端分离的,但是还是自己默默地干了。像C#这样服务端渲染(SSR)和前后端分离后的前端渲染(CSR) 各自有着自己的优缺点,结合自己的需求和可调动资源选择。
服务端渲染(SSR)
工作方式:服务器生成页面的 HTML 内容,然后将其发送给客户端。客户端直接接收并显示已渲染的页面。
优点:
更快的首屏渲染:SSR 提供一个完整的 HTML 页面,客户端无需等待大量 JavaScript 加载和执行,因此首屏渲染速度较快。
有利于 SEO:搜索引擎可以直接抓取完整的 HTML 内容,使得 SSR 网站更易于被搜索引擎索引。
更好的兼容性:适用于一些性能较低的设备或不支持 JavaScript 的浏览器,用户能获得完整的页面体验。
缺点:
服务器压力较大:每个用户请求都会触发服务器生成 HTML,容易导致高并发下服务器压力增加。
交互性较低:需要与前端代码结合才能实现复杂的交互,前后端切换时可能有短暂的空白页面或跳转闪烁。
开发复杂性:需要将前后端逻辑整合,常常依赖框架来实现 SSR(如 Next.js、Nuxt.js)以简化开发。
适用场景:
网站内容主要为静态内容,页面内容变化较少的项目。
需要快速响应和 SEO 优化的网站,如新闻、博客、电子商务网站首页等。
前端渲染(CSR)
工作方式:服务器发送一个基础的 HTML 文件,随后加载 JavaScript,并在浏览器中生成和渲染页面内容。
优点:
更好的用户交互:页面可以通过 JavaScript 实现复杂的交互效果,不需要重新加载整个页面,用户体验流畅。
减少服务器负担:页面渲染和数据操作在客户端进行,减少了服务器的压力。
更灵活的前端架构:CSR 更适合构建高度动态、交互复杂的应用(如单页应用,SPA),可轻松实现前端组件复用。
缺点:
首屏加载较慢:初次加载需要传输和执行大量 JavaScript,导致页面在首屏上渲染速度变慢。
SEO 难度增加:因为页面内容是在客户端生成的,搜索引擎抓取器可能无法完全索引页面内容。
需要较好的客户端性能:依赖于客户端的性能,设备较慢或网络不佳的用户可能会体验卡顿。
适用场景:
交互性和动态性较高的单页应用,如社交网络、任务管理、在线文档编辑等。
用户登录后显示个性化内容的应用,例如管理控制台、CRM、仪表盘。
VUE 的使用(CDN引用)
使用Razor模版的一个痛点就是“需要与前端代码结合才能实现复杂的交互”,如果交互多,则需要写很多js代码,维护起来特别累。对于一些复杂页面还是选择使用vue来实现,这样页面交互会轻松一点。vue 的使用使用有两种情况,一种是Vue CLI使用 Vue CLI 或 Vite 创建项目,这些工具会自动配置好 Webpack 或 Vite 等打包工具。VUE有多个版本,每个版本语法不一样(如果照着写了效果不一样,记得检查版本),下面使用的是VUE3。
官网文档地址:➤ https://cn.vuejs.org/guide/introduction.html
# 使用 Vue CLI 创建项目
npm install -g @vue/cli
vue create my-project
# 使用 Vite 创建项目
npm create vite@latest my-project --template vue
另一种是CDN 引用,直接在页面上引用vuejs即可。
<script src="https://cdn.jsdelivr.net/npm/vue@3"></script>
<div id="app">
{{ message }}
</div>
<script>
const app = Vue.createApp({
data() {
return {
message: 'Hello Vue!'
}
}
})
app.mount('#app')
</script>
使用第一种Vue CLI方式需要编译打包后才能访问,第二种CDN引用不需要编译,使用比较简单。对于C# MVC 项目,本来没有做前后端分离,就是某个页面交互比较复杂,所以选择CDN引用的方式。
对于vue 的使用照着官网(➤ https://cn.vuejs.org/guide/introduction.html)写就行了,这里就不列出来了,介绍一下没有记录的。
第三方控件使用
CDN引入和创建vue项目不一样,引用其他vue控件时,需要umd.js,不是所有控件都会提供。
<script src="/vuedraggable/vuedraggable.umd.js"></script>
createApp({
components: {
vuedraggable,
},
data() {
return { items: [],}
},
})
<vuedraggable v-model="items" item-key="id" animation="30" :sort="true" @@end="dragEnd" class="col-md-12 row me-0">
<template #item="{ element }">
<div>
<input v-bind:id="element.id" v-bind:data-sort="element.sort" v-if="element.isEdit"
v-model="element.name" />
<span v-else class="media-title">{{element.name}} </span>
</div>
</template>
</vuedraggable>
UMD(Universal Module Definition)是一种模块化模式,它可以让一个库兼容多种 JavaScript 模块加载系统,适用于浏览器、CommonJS 和 AMD 环境。UMD 格式使得组件或库可以在各种环境下使用,通常包括以下几种形式:
浏览器直接引入:在没有模块系统的浏览器环境中,通过 <script> 标签加载 UMD 格式的库,全局会自动添加一个变量,如 VueTreeselect,可以直接在代码中使用。
CommonJS:在 Node.js 环境中使用 require 引入模块。
AMD:在 AMD 模块加载器中使用 define。
UMD 的特点
跨环境兼容:适用于浏览器、Node.js、AMD 等不同环境。
常见于 CDN 资源:许多组件库(如 Vue 和第三方库)提供 UMD 格式,便于通过 CDN 直接引入,尤其适合在浏览器环境中使用。
是否所有组件都有 UMD 格式?
并非所有组件或库都会提供 UMD 格式。是否提供取决于库的开发者以及项目的使用场景:
提供 UMD:许多主流组件库(如 Vue、Vue Router、Axios、Lodash 等)通常会提供 UMD 格式,方便直接在浏览器中引入。
不提供 UMD:一些现代的、偏向模块化设计的库可能仅提供 ESM(ES Modules)或 CommonJS 格式,因为 UMD 需要额外的代码逻辑来兼容不同环境,且在模块化已普及的环境下,UMD 的需求逐渐减少。
如果需要直接在 HTML 中通过 CDN 引入某个组件,可以查看该库的官方文档,通常会明确说明是否提供 UMD 格式。
自己创建组件
自己创建组件和官网上一样。这里组件写成一个变量形式,直接在components中使用即可。
html代码
<div>
<my-component></my-component>
</div>
js 代码
<script>
// 定义 MyComponent 组件
const MyComponent = {
template: `<div>Hello from MyComponent</div>`
};
// 创建 Vue 应用实例
const app = Vue.createApp({
components: { MyComponent }
});
// 挂载应用到页面中的 #app 元素
app.mount('#app');
</script>
控件的使用确实比较麻烦,如果使用控件比较多。还是单独创建一个vue项目,开发好编译后放在项目下面也是可以的,只是这样要多出一些操作。
数据操作(增、删,改,查,计算)
数据操作的方式大多都是js中的方法,即使没有使用vue也可以在引用了jq 的页面中使用。所有页面的交互,除了css的一些动画,其实也就是数据的改变。下面介绍一下实战过程中需要使用到的一些数据(数组)操作,同时也是记录方便自己查找,开发方向比较多,过几天又忘记了。列表中添加一条记录,vue中列表其实就是数组v-for=“items” 得到的,那么只要在items数组中添加数据即可。
数组中添加数据
1、push() 在数组末尾添加一个或多个元素(经常使用)
let arr = [1, 2, 3];
arr.push(4); // arr 变成 [1, 2, 3, 4]
arr.push(5, 6); // arr 变成 [1, 2, 3, 4, 5, 6]
2、unshift() 在数组开头添加一个或多个元素。
let arr = [1, 2, 3];
arr.unshift(0); // arr 变成 [0, 1, 2, 3]
arr.unshift(-2, -1); // arr 变成 [-2, -1, 0, 1, 2, 3]
3、splice() 在数组中指定位置插入一个或多个元素。( 可以新增,也可以删除)
/*删除元素*/
var items = ["小明", "大明", "高明", "神明"];
items.splice(2, 1); // 删除从位置 2 开始的一个元素 (高明)
// items结果 为: "小明", "大明", "神明"
/*添加和删除元素*/
var items = ["小明", "大明", "高明", "神明"];
items.splice(2, 1, "新明1", "新明2"); // 在位置 2 删除一个元素,并添加两个新元素
// items结果 为:"小明", "大明","新明1", "新明2", "神明"
/*只添加元素*/
var items = ["小明", "大明", "高明", "神明","小占时光"];
items.splice(2, 0, "新明1", "新明2"); // 在位置 2 删除一个元素,并添加两个新元素
// items结果 为:小明", "大明", "新明1", "新明2","高明", "神明","小占时光"
4、concat()用于连接数组,返回一个新的数组。
let arr = [1, 2, 3];
let newArr = arr.concat(4, 5); // newArr 为 [1, 2, 3, 4, 5]
5、使用索引直接添加
let arr = [1, 2, 3];
arr[3] = 4; // arr 变成 [1, 2, 3, 4]
arr[10] = 11; // arr 变成 [ 1, 2, 3, 4, <6 empty items>, 11 ]
6、 Array.from() 创建一个新数组并添加新元素
let arr = [1, 2, 3];
let newArr = Array.from([...arr, 4, 5]); // newArr 变成 [1, 2, 3, 4, 5]
// 生成从 1 到 6 的数组
const arr = Array.from({ length: 6 }, (_, index) => index + 1);
Array.from({ length: 6 }) 创建了一个长度为 6 的数组,数组的元素是 undefined。
第二个参数是一个映射函数 (_, index) => index + 1,它接受两个参数:当前元素的值(在这里是 undefined),和元素的索引。通过这个函数,索引 0 被映射为 1,索引 1 被映射为 2,以此类推,直到索引 5 映射为 6。
添加数据方法用的比较多的就是push方法,很多时候都只是在数组后面添加数据。太多了记不住,就记得push,基本的小功能就可以完成了。
数组中移除数据
1、pop() 移除数组末尾的一个元素,并返回该元素。
let arr = [1, 2, 3];
let removed = arr.pop(); // removed 为 3,arr 变成 [1, 2]
2、shift() 移除数组开头的一个元素,并返回该元素。
let arr = [1, 2, 3];
let removed = arr.shift(); // removed 为 1,arr 变成 [2, 3]
3、splice() 同新增的 splice,参考前面的例子即可
4、filter() 根据条件过滤数组中的元素,生成一个新的数组。
let arr = [1, 2, 3, 4, 5];
let newArr = arr.filter(item => item !== 3); // 过滤掉 3,newArr 为 [1, 2, 4, 5]
5、delete() 可以使用 delete 删除数组中的某个元素,但会留下 undefined 项。
let arr = [1, 2, 3];
delete arr[1]; // arr 变成 [ 1, <1 empty item>, 3 ]
6、slice() 不会修改原数组,而是返回一个新数组
let arr = [1, 2, 3, 4, 5];
let newArr = arr.slice(1); // [2, 3, 4, 5]
let newArr = arr.slice(0, arr.length - 1); // [1, 2, 3, 4]
// 提取从索引 0 到索引 3(不包括 3)的元素
let newArr2 = arr.slice(0, 3); // 输出: [1, 2, 3]
start(可选): 提取起始处的索引(从 0 开始)。如果省略,则默认为 0。
end(可选): 提取终止处的索引(从 0 开始)。如果省略,则提取到数组末尾。
删除操作中使用比较多的是 splice,两种方式我用得多一点
/*删除指定id 的记录*/
let obj={id:xxx,...}
let itemIndex = 0;
arr.forEach(function (item, index) {
if (item.id == obj.id) {
itemIndex = index;
}
});
// 移除指定id 的数据
arr.splice(itemIndex, 1);
/*删除某一个元素*/
var index = items.indexOf(item);
items.splice(index, 1);
数组中数据值修改
1、使用索引直接修改
let arr = [1, 2, 3, 4, 5];
arr[2] = 10; // 修改索引为 2 的元素(原为 3),现在变为 10
console.log(arr); // 输出: [1, 2, 10, 4, 5]
修改数据,一般是绑定到v-model上。如果需要修改某一个,会直接传递item 对象,直接修改就行。如果是批量修改则会使用forEach循环。
查询数组中的数据
1、filter() 方法可以根据条件过滤数组,返回满足条件的所有元素的新数组
let arr = [5, 12, 8, 130, 44];
// 查询所有大于 10 的元素
let result = arr.filter(item => item > 10);
console.log(result); // 输出: [12, 130, 44]
2、find() 方法返回数组中第一个满足条件的元素,如果没有找到则返回 undefined。
let arr = [5, 12, 8, 130, 44];
// 查询第一个大于 10 的元素
let result = arr.find(item => item > 10);
console.log(result); // 输出: 12
3、some() 方法测试数组中是否至少有一个元素满足条件,如果是,则返回 true,否则返回 false。
let arr = [5, 12, 8, 130, 44];
// 检查是否有大于 10 的元素
let hasGreaterThanTen = arr.some(item => item > 10);
console.log(hasGreaterThanTen); // 输出: true
4、every() 方法测试数组中的所有元素是否都满足条件,如果是,则返回 true,否则返回 false。
let arr = [5, 12, 8, 130, 44];
// 检查所有元素是否都大于 0
let allGreaterThanZero = arr.every(item => item > 0);
console.log(allGreaterThanZero); // 输出: true
划重点,查询操作中最常用的是 filter()。
数组的批量操作和遍历
1. for 循环
let arr = [1, 2, 3, 4, 5];
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]); // 访问每个元素
}
2、$.each()
let arr = [1, 2, 3, 4];
$.each(arr, function(index, value) {
console.log("Index: " + index + ", Value: " + value);
});
3、forEach() 方法
let arr = [1, 2, 3, 4, 5];
arr.forEach((item, index, array) => {
console.log(item, index); // item: 当前元素,index: 当前索引,array: 原数组
});
$.each:
属于 jQuery 库,是一个函数,用于遍历数组或对象。
提供了一种一致的方式来处理数组和对象的遍历。forEach:
是 JavaScript 原生数组的方法(从 ECMAScript 5 开始引入)。
仅用于遍历数组,不支持对象。(简单操作:页面使用forEach报错,那么使用$.each就行了,还报错就检查jquery是否引入)
4、map() 方法对数组的每个元素执行指定的函数,返回一个新数组。适合需要对数组元素进行转换的场景。(和C# LINQ中Select一样)
let arr = [1, 2, 3, 4, 5];
let newArr = arr.map((item, index) => item * 2);
console.log(newArr); // 输出: [2, 4, 6, 8, 10]
5、filter() 方法创建一个新数组,包含所有满足条件的元素。(遍历所有元素进行判断)
let arr = [1, 2, 3, 4, 5];
let filteredArr = arr.filter(item => item > 2);
console.log(filteredArr); // 输出: [3, 4, 5]
6、 some() 前面已有介绍
7、every() 前面已有介绍
8、reduce() 方法对数组中的每个元素执行一个函数,最终计算出一个值。可以用于聚合操作。(求和函数)
let arr = [1, 2, 3, 4, 5];
let sum = arr.reduce((accumulator, current) => accumulator + current, 0);
console.log(sum); // 输出: 15
参数:
callback:要执行的函数,接收四个参数:
accumulator:累加器,累积回调的返回值。
current:当前元素。
index (可选):当前元素的索引。
array (可选):原数组。
initialValue (可选):指定初始值,如果未提供,则使用数组的第一个元素作为初始值。
vue 计算属性
前面介绍的数组操作只要引用了js都可以使用,下面介绍一下vue的计算属性(下面例子都是vue3的,vue2不适用)。
<script src="https://cdn.jsdelivr.net/npm/vue@3"></script>
<div id="app">
{{ message }}
</div>
<script>
const { createApp, computed } = Vue
var appObj = createApp({
data() {
return {
message: 'Hello Vue!',
count:0,
items:[1,2,3]
}
},
mounted(){
var self = this;
this.count = computed(()=>{
return self.items.reduce((sum, next) => { return sum + Number(next) }, 0);
})
}
})
app.mount('#app')
</script>
计算属性的好处,不管数据怎么变化,结果都是按公式计算出来的结果。例如上面count,不管数组中的值是增加了还是减少了,都是数组中的元素求和。修改值的时候就不用考虑重新计算,当然这个代码官网也有介绍,下面需要注意的是:
正确写法:
var obj1 = { sort: 0, start: 0, end: 0, count: 0 }
items.push(obj1)
// 最新加入的对象
var obj2 = items[(section.rules.length - 1)];
obj2.start = computed(() => {
var start = 0;
items.forEach((item) => {
if (item.sort < rule.sort) {
start += Number(item.count);
}
});
return start;
});
错误写法:
var obj1 = { sort: 0, start: 0, end: 0, count: 0 }
obj1.start = computed(() => {
var start = 0;
items.forEach((item) => {
if (item.sort < rule.sort) {
start += Number(item.count);
}
});
return start;
});
items.push(obj1)
为什么第二种是错误的写法呢?如果在普通 JavaScript 对象上直接使用 computed,Vue 的响应式系统无法自动追踪数据变化。
写法一种的obj1和obj2已经不是同一个类了
对于比较复杂,且数据结果层次很深的情况,要注意,计算属性不能再普通的js对象上添加,不会生效。现将数据添加到集合中,再监听计算。
扩展
使用 .split() 和 .join() 对字符串进行分割和重组。
let arr = ["我是", "小占", "时光"];
let str = arr.join(","); // 使用逗号 "," 作为分隔符
console.log(str); // 输出: "我是,小占,时光"
// 将字符串再次分割为数组
let splitArr = str.split(","); // 按逗号分割字符串
console.log(splitArr); // 输出: ["我是", "小占", "时光"]
三个点的使用(...)
... 是一种称为 扩展运算符(spread operator) 或 剩余参数(rest parameter) 的特殊语法,用于数组、对象、函数参数等多种场景。
个人总结就是将原来的数组和对象打散,变成新的数组和对象。
数组中使用
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
let combinedArr = [...arr1, ...arr2]; // 合并成一个新数组
console.log(combinedArr); // 输出: [1, 2, 3, 4, 5, 6]
let copiedArr = [...arr1]; // 创建 arr1 的浅拷贝
console.log(copiedArr); // 输出: [1, 2, 3]
对象中使用
let obj1 = { a: 1, b: 2 };
let copiedObj = { ...obj1 }; // 创建 obj1 的浅拷贝
console.log(copiedObj); // 输出: { a: 1, b: 2 }
对象中使用详细介绍一下,在vue中对象都是引用对象,讲一个对象赋值给了其他位置,这个对象其他位置发生改变,所有关联地方都会改变,这个时候就可以用... 得到新对象用于赋值,这样就可以互不干扰。
还有一个用法,可以在新对象中添加属性。假如api接口获得了一个集合,我要在集合中选择需要的数据,那么集合对象就需要有一个 ‘isChecked’属性用于绑定checkbox控件,后端不会带此属性给前端,那这个时候就可以使用... 来添加型属性。
// data 为ajax 获取到的后端数据
this.items = data.map(item => {
return { ...item, isChecked: false };
});