闲云博客

关注互联网科技,记录编程点滴

JavaScript学习笔记(2)

| 0 comments

函数

函数包含一组语句,它们是JavaScript的基础模块单元,用于代码复用、信息隐藏和组合调用。函数用于定义对象的行为。

1. 函数对象

在JavaScript中,函数就是对象。对象字面量产生的对象连接到Object.prototype。函数对象连接到Function.prototype(该原型对象本身连接到Object.prototype)。

每个函数对象在创建时也附带有一个prototype属性,它的值是一个拥有constructor属性且值即为该函数的对象。这和隐藏连接到Function.prototype完全不同。

因为函数是对象,所以它可以像任何其他值一样被使用。函数可以存放在变量、对象和数组中。函数可以被当作参数传递给其他函数。函数也可以再返回函数。而且,因为函数是对象,所以函数可以拥有方法。

函数的与众不同之处在于它们可以被调用。

2. 函数调用

调用一个函数将暂停当前函数的执行,传递控制权和参数给新函数。

除了声明时定义的形式参数,每个函数接收两个附加的参数:this和arguments

this的值取决与调用的模式。在JavaScript中存在四种调用模式:方法调用模式、函数调用模式、构造器调用模式和apply调用模式。这些模式在如何初始化关键参数this上存在差异。

当实际参数(arguments)的个数与形式参数(parameters)的个数不匹配时,不会导致运行时错误,并且对参数值不会进行类型检查。如果实际参数过多了,超出的参数值将被忽略。如果实际参数过少,缺失的参数值将会被赋予undefined值。

方法调用模式

当一个函数被保存为对象的一个属性时,我们称它为一个方法。当一个方法被调用时,this被绑定到该对象。

如果一个调用表达式包含一个属性存取表达式(即点号表达式或下标表达式),那么它被当作一个方法来调用。

var myObject = {
    value: 0,
    increment: function(inc){
        this.value += typeof(inc) == 'number' ? inc : 1;
    }
};
                                                                                      
myObject.increment();
document.writeln(myObject.value);  //1
                                                                                      
myObject.increment(2);
document.writeln(myObject.value);  //3

函数调用模式

当一个函数并非一个对象的属性时,那么它被当作一个函数来调用:

function add(a,b){
    return a + b;
};
                                                                                    
var sum = add(3,4);  //7

在此模式下,this被绑定到全局对象。

构造器调用模式

JavaScript是一门基于原型继承的语言。这意味着对象可以直接从其它对象继承属性。这偏离了当今编程语言的主流,当今大多数语言都是基于类的语言,尽管原型继承有着强大的表现力,但它并不被广泛理解。JavaScript本身对其原型的本质也缺乏信心,所以它提供了一套和基于类的语言类似的对象构建语法。

如果一个函数前面带上new来调用,那么将创建一个隐藏连接到该函数的prototype成员的新对象,同时this会被绑定到该新对象上。

//创建一个名为Quo的构造器函数,它构造一个带有status属性的对象
var Quo = function(s){
    this.status = s;
};
//在构造器函数的prototype对象上加一个方法,即给Quo的所有实例提供一个get_status的公共方法
Quo.prototype.get_status = function(){
    return this.status;
};
                                                                         
//构造一个Quo实例
var myQuo = new Quo("Confused");
document.writeln(myQuo.get_status());  //"Confused"

Apply调用模式

因为JavaScript是一门函数式的面向对象编程语言,所以函数可以拥有方法。

apply方法可以构建一个参数数组并用其去调用函数。

apply方法接收两个参数,第一个是将被绑定给将要调用的函数中this的值,第二个就是一个参数数组。

function add(a,b){
    return a + b;
};
                                                                 
var array = [3,4];
var sum = add.apply(null,array);  //7
var Quo = function(s){
    this.status = s;
};
                                                                
Quo.prototype.get_status = function(){
    return this.status;
};
                                                                
var statusObject = {
    status: "OK"
};
//statusObject并没有继承自Quo.prototype,所以也就没有get_status方法,但是我们可以通过apply调用Quo.prototype的get_status方法, this的值即为statusObject
var status = Quo.prototype.get_status.apply(statusObject);  //"OK"

3. 参数

当函数被调用时,会得到调用传递的实际参数,那就是arguments数组。通过它,函数可以访问所有它被调用时传递给它的参数列表。

这使得编写一个无需指定参数个数的函数成为可能:

var sum = function() {
    var i, sum = 0;
    for(i=0; i < arguments.length; i++)
    {
        sum += arguments[i];
    }
    return sum;
};
                                                           
document.writeln(sum(2,3,4));  //9
document.writeln(sum(2,5,15,20,40));  //82

因为语言的一个设计错误,arguments并不是一个真正的数组,它只是一个“类似数组(array-like)”的对象。arguments拥有一个length属性,但它缺少所有的数组方法。

4. Return

return语句可以使函数提前返回。

一个函数总是会返回一个值,如果没有指定返回值,则返回undefined。

如果函数是被在前面加上new前缀的方式调用的,且返回值不是一个对象,则返回this(该新对象)。

5. 给类型添加方法

JavaScript允许给语言的基本类型添加方法。

我们已经知道给Object.prototype添加方法可以使方法对所有的对象可用。这样的方式对函数、数组、字符串、数字、正则表达式和布尔值同样适用。

比如,我们可以给Function.prototype增加方法来使得该方法对所有函数可用:

Function.prototype.method = function(name, func){
    this.prototype[name] = func;
    return this;
};

通过给Function.prototype增加一个method方法,我们就不必键入prototype这个属性名。下面可以看到它的好处。

JavaScript并没有单独的整数类型,因此有时候提取数字中的整数部分是必要的。JavaScript本身的取整方法有些丑陋,我们可以通过给Number.prototype增加一个integer方法来改善它。它会根据数字的正负来判断是使用Math.ceil还是Math.floor:

Number.method("integer", function(){
    return Math[this < 0 ? 'ceil' : 'floor'](this);
});
                                        
document.writeln((-10 / 3).integer());  //-3

JavaScript缺少一个移除字符串首末的空白的方法。那是一个很容易修复的疏忽:

String.method('trim', function(){
    return this.replace(/^\s+|\s+$/g,'');
});
                                     
document.writeln("  test  ".trim());

基本类型的原型是公共的结构,所以在类库混用时务必小心。一个保险的做法是只在确定没有该方法时才添加它。

Function.prototype.method = function(name, func){
    if(!this.prototype[name]){
        this.prototype[name] = func;
    }
    return this;
};

6. 作用域

在编程语言中,作用域控制着变量与参数的可见性与生命周期。对程序员来说,这是一个重要的帮助,因为它减少了名称冲突,并且提供了自动内存管理。

var foo = function() {
    var a = 3, b = 5;
                                
    var bar = function() {
        var b = 7, c = 10;  //此时a为3,b为7,c为10
                                
        a += b + c;  //此时a为20,b为7,c为10
    };
                                
    //此时a为3,b为5,而c没有定义
    bar();
    //此时a为20,b为5,而c没有定义
};

大多数语言都有块级作用域。在一个代码块中(括在一对花括号中的语句集)定义的所有变量在代码块的外部都是不可见的。定义在代码块中的变量在代码块执行结束后会被释放掉。这是件好事。

但是JavaScript并不支持块级作用域。

JavaScript确实有函数作用域。那意味着定义在函数中的参数和变量在函数外部都是不可见的,而且在一个函数中的任何位置定义的变量在该函数中的任何地方都可见

很多现代语言都推荐尽可能迟地声明变量,而在JavaScript中却是糟糕的建议,因为它缺少块级作用域。所以,最好在函数体的顶部声明函数中可能用到的所有变量。

7. 闭包

作用域的好处是内部函数可以访问定义他们的外部函数的参数和变量(除了this和arguments)。这是一件非常好的事情。一个更有趣的情形是内部函数拥有比它的外部函数更长的生命周期。

之前,我们构造过一个myObject对象,它拥有一个value属性和一个increment方法。

var myObject = {
    value: 0,
    increment: function(inc){
        this.value += typeof inc == 'number' ? inc : 1;
    }
};

假定我们希望保护value值不被非法更改。

和以对象字面量的形式去初始化myObject不同,我们通过调用一个函数的形式去初始化myObject,该函数将返回一个对象字面量。

var myObject = function(){
    var value = 0;
               
    return {
        increment: function(inc){
            value += typeof inc == 'number' ? inc : 1;
        },
        getValue: function(){
            return value;
        }
    }
}();

此函数定义了一个value变量,该变量对increment和getValue方法总是可用的,但函数的作用域使得它对其它的程序来说是不可见的。

我们并没有把一个函数赋值给myObject,而是把调用该函数后返回的结果赋值给它。注意最后一行的括号。

该函数返回一个包含两个方法的对象,并且这些方法继续享有访问value变量的权限。

之前,我们定义了一个Quo构造器,能产生出带有status属性和get_status方法的一个对象。

var Quo = function(s){
    this.status = s;
};
         
Quo.prototype.get_status = function(){
    return this.status;
};
         
var myQuo = new Quo("Confused");
document.writeln(myQuo.get_status());

但那看起来并不十分有趣,我们为什么要通过一个方法去访问一个你本可以直接去访问的属性呢?如果status是私有属性的话,那才更有意义。所以,让我们定义另一种形式的quo函数来做此事:

//创建一个名为quo的构造函数。
//它构造出一个带有get_status方法和status私有属性的一个对象
var quo = function(status){
    return {
        get_status: function(){
            return status;
        }
    };
};
        
//构造一个quo实例
var my_quo = quo("amazed");
document.writeln(my_quo.get_status());

这个quo构造函数被设计成无需在前面加上new来使用。当我们调用quo时,它返回包含get_status方法的一个新对象。该对象的一个引用保存在my_quo中。即使quo已经return了,但get_status方法仍然享有访问quo对象的status参数的特权。get_status方法并不是访问该参数的拷贝,它访问的就是该参数本身。这是可能的,因为该函数可以访问它被创建时所处的上下文环境。这被称为闭包

原创文章,转载请注明: 转载自闲云博客

本文链接地址: JavaScript学习笔记(2)

Author: Jian Yun

Hi,我是闲云,感谢您阅读我的博客。我是一个微软ASP.NET方面的开发者,写写博客分享下互联网科技方面感兴趣的事和记录自己程序开发中的点点滴滴。 ------“立志难也,不在胜人,在自胜”

发表评论

Required fields are marked *.