JS语言精粹--函数篇之this与调用模式
久违的博文,貌似距离我上一篇也算是有些年岁(加班的日子真是度日如年啊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
。
第二部分,是 函数名,它可以省略不写。函数可以用它的名字来 递归 地调用自己。此名字也能被调试器和开发工具来识别函数(如:FireBug、Chrome console 等)。如果没有给函数命名,比如上面的例子,它会认为是 匿名函数
。
第三部分,是包围在圆括号中的一组 参数,其中每个参数之间用逗号隔开,这些参数(也称形式参数
,即形参)将被定义为函数中的变量,但是,它们不像普通变量那样被初始化为 undefined
,而是在该函数被调用时初始化为实际提供的参数的值(也称实际参数
,即实参)。
第四部分,是包围在花括号中的一组语句,这些语句就是 函数主体,它们在函数被调用时执行。
函数字面量可以出现在任何允许表达式出现的地方。当然,函数也可以嵌套在其他函数中,这样的话,一个内部函数不仅可以访问自己的参数和变量,同时也可以方便地访问它被嵌套的那个外部函数的参数和变量。
通过函数字面量创建的函数对象包含一个连到外部上下文的连接,这被称为 闭包
。它是 JavaScript 强大表现力的根基。而关于闭包的详细原理和使用方法,以后会发布一些专门的文章进行说明,敬请期待 ( ^_^ ) ~~
荤割线之后,接下来就是本文的重头戏 -- 关键字
this
上场。众所周知,这个老(son)伙(of)计(bit ch)可以说是JavaScript中的一大深坑,至于如何华丽丽地跳出这个坑,还请各位搬好板凳,备好瓜子,听我慢慢道来。
调用
当我们调用一个函数时,将暂停当前函数的执行,将传递控制器与参数给新函数。然而,除了声明时定义的形参,每个函数接收两个附加的参数:this
和 arguments
。参数 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(); };// 以方法的形式调用 doublemyObject.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 指针。