JS语言精粹--函数篇之this与调用模式

久违的博文,貌似距离我上一篇也算是有些年岁(加班的日子真是度日如年啊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开发工具

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

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

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

函数字面量能够出如今任何容许表达式出现的地方。固然,函数也能够嵌套在其余函数中,这样的话,一个内部函数不只能够访问本身的参数和变量,同时也能够方便地访问它被嵌套的那个外部函数的参数和变量。

经过函数字面量建立的函数对象包含一个连到外部上下文的链接,这被称为 闭包它是 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 指针。

相关文章
相关标签/搜索