# 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,再次运行就可以了。

80_port.png

参考: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.png

MVC 是一种经典的设计模式,是 Model-View-Controller 的缩写,即模型-视图-控制器。

  • Model 模型 对应数据 Java 类
  • View 视图 对应 JSP 页面显示(服务端渲染)
  • Controller 控制器 Servlet,用于处理客户端 HTTP 请求、加工模型数据、再响应对应的视图(html)给客户端

structs_mvc.png

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 的一种发展

mvvm.png

viewModel 主要实现了双向绑定,一般在框架内部实现:

  • 数据模型 M 和视图 V 通过 viewModel 绑定,只要修改了 数据模型 M ,viewModel 会自动帮你操作 dom 更新 视图。
  • 视图 V 表单输入变化,viewModel 会自动将输入内容同步到 M 数据模型中

Vue 是类似 MVVM 模式的一种实现,使用 Vue 后,无需再手动操作 dom,只需改变数据即可。这样极大提升了开发效率,可以抽出更多的时间来关注业务逻辑。

参考:

# 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 方法冲突

vue_data_property.png

另外,如果项目中开启了 ESLint 会自动提示该错误 "vue/no-reserved-keys" 不能使用保留关键字,如下图:

data_reserved_eslint.png

因此 ESLint 比我们想象的更加实用,它不仅仅只是单纯的 JS 语法检查。

# mac 其他文件占用很多,使用柠檬清理

mac 存储空间里面的其他文件占用较多,它主要是一些碎片文件、缓存等,一般不通过工具软件很难清理。如下图:

macos_disk_other.png

mac 清理工具中,CleanMyMac 算是比较知名的一个,但它是收费的,这里介绍一款国产免费的清理工具 - 腾讯柠檬清理 (opens new window)

ningmeng_clear.png.png

关于柠檬清理与其他同类产品的对比,官方写了一遍总结,可以看看 腾讯柠檬清理,真的比CleanMyMac好用么? (opens new window)

# 2021/05/10 周一

# if 或函数嵌套层级较多时通过改变代码组织方式减少代码嵌套

在 if 或函数嵌套层级较多,代码会逐渐变的不好理解、维护。一般可以通过改变代码组织方式,来减少代码嵌套层级,这里主要介绍下面几种方法:

  1. 将代码块按功能块封装成函数,减少函数内代码的嵌套层级
  2. if 优化,逻辑假时 true,减少 if 中包含大量代码的情况
  3. 使用三元运算符减少 if 层级
  4. 使用逻辑运算符减少 if 层级
  5. 使用策略模式减少 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>

下面来具体看减少代码嵌套层级的几种思路:

  1. 将代码块按功能块封装成函数
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);
}
  1. if 优化,逻辑假时 true,减少 if 中包含大量代码的情况
function func() {
  if (condition) {
    // 一大段代码
    // 可能存在 if 等嵌套层级的代码
  }
}
// 可以优化为下面这种,这样可以将一大段代码从 if (condition) 嵌套中解放出来
function func() {
  if (!condition) {
    return false // 条件不满足时结束函数
  }
  // 一大段代码
  // 可能存在 if 等嵌套层级的代码
}
  1. 三元运算符
function func() {
  let a = '1'
  if (b) {
    a = '2'
  }
}
// 可以优化为
function func() {
  let a = b ? '2' : '1'
}
  1. 逻辑运算符
function func() {
  if (callback) {
    callback()
  }
}
// 可以优化为
function func() {
  callback && callback()
}
  1. 策略模式
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 服务,并打开当前页面。

live-server.png

但目前这个插件可能会出现没反应,打不开的情况,可以使用 Preview on Web Server 来代替

preview-on-server.png

使用方法基本一致,右键可以选择浏览器打开,或者在 vscode 侧边栏预览

launch-on-browser.png

# v-if 和 v-show 生命周期钩子函数有什么不同

在 vue 中我们知道 v-if 和 v-show 都可以用来控制内容的显示与隐藏,他们的区别是

  1. v-if 是惰性加载,只有为 true 时,才真正渲染,否则页面是不存在该元素的。为 false 时,直接从 dom 移除。
  2. v-show 是根据 css display 属性来显示和隐藏组件的

v-show.gif

那么他们的生命周期钩子函数有什么区别呢?

  1. v-show 控制隐藏或显示的组件,一进入就立即加载,执行 beforeCreate、created、beforeMount、mounted,中间切换显示和隐藏不会触发钩子函数
  2. v-if 只有为 true 时,才会正常执行加载的钩子函数,否则不会加载。当切换时,实时挂载(created/mounted)、卸载组件(destoryed)。

使用场景:v-if 适合在 true、false 切换不频繁的场景,当为 false 时,可以减少初次渲染时间。如果切换很频繁 v-if 会不断的挂载、卸载组件,会加大开销,这时使用 v-show 就比较好,尽管它的初始开销会大一点。

上次更新: 2021/7/16 00:41:42