03月07, 2019

JS基础(2)——基本语法(3)——数据类型

所谓数据类型,就是指数据不同的种类。

在 JavaScript 中,存在 6 种简单数据类型以及 1 种复杂数据类型。接下来,就让我们首先一起来看一下 JavaScript 中 6 种简单的数据类型。

2-3-1 undefined 类型

undefined类型就只有一个值,undefined。在使用变量但是没有为其赋值的时候,这个变量的值就是 undefined。

还需要注意一点,就是没有申明的变量,使用时会报错,而不是 undefined。但是打印其类型的时候,显示的类型却是 undefined

let i;
console.log(typeof i); // undefined
console.log(typeof j); // undefined
console.log(i); // undefined
console.log(j);
// ReferenceError: j is not defined

2-3-2 null 类型

null类型的值也是只有一个,就是 null。null表示一个空的对象。从逻辑角度来看,null 值表示一个空对象指针,这也正是用typeof操作符检测 null 值时会返回 object 的原因。

示例:

let i = null;
console.log(typeof i); // object

实际上,undefined 值是从 null 值派生而来的,因此当我们对这两个数据类型进行相等测试时,会返回true。如下:

if(null == undefined){
    console.log('Yes'); // Yes
}

2-3-3 布尔类型

布尔类型,也被称为boolean类型。这个类型的值只有两个,一个是 true,另一个是 false。

let i = true;
console.log(i); // true
console.log(typeof i); // boolean

需要注意的是,这两个值与数字值不是一回事,因此 true 不一定等于 1,而 false 也不一定等于0。
还有一点就是布尔类型的字面值 true 和 false 是区分大小写的。也就是说,True 和 False 都不是布尔值。

虽然布尔类型的字面值只有 2 个,但是 ECMAScript 中所有类型的值都可以转换为布尔类型。可以使用Boolean()函数将其他类型转换为布尔值。

console.log(Boolean("Hello")); // true
console.log(Boolean(42)); // true
console.log(Boolean(0)); // false

最后需要注意的就是下面的 9 个值是对应着布尔类型里面的假值:

  • "":双引号的空字符串
  • '':单引号的空字符串
  • ``:空字符串模板
  • 0:数字0
  • -0:JavaScript 中 -0 和 0 为不同的值
  • NaN
  • false
  • null
  • undefined

2-3-4 数字类型

数字类型又被称之为number类型。number类型的值可以分为整数和实数两大类。

1. 整数

整数可以分为正整数和负整数,如下:

let a = 12;
let b = -7;

这个虽然没什么好说的,但是还是有一个注意点,那就是进制问题。二进制以0b开头,八进制以0开头,十六进制以0x开头,示例如下:

// 二进制
let a = 0b101; // 5
// 八进制
let b = 017; // 15
// 十进制
let c = 21; // 21
// 十六进制
let d = 0xFD; // 253
console.log(a,b,c,d);

需要注意的是,不管参与运算的变量是多少进制,最终计算结果仍然会为十进制。

在 ECMAScript 6 中提供了八进制数值新的写法,使用0o作为前缀,如下:

let a = 017;
let b = 0o17;
console.log(a,b); // 15 15

2. 实数

所谓实数,就是我们平常所常见的小数,或者称之为浮点数。在 JavaScript 里面,表示浮点数的方式有两种,小数型和科学记数法型。示例如下:

let a = 3.14;
let b = 9.12e+2;
console.log(a,b); // 3.14 912

3. 数值范围(扩展)

由于内存限制,JavaScript 并不能保存世界上所有的数值。在 JavaScript 中能够表示的最小数值在绝大多数浏览器中为5e-324,而最大值为1.7976931348623157e+308

通过Number.MIN_VALUENumber.MAX_VALUE我们可以查看到 JavaScript 中支持的最小值和最大值:

console.log(Number.MIN_VALUE); // 5e-324
console.log(Number.MAX_VALUE); // 1.7976931348623157e+308

如果某次计算的结果超出了 ECMAScript 的数值范围,那么正数会被转化为infinity(正无穷),负数会被转换为-infinity(负无穷)。例如,根据 ECMAScript 的规定:

大于等于 2 的 1024 次方的数为无穷大

let i = Math.pow(2,1024);
console.log(i); // Infinity

小于等于 2 的 1024 次方的数为无穷小

let i = -Math.pow(2,1024);
console.log(i); // -Infinity

如果某次计算返回了 infinity 值,那么该值无法参与下一次计算。因为 infinity 不是能够参与计算的数值。要想确定一个数值是不是有穷的,可以使用isFinite()函数。

示例:如果是无穷大数,会返回 false,否则返回 true(其实这个函数就是用于判断一个变量是否为数值的)

let i = Math.pow(2,1024);
console.log(isFinite(i)); // fasle
let j = 7;
console.log(isFinite(j)); // true

4. NaN

英文全称为 Not a Number,即非数值。这个数值用于表示本来要返回数值的操作数未返回数值的情况(这样就不会抛出错误了)。NaN 有两个特点:

1. 任何涉及 NaN 的操作都会返回 NaN

let a = NaN + 10;
console.log(a); // NaN

2. NaN 和任何值都不相等,包括它自己本身

console.log(NaN === NaN); // false

除此之外,ECMAScript 还定义了一个isNaN()函数,来判断一个参数是否不是数值。isNaN()函数在接收到一个值之后,会尝试将这个值转换为数值。示例如下:

console.log(isNaN(NaN)); // true
console.log(isNaN("123")); // false
console.log(isNaN(123)); // false
console.log(isNaN("Hello")); // true
console.log(isNaN(true)); // false

最后需要补充说明一下的是,NaN是属于number类型的:

console.log(typeof NaN); // number

5. 数值转换

在 JavaScript 里面,有 3 个函数可以将非数值的数据转为数值,分别是Number()parseInt()以及parseFloat()。下面我们将依次对这几个方法进行介绍:

Number():可以将非数值转为数值。有下面几个规则需要注意:

  • 如果是 Boolean 值,true 和 false 将分别被转换为 1 和 0
  • 如果是数字,那么就是简单的传入和返回
  • 如果是 null 值,那么返回 0
  • 如果是 undefined,那么返回 NaN

示例如下:

console.log(Number(true)); // 1
console.log(Number(false)); // 0
console.log(Number(10)); // 10
console.log(Number(null)); // 0
console.log(Number(undefined)); // NaN

如果是字符串,那么又有如下的规则:

  • 如果字符串只包含数字,则将其转为十进制,即 "1" 会变成 1,"123" 变成 123,而 "011" 会变为 11(注意这里不会被当成八进制来处理)
  • 如果字符串中包含有效的十六进制格式,如 "1.1",则将其转换为对应的浮点数值
  • 如果字符串中包含有效的十六进制格式,例如 "0xf",则会将其转换为相同大小的十进制整数
  • 如果字符串是空的,则将其转换为 0
  • 如果字符串包含上述格式之外的字符,则将其转换为 NaN

示例:

console.log(Number("1")); // 1
console.log(Number("012")); // 12
console.log(Number("0o10")); // 8
console.log(Number("0b111")); // 7
console.log(Number("3.14")); // 3.14
console.log(Number("0xf")); // 15
console.log(Number("")); // 0
console.log(Number("123Hello")); // NaN

parseInt():也是将一个非数值转为数值。

说明:相比number()函数,parseInt()会更多的看是否有数字,有就会将其转换为数值。

最简单的例子为number()函数转换 "123Hello" 时会转换为 NaN,而parseInt()则会将其转换为 123。

碰到空字符串时,number()函数会将其转换为 0,而parseInt()则会将其转换为 NaN

最后是在碰到小数时,会有一个取整的过程。例如 "3.14" 会被转换为 "3"

示例:

console.log(parseInt("1")); // 1
console.log(parseInt("012")); // 12
console.log(Number("0o10")); // 8
console.log(Number("0b111")); // 7
console.log(parseInt("3.14")); // 3
console.log(parseInt("0xf")); // 15
console.log(parseInt("")); // NaN
console.log(parseInt("123Hello")); // 123

我们的parseInt()从 ECMAScript 5 开始还提供了第二个参数,那就是指定参数是多少进制,这样parseInt()函数就可以将指定的进制转换为十进制,如下:

console.log(parseInt("012")); // 12
console.log(parseInt("012",8)); // 10
// 如果是 16 进制数,后面添加上参数 16 后,字符串前面可以不写0x
console.log(parseInt("AF"));//NaN
console.log(parseInt("AF",16));//175

除此之外,我们的parseInt()函数的第 2 个参数还可以是任意值,8 进制前面不用加前面的0,十六进制不用加前面的 0x

console.log(parseInt("21",3)); // 7
console.log(parseInt("10",2)); // 2
console.log(parseInt("11",8)); // 9
console.log(parseInt("AF",16)); // 175

parseFloat():将非数值转为浮点数

说明:parseFloat()只解析十进制值,所以没有第二个参数,该函数会将带有小数点的字符串转换为小数。如果没有小数点的数会被转换为整数。

同样的parseFloat()也是尽可能转换更多的数值。例如 "123Hello" 会被转换为 123

console.log(parseFloat("21")); // 21
console.log(parseFloat("123Hello")); // 123
console.log(parseFloat("0xA")); // 0
console.log(parseFloat("3.14")); // 3.14
console.log(parseFloat("22.34.5")); // 22.34
console.log(parseFloat("0908.34")); // 908.34
console.log(parseFloat("3.1415e2")); // 314.15

ECMAScript 6 将全局方法parseInt()parseFloat()等方法,移植到number对象上面,行为完全保持不变,这么做的目的,是为了逐步减少全局性的方法,使得语言逐步模块化。

// ES5的写法
console.log(parseInt("12.34")); // 12
console.log(parseFloat("12.34#")); // 12.34

// ES6的写法
console.log(Number.parseInt("12.34")); // 12
console.log(Number.parseFloat("12.34#")); // 12.34

6. 静态方法(扩展)

这里是第一次听到静态方法这个说法。什么是静态方法呢?

所谓静态方法,主要是涉及到了面向对象里面的知识,这里简单理解,就是通过该类型能够直接调用的方法。下面介绍两个数字类型的静态方法。

Number.isInteger():用来判断一个值是否为整数。需要注意的是,在 JavaScript 内部,整数和浮点数是同样的存储方法,所以 3 和 3.0 被视为同一个值。

console.log(Number.isInteger(25)); // true
console.log(Number.isInteger(25.0)); // true
console.log(Number.isInteger(25.1)); // false
console.log(Number.isInteger("15")); // false
console.log(Number.isInteger(true)); // false

Number.isFinite():这个方法我们前面在介绍数值范围的时候已经给大家介绍过了。如果一个值是字符串,布尔类型,Infinity,-Infinity,NaN 等(总之就不是数字的时候),则返回 false,如果是数字的话就会返回 true。并且不会进行自动类型转换。

console.log(Number.isFinite(true)); // false
console.log(Number.isFinite(7)); // true
console.log(Number.isFinite(NaN)); // false
console.log(Number.isFinite(Infinity)); // false
console.log(Number.isFinite("23")); // false

7. 实例方法(扩展)

首先解释一下什么叫做实例方法。所谓实例方法,就是指必须要实例化对象,然后在对象上面调用。这也涉及到了后面面向对象的知识。现在只需要知道要先有一个数,然后在这个数上面调用相应的方法。

toFixed():该方法会按照指定的小数位,返回数值四舍五入后的字符串表示(常用于处理货币值)

注意:toFixed()里的参数只接受 0 - 20,若不传参或参数为 undefined 则相当于参数是 0

let num = 10.456;
console.log(num.toFixed(2)); // 10.46
console.log(num.toFixed()); // 10
console.log(num.toFixed(0)); // 10
console.log(num.toFixed(undefined)); // 10
console.log(num.toFixed(-1)); // 报错
// RangeError: toFixed() digits argument must be between 0 and 20

toExponential():返回数值四舍五入后的指数表示法(e表示法)的字符串表示,参数为转换后的小数位数

注意:该方法里的参数只接受 0 - 20,但与toFxied()不同的是,若不传参或参数为 undefined,则保留尽可能多的有效数字,若参数是 0 则表示没有小数部分

let num = 10.456;
console.log(num.toExponential(2)); // 1.05e+1
console.log(num.toExponential()); // 1.0456e+1
console.log(num.toExponential(0)); // 1e+1
console.log(num.toExponential(undefined)); // 1.0456e+1
console.log(num.toExponential(-1)); // 报错
// RangeError: toExponential() argument must be between 0 and 20

toPrecision():接收 1 个参数,表示数值的总位数,自动调用toFixed()toExponential()

注意:toPrecision()里的参数只接受 1 - 21,若不传参或参数为 undefined 则相当于调用toString()方法,转换后的数值总位数不含指数部分

let num = 10.456;
console.log(num.toPrecision(3)); // 10.5
console.log(num.toPrecision(2)); // 10
console.log(num.toPrecision(1)); // 1e+1
console.log(num.toPrecision()); // 10.456
console.log(num.toPrecision(undefined)); // 10.456
console.log(num.toPrecision(0)); // 报错
// RangeError: toPrecision() argument must be between 1 and 21

注意:toFixed()toExponential()toPrecision()这三个方法在小数位用于四舍五入时都不太可靠,跟浮点数不是精确储存有关

console.log((12.25).toPrecision(3)); // 12.3
console.log((12.25).toFixed(1)); // 12.3
console.log((12.25).toExponential(2)); // 1.23e+1
console.log((12.35).toPrecision(3)); // 12.3
console.log((12.35).toFixed(1)); // 12.3
console.log((12.35).toExponential(2)); // 1.23e+1

2-3-5 字符串类型

1. 字符串介绍

这是程序里面使用最为广泛的一种类型。在 JavaScript 里面,可以使用单引号,也可以使用双引号

let a = "abcd";
let b = 'abcd';

这里需要注意一个问题,就是字符串的内容本身包含单引号或者双引号的话,需要和字符串界限符区分开,如下:

let a = "Hello 'World',welcome"; // 正确
let b = 'Hello "World",welcome'; // 正确
let c = "Hello "World",welcome"; // 错误

当然,我们这里使用转义字符也是一个很好的选择,如下:

let a = "Hello 'World',welcome"; // 正确
let b = 'Hello "World",welcome'; // 正确
let c = "Hello \"World\",welcome"; // 正确

字符串这种数据类型非常的霸道,它和其他数据类型相加都会被转换为字符串类型,示例如下:

let a = "abcd";
let b = 13 + a;
let c = 3.14 + a;
let d = true + a;
let e = null + a;
let f = undefined + a;
console.log(typeof b); // string
console.log(typeof c); // string
console.log(typeof d); // string
console.log(typeof e); // string
console.log(typeof f); // string

所以如果我们要让一个非字符串的变量转换为字符串的话,只需要和一个空字符串相加就可以了。

当然,转换字符串事实上我们也有相应的函数来转换,最常见的就是toString()函数。

toString()

说明:该函数会将除了 null 和 undefined 以外的数据类型转换为字符串。

示例:

let a = 10,b = true,c = null,d;
console.log(typeof a.toString()); // string
console.log(typeof b.toString()); // string
console.log(typeof c.toString()); // 报错
console.log(typeof d.toString());

可以看到,程序报错,因为 null 和 undefined 并不能通过toString()函数来转换为相应的字符串。

还有一个知识点,就是toString()函数在转换数值的时候是可以带有参数的。可以将数值指定转换为几进制。示例如下:

let i = 10;
console.log(i.toString()); // 10
console.log(i.toString(2)); // 1010
console.log(i.toString(8)); // 12
console.log(i.toString(10)); // 10
console.log(i.toString(16)); // a

当想要将 null 和 undefined 转换成字符串时,可以使用String()方法。该方法可以将 5 种数据类型都转换为字符串。示例如下:

let a = 10,b = true,c = null,d;
console.log(String(a),typeof String(a)); // 10 string
console.log(String(b),typeof String(b)); // true string
console.log(String(c),typeof String(c)); // null string
console.log(String(d),typeof String(d)); // undefined string

2. 字符串模板

在 ECMAScript 6 中新增了模板字面量。这可以看作是增强版的字符串,它用反引号 ` 进行标识:

let str = `Hello World`;
console.log(str); // Hello World
console.log(typeof str); // string
console.log(str.length); // 11

以上代码中,使用模板字面量语法创建一个字符串,并赋值给 str 变量,这时变量的值与一个普通的字符串无异。

如果想在字符串中包含反引号,只需使用反斜杠\转义即可。

ECMAScript 6 的模板字面量使得多行字符串更加容易创建,因为它不需要特殊的语法,只需要在想要的位置直接换行即可,如下:

let str = `Hello 
World`;
console.log(str);
// Hello
// World
console.log(typeof str); // string
console.log(str.length); // 12

在反引号之内的所有空白符都是字符串的一部分,因此需要特别留意缩进。

模板字面量看上去仅仅是普通 JavaScript 字符串的升级版,但二者之间真正的区别在于模版字面量存在变量占位符。变量占位符允许将任何有效的 JavaScript 表达式嵌入到模板字面量中,并将其结果输出为字符串的一部分。

变量占位符由起始的${与结束的}来界定,之间允许放入任意的 JavaScript 表达式。示例如下:

let name = "xiejie";
console.log(`Hello,${name}`);
// Hello,xiejie

占位符${name}会访问本地变量name,并将其值插入到字符串中。

既然占位符是可以是 JavaScript 表达式,那么可替换的就不仅仅是简单的变量名,可以轻易嵌入运算符、函数调用等,示例如下:

let count = 10,price = 0.25;
console.log(`${count} items cost $${(count*price).toFixed(2)}`);
// 10 items cost $2.50

2-3-6 symbol类型

ECMAScript 5 中包含 5 种原始类型:字符串、数字、布尔值、null 和 undefined。ECMAScript 6 引入了第 6 种原始类型:symbol

ECMAScript 5 的对象属性名都是字符串,很容易造成属性名冲突。比如,使用了一个他人提供的对象,想为这个对象添加新的方法,新方法的名字就有可能与现有方法产生冲突。如果有一种机制,保证每个属性的名字都是独一无二的,这样就从根本上防止了属性名冲突。这就是 ECMAScript 6 引入 symbol 的原因。

我们将在讲解面向对象的时候再具体来讲解 symbol 类型。

2-3-7 简单值和复杂值的区别(扩展)

至此,在 JavaScript 中除了 symbol 以外的简单数据类型,就给大家介绍完了。还剩下一种复杂数据类型,那就是对象,我们会在第 6 章中进行详细的介绍。

这里要给大家扩展的一个知识点,那就是简单数据类型所创建的值被称之为简单值,而复杂数据类型所创建的值被称之为复杂值。

那么简单值和复杂值具体有什么区别呢?下面我们一个一个来看:

1. 简单值(或原始值)

简单值是表示 JavaScript 中可用的数据或信息的最底层形式或最简单形式。简单类型的值被称为简单值,是因为它们是不可细化的。

也就是说,数字是数字,字符是字符,布尔值是 true 或 false,null 和 undefined 就是 null 和 undefined。这些值本身很简单,不能够再进行拆分。由于简单值的数据大小是固定的,所以简单值的数据是存储于内存中的栈区里面的。

let str = "Hello World";
let num = 10;
let bol = true;
let myNull = null;
let undef = undefined;
console.log(typeof str); // string
console.log(typeof num); // number
console.log(typeof bol); // boolean
console.log(typeof myNull); // object
console.log(typeof undef); // undefined

这里面就 null 和 undefined 比较特殊,打印出来的数据类型分别是objectundefined。因为这两个数据类型没有对应的构造函数,当然什么是构造函数我们后面再说,现在我们只需要将 null 和 undefined 看作是特殊的操作符来使用即可。

2. 复杂值

在 JavaScript 中,对象就是一个复杂值。因为对象可以向下拆分,拆分成多个简单值或者复杂值。复杂值在内存中的大小是未知的,因为复杂值可以包含任何值,而不是一个特定的已知值,所以复杂值的数据都是存储于堆区里面。

// 简单值
let str = "Hello World";
let num = 10;
let bol = true;
let myNull = null;
let undef = undefined;
// 复杂值
let obj = {
    name : 'xiejie',
    age : 18,
    isPassed : true
};
let arr = [1,2,3,"Hello",true];

3. 访问方式

按值访问

简单值是作为不可细化的值进行存储和使用的,引用它们会转移其值。

let str = "Hello";
let str2 = str;
str = null;
console.log(str,str2); // null "Hello"

引用访问

复杂值是通过引用进行存储和操作的,而不是实际的值。创建一个包含复杂对象的变量时,其值是内存中的一个引用地址。引用一个复杂对象时,使用它的名称(即变量或对象属性)通过内存中的引用地址获取该对象值。

let obj = {};
let obj2 = obj;
obj.name = "xiejie";
console.log(obj.name); // xiejie
console.log(obj2.name); // xiejie

4. 比较方式

简单值采用值比较,而复杂值采用引用比较。复杂值只有在引用相同的对象(即有相同的地址)时才相等。即使是包含相同对象的两个变量也彼此不相等,因为它们并不指向同一个对象

示例1:

let a = 10;
let b = 10;
let c = new Number(10);
let d = c;
console.log(a === b); // true
console.log(a === c); // false
console.log(a == c); // true
d = 10;
console.log(d == c); // true
console.log(d === c); // false

示例2:

let obj = {name : 'xiejie'};
let obj2 = {name : 'xiejie'};
console.log(obj == obj2); // false
console.log(obj === obj2); // false
let obj3 = {name : 'xiejie'};
let obj4 = obj3;
console.log(obj3 == obj4); // true
console.log(obj3 === obj4); // ture

5. 动态属性

对于复杂值,可以为其添加属性和方法,也可以改变和删除其属性和方法。但简单值不可以:

let str = 'test';
str.abc = true;
console.log(str.abc); // undefined
let obj = {};
obj.abc = true;
console.log(obj.abc); // true

复杂值支持动态对象属性,因为我们可以定义对象,然后创建引用,再更新对象,并且所有指向该对象的变量都会获得更新。

一个新变量指向现有的复杂对象,并没有复制该对象。这就是复杂值有时被称为引用值的原因。复杂值可以根据需求有任意多个引用,即使对象改变,它们也总是指向同一个对象

let obj = {name : 'xiejie'};
let obj2 = obj;
let obj3 = obj2;
obj.name = 'abc';
console.log(obj.name,obj2.name,obj3.name);
// abc abc abc

6. 变量赋值

最后说一下关于变量的赋值,其实是可以分为直接赋值和引用赋值的。直接赋值,就是指将简单值赋值给变量,而引用赋值是指将一个复杂值的引用赋值给变量,这个引用指向堆区实际存在的数据。

直接赋值

let a = 3;
let b = a;
b = 5;
console.log(a); // 3

引用赋值

let a = {value : 1};
let b = a;
b.value = 10;
console.log(a.value); // 10

2-3-8 类型转换

通过前面的介绍,我们已经知道了在 JavaScript 中有好几种不同的数据类型。然而这些数据类型都是可以相互转换的。类型转换可以分为两种,隐性转换显性转换

1. 隐性转换

当不同数据类型之间进行相互运算,或者当对非布尔类型的数据求布尔值的时候,会发生隐性转换。

预期为数字的时候:算术运算的时候,我们的结果和运算的数都是数字,数据会转换为数字来进行计算。

类型 转换前 转换后
number 4 4
string "1" 1
string "abc" NaN
string "" 0
boolean true 1
boolean false 0
undefined undefined NaN
null null 0

预期为字符串的时候:如果有一个操作数为字符串时,使用+符号做相加运算时,会自动转换为字符串。

预期为布尔的时候:前面在介绍布尔类型时所提到的 9 个值会转为 false,其余转为 true

2. 显性转换

所谓显性转换,就是只程序员强制将一种类型转换为另外一种类型。显性转换往往会使用到一些转换方法。常见的转换方法如下:

  • 转换为数值类型:number()parseInt()parseFloat()

  • 转换为布尔类型:Boolean()

  • 转换为字符串类型:toString()String()

当然,除了使用上面的转换方法,我们也可以通过一些快捷方式来进行数据类型的显性转换,如下:

  • 转换字符串:直接和一个空字符串拼接,例如:a = "" + 数据

  • 转换布尔:!!数据类型,例如:!!"Hello"

  • 转换数值:数据1 或 /1,例如:"Hello 1"

本文链接:http://www.yanhongzhi.com/post/js-basis-4.html

-- EOF --

Comments