# 2021年05月技术日常
# 2021/05/27 周四
# nginx emerg bind to 0.0.0.0:80 failed <10013:
服务器非常卡顿,于是重启了服务器,重启后发现开启 nginx 时,报错 nginx emerg bind to 0.0.0.0:80 failed <10013:,这个错误一般是 80 端口被占用的问题,访问 http://127.0.0.1 看看 80 端口的情况,发现是 Java 服务,检查后发现 tomcat 开了,它占用了 80 端口,关掉 tomcat,再次运行就可以了。
参考:Nginx 错误处理方法: bind() to 0.0.0.0:80 failed (opens new window)
# 2021/05/16 周日
# MVC 与 MVVM 的理解
每个新技术的出现都有其历史原因,MVVM 也是如此。Vue 就是基于 MVVM 模式。那什么是 MVVM 构架模式,和 MVC 模式有什么区别呢?
在介绍 MVVM 之前,我们先需要了解什么是 MVC 模式
在 ajax 技术没有出现之前,html 页面中发送 http 请求会刷新整个页面。页面内容强依赖后端接口响应的内容,于是催生了 jsp、php、asp 等前后端都耦合在一起的技术。下面来看一个实例
<!-- login 页面 -->
<form action="/login" method="post">
<input type="text" name="um" placeholder="请输入账号"/>
<input type="password" name="pw" placeholder="请输入密码"/>
<button type="submit">登录</button>
</form>
登录页面中,点击登录,页面会重定向到 /login 发送 post 请求给后端,后端处理好后,返回登录成功后的 html 文本,浏览器拿到 html 文本渲染页面,这就完成了登录功能。
为了更好的组织代码、解耦各个部分,MVC 模式被引入到 web 开发,比如 Struts、Spring MVC
MVC 是一种经典的设计模式,是 Model-View-Controller 的缩写,即模型-视图-控制器。
- Model 模型 对应数据 Java 类
- View 视图 对应 JSP 页面显示(服务端渲染)
- Controller 控制器 Servlet,用于处理客户端 HTTP 请求、加工模型数据、再响应对应的视图(html)给客户端
MVC 在不同的平台有不同的实现,比如 iOS MVC、ASP.NET MVC、Spring MVC 等,细节参考:浅谈 MVC、MVP 和 MVVM 架构模式 (opens new window),这种构架模式的出现主要是为了解耦模块,将不同的功能模块分散到合适的位置中,提高开发维护效率。
假设前端有 MVC 模式,M 对应数据 js 对象、V 对应视图 html。如果遵循 MVC 模式,C 用来解耦 M、V。那 C (控制器)负责的事情就比较多,主要是处理事件流
- 绑定 UI 表单交互事件
- 接收到事件后处理业务逻辑
- 业务逻辑可能会请求接口获取/操作 M 数据
- 根据需要操作 dom 更新 V
由于 JS DOM API 在使用复杂度、兼容性方面需要较多的处理,像 jQuery 这种易于绑定事件,操作 dom 的框架开始流行起来,使用 jQuery 你不需要考虑 DOM API 兼容性、且操作 dom 简单,ajax 请求简单,提升了开发效率。
后面随着 ajax、移动端、小程序等多平台的兴起,前后端分离的概念出现了,写一套接口各个平台适用,而不是单纯的写接口渲染 web 页面。MVC 在后端 View 这一层不再是 UI,而是改为返回 JSON 模型数据,多端兼容。
当然前后端分离也有缺点,不利于 SEO,这也催生了服务端渲染 SSR 框架的产生。
MVVM 设计模式是 Model-View-ViewModel 的缩写,模型-视图-视图模型,是 MVC 的一种发展
viewModel 主要实现了双向绑定,一般在框架内部实现:
- 数据模型 M 和视图 V 通过 viewModel 绑定,只要修改了 数据模型 M ,viewModel 会自动帮你操作 dom 更新 视图。
- 视图 V 表单输入变化,viewModel 会自动将输入内容同步到 M 数据模型中
Vue 是类似 MVVM 模式的一种实现,使用 Vue 后,无需再手动操作 dom,只需改变数据即可。这样极大提升了开发效率,可以抽出更多的时间来关注业务逻辑。
参考:
- MVC,MVP 和 MVVM 的图示 - 阮一峰的网络日志 (opens new window)
- 什么是MVVM,MVC和MVVM的区别,MVVM框架VUE实现原理 (opens new window)
- Model–view–controller - Wikipedia (opens new window)
- Model–view–viewmodel - Wikipedia (opens new window)
# 2021/05/15 周六
# Spring Boot 解决跨域问题
根据 Spring 官网 Building a RESTful Web Service (opens new window) 写了一个测试接口,在使用时,需要让该接口支持跨域,理论上只要设置好对应的响应头、处理好 Options 请求预检就可以,但发现貌似没有处理 OPTIONS 请求的注解。需要一些特殊设置处理。
后面发现有一个非常简单的方法来允许跨域,就是使用 @CrossOrigin
注解,使用方法如下
package com.zuo11.demo;
import java.util.concurrent.atomic.AtomicLong;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.http.ResponseEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
@RestController
public class GreetingController {
private static final String template = "Hello, %s!";
private static final String templatePost = "Post, %s!";
private final AtomicLong counter = new AtomicLong();
@GetMapping("/greeting")
public Greeting greeting(@RequestParam(value = "name", defaultValue = "World") String name) {
return new Greeting(counter.incrementAndGet(), String.format(template, name));
}
// @PostMapping("/greeting") // @RequestBody
// public Greeting greetingPost(@RequestBody Greeting newGreeting) {
// return new Greeting(counter.incrementAndGet(), String.format(templatePost, newGreeting.getContent()));
// }
@CrossOrigin(origins = "*",maxAge = 3600) // 跨域注解
@PostMapping("/greeting") // @RequestBody
public ResponseEntity<Greeting> greetingPost(@RequestBody Greeting newGreeting) {
Greeting greeting = new Greeting(counter.incrementAndGet(), String.format(templatePost, newGreeting.getContent()));
HttpHeaders responseHeaders = new HttpHeaders();
// responseHeaders.set("Access-Control-Allow-Origin", "*");
// responseHeaders.set("Access-Control-Allow-Methods", "*");
// responseHeaders.set("Access-Control-Allow-Headers", "*");
return new ResponseEntity<>(greeting, responseHeaders, HttpStatus.OK);
}
}
完整 demo 参见:springboot-demo | github (opens new window)
# data 无法定义下划线开头的变量
在 vue 中,如果 data 中定义了以下划线开头的变量名,是无法获取到值的,一直是 undefined,为什么会这样呢?
在官方文档 API 中有搜索 data 可以查到: 以 _ 或 $ 开头的 property 不会被 Vue 实例代理,因为它们可能和 Vue 内置的 property、API 方法冲突
另外,如果项目中开启了 ESLint 会自动提示该错误 "vue/no-reserved-keys" 不能使用保留关键字,如下图:
因此 ESLint 比我们想象的更加实用,它不仅仅只是单纯的 JS 语法检查。
# mac 其他文件占用很多,使用柠檬清理
mac 存储空间里面的其他文件占用较多,它主要是一些碎片文件、缓存等,一般不通过工具软件很难清理。如下图:
mac 清理工具中,CleanMyMac 算是比较知名的一个,但它是收费的,这里介绍一款国产免费的清理工具 - 腾讯柠檬清理 (opens new window)
关于柠檬清理与其他同类产品的对比,官方写了一遍总结,可以看看 腾讯柠檬清理,真的比CleanMyMac好用么? (opens new window)
# 2021/05/10 周一
# if 或函数嵌套层级较多时通过改变代码组织方式减少代码嵌套
在 if 或函数嵌套层级较多,代码会逐渐变的不好理解、维护。一般可以通过改变代码组织方式,来减少代码嵌套层级,这里主要介绍下面几种方法:
- 将代码块按功能块封装成函数,减少函数内代码的嵌套层级
- if 优化,逻辑假时 true,减少 if 中包含大量代码的情况
- 使用三元运算符减少 if 层级
- 使用逻辑运算符减少 if 层级
- 使用策略模式减少 if、else 层级
来看一个简单的例子,在 vue 实例中,watch 深度监听 userInfo 对象,里面的处理函数中有 if,有数组遍历函数,层级会比较深
<script>
export default {
data() {
return {
userInfo: {}
};
},
watch: {
userInfo: {
handler(value) {
if (value.name) {
let indexMap = this.arrList.reduce(() => {
console.log("test1");
// 可能存在 if 或函数嵌套
});
this.list.forEach(item => {
if (indexMap[item.index]) {
console.log("test2");
}
});
}
},
deep: true
}
}
};
</script>
下面来具体看减少代码嵌套层级的几种思路:
- 将代码块按功能块封装成函数
if (condition) {
let indexMap = this.arrList.reduce(() => {
console.log("test1");
// 可能存在的 if 或函数嵌套
});
}
// 可以优化为下面这种,这样就避免了函数代码直接嵌套在 reduce 方法内部
if (condition) {
const reduceFunc = () => {
console.log("test1");
// 可能存在的 if 或函数嵌套
}
let indexMap = this.arrList.reduce(reduceFunc);
}
- if 优化,逻辑假时 true,减少 if 中包含大量代码的情况
function func() {
if (condition) {
// 一大段代码
// 可能存在 if 等嵌套层级的代码
}
}
// 可以优化为下面这种,这样可以将一大段代码从 if (condition) 嵌套中解放出来
function func() {
if (!condition) {
return false // 条件不满足时结束函数
}
// 一大段代码
// 可能存在 if 等嵌套层级的代码
}
- 三元运算符
function func() {
let a = '1'
if (b) {
a = '2'
}
}
// 可以优化为
function func() {
let a = b ? '2' : '1'
}
- 逻辑运算符
function func() {
if (callback) {
callback()
}
}
// 可以优化为
function func() {
callback && callback()
}
- 策略模式
function func() {
if (type === 'a') {
// 执行内容 1
} else if (type === 'b') {
// 执行内容 2
} else if (type === 'c') {
// 执行内容 3
}
// 后续代码
}
// 使用策略模式优化
function func() {
let handlerA = () => { console.log('执行内容1') }
let handlerB = () => { console.log('执行内容2') }
let handlerC = () => { console.log('执行内容3') }
let handlerMap = {
a: handlerA,
b: handlerB,
c: handlerC
}
handlerMap[type] && handlerMap[type]()
// 后续代码
}
# 2021/05/04 周二
# Vscode Live Server 插件 Open With Live Server 没反应,无法打开浏览器
在调试单个 html 文件时,可以使用 Live Server 插件,快速开启 http 服务,并打开当前页面。
但目前这个插件可能会出现没反应,打不开的情况,可以使用 Preview on Web Server 来代替
使用方法基本一致,右键可以选择浏览器打开,或者在 vscode 侧边栏预览
# v-if 和 v-show 生命周期钩子函数有什么不同
在 vue 中我们知道 v-if 和 v-show 都可以用来控制内容的显示与隐藏,他们的区别是
- v-if 是惰性加载,只有为 true 时,才真正渲染,否则页面是不存在该元素的。为 false 时,直接从 dom 移除。
- v-show 是根据 css display 属性来显示和隐藏组件的
那么他们的生命周期钩子函数有什么区别呢?
- v-show 控制隐藏或显示的组件,一进入就立即加载,执行 beforeCreate、created、beforeMount、mounted,中间切换显示和隐藏不会触发钩子函数
- v-if 只有为 true 时,才会正常执行加载的钩子函数,否则不会加载。当切换时,实时挂载(created/mounted)、卸载组件(destoryed)。
使用场景:v-if 适合在 true、false 切换不频繁的场景,当为 false 时,可以减少初次渲染时间。如果切换很频繁 v-if 会不断的挂载、卸载组件,会加大开销,这时使用 v-show 就比较好,尽管它的初始开销会大一点。