JavaScript高级程序设计(第三版)②

7 语句

if

if (i > 25) {
    alert(" Greater than 25.");
} else if (i < 0) {
    alert(" Less than 0.");
} else {
    alert(" Between 0 and 25, inclusive.");
}

do-while后测试循环,至少运一次

do {
    statement
} while (出口条件);
var i = 0;
do {
    i += 2;
} while (i < 10);
alert(i);

while前测试循环,可能一次不运行

var i = 0;
while (i < 10) {
    i += 2;
}

for

for (initialization; expression; post - loop - expression) {
    statement
}
var count = 10;
for (var i = 1; i < count; i++) {
    alert(i);
}

 注意表达式用;号结束,初始化表达式、控制表达式和循环后表达式都是可选的,将这些表达式全部省略,就会创建一个无限循环:

for (;;) {
    // 无限 循环 doSomething(); 
}

for-in
for-in语句是一种精准的迭代语句,可以用来枚举对象的属性,在ECMAScript对象的属性是没有顺序的。

for (var propName in window) {
    document.write(propName);
}

label

start: for (var i = 0; i < count; i++) {
    alert(i);
}

 主要作用:在嵌套循环中使用breakcontinue配合label可以精确地返回想要的位置:

var num = 0;
for (var i = 0; i < 10; i++) {
    for (var j = 0; j < 10; j++) {
        if (i == 5 && j == 5) {
            break;
        }
        num++;
    }
}
alert(num); //循环在i为5,j为5的时候跳出j循环,但会继续执行i循环,输出95

 添加Label之后:

var num = 0;
outPoint:
for (var i = 0; i < 10; i++) {
    for (var j = 0; j < 10; j++) {
        if (i == 5 && j == 5) {
            break outPoint;
        }
        num++;
    }
}
alert(num); //循环在i为5,j为5的时候跳出双循环,返回到outPoint层,执行下面的语句,输出55

breakcontinue

var num = 0;
for (var i = 1; i < 10; i++) {
    if (i % 5 == 0) {
        break;
    }
    num++;
}
alert(num); //4

 当i等于5,直接跳出循环,num加到了4。

var num = 0;
for (var i = 1; i < 10; i++) {
    if (i % 5 == 0) {
        continue;
    }
    num++;
}
alert(num); //8

 当i等于5,会跳出来再从循环头部重新以i等于5开始,再到10的时候再跳一次,此时i < 10已经不成立了,不进入循环,等到8。

with不建议使用

var qs = location.search.substring(1);
var hostName = location.hostname;
var url = location.href;
/* 使用with简化后 */
with(location) {
    var qs = search.substring(1);
    var hostName = hostname;
    var url = href;
}

switch

switch (i) {
    case 25:
        alert(" 25");
        break;
    case 35:
        alert(" 35");
        break;
    case 45:
        alert(" 45");
        break;
    default:
        alert(" Other");
}

 在语句里break;得加上,防止发生同时执行多个case语句。
 JS的switch语句能带任意数据类型,甚至是对象:

switch ("hello world") {
    case "hello" + " world":
        alert(" Greeting was found.");
        break;
    case "goodbye":
        alert(" Closing was found.");
        break;
    default:
        alert(" Unexpected message was found.");
}

8 函数

写法

function sayHi(name, message) {
    alert(" Hello " + name + "," + message);
}
sayHi(" Nicholas", "how are you today?");

return,实际上,没有指定返回值的函数返回的是一个特殊的undefined值:

function sayHi(name, message) {
    return;
    alert(" Hello " + name + "," + message); //永远不会调用 
}
function sum(num1, num2) {
    return num1 + num2;
}
var result = sum(5, 10);
function diff(num1, num2) {
    if (num1 < num2) {
        return num2 - num1;
    } else {
        return num1 - num2;
    }
}

传递参数有无,数据类型任意都可,原因是ECMAScript中的参数在内部是用一个数组来表示的;还通过arguments可以查看传进的参数:

function doAdd() {
    if (arguments.length == 1) {
        alert(arguments[0] + 10);
    } else if (arguments.length == 2) {
        alert(arguments[0] + arguments[1]);
    }
}
doAdd(10); //20 
doAdd(30, 20); //50

 还可以同时使用传参与argumentsarguments[0]对应num1

function doAdd(num1, num2) {
    if (arguments.length == 1) {
        alert(num1 + 10);
    } else if (arguments.length == 2) {
        alert(arguments[0] + num2);
    }
}

 给传参赋值:

function doAdd(num1, num2) {
    arguments[1] = 10;
    alert(arguments[0] + num2);
}

 还有些要注意的事情,例如,如果只给doAdd()函数传递了一个参数,则num2中就会保存undefined值。这种赋值方法在严格模式下不起作用。

没有重载:

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

function addSomeNumber(num) {
    return num + 200;
}
var result = addSomeNumber(100); //300 FunctionExample10.

 不过可以用检查arguments的方法,模仿重载,让一个函数有两个定义。

9 变量、作用域与内存问题

变量 ★
 ECMAScript变量可能包含两种不同数据类型的值:基本类型值引用类型值基本类型是按值访问的,因为可以操作保存在变量中的实际的值;引用类型的值是保存在内存中的对象。与其他语言不同,JavaScript不允许直接访问内存中的位置,也就是说不能直接操作对象的内存空间。在操作对象时,实际上是在操作对象的引用而不是实际的对象。为此,引用类型的值是按引用访问的。
引用类型与基本类型的差异:
 1> 动态赋值:

var person = new Object();
person.name = "Nicholas";
alert(person.name); //"Nicholas"
var name = "Nicholas";
name.age = 27;
alert(name.age); //undefined

 2> 复制变量值:

var num1 = 5;
var num2 = num1;
num1 = 4;
alert(num2); //5
var obj1 = new Object();
var obj2 = obj1;
obj1.name = "Nicholas";
alert(obj2.name); //"Nicholas"

 3> 作为传参时,函数的参数都是按值传递的:

function addTen(num) {
    num += 10;
    return num;
}
var count = 20;
var result = addTen(count);
alert(count); //20,不影响作为传参的count
alert(result); //30
function setName(obj) {
    obj.name = "Nicholas";
}
var person = new Object();
setName(person);
alert(person.name); //"Nicholas"
function setName(obj) {
    obj.name = "Nicholas";
    obj = new Object();
    obj.name = "Greg";
    /* 在函数里重写obj时,obj会指向一个新的Object,而且这个变量引用的是一个局部变量,会在函数执行完后被销毁 */
}
var person = new Object();
setName(person);
alert(person.name); //"Nicholas"

 3> 检测变量类型:
typeof操作符是确定一个变量是字符串、数值、布尔值,还是undefined的最佳工具。
instanceof操作符用于检查对象是何类型,返回布尔值:

alert(person instanceof Object); // 变量 person 是 Object 吗? 
alert(colors instanceof Array); // 变量 colors 是 Array 吗? 
alert(pattern instanceof RegExp); // 变量 pattern 是 RegExp 吗?

执行环境与作用域 ★:
 1> ES6之前只有全局作用域与函数作用域;
 2> 每个执行环境都有一个与之关联的变量对象(variableobject),环境中定义的所有变量和函数都保存在这个对象中;
 3> 每个函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境。ECMAScript程序中的执行流正是由这个方便的机制控制着;
 4> 当代码在一个环境中执行时,会创建变量对象的一个作用域链,作用域链的前端始终是当前代码执行的变量对象,链子上后一个的变量对象是包含当前变量对象的变量对象,以此类推,自至到全局执行环境;

var color = "blue";

function changeColor() {
    var anotherColor = "red";

    function swapColors() {
        var tempColor = anotherColor;
        anotherColor = color;
        color = tempColor;
        /* 这里可以访问color、anotherColor和tempColor */
    }
    /* 这里可以访问color和anotherColor,但不能访问tempColor */
    swapColors();
}
/* 这里只能访问color */
changeColor();

 5> 有些语句可以在作用域的前端临时增加一个变量对象,比如with语句和try-catch语句的catch块,前者把填入的变量对象增加到作用域的前端,后者把错误对象添加到前端。
 6> 在ES6之前,JavaScript没有块级作用域;在其他类型的语言中,连由花括号封闭的代码块都有自己的作用域。
 7> 使用var声明的变量会自动被添加到最接近的环境中。在函数内部,最接近的环境就是函数的局部环境;在with语句中,最接近的环境是函数环境。如果初始化变量时没有使用var声明,该变量会自动被添加到全局环境。

垃圾收集
 JS具有自动垃圾回收机制,通过以下几种方法去判断是否可以回收:
 1> 标记清除(mark-and-sweap),最常用的,当代码执行时,会将已存储在内存中的所有变量加上标记,然后每每进入一个环境都会去掉环境中的变量和被环境中的变量引用的变量的标记,然后其余的变量都被视为准备删除的变量;
 2> 引用计数(reference counting),不太常见,当声明一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是1。如果同一个值又被赋给另一个变量,则该值的引用次数加1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数减1。当这个值的引用次数变成0时,会在垃圾回收机制触发时被销毁;
 3> 垃圾回收机制触发靠的是判断是否达到动态修正的一个浏览器内存分配量的临界值,也可以手动触发,比如IE的window.CollectGarbage()

 ④ 手动内存管理,太骚气了...
 1> 把值设置为null来释放其引用:

function createPerson(name) {
    var localPerson = new Object();
    localPerson.name = name;
    return localPerson;
}
var globalPerson = createPerson(" Nicholas");
globalPerson = null; // 手动解除globalPerson的引用

10 引用类型

对象类型:
1> 创建Object实例的两种方法:


var person = new Object();
person.name = "Nicholas";
person.age = 29;
var person = {
    name: "Nicholas",
    age: 29
};
var person = {}; //与new Object()相同 
person.name = "Nicholas";
person.age = 29;

 2> 关于对象字面量语法,我们推荐只在考虑对象属性名的可读性时使用。
3> 使用对象字面量作为参数传递也是向函数传递大量可选参数的首选方法,如下:

function displayInfo(args) {
    var output = "";
    if (typeof args.name == "string") {
        output += "Name: " + args.name + "\n";
    }
    if (typeof args.age == "number") {
        output += "Age: " + args.age + "\n";
    }
    alert(output);
}
displayInfo({
    name: "Nicholas",
    age: 29
});
displayInfo({
    name: "Greg"
});

4> 访问对象属性的两种方法:

alert(person["name"]); //"Nicholas" 

 等价于:

alert(person.name); //"Nicholas"

 等价于:

var propertyName = "name";
alert(person[propertyName]); //"Nicholas"

 如果对象属性名带有空格,那只能用方括号表示法:

person.first name = "Nicholas";// 点表示法不能带空格,会保错
person["first name"] = "Nicholas";// 方括号表示法可以

② Array类型 ★

var colors = new Array(3); // 创建一个包含3项的数组var 
names = new Array(" Greg"); // 创建一个包含1项,即字符串"Greg"的数组

 1> Array类型也有数组字面量表示法:

var colors = ["red", "blue", "green"]; // 创建一个包含3个字符串的数组 
var names = []; // 创建一个空数组
var values = [1, 2,]; // 不要这样!这样会创建一个包含2或3项的数组
var options = [,,,,,]; // 不要这样!这样会创建一个包含5或6项的数组

 2> 与对象一样,在使用数组字面量表示法时,也不会调用Array构造函数,会节约性能;
 3> 读取和修改数组

var colors = ["red", "blue", "green"]; // 定义一个符串数组 
alert(colors[0]); // 显示第一项 
colors[2] = "black"; // 修改第三项 
colors[3] = "brown"; // 新增第四项
var colors = ["red", "blue", "green"]; // 创建一个包含3个字符串的数组 
var names = []; // 创建一个空数组
alert(colors.length); // 3 
alert(names.length); // 0

 4> 通过length属性可以去访问数组末尾项,可以新增、修改和删除:

var colors = ["red", "blue", "green"]; // 创建一个包含3个字符串的数组 
colors.length = 2; // 把数组长度变为2,变相删除末尾项
alert(colors[2]); //undefined
var colors = ["red", "blue", "green"]; // 创建一个包含3个字符串的数组
colors[colors.length] = "black"; //(在位置3)添加一种颜色
colors[colors.length] = "brown"; //(在位置4)再添加一种颜色

 5> 检测数组

if (Array.isArray(value)) {
    // 判断是否为数组
    // 对数组执行某些操作
}

 6> 如前所述,所有对象都具有toLocaleString()toString()valueOf()方法,对数组应用这些方法时,toString()valueOf()得到的值是相同的字符串,实际上,为了创建这个字符串会调用数组每一项的toString()方法,alert输出数组也会在后台调用toString()

var colors = ["red", "blue", "yellow"];
alert(colors.toString()); // red,blue,yellow
alert(colors.valueOf()); // red,blue,yellow
alert(colors); // red,blue,yellow

toLocaleString()有时候会返回和用toString()valueOf()方法不一样的值,因为toLocaleString()是对数组每一项调用toLocaleString()方法。我们在数组里重写toLocaleString()toString()测试下:

var person1 = {
    toLocaleString: function() {
        return "Nikolaos";
    },
    toString: function() {
        return "Nicholas";
    }
};
var person2 = {
    toLocaleString: function() {
        return "Grigorios";
    },
    toString: function() {
        return "Greg";
    }
};
var people = [person1, person2];
alert(people); //Nicholas, Greg 
alert(people.toString()); //Nicholas, Greg 
alert(people.toLocaleString()); //Nikolaos, Grigorios

 关于join()方法,用于重现toString(),代入的参数是什么分隔符,就可以得到什么样的字符串:

var colors = ["red", "green", "blue"];
alert(colors.join(",")); // red,green,blue 
alert(colors.join("||")); // red||green||blue

 7> 数组还有种栈方法可以让数组的行为表现得像栈(后进先出)一样,方法有两:push()(接收任意数量的参数,把它们逐一添加到数组末尾,再返回修改后的数组长度)和pop()(移除数组最后一项,数组长度减一,)。

var colors = new Array(); // 创建一个数组
var count = colors.push(" red", "green"); // 推入两项 
alert(count); // 2 
count = colors.push(" black"); // 推入另 一项 
alert(count); // 3 
var item = colors.pop(); // 取得最后一项 
alert(item); // "black" 
alert(colors.length); // 2

 8> 数组还还有种队列方法可以让数组的行为表现得像队列(先进先出)一样,方法有三:push()(因为队列和栈都是从列表的末端添加项)、shift()(移除数组的第一项,返回该项,同时长度减一),unshift()(往数组的头部添加任意项,返回数组长度):

var colors = new Array(); // 创建一个数组 
var count = colors.unshift(" red", "green"); // 推入两项 
alert(count); // 2
count = colors.unshift(" black"); // 推入另一项 
alert(count); //3 
var item = colors.pop(); // 取得最后一项 
alert(item); //"green" 
alert(colors.length); // 2

 注意上面输出green,表明unshift()推入项的顺序

 9> reverse()sort()reverse()用来反转数组项的顺序,sort()更加灵活,可以按升序排列数组,最小的在前面,最大的排在后面,sort()方法会对数组每个项调用toString(),再比较它的字符串,即便它是数值

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

 由于比较的是字符串,所以, 不一定返回我们想当然的结果,就像上面的例子,"5"在"15"和"10"前面,为了得到准确的结果,可以往sort()里加一个比较函数,比较函数接受两个参数,返回结果有3种,如果第一个参数应该在第二个参数后面,返回正数;如果在前面,返回负数;两数相等,返回0。示例如下:

function compare(value1, value2) {
    if (value1 < value2) {
        return -1;
    } else if (value1 > value2) {
        return 1;
    } else {
        return 0;
    }
}

代入sort()后:

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

 10> 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

 11> slice(),用原有数组的项组成新的数组,并返回新数组,传入的两个参数为起始位置与终止位置:

var colors = ["red", "green", "blue", "yellow", "purple"];
var colors2 = colors.slice(1); //不传入终止位置,默认为数组的长度-1
var colors3 = colors.slice(1, 4);
alert(colors2); //green, blue, yellow, purple 
alert(colors3); //green, blue, yellow

12> splice(),超常用,始终返回一个包含被删除项的数组 ★

var colors = ["red", "green", "yellow", "blue", "black"];
colors.splice(0, 1);
alert(colors); // green,yellow,blue,black
colors.splice(0, 2);
alert(colors); // blue,black
colors.splice(0, 0, "red", "green");
alert(colors); // red,green,blue,black
colors.splice(2, 0, "yellow");
alert(colors); // red,green,yellow,blue,black
var remove = colors.splice(1, 2, "green1", "yellow1");
alert(colors); // red,green1,yellow1,blue,black
alert(remove); // green,yellow

 13> indexOf()(从前面往后找)和lastIndexOf()(从后面找起)用于查找特定项在数组中的位置,后台比较用的是===

var colors = ["red", "green", "yellow", "red", "blue", "black"];
alert(colors.indexOf("red")); // 0
alert(colors.lastIndexOf("red")); // 3
alert(colors.lastIndexOf("white")); // -1


var ball = {
    color: "white"
}
var ball1 = {
    color: "black"
}
var balls = [{
    color: "white"
}];
alert(balls.indexOf(ball)); // -1
var balls1 = [ball1, ball];
alert(balls1.indexOf(ball)); // 1

14> 迭代方法:every() filter() forEach() map() some(),每个方法都接受两个参数,一是对数组每一项都运行的函数,二是该函数的作用域(会影响this的值,该参数可选用),另外作为参数的函数要接收这三个参数,数组项的值、该项在数组中的位置和数组对象本身:★
every() some(),返回布尔值:

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

filter(),返回符合条件项组成的数组;map(),返回被传入函数操作过后的每一项组成的数组:

var numbers = [1, 2, 3, 4, 5, 6];
alert(numbers.filter(function(item, index, array) {
    return (item > 3);
}));
alert(numbers.map(function(item, index, array) {
    return (item + 3);
}));

forEach(),没有返回值,只是对每一项运行传入参数:

var numbers = [1, 2, 3, 4, 5, 6];
var sum = 0;

function foo(item, index, array) {
    sum += item;
}
numbers.forEach(foo);
alert(sum); // 21

 15> reduce() reduceRight(),这两个方法都会迭代数组的所有项,然后构建一个最终返回的值;方法接受两个参数:传入的函数(接收4个参数,前一个值、当前值、项的索引和数组对象,而且函数返回的值会作为下一项的第一个参数)和(可选的)作为缩小基础的初始值。
第一次迭代反生在数组的第二项,因此第一个参数是数组的第一项,第二个参数就是数组的第二项:

var numbers = [1, 2, 3, 4, 5, 6];

function bar(prev, cur, index, array) {
    return prev + cur;
}
alert(numbers.reduce(bar));// 21

reduceRight()只是换了个方向的reduce()方法,从数组后面开始迭代:

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

var now = new Date(); //不传入参数,默认返回当前时间

 1> Date.parse(),返回转换后的毫秒数:

var someDate = new Date(Date.parse("july 29, 2019"));
var someDate1 = new Date("july 29, 2019"); // 后台调用Date.parse,等同于上一行

 2> Date.UTC()

// GMT 时间 2000 年 1 月 1 日 午夜 零时 
var y2k = new Date(Date.UTC(2000, 0));
// GMT 时间 2005 年 5 月 5 日 下午 5: 55: 55 
var allFives = new Date(Date.UTC(2005, 4, 5, 17, 55, 55));

// 本地 时间 2000 年 1 月 1 日 午夜 零时 
var y2k = new Date(2000, 0); // 如果第一个是年份,后台调用Date.UTC
// 本地 时间 2005 年 5 月 5 日 下午 5: 55: 55 
var allFives = new Date(2005, 4, 5, 17, 55, 55);

 3> 用于分析代码运行时间:

//取得开始时间 
var start = Date.now();
//调用函数 
doSomething();
//取得停止时间 
var stop = Date.now(),
    result = stop– start;

 4> 与其他引用类型一样,Date类型也重写了toLocaleString()toString()valueOf()方法,还有一些把日期格式格式化成特定字符串方法,如toDateString() toTimeString() toLocalDateString() toLocalTimeString() toUTCStringtoDateString()toString()是一样的:

var now = new Date();
alert(now.toString()); // Mon Jul 29 2019 21:08:54 GMT+0800 (中国标准时间)
alert(now.toUTCString()); // Mon, 29 Jul 2019 13:08:54 GMT
alert(now.toLocaleDateString()); // 2019/7/29
alert(now.toTimeString()); // 21:08:54 GMT+0800 (中国标准时间)
alert(now.toLocaleTimeString()); // 下午9:08:54
alert(now.toDateString()); // Mon Jul 29 2019

 还有一些别的格式化方法,如下:

JavaScript高级程序设计(第三版)②_第1张图片

RegExp类型

var expression = / pattern / flags;  

 1> pattern部分是正则表达式,flags为标识,用以标明正则表达式的行为;构建方法也有两种,但其实都会创建一个RegExp实例;实例有着内置属性;元字符需要转义:

var pattern1 = /\[bc\] at/i;
alert(pattern1.global); //false 
alert(pattern1.ignoreCase); //true 
alert(pattern1.multiline); //false 
alert(pattern1.lastIndex); //0 
alert(pattern1.source); //"\[bc\] at" 

var pattern2 = new RegExp("\\[ bc\\] at", "i");
alert(pattern2.global); //false 
alert(pattern2.ignoreCase); //true 
alert(pattern2.multiline); //false 
alert(pattern2.lastIndex); //0 
alert(pattern2.source); //"\[bc\] at"

 2> exec()是RegExp实例常用的方法:

var text = "I am a rookie";
var pattern = /I( am( a rookie)?)?/gi; // ?表示匹配前面的子表达式零次或一次
var matches = pattern.exec(text);
alert(matches[0]); // I am a rookie
alert(matches[1]); //  am a rookie
alert(matches[2]); //  a rookie

 捕获组可以通过从左到右计算其开括号来编号 。例如,在表达式 (A)(B(C)) 中,存在四个这样的组:

JavaScript高级程序设计(第三版)②_第2张图片

 在g标识符下每次调用exec()都会返回字符串中的下一个匹配项,直至搜索到字符串末尾为止,lastIndex的值也会在每次调用exec()后都会增加:

var text = "cat, bat, sat, fat";
var pattern1 = /.at/;
var matches = pattern1.exec(text);
alert(matches.index); //0 
alert(matches[0]); //cat 
alert(pattern1.lastIndex); //0 

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

var pattern2 = /.at/g;
var matches = pattern2.exec(text);
alert(matches.index); //0 
alert(matches[0]); //cat 
alert(pattern2.lastIndex); //0 

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

 2> test()

var text = "000-00-0000";
var pattern = /\d{3}-\d{2}-\d{4}/;
if (pattern.test(text)) {
    alert(" The pattern was matched.");
}

 2> toString()toLocalString()

var pattern = new RegExp("\\[bc\\]at","gi");
alert(pattern.toString()); // /\[bc\]at/gi 
alert(pattern.toLocaleString()); // /\[bc\]at/gi

④ Function类型 ★
1> 每个函数都是Function类型的实例,函数名是指向实例函数指针,ECMAScript中没有函数重载的概念,定义方法如下:

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

 2> 函数声明会被解析器提升到源代码树的顶部,至于函数表达式var foo = new function(){...},则必须等到解析器执行到它所在的代码行,才会真正被解释执行:

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

 3> 函数作为函数的参数:

function callSomeFunction(someFunction, someArgument) {
    return someFunction(someArgument);
}

function add(item) {
    return item + 10;
}
alert(callSomeFunction(add, 10));
function createComparisonFunction(propertyName) {
    return function(object1, object2) {
        var value1 = object1[propertyName];
        var value2 = object2[propertyName];
        if (value1 < value2) {
            return -1;
        } else if (value1 > value2) {
            return 1;
        } else {
            return 0;
        }
    };
}

var data = [{
    name: "Zachary",
    age: 28
}, {
    name: "Nicholas",
    age: 29
}];
data.sort(createComparisonFunction(" name"));
alert(data[0].name); //Nicholas
data.sort(createComparisonFunction(" age"));
alert(data[0].name); //Zachary

4> 函数内部属性 ★
 在函数内部,有两个特殊的对象:
arguments(类数组对象,包含着传入函数中的所有参数),其中arguments.callee属性指向拥有这个argument的函数:

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

 为了消除与函数名factorial的耦合,改写后:

function factorial(num) {
    if (num <= 1) {
        return 1;
    } else {
        return num * arguments.callee(num - 1)
    }
}
var trueFactorial = factorial;
factorial = function() {
    return 0;
};
alert(trueFactorial(5)); //120
alert(factorial(5)); //0

arguments.callee.caller提供更好的解耦,它保存着调用当前函数的函数的引用:

function outer() {
    inner();
}

function inner() {
    alert(arguments.callee.caller);
}
outer();

this(引用的是函数据以执行的环境对象,当在网页的全局作用域中调用函数时,this对象引用的就是window):

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

function sayColor() {
    alert(this.color);
}
sayColor(); // red
o.sayColor = sayColor; // 添加sayColor方法给o
o.sayColor(); // blue

length,表示函数希望接收的参数个数:

function sayName(name) {
    alert(name);
}

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

function sayHi() {
    alert(" hi");
}
alert(sayName.length); //1 
alert(sum.length); //2
alert(sayHi.length); //0

prototype点击

5> 函数内的方法 ★
apply(),内置于函数,用于在特定的作用域调用函数;接受两个参数,一个是在其中运行函数的作用域,另一个是参数数组:

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

function callSum1(num1, num2) {
    return sum.apply(this, arguments); // 传入arguments对象 
}

function callSum2(num1, num2) {
    return sum.apply(this, [num1, num2]); // 传入数组 
}
alert(callSum1(10, 10)); //20
alert(callSum2(10, 10)); //20

call()apply()的作用一样,区别在于接收的参数不同,除了this值,其余的参数都得一个个列出来:

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

function callSum(num1, num2) {
    return sum.call(this, num1, num2);
}
alert(callSum(10, 10)); //20

call()apply()真正强大的地方是使对象不需要与方法有任何耦合关系,比如修改之前那个例子,不用再把sayColor()方法复制给o

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

function sayColor() {
    alert(this.color);
}
sayColor(); // red
sayColor.call(this); // red 
sayColor.call(window); // red 
sayColor.call(o); // blue

bind()这个方法会创建一个函数的实例,这个函数的this值会被绑定到所传入函数:

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

function sayColor() {
    alert(this.color);
}
var objectSayColor = sayColor.bind(o);
objectSayColor(); //blue

⑤ 相似的基本包装类型:Boolean、Number和String ★
 1> 实际上,每当读取一个基本类型值的时候,后台就会创建一个对应的基本包装类型的对象实例,从而让我们使用一些方法去操纵数据,而且每次使用完都会销毁这个实例:

var s1 = "some text";
var s2 = s1.substring(2);
var s1 = "some text";
s1.color = "red";// 运行结束后,实例对象被销毁
alert(s1.color); //undefined,因为这个是新建的实例,不带color属性

 等同于:

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

 2> Object构造函数也会像工厂方法一样,根据传入值的类型返回相应基本包装类型的实例:

var obj = new Object(" some text");
alert(obj instanceof String); //true

 2> 使用new调用基本包装类型的构造函数,与直接调用同名的转型函数是不一样的:

var value = "25";
var number = Number(value); //转型函数,把字符串转为数值,number保存的是数值
alert(typeof number); //"number" 
var obj = new Number(value); //构造函数,保存的是实例
alert(typeof obj); //"object"

 3> 这些类型都重写了valueOf()toString()toLocalString()方法。

 4> Boolean类型需要注意的是,Boolean类型实例与bool值的区别:

var falseObject = new Boolean(false);
var result = falseObject && true;
alert(result); //true,因为所有对象转化成bool值是true

var falseValue = false;
result = falseValue && true;
alert(result); //false

 5> Number类型

var numberObject = new Number(10);

var num = 10;
alert(num.toString()); //"10" 
alert(num.toString(2)); //"1010"
alert(num.toString(8)); //"12" 
alert(num.toString(10)); //"10" 
alert(num.toString(16)); //"a"

alert(num.toFixed(2)); //"10.00",toFixed会按照参数返回指定小数点位的数值
var num1 = 10.005;
alert(num1.toFixed(2)); //"10.01",还可以四舍五入

alert(num.toExponential(1)); //"1.0e+1",e表示法

// toPrecision方法返回参数指定位数的数组,还可以自动选择最合适的表达方式
var num3 = 99;
alert(num3.toPrecision(1)); //"1e+ 2",一位数表达99的表达法
alert(num3.toPrecision(2)); //"99" 
alert(num3.toPrecision(3)); //"99. 0"
var numberObject = new Number(10);
var numberValue = 10;
alert(typeof numberObject); //"object" 
alert(typeof numberValue); //"number" 
alert(numberObject instanceof Number); //true 
alert(numberValue instanceof Number); //false

 6> String类型

var stringValue = "hello world";
alert(stringValue.charAt(1)); //"e"
alert( stringValue. charCodeAt( 1)); //输出" 101",101是小写e的字符编码
alert(stringValue[1]); //"e"
var stringValue = "hello ";
var result = stringValue.concat(" world");
alert(result); //"hello world" 
alert(stringValue); //"hello"

 字符操作方法(返回新字符串),slice()substring()substr()

var stringValue = "hello world";
alert(stringValue.slice(3)); //"lo world" 
alert(stringValue.substring(3)); //"lo world" 
alert(stringValue.substr(3)); //"lo world"
alert(stringValue.slice(3,7)); //"lo w" ,第一个参数都是起始位置
alert(stringValue.substring(3,7)); //"lo w",slice()和substring()的第二个参数指定的是子字符串最后一个字符的位置 
alert(stringValue.substr(3,7)); //"lo worl",第二个参数指定的是返回的字符个数
var stringValue = "hello world";
alert(stringValue.slice(-3)); //"rld"
alert(stringValue.substring(-3)); //"hello world" 
alert(stringValue.substr(-3)); //"rld" 
alert(stringValue.slice(3,-4)); //"lo w" 
alert(stringValue.substring(3,-4)); //"hel" 
alert(stringValue.substr(3,-4)); //""( 空字符串)

 位置方法,indexOf()lastIndexOf()

var stringValue = "hello world";
alert(stringValue.indexOf("o")); //4 
alert(stringValue.lastIndexOf("o")); //7
alert(stringValue.indexOf("o",6)); //7 
alert(stringValue.lastIndexOf("o",6)); //4
var stringValue = "Lorem ipsum dolor sit amet, consectetur adipisicing elit";
var positions = new Array();
var pos = stringValue.indexOf("e");
while (pos > -1) {
    positions.push(pos);
    pos = stringValue.indexOf("e", pos + 1);
}
alert(positions); //"3, 24, 32, 35, 52"

trim()去除前面和后面空格:

var stringValue = "   hello world    ";
var trimmedStringValue = stringValue.trim();
alert(stringValue); //"   hello world    "
alert(trimmedStringValue); //"hello world"

toLowerCase()toLocaleLowerCase()toUpperCase()toLocaleUpperCase()用于转换大小写。

match()匹配方法,返回一个数组:

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

//与pattern.exec(text)相同 
var matches = text.match(pattern);
alert(matches.index); //0 
alert(matches[0]); //"cat" 
alert(pattern.lastIndex); //0,lastIndex:是RegExp的属性,一个可读/写的整数,如果匹配模式中带有g修饰符,这个属性存储在整个字符串中下一次检索的开始位置

search(),这个方法的唯一参数与match()方法的参数相同,返回字符串中第一个匹配项的索引:

var text = "cat, bat, sat, fat";
var pos = text.search(/at/);
alert(pos); //1

replace(),第二参数可以是一个函数或者一串字符串 ★:

var text = "cat, bat, sat, fat";
result = text.replace(/(.at)/g, "word ($1)");
alert(result); //word (cat), word (bat), word (sat), word (fat)
JavaScript高级程序设计(第三版)②_第3张图片
function htmlEscape(text) {
    return text.replace(/[<>"&]/g,
        function(match, pos, originalText) {
            switch (match) {
                case "<":
                    return "<";
                case ">":
                    return ">";
                case "&":
                    return "&";
                case "\"":
                    return """;
            }
        });
}
alert(htmlEscape("

Hello world!

")); //< p class=& quot; greeting& quot;& gt; Hello world!& lt;/ p& gt;

 最后一个匹配方法split()

var colorText = "red,blue,green,yellow";
var colors1 = colorText.split(","); //["red","blue","green","yellow"] 
var colors2 = colorText.split(",", 2); //["red","blue"]
var colors3 = colorText.split(/[^\,]+/); //["",",",",",",",""]

 剩下与String类型有关的几个方法:
localeCompare(),比较字符串,根据在字母表的顺序返回数字1(或正数)、-1(或负数)、0(相等)。
fromCharCode(),接收一到多个字符编码,转化成一个字符串。

Global对象,所有在全局作用域中定义的属性和函数,都是Global对象的属性;window对象在Web浏览器中,是对Global对象的一种实现,而且Global只是window对象的一部分。

⑥ Math对象
 1> 属性:

JavaScript高级程序设计(第三版)②_第4张图片

 2> 方法:
max() min()

var max = Math.max(3, 54, 32, 16);
alert(max); //54 
var min = Math.min(3, 54, 32, 16);
alert(min); //3

ceil() floor() round(),舍入方法:

alert(Math.ceil(25.9)); //26 
alert(Math.ceil(25.5)); //26 
alert(Math.ceil(25.1)); //26 
alert(Math.round(25.9)); //26

random()

值 = Math.floor(Math. random() * 可能值的总数 + 第一个可能的值)

 返回1到10的数值:

var num = Math.floor(Math.random() * 10 + 1);

 其他方法:


JavaScript高级程序设计(第三版)②_第5张图片

11 面向对象的JS

一次定义多个属性,defineProperties

var book = {};
Object.defineProperties(book, {
    _year: {
        value: 2004
    },
    edition: {
        value: 1
    },
    year: {
        get: function() {
            return this._ year;
        },
        set: function(newValue) {
            if (newValue > 2004) {
                this._ year = newValue;
                this.edition += newValue - 2004;
            }
        }
    }
});

下面是几种创建对象的方法与其的优缺点:
② 构造函数模式
 构造函数模式,构造函数应始终都应该以一个大写字母开头,而非构造函数则应该以一个小写字母开头:

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function() {
        alert(this.name);
    };
}
var person1 = new Person(" Nicholas", 29, "Software Engineer");
var person2 = new Person(" Greg", 27, "Doctor");

 构造函数可以当普通函数使用:

// 当构造函数使用 
var person = new Person(" Nicholas", 29, "Software Engineer");
person.sayName(); //"Nicholas" 
// 作为普通函数调用 
Person(" Greg", 27, "Doctor");
// 添加到window 
window.sayName(); //"Greg" 
// 在另一个对象的作用域中调用
var o = new Object();
Person.call(o, "Kristen", 25, "Nurse");
o.sayName(); //"Kristen"

构造函数的缺点:每次创建实例都会为实例新建一个方法,而且不是同一个方法的实例:

alert(person1.sayName == person2.sayName); //false

 可以这么改写:

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = sayName;
}

function sayName() {
    alert(this.name);
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");

③ 原型模式

function Person() {}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function() {
    alert(this.name);
};

var person1 = new Person();
person1.sayName(); //"
Nicholas " var person2 = new Person(); person2. sayName(); //"
Nicholas " 
alert(person1.sayName == person2.sayName); //true

 在上面,我们注意到在为原型对象添加属性时,需要每个都增加Person.prototype,这个工作很重复,可以改成更简单的语法:

function Person() {}
Person.prototype = {
    name: "Nicholas",
    age: 29,
    job: "Software Engineer",
    sayName: function() {
        alert(this.name);
    }
};

原型模式的缺点:假如原型有引用类型,假如是数组,那么所有的实例里面的数组都指向同一个数组。

JavaScript高级程序设计(第三版)②_第6张图片

④ 混合模式 ★★
 创建自定义类型的最常见方式,就是组合使用构造函数模式与原型模式。构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性,这样我们就可以传递不同的参数来创建出不同的对象,同时又拥有了共享的方法和属性

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.friends = ["Shelby", "Court"];
}
Person.prototype = {
    constructor: Person,
    sayName: function() {
        alert(this.name);
    }
}
var person1 = new Person(" Nicholas", 29, "Software Engineer");
var person2 = new Person(" Greg", 27, "Doctor");
person1.friends.push(" Van");
alert(person1.friends); //"Shelby, Count, Van" 
alert(person2.friends); //"Shelby, Count"
alert(person1.friends === person2.friends); //false 
alert(person1.sayName === person2.sayName); //true

⑤ 动态原型模式——将原型方法和构造函数封装到一块★★★

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.friends = ["Shelby", "Court"];
    if (typeof this.sayHi != "function") {
        Person.prototype.sayHi = function() {
            alert("hi,i am " + this.name);
        }
    }// 这段判断语句的作用是限制Person.prototype属性(原型属性对象)只生成一次,要不然每次实例化一个Person对象都会去写一遍原型对象!!!!!
}
var lee = new Person("lee", 17, "student");
var jack = new Person("jack", 28, "doctor");
lee.friends.push("dand");
lee.sayHi(); // hi,i am lee
alert(lee.friends); // Shelby,Court,dand
jack.sayHi(); // hi,i am jack
alert(jack.friends); // Shelby,Court

⑥ 寄生构造函数模式——假设我们想创建一个具有额外方法的特殊数组。由于不能直接修改Array构造函数,所以我们可以使用寄生模式:

function SpecialArray() {
    //创建数组
    var array=new Array();
    //添加值  arguments获取的是实参,不是形参,所以SpecialArray()并没有形参接收传递过来的参数
    array.push.apply(array,arguments);
    array.toPipedString=function(){
        return this.join("|");
    }
    return array;
}
var colors=new SpecialArray("red","blue","black");
alert(colors.toPipedString());  //输出:red|blue|black

⑦ 稳妥构造函数模式,所谓稳妥对象,指的是没有公共属性,而且其方法也不引用this的对象。:

function Person(name,age) {
    //创建要返回的对象
    var o=new Object();
    //可以在这里定义私有变量和函数
    //添加方法
    o.sayName=function(){
        alert(name);
    }
    //返回对象
    return o;
}
var person=Person("张三",22);
    person.sayName();  //使用稳妥构造函数模式只能通过其构造函数内部的方法来获取里面的属性值

你可能感兴趣的:(JavaScript高级程序设计(第三版)②)