第五章 基本引用类型(下)——原始值包装类型、-程序员宅基地

技术标签: JavaScript高级程序设计  javascript  ecmascript  

5.3 原始值包装类型

        为了方便操作原始值,ECMAScript提供了3种特殊的引用类型:Boolean、Number和String。这些类型具有本章介绍的其他引用类型一样的特点,但也具有与各自原始类型对应的特殊行为。每当用到某个原始值的方法或属性时,后台都会创建一个相应原始包装类型的对象,从而暴露出操作原始值的各种方法。来看下面的例子:

let s1 = "some text";
let s2 = s1.substring(2);

        在这里,s1是一个包含字符串的变量,它是一个原始值。第二行紧接着在s1上调用了substring()方法,并把结果保存在s2中。我们知道,原始值本身不是对象,因此逻辑上不应该有方法。而实际上这个例子又确实按照预期运行了。这是因为后台进行了很多处理,从而实现了上述操
作。具体来说,当第二行访问s1时,是以读模式访问的,也就是要从内存中读取变量保存的值。在以读模式访问字符串值的任何时候,后台都会执行以下3步: 

(1) 创建一个String类型的实例;
(2) 调用实例上的特定方法;
(3) 销毁实例。

可以把这3步想象成执行了如下3行ECMAScript代码:

let s1 = new String("some text");
let s2 = s1.substring(2);
s1 = null;

        这种行为可以让原始值拥有对象的行为。对布尔值和数值而言,以上3步也会在后台发生,只不过使用的是Boolean和Number包装类型而已。 

        引用类型与原始值包装类型的主要区别在于对象的生命周期。在通过new实例化引用类型后,得到的实例会在离开作用域时被销毁,而自动创建的原始值包装对象则只存在于访问它的那行代码执行期间。这意味着不能在运行时给原始值添加属性和方法。比如下面的例子:

let s1 = "some text";
s1.color = "red";
console.log(s1.color); // undefined

         这里的第二行代码尝试给字符串s1添加了一个color属性。可是,第三行代码访问color属性时,它却不见了。原因就是第二行代码运行时会临时创建一个String对象,而当第三行代码执行时,这个对象已经被销毁了。实际上,第三行代码在这里创建了自己的String对象,但这个对象没有color属性。

        可以显式地使用Boolean、Number和String构造函数创建原始值包装对象。不过应该在确实必要时再这么做,否则容易让开发者疑惑,分不清它们到底是原始值还是引用值。在原始值包装类型的实例上调用typeof会返回"object",所有原始值包装对象都会转换为布尔值true。

        另外,Object构造函数作为一个工厂方法,能够根据传入值的类型返回相应原始值包装类型的实例。比如:

let obj = new Object("some text");
console.log(obj instanceof String); // true

         如果传给Object的是字符串,则会创建一个String的实例。如果是数值,则会创建Number的实例。布尔值则会得到Boolean的实例。

        注意,使用new调用原始值包装类型的构造函数,与调用同名的转型函数并不一样。例如:

let value = "25";
let number = Number(value); // 转型函数
console.log(typeof number); // "number"
let obj = new Number(value); // 构造函数
console.log(typeof obj); // "object"

        在这个例子中,变量number中保存的是一个值为25的原始数值,而变量obj中保存的是一个Number的实例。 

        虽然不推荐显式创建原始值包装类型的实例,但它们对于操作原始值的功能是很重要的。每个原始值包装类型都有相应的一套方法来方便数据操作。

5.3.1 Boolean

        Boolean是对应布尔值的引用类型。要创建一个Boolean对象,就使用Boolean构造函数并传入true或false,如下例所示:

let booleanObject = new Boolean(true);

        Boolean的实例会重写valueOf()方法,返回一个原始值true或false。toString()方法被调用时也会被覆盖,返回字符串"true"或"false"。不过,Boolean对象在ECMAScript中用得很少。不仅如此,它们还容易引起误会,尤其是在布尔表达式中使用Boolean对象时,比如:

let falseObject = new Boolean(false);
let result = falseObject && true;
console.log(result); // true

let falseValue = false;
result = falseValue && true;
console.log(result); // false

         在这段代码中,我们创建一个值为false的Boolean对象。然后,在一个布尔表达式中通过&&操作将这个对象与一个原始值true组合起来。在布尔算术中,false && true等于false。可是,这个表达式是对falseObject对象而不是对它表示的值(false)求值。前面刚刚说过,所有对象在布尔表达式中都会自动转换为true,因此falseObject在这个表达式里实际上表示一个true值。那么true && true当然是true。

        除此之外,原始值和引用值(Boolean对象)还有几个区别。首先,typeof操作符对原始值返回"boolean",但对引用值返回"object"。同样,Boolean对象是Boolean类型的实例,在使用instaceof操作符时返回true,但对原始值则返回false,如下所示:

console.log(typeof falseObject); // object
console.log(typeof falseValue); // boolean
console.log(falseObject instanceof Boolean); // true
console.log(falseValue instanceof Boolean); // false

         理解原始布尔值和Boolean对象之间的区别非常重要,强烈建议永远不要使用后者。

5.3.2 Number

        Number是对应数值的引用类型。要创建一个Number对象,就使用Number构造函数并传入一个数值,如下例所示:

let numberObject = new Number(10);

        与Boolean类型一样,Number类型重写了valueOf()、toLocaleString()和toString()方法。valueOf()方法返回Number对象表示的原始数值,另外两个方法返回数值字符串。toString()方法可选地接收一个表示基数的参数,并返回相应基数形式的数值字符串,如下所示:

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

        除了继承的方法,Number类型还提供了几个用于将数值格式化为字符串的方法。
        toFixed()方法返回包含指定小数点位数的数值字符串,如:

let num = 10;
console.log(num.toFixed(2)); // "10.00"

        这里的toFixed()方法接收了参数2,表示返回的数值字符串要包含两位小数。结果返回值为"10.00",小数位填充了0。如果数值本身的小数位超过了参数指定的位数,则四舍五入到最接近的小数位:

let num = 10.005;
console.log(num.toFixed(2)); // "10.01"

        toFixed()自动舍入的特点可以用于处理货币。不过要注意的是,多个浮点数值的数学计算不一定得到精确的结果。比如,0.1 + 0.2 =0.30000000000000004。

        另一个用于格式化数值的方法是toExponential(),返回以科学记数法(也称为指数记数法)表示的数值字符串。与toFixed()一样,toExponential()也接收一个参数,表示结果中小数的位数。来看下面的例子:

let num = 10;
console.log(num.toExponential(1)); // "1.0e+1"

        这段代码的输出为"1.0e+1"。一般来说,这么小的数不用表示为科学记数法形式。如果想得到数值最适当的形式,那么可以使用toPrecision()。

        toPrecision()方法会根据情况返回最合理的输出结果,可能是固定长度,也可能是科学记数法形式。这个方法接收一个参数,表示结果中数字的总位数(不包含指数)。来看几个例子:

let num = 99;
console.log(num.toPrecision(1)); // "1e+2"
console.log(num.toPrecision(2)); // "99"
console.log(num.toPrecision(3)); // "99.0"

        在这个例子中,首先要用1位数字表示数值99,得到"1e+2",也就是100。因为99不能只用1位数字来精确表示,所以这个方法就将它舍入为100,这样就可以只用1位数字(及其科学记数法形式)来表示了。用2位数字表示99得到"99",用3位数字则是"99.0"。本质上,toPrecision()方法会根据数值和精度来决定调用toFixed()还是toExponential()。为了以正确的小数位精确表示数值,这3个方法都会向上或向下舍入。

        与Boolean对象类似,Number对象也为数值提供了重要能力。但是,考虑到两者存在同样的潜在问题,因此并不建议直接实例化Number对象。在处理原始数值和引用数值时,typeof和instacnceof操作符会返回不同的结果,如下所示:

let numberObject = new Number(10);
let numberValue = 10;
console.log(typeof numberObject); // "object"
console.log(typeof numberValue); // "number"
console.log(numberObject instanceof Number); // true
console.log(numberValue instanceof Number); // false

        原始数值在调用typeof时始终返回"number",而Number对象则返回"object"。类似地,Number对象是Number类型的实例,而原始数值不是。

        isInteger()方法与安全整数
        ES6新增了Number.isInteger()方法,用于辨别一个数值是否保存为整数。有时候,小数位的0可能会让人误以为数值是一个浮点值:

console.log(Number.isInteger(1)); // true
console.log(Number.isInteger(1.00)); // true
console.log(Number.isInteger(1.01)); // false

5.3.3 string

        String是对应字符串的引用类型。要创建一个String对象,使用String构造函数并传入一个数值,如下例所示:

let stringObject = new String("hello world");

        String对象的方法可以在所有字符串原始值上调用。3个继承的方法valueOf()、toLocaleString()和toString()都返回对象的原始字符串值。

        每个String对象都有一个length属性,表示字符串中字符的数量。来看下面的例子:

let stringValue = "hello world";
console.log(stringValue.length); // "11"

        这个例子输出了字符串"hello world"中包含的字符数量:11。注意,即使字符串中包含双字节字符(而不是单字节的ASCII字符),也仍然会按单字符来计数。

        String类型提供了很多方法来解析和操作字符串。

1、JavaScript字符

        JavaScript字符串由16位码元(code unit)组成。对多数字符来说,每16位码元对应一个字符。换句话说,字符串的length属性表示字符串包含多少16位码元:

let message = "abcde";
console.log(message.length); // 5

        此外,charAt()方法返回给定索引位置的字符,由传给方法的整数参数指定。具体来说,这个方法查找指定索引位置的16位码元,并返回该码元对应的字符:

let message = "abcde";
console.log(message.charAt(2)); // "c"

        JavaScript字符串使用了两种Unicode编码混合的策略:UCS-2和UTF-16。对于可以采用16位编码的字符(U+0000~U+FFFF),这两种编码实际上是一样的。

        使用charCodeAt()方法可以查看指定码元的字符编码。这个方法返回指定索引位置的码元值,索引以整数指定。比如:

let message = "abcde";

// Unicode "Latin small letter C"的编码是U+0063
console.log(message.charCodeAt(2)); // 99

// 十进制99等于十六进制63
console.log(99 === 0x63); // true

         fromCharCode()方法用于根据给定的UTF-16码元创建字符串中的字符。这个方法可以接受任意多个数值,并返回将所有数值对应的字符拼接起来的字符串:

// Unicode "Latin small letter A"的编码是U+0061
// Unicode "Latin small letter B"的编码是U+0062
// Unicode "Latin small letter C"的编码是U+0063
// Unicode "Latin small letter D"的编码是U+0064
// Unicode "Latin small letter E"的编码是U+0065

console.log(String.fromCharCode(0x61, 0x62, 0x63, 0x64, 0x65)); // "abcde"

// 0x0061 === 97
// 0x0062 === 98
// 0x0063 === 99
// 0x0064 === 100
// 0x0065 === 101

console.log(String.fromCharCode(97, 98, 99, 100, 101)); // "abcde"

         对于U+0000~U+FFFF范围内的字符,length、charAt()、charCodeAt()和fromCharCode()返回的结果都跟预期是一样的。这是因为在这个范围内,每个字符都是用16位表示的,而这几个方法也都基于16位码元完成操作。只要字符编码大小与码元大小一一对应,这些方法就能如期工作。

        这个对应关系在扩展到Unicode增补字符平面时就不成立了。问题很简单,即16位只能唯一表示65 536个字符。这对于大多数语言字符集是足够了,在Unicode中称为基本多语言平面(BMP)。为了表示更多的字符,Unicode采用了一个策略,即每个字符使用另外16位去选择一个增补平面。这种每个字符使用两个16位码元的策略称为代理对。

        在涉及增补平面的字符时,前面讨论的字符串方法就会出问题。比如,下面的例子中使用了一个笑脸表情符号,也就是一个使用代理对编码的字符:

// "smiling face with smiling eyes" 表情符号的编码是U+1F60A
// 0x1F60A === 128522
let message = "abde";

console.log(message.length); // 6
console.log(message.charAt(1)); // b
console.log(message.charAt(2)); // <?>
console.log(message.charAt(3)); // <?>
console.log(message.charAt(4)); // d

console.log(message.charCodeAt(1)); // 98
console.log(message.charCodeAt(2)); // 55357
console.log(message.charCodeAt(3)); // 56842
console.log(message.charCodeAt(4)); // 100

console.log(String.fromCodePoint(0x1F60A)); // 

console.log(String.fromCharCode(97, 98, 55357, 56842, 100, 101)); // abde

        这些方法仍然将16位码元当作一个字符,事实上索引2和索引3对应的码元应该被看成一个代理对,只对应一个字符。fromCharCode()方法仍然返回正确的结果,因为它实际上是基于提供的二进制表示直接组合成字符串。浏览器可以正确解析代理对(由两个码元构成),并正确地将其识别为一个Unicode笑脸字符。

        为正确解析既包含单码元字符又包含代理对字符的字符串,可以使用codePointAt()来代替charCodeAt()。跟使用charCodeAt()时类似,codePointAt()接收16位码元的索引并返回该索引位置上的码点(code point)。码点是Unicode中一个字符的完整标识。比如,"c"的码点是0x0063,而""的码点是0x1F60A。码点可能是16位,也可能是32位,而codePointAt()方法可以从指定码元位置识别完整的码点。

let message = "abde";

console.log(message.codePointAt(1)); // 98
console.log(message.codePointAt(2)); // 128522
console.log(message.codePointAt(3)); // 56842
console.log(message.codePointAt(4)); // 100

        注意,如果传入的码元索引并非代理对的开头,就会返回错误的码点。这种错误只有检测单个字符的时候才会出现,可以通过从左到右按正确的码元数遍历字符串来规避。迭代字符串可以智能地识别代理对的码点:

console.log([..."abde"]); // ["a", "b", "", "d", "e"]

        与charCodeAt()有对应的codePointAt()一样,fromCharCode()也有一个对应的fromCodePoint()。这个方法接收任意数量的码点,返回对应字符拼接起来的字符串:

console.log(String.fromCharCode(97, 98, 55357, 56842, 100, 101)); // abde
console.log(String.fromCodePoint(97, 98, 128522, 100, 101)); // abde

2、normalize()方法

        某些Unicode字符可以有多种编码方式。有的字符既可以通过一个BMP字符表示,也可以通过一个代理对表示。比如:

// U+00C5:上面带圆圈的大写拉丁字母A
console.log(String.fromCharCode(0x00C5)); // Å

// U+212B:长度单位“埃”
console.log(String.fromCharCode(0x212B)); // Å

// U+004:大写拉丁字母A
// U+030A:上面加个圆圈
console.log(String.fromCharCode(0x0041, 0x030A)); // Å

         比较操作符不在乎字符看起来是什么样的,因此这3个字符互不相等。

let a1 = String.fromCharCode(0x00C5),
    a2 = String.fromCharCode(0x212B),
    a3 = String.fromCharCode(0x0041, 0x030A);

console.log(a1, a2, a3); // Å, Å, Å

console.log(a1 === a2); // false
console.log(a1 === a3); // false
console.log(a2 === a3); // false

         为解决这个问题,Unicode提供了4种规范化形式,可以将类似上面的字符规范化为一致的格式,无论底层字符的代码是什么。这4种规范化形式是:NFD(Normalization Form D)、
NFC(Normalization Form C)、NFKD(Normalization Form KD)和NFKC(Normalization Form KC)。可以使用normalize()方法对字符串应用上述规范化形式,使用时需要传入表示哪种形式的字符串:"NFD"、"NFC"、"NFKD"或"NFKC"。

        通过比较字符串与其调用normalize()的返回值,就可以知道该字符串是否已经规范化了:

let a1 = String.fromCharCode(0x00C5),
    a2 = String.fromCharCode(0x212B),
    a3 = String.fromCharCode(0x0041, 0x030A);

// U+00C5是对0+212B进行NFC/NFKC规范化之后的结果
console.log(a1 === a1.normalize("NFD")); // false
console.log(a1 === a1.normalize("NFC")); // true
console.log(a1 === a1.normalize("NFKD")); // false
console.log(a1 === a1.normalize("NFKC")); // true

// U+212B是未规范化的
console.log(a2 === a2.normalize("NFD")); // false
console.log(a2 === a2.normalize("NFC")); // false
console.log(a2 === a2.normalize("NFKD")); // false
console.log(a2 === a2.normalize("NFKC")); // false

// U+0041/U+030A是对0+212B进行NFD/NFKD规范化之后的结果
console.log(a3 === a3.normalize("NFD")); // true
console.log(a3 === a3.normalize("NFC")); // false
console.log(a3 === a3.normalize("NFKD")); // true
console.log(a3 === a3.normalize("NFKC")); // false

        选择同一种规范化形式可以让比较操作符返回正确的结果: 

let a1 = String.fromCharCode(0x00C5),
    a2 = String.fromCharCode(0x212B),
    a3 = String.fromCharCode(0x0041, 0x030A);

console.log(a1.normalize("NFD") === a2.normalize("NFD")); // true
console.log(a2.normalize("NFKC") === a3.normalize("NFKC")); // true
console.log(a1.normalize("NFC") === a3.normalize("NFC")); // true

3、字符串操作方法

        本节介绍几个操作字符串值的方法。首先是concat(),用于将一个或多个字符串拼接成一个新字符串。来看下面的例子:

let stringValue = "hello ";
let result = stringValue.concat("world");

console.log(result); // "hello world"
console.log(stringValue); // "hello"

        在这个例子中,对stringValue调用concat()方法的结果是得到"hello world",但stringValue的值保持不变。concat()方法可以接收任意多个参数,因此可以一次性拼接多个字符串,如下所示:

let stringValue = "hello ";
let result = stringValue.concat("world", "!");

console.log(result); // "hello world!"
console.log(stringValue); // "hello"

        这个修改后的例子将字符串"world"和"!"追加到了"hello "后面。虽然concat()方法可以拼接字符串,但更常用的方式是使用加号操作符(+)。而且多数情况下,对于拼接多个字符串来说,使用加号更方便。

        ECMAScript提供了3个从字符串中提取子字符串的方法:slice()、substr()和substring()。这3个方法都返回调用它们的字符串的一个子字符串,而且都接收一或两个参数。第一个参数表示子字符串开始的位置,第二个参数表示子字符串结束的位置。对slice()和substring()而言,第二个参数是提取结束的位置(即该位置之前的字符会被提取出来)。对substr()而言,第二个参数表示返回的子字符串数量。任何情况下,省略第二个参数都意味着提取到字符串末尾。与concat()方法一样,slice()、substr()和substring()也不会修改调用它们的字符串,而只会返回提取到的原始新字符串值。来看下面的例子:

let stringValue = "hello world";
console.log(stringValue.slice(3)); // "lo world"
console.log(stringValue.substring(3)); // "lo world"
console.log(stringValue.substr(3)); // "lo world"
console.log(stringValue.slice(3, 7)); // "lo w"
console.log(stringValue.substring(3,7)); // "lo w"
console.log(stringValue.substr(3, 7)); // "lo worl"

        在这个例子中,slice()、substr()和substring()是以相同方式被调用的,而且多数情况下返回的值也相同。如果只传一个参数3,则所有方法都将返回"lo world",因为"hello"中"l"位置为3。如果
传入两个参数3和7,则slice()和substring()返回"lo w"(因为"world"中"o"在位置7,不包含),而substr()返回"lo worl",因为第二个参数对它而言表示返回的字符数。

        当某个参数是负值时,这3个方法的行为又有不同。比如,slice()方法将所有负值参数都当成字符串长度加上负参数值。

        而substr()方法将第一个负参数值当成字符串长度加上该值,将第二个负参数值转换为0。substring()方法会将所有负参数值都转换为0。看下面的例子:

let stringValue = "hello world";

console.log(stringValue.slice(-3)); // "rld"
console.log(stringValue.substring(-3)); // "hello world"
console.log(stringValue.substr(-3)); // "rld"
console.log(stringValue.slice(3, -4)); // "lo w"
console.log(stringValue.substring(3, -4)); // "hel"
console.log(stringValue.substr(3, -4)); // "" (empty string)

        这个例子明确演示了3个方法的差异。在给slice()和substr()传入负参数时,它们的返回结果相同。这是因为-3会被转换为8(长度加上负参数),实际上调用的是slice(8)和substr(8)。而substring()方法返回整个字符串,因为-3会转换为0。

        在第二个参数是负值时,这3个方法各不相同。slice()方法将第二个参数转换为7,实际上相当于调用slice(3, 7),因此返回"low"。而substring()方法会将第二个参数转换为0,相当于调
用substring(3, 0),等价于substring(0, 3),这是因为这个方法会将较小的参数作为起点,将较大的参数作为终点。对substr()来说,第二个参数会被转换为0,意味着返回的字符串包含零个字符,因而会返回一个空字符串。

4、字符串位置方法

        有两个方法用于在字符串中定位子字符串:indexOf()和lastIndexOf()。这两个方法从字符串中搜索传入的字符串,并返回位置(如果没找到,则返回-1)。两者的区别在于,indexOf()方法从字符串开头开始查找子字符串,而lastIndexOf()方法从字符串末尾开始查找子字符串。来看下面的例子:

let stringValue = "hello world";
console.log(stringValue.indexOf("o")); // 4
console.log(stringValue.lastIndexOf("o")); // 7

        这里,字符串中第一个"o"的位置是4,即"hello"中的"o"。最后一个"o"的位置是7,即"world"中的"o"。如果字符串中只有一个"o",则indexOf()和lastIndexOf()返回同一个位置。

        这两个方法都可以接收可选的第二个参数,表示开始搜索的位置。这意味着,indexOf()会从这个参数指定的位置开始向字符串末尾搜索,忽略该位置之前的字符;lastIndexOf()则会从这个参数指定的位置开始向字符串开头搜索,忽略该位置之后直到字符串末尾的字符。下面看一个例子:

let stringValue = "hello world";
console.log(stringValue.indexOf("o", 6)); // 7
console.log(stringValue.lastIndexOf("o", 6)); // 4

        在传入第二个参数6以后,结果跟前面的例子恰好相反。这一次,indexOf()返回7,因为它从位置6(字符"w")开始向后搜索字符串,在位置7找到了"o"。而lastIndexOf()返回4,因为它从位置6开始反向搜索至字符串开头,因此找到了"hello"中的"o"。像这样使用第二个参数并循环调用indexOf()或lastIndexOf(),就可以在字符串中找到所有的目标子字符串,如下所示:

let stringValue = "Lorem ipsum dolor sit amet, consectetur adipisicing elit";
let positions = new Array();
let pos = stringValue.indexOf("e");

while(pos > -1) {
    positions.push(pos);
    pos = stringValue.indexOf("e", pos + 1);
}

console.log(positions); // [3,24,32,35,52]

        这个例子逐步增大开始搜索的位置,通过indexOf()遍历了整个字符串。首先取得第一个"e"的位置,然后进入循环,将上一次的位置加1再传给indexOf(),确保搜索到最后一个子字符串实例之后。每个位置都保存在positions数组中,可供以后使用。

5、字符串包含方法

        ECMAScript 6增加了3个用于判断字符串中是否包含另一个字符串的方法:startsWith()、endsWith()和includes()。这些方法都会从字符串中搜索传入的字符串,并返回一个表示是否包含的布尔值。它们的区别在于,startsWith()检查开始于索引0的匹配项,endsWith()检查开始于索引(string.length -substring.length)的匹配项,而includes()检查整个字符串:

let message = "foobarbaz";

console.log(message.startsWith("foo")); // true
console.log(message.startsWith("bar")); // false

console.log(message.endsWith("baz")); // true
console.log(message.endsWith("bar")); // false

console.log(message.includes("bar")); // true
console.log(message.includes("qux")); // false

        startsWith()和includes()方法接收可选的第二个参数,表示开始搜索的位置。如果传入第二个参数,则意味着这两个方法会从指定位置向着字符串末尾搜索,忽略该位置之前的所有字符。下面是一个例子:

let message = "foobarbaz";

console.log(message.startsWith("foo")); // true
console.log(message.startsWith("foo", 1)); // false

console.log(message.includes("bar")); // true
console.log(message.includes("bar", 4)); // false

         endsWith()方法接收可选的第二个参数,表示应该当作字符串末尾的位置。如果不提供这个参数,那么默认就是字符串长度。如果提供这个参数,那么就好像字符串只有那么多字符一样:

let message = "foobarbaz";

console.log(message.endsWith("bar")); // false
console.log(message.endsWith("bar", 6)); // true

6、trim()方法

        ECMAScript在所有字符串上都提供了trim()方法。这个方法会创建字符串的一个副本,删除前、后所有空格符,再返回结果。比如:

let stringValue = " hello world ";
let trimmedStringValue = stringValue.trim();

console.log(stringValue); // " hello world "
console.log(trimmedStringValue); // "hello world"

        由于trim()返回的是字符串的副本,因此原始字符串不受影响,即原本的前、后空格符都会保留。

         另外,trimLeft()和trimRight()方法分别用于从字符串开始和末尾清理空格符。

7、repeat()方法

        ECMAScript在所有字符串上都提供了repeat()方法。这个方法接收一个整数参数,表示要将字符串复制多少次,然后返回拼接所有副本后的结果。

let stringValue = "na ";
console.log(stringValue.repeat(16) + "batman");
// na na na na na na na na na na na na na na na na batman
8、padStart()和padEnd()方法

        padStart()和padEnd()方法会复制字符串,如果小于指定长度,则在相应一边填充字符,直至满足长度条件。这两个方法的第一个参数是长度,第二个参数是可选的填充字符串,默认为空格
(U+0020)。

let stringValue = "foo";

console.log(stringValue.padStart(6)); // " foo"
console.log(stringValue.padStart(9, ".")); // "......foo"

console.log(stringValue.padEnd(6)); // "foo "
console.log(stringValue.padEnd(9, ".")); // "foo......"

        可选的第二个参数并不限于一个字符。如果提供了多个字符的字符串,则会将其拼接并截断以匹配指定长度。此外,如果长度小于或等于字符串长度,则会返回原始字符串。

let stringValue = "foo";

console.log(stringValue.padStart(8, "bar")); // "barbafoo"
console.log(stringValue.padStart(2)); // "foo"

console.log(stringValue.padEnd(8, "bar")); // "foobarba"
console.log(stringValue.padEnd(2)); // "foo"

9、字符串迭代与解构

        字符串的原型上暴露了一个@@iterator方法,表示可以迭代字符串的每个字符。可以像下面这样手动使用迭代器:

let message = "abc";
let stringIterator = message[Symbol.iterator]();

console.log(stringIterator.next()); // {value: "a", done: false}
console.log(stringIterator.next()); // {value: "b", done: false}
console.log(stringIterator.next()); // {value: "c", done: false}
console.log(stringIterator.next()); // {value: undefined, done: true}

         在for-of循环中可以通过这个迭代器按序访问每个字符:

for (const c of "abcde") {
    console.log(c);
}
// a
// b
// c
// d
// e

         有了这个迭代器之后,字符串就可以通过解构操作符来解构了。比如,可以更方便地把字符串分割为字符数组:

let message = "abcde";

console.log([...message]); // ["a", "b", "c", "d", "e"]

 10、字符串大小写转换

        下一组方法涉及大小写转换,包括4个方法:toLowerCase()、toLocaleLowerCase()、toUpperCase()和toLocaleUpperCase()。toLowerCase()和toUpperCase()方法是原来
就有的方法,与java.lang.String中的方法同名。toLocaleLowerCase()和toLocaleUpperCase()方法旨在基于特定地区实现。在很多地区,地区特定的方法与通用的方法是一样的。但在少数语言中(如土耳其语),Unicode大小写转换需应用特殊规则,要使用地区特定的方法才能实现正确转换。下面是几个例子:

let stringValue = "hello world";
console.log(stringValue.toLocaleUpperCase()); // "HELLO WORLD"
console.log(stringValue.toUpperCase());       // "HELLO WORLD"
console.log(stringValue.toLocaleLowerCase()); // "hello world"
console.log(stringValue.toLowerCase());       // "hello world"

        这里,toLowerCase()和toLocaleLowerCase()都返回hello world,而toUpperCase()和toLocaleUpperCase()都返回HELLO WORLD。通常,如果不知道代码涉及什么语言,则最好使用地区特定的转换方法。

11、字符串模式匹配方法

        String类型专门为在字符串中实现模式匹配设计了几个方法。第一个就是match()方法,这个方法本质上跟RegExp对象的exec()方法相同。match()方法接收一个参数,可以是一个正则表达式字符串,也可以是一个RegExp对象。来看下面的例子:

let text = "cat, bat, sat, fat";
let pattern = /.at/;

// 等价于pattern.exec(text)
let matches = text.match(pattern);
console.log(matches.index); // 0
console.log(matches[0]); // "cat"
console.log(pattern.lastIndex); // 0

        match()方法返回的数组与RegExp对象的exec()方法返回的数组是一样的:第一个元素是与整个模式匹配的字符串,其余元素则是与表达式中的捕获组匹配的字符串(如果有的话)。

        另一个查找模式的字符串方法是search()。这个方法唯一的参数与match()方法一样:正则表达式字符串或RegExp对象。这个方法返回模式第一个匹配的位置索引,如果没找到则返回-1。search()始终从字符串开头向后匹配模式。看下面的例子:

let text = "cat, bat, sat, fat";
let pos = text.search(/at/);
console.log(pos); // 1

        这里,search(/at/)返回1,即"at"的第一个字符在字符串中的位置。 

        为简化子字符串替换操作,ECMAScript提供了replace()方法。这个方法接收两个参数,第一个参数可以是一个RegExp对象或一个字符串(这个字符串不会转换为正则表达式),第二个参数可以是一个字符串或一个函数。如果第一个参数是字符串,那么只会替换第一个子字符串。要想替换所有子字符串,第一个参数必须为正则表达式并且带全局标记,如下面的例子所示:

let text = "cat, bat, sat, fat";
let result = text.replace("at", "ond");
console.log(result); // "cond, bat, sat, fat"

result = text.replace(/at/g, "ond");
console.log(result); // "cond, bond, sond, fond"

         在这个例子中,字符串"at"先传给replace()函数,而替换文本是"ond"。结果是"cat"被修改为"cond",而字符串的剩余部分保持不变。通过将第一个参数改为带全局标记的正则表达式,字符串中的所有"at"都被替换成了"ond"。

        第二个参数是字符串的情况下,有几个特殊的字符序列,可以用来插入正则表达式操作的值。ECMA-262中规定了下表中的值。

        使用这些特殊的序列,可以在替换文本中使用之前匹配的内容,如下面的例子所示:

let text = "cat, bat, sat, fat";
result = text.replace(/(.at)/g, "word ($1)");
console.log(result); // word (cat), word (bat), word (sat), word (fat)

         这里,每个以"at"结尾的词都会被替换成"word"后跟一对小括号,其中包含捕获组匹配的内容$1。

        replace()的第二个参数可以是一个函数。在只有一个匹配项时,这个函数会收到3个参数:与整个模式匹配的字符串、匹配项在字符串中的开始位置,以及整个字符串。在有多个捕获组的情况下,每个匹配捕获组的字符串也会作为参数传给这个函数,但最后两个参数还是与整个模式匹配的开始位置和原始字符串。这个函数应该返回一个字符串,表示应该把匹配项替换成什么。使用函数作为第二个参数可以更细致地控制替换过程,如下所示:

function htmlEscape(text) {
    return text.replace(/[<>"&]/g, function(match, pos, originalText) {
        switch(match) {
            case "<":
                return "&lt;";
            case ">":
                return "&gt;";
            case "&":
                return "&amp;";
            case "\"":
                return "&quot;";
        }
    });
}
console.log(htmlEscape("<p class=\"greeting\">Hello world!</p>"));
// "&lt;p class=&quot;greeting&quot;&gt;Hello world!</p>"

         这里,函数htmlEscape()用于将一段HTML中的4个字符替换成对应的实体:小于号、大于号、和号,还有双引号(都必须经过转义)。实现这个任务最简单的办法就是用一个正则表达式查找这些字符,然后定义一个函数,根据匹配的每个字符分别返回特定的HTML实体。

        最后一个与模式匹配相关的字符串方法是split()。这个方法会根据传入的分隔符将字符串拆分成数组。作为分隔符的参数可以是字符串,也可以是RegExp对象。(字符串分隔符不会被这个方法当成正则表达式。)还可以传入第二个参数,即数组大小,确保返回的数组不会超过指定大小。来看下面的例子:

let colorText = "red,blue,green,yellow";
let colors1 = colorText.split(","); // ["red", "blue", "green", "yellow"]
let colors2 = colorText.split(",", 2); // ["red", "blue"]
let colors3 = colorText.split(/[^,]+/); // ["", ",", ",", ",", ""]

         在这里,字符串colorText是一个逗号分隔的颜色名称符串。调用split(",")会得到包含这些颜色名的数组,基于逗号进行拆分。要把数组元素限制为2个,传入第二个参数2即可。最后,使用正则
表达式可以得到一个包含逗号的数组。注意在最后一次调用split()时,返回的数组前后包含两个空字符串。这是因为正则表达式指定的分隔符出现在了字符串开头("red")和末尾("yellow")。

12、localeCompare()方法

         最后一个方法是localeCompare(),这个方法比较两个字符串,返回如下3个值中的一个。

  • 如果按照字母表顺序,字符串应该排在字符串参数前头,则返回负值。(通常是-1,具体还要看与实际值相关的实现。)
  • 如果字符串与字符串参数相等,则返回0。
  • 如果按照字母表顺序,字符串应该排在字符串参数后头,则返回正值。(通常是1,具体还要看与实际值相关的实现。)

        下面是一个例子:

let stringValue = "yellow";
console.log(stringValue.localeCompare("brick")); // 1
console.log(stringValue.localeCompare("yellow")); // 0
console.log(stringValue.localeCompare("zoo")); // -1

         在这里,字符串"yellow"与3个不同的值进行了比较:"brick"、"yellow"和"zoo"。"brick"按字母表顺序应该排在"yellow"前头,因此localeCompare()返回1。"yellow"等于"yellow",因此"localeCompare()"返回0。最后,"zoo"在"yellow"后面,因此localeCompare()返回-1。强调一
下,因为返回的具体值可能因具体实现而异,所以最好像下面的示例中一样使用localeCompare():

function determineOrder(value) {
    let result = stringValue.localeCompare(value);
    if (result < 0) {
        console.log(`The string 'yellow' comes before the string '${value}'.`);
    } else if (result > 0) {
        console.log(`The string 'yellow' comes after the string '${value}'.`);
    } else {
        console.log(`The string 'yellow' is equal to the string '${value}'.`);
    }
}
determineOrder("brick");
determineOrder("yellow");
determineOrder("zoo");

        这样一来,就可以保证在所有实现中都能正确判断字符串的顺序了。 

        localeCompare()的独特之处在于,实现所在的地区(国家和语言)决定了这个方法如何比较字符串。在美国,英语是ECMAScript实现的标准语言,localeCompare()区分大小写,大写字母排在小写字母前面。但其他地区未必是这种情况。

13、HTML方法

        早期的浏览器开发商认为使用JavaScript动态生成HTML标签是一个需求。因此,早期浏览器扩展了规范,增加了辅助生成HTML标签的方法。下表总结了这些HTML方法。不过,这些方法基本上已经没有人使用了,因为结果通常不是语义化的标记。

 

5.4 单例内置对象

        ECMA-262对内置对象的定义是“任何由ECMAScript实现提供、与宿主环境无关,并在ECMAScript程序开始执行时就存在的对象”。这就意味着,开发者不用显式地实例化内置对象,因为它们已经实例化好了。前面我们已经接触了大部分内置对象,包括Object、Array和String。本节介绍ECMA-262定义的另外两个单例内置对象:Global和Math。

5.4.1 Global

        Global对象是ECMAScript中最特别的对象,因为代码不会显式地访问它。ECMA-262规定Global对象为一种兜底对象,它所针对的是不属于任何对象的属性和方法。事实上,不存在全局变量或全局函数这种东西。在全局作用域中定义的变量和函数都会变成Global对象的属性 。本书前面介绍的函数,包括isNaN()、isFinite()、parseInt()和parseFloat(),实际上都是Global对象的方法。除了这些,Global对象上还有另外一些方法。

1、URL编码方式

        encodeURI()和encodeURIComponent()方法用于编码统一资源标识符(URI),以便传给浏览器。有效的URI不能包含某些字符,比如空格。使用URI编码方法来编码URI可以让浏览器能够理解它们,同时又以特殊的UTF-8编码替换掉所有无效字符。

        ecnodeURI()方法用于对整个URI进行编码,比如"www.wrox.com/illegal value.js"。而encodeURIComponent()方法用于编码URI中单独的组件,比如前面URL中的"illegal value.js"。这两个方法的主要区别是,encodeURI()不会编码属于URL组件的特殊字符,比如冒号、斜杠、问号、井号,而encodeURIComponent()会编码它发现的所有非标准字符。来看下面的例子:

let uri = "http://www.wrox.com/illegal value.js#start";
// "http://www.wrox.com/illegal%20value.js#start"
console.log(encodeURI(uri));

// "http%3A%2F%2Fwww.wrox.com%2Fillegal%20value.js%23start"
console.log(encodeURIComponent(uri));

        这里使用encodeURI()编码后,除空格被替换为%20之外,没有任何变化。而encodeURIComponent()方法将所有非字母字符都替换成了相应的编码形式。这就是使用encodeURI()编码整个URI,但只使用encodeURIComponent()编码那些会追加到已有URI后面的字符串的原因。

注意         一般来说,使用encodeURIComponent()应该比使用encodeURI()的频率更高,这是因为编码查询字符串参数比编码基准URI的次数更多。

        与encodeURI()和encodeURIComponent()相对的是decodeURI()和decodeURIComponent()。decodeURI()只对使用encodeURI()编码过的字符解码。例如,%20会被替换为空格,但%23不会被替换为井号(#),因为井号不是由encodeURI()替换的。类似地,decodeURIComponent()解码所有被encodeURIComponent()编码的字符,基本上就是解码所有特殊值。来看下面的例子:

let uri = "http%3A%2F%2Fwww.wrox.com%2Fillegal%20value.js%23start";

// http%3A%2F%2Fwww.wrox.com%2Fillegal value.js%23start
console.log(decodeURI(uri));

// http:// www.wrox.com/illegal value.js#start
console.log(decodeURIComponent(uri));

        这里,uri变量中包含一个使用encodeURIComponent()编码过的字符串。首先输出的是使用decodeURI()解码的结果,可以看到只用空格替换了%20。然后是使用decodeURIComponent()解码的结果,其中替换了所有特殊字符,并输出了没有包含任何转义的字符串。(这个字符串不是有效的URL。)

注意         URI方法encodeURI()、encodeURIComponent()、decodeURI()和
decodeURIComponent()取代了escape()和unescape()方法,后者在ECMA-262第3版中就已经废弃了。URI方法始终是首选方法,因为它们对所有Unicode字符进行编码,而原来的方法只能正确编码ASCII字符。不要在生产环境中使用escape()和unescape()。

2、eval()方法

        最后一个方法可能是整个ECMAScript语言中最强大的了,它就是eval()。这个方法就是一个完整的ECMAScript解释器,它接收一个参数,即一个要执行的ECMAScript(JavaScript)字符串。来看一个例子:

eval("console.log('hi')");

 上面这行代码的功能与下一行等价:

console.log("hi");

        当解释器发现eval()调用时,会将参数解释为实际的ECMAScript语句,然后将其插入到该位置。通过eval()执行的代码属于该调用所在上下文,被执行的代码与该上下文拥有相同的作用域链。这意味着定义在包含上下文中的变量可以在eval()调用内部被引用,比如下面这个例子:

let msg = "hello world";
eval("console.log(msg)"); // "hello world"

        这里,变量msg是在eval()调用的外部上下文中定义的,而console.log()显示了文本"hello world"。这是因为第二行代码会被替换成一行真正的函数调用代码。类似地,可以在eval()内部
定义一个函数或变量,然后在外部代码中引用,如下所示:     

eval("function sayHi() { console.log('hi'); }");
sayHi();

         这里,函数sayHi()是在eval()内部定义的。因为该调用会被替换为真正的函数定义,所以才可能在下一行代码中调用sayHi()。对于变量也是一样的:

eval("let msg = 'hello world';");
console.log(msg); // Reference Error: msg is not defined

        通过eval()定义的任何变量和函数都不会被提升,这是因为在解析代码的时候,它们是被包含在一个字符串中的。它们只是在eval()执行的时候才会被创建。

msg是一个普通变量,它的作用域是eval()函数的局部作用域,eval()执行完毕后msg就被销毁了,无法在外部被访问。而sayHi函数是一种特殊的对象,eval()执行完毕后它不会被立即销毁,而是会在垃圾回收机制运行并将其销毁之前一直存在在内存中,所以可以在外部访问

        通过eval()定义的任何变量和函数都不会被提升,这是因为在解析代码的时候,它们是被包含在一个字符串中的。它们只是在eval()执行的时候才会被创建。
        在严格模式下,在eval()内部创建的变量和函数无法被外部访问。换句话说,最后两个例子会报错。同样,在严格模式下,赋值给eval也会导致错误: 

"use strict";
eval = "hi"; // 导致错误

注意         解释代码字符串的能力是非常强大的,但也非常危险。在使用eval()的时候必须极为慎重,特别是在解释用户输入的内容时。因为这个方法会对XSS利用暴露出很大的攻击面。恶意用户可能插入会导致你网站或应用崩溃的代码。

3、Global对象属性

        Global对象有很多属性,其中一些前面已经提到过了。像undefined、NaN和Infinity等特殊值都是Global对象的属性。此外,所有原生引用类型构造函数,比如Object和Function,也都是Global对象的属性。下表列出了所有这些属性。

4、window对象

         虽然ECMA-262没有规定直接访问Global对象的方式,但浏览器将window对象实现为Global对象的代理。因此,所有全局作用域中声明的变量和函数都变成了window的属性。来看下面的例子:

var color = "red";

function sayColor() {
    console.log(window.color);
}

window.sayColor(); // "red"

        这里定义了一个名为color的全局变量和一个名为sayColor()的全局函数。在sayColor()内部,通过window.color访问了color变量,说明全局变量变成了window的属性。接着,又通过window对象直接调用了window.sayColor()函数,从而输出字符串。

        另一种获取Global对象的方式是使用如下的代码:

let global = function() {
    return this;
}();

        这段代码创建一个立即调用的函数表达式,返回了this的值。如前所述,当一个函数在没有明确(通过成为某个对象的方法,或者通过call()/apply())指定this值的情况下执行时,this值等于Global对象。因此,调用一个简单返回this的函数是在任何执行上下文中获取Global对象的通用方式。

5.4.2  Math

        ECMAScript提供了Math对象作为保存数学公式、信息和计算的地方。Math对象提供了一些辅助计算的属性和方法。

注意         Math对象上提供的计算要比直接在JavaScript实现的快得多,因为Math对象上的计算使用了JavaScript引擎中更高效的实现和处理器指令。但使用Math计算的问题是精度会因浏览器、操作系统、指令集和硬件而异。

1、Math对象属性

        Math对象有一些属性,主要用于保存数学中的一些特殊值。下表列出了这些属性。

2、min()和max()方法

        Math对象也提供了很多辅助执行简单或复杂数学计算的方法。min()和max()方法用于确定一组数值中的最小值和最大值。这两个方法都接收任意多个参数,如下面的例子所示:

let max = Math.max(3, 54, 32, 16);
console.log(max); // 54

let min = Math.min(3, 54, 32, 16);
console.log(min); // 3

        在3、54、32和16中,Math.max()返回54,Math.min()返回3。使用这两个方法可以避免使用额外的循环和if语句来确定一组数值的最大最小值。

        要知道数组中的最大值和最小值,可以像下面这样使用扩展操作符:

let values = [1, 2, 3, 4, 5, 6, 7, 8];
let max = Math.max(...val);

3、舍入方法

        接下来是用于把小数值舍入为整数的4个方法:Math.ceil()、Math.floor()、Math.round()和Math.fround()。这几个方法处理舍入的方式如下所述。

  • Math.ceil()方法始终向上舍入为最接近的整数。
  • Math.floor()方法始终向下舍入为最接近的整数。
  • Math.round()方法执行四舍五入。
  • Math.fround()方法返回数值最接近的单精度(32位)浮点值表示。

        以下示例展示了这些方法的用法:

console.log(Math.ceil(25.9)); // 26
console.log(Math.ceil(25.5)); // 26
console.log(Math.ceil(25.1)); // 26

console.log(Math.round(25.9)); // 26
console.log(Math.round(25.5)); // 26
console.log(Math.round(25.1)); // 25

console.log(Math.fround(0.4)); // 0.4000000059604645
console.log(Math.fround(0.5)); // 0.5
console.log(Math.fround(25.9)); // 25.899999618530273

console.log(Math.floor(25.9)); // 25
console.log(Math.floor(25.5)); // 25
console.log(Math.floor(25.1)); // 25

        对于25和26(不包含)之间的所有值,Math.ceil()都会返回26,因为它始终向上舍入。Math.round()只在数值大于等于25.5时返回26,否则返回25。最后,Math.floor()对所有25和26(不包含)之间的值都返回25。

4、random()方法

        Math.random()方法返回一个0~1范围内的随机数,其中包含0但不包含1。对于希望显示随机名言或随机新闻的网页,这个方法是非常方便的。可以基于如下公式使用Math.random()从一组整数中随机选择一个数:

number = Math.floor(Math.random() * total_number_of_choices + first_possible_value

        这里使用了Math.floor()方法,因为Math.random()始终返回小数,即便乘以一个数再加上一个数也是小数。因此,如果想从1~10范围内随机选择一个数,代码就是这样的:

let num = Math.floor(Math.random() * 10 + 1);

        这样就有10个可能的值(1~10),其中最小的值是1。如果想选择一个2~10范围内的值,则代码就要写成这样:

let num = Math.floor(Math.random() * 9 + 2);

        2~10只有9个数,所以可选总数(total_number_of_choices)是9,而最小可能的值(first_possible_value)是2。很多时候,通过函数来算出可选总数和最小可能的值可能更方便,比如:

function selectFrom(lowerValue, upperValue) {
    let choices = upperValue - lowerValue + 1;
    return Math.floor(Math.random() * choices + lowerValue);
}
let num = selectFrom(2,10);
console.log(num); // 2~10范围内的值,其中包含2和10

        这里的函数selectFrom()接收两个参数:应该返回的最小值和最大值。通过将这两个值相减再加1得到可选总数,然后再套用上面的公式。于是,调用selectFrom(2,10)就可以从2~10(包含)范围内选择一个值了。使用这个函数,从一个数组中随机选择一个元素就很容易,比如:

let colors = ["red", "green", "blue", "yellow", "black", "purple", "brown"];
let color = colors[selectFrom(0, colors.length-1)];

        在这个例子中,传给selecFrom()的第二个参数是数组长度减1,即数组最大的索引值。

注意         Math.random()方法在这里出于演示目的是没有问题的。如果是为了加密而需要生成随机数(传给生成器的输入需要较高的不确定性),那么建议使用window.crypto.getRandomValues()。

5、其他方法

                                 

5.5 小结

        JavaScript中的对象称为引用值,几种内置的引用类型可用于创建特定类型的对象。

  • 引用值与传统面向对象编程语言中的类相似,但实现不同。
  • Date类型提供关于日期和时间的信息,包括当前日期、时间及相关计算。
  • RegExp类型是ECMAScript支持正则表达式的接口,提供了大多数基础的和部分高级的正则表达式功能。

        JavaScript比较独特的一点是,函数实际上是Function类型的实例,也就是说函数也是对象。因为函数也是对象,所以函数也有方法,可以用于增强其能力。
        由于原始值包装类型的存在,JavaScript中的原始值可以被当成对象来使用。有3种原始值包装类型:Boolean、Number和String。它们都具备如下特点。

  • 每种包装类型都映射到同名的原始类型。
  • 以读模式访问原始值时,后台会实例化一个原始值包装类型的对象,借助这个对象可以操作相应的数据。
  • 涉及原始值的语句执行完毕后,包装对象就会被销毁。

        当代码开始执行时,全局上下文中会存在两个内置对象:Global和Math。其中,Global对象在大多数ECMAScript实现中无法直接访问。不过,浏览器将其实现为window对象。所有全局变量和函数都是Global对象的属性。Math对象包含辅助完成复杂计算的属性和方法。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_52878347/article/details/135606646

智能推荐

基于Java在线电影票购买系统设计实现(源码+lw+部署文档+讲解等)-程序员宅基地

文章浏览阅读4.1k次,点赞2次,收藏4次。社会和科技的不断进步带来更便利的生活,计算机技术也越来越平民化。二十一世纪是数据时代,各种信息经过统计分析都可以得到想要的结果,所以也可以更好的为人们工作、生活服务。电影是生活娱乐的一部分,特别对喜欢看电影的用户来说是非常重要的事情。把计算机技术和影院售票相结合可以更符合现代、用户的要求,实现更为方便的购买电影票的方式。本基于Java Web的在线电影票购买系统采用Java语言和Vue技术,框架采用SSM,搭配MySQL数据库,运行在Idea里。

集合的addAll方法--list.addAll(null)会报错--java.lang.NullPointerException-程序员宅基地

文章浏览阅读1.8k次。Exception in thread "main" java.lang.NullPointerException at java.util.ArrayList.addAll(ArrayList.java:559) at com.iflytek.epdcloud.recruit.utils.quartz.Acool.main(Acool.java:16)import java.u..._addall(null)

java获取当天0点到24点的时间戳,获得当前分钟开始结束时间戳_java 获取某分钟的起止时间戳-程序员宅基地

文章浏览阅读4.5k次。public static void main(String[] args) { Calendar todayStart = Calendar.getInstance(); todayStart.set(Calendar.HOUR_OF_DAY, 0); todayStart.set(Calendar.MINUTE, 0); toda..._java 获取某分钟的起止时间戳

北京内推 | 京东AI研究院计算机视觉实验室招聘三维视觉算法研究型实习生-程序员宅基地

文章浏览阅读1.1k次。合适的工作难找?最新的招聘信息也不知道?AI 求职为大家精选人工智能领域最新鲜的招聘信息,助你先人一步投递,快人一步入职!京东 AI 研究院京东 AI 研究院(https://air.jd..._京东计算机视觉实验室

Ubuntu18.04安装配置Qt5.15_ubuntu安装qt5.15-程序员宅基地

文章浏览阅读2.1k次。Ubuntu18.04安装配置Qt5.15 Ubuntu18.04安装配置Qt5.15 Qt选择下载Qt安装Qt5.15.0配置后记 Qt选择 在官方的声明中,Qt5.15是Qt5.x的最后一个LTS版本,增加了即将在2020年底推出的Qt6的部分新特性,为了之后的新_ubuntu安装qt5.15

针对Error: You must either define the environment variable DJANGO_SETTINGS_MODULE ...问题的解决_project structure->facets->django->-程序员宅基地

文章浏览阅读1.8w次,点赞5次,收藏3次。针对Error: You must either define the environment variable DJANGO_SETTINGS_MODULE or call settings.configure() before accessing settings问题的解决使用intelliJ Idea开发django项目,启动 manage.py 测试时,会出现如上所示问题。根据提示,有两种解_project structure->facets->django->

随便推点

网络安全实验---防火墙实验-程序员宅基地

文章浏览阅读2w次,点赞13次,收藏82次。文章目录一、实验目的:二、实验环境:三、实验内容:1. 安装天网防火墙2. 使用天网防火墙进行实验3.在上端的菜单栏最左边点击应用程序规则,点击下方需要修改应用的选项可以对其进行流量控制4.调节ip规则配置,将“允许自己ping探测其他机器”改为禁止,查看能否再次收到reply5.添加一条禁止邻居同学主机的FTP连接规则四、心得体会:五.软件共享一、实验目的:通过实验深入理解防火墙的功能和工作原理熟悉天网防火墙个人版的配置和使用二、实验环境:一台xp虚拟机和一台windows10虚拟机在xp上安_防火墙实验

vue项目运行报错:94% asset optimization ERROR Failed to compile with 2 errors13:03:01 error in ./src/ba-程序员宅基地

文章浏览阅读6.7k次。使用vue编写的前端项目运行报错:88% hashing 89% module assets processing 90% chunk assets processing 94% asset optimization ERROR Failed to compile with 2 errors13:03:01 error in ./src/base/components/head..._94% asset optimization

适用于 Linux 的 Windows 子系统安装指南 (Windows 10) (微软官方文档)_hyper-v-vmms 虚拟硬盘文件必须是未压缩和未加密的文件,并且不能是稀疏文件。-程序员宅基地

文章浏览阅读2.2k次,点赞2次,收藏3次。官方原文档微软官方教程地址:传送门安装适用于 Linux 的 Windows 子系统必须先启用“适用于 Linux 的 Windows 子系统”可选功能,然后才能在 Windows 上安装 Linux 分发版。以管理员身份打开 PowerShell 并运行:dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart安装所选的 Linux 分发版打开 Micro_hyper-v-vmms 虚拟硬盘文件必须是未压缩和未加密的文件,并且不能是稀疏文件。

rufus 一款好用的linux u盘,光盘刻录工具_rufus可以刻录光盘吗-程序员宅基地

文章浏览阅读2.2k次。rufus 一款好用的linux u盘,光盘刻录工具:下载(点击普通下载中的“立即下载”): http://share.cnop.net/file/1806028-401886318_rufus可以刻录光盘吗

用VB.net实现对.ini文件的读写操作的类-程序员宅基地

文章浏览阅读142次。Option Explicit OnModule INI 'INICont.bas Ver 1.0+a INI '==================================================================== 'GetIntFromINI( sectionName , keyName , defaultValue, iniPath ) '..._vb.net 读取ini文件 int

linux集群—负载均衡集群LBC_lbc在程序中是什么-程序员宅基地

文章浏览阅读615次。1 集群的定义集群的出现主要是为了解决单台设备性能不足、效率低下等问题,可以保证业务无中断,总体效率高,适合大型业务。2 集群的分类3 负载均衡集群LBC_lbc在程序中是什么

推荐文章

热门文章

相关标签