# 值类型和引用类型

    # 值类型

    值类型又叫基本数据类型,在 JavaScript 中值类型有以下五种:

    • 数值类型
    • 布尔类型
    • undefined
    • null
    • 字符串

    值类型存储在栈(stack)中,它们的值直接存储在变量访问的位置。比如:

    var num = 18;
    var flag = true;
    var un = undefined;
    var nu = null;
    var str = "zhangsan";
    
    1
    2
    3
    4
    5

    上面定义的这些值类型的数据在内存中的存储如下图所示:

    1

    # 引用类型

    引用类型又叫复合数据类型,在 JavaScript 中引用类型有以下三种:

    • 对象
    • 数组
    • 函数

    引用类型存储在堆中,也就是说存储在变量处的值是一个指针,指向存储对象的内存处。比如:

    var arr = [1, 2, 3];
    var p = { name: "张三", age: 18 };
    
    1
    2

    上面定义的这些引用类型的数据在内存中的存储如下图所示:

    1

    # 值类型和引用类型的特征

    # 值类型的特征

    • 值类型的值是不可变的,不可变是指值类型指向的空间不可变。比如:
    var a = 2;
    a = a + 2;
    console.log(a); // 打印结果为 4。
    
    1
    2
    3

    在上述例子中,a 变量指向的值变了,但是 2 的内存没有变。

    • 按值传递的变量之间互不影响。比如:
    var a = 1;
    var b = a;
    a = a + 2;
    console.log(a, b); // 打印结果为 3, 1
    
    1
    2
    3
    4
    • 值类型赋值,直接将值赋值一份。比如:
    var num1 = 10;
    var num2 = num1;
    
    1
    2

    上述代码在内存中的体现为:

    1

    • 当参数为值类型的时候,函数内和函数外的两个变量完全不同,仅仅只是存的值一样而已,修改时互不影响。比如:
    function foo(num) {
      num = num + 1;
    }
    
    var a = 1;
    foo(a);
    console.log(a); // 打印结果为 1
    
    1
    2
    3
    4
    5
    6
    7

    # 引用类型的特征

    • 引用类型赋值,是将地址复制一份。比如:
    var p = { name: "zhangsan", age: 18 };
    var p1 = p;
    
    1
    2

    上述代码在内存中的体现为:

    1

    再来看一段代码:

    var p = { name: "张三", age: 18 };
    var p1 = p;
    console.log(p.name); // 打印结果为张三
    console.log(p1.name); // 打印结果为张三
    p.name = "李四";
    console.log(p.name); // 打印结果为李四
    console.log(p1.name); // 打印结果为李四
    
    1
    2
    3
    4
    5
    6
    7
    • 当参数为引用类型的时候,函数内和函数外的两个变量不同,但是共同指向同一个对象,在函数内修改对象数据时会影响外部。比如:
    function foo(o) {
      o.age = o.age + 1;
    }
    
    var p = { name: "zhangsan", age: 18 };
    
    foo(p);
    
    console.log(p.age); // 打印结果为 19。
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

    注:其实我们可以这样理解。引用类型中的地址是一把钥匙,钥匙指向的是宝藏,复制一把钥匙后,两把钥匙能打开的是同一个宝藏。

    # 调试工具的使用

    在编写 JavaScript 代码时,如果遇见错误,首先在浏览器中运行我们的代码,然后 F12,查看错误信息。比如运行以下代码:

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="UTF-8" />
        <title></title>
      </head>
      <body>
        <script>
          alerttt("hello");
        </script>
      </body>
    </html>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    首先查看控制台信息,在控制台中会有报错提示,一般看这个就能知道问题是什么了。

    1

    在 Sources 中能够清楚的看到哪一行的代码出问题了,会有很明显地提醒。

    1

    如果我们想知道变量的值,在调试的时候,可以加一句 console.log(变量) 语句来打印出来,然后在控制台中看。console.log() 语句也是我们编程中经常需要使用的,因为有时候,我们也不能直观的一下就知道传递进来的值到底是什么,可能需要看半天的逻辑然后计算半天。直接打印出来的方式也有利于帮助我们判断,不过一般情况下大家记得调试完,要把这行语句注释掉或者删掉。

    # 设置断点,逐步执行

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="UTF-8" />
        <title></title>
      </head>
      <body>
        <script>
          for (var i = 1; i <= 100; i++) {
            for (var j = 1; j <= 10; i++) {
              j = i * j;
              console.log(j);
            }
          }
        </script>
      </body>
    </html>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17

    首先运行上述代码,点击 F12 进行调试界面,点击 Sources,设置断点的方法很简单,直接左键点击代码旁边的数字行数。如下所示:

    1

    将变量添加到 Watch 窗口,实时查看它的值的变化,如下所示:

    1

    准备工作做好之后我们还需要点击一下刷新,如下所示,红色箭头所指的地方点击一下:

    1

    然后就可以通过点击运行按钮,逐行运行我们设置断点时的代码,并在 Watch 中查看变量值的变化,如下所示:

    1

    # 异常处理

    在我们实际编程过程中,经常会遇到各种各样的错误,有可能是语法错误,也可能是拼写错误,也可能是浏览器的兼容性问题或者其它莫名其妙的问题。当出现错误时,JavaScript 引擎通常会停止,并生成一个错误信息。那我们应该怎么来调试我们的代码呢?

    # 异常捕获

    我们使用 try-catch 语句开捕获异常,语法为:

    try {
      // 这里写可能出现异常的代码
    } catch (err) {
      // 在这里写,出现异常后的处理代码
    }
    
    1
    2
    3
    4
    5

    例子:

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="UTF-8" />
        <title></title>
      </head>
    
      <body>
        <input type="button" value="点击一下" onclick="message()" />
        <script>
          var txt = "";
    
          function message() {
            try {
              alertt("我爱学习,身体好好"); // 故意写错 alert
            } catch (err) {
              txt = "There was an error on this page.\n\n";
              txt += "Error description: " + err.message + "\n\n";
              txt += "Click OK to continue.\n\n";
              alert(txt);
            }
          }
        </script>
      </body>
    </html>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25

    需要注意以下几点:

    • 语句 trycatch 是成对出现的。
    • 如果在 try 中出现了错误,try 里面出现错误的语句后面的代码都不再执行,直接跳转到 catch 中,catch 处理错误信息,然后再执行后面的代码。
    • 如果 try 中没有出现错误,则不会执行 catch 中的代码,执行完 try 中的代码后直接执行后面的代码。
    • 通过 try-catch 语句进行异常捕获之后,代码将会继续执行,而不会中断。

    # throw 语句

    通过 throw 语句,我们可以创建自定义错误。throw 语句常常和 try catch 语句一起使用。

    例子:

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="UTF-8" />
        <title></title>
      </head>
    
      <body>
        <p>请输入 0 到 100 之间的数字:</p>
        <input id="demo" type="text" />
        <button type="button" onclick="myFunction()">测试输入值</button>
        <p id="throwText"></p>
        <script>
          function myFunction() {
            try {
              var x = document.getElementById("demo").value;
              if (x == "") throw "您输入的值为空";
              if (isNaN(x)) throw "您输入的不是数字";
              if (x > 100) throw "您输入的值太大";
              if (x < 0) throw "您输入的值太小";
            } catch (err) {
              var y = document.getElementById("throwText");
              y.innerHTML = "错误提示:" + err + "。";
            }
          }
        </script>
      </body>
    </html>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28