你不知道的JavaScript--Item4 基本类型和基本包装类型(引用类型)

深入理解JavaScript数据类型与对象生命周期
本文详细解析JavaScript中的基本类型与引用类型的区别,包括生命周期、特点及操作方法,并对比基本类型与引用类型的存储方式与比较机制。通过具体代码示例,阐述基本类型与引用类型在赋值、方法调用、属性添加等方面的特性。

1、基本类型和引用类型

基本的数据类型有5个:undefined,boolean,number,string,null

typeof null;   //"object"
typeof undefined;  //"undefined"
typeof 1;  //"number"
typeof false  //"boolean"
typeof "1"  //"string"

(令人困惑的是,对null类型进行typeof操作得到的结果为“object”,然而,ECMAScript标准描述其为一个独特的类型。)

为了便于操作基本类型值,ECMAScript还提供了三个特殊的引用类型:Boolean、Number和String,标准库提供了构造函数来封装布尔值、数字和字符串作为对象。这些类型与其他引用类型相似,同时也具有与各自的基本包装类型相应的特殊行为。实际上,每当读取一个基本类型值时,后台就会创建一个对应的基本包装类型的对象,从而让我们能够调用一些方法来操作这些数据。

var s1 = "some text";
var s2 = s1.substring(2);
var s3 = new String("some text");

但是不同于原始的字符串,String对象是一个真正的对象。

typeof s1;  //"string"
typeof s3;  //"object"

这个例子中的变量s1包含一个字符串,字符串当然是基本类型值。而下一行调用了s1的substring()方法,并将返回的结果保存在s2中。我们知道,基本类型值不是对象,因此从逻辑上讲,它们不应该有方法(但是它们确实有方法)。其实,为了让我们实现这种直观的操作,后台已经自动完成了一系列的处理。当第二行代码访问s1时,访问过程处于一种读取模式,也就是从内存中读取这个字符串的值。而在读取模式中访问字符串时,后台都会自动完成下列处理:

(1)创建String类型的一个实例。

(2)在实例上调用指定方法。

(3)销毁这个实例。

可以使用以下代码表示:

var s1 = new String("some text");
var s2 = s1.substring(2);
s1 = null;

经过此番处理,基本的字符串值就变得跟对象一样了。而且,上面三个步骤也适用于Boolean和Number类型对应的布尔值和数字值。

2、生命周期

引用类型与基本包装类型的主要区别就是对象的生命周期。使用new操作符创建的引用类型的实例,在执行流离开当前作用域之前都一直保存在内存中。而自动创建的基本包装类型的对象,只存在于这一行代码的执行期(瞬间),然后立即销毁。这就意味着我们不能在运行时为属性添加属性和方法。

var s1 = "some text";
s1.color = "red";
alert(s1.color); //undefined

当然,可以显示调用Boolean、Number和String来创建基本包装类型的对象,不过,不帮不建议这么做。对基本包装类型的实例调用typeof会返回”object”,而且所有基本包装类型的对象都会被转化为布尔值true。。

var obj = new Object("some text");
alert(obj instanceof String) //true

值得注意的是,使用new调用基本包装类型的构造函数,与直接调用同名的转型函数是不一样的。

var value = "25";
var number = Number(value);//转型函数
alert(typeof number) //number

var obj = new Number(var); //构造函数
alert(typeof obj) //object

3.基本类型特点

1.基本类型的值是不可变得:

任何方法都无法改变一个基本类型的值,比如一个字符串:

var name = 'jozo';
name.toUpperCase(); // 输出 'JOZO'
console.log(name); // 输出  'jozo'

会发现原始的name并未发生改变,而是调用了toUpperCase()方法后返回的是一个新的字符串。
再来看个:

var person = 'jozo';
person.age = 22;
person.method = function(){//...};

console.log(person.age); // undefined
console.log(person.method); // undefined

通过上面代码可知,我们不能给基本类型添加属性和方法,再次说明基本类型时不可变得;

2.基本类型的比较是值的比较:

只有在它们的值相等的时候它们才相等。
但你可能会这样:

var a = 1;
var b = true;
console.log(a == b);//true

它们不是相等吗?其实这是类型转换和 == 运算符的知识了,也就是说在用==比较两个不同类型的变量时会进行一些类型转换。像上面的比较先会把true转换为数字1再和数字1进行比较,结果就是true了。 这是当比较的两个值的类型不同的时候==运算符会进行类型转换,但是当两个值的类型相同的时候,即使是==也相当于是===。

var a = 'jozo';
var b = 'jozo';
console.log(a === b);//true

3.基本类型的变量是存放在栈区的(栈区指内存里的栈内存)

假如有以下几个基本类型的变量:

var name = 'jozo';
var city = 'guangzhou';
var age = 22;

那么它的存储结构如下图:

这里写图片描述

栈区包括了 变量的标识符和变量的值。

4.引用类型特点

引用类型会比较好玩有趣一些。

javascript中除了上面的基本类型(number,string,boolean,null,undefined)之外就是引用类型了,也可以说是就是对象了。对象是属性和方法的集合。也就是说引用类型可以拥有属性和方法,属性又可以包含基本类型和引用类型。来看看引用类型的一些特性:

1.引用类型的值是可变的

我们可为为引用类型添加属性和方法,也可以删除其属性和方法,如:

var person = {};//创建个控对象 --引用类型
person.name = 'jozo';
person.age = 22;
person.sayName = function(){console.log(person.name);} 
person.sayName();// 'jozo'

delete person.name; //删除person对象的name属性
person.sayName(); // undefined

上面代码说明引用类型可以拥有属性和方法,并且是可以动态改变的。

2.引用类型的值是同时保存在栈内存和堆内存中的对象

javascript和其他语言不同,其不允许直接访问内存中的位置,也就是说不能直接操作对象的内存空间,那我们操作啥呢? 实际上,是操作对象的引用,所以引用类型的值是按引用访问的。

准确地说,引用类型的存储需要内存的栈区和堆区(堆区是指内存里的堆内存)共同完成,栈区内存保存变量标识符和指向堆内存中该对象的指针,也可以说是该对象在堆内存的地址。
假如有以下几个对象:

var person1 = {name:'jozo'};
var person2 = {name:'xiaom'};
var person3 = {name:'xiaoq'};

则这三个对象的在内存中保存的情况如下图:

这里写图片描述

3.引用类型的比较是引用的比较

var person1 = '{}';
var person2 = '{}';
console.log(person1 == person2); // true

上面讲基本类型的比较的时候提到了当两个比较值的类型相同的时候,相当于是用 === ,所以输出是true了。再看看:

var person1 = {};
var person2 = {};
console.log(person1 == person2); // false

可能你已经看出破绽了,上面比较的是两个字符串,而下面比较的是两个对象,为什么长的一模一样的对象就不相等了呢?

别忘了,引用类型时按引用访问的,换句话说就是比较两个对象的堆内存中的地址是否相同,那很明显,person1和person2在堆内存中地址是不同的:

这里写图片描述

所以这两个是完全不同的对象,所以返回false;

5.简单赋值

在从一个变量向另一个变量赋值基本类型时,会在该变量上创建一个新值,然后再把该值复制到为新变量分配的位置上:

var a = 10;
var b = a;

a ++ ;
console.log(a); // 11
console.log(b); // 10

此时,a中保存的值为 10 ,当使用 a 来初始化 b 时,b 中保存的值也为10,但b中的10与a中的是完全独立的,该值只是a中的值的一个副本,此后,这两个变量可以参加任何操作而相互不受影响。

这里写图片描述

也就是说基本类型在赋值操作后,两个变量是相互不受影响的。

6.对象引用

当从一个变量向另一个变量赋值引用类型的值时,同样也会将存储在变量中的对象的值复制一份放到为新变量分配的空间中。前面讲引用类型的时候提到,保存在变量中的是对象在堆内存中的地址,所以,与简单赋值不同,这个值的副本实际上是一个指针,而这个指针指向存储在堆内存的一个对象。那么赋值操作后,两个变量都保存了同一个对象地址,则这两个变量指向了同一个对象。因此,改变其中任何一个变量,都会相互影响:

var a = {}; // a保存了一个空对象的实例
var b = a;  // a和b都指向了这个空对象

a.name = 'jozo';
console.log(a.name); // 'jozo'
console.log(b.name); // 'jozo'

b.age = 22;
console.log(b.age);// 22
console.log(a.age);// 22

console.log(a == b);// true

它们的关系如下图:

这里写图片描述

因此,引用类型的赋值其实是对象保存在栈区地址指针的赋值,因此两个变量指向同一个对象,任何的操作都会相互影响。

<think>我们正在解决RuoYi框架中请求参数类型匹配的问题。根据引用[1]引用[4],问题通常出现在前端传递的参数类型与后端接口期望的参数类型一致。例如,后端期望一个Long[]类型的参数,而前端可能传递了其他类型(如字符串数组)或者参数名匹配。 解决方案步骤: 1. **确认后端接口期望的参数类型**: 查看后端Controller方法的参数声明,特别是使用`@RequestParam`、`@PathVariable`或`@RequestBody`注解的参数。例如,引用[4]中有一个方法: ```java @GetMapping(value = { "/", "/{orderNo}" }) public AjaxResult getInfo(@PathVariable(value = "orderNo", required = false) String orderNo) { // ... } ``` 这里,`orderNo`被声明为路径变量,类型为`String`。如果另一个接口期望一个`Long`类型的参数,那么前端必须传递一个可以转换为`Long`的字符串(如"123"),否则会抛出类型匹配异常。 2. **检查前端传递的参数**: 使用浏览器开发者工具(Network标签)查看前端发送的请求参数。确认参数名值是否符合后端要求。例如,如果后端期望一个名为`ids`的`Long[]`,那么前端应该传递一个数组,数组中的每个元素都是数字(而是字符串形式的数字,除非后端能自动转换)。 3. **常见问题及修复**: - **问题1:前端传递字符串,后端期望Long**: 例如,前端传递`"123"`(字符串)给一个期望`Long`类型的参数。这种情况,Spring MVC通常可以自动转换,但如果传递的是非数字字符串(如"abc")则会出错。 - 修复:确保前端传递的是有效的数字字符串,或者在前端转换为数字类型(如果使用JSON,则直接传递数字类型)。 - **问题2:数组类型匹配**: 如引用[1]所述,后端期望`Long[]`,但前端传递了其他类型(比如字符串数组)。虽然Spring MVC可以尝试将字符串数组转换为Long数组(如果每个字符串都是数字),但如果数组元素包含非数字字符串,则转换失败。 - 修复:确保前端传递的数组元素都是数字(或者可以转换为Long的字符串)。或者,在后端将参数类型改为`String[]`,然后在服务层进行转换(这样更可控,可以处理转换异常)。 - **问题3:参数名匹配**: 前端传递的参数名与后端接口定义的参数名一致。例如,后端期望`giftId`,但前端传递了`giftID`(大小写一致)或`gift_id`。 - 修复:统一前后端的参数命名。 4. **自定义类型转换器**: 如果遇到Spring MVC无法自动转换的类型(比如日期格式、自定义格式),可以注册自定义的类型转换器。例如,实现`Converter<String, MyCustomType>`接口,并在配置类中注册。 5. **使用包装类型接收参数**: 对于复杂对象,可以使用一个DTO(Data Transfer Object)类来接收参数,并使用`@RequestBody`注解(通常用于POST请求的JSON数据)。这样,Spring会使用HttpMessageConverter(如Jackson)将JSON字符串转换为Java对象,转换规则由Jackson配置决定。 6. **处理批量删除的示例**: 引用[1]中提到批量删除功能参数类型问题。假设后端接口如下: ```java @PostMapping("/delete") public AjaxResult delete(@RequestParam("ids") Long[] ids) { // 删除操作 } ``` 前端应该传递一个名为`ids`的参数,值为数组,如`ids: [1, 2, 3]`(在JSON请求体中)或者`ids=1&ids=2&ids=3`(在表单数据中)。如果前端传递的是字符串`ids: ["1","2","3"]`,Spring MVC会尝试将每个字符串转换为Long,如果转换失败则抛出异常。 7. **调试与日志**: 在开发环境中,可以开启Spring MVC的调试日志,查看参数绑定过程。或者在Controller方法入口打印参数值,检查是否接收到正确的参数类型。 8. **前端代码调整**: 如果前端使用Ajax请求,确保设置正确的`contentType`(如`application/json`)正确的数据格式。例如,使用axios发送数组: ```javascript axios.post('/delete', { ids: [1, 2, 3] }, { headers: { 'Content-Type': 'application/json' } }) ``` 或者,如果后端接收的是表单数据,则使用: ```javascript let params = new URLSearchParams(); params.append('ids', 1); params.append('ids', 2); params.append('ids', 3); axios.post('/delete', params) ``` 9. **后端接口调整**: 如果无法改变前端传递的数据格式,可以考虑调整后端接口,将参数类型改为`String[]`,然后在服务层进行转换。例如: ```java @PostMapping("/delete") public AjaxResult delete(@RequestParam("ids") String[] idStrs) { Long[] ids = Arrays.stream(idStrs).map(Long::valueOf).toArray(Long[]::new); // 删除操作 } ``` 但要注意,如果`idStrs`包含非数字字符串,这里会抛出`NumberFormatException`,需要处理异常。 10. **使用注解验证参数**: 使用`@Validated`注解校验注解(如`@NotNull`,`@Min`等)来验证参数,并给出明确的错误信息。 总结:解决参数类型匹配的关键是确保前后端对参数的类型、名称格式达成一致。通过检查后端接口定义、前端实际发送的请求数据,并进行必要的调整(如修改参数类型、添加转换逻辑等),可以解决此问题。 根据引用[2],RuoYi框架具有模块化组件化的特点,因此我们可以灵活地调整前后端代码,而会影响整体结构。 相关问题: 1. 在RuoYi框架中,如何自定义参数类型转换器? 2. 如何全局处理参数类型转换异常? 3. 在RuoYi框架中,如何设计一个DTO类来接收复杂的请求参数?</think>### RuoYi框架请求参数类型匹配解决方案 在RuoYi框架中,参数类型匹配是常见问题,通常由**前后端数据类型一致**导致。以下是系统化的解决方案: #### 1. **问题定位与常见场景** - **根本原因**:前端传递的数据类型与后端`@RequestParam`、`@PathVariable`或`@RequestBody`声明的类型兼容[^1][^4]。 - **典型场景**: - 后端要求`Long`,前端传递`String`(如`"123"`) - 后端要求`Long[]`,前端传递`String[]`(如批量删除操作) - 路径参数类型错误(如`@PathVariable`期望数值型,前端传字符串) #### 2. **前端修复方案** ```javascript // 正确转换类型(以axios为例) axios.delete('/system/model/delete', { params: { ids: selectedRows.map(item => Number(item.id)) // 显式转换为数值 } }) ``` - **关键操作**: 1. 使用`JSON.stringify()`确保复杂对象类型正确 2. 路径参数用模板字符串:``/${Number(id)}`` 3. 表单数据用`FormData`封装 #### 3. **后端修复方案** ```java // 方案1:修改参数接收类型(推荐) @DeleteMapping("/delete") public AjaxResult delete(@RequestParam Long[] ids) { ... } // 方案2:添加类型转换器 @GetMapping("/detail/{id}") public AjaxResult detail(@PathVariable("id") Long id) { ... } // 明确声明Long类型 // 方案3:自定义Converter(全局生效) @Bean public Converter<String, Long> stringToLongConverter() { return source -> StringUtils.isEmpty(source) ? null : Long.parseLong(source); } ``` #### 4. **调试与验证工具** - **前端检查**:浏览器开发者工具 → Network → 查看请求载荷类型 - **后端日志**:开启`logging.level.org.springframework.web=DEBUG`观察参数绑定过程 - **测试工具**:Postman直接测试接口,排除前端干扰 #### 5. **预防措施** 1. 定义统一DTO对象接收参数 ```java @Data public class DeleteDTO { private Long[] ids; // 明确类型 } ``` 2. 使用全局异常处理器捕获`MethodArgumentTypeMismatchException` 3. 接口文档标注参数类型(Swagger的`@ApiParam(dataType = "long")`) > **经验提示**:RuoYi框架的`BaseController`已内置类型转换逻辑,优先检查是否覆盖了默认配置[^2][^4]。批量删除场景需特别注意数组类型的一致性[^1]。 --- ### 相关问题 1. 如何在RuoYi中全局处理参数类型转换异常? 2. 使用`@RequestBody`接收JSON数据时如何避免类型错误? 3. RuoYi框架如何实现自定义参数验证规则? 4. 前端Vue组件如何正确处理数值型参数的传递? 5. 如何通过Swagger文档明确接口参数类型要求? [^1]: Ruoyi-AI项目中的批量删除功能参数类型问题分析与修复 [^2]: ruoyi框架的设计理念是“简单、实用、高效” [^4]: ruoyi 请求参数类型匹配,参数[giftId]要求类型为:‘java.lang.Long‘,但输入值为:‘orderGiftUnionList’
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值