Skip to main content
 首页 » 编程设计

Javascript面向对象之类模式

2022年07月19日123think

Javascript面向对象(七)——类模式

在面向对象编程里,类是一个为创建对象的可扩展编程代码模板。提供了状态的初始值(成员变量),实现行为(成员函数或方法)。——维基百科

在Javascript中有特定语法结构以及关键字class,但学习之前,我们应该考虑术语“类”是面向对象编程理论。其定义是引用上面内容,且和语言无关。

即使不用class关键字,在Javascript也有几种众所周知的类编程模型。这里我们首先讨论它们。

类结构将在下一章描述,但在Javascript中它是个“语法糖”,我们马上要学习模型中一个的扩展。

函数类模式

根据类的定义,下面代码的构造函数可以视作类:

function User(name) { 
  this.sayHi = function() { 
    alert(name); 
  }; 
} 
 
let user = new User("John"); 
user.sayHi(); // John 

它遵循定义的所有部分:
- 是创建对象的编程代码模板(使用new调用)
- 提供了状态初始值(name,从参数提供)
- 提供了方法(sayHi)

这被称为函数类模型。局部变量和内嵌函数在User里面,没有赋值给this,对内可见,但是不能被外部代码访问。
所以我们能容易增加内部函数和变量,如calAge():

function User(name, birthday) { 
 
  // only visible from other methods inside User 
  function calcAge() { 
    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 

上面代码中,name、birthday和函数calcAge()在内部,是对象私有的,仅仅在对象内部可见。
另外方面,sayHi是外部的,公共方法。外部代码可以访问,如创建的对象user可以访问它。
这种方式能够让外部访问的助手方法隐藏实现细节,仅赋值this的方法成为外部可见的。

工厂类模式

我们能创建类根本不使用new关键字,如下代码:
function User(name, birthday) {
// only visible from other methods inside User
function calcAge() {
new Date().getFullYear() - birthday.getFullYear();
}

  return { 
    sayHi() { 
      alert(name + ', age:' + calcAge()); 
    } 
  }; 
} 
 
let user = User("John", new Date(2000,0,1)); 
user.sayHi(); // John 

我们能看到,函数User返回一个对象,带有公共的方法。这种方式唯一好处就是可以省略new关键字,直接写 let user = User(...) ,代替写let user = new User(...),除此以外,几乎和函数类模式完全一样。

基于原型类模式

基于原型类模式是最重要、最好的方式。实践中函数类模式、工厂类模式很少使用。很快我们会说明原因。
这里我们用原型方式重新写上面代码:

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 

代码结构解释如下:

  • 构造函数User仅初始化当前对象状态。
  • 方法通过原型增加:User.prototype.
    我们看到,方法在词法上不在function User内部,他们不共享一个词法环境。如果我们声明变量在function User内部,那么他们对方法不可见。
    所以,有一个广泛的共识是:内部属性和方法通常使用“”开头,如_name_calcAge()。技术,这仅是一个约定,外部代码仍然能访问他们,但几乎所有的开发者认同“”的意思,外部代码尽力不触碰下划线开头的属性和方法。

这里是原型模式优于函数类模式之处:

  • 在函数模式中,每个对象有自己所有方法的拷贝,示例中的拷贝是:this.sayHi = function(){...},以及其他构造函数中的方法。
  • 在原型模式中,所有方法在User.prototype中,为所有对象共享,对象仅存储数据。

所以,原型模式是更有效的。

不仅如此,原型允许我们使用有效的方式实现继承。内置Javascript对象都使用原型。也有特定的语法机构提供漂亮的语法方式实现“类”,后续详解。

基于原型的类继承

我们有两个基于原型的类Rabbit:

function Rabbit(name) { 
  this.name = name; 
} 
 
Rabbit.prototype.jump = function() { 
  alert(this.name + ' jumps!'); 
}; 
 
let rabbit = new Rabbit("My rabbit"); 

和Animal类:

function Animal(name) { 
  this.name = name; 
} 
 
Animal.prototype.eat = function() { 
  alert(this.name + ' eats.'); 
}; 
 
let animal = new Animal("My animal"); 

现在他们完全独立。

但我们想让Rabbit继承Animal,换句话说,rabbit应该基于animals,能够访问Animal的方法,并可以扩展实现自己的方法。
Javascript语言中原型意味什么?
现在rabbit对象的方法在Rabbit.prototype中,我们想如果访问rabbit对象的某个方法不在Rabbit.prototype中,使用Animal.prototype作为后备。
所以原型链应该是rabbit –> Rabbit.prototype –>Animal.prototype. 如图所示:

代码实现:

// 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是一般的普通对象,所以继承自Object。
完整图示如下:

总结

属于“类”来自面向对象编程,在Javascript中,通常意味着函数类模式或原型模式。原型模式更强大且高效,所以推荐使用。
根据原型模式规范:
- 方法存储在Class.prototype中。
- 原型继承自其他原型。

下一章,我们讨论class关键字和其结构,是提供了更简洁的方式实现原型模式,并且还有其他优势。


本文参考链接:https://blog.csdn.net/neweastsun/article/details/70551592