5 数据类型
约 5213 字大约 17 分钟
2025-06-19
作用域,即变量(变量作用域又称上下文)和函数生效(能被访问)的区域或集合 作用域决定了代码区块中变量和其他资源的可见性
全局作用域 任何不在函数中或是大括号中声明的变量,都是在全局作用域下,全局作用域下声明的变量可以在程序的任意位置访问
函数作用域 函数作用域也叫局部作用域,如果一个变量是在函数内部声明的它就在一个函数作用域下面。这些变量只能在函数内部访问,不能在函数以外去访问
块级作用域 代码块的范围,如if、while或单大括号包裹的作用域 ES6引入了
let
和const
关键字,和var
关键字不同,在大括号中使用let
和const
声明的变量存在于块级作用域中。在大括号之外不能访问这些变量
词法作用域
词法作用域,又叫静态作用域,变量被创建时就确定好了,而非执行阶段确定的。也就是说我们写好代码时它的作用域就确定了,JavaScript
遵循的就是词法作用域
作用域链
当在Javascript
中使用一个变量的时候,首先Javascript
引擎会尝试在当前作用域下去寻找该变量,如果没找到,再到它的上层作用域寻找,以此类推直到找到该变量或是已经到了全局作用域 如果在全局作用域里仍然找不到该变量,它就会在全局范围内隐式声明该变量(非严格模式下)或是直接报错
变量提升
function和var关键字都会变量提升
[!NOTE] Title 函数内部声明变量是不能提升到全局的 函数内部赋值,实际上相当于在全局声明,是可以提升的
var变量和函数同名变量会首先提升,函数提升会覆盖变量提升 变量和函数同名变量执行,执行函数报错
var a=1
function a(){
console.log(2)
}
console.log(a) //1
a() //未定义
数据类型
基础数据类型
基础类型存储在栈内存 被引用或拷贝时,会创建一个完全相等的变量
- undefined
- Null
- Boolean
- String
- Number
- Symbol
- Biglnt
引用数据类型
引用类型存储在堆内存 存储的是地址,多个引用指向同一个地址 这里会涉及一个“共享”的概念
- Object
深拷贝和浅拷贝
深拷贝 定义:创建一个新的对象,复制原对象的所有属性,包括引用类型,确保新对象与原对象完全独立,修改任何一方不会影响另一方。 实现方
- 手写递归函数:遍历原对象,判断属性是否为对象,是则递归复制,否则直接赋值。
- JSON.parse(JSON.stringify(obj)):简单高效,但不支持函数、undefined、Symbol等特殊类型,且无法处理循环引用。
- 使用第三方库:如Lodash的
cloneDeep
方法,功能强大,支持各种复杂数据结构。
浅拷贝 -定义:创建新对象,复制原对象的第一层属性,对于引用类型属性,只复制引用,新旧对象共享同一内存地址,修改一方会影响另一方。
实现方法
- Object.assign(target, source):将源对象的属性复制到目标对象,只复制一层。
- 展开运算符
...
:如let newObj = {...oldObj}
,适用于对象和数组,同样只进行浅拷贝。 - 数组的
slice()
或concat()
方法:如let newArr = oldArr.slice()
,仅对数组进行浅拷贝。
判断数据类型
- typeof
typeof a
返回a的数据类型
对于引用数据类型只能判断出object,无法获得详细的 由于历史残留的问题null也是object
- instanceof
a instanceof A
判断a是否是A构造函数生成的对象
只能判断引用数据类型 instanceof是基于原型链实现的
getProtypeOf
函数是object对象上自带的函数,用于获取实例身上的原型
- Object.prototype.tostring() 获取对象身上的原型并转成字符串返回
类型转换
显式类型转换
显示转换,即我们很清楚可以看到这里发生了类型的转变,常见的方法有:
- Number()转换的时候是很严格的,只要有一个字符无法转成数值,整个字符串就会被转为
NaN
- parseInt()相比
Number
,没那么严格了,parseInt
函数逐个解析字符,遇到不能转换的字符就停下来 - String()可以将任意类型的值转化成字符串
- Boolean()可以将任意类型的值转为布尔值
隐式转换
我们这里可以归纳为两种情况发生隐式转换的场景:
比较运算(
==
、!=
、>
、<
)、if
、while
需要布尔值地方算术运算(
+
、-
、*
、/
、%
) 除了上面的场景,还要求运算符两边的操作数不是同一类型在需要布尔值的地方,就会将非布尔值的参数自动转为布尔值,系统内部会调用
Boolean
函数遇到预期为字符串的地方,就会将非字符串的值自动转为字符串。具体规则是:先将复合类型的值转为原始类型的值,再将原始类型的值转为字符串
Object
和map的比较
Object是最常用的一种引l用类型数据,可用于存储键值对的集合,在ECMAScript 1st里添加的 Map是键值对的集合,采用Hash结构存储,在EcMAScript2015版本里新增的
相同点
- 都可以进行键值对的设置和添加
不同点
不同点 | Object | Map |
---|---|---|
属性的设置和添加 | 可以直接设置,判断属性是否存在一般使用三等运算符和undefined比较,删除属性使用delete运算符 | 需要使用api |
键的类型 | String或者Symbol | 可以是任意类型,包括对象,数组,函数等,不会进行类型转换。在添加键值对时,会通过严格相等(===)来判断键属性是否已存在。但是NaN是一个特例,在使用三等号判断两个NaN时会返回false,因此在将NaN设置为map的键时NaN会覆盖 |
键的顺序 | 1.对于大于等于0的整数,会按照大小进行排序,对于小数和负数 会当做字符串处理 2.对于String类型,按照定义的顺序进行输出 3.对于Symbol类型,会直接过滤掉,不会进行输出, 如果想要输出Symbol类型属性 通过Object.getOwnPropertySymbols()方法 | key是有序的,按照插入的顺序进行返回 |
获取属性数量 | 只能手动计算,通过object.keys0方法或者通过for...in循环统计 | 直接通过size属性访问 |
迭代器 | 不可以使用for...of迭代器遍历 | 可以使用for...of |
JSON序列号 | 可以序列化 | 不可以序列化,使用JSON序列化返回的是一个空对象字符串 |
适用场景
Object | Map |
---|---|
仅做数据存储,并且属性仅为字符串或者Symbol | 1.会频繁更新和删除键值对时 |
需要进行序列化转换为json传输时 | 存储大量数据时,尤其是key类型未知的情况下 |
当做一个对象的实例,需要保留自己的属性和方法时 | 需要频繁进行迭代处理 |
常见方法
- Object.is ES5比较两个值是否相等,只有两个运算符:(==)和严格相等运算符(===) 他们都有缺点,前者会自动转换数据类型,后者的NaN不等于自身、以及+0等于-0。
ES6提出了《同值相等》的算法来解决这个问题。Object.is就是部署了这个算法的新方法。它用来比较两个值是否严格相等,与===运算符的行为基本一致。
- Object.assign Object.assign()方法用于将源对象(source)的所有可枚举对象属性赋值到目标对象 可以有多个参数,第一个参数是目标对象,后面的对象都是源对象
该方法的复制方式是浅拷贝,也就是说如果源对象的某个属性是引用类型的话目标对象复制到的该属性只是那个属性的引用
- Object.getProtoTypeOf() Object.getProtoTypeOfO方法返回参数对象的原型。这是获取原型对象的标准方法。
和直接使用对象.__proto__
作用一样,但是__proto__是一个内部属性,不应该直接操作
Object.setProtypeOf() Object.setPrototypeOf方法为参数对象设置原型,返回该参数对象。它接受两个参数,第一个是现有对象,第二个是原型对象。
Object.create 生成一个实例
与new关键字的区别 使用new关键字生成实例对象时需要有构造函数(class) Object.create可以根据一个实例生成实例对象该方法接受一个对象作为参数,然后以它为原型,返回一个实例对象,该实例完全继承原型对象的属性
Object.create方法还可以接受第二个参数。该参数是一个属性描述对象,它所描述的对象属性,会添加到实例对象,作为该对象自身的属性
[!warning] Title
- Object.create方法生成的新对象,动态继承了原型。在原型上添加或修改任何方法,会立刻反映在新对象之上。
- Object.create方法生成的对象,继承了它的原型对象的构造函数。
Object.getOwnProprotyNames() 方法返回一个数组,成员是参数对象本身的所有属性的键名,不包含继承的属性键名。
Object.prototype.hasOwnProperty() 对象实例的hasOwnProperty方法返回一个布尔值,用于判断某个属性定义在对象自身,还是定义在原型链上。
Object.getOwnPropotyDescriptor() 获取该属性的描述对象(使用defineProperty定义属性时的配置)。
9. 执行上下文和执行栈
执行上下文是对Javascript
代码执行环境的一种抽象概念,只要有Javascript
代码运行,那么它就一定是运行在执行上下文中
执行上下文的类型分为三种:
- 全局执行上下文:只有一个,浏览器中的全局对象就是
window
对象,this
指向这个全局对象 - 函数执行上下文:存在无数个,只有在函数被调用的时候才会被创建,每次调用函数都会创建一个新的执行上下文
- Eval 函数执行上下文:指的是运行在
eval
函数中的代码,很少用而且不建议使用
只有全局上下文(的变量)能被其他任何上下文访问 可以有任意多个函数上下文,每次调用函数创建一个新的上下文,会创建一个私有作用域,函数内部声明的任何变量都不能在当前函数作用域外部直接访问
生命周期
执行上下文的生命周期包括三个阶段:创建阶段 → 执行阶段 → 回收阶段
创建阶段
创建阶段即当函数被调用,但未执行任何其内部代码之前
创建阶段做了三件事:
确定 this 的值,也被称为
This Binding
this
的值是在执行的时候才能确认,定义的时候不能确认LexicalEnvironment(词法环境) 组件被创建 词法环境有两个组成部分:
- 全局环境:是一个没有外部环境的词法环境,其外部环境引用为
null
,有一个全局对象,this
的值指向这个全局对象 - 函数环境:用户在函数中定义的变量被存储在环境记录中,包含了
arguments
对象,外部环境的引用可以是全局环境,也可以是包含内部函数的外部函数环境
- VariableEnvironment(变量环境) 组件被创建 变量环境也是一个词法环境,因此它具有上面定义的词法环境的所有属性 在 ES6 中,词法环境和变量环境的区别在于前者用于存储函数声明和变量(
let
和const
)绑定,而后者仅用于存储变量(var
)绑定
创建阶段,会在代码中扫描变量和函数声明,然后将函数声明存储在环境中 但变量会被初始化为undefined
(var
声明的情况下)和保持uninitialized
(未初始化状态)(使用let
和const
声明的情况下) 这就是变量提升的实际原因
执行阶段
在这阶段,执行变量赋值、代码执行 如果 Javascript
引擎在源代码中声明的实际位置找不到变量的值,那么将为其分配 undefined
值
回收阶段
执行上下文出栈等待虚拟机回收执行上下文
执行栈
执行栈,也叫调用栈,具有 LIFO(后进先出)结构,用于存储在代码执行期间创建的所有执行上下文 当Javascript
引擎开始执行你第一行脚本代码的时候,它就会创建一个全局执行上下文然后将它压到执行栈中 每当引擎碰到一个函数的时候,它就会创建一个函数执行上下文,然后将这个执行上下文压到执行栈中 引擎会执行位于执行栈栈顶的执行上下文(一般是函数执行上下文),当该函数执行结束后,对应的执行上下文就会被弹出,然后控制流程到达执行栈的下一个执行上下文
undefined和null的区别
undefined | null | |
---|---|---|
表示 | 表示一个变量已声明但尚未赋值,或者对象属性不存在时的默认值。 | 表示一个变量明确地指向一个空值或不存在的对象。 |
typeof | -使用typeof 操作符检查时,undefined 的类型是"undefined" 。 | 而null 的类型则是"object" 。这是一个历史遗留问题,在JavaScript最初实现时的一个bug,后来为了兼容性而保留下来了。 |
直接比较null 和undefined (例如使用== )会返回true ,因为两者都被视为等价于“缺失值”。但是,如果使用严格相等运算符=== 进行比较,则由于它们的类型不同而返回false 。 |
数据类型
基本数据类型(Primitive Types) 存储在栈(stack)中的简单数据段:
- Undefined - 表示未定义的值。
- Null - 表示空值或不存在的对象。
- Boolean - 包含
true
和false
两个值。 - Number - 用于表示整数或浮点数。此外,还包括特殊的数值如
Infinity
、-Infinity
和NaN
。 - String - 用于表示文本数据,可以通过单引号、双引号或反引号定义。
- Symbol (ES6新增) - 表示独一无二的值,通常用来创建对象的唯一属性键。
基本数据类型的值是不可改变的(immutable),每次操作都会创建一个新的值。
引用数据类型(Reference Types) 存储在堆(heap)中的对象,实际的值存放在内存的一个新位置,而变量中存放的是指向这个位置的引用或指针。
- Object - 是一种复杂的数据类型,它可以包含多个数据类型,支持属性和方法。包括普通对象、数组(
Array
)、函数(Function
)、日期(Date
)等。
当操作引用数据类型时,实际上是在操作对象在内存中的地址,而不是直接操作数据本身。这意味着当你将一个对象赋值给另一个变量时,实际上是创建了一个新的引用指向同一个对象,因此修改其中一个变量会影响到另一个变量所指向的对象。
主要区别
- 存储方式:基本数据类型直接存储其值,而引用数据类型存储的是指向该值在内存中的地址的引用。
- 比较方式:基本数据类型按值进行比较,只有当值相同时才认为两者相等;引用数据类型则比较的是它们在内存中的地址,即使两个对象的内容完全相同,只要它们不是同一个对象实例,在比较时也不会认为它们相等。
- 传递方式:函数参数传递时,基本数据类型以值传递的方式传入,而引用数据类型是以引用传递的方式传入。
Symbol
符号,相当于永不重复的符号,作为数据的唯一标识 用于解决重复命名冲突
定义Symbol
let a = Symbol()
let b = Symbol()
console.log(a) // Symbol
console.log(b) // Symbol
console.log(a == b) // false
在定义Symbol时还可以附带自定义的描述: 使用Symbol上面的description属性来获取描述
let a = Symbol('this a')
console.log(a) // Symbol(this a)
console.log(a.description) // this a
for函数 for函数是用于添加Symbol的描述的,和直接使用Symbol(描述)
不同的是:
- 使用for定义是全局定义的,直接添加的是局部定义的
- 使用for指定描述时会先去内存中查看是否有相同描述的Symbol,如果有则新定义的Symbol就会指向这个Symbol(浅拷贝)
- 使用for定义的可以使用keyFor函数获取描述,相当于description,而直接定义的无法使用keyFor获取到
let a = Symbol.for('aaa')
let b = Symbol.for('aaa')
console.log(a == b) // true
使用
let user1={
name:"李四",
key: Symbol()
}
let user2 = {
name:"李四",
key: Symbol()
}
let grade = {
[user1.key]: { js: 100, css: 89 }
[user2.key]: { js: 35 css: 55 }
}
console.log(grade[user1.key]); // { js: 100, css: 89 }
console.log(grade[user2.key]); // { js: 35 css: 55 }
JSON
JSON常用的两个方法:
- stringify:将一个JavaScript对象或值转换为JSON字符串
- parse:将JSON字符串转换为JavaScript对象
数组扁平化
将多维数组降维成一维
类数组
它们不能直接调用数组的方法,但是又和数组比较类似,在某些特定的编程场景中会出现
类数组的场景:
- 函数里面的参数对象arguments
- 用getElementsByTagName/ClassName/Name获得的HTMLCollection
- 用querySelector获得的NodeList
sort实现原理
- 当n<=10 时,采用插入排序
- 当n>10 时,采用三路快速排序
- 110<n<=1000,采用中位数作为哨兵元素
- n>1000,每隔200~215个元素挑出一个元素放到一个新数组中,然后对它排序,找到中间位置的数,以此作为中位数
数据类型检测
typeof
使用typeof关键字判断类型时,如果判断的是null也会被认为是object 这是因为js中的对象在底层的存储前三位都是000,而null全是0,因此null也会被判定为是一个object,这是一个bug
instanceof
instanceof是用于检测一个对象是否是一个类的实例 底层原理是检测当前对象的原型链,如果当前对象的原型链上有该类,那么就判定成功 因此使用instanceof判定一个对象的祖宗也是true
手动修改原型链也会导致instanceof判定出错
functoion Fn(){
this.x=100
}
Fn.prototype=Object.create(Array.prototype)
let f=new Fn
console.log(f instanceof Array) //true
垃圾回收
两种垃圾回收机制 js使用的时第一种标记清除
- 标记清除: 标记:所有数据和对象进行标记 清除:没有标记的清除
添加标记:垃圾收集器 运行时会给内存中的所有变量添加一个标记,内存中垃圾标记0。便历所有对象,有用的变量标记1,清除所有标记为0的变量和对象,原来有用的1,变成0,在反复清理
优点: 实现简单,01两种状态 缺点: 清除后空间不连续的,产生很多内存碎片
- 引用计数: 记录变量和对象的引用次数,次数为0 无法处理变量循环引用情况,导致内存泄漏
引用计数: 添加引用计数器初始0,对象引用时:+1,取消引用-1,当对象0清除
优点: 引用计数器为0立即回收 优点: 引用计数器0立即回收,不需要便历访问内存,只需要观察计数器 缺点: 计数器占位大,不知道数字上限,无法解决循环引用
贡献者
版权所有
版权归属:PinkDopeyBug