JavaScript语言基础(二)原始值与引用值

504 阅读18分钟

原始值与引用值

原始值——最简单的数据;

引用值——由多个值构成的对象。 JS中的对象是引用值。

1. 存储位置

  • 原始值大小固定,保存在栈内存上。
  • 引用值是对象,存储在堆内存上。

2. 访问方式

  • 保存原始值的变量是按值(by value)访问的,
  • 引用值是保存在内存中的对象,JavaScript不允许直接访问内存位置,在操作对象时,实际上操作的是对该对象的引用,而非实际的对象本身。因此,保存引用值的变量是按引用(by reference)访问的。

动态属性

对于引用值,可以随时添加、修改和删除其属性和方法

原始值不能有属性,尽管尝试给原始值添加属性不会报错。

1.引用值
let person = new Object();
person.name = "Leo";
console.log(person.name);	//"Leo"

2.原始值
let name = "Mary";
name.age = 20;
console.log(name.age);	//undefined

若使用的是new关键字,JavaScript会创建一个Object类型的实例。

let name2 = new String("Lili");
name2.age = 18;
console.log(name2.age);	//18
console.log(typeof name2);	//object

复制值

  1. 原始值 num1和num2中存储的值都是5,但两个变量是可独立使用,互不干扰,num2中的值是num1中的值的副本
let num1 = 5;
let num2 = num1;
console.log(num1+"   "+num2);//5  5
num1+=1;
num2+=2;
console.log(num1+"..."+num2);//6...7
  1. 引用值 这里复制的值实际上是一个指针,指向存储在堆内存中的对象,两个变量实际上指向同一个对象
let obj1 = new Object();
let obj2 = obj1;
obj1.name = "Mary";
console.log(obj2.name);	//"Mary"

传递参数

ECMAScript中所有函数的参数都是按值传递的,而且是局部变量

当obj在函数内部被重写时,它变成了一个指向本地对象的指针。而那个本地对象在函数执行结束时就被销毁了。

function setName(obj){
    obj.name = "Jack";
    obj = new Object();
    obj.name = "Anny";
}

let person = new Object();
setName(person);
console.log(person.name);	//Jack

确定类型

instanceof 操作符【引用值

如果变量是给定引用类型的实例,则 instanceof 操作符返回true。

所有引用值都是Object的实例,因此通过 instanceof 操作符,检测任何引用值和Object构造函数都会返回true。

console.log(person instanceof Object);	//变量person时Object吗?  true

如果用 instanceof 检测原始值,则始终返回false,因为原始值不是对象。原始值应使用前面提及的typeof操作符。

执行上下文与作用域

执行上下文

执行上下文分为全局、函数和块级上下文。

变量或函数的上下文决定了它们可以访问哪些数据,以及它们的行为。

每个上下文都有一个关联的对象,存放着上下文定义的所有变量和函数。

  • 上下文在其所有代码都执行完毕后会被销毁。(全局上下文在应用程序退出前才会被销毁,比如关闭网页或退出浏览器)。
  • 上下文中的代码在执行时,会创建变量对象的一个作用域链【决定了各级上下文中的代码在访问变量和函数时的顺序】,代码正在执行的上下文的变量对象始终位于作用域链的最前端全局上下文的变量对象始终是作用域链的最后一个变量对象
  • 上下文之间的连接是线性的、有序的。每个上下文都可以到上一级上下文去搜索变量和函数,但任何上下文都不能到下一级上下文中去搜索

全局上下文

为最外层的上下文。若在浏览器中,全局上下文就是window对象,所有通过var定义的全局变量和函数都会称为window对象的属性和方法。

使用let和const的顶级声明不会定义在全局上下文中,但在作用域解析上效果是一样的。

函数上下文

每个函数调用都有自己的上下文,ECMAScript程序的执行流就是通过上下文栈进行控制的

作用域链增强

代码执行到以下任意一种情况时,会在作用域链前端临时添加一个上下文,在代码执行后会被删除。

  1. try/catch语句的catch块
  2. with语句 with语句,会向作用域链前端添加指定的对象;

catch语句,则会创建一个新的变量,包含要抛出错误对象的声明。

垃圾回收

JavaScript通过自动内存管理实现内存分配和闲置资源回收。

基本思路:确定哪个变量不会再用,然后释放它占用的内存。 即垃圾回收程序每隔一定时间就会自动运行,具有周期性。

离开作用域的值会被自动标记为可回收,然后在垃圾回收期间被删除。

垃圾回收策略:

1. 标记清理【常用】

即先给当前不使用的值加上标记,再回来回收它们的内存。

当变量进出上下文的时候,会被加上标记。

2. 引用计数

对每个值都记录它被引用的次数。

垃圾回收程序下次运行时就会释放引用书为0的值的内存。

在代码中存在循环引用时会出现问题。

内存管理

解除引用【适合全局变量和全局对象的属性】

如果数据不再必要,那么把它设置为null,从而释放其引用。

适合全局变量和全局对象的属性。局部变量在超出作用域后会被自动解除引用。

解除对一个值的引用并不会自动导致相关内存被回收。

解除相关引用的关键在于确保相关的值已经不在上下文里了,因此它在下次垃圾回收时会被回收。

解除变量的引用不仅可以消除循环引用,而且对垃圾回收也有帮助。

基本引用类型

引用值(或者对象)是某个特定引用类型的实例。函数也是一种引用类型。

引用类型是把数据和功能组织到一起的结构,有时被称为对象定义,描述了自己的对象应有的属性和方法。

Date

parse()

接收一个表示日期的字符串参数,将字符串转换为表示该日期的毫秒数。 字符串格式如下: 图片.png

UTC()

接收参数是年、零起点月数(0为1月,1为2月.......)、日(1~31日)、时(0~23)、分、秒和毫秒。年和月是必需的。

now()

返回表示方法执行时日期和时间的毫秒数。

继承的方法

  • toLocaleString()返回与浏览器运行的本地环境一致的日期和时间,但不包含时区信息.
  • toString()通常返回带时区信息的日期和时间,时间时以24小时制(0~23)表示的。
  • valueOf()根本就不返回字符串,返回的是日期的毫秒表示,操作符(如<和>)可以直接使用它返回的值。

图片.png

图片.png

RegExp

RegExp对象表示正则表达式,通常用于检索文本中是否包含指定的字符串。

let expression = /pattern/flags;

  • pattern-字符串形式,用于规定正则表达式的匹配规则或填入其他正则表达式。
  • flags-匹配模式,为可选参数。 匹配模式如下:

图片.png

所有元字符在模式中使用反斜杠来转义,元字符包括:( [ { \ ^ $ | ) ] } ? * + .

匹配第一个“[bc]at”,忽略大小写

let pattern2 = /[bc]at/i

//字面量
// 匹配第一个"bat"或"cat",忽略大小写
let pattern1 = /[bc]at/i;

//构造函数
// 跟pattern1一样,只不过是用构造函数创建的
let pattern2 = new RegExp("[bc]at","i");

注意:构造函数中的两个参数是字符串。

实例方法

1. exec()【主要】

只接收一个参数,即需进行匹配的字符串。属性:index-匹配模式的起始下标,input-查询的字符串。

2. test()

用于测试特定的数值序列,常用于验证用户输入。

3. toLocaleString()和toString()

获取正则表达式字面量的形式。

let str = 'abcabce';
let pattern = /(.)b(.)/g;

let result = pattern.exec(str);
console.log(result.input);      //abcabce   查询的字符串
console.log(result.index)       //0         匹配模式的起始下标
console.log(result[0]);         //abc  匹配整个模式的字符串
console.log(result[1]);         //a    与str中的第一个捕获组匹配的字符串
console.log(result[2]);         //c    与str中的第二个捕获组匹配的字符串

注意:正则表达式的valueOf()方法返回正则表达式的本身。

RegExp构造函数的属性

image.png

let text = 'this is a sentence';
let pattern = new RegExp("(.)s","g");

if(pattern.test(text)){
    console.log(RegExp.input);          // this is a sentence
    console.log(RegExp.lastMatch);      // is  最后匹配的文本
    console.log(RegExp.lastParen);      // i   最后匹配的捕获组
    console.log(RegExp.leftContext);    // th
    console.log(RegExp.rightContext);   // is a sentence    
}

获取捕获组的匹配项最多为9个,RegExp.$1 ~ RegExp.$9 来访问匹配项。

简写方式:RegExp[ " $_ " ]

注意:RegExp构造函数的所有属性都没有任何Web标准出处,因此不要在生产环境中使用它们。

函数是对象【JS中】

JS中比较独特的一点是,函数实际上是Function 类型的实例,即函数也是对象,所以函数也有方法。

原始值包装类型

为了方便操作原始值,ECMAScript提供3种特殊的引用类型:Boolean、Number和String,

这些类型具有其他引用类型一样的特点,也具有与各自原始类型对应的特殊行为。每当用到某个原始值的方法或属性时,后台都会创建一个相应原始包装类型的对象。

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

let s1 = "some text";
// 上面代码相当于 let s1 = new String("some text"); 创建一个String类型的实例
s1.color = "red";
console.log(s1.color);     // undefined

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

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

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

共同特点

  1. 每种包装类型都映射到同名的原始类型;
  2. 在以读模式访问原始值时,后台会实例化一个原始值包装对象,通过这个对象可以操作数据;
  3. 涉及原始值的语句只要一执行完毕,包装对象就会立即销毁。

Boolean【用得较少】

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

原始值和引用值(Boolean 对象)的区别:

1)typeof 操作符对原始值为“boolean”,对引用值返回“object”。

2)Boolean 对象是 Boolean 类型的实例,instanceof操作符时返回true,对原始值返回false。

Number

是对应数值的引用类型。 并不建议直接实例化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(16));    // "a"
toFixed( x ) 方法【可表示有0~20 个小数位的数值】

返回包含只当小数点位数的数值字符串。 参数 x 表示返回的数值字符串要包含 x 位小数。 若数值本身的小数>参数 x ,则四舍五入到最接近的有效位。

let num = 10.005;
console.log(num.toFixed(2));    // "10.01"
toExponential( y )【可表示有1~21 个小数位的数值】

返回以科学计数法表示的数值字符串。 参数 y 表示结果中小数的位数。

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

会根据情况返回最合理的输出结果,可能是固定长度,也可能是科学计数法的形式。 参数 z 表示结果中数字的总位数(不包含指数)。

let num = 99;
console.log(num.toPrecision(1));    // "1e+2" 即100,因为 99 不能用1位数字来精确表示,只能舍入为100.
console.log(num.toPrecision(3));    // "99.0"
isInteger()

用于辨别一个数值是否保存为整数,但有时小数位的0,可能会被误认为数值是一个浮点值。

isSafeInteger()

用于鉴别整数是否在这个安全整数的范围内。

安全整数,范围从-2^53+1到2^53-1。 超过这个范围的数值,即使尝试保存为整数,二进制值可能会表示为一个完全不同的数值。

String

是对应字符串的引用类型。 每个String 对象都有一个 length 属性,表示字符串中字符的数量。注意,即使字符串中包含双字节字符(而不是单字节的 ASCII字符 ),也仍然会按单字符来计数。

JavaScript 字符

由16位码元组成,每16位码元对应一个字符,即字符串的length属性表示字符串包含多少16位码元。 JavaScript 字符串使用两种 Unicode 编码混合的策略: UCS-2 和 UTF-16。

charAt( x ) 返回给定索引位置 x 的字符。

charCodeAt()查看指定码元的字符编码,返回指定索引位置的码元值,索引以整数指定。

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

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

为正确解析既包含单码元字符又包含代理对字符的字符串,可使用codePointAt()来代替charCodeAt(),codePointAt()接收16位码元的索引并返回该索引位置上的码点,码点是Unicode中一个字符的完整标识。码点可能是16位/32位。 formCharCode()也有对应的formCodePoint(),接收任意数量的码点,返回对应字符拼接起来的字符串。

normalize()

对字符串应用规范化形式,使用时需要传入表示哪种形式的字符串:"NFD"、"NFC"、"NFKD"或"NFKC"。

字符串操作方法
1. concat()

将一个或多个字符串拼接称一个新的字符串。对于拼接多个字符串来说,使用加号操作符(+)更方便。

2. 提取子字符串的方法

slice()、substr()和substring(), 都接收一或两个参数,第一个参数表示子字符串开始的位置,第二个参数表示子字符串结束的位置。

对slice()和substring()而言,第二个参数是提取结束的位置(即该位置之前的字符都会被提取出来);

对substr()而言,第二个参数表示返回的子字符串数量。 任何情况下,省略第二个参数则默认提取到字符串末尾。

let info = "hello cln1003";
console.log(info.slice(3,7));       // "lo cl"
console.log(info.substr(3,7));      // "lo cl"
console.log(info.substring(3,7));   // "lo cln1"

当某个参数是负值时, slice()将所有负值参数都当成字符串长度加上负参数值; 而substr()将第一个负参数当成字符串长度加上该值,将第二个负参数转换为0;substring()将所有负参数值都转换为0.

let info = "hello cln1003";
console.log(info.slice(3,-4));         // "lo cln" slice(3,913+(-4)】)
console.log(info.substr(3,-4));        // "" (empty string) 因为此时是substr(30)。
console.log(info.substring(3,-4));     // "hel"  因为此时substring(3,0)等价于substring(0,3),将较小的参数作为起点,较大的参数作为终点。
字符串位置方法
indexOf()

indexOf()从字符串开头开始查找子字符串。

lastIndexOf()

而lastIndexOf()方法从字符串末尾开始查找子字符串。

都可以接收可选的第二个参数,表示开始搜索的位置。若没找到则返回-1。

字符串包含方法

ES6新增3个用于判断字符串中是否包含另一个字符串的方法,返回一个表示是否包含的布尔值。

startsWith()

startsWith()检查开始于索引0的匹配项。

endsWith()

endsWith()检查开始于索引(string.length - substring.length)的匹配项。 第二个可选的参数,表示应该当作字符串末尾的位置。

includes()。

includes()检查整个字符串。 startsWith()和includes()方法的第二个可选的参数,表示开始搜索的位置。

let msg = "foobarbaz";
console.log(msg.startsWith("foo"));    // true
console.log(msg.startsWith("foo", 1)); // false

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

console.log(msg.endsWith("bar"));    // false
console.log(msg.endsWith("bar", 6)); // true
trim()方法

会创建字符串的一个副本,删除前、后所有空格符,再返回结果。返回的是字符串的副本,原始字符串并不受影响。 trimLeft()和trimRight()方法分别用于从字符串开始和末尾清理空格符。

repeat()方法

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

padStart()和padEnd()方法

复制字符串,若小于指定长度,则在相应一边填充字符,直至满足长度条件。 第一个参数是长度,第二个参数是可选的填充字符串,默认为空格。

可选的第二个参数并不限于一个字符,若提供了多个字符的字符串,则会将其进行截断来匹配指定长度。若长度小于或等于字符串长度,则会返回原始字符串。

let string = "cln";
console.log(string.padStart(8, "cute"));   // "cuteccln"
console.log(stirng.padStart(2));              // "cln"

console.log(string.padEnd(8, "cute"));   // "clncutec"
console.log(stirng.padEnd(2));              // "cln"
字符串大小写转换

toLowerCase()、toLocaleLowerCase()、toUpperCase()和toLocaleUpperCase()。 toLocaleLowerCase()和toLocaleUpperCase()是基于特定地区实现最好使用地区特定的转换方法。

字符串模式匹配方法
match()

本质上是跟RegExp对象的exec()方法相同。接收一个参数,可以是正则表达式字符串或者RegExp对象。

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

search()

接受的唯一的参数与match()方法一样,始终从字符串开头向后匹配模式,返回模式第一个匹配的位置索引,若没找到则返回-1。

split()

根据传入的分隔符将字符串拆分成数组。作为分隔符的参数可以是字符串或RegExp对象,可选的第二个参数是数组大小,确保返回的数组不会超过指定大小。

let colors = "red,blue,green,yellow";
let color1 = colors.split(","); 	// ["red","blue","green","yellow"]
let color2 = colors.split(",", 2);		 // ["red", "blue"]
let color3 = colors.split(/[^,]+/); 	// ["", ",", ",", ",", "" ]  

第三个调用,使用正则表达式可以得到一个包含逗号的数组,返回的数组前后包含两个空字符串,因为正则表达式指定的分隔符出现在了字符串开头(“red”)和末尾(“yellow”)。

localeCompare()

比较两个字符串,返回的值分为3种:

(1)若按照字母表顺序,字符串应该排在字符串参数前头,则返回负值(通常是-1);

(2)若字符串与字符串参数相等,则返回0;

(3)若按照字母表顺序,字符串应该排在字符串参数后头,则返回正值(通常是1);

let str = "yellow";
console.log(str.localeCompare("brick")); 	// 1
console.log(str.localeCompare("yellow")); 	// 0
console.log(str.localeCompare("zoo")); 		// -1
子字符串替换方法

简化子字符串替换操作,ES提供replace()方法,接收两个参数,第一个参数可以是RegExp对象或一个字符串(此字符串不会转换为正则表达式),第二个参数可以是字符串或函数。 若参数1是字符串,那么只会替换第一个子字符串;若想替换所有子字符串,参数1必须为正则表达式且带全局标记。

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

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

单例内置对象

内置对象的定义是 “任何由ECMAScript实现提供、与宿主环境无关,并且在ECMAScript程序开始执行时就存在的对象。” 前面我们已经接触了大部分内置对象,如Object、Array和String。

Global

在全局作用域中定义的变量和函数都会变成Global 对象的属性。 前面介绍的isNaN()、isFinite()、parseInt()和parseFloat(),实际上都是Global 对象的方法。

1.URL编码方法

encodeURI()和encodeURIComponent()用于编码统一资源表示符(URI),以便传给浏览器。有效的URI不能包含某些字符,如空格,可以使用URI编码方法来让浏览器能理解它们。

encodeURI()

用于对整个URI进行编码,如 "www.baidu.com/login.js" , 不会编码属于URI组件的特殊字符,如冒号、斜杠、问号、井号。

encodeURIComponent()

用于编码URI种单独的组件,如前面URL中的"login.js"。会编码它发现的所有非标准字符。使用频率更高。

let uri = "http://www.baidu.com/user login.js#start";

// "http://www.baidu.com/user%20login.js#start"
console.log(encodeURI(uri));

// "http%3A%2F%2Fwww.baidu.com%2Fuser%20login.js%23start"
console.log(encodeURIComponent(uri));

相对应的解码方法为decodeURI()和decodeURIComponent(),只能对相应编码过的字符解码。

2. eval()方法【使用时必须极为慎重】

是一个完整的ECMASript解释器,接收一个参数,即一个要执行的ECMASript(JavaScript)字符串。

eval("console.log('hi')");
等价于
console.log("hi");

可以在eval()内部定义一个函数或变量,然后在外部代码中引用。

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

在严格模式下,在eval()内部创建的变量和函数无法被外部访问,赋值给eval也会导致错误。

3. Global对象属性

前面提及的undefined、NAN和Infinity等特殊值都是Global对象的属性。此外,所有原生引用类型构造函数,如Object和Function,也是Global对象的属性。

4. window 对象

浏览器将window对象实现为Global对象的代理,因此,所有全局作用域中声明的变量和函数都变成了window的属性。

调用一个简单返回this的函数是在任何执行上下文中获取Global对象的通用方式。

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

Math

1. Math对象属性

image.png

2. min()和max()方法

用于确定一组数值中的最大值和最小值,可以避免使用额外的循环和if语句来确定最大/小值。

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

返回一个0-1范围内的随机数,其中包含0但不包含1。

若为了加密而需要生成随机数,建议使用window.crypto.getRandomValues()。