JavaScript的数据类型

数据类型(type):能够表示并操作的值得类型
变量(variable):一个值的符号名称,通过名称获得对值的引用
  • 原始类型(primitive type)

    • 数字

    • 字符串

    • 布尔值

  • 对象类型(object type)

  • 特殊的原始值

    • null(空)

    • undefined(未定义)

数字

JavaScript不区分整数值和浮点数,所有数字均用浮点数(IEEE754)表示

整型

  • 十进制:0、9、314124

  • 十六进制(以0x或0X为前缀):0xff(255)

  • 八进制:部分支持,ES6严格模式不支持

浮点型

  • 含有小数点:3.14、.475

  • 指数计数法:9.234e1231、2.31E-1423523

运算

  • 加(+)、减(-)、乘(*)、除(/)、求余(%)

  • 特殊情况

    • 溢出(overflow):数字运算结果超过JavaScript所能表示的数字上限,结果为一个无穷大值,用Infinity表示

      1
      2
      3
      4
      1 / 0                   // => Infinity
      -1 / 0 // => -Infinity
      1 + Infinity // => Infinity
      Infinity + Infinity // => Infinity

      Number.MAX_VALUE + 1结果并非Infinity(1.7976931348623157e+308)。参考为什么在 js 中 Number.MAX_VALUE + 1 不是 Infinity?

    • 下溢(underflow):运算结果无限接近0并比JavaScript能表示的最小值还小,此时JavaScript返回0。当一个负数发生下溢时,返回-0

      1
      2
      0 === -0                // => true
      1 / 0 === 1 / -0 // => false
    • 非数字(NaN)

      1
      2
      3
      4
      0 / 0                   // => NaN
      Infinity - Infinity // => NaN
      Infinity / Infinity // => NaN
      Math.sqrt(-1) // => NaN

      NaN与任何值都不相等,包括自身,无法用x == NaN判断。当且仅当x为NaNx != x为true

      1
      2
      var x = NaN;
      console.log(x != x); // => true

二进制浮点数和四舍五入错误

用JavaScript使用实数时,常常只是真实值的一个近似表示

浮点数转化为二进制可能会产生无限循环小数,由于IEEE754尾数位数限制,需要将后面多余的位截掉,由于指数位数不相同,运算时需要对阶运算,这部分也可能产生精度损失

0.1 -> 0.0001100110011001…(无限循环)
0.2 -> 0.0011001100110011…(无限循环)

1
2
3
4
5
6
var x = 0.3;
var y = 0.2;
var z = 0.1;
(x - y) == (y - z); // => false
(x - y) == 0.1; // => false
(y - z) == 0.1; // => true

可以通过转化为整数解决这个问题

1
(10 * x - 10 * y) == (10 * y - 10 * z);     // => true

日期和时间

1
2
3
4
5
6
7
8
9
10
var then = new Date(2018, 0, 1);               // => 2018年1月1日
var later = new Date(2018, 0, 1, 18, 0, 0); // => 2018年1月1日18点
var now = new Date(); // => 当前日期和时间
var elapsed = now - then; // => 计算时间间隔的毫秒数
later.getFullYear() // => 2019
later.getMonth() // => 2,从0开始计数
later.getDate() // => 1,从1开始计数
later.getDay() // => 5,得到星期几,0为周日
later.getHours() // => 当地时间小时
later.getUTCHours() // => 使用UTC表示小时的时间,基于时区
  • 获取当月第一天

    1
    new Date(year, month - 1, 1)
  • 获取上月最后一天

    1
    new Date(year, month - 1, 0)

字符串

由16位值组成的不可变的有序序列。每个字符通常来自Unicode字符集

字符串直接量

  • 由单引号或双引号括起来的字符序列。

  • 由单引号定界的字符串中可以包含双引号,由双引号定界的字符串中可以包含单引号。

    1
    2
    3
    4
    ''                          // => 空字符串
    'abcdefg' // => 字符串abcdefg
    '3.14' // => 字符串3.14
    'name = "tfcx"' // => 字符串(name = "tfcx")
  • 字符串直接量可以拆分成数行,每行必须以反斜线\结束,反斜线和行结束符都不算字符串直接量的内容

    1
    2
    3
    4
    var str = 'tong\
    hao'
    alert(str); // tong hao
    alert(str.length); // 9

    换行时行前空格也算作字符

  • 希望字符串另起一行可以加\n\n占一个字符

    1
    2
    3
    4
    var str = 'tong\nhao'
    alert(str); // => tong
    // hao
    alert(str.length); // => 8

字符串的使用

  • 字符串连接

    +用于字符串表示将第二个字符串拼接在第一个之后

    1
    'XJ' + 'TU'                  // => XJTU

    连接数字和字符串时,从左往右计算,数字转换为字符串

    1
    2
    'asd' + 1 + 2                // => asd12
    1 + 2 + 'asd' // => 3asd
  • 其他方法

    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
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    var str = 'hello, world';
    str.length // => 12
    str.charAt(0) // => h
    str.charAt(str.length - 1) // => d\
    str.toUpperCase() // => HELLO, WORLD
    str.toLowerCase() // => hello, world
    str.replace('h', 'H') // => Hello, world

    /**
    * String.substring(from, to) 返回字符串子串
    * @param {number} from - 非负整数,指定要提取的子串的第一个字符在string中的位置
    * @param {number} to - 非负整数,比要提取的子串的最后一个字符的位置大1;若省略则子串持续到末尾
    * @returns {String} - 返回新的字符串,为原字符串from到to-1的副本
    */
    str.substring(1, 4) // => ell
    str.substring(7) // => world

    /**
    * String.slice(start, end) 提取一个子串
    * @param {number} start - 切片开始的索引,如果为负,则从尾部开始计算
    * @param {number} end - 紧跟着切片结尾的字符串索引;如果不指定,则切片将包括从start开始到结尾的所有字符;如果为负,则从尾部开始计算
    * @returns {String} - 返回新的字符串,为原字符串start到end但不包括end的所有字符的副本
    */
    str.slice(2, 6) // => llo,
    str.slice(9) // => rld
    str.slice(-4, -1) // => orl
    str.slice(4, -1) // => o, worl

    /**
    * String.split(delimiter, limit) 将一个字符串切分成一个由字符串组成的数组
    * @param delimiter - string切分处的字符串或正则表达式
    * @param limit {number} - 可选,指定已返回数组的最大长度
    * @returns {Array} - 一个由字符串组成的数组
    */
    str.split(',') // => ['hello', ' world']
    str.split('o', 2) // => ['hell', ', w']

    /**
    * String.indexOf(substring, start) 搜索一个子串
    * @param substring {String} - 在String中搜索的子串
    * @param start {number} - 可选的整数,指定该次搜索在字符串中的开始位置,合法值为0到String.length - 1;若省略,则从头开始
    * @returns {number} - 返回substring第一次出现的位置,若没找到则返回-1
    */
    str.indexOf('ll') // => 2
    str.indexOf('lluj') // => -1

    在JavaScript中字符串都是固定不变的,类似replace()等方法都返回新字符串,原字符串本身不发生改变;字符串也可看做只读数组,用方括号访问单个字符

布尔值

布尔值类型只有truefalse,任意JavaScript的值都可以转换为布尔值

undefined, null, 0, -0, NaN, ''会被转换为false;所有其他值都会转换成true

null和undefined

  • null是JavaScript的关键字,常用来描述“空值”。null是一个特殊的对象值,通常认为null是它自有类型的唯一一个成员,表示数字、字符串和对象是“无值”的。

    1
    typeof null                         // => object
  • undefined是预定义的全局变量,不是关键字,ES5中是只读的

    1
    typeof undefined                         // => undefined
    • undefined是变量的一种取值,表示变量没有初始化;

    • 如果要查询对象的属性或数组元素的值时返回undefined则说明这个属性或者元素不存在;

    • 如果函数没有返回任何值,则返回undefined

    • 引用没有提供实参的函数形参的值也只会得到undefined

    1
    2
    null == undefined                         // => true
    null === undefined // => false

全局对象

  • 全局对象的属性是全局定义的符号,JavaScript程序可以直接使用。当JavaScript解释器启动时(或任何web浏览器加载新页面时),将创建一个新的全局对象,并给他一组定义的初试属性:

    • 全局属性:undefinedInfinityNaN

    • 全局函数:isNaN()parseInt()eval()

    • 构造函数:Date()RegExp()String()Object()Array()

    • 全局对象:MathJSON

  • 在客户端JavaScript中,Window对象充当了全局对象。

  • 当初次创建时,全局对象定义了JavaScript中所有的预定义全局值。这个特殊对象同样包含了为程序定义的全局值。如果代码声明了一个全局变量,这个全局变量就是全局对象的一个属性。

包装对象

存取字符串、数字或布尔值的属性时创建的临时对象称作包装对象。

例:

1
2
3
var str = 'hello, world';
console.log(str.substring(str.indexOf(' ') + 1, str.length));
// => world

当引用字符串str的属性,JavaScript就会将字符串值通过调用new String(s)的方式转换成对象,这个对象继承了字符串的方法,并被用来处理属性的引用。一旦属性引用结束,这个新创建的对象就会销毁。

1
2
3
4
var str = 'test';
str.len = 4;
var t = str.len;
console.log(t); // => undefined

修改只发生在临时对象身上,而临时对象并未继续保留下来

1
2
3
4
5
6
var str = 'test';
var Str = new String(str);
console.log(typeof str); // => string
console.log(typeof Str); // => object
console.log(str == Str); // => true
console.log(str === Str); // => false

不可变的原始值和可变的对象引用

原始值:数字、字符串、布尔值、undefinednull

对象:数组、函数等

原始值

  • 原始值用任何方法都不能更改;字符串的所有方法均返回一个新的字符串

    1
    2
    3
    var str = 'test';
    var str1 = str.toUpperCase();
    console.log(str); // => 'test'
  • 原始值的比较是值的比较:值相等的时候才相等;对于字符串,当且仅当长度相等且每个索引的字符串都相等时才相等。

对象

  • 对象的值是可修改的

    1
    2
    3
    var arr = [1, 2, 3];
    arr[1] = 5;
    console.log(arr); // => [1, 5, 3]
  • 对象的比较不是值的比较,即使两个对象包含同样的属性及相同的值,他们也不相等;各个索引元素完全相同的两个数组也不相等

    1
    2
    3
    var obj1 = { x: 1 },
    obj2 = { x: 1 };
    console.log(obj1 === obj2); // => fasle
  • 对象是引用类型(reference type),对象的比较均是引用的比较,当且仅当它们引用同一个基对象时才相等

    1
    2
    3
    4
    5
    var arr1 = [1, 2, 3];
    var arr2 = arr1;
    console.log(arr1 === arr2); // => true
    arr2[2] = 9;
    console.log(arr1[2]); // => 9
  • 想得到一个数组或对象的副本,必须显式复制对象的每个属性或数组的每个元素

    1
    2
    3
    4
    5
    var arr1 = [1, 2, 3];
    var arr2 = [];
    for (var i = 0; i < arr1.length; i++) {
    arr2[i] = arr1[i];
    }
  • 如果想比较两个单独的对象或者数组,则必须比较它们的属性或元素

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function euqalArrays(arr1, arr2) {
    if (arr1.length != arr2.length) {
    return false;
    }
    for (var i = 0; i < arr1.length; i++) {
    if (arr1[i] != arr2[i]) {
    return false;
    }
    }
    return true;
    }

类型转换

原始值的转换

值(转换为) 字符串 数字 布尔值 对象
undefined ‘undefined’ NaN false throw TypeError
null ‘null’ 0 false throw TypeError
true ‘true’ 1 new Booleaan(true)
false ‘false’ 0 new Boolean(false)
‘’(空字符串) 0 false new String(‘’)
‘1.2’ 1.2 true new String(‘1.2’)
‘one’ NaN true new String(‘one’)
0 ‘0’ false new Number(0)
-0 ‘0’ false new Number(-0)
NaN ‘NaN’ false new Number(NaN)
Infinity ‘Infinity’ true new Number(Infinity)
-Infinty ‘-Infinity’ true new Number(-Infinity)

以数字表示的字符串转换为数字时,允许开头或结尾带空格,但开头或结尾处的任意非空格字符以及数字中间的任意字符会造成字符串转换为NaN

1
2
3
4
var str = '  3.14  ',
str2 = '3. 14';
console.log(Number(str1)); // => 3.14
console.log(Number(str2)); // => NaN

显式类型转换

  • 最简单的方法是使用Boolean()Number()String()Object()函数

    除了nullundefined之外的任何值都具有toString()方法,这个方法的执行结果通常和String()方法的返回结果一致

  • JavaScript中的某些运算符会做隐式的类型转换

    1
    2
    3
    x + ''                               // => String(x)
    +x // => Number(x)
    !!x // => Boolean(x);
  • Number类转换为String的方法

    • toString()方法可以接受表示转换基数的可选参数,如果不指定,则默认十进制

      1
      2
      3
      4
      var n = 15;
      n.toString(2) // => '1111'
      n.toString(8) // => '17'
      n.toString(16) // => 'f'
    • toFixed()根据小数点后指定位数将数字转换为字符串

      1
      2
      3
      4
      var n = 3.141;
      n.toFixed() // => 3
      n.toFixed(2) // => 3.14
      n.toFixed(4) // => 3.1410
    • toExponential()使用指数计数法将数字转换为指数形式的字符串,其中小数点前只有一位,小数点后的位数由参数指定

      1
      2
      3
      4
      var n = 12345.6789;
      n.toExponential() // => '1.23456789e+4'
      n.toExponential(2) // => '1.23e+4'
      n.toExponential(9) // => '1.234567890e+4'
    • toPrecision()根据指定的有效数字位数将数字转换成字符串

      1
      2
      3
      4
      var n = 12345.6789;
      n.toPrecision(3) // => '1.23e+4'
      n.toPrecision(7) // => '12345.68'
      n.toPrecision(10) // => '12345.67890'
  • String类转换为Number的方法

    • 通过Number()方法只能基于十进制,并且不能出现非法的尾随字符

      1
      2
      var str = '3.14qwe'
      Number(str) // => NaN
    • parseInt()方法只解析整数,如果字符串前缀是0x0X将被认为是十六进制;parseFloat()可以解析整数和浮点数。调用时会跳过任意数量的前导空格,尽可能解析更多数字字符,并忽略后面的内容。如果第一个非空格字符是非法字符,则返回NaN

      1
      2
      3
      4
      5
      parseInt('3asd')                // => 3
      parseInt('-3.14') // => -3
      parseInt('0xff') // => 255
      parseFloat('3.14qwe') // => 3.14
      parseFloat('qw12.324') // => NaN
    • parseInt()方法可以接受第二个可选参数,这个参数指定数字转换的基数,合法的取值范围是2~36

      1
      parseInt('1010', 2)             // => 10

对象转换为原始值

  • 对象到布尔值:所有的对象(包括数组和函数)都转换为true

    1
    new Boolean(false)                  // 将被转换为true
  • 对象到字符串和对象到数字:通过调用待转换对象的一个方法来完成的(这里提到的转换规则只适用于本地对象)

    • toString():作用是返回一个反映这个对象的字符串

      1
      2
      3
      4
      5
      {x: 1, y: 2}.toString()             // => '[object Object]'
      [1, 2, 3].toString() // => '1,2,3'
      (function(x) { x++; }).toString() // => 'function (x) { x++; }'
      new Date(2019, 1, 1).toString() // => 'Fri Feb 01 2019 00:00:00 GMT+0800 (CST)'
      (/\d+/g).toString() // => '/\d+/g'
    • valueOf():如果存在任意原始值,默认将对象转换为表示他的原始值;对象是复合值,默认的valueOf方法简单的返回对象本身

      1
      2
      3
      [1, 2, 3].valueOf()                 // => [1,2,3]
      {x:1, y:2}.valueOf() // => {x:1, y:2}
      new Date(2019, 1, 1).valueOf() // => 1548950400000
  • 对象到字符串的转换步骤

    • 如果对象具有toString()方法,则调用这个方法;若返回一个原始值,JavaScript将这个值转换为字符串,并返回字符串;

    • 如果对象不具有toString()方法,或者不返回一个原始值,JavaScript将调用valueOf()方法;若返回一个原始值,JavaScript将这个值转换为字符串,并返回字符串;

    • 否则JavaScript无法从toString()valueOf()获得一个原始值,将抛出类型错误异常

  • 对象到数字的转换中,首先尝试valueOf()

变量作用域

全局作用域与函数作用域

全局变量具有全局作用域;在函数内声明的变量只在函数体内有定义

  • 在函数体内,局部变量的优先级高于同名的全局变量

    1
    2
    3
    4
    5
    6
    7
    8
    var scope = 'asd';
    function f() {
    var scope = 'qwe';
    console.log(scope);
    return scope;
    }
    f(); // => qwe
    console.log(scope); // => asd
  • 声明局部变量时必须使用var

    1
    2
    3
    4
    5
    6
    7
    8
    scope = 'asd';
    function f() {
    scope = 'qwe';
    console.log(scope);
    return scope;
    }
    f(); // => qwe
    console.log(scope); // => qwe

函数作用域和声明提前

  • JavaScript没有块级作用域

  • 函数作用域:变量在声明他们的函数体以及这个函数体嵌套的任意函数体内都是有定义的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function f(o) {
    var i = o;
    if (typeof o == 'object') {
    var j = 0;
    for (var k = 0; k < 10; k++) {
    console.log(k); // => 0-9
    }
    console.log(k); // => 10
    }
    console.log(j); // => 0
    }
  • 声明提前:JavaScript函数里声明的所有变量(不涉及赋值)都被提升到函数体的顶部

    1
    2
    3
    4
    5
    6
    var scope = 'asd';
    function f() {
    console.log(scope); // => undefined
    var scope = 'qwe';
    console.log(scope); // => qwe
    }