• 欢迎访问天天编码网站,Java技术、技术书单、开发工具,欢迎加入天天编码
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏天天编码吧
  • 我们的淘宝店铺已经开张了哦,传送门:https://shop145764801.taobao.com/

7.7 Class patterns(类模式)

JS 教程 tiantian 366次浏览 0个评论 扫描二维码

在 JavaScript中,存在一个特殊的语法结构和关键词class。但是在学习它们之前,我们应该明白,那个名为class(类)的概念来源于面向对象编程的理论。关于什么是面向对象编程,我就不在此讲解了,而且它是语言独立的技术。

在 JavaScript 中,存在多个著名的编程模式,可以在不使用class关键字的情况下实现class。本章节我们就将来学习它们。

那个class构造将会在下个章节中讲解,但在 JavaScript 中,它仅仅是一个“语法糖”,而且仅是我们本章节将学习的多种模式中的一个模式扩展。

Functional class pattern(函数类模式)

根据定义,下面所示的那个构造器函数可以被认为是一个“class”:

function User(name) {
  this.sayHi = function() {
    alert(name);
  };
}

let user = new User("John");
user.sayHi(); // John

它遵守定义的所有要求:

  1. 它是一个创建对象的”编程代码模板“(可以使用new进行调用).
  2. 它为状态提供了初始值(从参数获得name).
  3. 它提供了方法(sayHi).

这个模式为 functional class pattern(函数类模式).

在那个函数类模式,本地变量和内嵌函数位于User,它们并没有赋值给this,代码外部无法访问它们,内部却是可以访问它们。

所以,我们可以很容易地添加内部函数和变量,比如calcAge()

function User(name, birthday) {
  // only visible from other methods inside User
  function calcAge() {
    return new Date().getFullYear() - birthday.getFullYear();
  }

  this.sayHi = function() {
    alert(`${name}, age:${calcAge()}`);
  };
}

let user = new User("John", new Date(2000, 0, 1));
user.sayHi(); // John, age:17

在上述代码中,对于对象而言,变量namebirthday和函数calcAge()都是内部,私有。它们仅在函数内部可被访问。

另一方面,sayHi是处于外部,功能的方法。那个创建user的外部代码可以访问它。

通过这个方式,我们可以对外部代码隐藏内存实现细节和辅助方法。只有那些被赋值给this的变量对于外部代码才是可见的。

Factory class pattern(工厂类模式)

我们可以在不使用new的情况下创建一个class(类)。

function User(name, birthday) {
  // only visible from other methods inside User
  function calcAge() {
    return new Date().getFullYear() - birthday.getFullYear();
  }

  return {
    sayHi() {
      alert(`${name}, age:${calcAge()}`);
    }
  };
}

let user = User("John", new Date(2000, 0, 1));
user.sayHi(); // John, age:17

正如我们所见,那个函数User返回一个对象,该对象具有公共的属性和方法。那个函数的唯一优势在于我们可以省略new:直接写let user = User(...),而不是let User = new User(...)。在其他方面,它基本上与上面的函数类模式一样。

Prototype-based classes(基于原型的类)

基于原型的类可能是最重要,最好的方法。上述的函数类模式和工厂类模式在实践中很少被使用。

很快你就会发现原因。

下面是一个使用原型模式重写的类:

function User(name, birthday) {
  this._name = name;
  this._birthday = birthday;
}

User.prototype._calcAge = function() {
  return new Date().getFullYear() - this._birthday.getFullYear();
};

User.prototype.sayHi = function() {
  alert(`${this._name}, age:${this._calcAge()}`);
};

let user = new User("John", new Date(2000, 0, 1));
user.sayHi(); // John, age:17

代码结构:

  • 那个构造器User仅仅只初始化当前对象的状态。
  • 方法都添加到User.prototype中。

正如我们所见,在语法上,方法不是位于function User中,它们没有共享一个同一个词法环境。如果我们在function User中定义变量,那么它们对于方法是不可见的。

所以,JavaScript 中存在一个著名的约定:内部属性和方法都是以下划线_开头。比如_name_calcAge()。技术上,那个仅仅是一个约定,外部代码仍然可以访问它们。但是绝大多数的开发者都遵守那个_的约定和意义,在外部代码中不直接访问使用下划线开头的变量和方法。

下面是该模式相对比函数类模式的优势:

  • 在函数类模式,每个对象都具有每个方法的它自己的版本。
  • 在原型类模式,所有方法都是User.prototype中,它们在所有的对象间共享。对象本身只存储数据。

所以,原型模式在内存使用上更加高效。

但是,不仅仅如此。原型允许我们以一种非常高效的方式来实现继承。内建的 JavaScript 对象都是用了原型。 此外,还存在一个特殊的语法:class为它们提供了更加优美的语法。所以,让我们来继续学习它们。

Prototype-based inheritance for classes(基于原型的继承)

假设我们具有两个基于原型的类。

function Rabbit(name) {
  this.name = name;
}

Rabbit.prototype.jump = function() {
  alert(`${this.name} jumps!`);
};

let rabbit = new Rabbit("My rabbit");

7.7 Class patterns(类模式)

function Animal(name) {
  this.name = name;
}

Animal.prototype.eat = function() {
  alert(`${this.name} eats.`);
};

let animal = new Animal("My animal");

7.7 Class patterns(类模式)

现在,它们是完全独立的。

但是,如果我们希望Rabbit去继承Animal。换句话说,rabbits 应该是基于 animals的,可以访问Animal的方法,并且具有它自己的方法。

这对于语言的原型而言,意味着什么?

现在,rabbit对象的方法位于Rabbit.prototype里面。我们希望rabbit使用Animal.prototype做为一个备份,在目标方法不存在于Rabbit.prototype时查找Animal.prototype

所有,整个的原型链应该为:rabbit -> Rabbit.prototype -> Animal.prototype

7.7 Class patterns(类模式)

实现代码如下:

// Same Animal as before
function Animal(name) {
  this.name = name;
}

// All animals can eat, right?
Animal.prototype.eat = function() {
  alert(`${this.name} eats.`);
};

// Same Rabbit as before
function Rabbit(name) {
  this.name = name;
}

Rabbit.prototype.jump = function() {
  alert(`${this.name} jumps!`);
};

// setup the inheritance chain
Rabbit.prototype.__proto__ = Animal.prototype; // (*)

let rabbit = new Rabbit("White Rabbit");
rabbit.eat(); // rabbits can eat too
rabbit.jump();

上述的代码行(*)会设置原型链。所有那个rabbit会首先在Rabbit.prototype中搜索方法,然后就是在Animal.prototype中查找。从完整性考虑,在目标方法无法在Animal.prototype中查找成功时,它还会在Object.prototype中进行查找,因为Animal.prototype本身是一个普通的简单对象,所有它继承了该原型。

下面是一个原型链的完整图:

7.7 Class patterns(类模式)

那个class的概念来源于面向对象编程。在 JavaScript 中,它经常意味着函数类模式或者原型模式。那个原型模式更加强大和高效,所以建议大家使用该模式。

根据 原型模式定义:

  1. 方法存储在Class.prototype中。
  2. 属性得以继承。

在下一章节,我们将学习class关键词和构造器。它允许以更短的代码来完成原型类,而且还提供了其他的额外优势。

任务


继承的错误

在下列的原型继承链中查找一个错误。错误代码的位置?后果是什么?

function Animal(name) {
  this.name = name;
}

Animal.prototype.walk = function() {
  alert(this.name + ' walks');
};

function Rabbit(name) {
  this.name = name;
}

Rabbit.prototype = Animal.prototype;

Rabbit.prototype.walk = function() {
  alert(this.name + " bounces!");
};

重写原型

那个Clock类是以函数类模式创建,请使用原型模式重写该类。

P.S. 那个时钟在控制台进行输出,请打开控制台查看。


天天编码 , 版权所有丨本文标题:7.7 Class patterns(类模式)
转载请保留页面地址:http://www.tiantianbianma.com/class-patterns.html/
喜欢 (1)
支付宝[多谢打赏]
分享 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址