Javascript 变量与数据类型(一)

本章讲解了一些关于JavaScript的变量和数据类型,存储方式,以及 一些其它的知识点。

  • javascript 数据类型
  • 基础数据类型与引用数据类型
  • nullundefined
  • SymbolBigInt
  • Number精度问题

一、数据类型

javascript中的数据类型共有 8 种,其中又分为基本数据类型引用数据类型

基本数据类型有:

  • null -> 空,只有一种值
  • undefined -> 未定义,只有一种值
  • Boolean -> 布尔值,有两种值,truefalse
  • Number -> 数值(包括浮点数),其中包括一些特殊的值(infinityNaN)
  • String -> 字符串,表示一连串的字符
  • Symbol(ES6) -> 一种实例,唯一且不可变的数据类型
  • BigInt(ES10) -> 比Number支持范围还要大的整数值

引用数据类型有:

  • Object -> 对象,包含了Array(数组),Function(函数),Date(时间)等特俗的对象

二、基本数据类型与引用数据类型

这里我们讲解两种类型的时候,我们先要了解一下js的存储机制:
存储就如一个队列一般,声明后由系统分配好内存便一一存放。

  • 栈内存 一般用于存储 基本数据类型
  • 存储大小固定
  • 空间较小
  • 能够直接操作变量,运行效率较高
  • 由系统自动分配空间

栈空间存储方式

存储就如一个个堆放的团子,没有序列,而且大小是动态的(因为可以随意添加删除数据)

  • 堆内存 一般用于存储 引用数据类型
  • 存储大小不固定,可动态调整
  • 空间较大,运行效率低
  • 无法直接操作,需要通过引用地址读取
  • 通过代码进行分配空间

image.png

了解完 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的指向发生了改变:

image.png

原本 str 的值指向 ConaardLi,因为在栈内存中,存储大小是固定不变的,所以在我们个str赋值的时候其实是开辟了新的内存空间用于存储新的 str 的值,然后将 str 的指向修改到了新的内存空间上。

image.png

2.2 引用类型

引用类型实际的值存储在堆中,而在栈中只是存储了个固定长度的引用地址,这个地址指向了引用类型中的值。

var obj1 = {name:"ConardLi"}
var obj2 = {age:18}
var obj3 = function(){...}
var obj4 = [1,2,3,4,5,6,7,8,9]

image.png

引用类型不具备不可变性,所以我们可以通过引用地址来修改他的值

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;

我们把值复制一份后,相当于又开辟了一个新的内存空间存储新的值。两者互相操作互不干扰

image.png

在引用数据类型中:

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

image.png

对于基础数据类型,比较的是双方的值,当值相同时则返回为 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中所有的函数的参数都是按值传递的。

三、nullundefined

null:

  • null,指的是 空的 ,不存在有值
  • null 转化为数值为 0
  • null作为对象的属性值是正常的
  • null 是一个关键字
  • nullobject 类型

undefined:

  • undefined,指的时 未定义的,应该存在值但没有定义
  • undefined转化为数值为 NaN
  • undefined作为对象的属性值是错误的
  • undefined不是一个关键字,而是一个Global全局变量
  • undefinedundefined 类型
  • 以下情况会出现 undefined
  • 未定义的变量

定义但为声明的变量
对象中不存在或未赋值的属性
函数中未传递的参数
没有返回值的函数默认返回

相同点:

都是基本数据类型的值,都保存在本地栈内存中

不同点:

类型不同(null=>object , undefined=>undefined)
转换数值不同(null=> 0 , undefined=>NaN)
undefined == null 返回 true
undefined === null 返回 false

四、SymbolBigInt

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_INTEGERNumber.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

BigIntNumber还是不同类型的数据

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标准包含一组实数的二进制表示法。它有三部分组成:

  • 符号位
  • 指数位
  • 尾数位

三种精度的浮点数各个部分位数如下:

image

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.jsnumber-precison)
  • 使用toFixed()方法
  • 封装计算机类(加减乘除)
  • ES6Number对象新增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的博客


WANTED
0 声望0 粉丝