JavaScript设想形式与开辟实践 | 02 - this、call和apply

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设想形式与开辟实践》第二章 笔记。

    原文作者:small2
    原文地址: https://segmentfault.com/a/1190000003959359
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞