第5章 基本引用类型


引用值(或者对象)是某个特定引用类型的实例。在ECMAScript中,引用类型是把数据和功能组织到一起的结构,引用类型有时候也被称为对象定义(不是“类”),因为它们描述了自己的对象应有的属性和方法。

对象被认为是某个特定引用类型的实例。新对象通过使用new操作符后跟一个构造函数来创建。构造函数就是用来创建新对象的函数。

5.1 Date

ECMAScript中的Date类型将日期保存为协调世界时(UTC,Universal Time Coordinated,是最主要的世界時間標準,其以原子时秒长为基础,在时刻上尽量接近于格林威治标准时间)自1970年1月1日零时至今经过的毫秒数。使用这种存储格式,Date类型可以精确表示1970年1月1日之前之后285616年的日期。

let now = new Date();
console.log(now); //2021-06-21T13:36:44.910Z 在不传参数给Date()的情况下,创建的对象将保存当前日期和时间

如果要基于其他日期和时间来创建日期对象,必须传入其毫秒表示(UNIX纪元 1970年1月1日零时之后的毫秒数)(UNIX 纪元是Unix或类Unix系统,一些C/C++,Java等编程语言使用的纪元,从1970年一月一日00:00 开始)。ECMAScript提供了两个辅助方法,Date.parse() 和 Date.UTC()。

let date1 = new Date("1/2/2018");//直接传字符串,后台先调用Date.parse(),再传给Date()
let date2 = new Date(Date.parse("1/2/2018"));

console.log(date1);//2018-01-01T16:00:00.000Z
console.log(date2);//2018-01-01T16:00:00.000Z
console.log(Date.parse("1/2/2018")); //1514822400000

越界的日期,比如1月32日,有些浏览器会解释为2月1日,有的会解释为1月当前日,需要提前验证。

Date.UTC()方法也返回日期的毫秒表示,传给其的参数是年、月(0-11)、日、时(0-23)、分、秒、毫秒。这些参数中,只有年和月是必需的,如果不提供日,那么默认为1日,其他参数默认为0。

let y2k = new Date(Date.UTC(2000,0));//GMT时间2000年1月1日零点
let allFives = new Date(Date.UTC(2005,6,7,12,54,12,111));

let y2k = new Date(2000,0);//同用Date.UTC()创建的日期,但是是本地时区的2000年1月1日零点
let allFives = new Date(2005,6,7,12,54,12,111);

let start = Date.now();
doSomething();
let stop = Date.now();
result = stop - start;//测试运行时间
  • 继承的方法

    与其他类型一样,Date类型重写了toLocaleString()、toString()和value()方法。

    Date.toLocaleString();//返回与浏览器运行的本地环境一致的日期和方法,意味着格式中包含着AM或PM,但不包括时区信息
    Date.toString();//返回带时区信息的日期和时间,时间以24(0-23)小时制表示。
    Date.Value();//返回日期的毫秒表示
    
  • 日期格式化方法(P106)

  • 日期/时间组件方法

5.2 RegExp

ECMAScript通过RegExp类型支持正则表达式。正则表达式创建:

let expression = /pattern/flags;
let expression = new RegExp(pattern,flags);//也可以以构造函数创建,两个参数都是字符串,需要加引号

pattern (模式)包括字符类、限定符、分组、向前查找和反向引用。flags(标记),可选,零个或多个,用于控制正则表达式的行为。

//flags:
g:全局模式,global
i:不区分大小写,ignoreCase
m:多行模式,multiline,查找到一行文本末尾会继续查找
y:粘附模式,sticky,只查找从lastIndex开始及之后的字符串
u:Unicode模式,启用Unicod匹配
s:dotAll模式,表示元字符.匹配任何字符(包括\n或\r)

let pattern1 = /at/g;匹配字符串所有“at”
let pattern2 = /[bc]at/i;匹配第一个“bat”或“cat”,不区分大小写
let pattern3 = /.at/gi;匹配所有以“at”结尾的三字符组合,不区分大小写

元字符(小中大括号、加乘、\、^、$、|、?、.、/)在正则表达式中都有一种或多种特殊功能,所以要匹配这些字符本身,就必须用反斜杠\来转义。

let p1 = /\[bc\]at/i;//匹配第一个“[bc]at”,忽略大小写
let p2 = /\.at/gi;//匹配所有“.at”,忽略大小写

在用RegExp()构造函数创建正则表达式时,因为传入的参数是字符串,有时需要二次转义。

let p1 = new RegExp(".at","gi");
let p2 = new RegExp("\.at","gi");
let p3 = new RegExp("\\.at","gi");

let text = "but he at school,.at";

let match1 = p1.exec(text);
let match2 = p2.exec(text);
let match3 = p3.exec(text);

console.log(match1);//[ ' at', index: 6, input: 'but he at school,.at', groups: undefined ]
console.log(match2);//[ ' at', index: 6, input: 'but he at school,.at', groups: undefined ],效果同第一种情况
console.log(match3);//[ '.at', index: 17, input: 'but he at school,.at', groups: undefined ],“\\”才能表达出找“.at”的目的

此外,使用RegExp也可以基于已有的正则表达式实例。

const re1 = /cat/g;
const re2 = new RegExp(re1);//"/cat/g"
const re3 = new RegExp(re2,"i");//"cat/i",将flags 修改为了 “i”

5.2.1 RegExp实例属性

let p1 = /\[bc\]at/i;
p1.global;// 是否设置了g标记
p1.ignoreCase;//是否设置了i标记
p1.multiline;//是否设置了m标记
p1.lastIndex;//表示在源字符串中下一次搜索的开始位置
p1.source;// "\[bc\]at",正则表达式的字面量字符串
p1.flags;//"i",正则表达式的标记字符串

5.2.2 RegExp实例方法

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

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",匹配的第二个捕获组的字符串

如果模式为全局模式,则每次调用exec()方法会返回下一个匹配的信息。如果没有设置全局标记,无论对同一个字符串调用多少次exec(),也只会返回第一个匹配的信息。

在全局模式下,每次调用exec()都会更新lastIndex值,表示上次匹配的最后一个字符的下一个字符的索引。如果模式设置了粘附标记y,则每次调用exec()只会在 latIndex的位置上寻找匹配项。即粘附标记覆盖全局标记。如果没找到,则又将lastIndex的值设置为0。

let text = "0000-00-0000";
let pattern = /\d{3}-\d{2}-\d{4}/;

if (pattern.test(text)){ //test()方法用于测试模式是否匹配
    console.log("matched!")
}

console.log(pattern.toString());// /\d{3}-\d{2}-\d{4}/,返回正则表达式的字面量表示
console.log(pattern.toLocaleString());// /\d{3}-\d{2}-\d{4}/,返回正则表达式的字面量表示

5.2.3 RegExp 构造函数属性

RegExp 构造函数本身也有几个属性。在其他语言中,这种属性被称为静态属性。这些属性适用于作用域中的所有正则表达式,而且会根据最后执行的正则表达式操作而变化。这些属性还可以通过两种不同的方式访问它们。一个全名、一个简写。

全名 简写 说明
input $_ 最后搜索的字符串(非标准特性)
lastMatch input
lastMatch $& 最后匹配的文本
lastParen $+ 最后匹配的捕获组(非标准特性)
leftContext $` input字符串出现在lastMatch前面的文本
rightContext $' input字符串出现在lastMatch后面的文本
最后匹配的文本
lastParen $+ 最后匹配的捕获组(非标准特性)
leftContext 全名
------------ ---- ------------------------------------
input字符串出现在lastMatch前面的文本
rightContext input字符串出现在lastMatch后面的文本
let text = "this has been a short summer";
let pattern = /(..)or(.)/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);// t
    console.log(RegExp.$1);// sh,第一个捕获组的匹配项
    console.log(RegExp.$2);// t,第二个捕获组的匹配项,最多9个
}

5.3 原始值包装类型

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

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

原始值本身不是对象,逻辑上不应该有方法。而实际上这个例子确实按照预期运行了,这是因为后台进行了很多处理,从而实现了上述操作。具体地,当第二行访问s1时,是以读模式访问的,也就是从内存中读取变量保存的值。在以读模式访问字符串值的任何时候,后台会执行以下3步:

(1)创建一个String类型的实例;

(2)调用实例上的特定方法;

(3)销毁实例。

相当于执行了以下3行代码:

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

这种行为可以让原始值拥有对象的行为。引用类型与原始值包装类型的主要区别在于对象的生命周期。通过new实例化引用类型后,得到的实例会在离开作用域时被销毁,而自动创建的原始值包装对象则只存在于访问它的那行代码执行期间。

let s1 = "some text";
s1.color = "red";
cosole.log(s1.color); // undefined,第二行创建临时对象,当第三行执行时,对象已经被销毁了

5.3.1 Boolean

let falseObject = new Boolean(false);
let result = falseObject & result;//这里是对falseObject对象求值,而不是对它表示的值求值。所有对象在布尔表达式中都会自动转换为true
console.log(result); //true

let falsevalue = false;
typeof falseObject;//object
typeof falsevalue;//boolean

falseObject intanceof Boolean;//true
falsevalue intanceof Boolean;//false

5.3.2 Number

let num = 10;

num.toString();//"10",返回数值字符串
num.toString(2);//"1010",传入的参数代表进制数
num.toString(16);//"a"

num.toFixed(2);//"10.00",返回包含指定小数点位数的数值字符串

num.toExponential(1);//"1.0e+1",返回以科学计数法表示的数值字符串,接收一个参数表示结果中小数的位数

num.toPrecision(1);//“1e+2”,根据情况返回最合理的输出结果,接收一个参数表示结果中数字的总位数(不包含指数)

Number.isinteger(1);//true
Number.isinteger(1.00);//true
Number.isinteger(1.01);//false

Number.MIN_SAFE_INTEGER:-1*(2**53) + 1
Number.MAX_SAFE_INTEGER:2**53 - 1//分别表示可以保存的整数的上下限,超出后的值不能安全保存,IEEE 754编码格式意味着二进制值可能会表示为一个完全不同的数值

Number.issafeInteger(-1*(2**53));//false
Number.issafeInteger(-1*(2**53)+1);//true
Number.issafeInteger(2**53);//false
Number.issafeInteger(2**53-1);//true

5.3.3 String

let stringValue = "hello world";
console.log(stringValue.length);//11
  • javaScript 字符(此处需要进一步深入学习

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

    let message = "abcde";
    
    message.length; //5
    message.charAt(2);//"c",查找指定位置的码元,并返回该码元对应的字符
    message.charCodeAt(2);//99,查看指定码元的字符编码
    
    String.fromCharcode(97,98,99,100,101);//"abcde",根据字符编码返回字符,并粘贴
    String.fromCharcode(0x61,0x62,0x63,0x64,0x65);//"abcde",十六进制也可,16位码元
    

    16位只能唯一表示65536个字符,这对于大多数语言字符集是足够了,在Unicode中称为基本多语言平面(BMP),UTF-16共有17个平面,在表示增补字符平面的字符时,65536个码位可能并不够。为了表示更多的字符,Unicode采用了一个策略,即每个字符使用另外16位去选择一个增补平面。这种每个字符使用两个16位码元的策略称为代理对。

    let message = "ab🙂de";
    
    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));//56898,两个16位码元表示一个符号
    console.log(message.charCodeAt(4));//100
    
    console.log(message.codePointAt(1));//98
    console.log(message.codePointAt(2));//128578,码点是Unicode中一个字符的完整标识
    console.log(message.codePointAt(3));//56898
    console.log(message.codePointAt(4));//100
    
    console.log([...message]);
    
    console.log(String.fromCharCode(97,98,55357,56898,100,101));
    console.log(String.fromCharCode(97,98,128578,100,101));
    
  • normalize()方法

    ​ 某些Unicode字符可以有多种编码方式。有的字符可以通过一个BMP字符表示,也可以通过一个代理对表示。有时候同一种看起来一样的字符实际上互不相等,为了解决这个问题,Unicode提供了4种规范化形式,可以将字符规范化为一致的格式,无论底层字符的代码是什么。4种规范化的形式是:NFD(Normlization Form D)、NFC(Normallization Form C)、NFKD(Normallization Form KD)、NFKC(Normlization Form KC)。可以使用normlize()方法对字符串应用上述规范化形式。

  • 字符串操作方法

    let stringValue = "hello ";
    let result = stringValue.concat("world");// "hello world"
    stringValue.slice(3,7);//"lo w",第一个参数是字符串开始位置,第二个参数是字符串结束位置
    stringValue.substring(3,7);//"lo w",第一个参数是字符串开始位置,第二个参数是字符串结束位置
    stringValue.substr(3,7);//"lo worl",第一个参数是字符串开始位置,第二个参数是返回的字符串数量
    
    stringValue.slice(-3);//"rld",第一个参数是负数表示从倒数几个字符
    stringValue.substring(-3);//"hello world",第一个参数是负数会直接被转换成0
    stringValue.substr(-3);//"rld",第一个参数是负数表示倒数几个字符
    
    stringValue.slice(3,-4);//"lo w",第二个参数是负数表示截止到倒数几个字符
    stringValue.substring(3,-4);//"hel",第二个参数是负数会直接被转换成0,等价于substring(0,3)
    stringValue.substr(3,-4);//"",第二个参数是负数会直接被转换成0,意味着包含0个字符
    
  • 字符串位置方法

    let stringValue = "hellow world";
    stringValue.indexOf("o");//4
    stringValue.lastIndexOf("o");//7,从字符串末尾开始查找字符串
    
    stringValue.indexOf("o",6);//7,第二个参数表示从该位置开始搜索
    stringValue.lastIndexOf("o",6);//4,从位置6开始向字符串开头搜索
    
    let stringValue = "some text";
    let position = new Array();
    
    pos = stringValue.indexOf("x");
    while(pos>-1){
        position.push(pos);
        pos = stringValue.indexOf("x",pos+1);
    }
    
  • 字符串包含方法

    let message = "foobarbaz";
    
    message.startWith("foo");//true
    message.endtWith("bar");//false
    message.include("bar");//true
    
    message.startWith("foo",1);//false,第二个参数表示开始搜索的位置
    message.include("bar",4);//false,第二个参数表示开始搜索的位置
    message.endtWith("bar",6);//false,第二个参数表示该位置前的字符串是否以前面的字符串结尾
    
  • trim()方法

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

    let stringValue = " hellow world ";
    stringValue.trim();
    stringValue.trimLeft();
    strinValue.trimRight();
    
  • repeat()方法

    这个方法接收一个整数参数,表示将字符串复制多少次,然后返回拼接所有副本后的结果。

    stringValue.repeat(16) + "batman";
    
  • padStart()和padEnd()方法

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

    let stringValue = "foo";
    
    stringValue.padStart(6);//"   foo"
    stringValue.padStart(9,".");//"......foo"
    
    stringValue.padEnd(6);//"foo   "
    stringValue.padEnd(9,".");//"foo......"
    
    stringValue.padStart(8,"bar");//"barbafoo"
    stringValue.padStart(2);//"foo",长度小于字符串长度,返回源字符串
    
    stringValue.padEnd(8,"bar");//"foobarbarba"
    stringValue.padEnd(2);//"foo",长度小于字符串长度,返回源字符串
    
  • 字符串迭代与解构

    字符串的原型上暴露一个@@iterator方法,表示可以迭代字符串的每个字符。

    let message = "abc";
    let stringIterator = message[Symbol.iterator]();
    
    stringIterator.next();//{value:"a",done:false}
    stringIterator.next();//{value:"b",done:false}
    stringIterator.next();//{value:"c",done:false}
    stringIterator.next();//{value:undefined,done:true}
    
    for (const c of "abcde"){
        console.log(c);
    }
    
    console.log([...message]);//["a","b","c","d","e"]
    
  • 字符串大小写转换

    toLowerCase()和toUpperCase()方法是原来就有的方法。toLocaleLowerCase()与toLocaleUpperCase()方法旨在基于特定地区实现。在很多地区,特区特定的方法与通用方法是一致的。但在少数语言中(如土耳其语),Unicode大小写转换需应用特殊规则,需要地区特定的方法才能实现正确转换。

    sringValue.toUpperCase();
    stringValue.toLowerCase();
    stringValue.toLocaleUpperCase();
    stringValue.toLocaleLowercase();
    
  • 字符串模式匹配方法

    let text = "cat,bat,sat,fat";
    let pattern = /.at/;
    
    let matches = text.match(pattern);
    let pos = text.search(/at/);
    
    let result = text.replace("at","ond");
    result = text.replace(/at/g,"ond");
    result = text.replace(/(.at)/g,"word ($1)");//word (cat),word (bat),word (sat),word (fat)
    
    let colorText = "red,blue,green,yellow";
    colorText.split(",");//["red","blue","green","yellow"]
    colorText.split(",",2);//["red","blue"]
    colorText.split(/[^,]+/);//["",",",",",",",""];//?
    
  • localeCompare()方法

    let stringValue = "yellow";
    
    console.log(stringValue.localeCompare("brick"));//1,参数字符串排在目标字符串前面,返回1
    console.log(stringValue.localeCompare("yellow"));//0,两个字符串相等
    console.log(stringValue.localeCompare("zoo"));//-1,参数字符串排在目标字符串前面,返回-1
    

5.4 单例内置对象

​ ECMAScript对内置对象的定义是“任何由ECMAScript实现提供、与宿主环境无关,并在ECMAScript程序开始执行时就存在的对象”。意味着开发者不用显式地实例化内置对象,因为它们已经实例化好了。内置对象包括但不限于 Object、Array、String、Global、Math。

5.4.1 Global

ECMA-262 规定Global对象为兜底对象,它所针对的是不属于任何对象的属性和方法。事实上,不存在全局变量或全局函数这种东西。在全局作用域中定义的变量和函数都会变成Global对象的属性。isNaN()、isFinite()、parseInt()和parseFloat()实际上都是Global对象的方法。除此之外,Global对象上还有另外一些方法。

  • URL编码方法

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

    let uri = "http://www.baidu.com/illegal value.js#start";
    console.log(encodeURI(uri));
    //http://www.baidu.com/illegal%20value.js#start
    //encodeURI()不会编码属于URL组件的特殊字符,比如冒号、斜杠、问号、井号
    
    console.log(encodeURIComponent(uri));
    //http%3A%2F%2Fwww.baidu.com%2Fillegal%20value.js%23start
    //encodeURIComponent()会编码它发现的所有非标准字符
    
    console.log(decodeURI(encodeURI(uri)));
    //http://www.baidu.com/illegal value.js#start
    console.log(decodeURIComponent(encodeURIComponent(uri)));
    //http://www.baidu.com/illegal value.js#start
    
  • eval()方法

    这个方法就是一个完整的ECMAScript解释器,它接收一个参数,即一个要执行的ECMAScript字符串。当解释器发现eval()调用时,会将参数解释为实际的ECMAScript语句。然后将其插入到该位置。

    let msg = "hello world";
    eval("console.log(msg)");// "hello world"
    
    eval("function sayHi(){ console.log('hi');}");
    sayHi();//"hi",可以在eval()内部定义一个函数或变量,然后在外部代码中使用
    
    eval("let msg = 'hello world';");
    console.log(msg);//ReferenceError: msg is not defined
    
    eval("var msg = 'hello world';");
    console.log(msg);//"hello world"
    
  • Global对象属性

    undefined、NaN、Infinity等特殊值都是Global对象的属性,所有原生引用类型构造函数,比如Object和function,也都是Global对象的属性。

    //Global对象属性
    undefined;
    NaN;
    Infinity;
    Object;
    Array;
    Function;
    Boolean;
    String;
    Number;
    Date;
    RegExp;
    Symbol;
    Error;
    EvalError;
    RangeError;
    ReferenceError;
    SyntaxError;
    TypeError;
    URIError;
    
  • window 对象

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

    var color = "red";
    function sayColor(){
        console.log(window.color);
    }
    window.sayColor();//"red"
    
    //获取全局变量
    let global = function(){
        return this;
    }();//直接调用,当一个函数在没有明确指定this值的情况下执行时,this值等于global对象
    

5.4.2 Math

Math.max(3,52,81,3);

let value = [1,2,9,6];
Math.max(...val);

Math.ceil(23.8);
Math.floor();
Math.round(12.4);//12,只接收一个参数
Math.fround();//返回数值最接近的单精度(32位)浮点值表示

Math,random();//返回一个0-1范围内的随机数,包含0但不包含1
posted @ 2021-09-01 16:38  unuliha  阅读(74)  评论(0)    收藏  举报