基本引用类型

Date

Date和java的Date类似,里面保存的是从1970年1月1日至今的毫秒数。

创建

  • Date(date): 创建一个Date对象,包含创建时的时间.date是字符串形式的时间,格式可以参照parse()的格式
  • Date.now(): 返回当前时间的毫秒数
  • Date.parse(): 将其他格式的时间字符串转换为该日期的毫秒数,例如let someDate = new Date(Date.parse("May 23, 2019"));
    • 可以使用下列格式
    • 月/日/年: 如”5/23/2019”
    • 月名 日, 年: 如”May 23, 2019”
    • 周几 月名 日 年 时:分:秒:时区,如”Tue May 23 2019 00:00:00 GMT-0700”
    • YYYY-MM-DDTHH:mm:ss:sssZ,如”2019-05-23T00:00:00
  • Date.UTC(year, month, day, hour, minute, second):传入年、月(0-11)、日(1-31)、时(0-23)、分、秒并转化为毫秒数,如let y2k = new Date(Date.UTC(2000, 0));

显示

  • toLocaleSting(): 输出类似于2021/5/30 下午3:51:36
  • toString(): 输出为Sun May 30 2021 15:51:36 GMT+0800 (中国标准时间)
  • toDateString(): 返回周几、月、日、年
  • toTimeString(): 返回时分秒和时区
  • toLocaleDateString(): 显示月日年
  • toLocaleTimeString(): 显示时分秒
  • toUTCString(): 显示完整的UTC时间

还有一些获得单独时间的函数如getYear(),getDate()等可以从函数名得知意思就不再赘述。其中getTime()是获得毫秒的时间

RegExp

RegExp是正则表达式,可以通过一些简单的语法创建。如let expression = /pattern/flags

pattern可以设置lastIndex表示下次从该index处开始

其中pattern是任何正则表达式。每个正则表达式可以带零个或多个标记(flags),用于控制正则表达式的行为,常见的标记有

  • g(global): 全局模式,查找所有匹配字符串,而不是匹配一个就结束.
  • i(ignoreCase): 不区分大小写
  • m(multiline): 多行模式,查找到一行文本末尾会继续查找
  • y(sticky): 粘附模式,表示查找从lastIndex开始的字符串
  • u(unicode): Unicode模式,启用Unicode匹配
  • s(dotAll): dotAll模式,匹配任意字符(如\n\r等)

可以使用后面的全称来访问是否设置了这些标记

console.log(pattern.global);
console.log(pattern.source);//表达式的字面字符串
console.log(pattern.flags);所有标记

除了使用字面量,还可以使用构造函数来创建。例如:

let pattern1 = /[bc]at/i;
let pattern2 = new RegExp("[bc]at", "i");

RegExp的模式参数时字符串,有些时候需要二次转义
let pattern3 = /\[bc\]at/
let pattern4 = new RegExp("\\[bc\\]at", "");

匹配

使用RegExp进行匹配主要是exec(s)方法,方法的参例如:数是待匹配的字符串。如果成功,返回包含第一个匹配的数组,每个字符占一个位置。如果未成功,返回null。返回值虽然是数组,但是额外多加了index和input。index是匹配串中的起始位置,input是待匹配的字符串。

例如:

let text = 'mom and dad and baby';
let pattern = /mom( and dad( and baby)?)?/gi;

let matches = pattern.exec(text);
console.log(matches[0]);//mom and data and baby
console.log(matches[1]);// and data and baby
console.log(matches[2]);/ and baby

每个括号表示一个捕获组,也就是第一个会匹配mom and dad and baby,第二个会匹配 and dad and baby.有关捕获组可看这

如果使用了全局标记,那么一次匹配会显示一个匹配的信息,如果没有设置,那么只会返回第一个匹配信息。

let text = 'cat, bat, sat, fat';
let pattern = /.at/g;
let matches = pattern.exec(text);
console.log(matches[0]);//cat
matches = pattern.exec(text);
console.log(matches[0]);//bat

如果使用了y,那么下次就会从lastIndex开始进行搜索,并且该标记会覆盖全局标记。注意y和g并不相同

let pattern = /.at/y;
let matches = pattern.exec(text);
console.log(matches[0]);//cat
console.log(pattern.lastIndex);//3

let matches = pattern.exec(text);
console.log(matches[0])//null
//和上面不同的原因是这里从3开始搜索,而3占据着.的位置,后面两个字符不是at,所以最终出错

pattern.lastIndex = 5;
matches = pattern.exec(text);
console.log(matches[0]);//bat

test(),如果有模式匹配则返回true

此外RegExp本身还有一些属性

  • input: 最后搜索的字符串
  • lastMatch: 最后匹配的文本
  • lastParen: 最后匹配的捕获组
  • leftContext: input字符串中出现在lastMatch前面的文本
  • rightContext: 出现在后面的文本
let text = 'this has been a short summer';
let pattern = /(.)hort/g;
if(pattern.test(text))
{
console.log(RegExp.input);//this has been a shor summer
console.log(RegExp.leftContext);//this has been a
}

Boolean、Number、String

这些都是原始值包装类型,也就是对原始值的行为进行扩充。每当用到某个原始值的方法和属性时,后台都会创建一个包含原始值的对象。

let s1 = 'some text';//原始值
let s2 = s1.substring(2);//创建了一个包装对象并调用substring函数

包装类型的形成大致过程为:

  1. 发现调用原始值的函数时,创建包装类型
  2. 调用实例上的方法
  3. 销毁实例

这种方式也就意味着我们不能再包装类型上添加方法,因为它使用后会立刻销毁。可以显式的使用Boolean、Number、String创建包装对象,就可以对这些对象进行操作。但是不推荐直接使用包装类型,因为可能会导致一些错误。

构造函数创建的包装类型使用typedef会返回object,因此Object构造方法会根据传入参数不同自动转型。并且如果使用转型函数那么typeof会显示对应类型

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

let s = new String('a');
console.log(typeof s);//object
let value = '23';
let number = Number(value);//转型函数
console.log(typeof number);//number

Number

  • toFixed(precision): 设置保留几位小数。例如
    let num = 10;
    console.log(num.toFixed(2));//10.00
  • toExponential(precision): 返回科学记数法形式的字符串,也可以设置精度
  • toPrecision(): 他会根据情况返回结果,可能是固定长度,也可能是科学记数法
    let num = 99;
    console.log(num.toPrecision(1));//1e+2
    console.log(num.toPrecision(2));//99
    console.log(num.toPrecision(3));//99.0
  • isInteger(): 判断一个数值是否是整数,并不是说加了小数点就是整数,例如1.00是整数。他根据小数点之后是否还有位不是0判断是否是整数

String

js使用了两种unicode编码,都是两个字节表示一个字符。

两个字节最多表示65536中字符。但是为了表示更多字符,有时候会使用前16位中某些为特殊字符,然后后16位和前16位共同表示一个字符,这时使用charAt就会出现问题。

例如:

let message = String.fromCodePoint(97, 98, 128522, 100, 101);//将数字转换成unicode字符,分别是a, b, 笑脸, d, e

//128522的十六进制是0x1f60a

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,b的序号
console.log(message.charCodeAt(2));//55357

  • length
  • charAt(index): 返回下标处的字符
  • charCodeAt(index): 返回下标处的字符对应数值,例如0是48。但是它不会考虑扩展字符
  • codePointAt(index): 返回下表处的字符对应数值,考虑了扩展字符
  • fromCharCode(…): 根据字符对应数值返回字符串,如String.fromChatCode(48, 49)//01
  • fromCodePoint(…): …
  • normalize(): 某些字符可以有多种编码方式,normalize()统一这些编码,共有NFD、NFC、NFKD、NFKC四种规范形式
    console.log(String.fromCharCode(0x00c5));//大写拉丁字母
    console.log(String.fromCharCode(0x212B));//长度单位埃
    console.log(String.fromCharCode(0x0041, 0x030a));//大写拉丁字母上面加个圆圈
    这三个虽然编码不相同,但是实际上最后返回的是同一个东西,而使用==是比较不出的,这时我们就需要normalize()规范编码
    let a1 = String.fromCharCode(0x00c5));
    let a2 = String.fromCharCode(0x212b));

    console.log(a1 == a2);//false
    console.log(a1.normalize('NFD') == a2.normalize('NFD'));//true

下面是字符串操作方法

  • concat(): 将若干个字符串拼接到当前字符串后面,concat()可以接受任意数量的字符串,例如
    let s = 'hello ';
    let result = s.concat('world');
    console.log(result);//hello world
  • slice,substring, substr: 三个都是获得子字符串,第一个参数都是子字符串开始位置,前两个的第二个参数是结尾位置,而substr第二个参数是抽取字符数量。
  • indexOf(),lastIndexOf(): 查找子字符串在字符串中的位置,不同的是indexOf从开始查找,而lastIndexOf从结尾开始查找。第一个参数是要查找的字符串,第二个参数时起始位置
  • startsWith、endsWith/includes: 检查一个字符串是否包含另一个字符串,startsWith是从第一个字符开始匹配(检查是否是以这个字符串开头),endsWith是从string.length-substring.length开始匹配。而includes检索整个字符串
    let message = 'foobarbaz';
    console.log(message.startsWith('foo'));//true
    console.log(message.startsWith('bar'));//false,不是以bar开头

    console.log(message.endsWith('baz'));//true
    console.log(message.includes('baz'));/true
  • trim(): 删除两边所有的空格
  • repeat(times): 把原字符串复制times次
  • padStart()、padEnd(): 复制字符串,如果小于指定长度则会填充字符。第一个参数时长度,第二个参数时填充字符
  • toLowerCase(), toUpperCase()

下面是匹配(正则表达式):

  • match(regexp): 作用和RegExp中的exec类似,只是这里的参数变成了模式串,并且这里全局模式直接返回一个数组
  • search(regexp): 返回第一个匹配
  • replace(regexp, replace_str): 如果第一个参数是一个字符串,那么只会替换第一个匹配到的,如果想要匹配所有,必须使用正则表达式并且加上g。
    其中replace_str有几种特殊的符号可以使用

    | 字符 | 替换文本 |
    |-|-|
    | $$$$ | $ |
    | $' | 替换匹配子字符串之前的字符串 |
    | $` | 匹配子字符串之后的字符串 |
    | $n | 匹配第n个捕获组,关于捕获组可以看前面.范围是0-9 |
    | $nn | 匹配第nn个捕获组,如01-99 |

    例如:

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

字符串有迭代器,可以使用for-of访问每个字符,例如:

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

console.log(stringIterator.next());//[value: 'a', done: false]

for(const c of 'abcde')
{
console.log(c);
}
并且我们还可以使用解构操作符把字符串变成数组
let message = 'abcde';
console.log([...message]);//['a', 'b', 'c', 'd', 'e'];

Global

内置对象就是在程序执行前就开始执行的对象,和宿主环境无关,例如Object、Array和String都是内置对象。

window对象已经实现了Global对象的代理,它有Global的一系列方法。

方法:

  • encodeURI(),encodeURIComponent(): 用于编码URI来传给浏览器。他会对某些不能出现在URI中的字符进行处理,如空格.其中encodeURIComponent()会对冒号、斜杠、问号等字符也进行编码
    let uri = 'http://www.wrox.com/illegal value.js';
    console.log(encodeURI(uri));
    //http://www.wrox.com/illegal%20value.js',把空格变成%20

    console.log(encodeURIComponent(uri));
    //http%3a%2f%2fwww.wrox.com%2fillegal%20value.js';
  • eval(): 调用eval时,字符串会被解析为语句并执行,例如eval("console.log('hi')");//hi

Math

math提供了许多数学计算的方法。并且math上提供的计算比普通计算要快很多,因为它的实现更加高效。

math属性

math中的属性包含了一些特殊值

  • E: 2.71828
  • LN10: 以10为底的自然对数
  • LN2: 以2为底的自然对数
  • LOG2E: 以2为底e的对数
  • LOG10E: 以10为底e的对数
  • PI
  • SQRT1_2: 1/2的平方根
  • SQRT2: 2的平方根

方法:

  • min(), max()
  • ceil(): 向上舍入
  • floor(): 向下舍入
  • round(): 四舍五入
  • fround(): 返回浮点数表示
  • random(): 返回0-1的随机数,其中包含0但不包含1
  • 等等

集合引用类型

Array

创建数组

let colors = new Array();
let colors = new Array(3);//创建长度为3的数组
let names = new Array("Greg");//只包含一个元素的数组

let colors = ['red', 'blue', 'green']//使用数组字面量创建数组

let colors = [,,,,,];//创建包含5个元素的数组

  • from(data, func, this): 从data转换为数组,data可以是集合,map等。func是进行的一些操作。this是func中的this值,但是func不能是箭头函数
  • of(): 将一组参数转换成数组
const a1 = [1, 2, 3, 4]
const a2 = Array.from(a1, x=>x**2)
const a3 = Array.from(a1, function(x) {return x**this.exponent}, {exponent: 2});

Array.of(1, 2, 3, 4)

索引

数组索引和c++最大的不同是它可以动态变化,例如

let colors = ['red', 'blue', green'];
colors[3] = 'yellow';//添加一个元素

Array中有一个属性length代表了数组的长度,这个值是可以进行设置的,从而改变数组长度(如果缩减长度会导致原有内容消失)

let colors = ['red', 'blue', 'green']
colors[99] = 'black';
console.log(colors.length)//100
colors.length = 2
console.log(colors[2])//undefined
colors.length = 3
console.log(colors[2])//undefined,缩减后数据被删除了

//可以很方便的添加数据
colors[length] = 'yellow';

方法

  • keys(),values(), entries(): keys是索引的迭代器,values是值的迭代器,entries()是键值对的迭代器,例如
    const key = Array.from(a.keys())
    console.log(key)//0, 1, 2, 3
    const value = Array.from(a.values())
    console.log(value)//'foo', 'bar', 'baz', 'qux'
    const entries = Array.from(a.entries())
    console.log(entires)//[0, 'foo'], [1, 'bar'], [2, 'baz'], [3, 'qux']

    for(const [idx, element] of a.entires())
    {
    console.log(idx);
    console.log(element);
    }
  • fill(sum, begin, end): 从begin到end填充sum
  • copyWithin(index, begin, end): 复制数组begin到end的内容并且插入到以index开始的位置中
  • toString(): 每个值都会调用toString()方法
  • push(): 拖入任意数量的值并且放入末尾
  • pop(): 删除最后一项
  • shift(): 取得第一项
  • unshift(): 从第一个位置插入
  • sort(func): 排序,可以按照指定的func进行排序
  • reverse(): 反向排序
  • concat(): 拼接
  • indexOf(), includes(), lastIndexOf(): 搜索某一个元素在数组中的位置,indexOf(), includes()从前往后搜索,lastIndexOf()从后往前.indexOf()和lastIndexOf()没有搜索到返回-1.includes()没有搜索到返回false。他们进行比较时使用====,也就是两项必须严格相等
  • every(func): 对每一项都运行函数,如果全部返回为真,则结果为真
  • filter(func): 运行函数为true的项会组成数组返回
  • foreach(func): 对每一项都运行函数
  • map(func): 对每一项都运行函数,返回由函数结果构成的数组
  • some(func): 只要有一项返回为true结果就为true
    let number = [1, 2, 3, 4, 4]
    let result = number.every((item, index, array)=>item > 2);//false
    let mapres = number.map((item, index, array)=>item * 2);
    number.forEach((item, index, array)=>{...});
  • reduce(func): 对数组中数据进行归约
    let values = [1, 4, 9, 16]
    let sum = values.reduce((prev, cur, index, array)=> prev + cur);//30

    第一次执行时prev=1,cur=4,第二次执行prev=5(前面的归约结果), cur=9(第三项

    Map

创建:

const m = new Map();
const m1 = new Map([
['key1', 'val1'],
['key2', 'val2'],
['key3', 'val3']
]);

map和object类型都可以插入键值对,但是他们还是有很大区别的。Object类型的键只能是数值,字符串或符号,而map可以是任意类型。其次map内部是有序的(插入顺序),而Object是无序的。我们可以使用entries()获得一个个按照插入顺序排列的(key, value)形式的数组

for(let pair of m.entries())
{
alert(pair);
}
//[key1, val1]
//[key2, val2]
//[key3, val3]

函数:

  • set(): 添加键值对
  • get()/has(): 输入键,查询值
  • delete()/clear(): 删除
    const m = new Map();
    m.get("firstName");
    m.set("firstName", "Matt");

    m.has("firstName");//true

    m.delete("firstName");
  • keys()/values(): 返回key或value的迭代器

键和值在迭代器遍历时是可以修改的,但是在Map内部不会修改。当然,这种修改不会影响使用这个键访问值

const m = new Map({% post_link "key1", "val1" %});

for(le tkey of m.keys())
{
key = "newKey";
alert(m.get("key1")//val1,说明在map内没有被修改
}

const keyObj = {id: 1};
const m1 = new Map({% post_link keyObj, "val1" %});
for(let key of m.keys())
{
key.id = "newKey";
alert(m.get(key);//val1
}

WeakMap

弱映射表示映射的键是弱的,一旦键的引用被清除,这个键值对就会被清除。

他和普通的Map不同在于普通的map的键是在对象中独立存在的,除非调用delete否则不会被清除,而WeakMap中键直接引用外部变量,外部变量被清除这个键值对也就被清除。

const w = new WeakMap();

const container = { key: {}};

w.set(container.key, 'val');

function remove_reference()
{
container.key = null;
}

由于container对象保有对key的引用,所以这个键值对暂时不会被清除,一旦调用remove_reference清除了外部引用之后,这个键值对将自动清除。

因为引用被删除就会自动回收,所以它可以很方便的给某些数据添加额外属性,省的每次都要delete

const w = new WeakMap();
const loginButton = document.querySelector('#login');
w.set(loginButton, {disabled: true});

set

set的基本函数和map类似,并且它的插入也是按序的。但是js里面的set和其他的set不同。它并没有提供集合操作方法,需要我们自己实现。

static union(a, ...bSets)
{
const unionSet = new My_set(a);
for(const b of bSets)
{
for(const bValue of b)
{
unionSet.add(bValue);
}
}
return unionSet;
}

static intersect(a, ...bSets)
{
const interset = new My_set(a);
for(const aValue of interSet)
{
for(const b of bSets)
{
if(!b.has(aValue)
{
interset.delete(aValue);
}
}
}
}

...

此外,他也有WeakSet,效果和WeakMap相同