久违的博文,貌似距离我上一篇也算是有些年岁(加班的日子真是度日如年啊T^T)了,因此呢,如今是时候回归正道了,仍是欢迎各位IT道友多多交(tu)流(cao)哈!编程
首先,说到 JavaScript 函数,咱们就要先理解下一些极可能被忽视的小概念:函数对象
和 函数字面量
。数组
咱们知道,在JavaScript中 函数 就是 对象。对象是“名/值”的集合,并拥有一个连到原型对象的隐藏链接。其中,对象字面量产生的对象链接到 Object.prototype
,而函数对象链接到 Function.prototype
(注:该原型对象自己链接到 Object.prototype )。每一个函数在建立时,都附有两个附加的隐藏属性: 函数的上下文 和 实现函数行为的代码 。闭包
另外,每一个函数对象在建立时,也随带有一个 prototype
属性,他的值是一个拥有 constructor
属性,并且其值即为该函数的对象。这和隐藏链接到 Function.prototype 彻底不一样,而这个使人费解的构造过程的意义,我先埋个坑,之后在继承篇的相关文章中再来填好了。app
由于函数是对象,因此它能够像其余值同样被使用,好比,能够存放在变量、对象和数组中,能够被当作参数传递给其余函数,也能够在函数中返回函数,并且,更由于函数是对象,所以 函数也能够拥有方法。编程语言
函数对象能够经过函数字面量来建立:函数
var add = function (a, b) { return a + b; }
函数字面量包括四个部分:工具
第一部分,是 保留字 function
。开发工具
第二部分,是 函数名,它能够省略不写。函数能够用它的名字来 递归 地调用本身。此名字也能被调试器和开发工具来识别函数(如:FireBug、Chrome console 等)。若是没有给函数命名,好比上面的例子,它会认为是 匿名函数
。this
第三部分,是包围在圆括号中的一组 参数,其中每一个参数之间用逗号隔开,这些参数(也称形式参数
,即形参)将被定义为函数中的变量,可是,它们不像普通变量那样被初始化为 undefined
,而是在该函数被调用时初始化为实际提供的参数的值(也称实际参数
,即实参)。prototype
第四部分,是包围在花括号中的一组语句,这些语句就是 函数主体,它们在函数被调用时执行。
函数字面量能够出如今任何容许表达式出现的地方。固然,函数也能够嵌套在其余函数中,这样的话,一个内部函数不只能够访问本身的参数和变量,同时也能够方便地访问它被嵌套的那个外部函数的参数和变量。
经过函数字面量建立的函数对象包含一个连到外部上下文的链接,这被称为 闭包
。它是 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(); }; // 以方法的形式调用 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的继承篇为各位提供更好的解决方案。
由于 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 指针。