没有this的JavaScript
发布在每天学点javascript2014年3月7日view:3855
在文章任何区域双击击即可给文章添加【评注】!浮到评注点上可以查看详情。

许多JavaScript的难题都是由this引起的。this是一个很让人困惑的东西,这是由于他的语义和其他变量的词法作用域规则有着很大不同。this引用的对象常常和一个函数的词法作用域完全不相关。于是我们常常看到下面的这个小技巧:

function blah(){
    var that = this;
    somethingThatRebindsThings(function(){
        that.whatever();
    });
}  

如果你曾经做过一些JavaScript的开发,那么你一定了解其中的痛苦。想象一下,要是我们不需要this那该多好。这可能吗?听起来像是不太可能的样子!我们现在就来看看究竟能不能达成这个目标。

为什么要使用this?

我们使用this的动机常常和一个面向对象编程范式中的最有用的抽象有关:状态和行为相辅相成。尤其是在对象拥有许多属性和方法的时候。你可能会觉得如果我们不使用this的话,可能会丢掉这个强大的抽象。如果不使用this,这些对象该怎么来引用它的方法和属性呢?也许你已经猜到了答案:闭包。

如果你仔细的考虑一下,闭包就是另一种让状态和对象相辅相成的方法。我们现在就来用一些包含闭包的代码替换传统的包含this的代码。下面我们用原生的JavaScript来实现一个Car类型:

function Car(numberOfDoors){
  this.numberOfDoors = numberOfDoors;
  this.numberOfWheels = 4;

  this.describe = function(){
    return "I have " + this.numberOfWheels +
      " wheels and " + this.numberOfDoors + " doors.";
  }
}

var sportsCar = new Car(2);
console.log( sportsCar.describe() );  

如果使用闭包的话,我们可以用下面的代码来实现同样的目的:

function createCar(numberOfDoors){
  var numberOfWheels = 4;

  function describe(){
    return "I have " + numberOfWheels +
      " wheels and " + numberOfDoors + " doors.";
  }

  return {
    describe: describe
  };
}

var suv = createCar(4);
console.log( suv.describe() );

在上面的代码中,我们通过一个叫做createCar的构造器函数来实现了我们的目的。这个构造器函数定义了Car类型的所有状态和方法,同时返回了一个对象,它只暴露出一个公共行为。在这个构造器函数中定义的所有东西在外部都无法被访问到,但是由于闭包,构造器函数中定义的所有东西都可以相互访问。每次调用这个构造器函数都会创建一个新的闭包,同属创建一个新的小包裹,它包含着状态和行为。

继承

在继承中又是什么情况呢?通常来说,原型继承意味着使用this。但是我们现在不想使用this,因此我们来用另外一种方法来实现继承:

function createMiniVan(capacity){
  var car = createCar(4);
  car.capacity = function(){
    return "I have room for " + capacity + " passengers.";
  };
  return car;
}

var miniVan = createMiniVan(7);
console.log( miniVan.describe() );
console.log( miniVan.capacity() );  

在上面的例子中我创造了一个新的MiniVan类型,它从Car类型继承了所有的共有方法,同时添加了capacity方法。这样的做法和其他语言例如CLOS和Ruby中使用的混入类的概念很相似。这种方式的一个潜在的缺点是它不允许子类或者超类去访问内在的状态和行为 – 换句话说,我们在这种方法中看不到“保护”的概念。

聚合

下面我们使用聚合来为一个类型添加一个新的行为:

function createOdometer(){
  var mileage = 0;

  function increment(numberOfMiles){ mileage += numberOfMiles; }
  function report(){ return mileage; }

  return {
    increment: increment,
    report: report
  }
}

function createCarWithOdometer(numberOfDoors){
  var odometer = createOdometer();
  var car = createCar(numberOfDoors);

  car.drive = function(numberOfMiles){
    odometer.increment(numberOfMiles);
  }

  car.mileage = function(){
    return "car has driven " + odometer.report() + " miles";
  }

  return car;
}

在createCarWithOdometer构造器函数中,我们创建了一个里程表,然后使用里程表的功能来实现一些额外的方法。接着,我们创建了一个基本的car实例,并且将这个新行为混合到这个实例中。我们最后得到的是一个新的类型,它使用odometer的功能来扩展了Car类型。上面的代码完全没有使用原型继承,或者this。

真的有用吗?

这种方法之所以是有用的,有以下几个原因:

  1. 首先,它让我们避免了在使用this时遇到的混乱的情形。
  2. 在构造器函数内将我们的类型和一个临时的类型聚合起来是一种好方法。它让我们可以使用多重继承的有用的部分,同时也去除了与之相关的依赖事宜。
  3. 最后,最大的好处在于我们可以为每种类型提供可良好控制的API,所有的非公开功能都安全的隐藏在构造器函数的闭包内部。这非常有利于维护一个巨大的代码库。

本文译自Javascript without the this,原文地址http://programming.oreilly.com/2014/03/javascript-without-the-this.html

如果你觉得本文对你有帮助,请为我提供赞助https://me.alipay.com/jabez128

评论
发表评论
5年前

@ucdream 一般来说还是会直接用this,这个只是提供一种新思路,具体问题具体解决嘛

5年前

this确实会带来一些问题,特别是多层继承,出错以后还较难调试。不用this的这种方式,我看到两种不利之处: 1.如果子类覆盖父类的方法,在父类定义的方法里就不能访问子类的实现;2. 运行每次构造函数都会构造新的闭包,跟在构造函数里往this上加方法是一个道理吧,这也是为了避免多份拷贝而使用prototype

WRITTEN BY
张小俊128
Intern in Baidu mobile search department。认真工作,努力钻研,期待未来更多可能。
TA的新浪微博
PUBLISHED IN
每天学点javascript

javascript进阶级教程,循序渐进掌握javascript

我的收藏