likes
comments
collection
share

你了解JavaScript中的变量吗?

作者站长头像
站长
· 阅读数 21

什么是变量

变量是用来存储的容器,可以存储任意类型的值,并且存储的值是可以改变的(松散类型)。 注意: 变量是存储值的,它并不代表值本身,也就是说变量实际上是用于保存值的占位符。

类型

首先我们要明确一个概念:变量是没有类型的,值才有类型,所谓的变量的类型指的是其绑定的值的类型。

类型的分类

基础类型:undefined、null、boolean、number、string、symbol 复杂类型:Object、Array、Function

当然有其他的说法:Array、Function 都是属于 Object 的子类,所以复杂类型只有 Object 一种。 个人觉得子类这种说法和有多个复杂类型不冲突,毕竟类型是类型,类型的实现是实现,两者并没有必然的因果关系。

undefined

变量未持有值的时候,变量的值就是undefined

看看下面这段代码:

var a;
console.log(a); // undefined
console.log(b); // Uncaught ReferenceError: b is not defined
console.log(typeof a); // undefined
console.log(typeof b); // undefined

输出a和其类型,值是 undefined,这个大家都没有异议。 但b就很坑了,输出b报错,这里应该提示b不存在(not found 之类的),而实际上的错误信息是b is not definedtypeof就让人更头疼了,一个不存在的变量的类型是undefined???这个逻辑说明 typeof 有一个特殊的处理机制:如果什么都不是就返回 undefined 这就让很多人都误以为b不存在等于b is undefined。然后就把undefined未声明对等。但是这个是错误的。

一个神奇的操作 在非严谨模式下,可以为全局表示符undefined赋值(what the f***)

function setUndefined(val) {
    undefined = val;
}

setUndefined('a');
console.log(undefined); // undefined

Number

JavaScript 跟其他语言一个很大不同点就是:没有区分整数和小数,所有的数字都属于**数字(Number)**类型。 Number 是基于IEEE 754标准来实现的,使用的是“双精度浮点数”的格式(64 位的二进制)。 基于这套标准的角度,JavaScript 中其实是没有“整数”,所谓的“整数”其实就是没有小数位的浮点数:

42 === 42.0; // true

为什么这么理解呢? 因为在区分整数与浮点数的语言中,两种数据类型的存储方式是不一样的,整数的存储是直接存储二进制数的(符号位+数值),浮点数是分段存储的(符号位+指数+分数)。两个者从根本上就存在差别,而 JavaScript 中全部都是以浮点数的方式存储的。

浮点数的内存分布

 你了解JavaScript中的变量吗?

数字的显示

科学计数法 一般来说,太大的数字或者太小的数字,会使用科学计数法来记录和显示。

let a = 4e11;
console.log(a); // 400000000000

toExponential 可以将数字转换成科学计数法显示。

let b = 4400000000000;
console.log(b.toExponential()); // "4.4e+12"

toPrecision 可以指定要显示的有效数个数,个数大于实际位数时会自动补0,会根据显示的位数进行四舍五入。

let c = 32.45;
console.log(c.toPrecision(1)); // "3e+1"
console.log(c.toPrecision(2)); // "32"
console.log(c.toPrecision(3)); // "32.5"
console.log(c.toPrecision(5)); // "32.450"

注意: 上面的俩各个方法和toFixed方法返回的都是字符串不是数字。

let d = 32.45;
console.log(d.toExponential()); // "3.245e+1"
console.log(d.toPrecision(3)); // "32.5"
console.log(d.toFixed(1)); // "32.5"

语法问题

下面的写法是否有效?

88.toFixed(3);

这个写法是无效的,因为运算符.会被优先识别成常量数字的一部分(88.是一个有效的数字)。上面的代码会报错:

Uncaught SyntaxError: Invalid or unexpected token

那要怎么写呢?

(88).toFixed(3); // "88.000"
88..toFixed(3); // "88.000"
88 .toFixed(3); // "88.000" 88和.中间是空格

精度问题

精度问题是因为数字存储的时候需要装换成二进制,双精度浮点数的长度是64位,如果小数部分需要的位数大于分数的位数(52位),那么就会出现精度问题。

NaN

NaN表示”不是一个数字“(not a Number),是IEEE 754中的规定的是一类值,表示未定义或不可表示的值,IEEE 754中用指数部分全为1并且小数部分非零来表示NaN。 记住NaN表示的是一类值不是一个值,我们看下NaN的特性:

NaN === NaN; // false
NaN != NaN; // true

这里可能会有一个疑问,NaN指的是一类值,那么NaN != NaN应该不会永远为真(会不会出现两个NaN一样?),这里主要是因为IEEE 754规定了NaN与任意值都不能相等,不过IEEE 754并没有说明设计NaN的原因,个人理解NaN的定义是为了保证数学运算的正确性,毕竟IEEE 754对NaN的定义是未定义或无法表示的值,在数学运算中,假如我们做了两个复杂的数学运算并且想比较他们的值是否相等,那么当且仅当两个运算结果得到的数字是一致的时候,他们才是相等的,如果计算错误或者输入值有问题,导致计算过程中出现NaN,那么计算的结果也会是NaN,两个返回结果比较应该是不等的,这样可以避免很多计算错误导致的问题。(关于NaN的说法,你有没有其他看法,可以在评论区一起交流)

因为NaN不等于任意值,所以它也是JavaScript中唯一一个非自反的值(非自反:x === x不成立)。

第一个: not a Number这个意思很容易引起误会,例如:

let d = 3 / "aaa"; // NaN
console.log(typeof d); // "number"

怎么理解上面代码的意思?? 不是数字的数字是数字类型。 所以我们不能就字面意思来理解,应该将它理解成”无效的数字“更贴切。

第二个: JavaScript提供一个方法来识别NaN:isNaN,这玩意也是个坑,它的作用不是识别传入是的参数是不是NaN,而是识别传入的参数“既不是NaN,也不是数字”。

let e = 3 / 'aaa';
let f = 'aaa';

console.log(isNaN(e)); // true
console.log(isNaN(f)); // true

这次倒是需要从字面意思上去理解:'aaa'和NaN都符合not a Number的定义。

不过es6新增的Number.isNaN是可以正确识别的:

console.log(Number.isNaN(f)); // false

当然我们也可以自己实现这个方法:

if (!Number.isNaN) {
    Number.isNaN = function(num) {
        return num != num;
        // return typeof num === 'number' && window.isNaN(num);
    }
}

String

很多人认为字符串就是字符串数组,虽然两者有很多相似的地方但是,其实大不相同。

let string = 'demo';
let arr = ['d', 'e', 'm', 'o'];

string.length; // 4
arr.length; // 4

string[0]; // d
arr[0]; // d

string[3] = 'f'; // demo
arr[3] = 'f'; // ['d', 'e', 'm', 'f']
  1. string[0]这种写法不一定是合法的,在IE6中这个写法是非法的,要使用string.charAt(1)为什么现在大部分浏览器的都允许这种写法呢? ES5对字符串的描述: 字符串类型是有限的零个或多个 16 位无符号整数值(“元素”)的有序序列 也就是说在ES的中,字符串实际上是被当成一个有序的序列来处理,也就是一个类数组,使用String关键字来定义一个字符串就可以看到:
var s = new String('demo')

打印s可以得到以下的结果:

{
    0: "d"
    1: "e"
    2: "m"
    3: "o"
    length: 4
    __proto__: String
    [[PrimitiveValue]]: "demo"
}

当然,有人会说,这是定一个字符串对象,不是字符串。是的,s是一个字符串对象,但是它其实是等价于var ss = "demo",因为JavaScript在操作ss的时候,也会自动将它转换成new String("demo")的包装对象。

  1. 数组的元素是可修改的,但是字符串不行。因为字符串对应未知的属性并不是可读或配置的。简单说:字符串是不可变的。所有字符串的成员函数都是返回新的字符串,不会变更原有的字符串。

为什么要做一个不可变的设计 个人认为有这几点:

  1. String是一个基础数据类型,在ECMAScript中String是一个原始数据类型,而原始数据类型是不可变的。
  2. es对字符串的定义是“零个或多个16位的字符的有序序列”,空字符串就是代表零,那么如果在一个非零的字符串中将某一位置空,那就很容易引起误会,看到的字符串与实际的长度不匹配。(类似iOS上八分之一个空格的问题)
  3. 字符串在初始化的时候就已经申请了固定长度的内存空间(“demo”在初始化的时候会申请长度为4个字符的空间)如果可变,那么我在原来的字符串上增加,这就意味着要扩展原来已经申请的内存空间。es为了保证字符串的效率,字符串的存放是在一段连续的内存中的,如果现在追加字符串的长度,就要申请新的内存空间,追加的内存空间不能保证跟原来是连续的,这样会对字符串的寻址产生很大的影响。

所以字符串的修改操作是这样的:

var s = 'demo';
s = s + ' demo';
  1. 申请一个长度为4个字符的内存空间,存储demo
  2. 把s和demo绑定
  3. 申请一个长度为9个字符的内存空间,把s + ' demo'存储进去
  4. 把新的字符串与s绑定

变量在内存中

JavaScript的变量存储主要是在栈区和堆区(其他的还有代码区,C和Java还有全局区)

看看变量是怎么存储的: 定义一些变量:

var num = 10,
    bol = true,
    str = "abc",
    obj = {a: 2},
    arr = [1,2,3];

function demo(){ 
    var a=1,
    b='2',
    c={c:3};
};

上面的变量在内存中的情况:

 你了解JavaScript中的变量吗?

可以看到越先定义的遍历越先入栈,函数是最先进去的,因为被提升了。

为什么内存要分栈区和堆区: 主要是为了保证效率和速度 栈区:

  1. 栈内存的大小是固定的(有最大限制),遵循后进先出的原则。存储的都是在编译时就可确定大小的内容。
  2. 存取速度快,仅次于寄存器。(整个栈都是连续的有关联的)
  3. 可以实现快速回收:修改栈顶的指针即可完成自顶向下的销毁。 缺点:大小是固定的,不好扩展

堆区:

  1. 堆相对于栈来说,堆的空间就大很多,用来存储运行时大小不能确定的内容(大小可变化)
  2. 堆在运行时会动态的申请内存空间,由系统进行分配,动态分配和销毁都是比较占时间的,所以相对来说效率较低
  3. 堆的自由度更高,不需要管空间(要多大)和时间(停留多久)的问题

总结

本文总结了下一些基础数据类型的相关的点和一些个人的看法,变量是我们最常用的,也是最容易忽略的,相信大家多多少少都遇到过由变量引起的问题,了解下一些基础的点,可以让我们更好的避开奇奇怪怪的坑。 关于本文的话题,大家有没有其他看法,可以在评论区跟其他人互动下。