Javascript
变量与数据类型(一)
本章讲解了一些关于JavaScript
的变量和数据类型,存储方式,以及 一些其它的知识点。
javascript
数据类型- 基础数据类型与引用数据类型
null
与undefined
Symbol
与BigInt
Number
精度问题
一、数据类型
javascript
中的数据类型共有 8 种,其中又分为基本数据类型和引用数据类型。
基本数据类型有:
null
-> 空,只有一种值undefined
-> 未定义,只有一种值Boolean
-> 布尔值,有两种值,true
和false
Number
-> 数值(包括浮点数),其中包括一些特殊的值(infinity
、NaN
)String
-> 字符串,表示一连串的字符Symbol
(ES6) -> 一种实例,唯一且不可变的数据类型BigInt
(ES10) -> 比Number支持范围还要大的整数值
引用数据类型有:
Object
-> 对象,包含了Array
(数组),Function
(函数),Date
(时间)等特俗的对象
二、基本数据类型与引用数据类型
这里我们讲解两种类型的时候,我们先要了解一下js的存储机制:栈 与 堆
栈 存储就如一个队列一般,声明后由系统分配好内存便一一存放。
- 栈内存 一般用于存储 基本数据类型
- 存储大小固定
- 空间较小
- 能够直接操作变量,运行效率较高
- 由系统自动分配空间
堆 存储就如一个个堆放的团子,没有序列,而且大小是动态的(因为可以随意添加删除数据)
- 堆内存 一般用于存储 引用数据类型
- 存储大小不固定,可动态调整
- 空间较大,运行效率低
- 无法直接操作,需要通过引用地址读取
- 通过代码进行分配空间
了解完 js存储机制,我们先来搞定基本数据类型
2.1 基本类型
2.1.1 基本类型不可变性
上面所提到的原始类型,在ECMAScript
标准中,它们被定义为primitive values
,即原始值,代表值本身是不可被改变的。
如字符串:
var str = 'ConardLi';
str.slice(1);
str.substr(1);
str.trim(1);
str.toLowerCase(1);
str[0] = 1;
console.log(str); // ConardLi
无论如何操作 str
,输出来的str
的值依旧没变,而当我们通过下面的方式操作时:
str += '6'
console.log(str); // ConardLi6
输出的值变了,不是因为 str
的值发生了变化,而是在内存中str的指向发生了改变:
原本 str
的值指向 ConaardLi
,因为在栈内存中,存储大小是固定不变的,所以在我们个str
赋值的时候其实是开辟了新的内存空间用于存储新的 str
的值,然后将 str
的指向修改到了新的内存空间上。
2.2 引用类型
引用类型实际的值存储在堆中,而在栈中只是存储了个固定长度的引用地址,这个地址指向了引用类型中的值。
var obj1 = {name:"ConardLi"}
var obj2 = {age:18}
var obj3 = function(){...}
var obj4 = [1,2,3,4,5,6,7,8,9]
引用类型不具备不可变性,所以我们可以通过引用地址来修改他的值
obj1.name = "ConardLi6";
obj2.age = 19;
obj4.length = 0;
console.log(obj1); //{name:"ConardLi6"}
console.log(obj2); // {age:19}
console.log(obj4); // []
如数组:可以通过 push()
在末尾添加值,pop()
删除末尾的值,unshift()
在头部添加值,shift()
删除头部的值。
2.3 复制变量
当我们把一个变量的值复制给另一个变量时,在基本数据类型上和引用数据类型上的表现是不一样的。
在基础数据类型中:
var name = 'ConardLi';
var name2 = name;
name2 = 'code秘密花园';
console.log(name); // ConardLi;
我们把值复制一份后,相当于又开辟了一个新的内存空间存储新的值。两者互相操作互不干扰
在引用数据类型中:
var obj = {name:'ConardLi'};
var obj2 = obj;
obj2.name = 'code秘密花园';
console.log(obj.name); // code秘密花园
复制给obj2
的是obj1
的值引用地址,两个对象的指向是同一个值,所以当其中一个值发生改变时,另一个也会随之发生改变。
2.4 变量比较
var name = 'ConardLi';
var name2 = 'ConardLi';
console.log(name === name2); // true
var obj = {name:'ConardLi'};
var obj2 = {name:'ConardLi'};
console.log(obj === obj2); // false
对于基础数据类型,比较的是双方的值,当值相同时则返回为 true
对于引用数据类型,比较多时双方的引用地址,即使内容相同,引用地址指向不同的值,也返回 false
2.5 值传递与引用传递
在基础数据类型中:
let name = 'ConardLi';
function changeValue(name){
name = 'code秘密花园';
}
changeValue(name);
console.log(name); // ConardLi
输出的值不变,说明在基础数据类型作为参数传递时,只是将本身的值复制一份作为参数传入,并不影响本身的值,我们把这种参数传递方式叫做 值传递
在引用数据类型中:
let obj = {};
function changeValue(obj){
obj.name = 'ConardLi';
obj = {name:'code秘密花园'};
}
changeValue(obj);
console.log(obj.name); // ConardLi
同样,将 obj
的值传递到函数中,不过obj的值是在栈中的引用地址,所以在其中修改也会引起外面的改变,但当函数内的obj
将自己赋值为{name:'code秘密花园'}
时,也不影响到外面的obj
,所以这也是值传递的一种。
ECMAScript
中所有的函数的参数都是按值传递的。
三、null
与 undefined
null
:
null
,指的是 空的 ,不存在有值null
转化为数值为 0null
作为对象的属性值是正常的null
是一个关键字null
为object
类型
undefined
:
undefined
,指的时 未定义的,应该存在值但没有定义undefined
转化为数值为NaN
undefined
作为对象的属性值是错误的undefined
不是一个关键字,而是一个Global
全局变量undefined
为undefined
类型- 以下情况会出现
undefined
未定义的变量
定义但为声明的变量
对象中不存在或未赋值的属性
函数中未传递的参数
没有返回值的函数默认返回
相同点:
都是基本数据类型的值,都保存在本地栈内存中
不同点:
类型不同(null
=>object
,undefined
=>undefined
)
转换数值不同(null
=> 0 ,undefined
=>NaN
)undefined == null
返回true
undefined === null
返回false
四、Symbol
与 BigInt
4.1 Symbol
Symbol
类型是ES6
中新添加的一种基本数据类型
每个从Symbol()返回的symbol值都是唯一的。一个symbol值能作为对象属性的标识符;这是该数据类型仅有的目的。
看看Symbol
类型具有哪些特性
4.1.1 独一无二
通过Symbol()
来创建一个Symbol变量,而不是通过构造函数new
一个Symbol对象,会报错。
typeof Symbol() === 'symbol'
typeof Symbol('ConardLi') === 'symbol'
可以选用一个字符串进行描述,但参数为对象时会自动调用toString()
方法
var sym1 = Symbol(); // Symbol()
var sym2 = Symbol('ConardLi'); // Symbol(ConardLi)
var sym3 = Symbol('ConardLi'); // Symbol(ConardLi)
var sym4 = Symbol({name:'ConardLi'}); // Symbol([object Object]) 使用 toString() 方法
console.log(sym2 === sym3); // false
两个 Symbol
变量时不相等的,因为每一个Symbol
变量都是独一无二的。
当需要创建两个相等的Symbol
变量时,可以使用Symbol.for(key)
方法
使用给定的key搜索现有的symbol,如果找到则返回该symbol。否则将使用给定的key在全局symbol注册表中创建一个新的symbol。
var sym1 = Symbol.for('ConardLi');
var sym2 = Symbol.for('ConardLi');
console.log(sym1 === sym2); // true
4.1.2 不可枚举
当使用Symbol
作为对象属性时,可以保证对象不会出现重名属性,调用for...in
不能将其枚举出来
另外调用Object.getOwnPropertyNames、Object.keys()
也不能获取Symbol
属性
可以调用Object.getOwnPropertySymbols()用于专门获取Symbol属性。
var obj = {
name:'ConardLi',
[Symbol('name2')]:'code秘密花园'
}
Object.getOwnPropertyNames(obj); // ["name"]
Object.keys(obj); // ["name"]
for (var i in obj) {
console.log(i); // name
}
Object.getOwnPropertySymbols(obj) // [Symbol(name)]
4.1.3 应用场景
应用一: 防止XSS
例如在JSON
中不能存储Symbol
类型的变量
应用二: 私有属性
借助Symbol
类型的不可枚举,我们可以在类中模拟私有属性,控制变量读写:
const privateField = Symbol();
class myClass {
constructor(){
this[privateField] = 'ConardLi';
}
getField(){
return this[privateField];
}
setField(val){
this[privateField] = val;
}
}
应用三: 防止属性污染
模拟实现一个call
方法:
Function.prototype.myCall = function (context) {
if (typeof this !== 'function') {
return undefined; // 用于防止 Function.prototype.myCall() 直接调用
}
context = context || window;
const fn = Symbol();
context[fn] = this;
const args = [...arguments].slice(1);
const result = context[fn](...args);
delete context[fn];
return result;
}
需要在某个对象上临时调用一个方法,又不能造成属性污染,Symbol
是一个很好的选择
4.2 BigInt
BigInt
数据类型的目的是比Number
数据类型支持的范围更大的整数值。在对大整数执行数学运算时,使用BigInt
,整数溢出将不再是问题
此外,可以安全地使用更加准确时间戳,大整数ID等,而无需使用变通方法。 BigInt
目前是第3阶段提案, 一旦添加到规范中,它就是JS 第二个数字数据类型,也将是 JS 第8种基本数据类型
在JS中,按照IEEE 754-2008标准的定义,所有数字都以双精度64位浮点格式表示。
关于 JS 的精度问题下面会讲
使用BigInt,应用程序不再需要变通方法或库来安全地表示Number.MAX_SAFE_INTEGER
和Number.Min_SAFE_INTEGER
之外的整数。 现在可以在标准JS中执行对大整数的算术运算,而不会有精度损失的风险。
要创建BigInt
,只需在整数的末尾追加n即可。比较:
console.log(9007199254740995n); // → 9007199254740995n
console.log(9007199254740995); // → 9007199254740996
或者,可以调用BigInt()
构造函数
BigInt("9007199254740995"); // → 9007199254740995n
BigInt
文字也可以用二进制、八进制或十六进制表示
// binary
console.log(0b100000000000000000000000000000000000000000000000000011n);
// → 9007199254740995n
// hex
console.log(0x20000000000003n);
// → 9007199254740995n
// octal
console.log(0o400000000000000003n);
// → 9007199254740995n
// note that legacy octal syntax is not supported
console.log(0400000000000000003n);
// → SyntaxError
BigInt
与 Number
还是不同类型的数据
console.log(10n === 10); // → false
console.log(10n == 10); // -> true
console.log(typeof 10n); // → bigint
console.log(typeof 10); // → number
除一元加号(+
)运算符外,所有算术运算符都可用于BigInt
+10n // TypeError: Cannot convert a BigInt value to a number
五、JavaScript
精度问题
在JS
中,会遇到这么个问题:
0.1 + 0.2 == 0.3 // ->false
原因:
在ECMAScript®语言规范中可以看到,ECMAScript
中的Number
类型遵循IEEE 754
标准。使用64位固定长度来表示。
IEEE754
标准包含一组实数的二进制表示法。它有三部分组成:
- 符号位
- 指数位
- 尾数位
三种精度的浮点数各个部分位数如下:
JavaScript
使用的是64位双精度浮点数编码,所以它的符号位
占1
位,指数位占11
位,尾数位占52
位。
符号位
就是标识正负的,1
表示负
,0
表示正
;指数位
存储科学计数法的指数;尾数位
存储科学计数法后的有效数字;
在JS
计算时,将 0.1 和 0.2 转为了二进制
0.1的二进制:0.0001100110011001100110011001100110011001100110011001101
0.2的二进制:0.001100110011001100110011001100110011001100110011001101
由于限制,有效数字第53
位及以后的数字是不能存储的,它遵循,如果是1
就向前一位进1
,如果是0
就舍弃的原则。
解决办法:
- 计算的时候设置误差
- 引入第三方类库(如
math.js
、number-precison
) - 使用
toFixed()
方法 - 封装计算机类(加减乘除)
ES6
中Number
对象新增Number.EPSILON
方法,用于表示Javascript
的最小精度
Number.ERSILON = Math.pow(2,-52) = 2^-52
Javascript
中:
- 最大可存储数值:
Number.MAX_VALUE
(大于最大安全数字) - 最小可存储数值:
Number.MIN_VALUE
(小于最小安全数字) - 最大安全数字:
Number.MAX_SAFE_INTEGER
- 最小安全数字:
Number.MIN_SAFE_INTEGER
- 当数值未超过安全数字时,可以安全保存在内存中
- 在
es10
中可以使用BigInt
操作超过最大安全数字的数值 - 当数值超过安全数字但未超过最大数字时,可以表示,但不精确,会自动转为特殊的
Infinity
值
新人一枚,还请多多包涵
文中内容如果有误,欢迎私信或评论指正。
特别鸣谢:conardli的博客
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。