《JavaScript高级程序设计》学习笔记,第五章- 下

Function类型

ECMAScript中的函数实际上也是对象。每个函数都是Function类型的实例,并且与其它引用类型一样也具有属性和方法。所以,函数名称实际上也是一个指向函数对象的指针。

这也就不难理解函数的另一种定义方法,实际就是在声明一个变量:

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

实际上,函数也能使用构造方法来定义,不过这种方法是不推荐使用的:

    // 最后一个参数会被当成函数体
    var sum = new Function("num1", "num2", "return num1 + num2");

没有重载(深入理解)

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

    function addSomeNumber(num) { return num + 100; }
    function addSomeNumber(num1, num2) { return num1 + 200; }
    var result = addSomeNumber(100); // 300

    // --> 等价于 <--

    var addSomeNumber = function(num) { return num + 100; }
    addSomeNumber = function(num1, num2) { return num1 + 200; }
    var result = addSomeNumber(100); // 300

可以看到,当声明两个同名函数时,实际上是第一个函数的变量被后面的所覆盖了,也就无法构成重载。

函数声明与函数表达式

在解析器向执行环境加载数据时,对待函数声明和函数表达式是有区别的。解析器会率先读取函数声明,并使其可以在执行任何代码之前可用,而对于函数表达式,则必须等到解析器执行到它所在的代码行,才会真正被执行。

    // 运行正确
    alert(sum(10,10));
    function sum(num1, num2){
        return num1 + num2;
    }

    // 运行出错
    alert(sum(10,10));
    var sum = function(num1, num2){
        return num1 + num2;
    };

由于第一段代码使用了函数声明,所以解析器会在运行代码之前将其添加到执行环境中,所以可以正确运行。而第二段代码因为是函数表达式,所以在执行第一句的时候,实际上sum函数还未被定义,导致运行出错。

作为值的函数

因为ECMAScript当中的函数名本身就是变量,所以函数也可以被当成参数来使用,也可以作为返回值从一个函数中返回。

函数内部属性

在函数的内部,有两个特殊的属性:argumentsthis,这两个属性本身也都是引用类型。

arguments除了保存函数参数的作用,还有一个callee的属性,该属性是一个指针,指向拥有这个arguments对象的函数。

    function factorial(num){
        if (num <=1) {
            return 1;
        } else {
            return num * factorial(num-1)
        }
    }
    // <-- 等价于 -->
    function factorial(num){
        if (num <=1) {
            return 1;
        } else {
            return num * arguments.callee(num-1)
        }
    }

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

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

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

    sayColor(); //"red"

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

当在全局作用域中调用函数时,this引用的是全局对象window。而使用对象调用时,则this指向的是该对象本身。

关于this以后还会进行详细的讨论。

ECMAScript 5中还定义了另一个函数属性的对象:caller,这个属性保存了调用当前函数的函数的引用,如果是在全局使用域内,则它的值为null

函数属性和方法

因为函数也是对象,所以函数也有属性和方法。每个函数都有两个属性:lengthprototype

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是ECMAScript中面向对象一个十分重要的属性,关于这个属性之后会更加详细地讨论。

除了上面两个属性,每个函数还有两个非继承而来的方法:apply()call(),使用这两个方法可以设置函数内this对象的值。

apply()方法接收两个参数,一个是在其中运行函数的使用域,别一个是参数数组,可以是Array的实例,也可以是arguments对象。如:

    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()方法类似,区别只在于第二个参数不同,对于call()方法,所以参数都是直接指定,而不是传递数组。

apply()call()方法真正强大的地方在于他们能够扩充函数运行的作用域。

    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

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

    window.color = "red";
    var o = { color: "blue" };
        function sayColor(){
        alert(this.color);
    }

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

而对于toString()toLocaleString()valueOf()方法,都会返回函数的代码。

基本包装类型

我们之前说过,只有引用类型的数据才能添加属性,但是我们却经常对字符串调用各种方法。这是怎么回事呢?实际上,每当我们读取一个基本类型值的时候,后台就会创建一个对应的基本包装类型的对象,从而我们能够调用一些方法来操作这些数据。

基本包装类型只存在于一行代码的执行瞬间,然后立即被销毁。这也就解释了为什么我们不能为基本类型添加属性和方法,因为对基本包装类型添加的属性,在下一行代码执行的时候就被销毁了。

我们应该避免显式地创建基本包装类型对象。

Boolean类型

Boolean类型是与布尔值对应的引用类型。Boolean类型的实例重写了valueOf()方法,返回基本类型值的truefalse。重写了toString()方法,返回字符串truefalse

Number类型

Number是与数字值对应的引用类型。NumbervalueOf()方法也会返回基本类型的数值,而toString()toLocaleString()方法会返回数值对应的字符串。

Number类型还提供了一些用于数值格式化为字符串的方法。

toFixed()方法会按照指定的小数返回数值的字符串表示:

    var num = 10;
    alert(num.toFixed(2)); //"10.00"

toExponential()方法会返回以指数表示法表示的数值的字符串形式。

    var num = 10;
    alert(num.toExponential(1)); //"1.0e+1"

对于一个数值来说,toPrecision()方法可能会返回固定大小(fixed)格式,也可能返回指数格式(exponential),具体看哪种格式最合适。

    var num = 99;
    alert(num.toPrecision(1)); //"1e+2"
    alert(num.toPrecision(2)); //"99"
    alert(num.toPrecision(3)); //"99.0"

String类型

String类型是字符串的对象包装类型。对于String类型的对象,三个继承的方法都直接返回对象所表示的基本字符串值。String类型每个实例都有一个length属性,表示字符串中的字符个数。要注意的是,即使字符串中包含双字节字符,每个字符也仍然算一个字符。

String类型提供了很多方法,以提供字符串的操作和解析。

字符方法

charAt()charCodeAt()用于访问字符串中的特定字符。这两个方法都接收一个参数,即基于0的字符位置。charAt()以字符形式返回给定位置的字符,而charCodeAt()会以字符编码的形式返回。

    var stringValue = "hello world";

    alert(stringValue.charAt(1)); //"e"
    alert(stringValue.charCodeAt(1)); // 输出"101"

在ECMAScript 5当中,还可以使用方括号加数字索引来访问字符串,与数组的取值方法类似。

字符串操作方法

concat()方法用于将一或多个字符串拼接起来,返回拼接得到的新字符串。

    var stringValue = "hello ";
    var result = stringValue.concat("world", "!");

    alert(result); //"hello world!"
    alert(stringValue); //"hello"

实际上,在大多数情况下,字符串拼接使用更多的还是加号操作符(+)。

ECMAScript还提供了三个用于获取子字符串的方法:slice()substr()substring()。这三个方法可以接受一个或两个参数,第一个参数为指定开始位置,对于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"
    alert(stringValue.substr(3, 7)); //"lo worl"

在传递参数为负值的情况下,slice() 方法会将传入的负值与字符串的长度相加, substr() 方法将负的第一个参数加上字符串的长度,而将负的第二个参数转换为 0。最后, substring() 方法会把所有负值参数都转换为 0。

    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()可以用于从字符串中查找子字符串,并返回子字符串的位置(如果没有找到,则返回-1)。两个方法区别只在于搜索的起始位置。并且两个方法都可以接受第二个参数,用于指定从哪个位置可以搜索。

trim()方法

ECMAScript 5为所有字符串字义了trim()方法。这个方法会创建一个字符串副本,删除前置和后缀的所有空格,然后返回结果。

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

字符串大小写转换方法

toLowerCase()toLocalceLowerCase()toUpperCase()toLocaleUpperCase()用于转换字符串的大小写。

字符串模式匹配方法

String类型定义了几个用于在字符串中匹配模式的方法。

match()RegExpexec()方法本质上是一样的。match()方法接收一个参数,要么是正则字面量,或一个RegExp对象。

    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

search()方法用于在字符串中查找符合特定模式的子字符串,并返回子字符串的位置,它接收的参数与match()一样。

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

String类型还提供了一个用于替换子字符串。这个方法可以接收两个参数:第一个参数可以是一个RegExp对象或者一个字符串(不会被转换为正则表达式),第二个参数可以是一个字符串或者一个函数。如果第一个参数是字符串,则只会替换第一个匹配的子字符串。

    var text = "cat, bat, sat, fat";
    var result = text.replace("at", "ond");
    alert(result); //"cond, bat, sat, fat"

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

第二个参数如果指定一个函数,传递给函数的参数依次是模式的匹配项、各个捕获组的匹配项、匹配项的位置和原始字符串。

最后一个与模式匹配有关的方法是split(),这个方法可以基本指定的分隔符将一个字符串分割成多个子字符串,并将结果放在一个数组中。分隔符可以是一个字符串(不会被转化为正则表达式),也可以是一个RegExp对象。这个方法也可以接收第二个参数,用于指定数组的大小。

    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(/[^\,]+/); //["", ",", ",", ",", ""]

localeCompare()方法

这个方法比较两个字符串,根据字符编码进行比较返回正数、负数或者0。

fromCharCode()方法

String构造函数本身还有一个静态方法:fromCharCode(),这个方法接收一个或多个字符编码,然后将它们转换成一个字符串。

    alert(String.fromCharCode(104, 101, 108, 108, 111)); //"hello"

单体内置对象

除了上面介绍的内置对象,ECMA-262还定义了两个单体内置对象:GlobalMath

Global对象

所以在全局作用域中定义的属性和函数,都是Global对象的属性。在web浏览器当中,这个对象被当作window对象的一部分来实现。Global对象还包含其他一些方法:

URI编码方法

encodeURI()encodeURIComponent()方法可以对URI进行编码,它们会以UTF-8编码替换掉无效的字符。其中,encodeURI()用于整个URI,而encodeURIComponent()只用于URI中某一段进行编码。与其对应的两个方法是decodeURI()decodeURIComponent()

eval()方法

eval()方法可以将传入的字符串当作ECMAScript语句来执行。通过eval()执行的代码被认为是包含该次调用的执行环境的一部分,因为被执行的代码具有与该执行环境相同的作用域链。

    eval("function sayHi() { alert('hi'); }");
    sayHi();
    eval("var msg = 'hello world'; ");
    alert(msg); //"hello world"

Math对象

ECMAScript对保存数学公式和信息提供了一个公共位置,即Math对象。

Math对象的属性

Math对象包含的属性大都是数学中可能用到的特殊值:

属性说明
Math.E自然对数的底数,即常量 e 的值
Math.LN1010的自然对数
Math.LN22的自然对数
Math.LOG2E以2为底 e 的对数
Math.LOG10E以10为底 e 的对数
Math.PIπ的值
Math.SQRT1_21/2的平方根(即2的平方根的倒数)
Math.SQRT22的平方根

min()和max()方法

这两个方法可以接收任意多个数值参数,并返回所有值的最大值或最小值。

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

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

舍入方法

Math.ceil()向上取整,Math.floor()向下取整,Math.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
    alert(Math.round(25.5)); //26
    alert(Math.round(25.1)); //25

    alert(Math.floor(25.9)); //25
    alert(Math.floor(25.5)); //25
    alert(Math.floor(25.1)); //25

random()方法

Math.random()方法返回大于等于0小于1的一个随机数。可以利用这个方法从某个整数范围内随机选择一个值。

    值 = Math.floor(Math.random() * 可能值的总数 + 第一个可能的值)
    // 通用函数
    function selectFrom(lowerValue, upperValue) {
        var choices = upperValue - lowerValue + 1;
        return Math.floor(Math.random() * choices + lowerValue);
    }

其他方法

Math对象中还包含了其他一些与计算相关的方法,在此就不详细记录,遇到的时候再查阅资料就可以了。