Functional Classes
In this section, we'll discuss some of the ways we can use functions to simulate the behavior of classes.
Using Functions
- example.js
- 'use strict';
- function Fruit (type) {
- this.type = type;
- this.color = 'unknown';
- this.getInformation = getFruitInformation;
- }
- function getFruitInformation() {
- return 'This ' + this.type + ' is ' + this.color + '.';
- }
- let lime = new Fruit('Mexican lime');
- console.log(lime.getInformation());
- lime.color = 'green';
- console.log(lime.getInformation());
- class3_ex2.js
- 'use strict';
- function Fruit (type) {
- this.type = type;
- this.color = 'unknown';
- this.getInformation = function() {
- return 'This ' + this.type + ' is ' + this.color + '.';
- }
- }
- let lime = new Fruit('Mexican lime');
- console.log(lime.getInformation());
- lime.color = 'green';
- console.log(lime.getInformation());
The drawback of internally defining the getInformation function is that it recreates that function every time we create a new Fruit object. Fortunately, every function in JavaScript has something called a prototypeproperty, which is empty by default. We can think of a function's prototype as an object blueprint or paradigm; when we add methods and properties to the prototype, they are accessible to all instances of that function. This is especially useful for inheritance (discussed below).
We can add a function to our constructor function's prototype like so:
- class3_ex3.js
- 'use strict';
- function Fruit (type) {
- this.type = type;
- this.color = 'unknown';
- }
- Fruit.prototype.getInformation = function() {
- return 'This ' + this.type + ' is ' + this.color + '.';
- }
- let lime = new Fruit('Mexican lime');
- console.log(lime.getInformation());
- lime.color = 'green';
- console.log(lime.getInformation());
We can use object literals to define an object's properties and functions by initializing a variable with a comma-separated list of property-value pairs enclosed in curly braces.
- class3_ex4.js
- 'use strict';
- let lime = {
- type: 'Mexican lime',
- color: 'green',
- getInformation: function() {
- return 'This ' + this.type + ' is ' + this.color + '.';
- }
- }
- console.log(lime.getInformation());
- lime.color = 'yellow';
- console.log(lime.getInformation());
- console.log(lime.constructor.name)
A singleton class is a design pattern that restricts a class to a single instance. When we assign the value of new function(){...} to a variable, the following happens:
- class3_ex5.js
- 'use strict';
- let lime = new function() {
- this.type = 'Mexican lime';
- this.color = 'green';
- this.getInformation = function() {
- return 'This ' + this.type + ' is ' + this.color + '.';
- };
- }
- console.log(lime.getInformation());
- lime.color = 'yellow';
- console.log(lime.getInformation());
JavaScript classes, introduced in ECMAScript 6, are essentially syntactic sugar over JavaScript's existing prototype-based inheritance that don't actually introduce a new object-oriented inheritance model. This syntax is a means of more simply and clearly creating objects and deal with inheritance.
We define classes in two ways:
Class Declarations
One way to define a class is using a class declaration. To declare a class, we use the class keyword and follow it with the class' name. Ideally, we always write class names in TitleCase.
- class Polygon {
- constructor(height, width) {
- this.height = height;
- this.width = width;
- }
- }
- let p = new Polygon(1, 2);
- console.log('Polygon p:', p);
An important difference between function declarations and class declarations is that function declarations are hoisted (i.e., can be referenced before they're declared) but class declarations are not. This means we must first declare our class before attempting to access (or reference) it; if we fail to do so, our code throws a ReferenceError.
- ex7.js
- try {
- let p = new Polygon(1, 2);
- console.log('Polygon p:', p);
- }
- catch (exception) {
- console.log(exception.name + ': ' + exception.message);
- }
- class Polygon {
- constructor(height, width) {
- this.height = height;
- this.width = width;
- }
- }
- p = new Polygon(1, 2);
- console.log('Polygon p:', p);
Class Expressions
Class expressions are another way to define a class, and they can be either named or unnamed. The name given to a named class expression is local to the class' body.
- ex8.js
- let Polygon = class {
- constructor(height, width) {
- this.height = height;
- this.width = width;
- }
- };
- console.log('Polygon:', Polygon);
- let p = new Polygon(1, 2);
- console.log('p:', p);
- ex9.js
- let Polygon = class Polygon {
- constructor(height, width) {
- this.height = height;
- this.width = width;
- }
- };
- console.log('Polygon:', Polygon);
- let p = new Polygon(1, 2);
- console.log('p:', p);
- ex10.js
- 'use strict';
- class Polygon {
- constructor(height, width) {
- this.height = height;
- this.width = width;
- }
- getArea() {
- return this.height * this.width;
- }
- }
- const square = new Polygon(10, 10);
- console.log(square.getArea());
Static methods are methods relevant to all instances of a class — not just any one instance. These methods receive information from their arguments and not a class instance, which allows us to invoke a class' static methods without creating an instance of the class. In fact, we actually can't call a static method on an instantiated class object (attempting to do so throws a TypeError).
We define a class' static methods using the static keyword. We typically use these methods to create utility functions for applications, as they can't be called on class objects.
- ex11.js
- 'use strict';
- class Point {
- constructor(x, y) {
- this.x = x;
- this.y = y;
- }
- static distance(a, b) {
- const dx = a.x - b.x;
- const dy = a.y - b.y;
- return Math.sqrt(dx * dx + dy * dy);
- }
- }
- const p1 = new Point(5, 5);
- const p2 = new Point(10, 10);
- // The correct way to call a static method
- console.log(Point.distance(p1, p2));
- // Attempt to call a static method on an instance of the class
- try {
- console.log(p1.distance(p1, p2));
- }
- catch (exception) {
- console.log(exception.name + ': ' + exception.message);
- }
In essence, this construct allows us to create an object prototype or class that's an extension of another object prototype or class. A class inheriting from some other class (referred to as a superclass or parent class) is called a subclass (or child class). The subclass inherits the superclass' methods and behaviors, but it can also declare new ones or even override existing ones. (Day5 Inheritance)
Subclassing with the extends Keyword
We use the extends keyword in class declarations or class expressions to create a child class (i.e., subclass).
- ex12.js
- 'use strict';
- class Animal {
- constructor(name) {
- this.name = name;
- }
- speak() {
- console.log(this.name, 'speaks.');
- }
- }
- class Dog extends Animal {
- speak() {
- console.log(this.name, 'barks.');
- }
- }
- let spot = new Dog('Spot');
- spot.speak();
- spot = new Animal('Spot');
- spot.speak();
- ex13.js
- 'use strict';
- function Animal(name) {
- this.name = name;
- }
- Animal.prototype.speak = function() {
- console.log(this.name, 'speaks.');
- }
- class Dog extends Animal {
- speak() {
- console.log(this.name, 'barks.');
- }
- }
- let spot = new Dog('Spot');
- spot.speak();
- spot = new Animal('Spot');
- spot.speak();
We use the super keyword to call functions on an object's parent.
- ex14.js
- 'use strict';
- class Animal {
- constructor(name) {
- this.name = name;
- }
- speak() {
- console.log(this.name, 'speaks.');
- }
- }
- class Dog extends Animal {
- speak() {
- super.speak();
- console.log(this.name, 'barks.');
- }
- }
- let spot = new Dog('Spot');
- spot.speak();
The ability to extend multiple classes from the same superclass (or model multiple object types after the same prototype) is powerful because it provides us with certain implied guarantees about the basic functionality of the subclasses; as extensions of the parent class, subclasses are guaranteed to (at minimum) have the superclass' fields, methods, and functions.
In this example, we call the superclass constructor using super(), override a superclass function (speak()), add an additional property (collarColor), and add a new subclass method (collar()).
- ex15.js
- 'use strict';
- class Animal {
- constructor(name) {
- this.animalType = 'Animal'
- this.name = name;
- }
- type() {
- console.log(this.name, 'is type', this.animalType);
- }
- speak() {
- console.log(this.name, 'speaks.');
- }
- }
- class Dog extends Animal {
- constructor(name, collarColor) {
- super(name);
- this.animalType = 'Dog';
- this.collarColor = collarColor;
- }
- speak() {
- console.log(this.name, 'barks.');
- }
- collar() {
- console.log(this.name, 'has a', this.collarColor, 'collar.');
- }
- }
- let spot = new Dog('Spot', 'red');
- spot.type();
- spot.speak();
- spot.collar();
- // Because the Animal constructor only expects one argument,
- // only the first value passed to it is used
- spot = new Animal('Spot', 'white');
- spot.type();
- spot.speak();
- try {
- spot.collar();
- }
- catch (exception) {
- console.log(exception.name + ': ' + exception.message
- + ' (collar is a method of Dog, not Animal).');
- }
* Day4 problem
* Day 5: Inheritance
沒有留言:
張貼留言