2024年3月

一个兜兜转转,从“北深”回到三线城市的小码农,热爱生活,热爱技术,在这里和大家分享一个技术人员的点点滴滴。欢迎大家关注我的微信公众号:果冻想

前言

不得不吐槽,学个JS,这个概念也太多了,但是这些概念你不懂吧,代码你都看不懂,你都寸步难行。好吧,这又遇到了作用域方面的知识盲区,然后发现,又牵扯出了自执行函数。那又能咋整,为了这点破工资,学呗。

适可而止,浅尝辄止。

JS作用域

作用域指的是一个变量的作用范围。我们定义的变量它只能在自己的作用域内有效,超出了自己的作用域,变量就不起作用了。但是,JavaScript这门语言很活,如果你不搞懂它的作用域原理,你很可能在不知不觉中被坑了。

在JavaScript中,主要有三种作用域:

  1. 全局作用域:在所有函数外部定义的变量、函数和对象,可以被代码中的所有部分访问。
  2. 函数作用域:在函数内部定义的变量、函数和对象,只能在函数内部访问。
  3. 块级作用域:在块级作用域(使用 let 或 const 关键字定义的变量)中定义的变量,只能在该块内访问。

下面通过不同的示例代码来演示这几种作用域,以便更好的理解:

// 全局作用域
var a = "global_var_a";
console.log("全局作用域中访问:" + a); // 全局作用域中访问:global_var_a

if (true) {
	console.log("在判断语句中访问:" + a); // 在判断语句中访问:global_var_a
}

function getA() {
	console.log("在函数中访问:" + a); // 在函数中访问:global_var_a
}

getA()

// ==================================================================================
// 函数作用域
var a = "global_var_a";
console.log("全局作用域访问:" + a); // 全局作用域访问:global_var_a

if (true) {
	var a = "block_var_a"; // 与全局变量同名
	console.log("在判断语句中访问:" + a); // 在判断语句中访问:block_var_a
}

function getA() {
	var a = "func_var_a"; // 与全局变量同名
	var b = "func_var_b";
	console.log("在函数中访问:" + a); // 在函数中访问:func_var_a
}

getA()
console.log("在全局作用域中访问:" + a); // 在全局作用域中访问:block_var_a;由于允许变量重复声明,导致变量被覆盖
console.log("在全局作用域中访问:" + b); // Uncaught ReferenceError: b is not defined

// ==================================================================================
// 块作用域
var a = "global_var_a";
const b = "global_const_b";

console.log("全局作用域中访问:" + a); // 全局作用域中访问:global_var_a
console.log("全局作用域中访问:" + b); // 全局作用域中访问:global_const_b

if (true) {
	let a = "block_let_a";
	const b = "block_const_b";
	console.log("在判断语句中访问:" + a); // 在判断语句中访问:block_let_a
	console.log("在判断语句中访问:" + b); // 在判断语句中访问:block_const_b
	
	let c = "block_let_c";
    const d = "block_let_d";
}

function getA() {
	let a = "func_let_a";
	const b = "func_const_b";
	console.log("在函数中访问:" + a); // 在函数中访问:func_let_a
	console.log("在函数中访问:" + b); // 在函数中访问:func_const_b

	let e = "func_let_e";
    const f = "func_const_f";
}

getA()
console.log("全局作用域中访问:" + a); // 全局作用域中访问:global_var_a
console.log("全局作用域中访问:" + b); // 全局作用域中访问:global_const_b
// console.log("全局作用域中访问:" + c); Uncaught ReferenceError: c is not defined
// console.log("全局作用域中访问:" + d); Uncaught ReferenceError: d is not defined
// console.log("全局作用域中访问:" + e); Uncaught ReferenceError: e is not defined
// console.log("全局作用域中访问:" + f); Uncaught ReferenceError: f is not defined

这里顺便多说一嘴,关于var定义变量时的变量提升问题,看下面这段代码:

if (false) {
	var a = "abc";
	console.log(a);
} else {
	console.log(a);
}
console.log(a);

我们执行上面的代码,理应报
Uncaught ReferenceError: a is not defined
这个错误的,但是由于变量提升问题,这段代码是不会报错的,但是逻辑是有问题的。

JS自执行函数

说完JS的作用域问题,再来说说自执行函数。它的定义如下:

自执行函数是指定义后立即执行的函数,它可以被用来创建一个私有作用域。自执行函数的作用域只在函数内部有效,可以用来隐藏变量和函数,避免全局命名冲突,保持代码的整洁性和可维护性。它可以用来创建私有作用域、实现模块化、简化代码等等,非常灵活和实用。

自执行函数有三种写法:

(function("参数") {"函数方法";})("给参数传的值")
(function("参数") {"函数方法";}("给参数传的值"))
!function("参数") {"函数方法";}("给参数传的值") // ! 可以换作 void 或其他运算符(比如 +,-,= 等,都能起到立即执行的作用)

因为全局变量很容易引起一些Bug,所以使用自执行函数来实现模块化,内部变量和函数对外部不可见,只有暴露出去的接口可以被外部访问。看下面这段代码。

var myModule = (function(){
	var privateVar ='私有变量';

	function privateFunc(){
		console.log('私有函数');
	}

	return {
		publicFunc: function() {
			console.log('公有函数');
		}
	};
})();

myModule.publicFunc(); // "公有函数"
console.log(myModule.privateVar); // undefined
myModule.privateFunc(); // Uncaught TypeError: myModule.privateFunc is not a function

在上面的代码中,自执行函数返回一个包含公有函数publicFunc的对象,这个函数可以被外部访问,而私有变量privateVar和私有函数privateFunc对外部不可见。这样可以有效地隔离代码,避免全局变量污染,提高代码的可维护性和重用性。大部分开元的JavaScript模块就是以这种方式提供的。

总结

每天一个小知识点,每天进步一点,与君共勉。

一、简介

本文介绍了在
vscode
中使用
cmake
工具时,如何传递参数给编译目标的方法。
前提:使用
vscode
+
cmake
编译
C/C++程序

二、方法


.vscode/
目录下新建
settings.json
文件,并将待传底的参数写在
cmake.debugConfig
里。
下面介绍了一个示例,将参数
first_arg
,
second-arg

third arg
传递给程序(此处需要注意,
third arg
中间虽然存在空格,但是仍然被视作一个参数):

settings.json
文件内容为:

{
    "cmake.debugConfig": {
        "args": [
        "first_arg",
        "second-arg",
        "third arg"
        ]
    }
}

main.cpp
文件内容为:

#include <iostream>
int main(int argc, char **argv)
{
    std::cout << "总参数个数为:" << argc << "\n";
    for (int i = 0; i < argc; i++)
    {
        std::cout << "第" << i + 1 << "个参数为:" << argv[i] << "\n";
    }
    return 0;
}

CMakeLists.txt
文件内容为:

cmake_minimum_required(VERSION 3.10)
project(Helloworld)
add_executable( Helloworld main.cpp )

然后点击下方状态栏里的
Build
按钮(下图中第一个红框),编译程序,再点击
debug
(下图中第二个红框)或者
launch
按钮(下图中第三个红框)运行程序,如下图:
vscode状态栏

程序运行的输出如下:

总参数个数为:4
第1个参数为:/home/Helloworld/build/Helloworld
第2个参数为:first_arg
第3个参数为:second-arg
第4个参数为:three arg

可以看到,程序成功的识别出了传递的参数
first_arg
,
second-arg

third arg

三、参考

[1].
Passing command line arguments to a debug target #121

机器视觉——角点检测

什么是角点检测

在几何学里,我们会看到各种各样的三角形、多边形等,它们都有一个显著的特征:包含了角点信息。比如在三角形里,我们有三个角;在矩形里,我们有四个角。我们将找到这些图像特征的过程称为
特征提取 (Feature Extraction)
,我们之前所接触的Canny边缘检测也是特征提取的一种,其提取的是边缘特征,而我们这次所需要的是提取角点特征。现在,我们有一张图片需要去处理,要求:将图中书本的四个角点提取出来,以方便后续做透视变换等操作。这应该怎么样处理?


样例图片

我们先对图像进行预处理操作


通过前面一系列知识的学习,我们可以将该RGB图片转换成灰度图像(本人比较懒,不想写代码,就用ps做处理了

您是否曾经访问过一个网站,它需要很长时间加载,最终你敲击 F5 重新加载页面。

即使用户刷新了浏览器取消了原始请求,而对于服务器来说,API也不会知道它正在计算的值将在结束时被丢弃,刷新五次,服务器将触发 5 个请求。

为了解决这个问题,ASP.NET Core 为 Web 服务器提供了一种机制,就是CancellationToken.

用户取消请求时,你可以使用HttpContext.RequestAborted访问,您也可以使用依赖注入将其自动注入到您的操作中。

长时间运行的任务请求

现在我们假设您有一个 API 操作,在向用户发送响应之前可能需要一些时间才能完成。

在处理该操作时,用户可以直接取消请求,或刷新页面(这会有效地取消原始请求,并启动新请求)。

[HttpGet(Name = "get")]public async Task<string>GetAsync()
{
try{
_logger.LogInformation(
"request in");await Task.Delay(5 * 1000);
_logger.LogInformation(
"request end");
}
catch(Exception ex)
{
_logger.LogInformation(
"request ex");
}
return "ok";
}

如果用户在请求中途刷新浏览器,那么浏览器永远不会收到第一个请求的响应,但在server端可以看到,操作方法执行完成两次。

这是否是正确将取决于您的应用程序。

如果请求修改某些业务的状态,那么您可能不希望在方法中途停止执行。如果请求没有副作用,那么您可能希望尽快停止(可能很昂贵)操作。

用户取消请求时,你可以使用HttpContext.RequestAborted访问,您也可以使用依赖注入将其自动注入到您的操作中。

CancellationTokens取消不必要的请求

以下代码显示了如何通过将 CancellationTokenSource 注入到操作方法中,并通过其取消不必要的操作。

[HttpGet(Name = "get")]public async Task<string>GetAsync(CancellationToken cancellationToken)
{
try{
_logger.LogInformation(
"request in");await Task.Delay(5 * 1000,cancellationToken);
_logger.LogInformation(
"request end");
}
catch(Exception ex)
{
_logger.LogInformation(
"request ex");
}
return "ok";
}

通过这个改变,我们可以再次测试我们的场景。

我们发出一个初始请求,然后我们重新加载页面。正如您从下面的日志中看到的,第一个请求不会继续执行。

用户刷新浏览器取消请求后不久,原始请求就会中止,并TaskCancelledException通过 API 过滤器管道传播回来,并备份中间件管道。

根据您的场景,您可能能够依靠此类框架方法来检查 的状态CancellationToken,或者您可能需要自己监视取消请求。

过滤器捕获异常

您可以通过以上try catch 捕获,或者通过一个过滤器统一监视此异常。

public classOperationCancelledExceptionFilter : ExceptionFilterAttribute
{
private readonlyILogger _logger;publicOperationCancelledExceptionFilter(ILoggerFactory loggerFactory)
{
_logger
= loggerFactory.CreateLogger<OperationCancelledExceptionFilter>();
}
public override voidOnException(ExceptionContext context)
{
if (context.Exception isOperationCanceledException)
{
_logger.LogInformation(
"Request was cancelled");
context.ExceptionHandled
= true;
context.Result
= new StatusCodeResult(400);
}
}
}


builder.Services.AddControllers(options
=>{
options.Filters.Add
<OperationCancelledExceptionFilter>();
});

写在前面

对于初学者来说,困难的不是理论知识,而是如何在程序中具体实现。

现在的项目基本上都是前后端分离的项目,如何打通前后端,接收前端传过来的参数呢?

废话不多说,这篇文章就来说一说接收前端参数的具体操作

一、获取路径中的值

1.1 核心代码

@GetMapping("/getArticle/{id}")
public Article getArticle(@PathVariable("id") Long id){
    System.out.println("获取到路径中的值为:"+id);
    return parmsService.findArticleById(id);
}

1.2 测试结果

在访问“
http://localhost:8080/article/1”
时,程序会自动将URL中的模板变量{id} 绑定到通过
@PathVariable
注解的同名参数上,所以程序中可以获取到id=1

获取路径中的值

打印结果

二、获取路径中的参数

2.1 核心代码

@RequestMapping(value = "/addUser/",method = RequestMethod.POST)
public String addUser(String  name,String sex,int age){
    System.out.println("获取到参数中的值为:"+name+","+sex+","+age);
    return "添加文章成功";
}

2.2 测试结果

这里的参数和上面的不一样,这里的参数是通过“=”隔开的,多个参数使用&分割。 例如测试例子中的“
http://localhost:8080/?name=小凡&sex=男&age=18”

postman测试结果

idea打印值

三、通过Bean接收对象参数

3.1 核心代码

//①User
@Data
public class User {
    private  Integer id;
    private  String name;
    private String sex;
    private  Integer age;
}


//接收参数方法
@PostMapping("/addnewUser/")
public String addUser(User user){
    System.out.println(user);
    return  "添加用户成功";
}

3.2 测试结果

这里直接将前端传过来的参数映射到
User
对象上

postman测试结果

idea打印结果

四、HttpServletRequest接收参数

4.1 核心代码

@PostMapping("/addBook/")
public String addBook(HttpServletRequest request){
    String bookName = request.getParameter("bookName");
    String author = request.getParameter("author");
    System.out.println("获取到参数中的值为:"+bookName+","+author);
    return "添加书籍成功";
}

4.2 测试结果

postman测试结果

idea测试结果

五、用@RequestParam接收参数

5.1 核心代码

@GetMapping("/getParams")
public String getParms(@RequestParam("param1") String  param1,@RequestParam("param2") String param2){
    System.out.println("获取到参数中的值为:"+param1+","+param2);
    return "Param1: " + param1 + ", parm2: " + param2;
}

5.2 测试结果

postman测试结果

idea测试结果

六、 用@RequestBody 接收json数据

6.1 核心代码

//① 定义User实体对象
@Data
public class User {
    private  Integer id;
    private  String name;
    private String sex;
    private  Integer age;
}
//② 接收参数
@PostMapping("/addUsers")
public List<User> addUsers(@RequestBody List<User> users){
    System.out.println(""+users);
    return users;
}

6.2 测试结果

postman测试结果

idea打印结果

七、接收文件 MultipartFile

7.1 核心代码

@PostMapping("/singleFileUpload")
public String singleFileUpload(@RequestParam("file") MultipartFile file){
    //文件存储路径
    final String BASE_PATH = "E:\\xiezhrspace\\parm-demo\\";
    if(file.isEmpty()){
        return "文件为空";
    }
    //获取文件名
    String fileName = file.getOriginalFilename();

    try {
        //新建一个文件路径
        File dest = new File(BASE_PATH + fileName);
        //判断路径是否存在,如果不存在就创建一个
        if(!dest.getParentFile().exists()){
            dest.getParentFile().mkdirs();
        }
        //将上传文件保存到一个目标文件当中
        file.transferTo(dest);

    } catch (IOException e) {
        e.printStackTrace();
    }
    return "上传成功";
}

7.2 测试结果

postman测试结果

文件上传成功

以上就是本期全部内容,希望对您有所帮助,我们下期再见(●'◡'●)