资讯专栏INFORMATION COLUMN

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

niceforbear / 2681人阅读

摘要:在中有四种调用模式方法调用模式函数调用模式构造器调用模式和调用模式。构造一个的实例目标就是结合前缀来调用的函数,被称为构造函数。

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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/85982.html

相关文章

  • 【阅读笔记】javascript 语言精粹

    摘要:前言由于最近的项目用到了一些的代码,所以我带着好奇心,认真阅读了这本书,粗略地了解语言的基本结构和特性,对于一些不熟悉的新概念,以记录的形式加强印象,也是对学习的反思总结。 前言 由于最近的项目用到了一些js的代码,所以我带着好奇心,认真阅读了这本书,粗略地了解js语言的基本结构和特性,对于一些不熟悉的新概念,以记录的形式加强印象,也是对学习的反思总结。 一、字面量(literals...

    tangr206 评论0 收藏0
  • 【阅读笔记】JavaScript语言精粹

    摘要:对之前看高级程序设计时没有注意到的一些知识点,结合本书做以补充语法注释源于的型既可以出现在字符串字面量中,也可能出现在正则表达式字面量中,如故一般建议使用型注释保留字语句变量参数属性名运算符和标记等标识符不允许使用保留字,此外在对象字面量中 对之前看《JavaScript高级程序设计》时没有注意到的一些知识点,结合本书做以补充 语法 注释 源于PL/I的/* */型既可以出现在字符串字...

    cucumber 评论0 收藏0
  • 《javascript语言精粹》学习笔记 - 继承

    摘要:但采用构造器调用模式,即是使用了前缀去调用一个函数时,函数执行的方式会改变。对象包含构造器需要构造一个新的实例的所有信息。构造器的变量和内部函数变成了该实例的私有成员。 JavaScript 是一门弱类型语言,从不需要类型转换。对象继承关系变得无关紧要。对于一个对象来说重要的时它能够做什么,而不是它从哪里来。 阅读《javascript语言精粹》笔记! 伪类 js的原型存...

    harriszh 评论0 收藏0
  • 前端—初级阶段4(13-15)—JavaScript语言精粹

    摘要:调用函数时,被绑定到全局对象。如果使用构造器调用有前缀,且返回不是一个对象,则返回该新对象。闭包会导致原有作用域链不释放,造成内存泄漏。当采用构造器调用模式,函数执行的方式会被修改。 内容 ECMAScript核心语法结构:1.语法2.对象3.函数4.继承5.数组6.正则表达式7.方法8.附录A-毒瘤9.附录B-糟粕 一、语法 1.类型、值和变量 1) 类型:区分数据类型 在JS中使...

    iflove 评论0 收藏0
  • 前端—初级阶段4(13-15)—JavaScript语言精粹

    摘要:调用函数时,被绑定到全局对象。如果使用构造器调用有前缀,且返回不是一个对象,则返回该新对象。闭包会导致原有作用域链不释放,造成内存泄漏。当采用构造器调用模式,函数执行的方式会被修改。 内容 ECMAScript核心语法结构:1.语法2.对象3.函数4.继承5.数组6.正则表达式7.方法8.附录A-毒瘤9.附录B-糟粕 一、语法 1.类型、值和变量 1) 类型:区分数据类型 在JS中使...

    Wuv1Up 评论0 收藏0

发表评论

0条评论

niceforbear

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<