S01E02-变量及数据类型

万物起源

变量

  1. 概念
    什么是变量?

    存储值的一个容器或代号

    什么是值?

    存储的数据

  2. 声明变量的几种方式

    • var(ES3)

    • function(ES3)创建函数(函数名也是变量,只不过存储的值是函数类型的)

    • ES6 新增:

      • let
      • const
      • import ES6 的模块导入
      • class 创建类

注意:常量声明必须赋值,而且不能重复赋值。

1
2
3
4
const a // Uncaught SyntaxError: Missing initializer in const declaration

const m = 100;
m = 200; //=> 报什么错?Uncaught Type: Assignment to constant variable.未捕获的类型错误:分配给常量变量

数据类型

值的类型:JS 中的变量是没有类型的,只有值才有。

值的类型可分为:

  1. 基本类型(值类型)
  • Null
  • Undefined
  • String
  • Boolean
  • Number
  • Symbol(ES6 中新增加的一个特殊的类型,唯一的值)
  1. 引用类型
  • 普通对象
    • RegExp(正则对象)
    • Date(日期对象)
    • Math(数学对象)
    • Error(错误对象)
  • Function

注意:说到数据的类型都是大写哦,尽管 typeof 会返回小写的

Function: 特殊的引用类型,不用于存储数据

需要注意的知识点

这部分内容比较基础,不会全部列出,会介绍一些特殊的,需要注意的。

基本包装类型

3 个特殊的基本类型:String、Number、Boolean

在逻辑上讲,基本类型值是没有属性和方法的,但却有 .length 属性和很多的 API,这是因为 JS 底层会自动将 String、Number、Boolean 类型值包装为一个封装对象。

栗子:

1
2
3
4
5
6
var a = new Boolean( false );

if (!a) {
console.log(1);
}
console.log(2);

答案是:输出 2!

null && undefined 的区别

都代表空或者没有,作为值时小写

  • null:空对象指针(没有指向任何的内存空间)
  • undefined:未定义

null 一般都是在初始化值时,先手动的先赋值为 null,然后再给他赋具体的值

1
2
var num = null;
num = 12;

undefined 一般都不是人为手动控制的,大部分都是浏览器自主为空(后面可以赋值也可以不赋值)

1
var num; //=>此时变量的值浏览器给分配的就是 undefined

项目中一些细节问题:
初始化值时,一般初始化为 null,因为它在内存中是不占空间的。而 0、[]、{} 等是有值的,会在内存中占空间。

undeclared 是一种语法错误。访问未声明的变量, 则会抛出异常, 终止执行。ReferenceError:a is not defined。

特殊的 NaN

我们来介绍一个非常特殊的数字:

NaN:not a number,不是一个数字

其实,not a number 容易引起误解,因为 NaN 仍然是数字类型,叫无效数值更准确些。

1
2
var a = 2 / "foo";      // NaN
typeof a === "number"; // true
  1. isNaN():检测当前的数字是不是无效数字
    重学 JS 系列 - 数据类型转换会对 isNaN 有更为细致的讲解。

  2. NaN 的比较

    特殊到自己不等于自己。

    isNaN(num) 常作为语句的条件,来检测是否是有效数字

    1
    2
    3
    if(isNaN(num)){
    }
    // 条件不可以用 Number(num) == NaN

对象字面量语法需要注意的几点

  1. 一般来说,对象的属性名只能是字符串格式的或者数字格式的,不能是其它类型的。
    当对象的属性名是数字时,不支持点表示法。

    1
    2
    3
    4
    5
    6
    7
    var obj = {
    name: 'chen',
    0: 100,
    };
    obj[0] //=>100
    obj['0'] //=>100
    obj.0 //=>Uncaught Syntax: Unexpected number 语法错误

    当属性名是其他格式时,浏览器会把这个值 toString() 转换为字符串,然后再以这个字符串为key进行存储。

    1
    obj[{}] = 300;  //=>先把({}).toString()后的结果作为对象的属性名存储进来 obj['[object Object]']=300
  2. 访问对象的属性

    • 点表示法:对象.属性
    • 方括号表示法:对象[“属性”],可以通过变量来访问属性

    不管是哪种写法,

    • 有这个属性名,则可以正常获取到值(哪怕是 null),赋值操作会修改这个属性的值
    • 没有这个属性名,则获取的结果是 undefined,赋值操作会新增加这个属性

    栗子

    1
    2
    3
    4
    5
    6
    7
    8
    9
    var obj = {
    name:'chen',
    age:9
    };
    var name = 'chen';

    obj.name //=>'chen'
    obj['name'] //=>'chen'
    obj[name] //=>?

基本类型与引用类型的区别

这是非常常见的、又非常基础的面试题哟!

红宝书中是这样概括的:

  1. 存储位置的区别

    • 基本类型的值一般被保存在于栈内存中,引用类型的值是对象,被保存在堆内存中。
    • 包含引用类型的变量的值是一个指向该对象的一个指针,这个指针被保存在栈内存中。
  2. 访问方式的区别

    • 基本类型是按值访问的。因为可以操作存储在变量中的实际的值
    • 引用类型是按引用访问的。因为引用类型的值是保存在堆内存中的对象,这块不同于其它语言,JS 不允许直接访问对象的内存空间。在操作对象时,实际上是操作的是对象的引用而不是实际的对象
  3. 复制操作的区别

    • 基本类型复制的是这个值的一个副本,操作两个变量互不影响。
    • 引用类型复制的其实是一个指针(地址的副本),复制操作结束后,两个变量将指向堆中的同一个对象,改变一个,会影响另一个。

为了彻底理解,我们来看一个的栗子:

1
2
3
4
var a = 12;
var b = a;
b = 13;
console.log(a); //=>12

执行过程是这样子的:

  1. 首先声明一个变量 a、b(变量提升,值为 undefined),在栈内存中开辟一块内存空间存储 12
  2. 执行 var b = a;,复制过程:
    复制一份 12 的副本,在栈内存中重新开辟一块内存空间,存储这个副本,然后将这个副本赋值给变量 b
    注意:原来的 12 和它的副本没有任何关系,在栈内存中占据不同的内存空间,互不影响
  3. 为了验证这句话,我们执行 b = 13,会在栈内存中再开辟一块内存空间,存储 13,将 13 赋值给变量 b,原来的 12 的副本已废弃,修改b的值不会影响a的值

值类型复制的过程,如图所示:

4

引用类型是如何实现复制的呢?还是上栗子吧。。。

1
2
3
4
var obj1 = {m: 20};
var obj2 = obj1;
obj2['m'] = 100;
console.log(obj1.m); //=>100

上面的代码,一起来分析一下:

  1. 首先声明一个变量 obj1、obj2(变量提升,值为 undefined),然后在堆内存中开辟一块内存空间,存储对象的键值对(为这个空间加了一个16进制的地址的标记,就是我们常说的指针),接着将这个地址赋值给变量 obj1
  2. 遇见 var obj2 = obj1;,是这样子复制的:
    复制一份这个地址的副本,在栈内存中重新开辟一块内存空间,存储起来,然后将这个副本赋值给变量 obj2,这时,obj2 和 obj1 指向堆内存中同一个对象,不管修改谁,其实修改的是一个值,所以最后输出 100.

最后,我们画一个图,来形象的展示一个引用类型复制的过程:

3

思考题:

1
2
3
4
5
var obj = {
n: 10,
m: obj.n * 10
};
console.log(obj.m);

答案是:
在 m: obj.n * 10 行报错:Uncaught TypeError: Cannot read property ‘n’ of undefined,思考一下,为什么?

我们一起来分析一下:

  1. 变量提升,obj = undefined
  2. 开辟一个新的堆内存(比如地址是 AAAFFF111),把键值对存储到堆内存中
    -> n: 10
    -> m: obj.n*10 =>obj.n 此时堆内存信息还没有存储完成,空间的地址还没有给 obj,此时的 obj 是undefined, 访问 obj.n 就是在访问 undefined.n

结束

重学 JS 系列 预计 25 篇左右,这是一个旨在帮助大家,其实也是帮助我自己捋顺 JavaScript 底层知识的系列。主要包括变量和类型、执行上下文、作用域及闭包、原型和继承、单线程和异步、JS Web API、渲染和优化几个部分,将重点讲解如执行上下文、作用域、闭包、this、call、apply、bind、原型、继承、Event-loop、宏任务和微任务等比较难懂的部分。让我们一起拥抱整个 JavaScript 吧。

大家或有疑问、或指正、或鼓励、或感谢,尽管留言回复哈!非常欢迎 star 哦!

点击返回博客主页

作者

ZhangLK

发布于

2021-01-03

更新于

2023-07-05

许可协议