JavaScript高级程序设计(第三版): 学习笔记, 查漏补缺

1.JavaScript简史

1.2.1 ECMAScript

*不完全兼容的实现

3.基本概念

3.6.9 switch语句

switch 语句中使用任何数据类型(在很多其他语言中只能使用数值),无论是字符串,还是对象都没有问题。其次,每个 case 的值不一定是常量,可以是变量,甚至是表达式。

switch 语句在比较值时使用的是全等(===)操作符,因此不会发生类型转换(例如,字符串"10" 不等于数值10)。

3.7.1 理解参数

ECMAScript函数的参数与大多数其他语言中函数的参数有所不同。ECMAScript函数不介意传递进来多少个参数,也不在乎传进来参数是什么数据类型。JS 的函数可以接受任意多的参数, 不管你定义的是几个。

实际上,在函数体内可以通过 arguments 对象来访问这个参数数组,从而获取传递给函数的每一个参数。

function howManyArgs() {
    var str = "";
    for (var i = 0; i < arguments.length; i++) {
        str += arguments[i] + " ";
    }
    console.log("参数总数:" + arguments.length + " 数据:" + str);
}
howManyArgs("b", 2);        // 参数总数:2 数据:b 2 
howManyArgs("c", 5, "d");   // 参数总数:3 数据:c 5 d 

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

4.2.2 没有块级作用域

for(var i=0; i < 10; i++){
    doSomething(i);
}
alert(i);   //10

而在 CJava 中这样可是会报错的

5.引用类型

5.2 Array数组

数组的length属性很有特点——它不是只读的。因此,通过设置这个属性,可以从数组的末尾移除项或向数组中添加新项。

var colors=["red","blue","green"]; //创建一个包含3个字符串的数组
colors.length=2;
alert(colors[2]);                  //undefined

利用length属性也可以方便地在数组末尾添加新项

var colors=["red","blue","green"];  //创建一个包含3个字符串的数组
colors[colors.length]="black";      //(在位置3)添加一种颜色
colors[colors.length]="brown";      //(在位置4)再添加一种颜色

5.2.3 栈方法

  • push() - 数组末尾添加项,并返回修改后数组的长度
  • pop() - 数组移除最后一项, 并返回移除项

5.2.4 队列方法

  • push() - 数组末尾添加项,并返回修改后数组的长度
  • shift() - 数组移除第一项, 并返回移除项

5.2.5 重排序方法

  • reverse() - 反转数组项的顺序
  • sort() - 按升序排列数组项

默认情况下, 按 toString() 的字符串进行比较

var values = [0, 1, 5, 10, 15];
values.sort();
alert(values);  // 0,1,10,15,5

因此 sort() 方法可以接收一个比较函数作为参数,以便我们指定哪个值位于哪个值的前面

function compare(value1, value2) {
    if (value1 < value2) {
        return -1;
    } else if (value1 > value2) {
        return 1;
    }
    return 0;
}
var values = [0, 1, 5, 10, 15];
values.sort(compare);
alert(values);

对于数值类型或者其 valueOf() 方法会返回数值类型的对象类型,可以使用一个更简单的比较函数。这个函数只要用第二个值减第一个值即可。

function compare(value1, value2) {
    return value2 - value1;
}

5.2.6 操作方法

concat() 方法可以基于当前数组中的所有项创建一个新数组. 具体来说, 这个方法会先创建当前数组的一个副本, 然后将接受到的参数添加到这个副本的末尾, 最后返回新构建的数组.

var colors = ["red", "green", "blue"];
var colors2 = colors.concat("yellow", ["black", "brown"]);
alert(colors);      //red,green,blue
alert(colors2);     //red,green,blue,yellow,black,brown

slice() 方法能够基于当前数组中的一或多个项创建一个新数组.

var colors = ["red", "green", "blue", "yellow", "purple"];
var colors2 = colors.slice(1);
var colors3 = colors.slice(1,4);

alert(colors2);     //green,blue,yellow,purple
alert(colors3);     //green,blue,yellow

alert(colors.slice(-2, -1));    //yellow

splice() 方法主要用途是向数组的中部插入项

语法: arrayObj.splice(start, deleteCount, [item1[, item2[, . . . [,itemN]]]])

从一个数组中移除一个或多个元素,如果必要,在所移除元素的位置上插入新元素,返回所移除的元素

var colors = ["red", "green", "blue"];
var removed = colors.splice(0, 1);                  // 删除第一项
alert(colors);      //green,blue
alert(removed);     //red

removed = colors.splice(1, 0, "yellow", "orange");  // 从位置1开始插入两项
alert(colors);      //green,yellow,orange,blue
alert(removed);     //返回的是一个空数组

removed = colors.splice(1, 1, "red", "purple");  // 插入两项, 删除一项
alert(colors);      //green,red,purple,orange,blue
alert(removed);     //yellow

5.2.7 位置方法

ECMAScript 5 为数组添加了两个位置方法: indexOf()lastIndexOf(); 返回要查找项在数组中的位置, 比较时使用全等操作符(===). 支持浏览器包括 IE9+, Firefox 2+, Safari 3+, Opera 9.5+ 和 Chrome

var numbers=[1,2,3,4,5,4,3,2,1];
alert(numbers.indexOf(4));      //3
alert(numbers.lastIndexOf(4));  //5

alert(numbers.indexOf(4,4));    //5
alert(numbers.lastIndexOf(4,4));//3

var person={name:"Nicholas"};
var people=[{name:"Nicholas"}];

var morePeople=[person];

alert(people.indexOf(person));      //-1
alert(morePeople.indexOf(person));  //0

5.2.8 迭代方法

ECMAScript5为数组定义了5个迭代方法。

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

以上方法都不会修改数组中的包含的值

var numbers = [1,2,3,4,5,4,3,2,1];
var everyResult = numbers.every(function(item,index,array){
    return (item>2);
});
alert(everyResult); //false

var someResult = numbers.some(function(item,index,array){
    return (item>2);
});
alert(someResult);  //true

var filterResult = numbers.filter(function(item,index,array){
    return (item>2);
});
alert(filterResult);//3,4,5,4,3

var mapResult = numbers.map(function(item,index,array){
    return item*2;
});
alert(mapResult);   //2,4,6,8,10,8,6,4,2

5.2.9 缩小方法

ECMAScript5 还新增了两个缩小数组的方法:reduce()reduceRight(); 前者是从第一到最后对数组遍历, 后者相反

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

alert(sum);  //15

支持这两个缩小函数的浏览器有IE9+、Firefox3+、Safari4+、Opera10.5和Chrome。

5.4 RegExp类型

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

var expression = /pattern/flags;

  • pattern : 可以是任何简单或复杂的正则表达式
  • flags : 一个或多个标志位

flags:

  • g - 全局(global) 模式,即模式将被应用于所有字符串,而非在发现第一个匹配项时立即停止
  • i - 不区分大小写(case-insensitive) 模式
  • m - 多行(multiline) 模式

pattern 模式中使用的 元字符 需要转移, 包括:([{\^$|)?*+.]}

上面的是简化的字面量形式的, 如果要使用 RegExp 构造的写法: (要注意\的转义, 推荐简写方式)

var expression = new RegExp(pattern, flags);

5.4.1 RegExp示例属性

  • global - 布尔值, 表示是否设置了 g 标志
  • ignoreCase - 布尔值, 表示是否设置了 i 标志
  • lastIndex - 整数, 表示开始搜索下一个匹配项的字符位置, 从0算起
  • multiline - 布尔值, 表示是否设置了 m 标志
  • source - 正则表达式的字符串表示, 按照字面量形式而非传入构造函数中的字符串
// 等同于 new RegExp("\\[bc\\]at", "i");
var pattern = /\[bc\]at/i;
console.log( pattern.global  );      //false
console.log( pattern.ignoreCase  );  //true
console.log( pattern.multiline  );   //false
console.log( pattern.lastIndex  );   //0
console.log( pattern.source  );      //"\[bc\]at"

5.4.2 RegExp实例方法

exec() - 专门为捕获组而设计的, 实例包含两个额外属性: index 和 input

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

var 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() 方法而言,即使在模式中设置了全局标志(g),它每次也只会返回一个匹配项。在不设置全局标志的情况下,在同一个字符串上多次调用 exec() 将始终返回第一个匹配项的信息。而在设置全局标志的情况下,每次调用 exec() 则都会在字符串中继续查找新匹配项

var text = "cat, bat, sat, fat";
var pattern1 = /.at/;

var matches = pattern1.exec(text);
console.log(matches.index);         //0
console.log(matches[0]);            //cat
console.log(pattern1.lastIndex);    //0

var matches = pattern1.exec(text);
console.log(matches.index);         //0
console.log(matches[0]);            //cat
console.log(pattern1.lastIndex);    //0


var pattern2 = /.at/g;

var matches = pattern2.exec(text);
console.log(matches.index);         //0
console.log(matches[0]);            //cat
console.log(pattern2.lastIndex);    //3

var matches = pattern2.exec(text);
console.log(matches.index);         //5
console.log(matches[0]);            //bat
console.log(pattern2.lastIndex);    //8

test() - 在模式与字符串参数匹配时返回 true, 否则返回 false; 通常放到 if 语句里

var text = "123-45-6789";
var pattern = /(\d{3})-(\d{2})-(\d{4})/;

if (pattern.test(text)) {
    console.log("pattern was matched.");
    console.log(RegExp.$1); // "123"
    console.log(RegExp.$2); // "45"
    console.log(RegExp.$3); // "6789"
}

注意, 如果 test()false 时, 不能使用 RegExp.$1 取值 (全局的, 结果为最后保存的捕获组内容, 最多到 RegExp.$9)!!!

5.5 Function类型

说起来ECMAScript中什么最有意思,我想那莫过于函数了——而有意思的根源,则在于函数实际上是对象。每个函数都是Function类型的实例,而且都与其他引用类型一样具有属性和方法。由于函数是对象,因此函数名实际上也是一个指向函数对象的指针,不会与某个函数绑定

// 函数声明方式
function sum(num1, num2) {
    return num1 + num2;
}

// 变量方式定义, 注意最后有分号!
var sum = function(num1, num2) {
    return num1 + num2;
};

5.5.1 没有重载(深入理解)

将函数名想象为指针,也有助于理解为什么ECMAScript中没有函数重载的概念。

function addSomeNumber(num) {
    return num + 100;
}

function addSomeNumber(num) {
    return num + 200;
}

var result = addSomeNumber(100);
console.log(result);    // 300

5.5.4 函数内部属性

在函数内部, 有两个特殊的对象: argumentsthis.

arguments 为类数组对象, 包含传入函数中的所有参数; 此对象还有一个叫 callee 的属性, 该属性是一个指针, 执行拥有这个 arguments 对象的函数

// 递归的阶层函数
function factorial(num) {
    if (num <= 1) {
        return 1;
    } else {
        return num * factorial(num - 1);
    }
}

// 解耦合的实现 arguments.callee
function factorial(num) {
    if (num <= 1) {
        return 1;
    } else {
        return num * arguments.callee(num - 1);
    }
}

第一种方式可能的问题

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

console.log(trueFactorial(5));  // 120
console.log(factorial(5));      // 0

this 函数内部的另一个特殊对象, 其行为与 Java 和 C# 中的 this 大致类似. 换句话说, this 引用的是函数据以执行的环境对象 —— 或者也可以说是 this 值 (当在网页的全局作用域中调用函数时, this 对象引用的就是 window)

window.color = "red";
var o = { color: "blue"  };

function sayColor() {
    console.log(this.color);
}

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

在调用函数之前,this 的值并不确定,因此 this 可能会在代码执行过程中引用不同的对象

5.5.5 函数属性和方法

每个函数都包含两个属性:lengthprototype

  • length - 表示函数希望接收的命名参数的个数
  • prototype - 对于 ECMAScript 中的引用类型而言, prototype 是保存它们所有实例方法的真正所在. 在创建自定义引用类型以及实现继承时, prototype 属性的作用是极为重要的

每个函数都包含两个非继承而来的方法:apply()call(). 这两个方法的用途都是在特定的作用域中调用函数, 实际上等于设置函数体内this对象的值.

  • apply() - 方法接收两个参数:
    • 运行函数的作用域
    • 参数数组, 可以是Array示例, 也可以是 arguments 对象
  • call() - 参数的个数按函数定义的来
    • 运行函数的作用域
    • 其他参数则是函数定义定义好的挨个传
function sum(num1, num2) {
    return num1 + num2;
}

// apply 的两种传参
function sum1(num1, num2) {
    return sum.apply(this, arguments);
}
function sum2(num1, num2) {
    return sum.apply(this, [num1, num2]);
}
// call 则具体传参
function sum3(num1, num2) {
    return sum.call(this, num1, num2);
}

console.log( sum1(10, 10)  );    // 20
console.log( sum2(10, 10)  );    // 20
console.log( sum3(10, 10)  );    // 20

两者的作用都相同, 区别就是参数的使用方式不同

事实上,传递参数并非 apply()call() 真正的用武之地;它们真正强大的地方是能够扩充函数赖以运行的作用域

window.color = "red";
var o = { color: "blue"  };

function sayColor() {
    console.log( this.color  );
}

sayColor();             // red
sayColor.call(this);    // red
sayColor.call(window);  // red
sayColor.call(o);       // blue

ECMAScript5 还定义了一个方法:bind()。这个方法会创建一个函数的实例,其this值会被绑定到传给bind()函数的值

window.color = "red";
var o = { color: "blue"  };

function sayColor() {
    console.log( this.color  );
}

var objectSayColor = sayColor.bind(o);
objectSayColor();   // blue

支持bind()方法的浏览器有IE9+、Firefox4+、Safari5.1+、Opera12+和Chrome



blog comments powered by Disqus

Published

02 July 2019

Tags