资讯专栏INFORMATION COLUMN

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

darryrzhong / 991人阅读

摘要:构造器的外表跟普通函数一样,他们的区别在于被调用的方式。即,使用运算符创建对象时,就是将函数当作构造器调用。本节内容为设计模式与开发实践第二章笔记。

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.callFunction.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();

我们通过Function.prototype.bind来“包装”func函数,并且传入一个对象context当作参数,这个context对象就是我们想要修正的this对象,即让函数内部的this指向这个对象。

3.借用其他对象的方法

我们知道,杜鹃即不会筑巢,也不会孵雏,而是把自己的蛋寄托给云雀等其他鸟类,让它们代为孵化和养育。在JavaScript中也存在类似的借用现象。

场景一:借用构造函数
通过这种技术,能够实现一些类似继承的效果:

var A = function(name){
 this.name = name;
};

var B = function(){  // 借用A的构造函数
  A.apply(this, arguments); 
};

B.prototype.getName = function(){
  return this.name;
};

var b = new B("baby");
console.log(b.getName());  // 输出:baby

场景二:类数组对象的操作
函数的参数列表arguments是一个类数组对象,虽然它也有下标,但它并非真正的数组,所以也不能像数组一样,进行排序操作或者往集合里添加一个新的元素。这时,可以借用Array.prototype对象上的方法。

比如,想往arguments中添加一个新的元素,可以借用Array.prototype.push:

(function(){
  Array.prototype.push.call(arguments, 3);
  console.log(arguments); // 输出:[1,2,3]
})(1, 2);

想把arguments转成真正的数组的时候,可以借用Array.prototype.slice方法;想截去arguments列表中的头一个元素时,可以借用Array.prototype.shift方法。

PS:本节内容为《JavaScript设计模式与开发实践》第二章 笔记。

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

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

相关文章

  • JavaScript设计模式开发实践 - 观察者模式

    摘要:发布者的状态发生变化时就会通知所有的订阅者,使得它们能够自动更新自己。观察者模式的中心思想就是促进松散耦合,一为时间上的解耦,二为对象之间的解耦。参考设计模式与开发实践第章发布订阅模式设计模式第章第节观察者模式 概述 观察者模式又叫发布 - 订阅模式(Publish/Subscribe),它定义了一种一对多的关系,让多个观察者对象同时监听某一个目标对象(为了方便理解,以下将观察者对象叫...

    xiangzhihong 评论0 收藏0
  • 学习JavaScriptthis,call,apply

    摘要:在全局对象中调用,自然读取的是全局对象的值构造器调用说明作为构造器调用时,指向返回的这个对象。最直观的表现就是,去看一些优秀框架的源代码时,不再是被绕的晕乎乎的。 学习起因: 在之前的JavaScript学习中,this,call,apply总是让我感到迷惑,但是他们的运用又非常的广泛。遂专门花了一天,来弄懂JavaScript的this,call,apply。中途参考的书籍也很多,以...

    wenhai.he 评论0 收藏0
  • javascript对象不完全探索记录02:疯狂打call!给谁打call?打什么call

    摘要:注意该方法的作用和方法类似,只有一个区别,就是方法接受的是若干个参数的列表,而方法接受的是一个包含多个参数的数组。指定的参数列表。返回值返回值是你调用的方法的返回值,若该方法没有返回值,则返回。 温馨提示:作者的爬坑记录,对你等大神完全没有价值,别在我这浪费生命温馨提示-续:打call原本是属于我们偶像宅文化中的专业名词,指的是饭们在看live时在台下配合爱豆演出的节奏喊口号,举例:超...

    Shimmer 评论0 收藏0
  • JavaScript设计模式开发实践》读书笔记

    摘要:订阅模式的一个典型的应用就是后面会写一篇相关的读书笔记。享元模式享元模式的核心思想是对象复用,减少对象数量,减少内存开销。适配器模式对目标函数进行数据参数转化,使其符合目标函数所需要的格式。 设计模式 单例模式 JS的单例模式有别于传统面向对象语言的单例模式,js作为一门无类的语言。使用全局变量的模式来实现单例模式思想。js里面的单例又分为普通单例和惰性单例,惰性单例指的是只有这个实例...

    Panda 评论0 收藏0
  • Javascript this 的一些学习总结02【转自cnblogs的JKhuang】

    摘要:发生这种情况的条件是当引用类型值的对象恰好为活跃对象。总结本文介绍中的使用,更重要的是帮助我们能更好地理解值在全局函数构造函数以及一些特例的情况中值的变化。然而,由于对于来说没有任何意义,因此会隐式转换为全局对象。 接上一篇Javascript this 的一些学习总结02【转自cnblogs的JKhuang】 引用类型以及this的null值 对于前面提及的情形,还有例外的情况,当调...

    suemi 评论0 收藏0

发表评论

0条评论

darryrzhong

|高级讲师

TA的文章

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