计算机 / 读书笔记 · 2022年8月13日 0

Professional JavaScript for Web Developers, 3rd Edition, Part 3

第4章 变量、作用域和内存问题

1.基本类型和引用类型的值

Undefined,Null,Boolean,Number和String是基本类型,他们是按值访问的,可以操作保存在变量中的实际的值。

基本类型是简单的数据段,引用类型指那些可能由多个值构成的对象。

引用类型可以修改其属性和方法,基本类型则不允许这种操作。

复制基本类型变量时,两个变量的值的内存空间是独立的。

复制引用类型时,两个变量其实保存的是同一个指针。

ECMAScript中所有函数的参数都是按值传递的。

instanceof操作符

instanceof可以用来判定对象具体是否属于某一个类。

2.执行环境及作用域

JavaScript没有块级作用域,只有”函数级”作用域。

所以与C/C++/Java不同,一些”临时变量”在相应的语句外部还是可以访问的。

JavaScript中的作用域是按全局、函数级、with语句来划分的。使用var声明的变量会被自动添加到最近的环境中,而如果没有var,则会直接添加到全局环境中。

3.垃圾收集

垃圾回收算法:标记清楚(mark-and-sweep),引用计数(reference counting);

性能问题:垃圾回收有性能overhead。

IE可以通过window.CollectGarbage()主动触发垃圾收集。

管理内存

对象不再使用时,可以通过赋值null解除其引用。

第5章 引用类型

ECMAScript中的引用类型虽然和类很类似,但是并不具备传统的面向对象语言所支持的类和接口等基本结构。

1.Object类型

创建对象的时候可以使用对象字面量表示法:

        var person = {
            name : "Nicholas",
            age : 29
        };

        alert(person.name);
        alert(person.age);

var person = new Object();

可以直接简写为:var person = {};

在通过对象字面量定义对象时,实际不会调用Object构造函数。

访问对象属性有两种方法,使用成员变量或者方括号表示法:

alert(person["name"]);
alert(person.name);

使用方括号表示法的好处是属性名可以是变量,以及可以访问属性名是不合法字符的属性。

2.Array类型

ECMAScript中的数组中的每一个元素可以存储不同类型的值。

构造函数:

var colors = new Array();

如果传递一个Number作为参数,则会指定创建的数组的长度:

var colors = new Array(20); // 创建的数组长度为20

如果传递的是其他类型的参数,则会将参数解析为数组的元素。

var colors = new Array(“red”, “blue”, “green”);

var names = new Array(“Greg”);

也可以通过字面量的方式创建数组

var colors = [“red”, “blue”, “green”];

var names = []; // 创建一个空数组;

var values = [1, 2, ]; //不能这样写,各个浏览器实现不一样,生成的数组长度可能是2,也可能是3

var values = [,,,,]; //不能这样写,原理跟上面一样;

跟Object一样,使用数组字面量时,不会调用Array的构造函数。

数组的length属性不是只读的,可以修改,因此可以通过改length属性移除或添加数组元素,新添加的数组元素为undefined。

对数组使用超出其长度的索引时(必须是赋值而不是读取),数组的长度会自动扩展到可以使用这个索引。

        var colors = ["red", "blue", "green"];    //creates an array with three strings
        colors[99] = "black";                     //add a color (position 99)
        alert(colors.length);  //100

数组最多可以包含4294967295个项。

1.检测数组

instanceof只能适用于只有一个全局执行环境的环境;

ECMAScript新增了Array.isArray()方法解决这个问题。

2.转换方法

数组的valueOf方法返回的还是数组本身,而toString方法则会返回数组中每个元素的字符串形式和逗号拼接成的字符串。

可以用join方法指定将数组转换为字符串时使用的分隔符。

调用数组的toLocaleString时,会分别调用每个元素的toLocaleString方法。

3.栈方法

即push和pop两种方法。push时可以一次性压入多个元素。

4.队列方法

shift方法可以移除数组第一个元素并返回该元素。

5.重排序方法

reverse方法可以将数组倒置。

sort方法可以对数组重新排序,但是默认是将每个元素先转换为字符串,再进行排序(很坑爹)。所以需要自己提供一个比较函数作为sort的参数。

6.操作方法

concat方法

基于当前数组创建一个新的副本,并在末尾添加新的元素。

slice方法

传入开始和结束位置(可选)索引(左闭右开),基于这个区间内的元素创建一个新的数组。

索引可以是负的,跟python的处理方式类似。

splice方法

splice(start_index,num_of_delete_items, insert_item1, insert_item2,….)

可以删除从start_index位置开始的num_of_delete_items个元素,然后再在此位置前插入insert_item1,insert_item2等元素。

所以在数组前面插入元素的使用方法为insert(0, 0, item1, item2, …)。

splice返回值为被删掉的元素构成的数组。

7.位置方法

indexOf方法从头开始查找元素,lastIndexOf方法则从末尾向前开始查找元素。

查找时使用全等比较方法。

找不到返回-1。

8.迭代方法

  • every方法,对数组中的每一项运行给定函数,如果该函数对每一项都返回true,则返回true;
  • filter方法,对数组中的每一项运行给定函数,返回该函数会返回true的项组成的数组;
  • forEach方法,对数组中的每一项运行给定函数,无返回值;
  • map方法,对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组;
  • some方法,对数组中的每一项运行给定函数,如果该函数对任一项返回true,则返回true。

9.归并方法

reduce和reduceRight方法都是迭代数组的所有项,然后构建一个最终的返回值。reduce从数组的第一项开始逐个遍历,reduceRight则从数组的最后一项开始,向前开始遍历。

        var values = [1,2,3,4,5];
        var sum = values.reduce(function(prev, cur, index, array){
            return prev + cur;
        });
        alert(sum);

reduce的函数接收4个参数,prev为前一项的值,cur为当前项的值,index为当前项的索引,array为数组对象。第一次迭代发生在数组的第二项上。

3.Date类型

Date类型使用自UTC 1970年1月1日午夜(零时)开始经过的毫秒数来保存日期。

构造函数:

a.无参构造函数

var now = new Date;

b.提供自UTC 1970年1月1日午夜(零时)经过的毫秒数来构造日期。这个毫秒数可以用Date.parse和Date.UTC方法来获得。Date.parse方法通过解析字符串来计算这个日期的毫秒数,Date.UTC则可以直接指定年、月、日等具体信息。

将地区设置为美国的浏览器,Date.parse方法通常都支持以下格式:

  • “月/日/年”,如6/13/2004;
  • “英文月名 日,年”,如January 12,2004
  • “英文星期几 英文月名 日 年 时:分:秒 时区”,如Tue May 25 2004 00:00:00 GMT-0700
  • YYYY-MM-DDTHH:mm:ss.sssZ,如2004-05-25T00:00:00,只有支持ECMAScript5的实现支持这种格式;

如果Date.parse()方法传入的字符串不能表示日期,那么它会返回NaN。如果直接将表示日期的字符串传给Date方法,Date也会自动调用Date.parse方法解析这个日期。

Date.UTC()方法的输入是年份、基于0的月份,月中的哪一天(1到31),小时数(0到23),分钟,秒和毫秒数。可以只提供前面的若干个而省略后面的参数。

Date的构造方法也可以直接接受Date.UTC方法的参数来构建日期。Date.UTC()与Date.parse()方法区别在于前者基于本地时区而后者基于GMT来构建日期。

Date.now()方法返回调用此函数时的日期和时间的毫秒数。

其他方法:

  • toString():返回带有时区信息的字符串时间
  • toLocaleString():返回与浏览器设置当前时区适应的字符串时间
  • valueOf():返回日期的毫秒数,因此不同Date可以直接使用'<‘,’>’,’=’进行相互比较。

日期格式化方法:

  • toDateString()
  • toTimeString()
  • toLocaleDateString()
  • toLocalTimeString()
  • toUTCString()

日期/时间组件方法:

  • getTime(),返回毫秒数,与valueOf()返回值相同
  • setTime(),设置毫秒数表示的日期
  • setFullYear/getFullYear
  • setMonth/getMonth
  • setDate/getDate 一月中的天数(1到31)
  • setDay/getDay 星期几
  • setHours/getHours
  • setMinutes/getMinutes
  • setSeconds/getSeconds
  • setMilliseconds/getMilliseconds

4.RegExp类型

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

创建正则表达式

var expression = /pattern/flags;

其中的patter部分可以是任何简单或复杂的表达式,可以包含字符类、限定符、分组、向前查找以及反向引用。每个正则表达式可以带一个或多个flags,用以表明正则表达式的行为。正则表达式的匹配模式支持下列3个标志:

  • g:表示全局模式,即模式将被用于所有字符串,而不是在发现第一个匹配项时立即停止;
  • i:表示不区分大小写;
  • m:表示多行模式,即在到达一行末尾时还会继续查找下一行中是否存在与模式相匹配的项。

flags可以省略不填。

正则表达式中的模式中的所有元字符都要转义,元字符包括:

( [ { \ ^ $ | ) ? * + . ] }

正则表达式也可以通过构造函数RegExp(pattern, flags)构造,如:

var pattern2 = new RegExp("[bc]at", "i");

使用构造函数创建的正则表达式各自是单独的实例,而通过正则表达式字面量创建的正则表达式,如果pattern和flags相同,那么他们会共享一个实例。

var re = null, i;
for (i = 0; i < 10; i++) {
     re = /cat/g;
     re.test("catastrophe");
}

上面例子第二次调用时会失败,因为re始终指向的是第一次调用时构建的正则表达式实例,所以后面的test方法相当于同一个正则表达式多次调用test方法,在第一次匹配到cat后,第二次调用匹配不了了,再下一次test又会从头开始匹配。所以为了避免出现这种问题,最好是使用构造函数创建正则表达式实例。

ECMAScript 5则规定使用字面量和直接调用RegExp构造函数要有相同的效果,每次都创建新的RegExp实例。

RegExp实例属性

每个RegExp实例都有以下属性:

  • global,布尔值,表示是否设置了g标志;
  • ignoreCase,布尔值,表示是否设置了i标志;
  • lastIndex,整数,表示开始搜索下一个匹配项的字符位置,从0算起;
  • multiline,布尔值,表示是否设置了m标志;
  • source,正则表达式的字符串表示,按照字符串字面量形式返回;

RegExp实例方法

exec方法

exec方法专门为捕获组而设计,接受一个参数为要应用模式的字符串,如果成功匹配上了模式则返回一个包含第一个匹配项的信息的数组,否则返回null。返回的数组虽然是Array的实例,但是还包含有index和input两个额外的属性,index为匹配项在字符串中的位置,input为应用正则表达式的字符串。在数组中,第一项是与整个模式匹配的字符串,其他项是与模式中的捕获组匹配的字符串。

设置全局标志g时,每次执行exec方法时会接着寻找新的匹配项(从上次匹配项的末尾开始找),并且找到一个就返回。而不设置全局标志g时,返回的永远是字符串里匹配到的第一项。

对于/aa/g这个正则表达式,aaaaa这个字符串里只能找到两个匹配项,aaaaaa可以找到三个匹配项。没找到匹配项并检测到字符串末尾时会返回null,然后下次调用再从字符串头部开始。

捕获组就是正则表达式里圆括号包含的部分。

test方法

测试字符串中是否存在匹配正则表达式的项。设置全局g标志时,多次执行也是会接着上一次的位置继续测试的,到末尾结束后,下一次调用又从头开始。

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

RegExp构造函数属性

这些所谓的RegExp构造函数属性相当于C++中类的静态属性,不属于某一个实例,而是属于这个类的。

长属性名短属性名说明
input$_最近一次要匹配的字符串
lastMatch$&最近一次匹配项
lastParen$+最近一次匹配的捕获组
leftContext$`input字符串中lastMatch之前的文本
multiline$*布尔值,表示是否所有表达式都使用多行模式
rightContext$’input字符串中lastMatch之后的文本

此外还可以用RegExp.$1、RegExp.$2、…、RegExp.$9表示最近一次匹配操作中9个匹配的捕获组。

ECMAScript中正则表达式不支持的特性

  • 匹配字符串开始和结束的\A和\Z锚(但是支持用^和$来匹配字符串开始和结束)
  • 向后查找(lookbehind),但是支持向前查找(lookahead)
  • 并集和交集类
  • 原子组(atomic grouping)
  • Unicode支持(单个字符除外,如\uFFFF)
  • 命名的捕获组,但支持编号的捕获组
  • s(single,单行)和x(free-spacing,无间隔)匹配模式
  • 条件匹配
  • 正则表达式注释

关于正则表达式:[^0-9]表示非数字字符。

5.Function类型

每个函数都是Function对象的实例,

function sum(num1, num2) {
    return num1 + num2;
}

var sum = function(num1, num2) {
    return num1 + num2;
};

最后一种定义函数的方式是使用Function构造函数,Function构造函数可以接受任意个数的参数,并且最后一个参数被看作是函数体。

var sum = new Function("num1", "num2", "return num1 + num2");

函数名和普通对象变量名没什么区别,也可以让函数名指向其他函数。所以EMCAScript中没有重载的原因可以理解为当定义第二个同名函数时,实际上是让同一个函数名指向了另外一个函数对象。

函数声明与表达式

对于函数声明,解析器会通过函数声明提升(function declaration hoisting)将函数声明提升到源代码树顶端并添加到执行环境中。因此向下面的代码是可以正常运行的:

alert(sum(10, 10));
function sum(num1, num2) {
    return num1 + num2;
}

而下面的代码是不行的,因为这不是函数声明:

alert(sum(10, 10));
var sum = function(num1, num2) {
    return num1 + num2;
}

在执行到sum的初始化这个语句之前,sum是没有定义的。

函数内部属性

arguments属性

arguments是一个包含了函数参数的类数组对象。arguments还有一个名为callee的特殊属性,指向拥有arguments这个对象的函数。通过callee这个属性函数可以实现自引用,并且与函数名解耦。

function factorial(num) {
    if (num <= 1) {
        return 1;
    } else {
        return num * arguments.callee(num - 1);
    }
}

var trueFactorial = factorial;
factorial = function() { 
    return 0;
}

上面的代码在factorial指向其他函数后,trueFactorial函数仍然是一个阶乘函数。而如果定义factorial时使用的下面的代码,那么trueFactorial就不是一个阶乘函数了,因为它引用了factorial函数:

function factorial(num) {
    if (num <= 1) {
        return 1;
    } else {
        return num * factorial(num - 1);
    }
}

this属性

this引用的是函数执行的环境对象,

  • 当在网页的全局作用域中调用函数时,this对象引用的就是window;
  • 当函数作为对象的方法执行时,this引用的是这个对象;
window.color = "red";
var o = {color: "blue"};

function sayColor() {
    alert(this.color);
}

sayColor();    // "red"

o.sayColor = sayColor;
o.sayColor();  // "blue"

caller属性

ECMAScript 5中还定义了caller属性,表示了调用此函数的函数的引用。如果是在全局作用域中调用当前函数,它的值为null。

function outer() {
    inner();
}

function inner() {
    alert(inner.caller);  // 会直接显示函数的代码
}

outer();

严格模式下不能为函数的caller属性赋值,否则会导致错误。

函数属性和方法

  • length属性,表示函数希望接收的参数的个数;
  • prototype属性,类似于C++中的虚函数表,在ECMAScript 5中,prototype属性不可枚举,不能用for-in表达式发现;
  • apply方法,apply(this, arguments/[args0, args1, … ]),第一个参数this为函数指定this对象,第二个参数为arguments对象或者一个参数数组;
  • call方法,call(this,args0, args1, …),第一个参数this为函数指定this对象,后面的参数直接传递给函数;
  • bind方法,bind(this),创建一个this值被绑定到this参数的函数,跟C++中bind也很类似;toLocalString/toString/valueOf,始终返回函数的代码;

在严格模式下,未指定环境对象而调用函数,则this值不会转换为window,除非明确把函数添加到某个对象或者调用apply/call方法,否则this值将是undefined。

6.基本包装类型

ECMAScript会在读取基本类型Boolean、Number、String值的时候,自动创建相应的基本包装类型的对象,从而可以直接通过基本类型变量调用相应的节本包装类型的方法。基于基本类型自动创建的基本包装类型的生存周期只在于调用基本包装类型成员方法的那一行。

如果要显示地创建基本包装类型,则需要使用new表达式。通过new表达式创建的基本包装类型对象调用typeof会返回object,而且所有基本包装类型对象在转换为布尔类型时值都是true。

Object构造函数也会根据传入值的类型返回相应基本包装类型的实例,传入字符串,就好返回String的实例,传入布尔值就会返回Boolean的实例。

创建基本包装类型的实例必须使用new表达式,而直接调用同名的转型函数则会返回基本类型。

var value = "25";
var number = Number(value);    // 转型函数
alert(typeof number);          // "number"

var obj = new Number(value);   // 构造函数
alert(typeof obj);             // "object"

1.Boolean类型

因为基本包装类型都会被转换为true,所以Boolean类型容易造成歧义(Boolean基本包装类型里存的是false,但是转换为布尔值时返回true),最好不要使用这个基本包装类型。

2.Number类型

  • toString(base),按指定基数返回几进制数值的字符串形式;
  • toFixed(num),指定小数位数;
  • toExponential(num),科学计数法,并且指定小数位数;
  • toPrecision(num),自动选择小数还是科学计数法,并且指定数字位数;
        alert(typeof numberObject);   //object
        alert(typeof numberValue);    //number
        alert(numberObject instanceof Number);  //true
        alert(numberValue instanceof Number);   //false

3.String类型

  • length属性,表示字符串中包含多少个字符,即使字符串中包含双字节字符,每个字符也仍然算作一个字符;
  • char/charCodeAt,char返回字符,charCodeAt返回字符编码;也可以使用方括号的方式访问指定位置的字符;
  • concat(str1. str2, …),连接多个字符;
  • slice/substr/substring,返回子串,区别在于slice方法会将传入的负值与字符串的长度相加,substr方法会将负的第一个参数加上字符串的长度,而将负的第二个参数转换为0,substring则会把所有的负值参数转换为0;此外substring方法会将较小的数作为开始位置,较大的数作为结束位置;
  • indexOf/lastIndexOf;
  • trim,删除前置及后缀的所有空格;
  • toLowerCase/toLocaleLowerCase/toUpperCase/toLocaleUpperCase;

字符串的模式匹配方法

  • match(pattern),接受一个正则表达式,效果与RegExp的exec方法相同;
  • search(pattern),返回字符串中第一个匹配项的索引,如果没有找到匹配项,则返回-1;
  • replace(pattern/str, str/func),第一个参数为正则表达式或一个字符串(字符串不会被转换为正则表达式),将字符串中与第一个参数相匹配的子字符串替换为第二个字符串;如果第一个参数是字符串,那么只会替换匹配的第一个子字符串,如果想要进行全部替换,只能为第一个参数提供带全局(g)标志的正则表达式;

replace的第二个参数为字符串时,支持一些特殊的字符序列以引用正则表达式匹配的结果:

字符序列替换文本
$$$
$&匹配整个模式的子字符串。与RegExp.lastMatch的值相同
$’匹配的子字符串之前的子字符串。与RegExp.leftContext相同
$`匹配的子字符串之后的子字符串。与RegExp.rightContext相同
$n匹配第n个捕获组的子字符串,其中n等于0~9。$1是匹配第一个捕获组的子字符串。如果正则表达式中没有定义捕获组,则使用空字符串
$nn匹配第nn个捕获组的子字符串。与$n类似

replace的第二个参数也可以是一个函数,用以动态决定要将第一个参数的匹配项替换为什么样的字符串。在只有一个匹配项的情况下,这个函数会接收3个参数:模式的匹配项、模式匹配项在字符串中的位置和原始字符串。在正则表达式中定义了多个捕获组的情况下,传递给函数的参数依次是模式的匹配项、第一个捕获组的匹配项、第二个捕获组的匹配项…,但最后两个参数仍然是模式的匹配项在字符串中的位置和原始字符串。

  • split方法,split(regexp/str, length);基于指定的分隔符(正则表达式或者字符串)将字符串划分为多个子字符串并存放在数组中,第二个参数为可选,用以指定结果数组的大小;
  • localeCompare方法;
  • fromCharCode方法,一个静态方法,接收多个字符编码,然后将它们转换成一个字符串;
alert(String.fromCharCode(104, 101, 108, 108, 111)); // "hello"

HTML格式化方法

浏览器还提供了基于给定字符串快速创建HTML标签的方法

方法输出结果
anchor(name)
big
bold
fixed
fontcolor
fontsize
italics
link(url)
small
strike
sub
sup

7.单体内置对象

ECMA-262对内置对象的定义:由ECMAScript实现提供的、不依赖于宿主环境的对象,这些对象在ECMAScript程序执行之前就已经存在了。

Global对象

终极的兜底儿对象,不属于任何其他对象的属性和方法,最终都是global这个“对象”的属性和方法。

  • isNaN
  • isFinite
  • parseInt
  • parseFloat

1.URI编码方法

encodeURIencodeURIComponent方法可以对URI进行编码。encodeURI主要用于对整个URI编码,而encodeURIComponent主要用于对URI中的某一段进行编码,他们的区别在于encodeURI不会对本身属于URI的特殊字符进行编码,如冒号、正斜杠、问号和井字号;而encodeURIComponent则会对它发现的任何非标准字符进行编码。

encodeURI对除了空格之外的其他字符都原封不动,只有空格被替换为%20,而encodeURIComponent会替换所有的非字母数字字符。所以可以对整个URI使用encodeURI,但是只能对附加在URI后面的字符串使用encodeURIComponent。

对应的还有两个解码方法decodeURIdecodeURIComponent

2.eval方法

eval方法接受表示要执行代码的字符串并执行这段代码。严格模式下,在外部访问不到eval()中创建的任何变量或函数。

3.Global对象的属性

属性说明
undefined
NaN
Infinity
Object
Array
Function
Boolean
String
Number
Date
RegExp
Error
EvalError
RangeError
ReferenceError
SyntaxError
TypeError
URIError

4.window对象

web浏览器都是将Global这个”对象”当做window对象的一部分加以实现的。因此全局作用域中声明的所有变量和函数,就都成为了window对象的属性。

除了window对象,另外一种获取Global对象的方法是使用以下代码:

var global = function() {
    return this;
}

Math对象

1.Math对象的属性

属性说明
Math.E
Math.LN10
Math.LN2
Math.LOG2E
Math.LOG10E
Math.PI
Math.SQRT1_2
Math.SQRT2

2.min和max方法

min和max方法可以接收多个参数并求出最小或者最大值。

3.舍入方法

ceil/floor/round(四舍五入)

4.random方法

返回大于等于0小于1的一个随机数。

5.其他方法

方法说明
Math.abs
Math.exp
Math.log
Math.pow(num, power)
Math.sqrt
Math.acos
Math.asin
Math.atan
Math.atan2(y, x)
Math.cos
Math.sin
Math.tan