学习JavaScript高级程序设计第4-6章

第四章 · 变量、作用域与内存

1、原始值和引用值

原始值就是最简单的数据,引用值则是由多个值构成的对象。

原始值是按值访问的,存放在栈内存上;引用值是按引用访问的,存放在堆内存上。

在通过变量把一个原始值赋值到另一个变量时,原始值会被复制到新变量的位置。引用值复制的是一个指针,它指向存储在堆内存中的对象。操作完成之后,两个变量实际上指向同一个对象,因此一个对象上面的变化会在另一个对象上反映出来。

所有函数的参数都是按值传递的。ECMAScript中的函数的参数就是局部变量。

typeof操作符最适合用来判断一个变量是否为原始类型;instanceof操作符用来判断引用类型。

2、执行上下文与作用域

每个上下文都有一个关联的变量对象。

上下文中的代码在执行的时候,会创建变量对象的一个作用域链。这个作用域链决定了各级上下文中的代码在访问变量和函数时的顺序。代码正在执行的上下文的变量对象始终位于作用域链的最前端。如果上下文是函数,则其活动对象用作变量对象。代码执行时的标识符解析是通过沿作用域链逐级搜索标识符名称完成的。

子局部上下文可以访问父全局上下文的变量;

使用var的函数作用域声明:变量提升;

使用let的块级作用域声明:会提升,但由于“暂时性死区”的原因,实际不能在声明之前使用let变量。

使用const的变量声明:不能重新赋值(引用值的键不受限制,如果限制不能修改,可以使用Object.freeze());其他和let一样。

3、垃圾回收

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

垃圾回收策略:标记清理和引用计数。

标记清理:垃圾回收机制在运行的时候,会标记内存中存储的所有变量。然后,它会将所有在上下文中的变量,以及被在上下文中的变量引用的变量的标记去掉。在此之后在被标记的变量就是待删除的了,原因是任何在上下文中的变量都访问不了它们。然后垃圾回收程序做一次内存清理,销毁带标记的值并回收它们的内存。

引用计数:对每个值记录引用的次数,引用一次+1,引用的变量被其他值覆盖-1,释放引用次数为0的值的内存。

COM对象使用引用计数实现垃圾回收。会出现循环引用问题。

触发垃圾回收机制:IE中,window.CollectGarbage();Opera7及更高版本中,window.opera.collect()。

内存管理:通过const和let声明提升性能;隐藏类和删除操作;内存泄漏;静态分配与对象池。

解除变量的引用不仅可以消除循环引用,而且对垃圾回收也有帮助。为促进内存回收,全局对象、全局对象的属性和循环引用都应该在不需要时解除引用。

第五章 · 基本引用类型

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

1、Date()

let now = new Date()

两个辅助方法:Date.parse()和Date.UTC()

Date.parse()方法接受一个表示日期的字符串参数,尝试将这个字符串转换为表示该日期的毫秒数。

let someDate = new Date(Date.parse("May 23, 2019"));
// 等价于
let someDate = new Date("May 23, 2019");

Date.UTC()方法也返回日期的毫秒表示,但使用的是跟Date.parse()不同的信息来生成这个值。

// GTM时间2000年1月1日零点
let y2k = new Date(Date.UTC(2000, 0));

// GTM时间2005年5月5日下午5点55分55秒
let allFives = new Date(Date.UTC(2005, 4, 5, 17, 55, 55));

// 等价于

// GTM时间2000年1月1日零点
let y2k = new Date(2000, 0);

// GTM时间2005年5月5日下午5点55分55秒
let allFives = new Date(2005, 4, 5, 17, 55, 55);

Date.now():返回表示方法执行时日期和时间的毫秒数。

valueOf()、getTime()

2、RegExp

正则表达式:g(全局模式)、i(不区分大小写)、m(多行模式)、y(粘附模式)、u(Unicode模式)、s(dotAll模式,表示元字符,匹配任何字符)

实例属性:global(布尔值,表示是否设置了g标记)、ignoreCase(布尔值,表示是否设置了g标记)、unicode(布尔值,表示是否设置了u标记)、sticky(布尔值,表示是否设置了y标记)、lastIndex(整数,表示在原字符串中下一次搜索的开始位置,始终从0开始)、multiline(布尔值,表示是否设置了m标记)、dotAll(布尔值,表示是否设置了s标记)、source(正则表达式的字面量字符串,不是传给构造函数的模式字符串,没有开头和结尾的斜杠)、flags(正则表达式的标记字符串,始终以字面量而非传入构造函数的字符串模式形式返回(没有前后斜杠))

RegExp实例方法:exec()(主要用于配合捕获组使用。这个方法只接收一个参数,即要应用模式的字符串。包含两个额外的属性: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.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(),也只会返回第一个匹配的信息。

test():接收一个字符串参数。这个方法适用于只想测试模式是否匹配,而不需要实际匹配内容的情况。

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

RegExp构造函数属性: ( i n p u t , 最 后 搜 索 的 字 符 串 ) 、 _(input,最后搜索的字符串)、 input&(lastMatch,最后匹配的文本)、 + ( l a s t P a r e n , 最 后 匹 配 的 捕 获 组 ) 、 +(lastParen,最后匹配的捕获组)、 +lastParen`(leftContext,input字符串中出现的lastMatch前面的文本)、$’(rightContext,input字符串中出现在lastMatch后面的文本)

Opera不支持简写属性名,IE不支持多行匹配。

RegExp 还有其他几个构造函数属性,可以存储最多 9 个捕获组的匹配项。这些属性通过 RegExp. $1~RegExp.$9 来访问,分别包含第 1~9 个捕获组的匹配项。RegExp 构造函数的所有属性都没有任何 Web 标准出处,因此不要在生产环境中使用它们。

3、原始值包装类型

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

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

3-1、Boolean

Boolean 的实例会重写 valueOf()方法,返回一个原始值 true 或 false。

toString()方法被调用时也会被覆盖,返回字符串"true"或"false"。

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

3-2、Number

toFixed()方法返回包含指定小数点位数的数值字符串。

toFixed()方法可以表示有 0~20 个小数位的数值。某些浏览器可能支持更大的范围,但这是通常被支持的范围。

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

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

ES6 新增了 Number.isInteger()方法,用于辨别一个数值是否保存为整数。有时候,小数位的 0可能会让人误以为数值是一个浮点值。为了鉴别整数是否在这个范围内,可以使用Number.isSafeInteger()方法。

3-3、String

字符串的 length 属性表示字符串包含多少 16 位码元。

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

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

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

首先是 concat(),用于将一个或多个字符串拼接成一个新字串。但更常用的方式是使用加号操作符(+)。

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

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

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

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

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

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

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

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

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

toLowerCase()、toLocaleLowerCase()、toUpperCase()和toLocaleUpperCase()。

match()、search()、replace()、split()、localeCompare()

4、单例内置对象

Global:ecnodeURI()方法用于对整个 URI 进行编码;而

encodeURIComponent()方法用于编码 URI 中单独的组件。

这两个方法的主要区别是,encodeURI()不会编码属于 URL 组件的特殊字符,比如冒号、斜杠、问号、井号,而 encodeURIComponent()会编码它发现的所有非标准字符。

eval():通过eval()定义的任何变量和函数都不会被提升,这是因为在解析代码的时候,它们是被包含在一个字符串中的。它们只是在eval()执行的时候才会被创建。在严格模式下,在eval()内部创建的变量和函数无法被外界访问。在使用eval()的时候必须极为慎重,特别是在解释用户输入的内容时。因为这个方法会对XSS利用暴露出很大的攻击面。恶意用户可能插入会导致你网站或应用崩溃的代码。

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

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

当一个函数在没有明确(通过成为某个对象的方法,或者通过call()/apply())指定this值得情况下执行时,this值等于Global对象。因此,调用一个简单返回this的函数是在任何执行上下文中获取Global对象的通用方式。

Math对象属性:Math.E、Math.LN10、Math.LN2、Math.LOG2E、Math.LOG10E、Math.PI、Math.SQRT1_2、Math.SQRT2

min()和max()方法;

舍入方法:Math.ceil、Math.floor()、Math.round()、Math.fround

random()方法

5、小结

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

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

JavaScript比较独特的一点是,函数实际上是Function类型的实例,也就是说函数就是对象。因为函数也是对象,所以函数也有方法,可以用于增强其能力。

由于原始值包装类型的存在,JavaScript中的原始值可以被当成对象来使用。有3种原始值包装类型:Boolean、Number和String。它们都具备如下特点。

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

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

第六章 · 集合引用类型

1、Object

适合存储和在应用程序间交换数据。显示地创建Object的实例有两种方式:使用new操作符和Object构造函数。另一种就是使用对象字面量表示法。属性一般通过点语法来存取,这也是面向对象语言的惯例,但也可以使用中括号来存取属性。

2、Array

创建数组的方法:new创建;也可以省略new;通过字面量创建。

静态方法:from()和of()。from()用于将类数组结构转换为数组实例,而of()用于将一组参数转换为数组实例。

通过修改length属性,可以从数组末尾删除或添加元素。

检测数组:instanceof,Array.isArray()

迭代器方法(检索数组内容的方法):keys()、values()、entries()。key()返回数组索引的迭代器,values()返回数组元素的迭代器,entries()返回索引/值对的迭代器。

复制和填充方法:copyWithin()、fill()

join():数组转字符串,参数为分隔符。

栈方法:push()、pop()。

队列方法:shift()/unshift()

排序方法:reverse()/sort()

操作方法:concat()/slice()/splice()

搜索和位置方法:严格相等(indexOf()/lastIndexOf()/includes())、断言函数(find()/findIndex())

迭代方法:every()/filter()/forEach()/map()/some()

归并方法:reduce()/reduceRight()。reduce()方法从数组第一项开始遍历到最后一项。而 reduceRight()从最后一项开始遍历至第一项。

3、定型数组

TypedArray,它所指的其实是一种特殊的包含数值类型的数组。

ArrayBuffer:实际上是一种视图。是所有定型数组及视图引用的基本单位。是一个普通的JavaScript构造函数,可用于在内存中分配特定数字的字节空间。一经创建就不能再调整大小。

ArrayBuffer在分配失败时会抛出错误。分配的内存不能超过Number.MAX_SAFE_INTEGER(2^53 - 1)字节。声明ArrayBuffer则会将所有二进制位初始化为0。通过声明ArrayBuffer分配的堆内存可以被当成垃圾回收,不用手动释放。

允许读写的ArrayBuffer的视图是DataView。这个视图是专为文件I/O和网络I/O设计,其API支持对缓冲数据的高度控制,但相比于其他类型的视图性能也差一些。DdataView对缓冲内容没有任何预设,也不能迭代。必须再对已有的ArrayBuffer读取或写入时才能创建DataView实例。这个实例可以使用全部或部分 ArrayBuffer,且维护着对该缓冲实例的引用,以及视图在缓冲中开始的位置。

要通过DataView读取缓冲,还需要几个组件:首先是要读或写的字节偏移量。可以看成DataView中的某种地址;DataView应该使用ElementType来实现JavaScript的Number类型到缓冲内二进制格式的转换;最后是内存中值得字节序,默认为大端字节序。

4、Map

初始化之后,可以使用set()方法再添加键值对。另外,可以使用get()和has()进行查询,可以通过size属性获取映射中的键值对的数量,还可以使用delete()和clear()删除值。

与object类型的一个主要差异是,Map实例会维护键值对的插入顺序,因此可以根据插入顺序执行迭代操作。可以通过entries()方法取得这个迭代器。keys()和values()分别返回以插入顺序生成键和值的迭代器。键和值在迭代器遍历时可以修改,但映射内部的引用则无法修改。

5、WeakMap

弱映射:一种新的集合类型,为这门语言带来了增强的键值对存储机制。WeakMap中的weak描述的是JavaScript垃圾回收程序对待弱映射中键的方式。

set()添加键值对,可以使用get()和has()查询,delete()删除。

这些键不属于正式的引用,不会阻止垃圾回收。

不可迭代键:因为WeakMap的键值对任何时候都可能被销毁,所以没必要提供迭代其键值对的能力。也不可能在不知道对象引用的情况下从弱映射中取得值。即便代码可以访问WeakMap实例,也没办法看到其中的内容。

6、Set

如果想在创建的同时初始化实例,则可以给 Set 构造函数传入一个可迭代对象,其中需要包含插入到新集合实例中的元素。

初始化之后,可以使用 add()增加值,使用 has()查询,通过 size 取得元素数量,以及使用 delete()和 clear()删除元素。

Set 会维护值插入时的顺序,因此支持按顺序迭代。

某些 Set 操作是有关联性的,因此最好让实现的方法能支持处理任意多个集合实例。

Set 保留插入顺序,所有方法返回的集合必须保证顺序。

尽可能高效地使用内存。扩展操作符的语法很简洁,但尽可能避免集合和数组间的相互转换能够节省对象初始化成本。

不要修改已有的集合实例。union(a, b)或 a.union(b)应该返回包含结果的新集合实例。

7、WeakSet

ECMAScript6新增的弱集合是一种新的集合类型。其API是Set的子集。描述的是JavaScript垃圾回收程序对待“弱集合”中值得方式。

弱集合中的值只能是Object或者继承自Object的类型,尝试使用非对象设置值会抛出TypeError。如果想在初始化时填充弱集合,则构造函数可以接收一个可迭代对象,其中需要包含有效的值。可迭代对象中的每个值都会按照迭代顺序插入到新实例中。

初始化之后可以使用add()再添加新值,可以使用has()查询,还可以使用delete()删除。add()方法返回弱集合实例,因此可以把多个操作连缀起来,包括初始化声明。

const val1 = {id: 1},
      val2 = {id: 2},
      val3 = {id: 3};

cosnt ws = new WeakSet().add(val1);
ws.add(val2).add(val3);

alert(ws.has(val1)); // true
alert(ws.has(val2)); // true
alert(ws.has(val3));  // true

弱值:WeakSet中“weak”表示弱集合中的值不属于正式的引用,不会阻止垃圾回收。

add()方法初始化了一个新对象,并将它用作一个值。因为没有指向这个对象的其他引用,所以当这行代码执行完成后,这个对象就会被当做垃圾回收。然后,这个值在弱集合中消失了,使其成为一个空集合。

cosnt ws = new WeakSet();

const container = {val: {}};

ws.add(container.val);

function removeReference() {
    container.val = null;
}
// container对象维护着一个对弱集合值的引用,因此这个对象值不会成为垃圾回收的目标。不过。如果调用了removeReference(),就会摧毁值对象的最后一个引用,垃圾回收程序就可以把这个值清除掉。

不可迭代值。

弱集合在给对象打标签时还是有价值的。

8、迭代与扩展操作

默认迭代器:for-of、扩展操作符(浅复制时可以使用)、Array.of()、Array.from()

9、小结

JavaScript中的对象是引用值,可以通过几种内置引用类型创建特定类型的对象。

  • 引用类型与传统面向对象编程语言中的类相似,但实现不同。
  • Object类型是一个基础类型,所有引用类型都从它继承了基本的行为。
  • Array类型表示一组有序的值,并提供了操作和转换的能力。
  • 定型数组包含一套不同的引用类型,用于管理数值在内存中的类型。
  • Date类型提供了关于日期和时间的信息,包括当前日期和时间以及计算。
  • RegExp类型是ECMAScript支持的正则表达式的接口,提供了大多数基本正则表达式以及一些高级正则表达式的能力。

JavaScript比较独特的一点是,函数其实是Function类型的实例,这意味着函数也是对象。由于函数是对象,因此也就具有能够增强自身行为的方法。

因为原始值包装类型的存在,所以JavaScript中的原始值可以拥有类似对象的方法。有三种原始值包装类型:Boolean、Number和String。它们都具有如下特点:

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

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

ECMAScript 6 新增了一批引用类型:Map、WeakMap、Set 和 WeakSet。这些类型为组织应用程序数据和简化内存管理提供了新能力。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值