JavaScript设计模式与开发实践 | this、call和apply

原文地址:http://segmentfault.com/a/1190000003959359

this

JavaScript的this总是指向一个对象,至于指向哪个对象,是在运行时基于函数的执行环境的动态绑定的,而非函数被声明时的环境。

this的指向

this的指向大致可以分为以下4类:

  • 作为对象的方法调用
  • 作为普通函数调用
  • 构造器调用
  • Function.prototype.callFunction.prototype.apply调用

1.作为对象的方法调用

当函数作为对象的方法被调用时,this指向该对象:

// 声明obj对象
var obj = {
    a: ‘a属性的值‘,    // a 属性
    getA: function(){  // getA()方法
        console.log(this === obj);  // 输出:true
        console.log(this.a);  // 输出: a属性的值
    }
};

obj.getA();

2.作为普通函数调用

当函数不作为对象的属性被调用时,也就是以普通函数方式,this指向全局对象。在浏览器的JavaScript里,全局对象是window对象。

window.name = ‘globalName‘;  // 声明全局对象的name属性

var getName = function(){  // 定义getName()函数
    return this.name;
};

// 调用函数
console.log(getName());  //输出: globalName
window.name = ‘globalName‘;  // 声明全局对象的name属性

var myObject = {  // 声明myObject对象
    name: ‘objectName‘;
    getName: function(){  // 定义getName()方法
        return this.name;
    }
}

var getName = myObject.getName;  // 将getName()方法赋给变量getName

console.log(getName());  // 输出: globalName

3.构造器调用

JavaScript没有类,但可以从构造器中创建对象,也提供了new运算符用于调用构造器。

大部分JavaScript函数都可以当作构造器使用。构造器的外表跟普通函数一样,他们的区别在于被调用的方式。即,使用new运算符创建对象时,就是将函数当作构造器调用。当用new运算符调用函数时,该函数总会返回一个对象,此时,构造器里的this指向返回的这个对象。

var myClass = function(){
    this.name = ‘className‘;
};

var obj = new myClass();
console.log(obj.name);  // 输出:seven

但,如果构造器显式地返回了一个object类型的对象,那么此函数将返回这个object类型的对象,而不是函数本身所定义的对象,例如:

var myClass = function(){
    this.name = ‘className‘;
    return {  //显式地返回一个对象
        name: ‘anne‘
    }
};

var obj = new myClass();
console.log(obj.name);  //  输出:anne

而,如果构造器不显式地返回任何数据,或返回一个非对象类型的数据,就不会造成上述情形。

var myClass = function(){
    this.name = ‘className‘;
    return ‘anne‘;  // 返回string类型
};

var obj = new myClass();
console.log(obj.name);  //  输出:className

4.Function.prototype.call 或 Function.prototype.apply调用

跟普通函数调用相比,用 Function.prototype.call 或 Function.prototype.apply 可以动态地改变传入函数的this。

var A = {
    name: ‘ObjectA‘,
    getName: function(){
        return this.name;
    }
};

var B = {
    name: ‘ObjectB‘
};

console.log(A.getName()); // 作为对象的方法调用,输出:ObjectA
console.log(A.getName.call(B)); // 输出:ObjectB

丢失的this

我们经常会因为this的指向与我们的期待不同,而出现undefined的情况,例如:

var obj = {
    name: ‘objName‘;
    getName: function(){
        return this.name;
    }
};

// 作为对象的方法调用,指向obj对象
console.log(obj.getName());   // 输出:objName

// 作为普通函数调用,指向全局对象window,name属性尚未定义
var getName2 = obj.getName;
console.log(getName2());  // 输出:Lundefined

call 和 apply

ECAMScript3给Function的原型定义了两个方法,分别是Function.prototype.call 或 Function.prototype.apply。在一些函数式风格的代码编写中,call和apply方法尤为有用。

call和apply的区别

Function.prototype.call 或 Function.prototype.apply的作用一模一样,区别仅在于传入参数形式的不同。

apply接受两个参数,第一个参数制定了函数体内this对象的指向,第二个函数为一个带下标的集合,这个集合可以是数组,也可以是类数组。apply方法把这个集合中的元素作为参数传递给被调用的函数。

var func = function(a, b, c){
  console.log([a, b, c]);  // 输出:[1,2,3]
};

func.apply(null, [1, 2, 3]);

call传入的参数数量不固定,第一个参数也是代表了函数体内的this指向,从第二个参数开始往后,每个参数依次被传入函数:

var func = function(a, b, c){
  console.log([a, b, c]);  // 输出:[1,2,3]
};

func.call(null, 1, 2, 3);

当调用一个函数时,JavaScript的解释器并不会计较形参和实参在数量、类型、以及顺序上的区别,JavaScript的参数在内部就是用一个数组来表示的。从这个意义上说,apply比call的使用率更高,我们不必关心具体有多少参数被传入函数,只要用apply一股脑地推过去就可以了。

当使用call或apply的时候,如果我们传入的第一个参数为null,函数体内的this会指向默认的宿主对象,在浏览器中则是window:

var func = function(a, b, c){
  console.log(this);
};

func.apply(null, [1, 2, 3]);  //输出:window对象
func.call(null, 1, 2, 3);  //输出:window对象

call和apply的用途

  • 改变this指向
  • Function.prototype.bind
  • 借用其他对象的方法

1.改变this指向

call和apply最常见的用途是改变函数内部的this指向:

var A = {
  name: ‘nameA‘;
};

var B = {
  name: ‘nameB‘;
};

window.name = ‘nameWindow‘;

var getName = function(){
  conlole.log(this.name);
};

getName();  // 以普通函数调用,指向了window对象,输出:nameWindow
getName.call(A);  // 改变了this的指向,指向了传入的对象,输出:nameA
getName.call(B);  // 改变了this的指向,指向了传入的对象,输出:nameB

2.Function.prototype.bind

大部分高级浏览器都实现了内置的Function.prototype.bind,用来指定函数内部的this指向。
若没有原生的Function.prototype.bind实现,可以通过模拟一个:

Function.prototype.bind = function(context){
  var self = this;  // 保存原函数
  return function(){  // 返回一个新的函数
      return self.apply(context, arguments);  // 执行新函数的时候,会把之前传入的context当作新函数体内的this
  }
};

var obj = {
 name: "objName"
};

var func = function(){
  console.log(this.name);  // 输出:objName
}.bind(obj);

func();
时间: 2024-10-12 12:24:28

JavaScript设计模式与开发实践 | this、call和apply的相关文章

《JavaScript设计模式与开发实践》读书笔记之观察者模式

1.<JavaScript设计模式与开发实践>读书笔记之观察者模式 观察者模式定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知. JavaScript中通常采用事件模型替代传统的观察者模式 1.1 逐步实现观察者模式 以客户看房为例 首先指定谁充当发布者,如售楼处 然后给发布者添加一个缓存列表,用于存放回调函数以便通知订阅者.这里为了让订阅者只接收自己感兴趣的消息,增加一个标识key 最后发布消息时候,发布者遍历缓存列表,依次触发里面存放的订阅者的回

JavaScript 设计模式与开发实践读书笔记 http://www.open-open.com/lib/view/open1469154727495.html

JavaScript 设计模式与开发实践读书笔记 最近利用碎片时间在 Kindle 上面阅读<JavaScript 设计模式与开发实践读书>这本书,刚开始阅读前两章内容,和大家分享下我觉得可以在项目中用的上的一些笔记. 我的 github 项目会不定时更新,有需要的同学可以移步到我的 github 中去查看源码: https://github.com/lichenbuliren/design-mode-notes 1.currying 函数柯里化 currying 又称 部分求值 .一个 cu

JavaScript设计模式与开发实践 – 观察者模式 http://web.jobbole.com/87809/

概述 观察者模式又叫发布 – 订阅模式(Publish/Subscribe),它定义了一种一对多的关系,让多个观察者对象同时监听某一个目标对象(为了方便理解,以下将观察者对象叫做订阅者,将目标对象叫做发布者).发布者的状态发生变化时就会通知所有的订阅者,使得它们能够自动更新自己. 观察者模式的使用场合就是:当一个对象的改变需要同时改变其它对象,并且它不知道具体有多少对象需要改变的时候,就应该考虑使用观察者模式. 观察者模式的中心思想就是促进松散耦合,一为时间上的解耦,二为对象之间的解耦.让耦合的

【读书】JavaScript 设计模式与开发实践

2016.08.30 <JavaScript 设计模式与开发实践> 曾探 人民邮电出版社 2016年5月第1版 p13 找到变化的部分并封装之,以使得容易替换:而剩下的就是不变的部分. P49 函数柯里化(currying)的作用是多次收集参数,然后作为数组传给处理函数再一次执行. 其意义在于预处理--将预处理的流程放到一个函数里会更为清晰可控. P57 惰性加载函数 在函数内部重写引用函数的外部变量的引用,从而在第一次"调用"此变量后,此变量就指向新的正确的函数. p84

【摘】JavaScript设计模式与开发实践--单例模式

本文章所有内容均摘自<Javascript设计模式与开发实践>一书(有兴趣的可以购买),加入了一点点自己的理解,写这篇文章的目的是,加强自身对设计模式的理解,以及对于没有接触过这一块的入门者的参考. 阅读本章内容,需要具备Javascript面向对象的知识,否则阅读起来可能会些许困难. 设计模式 单例模式 策略模式 代理模式 迭代器模式 发布-订阅模式 命令模式 组合模式 模板方法模式 享元模式 职责链模式 中介者模式 装饰者模式 状态模式 适配器模式 单例模式 单例模式的定义:保证一个类仅有

JavaScript设计模式与开发实践——JavaScript的多态

"多态"一词源于希腊文polymorphism,拆开来看是poly(复数)+ morph(形态)+ ism,从字面上我们可以理解为复数形态. 多态的实际含义是:同一操作作用于不同的对象上面,可以产生不同的解释和不同的执行结果.换句话说,给不同的对象发送同一个消息的时候,这些对象会根据这个消息分别给出不同的反馈. 从字面上来理解多态不太容易,下面我们来举例说明一下. 主人家里养了两只动物,分别是一只鸭和一只鸡,当主人向它们发出"叫"的命令时,鸭会"嘎嘎嘎&q

JavaScript设计模式与开发实践 – 观察者模式

概述 观察者模式又叫发布 – 订阅模式(Publish/Subscribe),它定义了一种一对多的关系,让多个观察者对象同时监听某一个目标对象(为了方便理解,以下将观察者对象叫做订阅者,将目标对象叫做发布者).发布者的状态发生变化时就会通知所有的订阅者,使得它们能够自动更新自己. 观察者模式的使用场合就是:当一个对象的改变需要同时改变其它对象,并且它不知道具体有多少对象需要改变的时候,就应该考虑使用观察者模式. 观察者模式的中心思想就是促进松散耦合,一为时间上的解耦,二为对象之间的解耦.让耦合的

JavaScript设计模式与开发实践【第一部分】

今天开始阅读<JavaScript设计模式与开发实践>,对于设计模式的学习一直渴望已久. 设计模式的定义是:在面向对象软件设计过程中针对特定问题的简洁而优雅的解决方案. 其实平时在工作中不知不觉在使用某些设计模式,只是我们不知道而已. 动态类型语言和静态类型语言 静态类型语言在编译时便已确定变量的类型,而动态类型语言的变量类型要到程序运行的时 候,待变量被赋予某个值之后,才会具有某种类型. 静态类型语言的优点首先是在编译时就能发现类型不匹配的错误,编辑器可以帮助我们提前 避免程序在运行期间有可

JavaScript设计模式与开发实践——读书笔记1.高阶函数(下)

上部分主要介绍高阶函数的常见形式,本部分将着重介绍高阶函数的高级应用. 1.currying currying指的是函数柯里化,又称部分求值.一个currying的函数会先接受一些参数,但不立即求值,而是继续返回给另一个函数,通过闭包存储起来.等到函数被真正需求要求值的时候,将之前传入的参数统一起来求值.例如,我们要计算一个月的开销,我们并不需要计算每天具体花了多少,而是需要计算月底总共花掉多少,也就是说,实际上我们只需要在月底计算一次.所以每个月的前29天,我们都只需要保存好当天的开销,到30

Javascript设计模式与开发实践详解(二:策略模式) http://www.jianshu.com/p/ef53781f6ef2

上一章我们介绍了单例模式及JavaScript惰性单例模式应用这一次我主要介绍策略模式策略模式是定义一系列的算法,把它们一个个封装起来,并且让他们可以互相替换.比方说在现实中很多时候也有很多途径到达同一个目的地,比如我们去某个地方旅游,可以选择坐飞机,乘火车,骑自行车等方式. 使用策略模式计算奖金 很多公司的年终奖是根据员工的工资基数和年底绩效来发放的.例如,绩效为 S 的人年终奖有4倍工资,绩效为 A 的人年终奖有3倍工资,绩效为 B 的人年终奖有2倍工资.现在我们来计算员工的年终奖. var