重新认识JavaScript(二)

333 阅读10分钟

上一篇:重新认识JavaScript(一)

上一篇文章中,着重了解了JavaScript中变量的知识,并详细解释了varlet之间的差异。这一篇,将对数字与字符串类型做一个介绍。

数字类型

在编程中,即便是我们最熟悉的十进制数,也比想象中复杂的多。通常会使用不同的术语来描述不同类型的十进制数。例如:

整数

就是整数,例如10,87,-36等。

浮点数

有小数点或者小数位,例如2.0和3.1415926。

双精度浮点数

是一种特定类型的浮点数,具有比标准符点数更高的精度(意味着比标准浮点数能精确到更大的小数位数)。

甚至还有不同类型的数字系统。十进制是基数10(意味着每列使用0-9),但JavaScript中还有这样的东西:

二进制

计算机最基础的语言:0和1。

八进制

基数8,每列使用0-7。

十六进制

基数16,每列使用0-9,然后是a-f。通常在CSS中设置颜色时,会遇到这些数字。

看上去有些混乱,但是与C/C++,Java这些语言不同,JavaScript中只有一个数据类型Number。这意味着,在JavaScript中,可以用相同的方式处理任何类型的数字。

let myInt = 10;
let myFloat = 3.14;

typeof myInt === 'number'
// => true
typeof myFloat === 'number'
// => true

运算符

算数运算符

算数运算符是处理数字的基本运算符。

运算符 名称 作用 示例
+ 加法 两个数相加 1 + 1
- 减法 用左边的数减去右边的数 2 - 1
* 乘法 两个数相乘 3 * 3
/ 除法 用右边的数除以左边的数 10 / 2
% 取余 在将左边的数分成同右边的数相等的若干部分后,取剩下的余数 11 % 3(返回2)
** 取底数的指数次方,在ES2016中首次引入 3 ** 3(返回27)

注:使用**与使用Math.pow()方法有类似的作用。比如Math.pow(5, 3)中,5是基数,3是指数,相当于5 ** 3

运算符优先级

先来看一个例子:

let result = 8 + 2 * 10 - 5;

按照传统的理解,这里的结果是23,而不是其他的数字。

这其中的原因就是运算符优先级:一些运算符将在计算总和的结果时被应用于其他运算符。这与在学校所学的数学相同,乘法和除法总是先完成,然后是加法和减法。

如果要覆盖运算符优先级,可以对要显示处理的部分加括号,即:

let result = (8 + 2) * (10 - 5);
// => 50

递增递减运算符

有时候,需要反复添加或从数字变量中减去1。这可以方便的使用递增++ 和递减--运算符来完成。

它们最常见于循环之中,后面会详细介绍。

需要注意的是,递增和递减运算符不能直接应用一个数字。原因是通过递增递减运算符,为变量赋于一个新的更新值,而不是对该值直接进行操作。例如:

let num = 7;
num++;
// => 8
num--;
// => 7

另外,还有一个需要注意的地方。递增递减运算符在变量左侧时与在变量右侧时,同一行代码中有些许的区别。

let num1 = 7;
console.log(num1++);
// => 7

let num2 = 7;
console.log(++num2);
// => 8

let num3 = 7;
num3++;
console.log(num3);
// => 8

可以看出,同一行中递增递减运算符在执行赋值与返回操作时,不同的位置有不同的顺序。在变量左侧时,先进行赋值再返回结果;在变量右侧时,先返回结果再进行赋值。

而不同行中,变量中存储的都是计算后的数值。

赋值运算符

赋值运算符是向变量分配值的运算符。在之前的例子中使用了很多次的=就是最基本的一个,它将右边的赋值给左边的变量。

还有一些更加复杂的类型,它们提供了有用的快捷方式,可以使代码更简洁和高效。

运算符 名称 作用 示例 等价于
+= 递增赋值 右边的数值加上左边的变量,然后返回新的变量 x = 3;x += 4 x = 3;x = x + 4;
-= 递减赋值 左边的变量减去右边的数值,然后返回新的变量 x = 6;x -= 3; x = 6;x = x - 3;
*= 乘法赋值 左边的变量除以右边的数值,然后返回新的变量 x = 2;x *= 3 x = 2;x = x * 3;
/= 除法赋值 左边的变量除以右边的数值,然后返回新的变量 x = 10;x /= 5; x = 10;x = x / 5;

请注意,还可以在每个表达式右侧使用其他变量,例如:

let num1 = 3;
let num2 = 4;
num1 += num2;
// => 7

比较运算符

有时候,在程序中会需要真/假测试,然后根据测试的结果进行相应的操作。为此,使用比较运算符。

运算符 名称 作用 示例
=== 严格等于 测试左右值是否相同 5 === 2 + 4
!== 严格不等于 测试左右值是否不相同 5 !== 3 + 3
< 小于 测试左值是否小于右值 10 < 6
> 大于 测试左值是否大于右值 10 > 20
<= 小于或等于 测试左值是否小于或等于右值 3 <= 2
>= 大于或等于 测试左值是否大于或等于右值 5 >= 4

在程序中,可能会看到使用==!=来判断相等和不想等,这些都是JavaScript中的有效运算符。与===!==不同的是,前者测试值是否相同,但数据类型可能不同,后者测试值和数据类型是否都相同。严格版本往往导致更少的错误,这里建议在代码中使用更严格的版本。

另外,如果一个变量的值是NaN,那么不建议用比较运算符来测试结果,例如:

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

而是用:

console.log(isNaN(NaN));
// => true

还有一种情况,用===!==可能无法得出想要的结果,例如:

let num1 = 0.43;
let num2 = 0.1 + 0.33;
console.log(num1 === num2);
// => false

出现这种问题,是因为JavaScript在计算数字运算时,出现了精度丢失。这里有一个简单的方法,就是使用toFixed()来舍入后面的数字:

console.log(num1 === num2.toFixed(2));
// => true

字符串

接下来,把注意力转向文本片段,也就是字符串。

基本知识

字符串与数字的处理方式第一眼看上去十分相似,但是深入挖掘时,将会看到一些显著的差异。

创建一个字符串

let str = 'Hello World.';

就像处理数字一样,声明一个变量,用一个字符串值初始化它,然后返回值。唯一的区别是,在编写字符串时,需要加上引号。

单引号和双引号

在JavaScript中,可以选择使用单引号或者双引号来包裹字符串。

let str1 = 'Hello World.';
let str2 = "Hello.";

这两种方式几乎没有什么区别,只是根据个人喜好来使用。但是,在项目中应该选择一个并坚持使用,不一致的引号混用代码可能会引起混乱,特别是在同一个字符串中使用不同的引号,例如:

let str = 'Hello World.";

这将引发一个错误。浏览器会认为字符串没有被关闭,因为在字符串中使用了不用的引号。下面这种情况是正确的:

let str = 'Hello "World".';

但是,不能在字符串中使用包含的引号:

let str = 'Hello 'World'.';

因为它会混淆字符串结束的位置。

转义字符串中的字符

要修复前面遇到的问题代码,避免相同引号的混淆字符串结束位置的问题,这里引入了转移字符串\\。转义字符串意味着对一个字符做了一些事情,确保它可以被识别为文本,而不是代码的一部分。

let str = 'Hello \'World\'.'

连接字符串

连接字符串的作用是将若干个字符串连接在一起。在JavaScript中使用+操作符。

let str1 = 'Hello ';
let str2 = 'World.';
let str = str1 + str2;
// => Hello World.

变量str的值最终为"Hello World."。

数字与字符

如果是两个数字之间使用+操作符,那么它将会被认为是一个算数运算符。但如果其中一个是字符串,将会出现下面的结果;

let str1 = '编号';
let str2 = 89757
let str = str1 + str2;
// => 编号89757

每一个数字都有一个toString()方法,可以将数字类型转换为字符串类型。相应的,Number对象将把传递给它的任何一个东西变成数字:

let num = Number('666');
typeof num;
// => number

即便是这样:

let num = Number('编号');
typeof num;
// => number

只不过此时,它的值为NaN

常见的字符串处理方法

在JavaScript中,一切东西都可以被当作对象。一旦对象成为字符串类型的实例,就有大量的原型和方法编辑它。

获取字符串长度

很简单,可以轻松的使用length属性:

let str = 'Hello.';
console.log(str.length);
// => 6

检索特定字符串字符

可以使用方括号表示法返回字符串中的任何字符。

let str = 'Hello.';
console.log(str[0]);
// => H

在字符串中查找子字符串并提取

有时候想要查找一个较小的字符是否存在与一个较大的字符中,可以使用indexOf()方法来完成,该方法需要一个参数:想要搜索的字符串。

let str = 'Hello.';
console.log(str.indexOf('ell'));
// => 1

如果目标字符串不存在于这个较大的字符串中,则会返回-1

当知道字符串中子字符串开始的位置,以及想要结束的字符时,slice()方法可以提取它。

let str = 'Hello World.';
console.log(str.slice(0, 3));
// => Hel

slice()方法需要两个参数,第一个参数是开始提取的位置,第二个参数是提取结尾字符之后一个字符的位置。另外,如果想要提取从某个位置开始到字符串结尾的所有字符,可以这么做:

let str = 'Hello World.';
console.log(str.slice(2));
// => llo World.

转换大小写

字符串方法toLowerCase()toUpperCase()将所有字符串分别转换为小写或大写。如果要在数据使用之前对字符串规范化,这两个方法可能非常有用。

let str = 'Hello.';
console.log(str.toLowerCase());
// => hello.
console.log(str.toUpperCase());
// => HELLO.

替换字符串的某部分

可以使用replace()方法将字符串中的一个子字符串替换为另外一个子字符串。

有两个参数:要被替换下的字符串和要被替换上的字符串:

let str = 'Hello World.';
console.log(str.replace('Hello', 'Hi'));
// => Hi World.

需要注意的是,replace()方法其实返回的是一个新的变量,而且并不会对旧的变量做修改,在上面的例子中,str的值仍然是"Hello World."。想要改变这个字符串,需要设置变量的值等于刚才的结果。

私货时间

返回被限制在一个区间的数

function clamp (num, lower, upper) {
    return Math.min(Math.max(num, lower), upper);
}

向下取整

function floor (num) {
    return num >> 0;
}

格式化国际化文本

function languageFormat (str) {
    if (str === null || str === undefined || str === '') {
        return;
    }

    let values = Array.prototype.slice.call(arguments);
    values.shift();
    
    let array = str.match(/{\d}/g);
    for (let i = 0; i < array.length; i++) {
        let index = array[i].match(/\d/)[0];
        if (values.length > index) {
            str = str.replace(array[i], values[index]);
        }
    }
    return str;
}

// 使用方法
let expStr = '经验值:{0}/{1}';
expStr = languageFormat(expStr, 1, 100);
// => 经验值:1/100
// 其中国际化文本中下标顺序与序列化方法中参数顺序一致(从第二个参数开始)