引用值或者对象,是某个特定引用类型的实例
在 ECMAScript 中,引用类型是把数据和功能组织到一起的结构,经常被人错误地称作“类”,但它不是类。
引用类型有时候也被称为对象定义,因为它们描述了自己的对象应有的属性和方法。
对象被认为是某个特定引用类型的实例。
创建对象:new操作符 + 构造函数(constructor)
let now = new Date();
这行代码创建了引用类型 Date 的一个新实例,并将它保存在变量 now 中。
Date()在这里就是构造函数,它负责创建一个只有默认属性和方法的简单对象。
函数也是一种引用类型。
Date 类型将日期保存为自协调世界时(UTC,Universal Time Coordinated)时间 1970 年 1 月 1 日午夜(零时)至今所经过的毫秒数。
创建Date对象:
let now = new Date();
在不给 Date 构造函数传参数的情况下,创建的对象将保存当前日期和时间。要基于其他日期和时间创建日期对象,必须传入其毫秒表示(UNIX 纪元 1970 年 1 月 1 日午夜之后的毫秒数)。
接收一个表示日期的字符串参数,尝试将这个字符串转换为表示该日期的毫秒数
支持以下日期格式:
let someDate = new Date(Date.parse("May 23, 2019"));
let someDate = new Date("May 23, 2019");
这两行得到的日期对象是相同的,因为如果直接把表示日期的字符串传给Date构造函数,那么Date会在后台调用Date.parse()。
如果传给 Date.parse()的字符串并不表示日期,则该方法会返回 NaN。
返回日期的毫秒表示
参数:
年和月是必需的。如果不提供日,那么默认为 1 日。其他参数的默认值都是 0。
// GMT 时间 2005 年 5 月 5 日下午 5 点 55 分 55 秒
let allFives = new Date(Date.UTC(2005, 4, 5, 17, 55, 55));
let allFives = new Date(2005, 4, 5, 17, 55, 55);
这两行得到的日期对象是相同的,因为Date.UTC()也会被 Date 构造函数隐式调用,但这次的两个日期是(由于系统设置决定的)本地时区的日期。
返回表示方法执行时日期和时间的毫秒数,可以用于代码分析中:
// 起始时间
let start = Date.now();
// 调用函数
doSomething();
// 结束时间
let stop = Date.now(),
result = stop - start;
与其他类型一样,Date 类型重写了 toLocaleString()、toString()和 valueOf()方法。但与其他类型不同,重写后这些方法的返回值不一样。
toLocaleString()方法返回与浏览器运行的本地环境一致的日期和时间。这通常意味着格式中包含针对时间的 AM(上午)或 PM(下午),但不包含时区信息(具体格式可能因浏览器而不同)。
toString()方法通常返回带时区信息的日期和时间,而时间也是以 24 小时制(0~23)表示的。
现代浏览器在这两个方法的输出上已经趋于一致。在比较老的浏览器上,每个方法返回的结果可能在每个浏览器上都是不同的。这些差异意味着 toLocaleString()和 toString()可能只对调试有用,不能用于显示。
valueOf()方法根本就不返回字符串,这个方法被重写后返回的是日期的毫秒表示。因此,操作符(如小于号和大于号)可以直接使用它返回的值。
let date1 = new Date(2019, 0, 1); // 2019 年 1 月 1 日
let date2 = new Date(2019, 1, 1); // 2019 年 2 月 1 日
console.log(date1 < date2); // true
console.log(date1 > date2); // false
Date 类型有几个专门用于格式化日期的方法,它们都会返回字符串:
这些方法的输出与 toLocaleString()和 toString()一样,会因浏览器而异。因此不能用于在用户界面上一致地显示日期。
Date 类型剩下的方法(见下表)直接涉及取得或设置日期值的特定部分。注意表中**“UTC 日期”,指的是没有时区偏移(将日期转换为 GMT)时的日期。**
let now = new Date();
let year = now.getFullYear();
let month = now.getMonth() + 1; // 得到的月份小1
let dates = now.getDate();
let arr = ['星期日','星期一','星期二','星期三','星期四','星期五','星期六'];
let day = now.getDay(); // 周日返回的是0,周一返回的是1
console.log('今天是:' + year + '年' + month + '月' + dates + '日' + arr[day]);
console.log('今天是:' + year + '-' + month + '-' + dates + '-' + arr[day]);
今天是:2021年10月24日星期日
今天是:2021-10-24-星期日
封装一个函数,返回当前的时分秒,格式是09:09:09,填充字符为0
// 封装一个函数,返回当前的时分秒,格式是09:09:09,填充字符为0
function getTime() {
let time = new Date();
let hour = time.getHours();
hour = hour > 10 ? hour : '0'+hour;
let min = time.getMinutes();
min = min > 10 ? min : '0' + min;
let sec = time.getSeconds();
sec = sec > 10 ? sec : '0' + sec;
return hour + ':' + min + ':' + sec;
}
console.log(getTime())
不是当前时间的毫秒数,而是距离1970年1月1日过来多少毫秒数
// 获得Date总的毫秒数---时间戳
// 不是当前时间的毫秒数,而是距离1970年1月1日过来多少毫秒数
var date = new Date();
alert(date.valueOf()); //方法一 valueOf()
alert(date.getTime()); //方法二 getTime()
// 简单写法-最常用
var date1 = +new Date();
alert(date1);
// H5新增
alert(Date.now());
核心算法:输入的时间减去现在的时间就是剩余的时间,即倒计时,但是不能拿着时分秒去相减,这样会有错误的结果,比如05-25
要用时间戳来做,用户输入的时间总毫秒数减去现在的总时间毫秒数,得到的就是剩余的毫秒数,然后转换为时分秒【时间戳转换为时分秒】
// 倒计时效果
function countDown(time) {
var nowTime = +new Date(); //返回当前时间总的毫秒数
var inputTime = +new Date(time); //返回的是用户输入时间总的毫秒数
var times = (inputTime - nowTime) / 1000; //两者差,毫秒数
var d = parseInt(times/24 /60 /60);
d = d > 10 ? d : '0' + d;
var h = parseInt(times/60 /60 %24);
h = h > 10 ? h : '0' + h;
var m = parseInt(times/60 %60);
m = m > 10 ? m : '0' + m;
var s = parseInt(times%60);
s = s > 10 ? s : '0' + s;
return d + '天' + h + '时' + m + '分' + s + '秒';
}
alert(countDown('2021-3-12 18:00:00'));
ECMAScript 通过 RegExp 类型支持正则表达式。
语法: let expression = /pattern/flags;
// 匹配字符串中的所有"at"
let pattern1 = /at/g;
// 匹配第一个"bat"或"cat",忽略大小写
let pattern2 = /[bc]at/i;
// 匹配所有以"at"结尾的三字符组合,忽略大小写
let pattern3 = /.at/gi;
所有元字符在模式中也必须转义,包括:( [ { \ ^ $ | ) ] } ? * + .
使用反斜杠来转义
// 匹配第一个"[bc]at",忽略大小写
let pattern2 = /\[bc\]at/i;
// 匹配所有".at",忽略大小写
let pattern4 = /\.at/gi;
语法:接收两个参数,且都是字符串
// 匹配第一个"bat"或"cat",忽略大小写
let pattern1 = /[bc]at/i;
// 跟 pattern1 一样,只不过是用构造函数创建的
let pattern2 = new RegExp("[bc]at", "i");
所有元字符都必须二次转义,包括转义字符序列,如\n(\转义后的字符串是\,在正则表达式字符串中则要写成\\)。
使用 RegExp 也可以基于已有的正则表达式实例,并可选择性地修改它们的标记
const re1 = /cat/g;
console.log(re1); // "/cat/g"
const re2 = new RegExp(re1);
console.log(re2); // "/cat/g"
const re3 = new RegExp(re1, "i");
console.log(re3); // "/cat/i"
小例子
var reg=/\d+/g;
console.log(reg.test("789456"));//true
var reg2=new RegExp("\\d+","g");
console.log(reg2.test("1234444456"));//true
console.log(reg2.test("asdsa"));//false
每个 RegExp 实例都有下列属性,提供有关模式的各方面信息。
通过这些属性可以全面了解正则表达式的信息,不过实际开发中用得并不多,因为模式声明中包含这些信息。
let pattern1 = /\[bc\]at/i;
console.log(pattern1.global); // false
console.log(pattern1.ignoreCase); // true
console.log(pattern1.multiline); // false
console.log(pattern1.lastIndex); // 0
console.log(pattern1.source); // "\[bc\]at"
console.log(pattern1.flags); // "i"
let pattern2 = new RegExp("\\[bc\\]at", "i");
console.log(pattern2.global); // false
console.log(pattern2.ignoreCase); // true
console.log(pattern2.multiline); // false
console.log(pattern2.lastIndex); // 0
console.log(pattern2.source); // "\[bc\]at"
console.log(pattern2.flags); // "i"
注意,虽然第一个模式是通过字面量创建的,第二个模式是通过 RegExp 构造函数创建的,但两个模式的 source 和 flags 属性是相同的。source 和 flags 属性返回的是规范化之后可以在字面量中使用的形式
适用于作用域中的所有正则表达式,会根据最后执行的正则表达式操作而变化。
可以通过 【全名】 和 【简写】两种不同的方式来访问。
通过这些属性可以提取出与 exec()和 test()执行的操作相关的信息。
全名形式举例:
let text = "this has been a short summer";
let pattern = /(.)hort/g;
if (pattern.test(text)) {
console.log(RegExp.input); // this has been a short summer
console.log(RegExp.leftContext); // this has been a
console.log(RegExp.rightContext); // summer
console.log(RegExp.lastMatch); // short
console.log(RegExp.lastParen); // s
}
以上代码创建了一个模式,用于搜索任何后跟"hort"的字符,并把第一个字符放在了捕获组中。不同属性包含的内容如下。
简写形式举例:要使用中括号语法来访问,因为大多数简写形式都不是合法的 ECMAScript 标识符
let text = "this has been a short summer";
let pattern = /(.)hort/g;
/*
* 注意:Opera 不支持简写属性名
* IE 不支持多行匹配
*/
if (pattern.test(text)) {
console.log(RegExp.$_); // this has been a short summer
console.log(RegExp["$`"]); // this has been a
console.log(RegExp["$'"]); // summer
console.log(RegExp["$&"]); // short
console.log(RegExp["$+"]); // s
}
RegExp 还有其他几个构造函数属性,可以存储最多 9 个捕获组的匹配项。
let text = "this has been a short summer";
let pattern = /(..)or(.)/g;
if (pattern.test(text)) {
console.log(RegExp.$1); // sh
console.log(RegExp.$2); // t
}
在这个例子中,模式包含两个捕获组。调用 test()搜索字符串之后,因为找到了匹配项所以返回true,而且可以打印出通过 RegExp 构造函数的$1 和$2 属性取得的两个捕获组匹配的内容。
注意 RegExp 构造函数的所有属性都没有任何 Web 标准出处,因此不要在生产环境中使用它们。
exec 该方法是专门为捕获组而设计的
功能:正则捕获的数据,一个数组;
参数:接收一个参数,即要应用模式匹配的字符串
特性:
使用全局标记g;持续查找所有匹配项并返回
不使用全局标记g;始终返回第一个匹配项信息
执行的过程
检测字符串参数,获取正则表达式匹配文本
找到匹配文本则返回一个数组
第0个元素:与整个模式匹配的字符串
其他元素:与捕获组匹配的字符串
否则返回null
派生属性
index 匹配项在字符串中的位置
input 应用正则表达式的字符串
length 返回数组元素的个数
添加链接描述
let text = "mom and dad and baby";
let pattern = /mom( and dad( and baby)?)?/gi;
console.log(pattern.exec(text)); // ["mom and dad and baby"," and dad and baby"," and baby"]
let matches = pattern.exec(text);
console.log(matches.index); // 0
console.log(matches.input); // "mom and dad and baby"
console.log(matches[0]); // "mom and dad and baby"
console.log(matches[1]); // " and dad and baby"
console.log(matches[2]); // " and baby"
在这个例子中,模式包含两个捕获组:最内部的匹配项" and baby",以及外部的匹配项" and dad"或" and dad and baby"。
调用 exec()后找到了一个匹配项。因为整个字符串匹配模式,所以 matchs数组的 index 属性就是 0。
数组的第一个元素是匹配的整个字符串,第二个元素是匹配第一个捕获组的字符串,第三个元素是匹配第二个捕获组的字符串。
test 方法主要是用来检测字符串是否符合正则表达式要求的规范,返回true或false
接收一个字符串参数。如果输入的文本与模式匹配,则参数返回 true,否则返回 false。这个方法适用于只想测试模式是否匹配,而不需要实际匹配内容的情况。
经常用于if语句中,比如下面的正则表达式用于测试特定的数值序列。常用于验证用户输入,此时我们只在乎输入是否有效,不关心为什么无效。
let text = "000-00-0000";
let pattern = /\d{3}-\d{2}-\d{4}/;
if (pattern.test(text)) {
console.log("The pattern was matched.");
}
无论正则表达式是怎么创建的,继承的方法 toLocaleString()和 toString()都返回正则表达式的字面量表示。
let pattern = new RegExp("\\[bc\\]at", "gi");
console.log(pattern.toString()); // /\[bc\]at/gi
console.log(pattern.toLocaleString()); // /\[bc\]at/gi
这里的模式是通过 RegExp 构造函数创建的,但 toLocaleString()和 toString()返回的都是其字面量的形式。
正则表达式的 valueOf()方法返回正则表达式本身。
JS详解对象篇-RegExp
正则表达式中的特殊字符
正则表达式里面不需要加引号,不管是数字型还是字符串型
1 边界符:
2 字符类
[ ] 字符类表示有一系列字符可供选择,只要匹配其中一个就可以了。所有可选择的字符在方括号里面。
[-] 方括号内部范围符 -
字符组合:不需要空格,直接拼接到后面
let reg = /abc/; //只要包含abc就可以
let rg = /[abc]/; // 只要包含有a 或者b 或者c 都返回true
let r = /^[abc]$/; // 三选一,只有是a 或者b 或者c 这三个字母都是true
console.log(reg.test('abc and dad and baby')); // true
console.log(rg.test('mom and dad and baby')); // true
console.log(r.test('a')); // true
let r = /^[a-z]$/; // 26个英文字母任意一个都是true
let r = /^[a-zA-Z]$/; // 26个英文字母(大小写都可以)任意一个都是true
[^] 如果在中括号里面有 ^ 表示取反的意思,不要和 边界符 ^ 混淆
let r = /^[^a-zA-Z]$/; // 取反,不能包含这些元素
// * 相当于 >=0 可以出现0次或很多次
let reg = /^a*$/;
// + 相当于 >=1 可以出现1次或很多次
let reg = /^a+$/; // 出现0次就是false
// ? 相当于 1 || 0 只允许出现1次或0次
let reg = /^a?$/;
如果要确定a的具体的次数的话,可以使用下面的三个量词:
// {3} 就是重复3次
let reg = /^a{3}$/;
// {3,} 就是重复大于等于3次
let reg = /^a{3,}$/;
// {3,6} 就是重复大于等于3并且小于等于6次
let reg = /^a{3,6}$/;
注意:量词中间不要有空格!!!
4 量词重复某个模式的次数
let r = /^[a-zA-Z0-9_-]{6,16}$/; // 表示方括号里面的内容限定出现6-16次
5 案例:用户名验证分析
6 括号总结
let r = /^abc{3}$/; // 这里只是让c重复3次,abccc
let r = /^(abc){3}$/; // 这里是让abc重复3次,abcabcabc
在线工具
正则表达式在线测试
为了方便操作原始值,也就是暴露出原始值的各种方法,ECMAScript 提供了 3 种特殊的引用类型:Boolean、Number 和 String。也就是说,每当用到某个原始值的方法或属性时,后台都会创建一个相应原始包装类型的对象,从而暴露出操作原始值的各种方法。
let s1 = "some text";
let s2 = s1.substring(2);
s1 是一个包含字符串的变量,它是一个原始值。第二行紧接着在 s1 上调用了 substring()方法,并把结果保存在 s2 中。我们知道,原始值本身不是对象,因此逻辑上不应该有方法。
当第二行访问 s1 时,是以读模式访问的,也就是要从内存中读取变量保存的值,这时后台会执行以下3个步骤:
所以上面的例子实际上执行了如下的内容:
let s1 = new String("some text"); // 创建一个 String 类型的实例
let s2 = s1.substring(2); // 调用实例上的方法
s1 = null; // 销毁实例
这样就可以让原始值拥有对象的行为,这两者的区别是:
对象的生命周期不同
在通过 new 实例化引用类型后,得到的实例会在离开作用域时被销毁,
而自动创建的原始值包装对象则只存在于访问它的那行代码执行期间。这意味着不能在运行时给原始值添加属性和方法。
// 引用类型
const strObj = new String("text");
strObj.prop = "test";
// "test"
strObj.prop;
// 原始类型
const strPrim = "Primitive";
strPrim.prop = "test";
// undefined;
strPrim.prop;
strObj 是一个字符串的引用类型,也就是一个对象,而对象本身是可以拥有属性的。所以当 strObj.prop 被赋值之后,prop 这个属性就会被保存在 strObj 中,一直到 strObj 被销毁。
但是对于原始类型 strPrim 来说就不一样了,一旦过了 ln9,strPrim 的原始值包装类型就会被销毁,因此在 ln10 调用 strPrim.prop 时会返回 undefined。
可以显式地使用 Boolean、Number 和 String 构造函数创建原始值包装对象。
在原始值包装类型的实例上调用 typeof 会返回"object",所有原始值包装对象都会转换为布尔值 true。
Object 构造函数作为一个工厂方法,能够根据传入值的类型返回相应原始值包装类型的实例。
let obj = new Object("some text");
console.log(obj instanceof String); // true
使用 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 的实例。
Boolean 是对应布尔值的引用类型。
要创建一个 Boolean 对象,就使用 Boolean 构造函数并传入true 或 false。
let booleanObject = new Boolean(true);
Boolean 的实例会重写 valueOf()方法
,返回一个原始值 true 或 false。toString()方法被调用时也会被覆盖,返回字符串"true"或"false"。
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 组合起来。这个表达式是对 falseObject对象而不是对它表示的值(false)求值,所有对象在布尔表达式中都会自动转换为 true,因此 falseObject 在这个表达式里实际上表示一个 true 值。那么true && true 当然是 true。
typeof
操作符对原始值返回"boolean",但对引用值返回"object"instaceof
操作符时返回 true,但对原始值则返回 false强烈建议永远不要使用引用值(Boolean 对象)
Number 是对应数值的引用类型。
要创建一个 Number 对象,就使用 Number 构造函数并传入一个数值
let numberObject = new Number(10);
继承的方法:
valueOf()
方法返回 Number 对象表示的原始数值,toLocaleString()
和toString()方法
返回数值字符串。toString()
方法可选地接收一个表示基数的参数,并返回相应基数形式的数值字符串。let num = 10;
console.log(num.toString()); // "10"
console.log(num.toString(2)); // "1010"
将数值格式化为字符串的方法:
toFixed()
方法返回包含指定小数点位数的数值字符串,不过的用0填充,多余的则四舍五入toExponential()
,接收一个参数,表示结果中小数的位数,但返回的是以科学记数法(也称为指数记数法)表示的数值字符串toPrecision()
方法会根据情况返回最合理的输出结果,可能是固定长度,也可能是科学记数法形式。接收一个参数,表示结果中数字的总位数(不包含指数)。本质上,toPrecision()方法会根据数值和精度来决定调用 toFixed()还是 toExponential()let num = 10;
console.log(num.toFixed(2)); // "10.00"
let num = 10.005;
console.log(num.toFixed(2)); // "10.01"
let num = 10;
console.log(num.toExponential(1)); // "1.0e+1"
let num = 99;
console.log(num.toPrecision(1)); // "1e+2"
console.log(num.toPrecision(2)); // "99"
console.log(num.toPrecision(3)); // "99.0"
不建议直接实例化 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
Number.isInteger()
方法,用于辨别一个数值是否保存为整数
IEEE 754 数值格式有一个特殊的数值范围,在这个范围内二进制值可以表示一个整数值。这个数值
范围从 Number.MIN_SAFE_INTEGER(2^53 + 1)到 Number.MAX_SAFE_INTEGER( 2^53 -1)。鉴别整数是否在这个范围内,可以使用 Number.isSafeInteger()
方法。
console.log(Number.isInteger(1)); // true
console.log(Number.isInteger(1.00)); // true
console.log(Number.isInteger(1.01)); // false
console.log(Number.isSafeInteger(-1 * (2 ** 53))); // false
console.log(Number.isSafeInteger(-1 * (2 ** 53) + 1)); // true
console.log(Number.isSafeInteger(2 ** 53)); // false
console.log(Number.isSafeInteger((2 ** 53) - 1)); // true
String 是对应字符串的引用类型。
要创建一个 String 对象,使用 String 构造函数并传入一个数值
let stringObject = new String("hello world");
继承的方法:valueOf()
、toLocaleString()
和 toString()
都返回对象的原始字符串值。
每个 String 对象都有一个 length 属性
,表示字符串中字符的数量。
let stringValue = "hello world";
console.log(stringValue.length); // "11"
JavaScript 字符串由 16 位码元(code unit)组成。对多数字符来说,每 16 位码元对应一个字符。
JavaScript 字符串使用了两种 Unicode 编码混合的策略:UCS-2 和 UTF-16。对于可以采用 16 位编码的字符(U+0000~U+FFFF),这两种编码实际上是一样的
charAt()
方法查找指定索引位置的 16 位码元,并返回该码元对应的字符charCodeAt()
方法可以查看指定码元的字符编码----> codePointAt()
接收 16 位码元的索引并返回该索引位置上的码点(code point)。码点是 Unicode 中一个字符的完整标识。fromCharCode()
方法用于根据给定的 UTF-16 码元创建字符串中的字符,可以接受任意多个数值,并返回将所有数值对应的字符拼接起来的字符串----> fromCodePoint()
对于 U+0000~U+FFFF 范围内的字符,length、charAt()、charCodeAt()和 fromCharCode()
返回的结果都跟预期是一样的。这是因为在这个范围内,每个字符都是用 16 位表示的,而这几个方法也都基于 16 位码元完成操作。只要字符编码大小与码元大小一一对应,这些方法就能如期工作。
这个对应关系在扩展到 Unicode 增补字符平面时就不成立了。问题很简单,即 16 位只能唯一表示65 536 个字符。这对于大多数语言字符集是足够了,在 Unicode 中称为基本多语言平面(BMP)。为了表示更多的字符,Unicode 采用了一个策略,即每个字符使用另外 16 位去选择一个增补平面。这种每个字符使用两个 16 位码元的策略称为代理对。
为正确解析既包含单码元字符又包含代理对字符的字符串,可以使用 codePointAt()
来代替charCodeAt()
, fromCodePoint()
来代替fromCharCode()
let message = "abcde";
console.log(message.charAt(2)); // "c"
// Unicode "Latin small letter C"的编码是 U+0063
console.log(message.charCodeAt(2)); // 99
// 十进制 99 等于十六进制 63
console.log(99 === 0x63); // true
// 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"
let message = "ab☺de";
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(String.fromCharCode(97, 98, 55357, 56842, 100, 101)); // ab☺de
console.log(String.fromCodePoint(97, 98, 128522, 100, 101)); // ab☺de
某些 Unicode 字符可以有多种编码方式。有的字符既可以通过一个 BMP 字符表示,也可以通过一个代理对表示。比较操作符不在乎字符看起来是什么样的,因此这 3 个字符互不相等。为了解决这个问题,Unicode提供了 4种规范化形式,可以将类似上面的字符规范化为一致的格式。
使用 normalize()
方法对字符串应用上述规范化形式,使用时需要传入表示哪种形式的字符串:“NFD”、“NFC”、“NFKD"或"NFKC”。
选择同一种规范化形式可以让比较操作符返回正确的结果:
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 **
concat()
,用于将一个或多个字符串拼接成一个新字符串,并不改变原来字符串的值 ,可以接收任意多个参数,一次性拼接多个字符串。
但更常用的方式是使用加号操作符(+)
,更加方便。
let stringValue = "hello ";
let result = stringValue.concat("world", "!");
console.log(result); // "hello world!"
console.log(stringValue); // "hello"
从字符串中提取子字符串:
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" ,不包括7
console.log(stringValue.substr(3, 7)); // "lo worl" ,第二个参数表示的是返回的字符数
参数是负值时:
slice()
方法将所有负值参数都当成字符串长度加上负参数值substr()
方法将第一个负参数值当成字符串长度加上该值,将第二个负参数值转换为 0substring()
方法会将所有负参数值都转换为 0let stringValue = "hello world";
// -3 会被转换为 8(长度加上负参数),实际上调用的是 slice(8)和 substr(8)
console.log(stringValue.slice(-3)); // "rld"
console.log(stringValue.substring(-3)); // "hello world"
console.log(stringValue.substr(-3)); // "rld", -3 会转换为 0
console.log(stringValue.slice(3, -4)); // "lo w" ,将第二个参数转换为 7,实际上相当于调用 slice(3, 7)
console.log(stringValue.substring(3, -4)); // "hel",将第二个参数转换为 0,相当于调用substring(3, 0),等价于 substring(0, 3),这是因为这个方法会将较小的参数作为起点,将较大的参数作为终点
console.log(stringValue.substr(3, -4)); // "" (empty string),第二个参数会被转换为 0,意味着返回的字符串包含零个字符,因而会返回一个空字符串
用于在字符串中定位子字符串
indexOf()
lastIndexOf()
let stringValue = "hello world";
console.log(stringValue.indexOf("o")); // 4
console.log(stringValue.lastIndexOf("o")); // 7
console.log(stringValue.indexOf("o", 6)); // 7
console.log(stringValue.lastIndexOf("o", 6)); // 4
使用第二个参数并循环调用就可以在字符串中找到所有的目标子字符串
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]
用于判断字符串中是否包含另一个字符串的方法
startsWith()
endsWith()
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
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
console.log(message.endsWith("bar")); // false
console.log(message.endsWith("bar", 6)); // true
trim()
方法会创建字符串的一个副本,删除前、后所有空格符,再返回结果,不改变原始字符串
trimLeft()
和 trimRight()
方法分别用于从字符串开始和末尾清理空格符
let stringValue = " hello world ";
let trimmedStringValue = stringValue.trim();
console.log(stringValue); // " hello world "
console.log(trimmedStringValue); // "hello world"
repeat()方法
接收一个整数参数,表示要将字符串复制多少次,然后返回拼接所有副本后的结果,不改变原始字符串
padStart()
和 padEnd()
方法复制字符串,如果小于指定长度,则在相应一边填充字符,直至满足长度条件
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......"
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"
字符串的原型上暴露了一个@@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"]
toLowerCase()
toLocaleLowerCase()
旨在基于特定地区实现toUpperCase()
toLocaleUpperCase()
旨在基于特定地区实现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"
通常,如果不知道代码涉及什么语言,则最好使用地区特定的转换方法。
match()
方法
search()
方法
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
let text = "cat, bat, sat, fat";
let pos = text.search(/at/);
console.log(pos); // 1 ,即"at"的第一个字符在字符串中的位置
replace()
方法
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"结尾的词都会被替换成"word"后跟一对小括号,其中包含捕获组匹配的内容$1
let text = "cat, bat, sat, fat";
result = text.replace(/(.at)/g, "word ($1)");
console.log(result); // word (cat), word (bat), word (sat), word (fat)
function htmlEscape(text) {
return text.replace(/[<>"&]/g, function(match, pos, originalText) {
switch(match) {
case "<":
return "<";
case ">":
return ">";
case "&":
return "&";
case "\"":
return """;
}
});
}
console.log(htmlEscape("Hello world!
"));
// "<p class="greeting">Hello world!"
split()
方法
注意在最后一次调用 split()时,返回的数组前后包含两个空字符串。这是因为正则表达式指定的分隔符出现在了字符串开头(“red”)和末尾(“yellow”)。
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(/[^,]+/); // ["", ",", ",", ",", ""]
localeCompare()
,比较两个字符串,返回如下 3 个值中的一个
let stringValue = "yellow";
console.log(stringValue.localeCompare("brick")); // 1
console.log(stringValue.localeCompare("yellow")); // 0
console.log(stringValue.localeCompare("zoo")); // -1
返回的具体值可能因具体实现而异,实现所在的地区(国家和语言)决定了这个方法如何比较字符串。
ECMA-262 对内置对象的定义是“任何由 ECMAScript 实现提供、与宿主环境无关,并在 ECMAScript程序开始执行时就存在的对象”。这就意味着,开发者不用显式地实例化内置对象,因为它们已经实例化好了。
ECMA-262 规定 Global对象为一种兜底对象,它所针对的是不属于任何对象的属性和方法。
在全局作用域中定义的变量和函数都会变成 Global 对象的属性 。
用于编码统一资源标识符(URI),以便传给浏览器。有效的 URI 不能包含某些字符,比如空格。使用 URI 编码方法来编码 URI 可以让浏览器能够理解它们,同时又以特殊的 UTF-8 编码替换掉所有无效字符。
encodeURI()
方法:用于对整个 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 后面的字符串的原因。
解码:
decodeURI()
:只对使用 encodeURI()
编码过的字符解码decodeURIComponent()
:基本上就是解码所有特殊值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));
不要在生产环境中使用 escape()和 unescape(),它们只能正确编码 ASCII 字符
eval()
方法就是一个完整的 ECMAScript 解释器,它接收一个参数,即一个要执行的 ECMAScript(JavaScript)字符串。
当解释器发现 eval()调用时,会将参数解释为实际的 ECMAScript 语句,然后将其插入到该位置。通过 eval()执行的代码属于该调用所在上下文,被执行的代码与该上下文拥有相同的作用域链。这意味着定义在包含上下文中的变量可以在 eval()调用内部被引用。
let msg = "hello world";
eval("console.log(msg)"); // "hello world"
eval("function sayHi() { console.log('hi'); }");
sayHi();
eval("let msg = 'hello world';");
console.log(msg); // Reference Error: msg is not defined
通过 eval()定义的任何变量和函数都不会被提升,这是因为在解析代码的时候,它们是被包含在一个字符串中的。它们只是在 eval()执行的时候才会被创建。
在严格模式下,在 eval()内部创建的变量和函数无法被外部访问,赋值给 eval 也会导致错误。
虽然 ECMA-262 没有规定直接访问 Global 对象的方式,但浏览器将 window 对象实现为 Global对象的代理。因此,所有全局作用域中声明的变量和函数都变成了 window 的属性。
var color = "red";
function sayColor() {
console.log(window.color);
}
window.sayColor(); // "red"
另一种获取 Global 对象的方式是使用如下的代码:
let global = function() {
return this;
}();
这段代码创建一个立即调用的函数表达式,返回了 this 的值。如前所述,当一个函数在没有明确(通过成为某个对象的方法,或者通过 call()/apply())指定 this 值的情况下执行时,this 值等于Global 对象。因此,调用一个简单返回 this 的函数是在任何执行上下文中获取 Global 对象的通用方式。
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
let values = [1, 2, 3, 4, 5, 6, 7, 8];
let max = Math.max(...val);
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
Math.random()
方法返回一个 0~1 范围内的随机数,其中包含 0 但不包含 1,[0, 1)
使用 Math.random()
从一组整数中随机选择一个数
number = Math.floor(Math.random() * total_number_of_choices + first_possible_value)
从 1~10 范围内随机选择一个数
let num = Math.floor(Math.random() * 10 + 1);
selectFrom()接收两个参数:应该返回的最小值和最大值。通过将这两个值相减再加 1 得到可选总数,然后再套用上面的公式。
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
从一个数组中随机选择一个元素就很容易
let colors = ["red", "green", "blue", "yellow", "black", "purple", "brown"];
let color = colors[selectFrom(0, colors.length-1)];
如果是为了加密而需要生成随机数(传给生成器的输入需要较高的不确定性),那么建议使用
window.crypto.getRandomValues()
JavaScript 中的对象称为引用值,几种内置的引用类型可用于创建特定类型的对象。
JavaScript 比较独特的一点是,函数实际上是 Function 类型的实例,也就是说函数也是对象。因为函数也是对象,所以函数也有方法,可以用于增强其能力。
由于原始值包装类型的存在,JavaScript 中的原始值可以被当成对象来使用。有 3 种原始值包装类型:Boolean、Number 和 String。它们都具备如下特点。
当代码开始执行时,全局上下文中会存在两个内置对象:Global 和 Math。其中,Global 对象在大多数ECMAScript 实现中无法直接访问。不过,浏览器将其实现为 window 对象。所有全局变量和函数都是 Global 对象的属性。Math 对象包含辅助完成复杂计算的属性和方法