[干货]你真的了解JavaScript中的封装类吗?你知道New的执行原理吗?你知道原始数据的隐式包装类吗?

前言

【干货】 你真的了解JavaScript中的封装类嘛?

今天,我来带大家轻松学习JavaScript中的封装类(对象)

正文

在学习封装类之前,我们首先要知道,在JavaScript中有两种数据类型:原始数据类型和引用类型

原始数据类型和引用类型

javascript 复制代码
在JavaScript中,数据类型大致可以分为原始数据类型和引用数据类型。

原始数据类型包括:Undefined,Null,Boolean,Number,String。这些类型直接存储在栈(stack)中的简单数据段,占据空间小,大小固定,属于被频繁使用的数据,所以存储在栈中。

引用数据类型则包括对象、数组、函数。它们存储在堆(heap)中的对象,占据空间大,大小不固定,如果存储在栈中,将会影响程序运行的性能。引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址,当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后,从堆中获得实体。

首先,原始数据类型有以下这些:

js 复制代码
原始数据类型
 let a = 'hello'
 let b = 123
 let c = true
 let u = undefined
 let n = null

接下来我们来看看:引用数据类型

js 复制代码
let obj = {
    name:'小花'
}
let lw = obj
obj.name='小红'
console.log(lw.name)

也是我们经常说的对象,关于引用数据类型,我们要知道:

引用类型不能放在调用栈里面,防止内存泄漏,关于调用栈的知识大家可以学习:【干货】带你轻松理解JavaScript中的闭包、调用栈和作用域链!! - 掘金 (juejin.cn)

浏览器内核会开辟一个新的内存空间,叫做 堆 用来存放引用数据类型!然后给引用类型的变量赋一个地址,调用的时候就将地址赋给别的变量

调用的时候,顺着地址先判断它是不是对象,在取值。

好,我们来为大家上个图解:

我们回顾一下我们的代码:

js 复制代码
let obj = {
    name:'小花'
}
let lw = obj
obj.name='小红'
console.log(lw.name)

结合图解为大家分析:

  1. 在全局执行上下文当中,我们声明了一个obj对象,此时我们的内核会在调用栈之外 ,开辟一个堆空间,然后,将obj的对象体存入堆空间当中,将存放这个对象体的地址 赋值给调用栈中的词法环境当中的obj变量----传址不传值
  2. 接着,我们定义lw变量,并让它等于obj,你说obj没有值嘛?**地址也是值!**于是,我们的内核会将obj的存储的地址赋值给lw
  3. 然后我们利用obj.name = '小红'修改了对象中的name属性!,也就是说:将obj指向的地址中的值修改成了"小红"
  4. 我们再打印lw.namelwobj指向的是同一个地址,这个obj指向的地址中的值发生了变化,lw指向的地址中的值也会随着改变。

因此,我们的输出结果就是:

js 复制代码
输出:小红

那我们如何给对象进行增删改查呢?

js 复制代码
let obj = {
    name:'丁总',
    age:18,

}
let girl = '小红'
obj[girl] = '小红'
obj['girl'] = 'girl'
delete obj.girl
console.log(obj)

在这一段代码中,我们的输出结果是什么呢?我们来简单分析一下。

markdown 复制代码
1. 我们定义了一个对象`obj`,定义一个变量`girl='小红'`
1. 为`obj`对象添加了两个属性 一个是属性`'小红'`,一个是属性`"girl"`,`小红`是哪里来的?在`obj[girl]`中的`girl`会被内核识别成一个变量,于是它会识别到这个变量的值,也就是`小红`
1. 后面我们又用`delete()`方法删除了`obj.girl`,这里指的是字符串`girl`而不是变量

所以,我们的输出结果是:

js 复制代码
输出:{ name: '丁总', age: 18, '小红': '小红' }

对象的创建方法

js 复制代码
对象的创建
1. var obj = {}//对象字面量||对象直接量 Object()//构造函数
2. var obj = new Object();
3. 自定义构造函数
4. Object.create({})

我来上一个案例更贴切的去学习这四种方法!

第一种var obj = {}:

js 复制代码
var obj = {}
obj.name = '小红'
console.log(obj)
js 复制代码
输出:{ name: '小红' }

第二种var obj = new Object()

js 复制代码
var obj = new Object();
    obj.name = '小奈'
    console.log(obj)
js 复制代码
输出:{ name: '小奈' }

第三种自定义构造函数

js 复制代码
var Newname = function(){
    this.name = '小明'
}
var person = new Newname()
console.log(person)
js 复制代码
Newname { name: '小明' }

第四中Object.create()方法

js 复制代码
let str = Object.create({})
str.name = '小行'
console.log(str)
js 复制代码
输出:{ name: '小行' }

构造函数就像工厂,可以批量化生产

函数也是引用类型,可以说成是一种特殊的对象。

New到底是什么执行原理?

在JavaScript中,new关键字用于创建一个用户定义的对象类型的实例或具有构造函数的内建对象的实例。其运行原理大致可以分为以下步骤:

  1. 创建新对象 :首先,new关键字会创建一个新的空对象。这个对象是新创建的对象,该对象的类型是所指定的类型。
  2. 设置原型链 :新创建的对象的[[Prototype]](即__proto__)会被设置为构造函数的prototype对象。这意味着新对象可以访问构造函数原型上的属性和方法。(这部分我们将在下次文章中学习--原型
  3. 构造函数执行 :之后,构造函数会使用这个新创建的对象作为其上下文被执行。换句话说,this关键字在构造函数内部指向这个新创建的对象。
  4. 返回新对象 :如果构造函数没有显式返回一个对象,那么会自动返回新创建的对象。如果构造函数返回了一个非原始类型(即对象或者函数),那么这个返回值会被作为整个new表达式的返回值。如果返回的是原始类型(例如:Number、String、Boolean、Null、Undefined),则忽略这个返回值,依然返回新创建的对象。

我们来看一看这样一段案例:

js 复制代码
function Person(name,age,sex){
    this.name = name
    this.age = age
    this.sex = sex
    }
let p = new Person('海军',18,'boy')
console.log(p)

毫无疑问,这个代码的输出结果为:

js 复制代码
Person { name: '海军', age: 18, sex: 'boy' }

那么,new的执行原理到底是什么呢?

按照,我们目前的理解,new的执行原理就相当于,在我们new的那个函数体当中生成一个"this"对象,第二步执行函数体内的逻辑,最后返回this。也就相当于下面这个案例

js 复制代码
function Person(name,age,sex){
    var that = {}//new 新生成
    that.name = name
    that.age = age
    that.sex = sex
    return that//new 新生成
}
let p1 = Person('小五',18,'boy')
console.log(p1)
css 复制代码
输出:{ name: '小五', age: 18, sex: 'boy' }

这就new产生的一个效果!

接下来, 我们来介绍

包装类与原始数据的一些差别

包装类

js 复制代码
var obj = {}
console.log(obj.a)

原始数据

js 复制代码
let a = 'hello'
let b = 123
let c = true
let u = undefined
let n = null
  1. 声明方式:原始数据类型不可以使用new关键字,而包装类型需要使用new关键字来在堆中分配存储空间。
  2. 存储方式及位置:原始数据类型是直接将变量值存储在堆栈中,而包装类型是将对象放在堆中,然后通过引用来使用。
  3. 初始值:原始类型的初始值如int为0,boolean为false,而包装类型的初始值为null。
  4. 使用方式:原始类型可以直接赋值直接使用,而包装类型在集合如Collection、Map时会使用到。
  5. 传递方式:基本类型在传递参数时都是按值传递,而封装类型是按引用传递的。
  6. 继承性:包装类都是继承Number接口实现Comparable接口的。例如,Double extends Number implements Comparable。

同时原始数据不具备属性,而包装类具备属性。

也不可以给原始数据添加属性。

例如:

js 复制代码
var num = 123
num.abc = 'hello'
console.log(num.abc)
js 复制代码
输出:undefined

既然说,不能给原始数据添加属性,为什么这里输出的是undefined而不是报错呢?

重点重点!!!!

这是因为当我们内核执行到给原始数据添加属性等操作时,每次内核会堆我们原始数据new一个对象,但是,我们内核发现,这个数据是原始数据,于是立马又会将它new的对象delete掉,每次调用num.abc都会有这样一个过程。因此,在console.log(num.abc)的时候也会执行这样一个过程,所以,最后输出的结果是:undefined

我们再来说一个案例:

js 复制代码
var num = new Number(123)
num.abc = 'hello'
console.log(num.abc)
console.log(num*2)
js 复制代码
输出:
hello
246

在这个案例当中,num不参与运算时会被识别为对象,如果num参加了四则运算会被识别为原始数据-数字

我们再来通过这个案例来学习一下隐式包装类

js 复制代码
var num = 4
num.len = 3
var num = new Number(4)
num.len = 3 //来弥补num.len的不足
delete num.len
new Number(4).len  //这个过程就叫隐式包装类
console.log(num.len)
js 复制代码
输出:undefined

为什么这里输出的是undefined?

在JavaScript中,当你尝试获取或设置一个原始数据类型的属性时,JavaScript实际上会创建一个包装对象来处理这个属性。这就是所谓的"隐式包装"。然而,当你再次尝试获取或设置这个属性时,如果之前没有设置过,那么就会返回undefined。

比如在上面的代码

js 复制代码
var num = 4;  
num.len = 3;  
console.log(num.len); 

会输出 undefined,因为num实际上被隐式地转换为了一个Number对象,而这个Number对象并没有一个名为len的属性

然后我们创建了一个新的Number对象:

js 复制代码
ar num = new Number(4);  
num.len = 3; //成功添加属性  
delete num.len; //删除属性  
console.log(num.len); // undefined,因为num.len已经被删除了

最后

js 复制代码
new Number(4).len; // undefined,因为新创建的Number对象并没有len属性

我们输出num.len由于num.len已经被删除,所以返回的是undefined

关于"隐式包装类"的概念,它指的是当JavaScript尝试将原始数据类型当作对象来处理时,会在背后创建一个包装对象来模拟原始数据类型的行为。然而,这个包装对象只会在背后创建,你无法直接访问到它。这就是为什么你无法直接给原始数据类型添加属性的原因。

考点

js 复制代码
var arr = [1,2,3,4,5]
arr.length = 2
console.log(arr)

这里输出的是什么?

js 复制代码
输出:[ 1, 2 ]

这是因为在JavaScript中,arr.length = 2并不会将数组截断为只有两个元素。相反,它会将数组的长度设置为2,这意味着数组将不再包含原来的所有元素。

当你运行 arr.length = 2 时,你实际上是在告诉JavaScript将数组的长度减少到2。这意味着只有索引0和1的元素会被保留,其它的元素将被丢弃。

所以,原本的数组 [1,2,3,4,5] 在执行 arr.length = 2 后变成了 [1,2]

我们再看这个案例:

js 复制代码
var str = 'abcd'
str.length = 2
console.log(str.length)
输出:4

那为什么这里又是4呢?

这是因为在JavaScript中,字符串是一个特殊的对象类型。当你创建一个字符串变量时,例如 var str = 'abcd',实际上你创建的是一个字符串对象,而'abcd'只是这个对象的一个属性。这个属性的名字是0,并且它的值就是字符串'abcd'。

然后,当你尝试修改字符串的长度属性(例如 str.length = 2),你实际上是在修改这个特殊对象的长度属性,这个属性的名字是 length。这个属性表示的是字符串中字符的数量。

然而,这个 length 属性的值并不会影响字符串对象中实际存储的字符数量。也就是说,无论你如何改变 length 属性的值,字符串对象中存储的实际字符数量(也就是属性 0 的值)都不会改变。

所以,当你运行 console.log(str.length) 时,输出的结果是你设定的 length 属性的值,也就是2。然而,这并不影响字符串对象中存储的实际字符数量,也就是属性 0 的值,它的值仍然是'abcd'。

所以,虽然你设定了 str.length = 2,但是当你运行 console.log(str) 时,输出的结果仍然是'abcd',而不是你设定的两个字符的字符串。

new String('abcd).length然后delete 刚生成立马会被删掉

改变不了原始值的值

好了!我们今日的学习就到此为止啦!

如果大家有任何指正和想法,欢迎大家在评论区留言!

希望大家给个小小的赞支持鼓励一下吧!🌹🌹🌹

相关推荐
并不会33 分钟前
常见 CSS 选择器用法
前端·css·学习·html·前端开发·css选择器
衣乌安、37 分钟前
【CSS】居中样式
前端·css·css3
兔老大的胡萝卜37 分钟前
ppk谈JavaScript,悟透JavaScript,精通CSS高级Web,JavaScript DOM编程艺术,高性能JavaScript pdf
前端·javascript
低代码布道师39 分钟前
CSS的三个重点
前端·css
耶啵奶膘2 小时前
uniapp-是否删除
linux·前端·uni-app
pianmian13 小时前
python数据结构基础(7)
数据结构·算法
王哈哈^_^4 小时前
【数据集】【YOLO】【目标检测】交通事故识别数据集 8939 张,YOLO道路事故目标检测实战训练教程!
前端·人工智能·深度学习·yolo·目标检测·计算机视觉·pyqt
cs_dn_Jie4 小时前
钉钉 H5 微应用 手机端调试
前端·javascript·vue.js·vue·钉钉
好奇龙猫5 小时前
【学习AI-相关路程-mnist手写数字分类-win-硬件:windows-自我学习AI-实验步骤-全连接神经网络(BPnetwork)-操作流程(3) 】
人工智能·算法
开心工作室_kaic5 小时前
ssm068海鲜自助餐厅系统+vue(论文+源码)_kaic
前端·javascript·vue.js