3

久违的博文,貌似距离我上一篇也算是有些年岁(加班的日子真是度日如年啊T^T)了,所以呢,现在是时候回归正道了,还是欢迎各位IT道友多多交(tu)流(cao)哈!

正文

首先,说到 JavaScript 函数,我们就要先理解下一些很可能被忽视的小概念:函数对象函数字面量

函数对象

我们知道,在JavaScript中 函数 就是 对象。对象是“名/值”的集合,并拥有一个连到原型对象的隐藏连接。其中,对象字面量产生的对象连接到 Object.prototype,而函数对象连接到 Function.prototype (注:该原型对象本身连接到 Object.prototype )。每个函数在创建时,都附有两个附加的隐藏属性: 函数的上下文 实现函数行为的代码

另外,每个函数对象在创建时,也随带有一个 prototype 属性,他的值是一个拥有 constructor 属性,而且其值即为该函数的对象。这和隐藏连接到 Function.prototype 完全不同,而这个令人费解的构造过程的意义,我先埋个坑,以后在继承篇的相关文章中再来填好了。

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

函数字面量

函数对象可以通过函数字面量来创建:

var add = function (a, b) {
    return a + b;
}

函数字面量包括四个部分:

第一部分,是 保留字 function

第二部分,是 函数名,它可以省略不写。函数可以用它的名字来 递归 地调用自己。此名字也能被调试器和开发工具来识别函数(如:FireBugChrome console 等)。如果没有给函数命名,比如上面的例子,它会认为是 匿名函数

第三部分,是包围在圆括号中的一组 参数,其中每个参数之间用逗号隔开,这些参数(也称形式参数,即形参)将被定义为函数中的变量,但是,它们不像普通变量那样被初始化为 undefined而是在该函数被调用时初始化为实际提供的参数的值(也称实际参数,即实参)。

第四部分,是包围在花括号中的一组语句,这些语句就是 函数主体,它们在函数被调用时执行。

函数字面量可以出现在任何允许表达式出现的地方。当然,函数也可以嵌套在其他函数中,这样的话,一个内部函数不仅可以访问自己的参数和变量,同时也可以方便地访问它被嵌套的那个外部函数的参数和变量。

通过函数字面量创建的函数对象包含一个连到外部上下文的连接,这被称为 闭包它是 JavaScript 强大表现力的根基。而关于闭包的详细原理和使用方法,以后会发布一些专门的文章进行说明,敬请期待 ( ^_^ ) ~~


荤割线之后,接下来就是本文的重头戏 -- 关键字 this 上场。众所周知,这个老(son)伙(of)计(bit ch)可以说是JavaScript中的一大深坑,至于如何华丽丽地跳出这个坑,还请各位搬好板凳,备好瓜子,听我慢慢道来。

调用

当我们调用一个函数时,将暂停当前函数的执行,将传递控制器与参数给新函数。然而,除了声明时定义的形参,每个函数接收两个附加的参数:thisarguments。参数 this 在面向对象编程中是非常重要的,它的值取决于调用的模式。在JavaScript中有四种调用模式:方法调用模式函数调用模式构造器调用模式apply调用模式

调用运算符,就是跟在任何一个函数值的表达式之后的一对圆括号,它可以包含零个或者多个用逗号隔开的表达式,每个表达式产生一个参数值,每个参数值被赋予函数声明时定义的形式参数名,而当实际参数(arguments)的个数与形式参数(parameters)的个数不匹配时,不会导致运行时报错。比如说,如果实参值过多,超出的参数值将被忽略,如果实参值过少,缺失的值将会被替换为 undefined。并且,对参数值不会进行类型检查,即任何类型的值都可以被传递给参数。

方法调用模式

当一个函数被保存为对象的一个属性时,我们称之为方法。当一个方法被调用时,this 会被绑定到该对象,即this就是该对象。如果一个调用表达式包含一个属性存取表达式(即一个 . 点表达式 或者 [subscript] 下标表达式),那么它将被当做一个方法来调用。

// 创建 myObject。它有一个 value 属性 和一个 increment 方法
// increment 方法接收一个可选的参数,若参数不是数字型,则默认使用数字 1。
var myObject = {
    value: 0,
    increment: function (inc) {
        this.value += typeof inc === 'number' ? inc : 1;
    }
};
// 不传参
myObject.increment();  
console.log(myObject.value);  // 1

// 传非数字型
myObject.increment('a');  
console.log(myObject.value);  // 2 

// 传数字型
myObject.increment(2);  
console.log(myObject.value);  // 4 

方法可以使用 this 去访问对象,所以它能从对象中取值或者修改该对象。this 到对象的绑定,发生在调用的时候。这个“超级”迟绑定(very late binding)使得函数可以对 this 高度复用。通过 this 可取得它们所属对象的上下文的方法,称为公共方法

函数调用模式

当一个函数并非一个对象的属性(即方法)时,那么它将被当做一个函数来调用。

function add (a, b) {
    return a + b;
}

var sum = add(3,4);
console.log(sum);  // 7

当函数以此模式调用时,this 被绑定到全局对象(即 window 对象),这是语言设计上的一个重大的错误啊!!如果设计正确的话,当内部函数被调用时,this 应该仍然绑定到外部函数的 this 变量才对。这个错误设计的后果是,方法不能利用内部函数来帮助他工作,因为内部函数的 this 被绑定了错误的值,或者说绑定了我们不想要的值,所以不能共享该方法对于对象的访问权。不过,幸运的是,有一个很容易的解决方案:如果该方法定义一个变量并给它赋值为this,那么内部函数就可以通过那个变量访问到 this,而这个变量我们通常命名为 that

function add (a, b) {
    return a + b;
}

// 给 myObject 增加一个double方法
myObject.double = function () {
    var that = this;
    console.log(that);

    var helper = function () {
        that.value = add(that.value, that.value);
    };

    // 以函数的形式调用 helper
    helper();  
};

// 以方法的形式调用 double
myObject.double();  
console.log(myObject.getValue());  // 8

构造器调用模式

JavaScript 是一门基于原型继承的语言,这就意味着对象可以直接从其它对象继承属性或方法,而该语言也是无类别的。

如果在一个函数前面加上一个 new 来调用,那么将会创建一个隐藏连接到该函数的 prototype 成员的新对象(或者称之为该对象的实例),同时,this 将会被绑定到那个新对象(实例)上。

然而,new 前缀也会改变 return 语句的行为,这个我们以后再做详细解析。

Quo.prototype.get_status = function () {
    return this.status;
};

// 构造一个 Quo 的实例
var myQuo = new Quo('success');
console.log(myQuo.get_status());  // success  

目标就是结合 new 前缀来调用的函数,被称为构造函数。按照约定,它们保存在以首字母大写命名的变量里。如果调用构造函数时,没有在前面加上 new,可能会发生非常糟糕的事情(如,实例无法调用该原型对象的方法,等),这样既没有编译时警告,也没有运行时警告,所以加 new 前缀和大写约定,是非常、非常、非常重要的(重要话,说三遍)。

然并卵,实际使用中,我们并不推荐这种形式的构造器函数,以后将在JavaScript的继承篇为各位提供更好的解决方案。

Apply 调用模式

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

apply 方法让我们构建一个参数数组并用其去调用函数,它也允许我们选择 this 的取值。apply 方法接收两个参数,第一个是将被绑定给 this 的值,第二个就是一个参数数组。

// 1.构造一个带有两个数字的数组,将之相加
var arr = [3, 4];
var sum = add.apply(null, arr);  

console.log(sum);  // 7

// 2.构造一个含有 status 成员的对象
var statusObj = {
    status: 'right'
};

var status = Quo.prototype.get_status.apply(statusObj);  
console.log(status);  // right

这里第二个例子的代码,通过了 apply 方法替换 Quo 对象中的 this 指针。


羅聲門X
232 声望6 粉丝

爱编程、爱旅行的四次元逗比