In earlier chapters, we met JavaScript's classes. We mentioned that classes are syntactic sugar on top of JavaScript's built-in prototype system. In this chapter, we'll learn about prototypal inheritance and how ES6 classes map to the constructor/prototype pattern.
Prototypal inheritance is a core feature in JavaScript. Objects can inherit properties and methods directly from other objects, known as prototypes, rather than from classes. This approach differs from the classical inheritance model used in most other OO languages. It provides more flexible and dynamic object creation and modification, enabling objects to share behavior by referencing a prototype object.
Prototype objects may contain both methods and properties. However, the vast majority of prototypes only contain methods. To simplify our language, we won't mention the possibility of ordinary properties in prototype objects. Nevertheless, you should try to remember that prototypes may contain ordinary properties.
Note that most JavaScript developers routinely use classes in new code. Nevertheless, you should still understand how classes work "under the hood" with JavaScript's prototype system.
The key to understanding prototypal inheritance begins with understanding prototypes (or, sometimes, prototype objects), of which there are two kinds: function prototypes and object prototypes. They are closely related but are different in how they are used.
Most JavaScript functions and classes have a property named prototype
. This property usually references an object that is called the function prototype. For example, consider the following code:
class Cat {
constructor(name, color) {
this.name = name;
this.color = color;
}
whoAmI() {
console.log(`My name is ${this.name}.`,
`\nI am a ${this.color} cat.`);
}
}
When JavaScript encounters the class definition in this code, it creates a Cat
function called the constructor function which, confusingly, is not the same as the constructor
method. It then creates an object that contains the constructor
and whoAmI
methods abd assigns the object to the Cat.prototype
property. This object is referred to as the function prototype for the Cat
class.
All JavaScript objects have an object prototype. The object prototype for an object is an object that contains the methods that an object inherits. For example, consider the following code:
class Cat {
constructor(name, color) {
this.name = name;
this.color = color;
}
whoAmI() {
console.log(`My name is ${this.name}.`,
`\nI am a ${this.color} cat.`);
}
}
//highlight
let cheddar = new Cat('Cheddar', 'ginger');
let cheddarProto = Object.getPrototypeOf(cheddar);
console.log(Object.getOwnPropertyNames(cheddarProto));
// ['constructor', 'whoAmI'];
cheddar.whoAmI(); // My name is Cheddar.
// I am a ginger cat.
//endhighlight
In this code, we've defined a Cat
class, just as we did in the previous subsection. This time, we've also created an instance named cheddar
.
Then, on line 15, we've called Object.getPrototypeOf
to retrieve cheddar
's object prototype. On line 17, we print a list of the methods defined by cheddar
's object prototype; the list contains the names of the two methods defined by the Cat
class: constructor
and whoAmI
.
Object.getPrototypeOf
returns the object prototype of an object.
Object.getOwnPropertyNames
returns an array with the names of all properties defined by the object given as an argument.
When we call a constructor function with the new
keyword, JavaScript takes the function prototype of the constructor (Cat.prototype
here) and assigns it as the object prototype of the object created by the constructor. Thus, an object prototype is, by default, identical to the function prototype of the object's constructor function.
Interestingly, function prototypes also have object prototypes. By default, the object prototype of a function prototype is the function prototype of the superclass.
class Animal {
constructor(type) {
this.type = type;
}
eat() {
console.log("I am eating.");
}
}
class Cat extends Animal {
constructor(name, color) {
super();
this.name = name;
this.color = color;
}
whoAmI() {
console.log(`My name is ${this.name}.`,
`\nI am a ${this.color} cat.`);
}
}
let cheddar = new Cat('Cheddar', 'ginger');
let cheddarProto = Object.getPrototypeOf(cheddar);
let cheddarProto2 = Object.getPrototypeOf(cheddarProto);
console.log(Object.getOwnPropertyNames(cheddarProto));
// ['constructor', 'whoAmI'];
console.log(Object.getOwnPropertyNames(cheddarProto2));
// ['constructor', 'eat'];
console.log(cheddarProto2 === Animal.prototype); // true
cheddar.whoAmI(); // My name is Cheddar.
// I am a ginger cat.
cheddar.eat(); // I am eating.
With almost all objects, you can follow this "chain" of object prototypes all the way back to a prototype object called Object.prototype
. This chain is called the prototype chain; we'll learn more about it soon. Since Object.prototype
's object prototype is null
, every JavaScript prototype chain ultimately ends with null
.
The following diagram depicts the structure of the above code. In particular, it shows that cheddar
's object prototype is the same as the Cat
constructor's function prototype. Likewise, the object prototype of Cat.prototype
is the same object as Animal.prototype
, and the object prototype of Animal.prototype
is the object given by Object.prototype
. Finally, the end of the chain is null
.
+----------------+
| cheddar |
|--------*-------+
| getPrototypeOf
+---------*--------+ |
| Cat | |
+------------------+ +--------*-------+
| Cat.prototype | -----> | constructor |
+------------------+ | whoAmI |
+--------*-------+
| getPrototypeOf
+------------------+ |
| Animal | |
+------------------+ +--------*-------+
| Animal.prototype | -----> | constructor |
+------------------+ | eat |
+--------*-------+
| getPrototypeOf
+------------------+ |
| Object | |
+------------------+ +--------*-------+
| Object.prototype | -----> | hasOwnProperty |
+--------*---------+ | isPrototypeOf |
| etc... |
+--------*-------+
| getPrototypeOf
|
+--*---+
| null |
+------+
All objects created from an object literal are instances of the Object
class.Hence, their object prototype is the function prototype of Object
, e.g., Object.prototype
:
let obj = { // Object literal
foo: 42,
};
let objProto = Object.getPrototypeOf(obj);
console.log(objProto === Object.prototype); // true
Since object factories use object literals, they also create objects that have Object.prototype
as their object prototype:
function createObj() {
return {
foo: 42,
};
}
let obj = createObj();
let objProto = Object.getPrototypeOf(obj);
console.log(objProto === Object.prototype); // true
It's also worth noting that objects created by new Object()
also have Object.prototype
as their prototype:
let obj = new Object();
let objProto = Object.getPrototypeOf(obj);
console.log(objProto === Object.prototype); // true
In JavaScript, every object has a hidden property called [[Prototype]]
that points to its object prototype or null
.
While you can't refer to [[Prototype]]
directly, you can access the object prototype with the Object.getPrototypeOf
method, as shown several times above. For example:
class Animal {}
class Cat extends Animal {}
let cat = new Cat();
console.log(Object.getPrototypeOf(cat)); // Animal {}
In addition to accessing the object prototype, you can give an object an entirely new prototype with the Object.setPrototypeOf
method.
class Animal {}
class Cat extends Animal {}
let cat = new Cat();
let catProto = Object.getPrototypeOf(cat);
console.log(catProto === Cat.prototype); // true
//highlight
let myProto = {
meow() {
console.log("Meow!");
}
};
Object.setPrototypeOf(cat, myProto);
catProto = Object.getPrototypeOf(cat);
cat.meow(); // Meow!
console.log(catProto === Cat.prototype); // false
console.log(catProto === myProto); // true
//endhighlight
Replacing an object's prototype with a new prototype may result in code that runs more slowly than it would otherwise. Changing object prototypes may also result in code that is difficult to understand or maintain.
Functions and classes are also objects. That means they have object prototypes in addition to the function prototypes we met earlier.
As we've already discussed, function prototypes are used when creating new objects from the function or class; the function prototype gets assigned as the object prototype for the newly created object. Object prototypes are used by objects to define where JavaScript should look for instance methods.
With function and class objects, the object prototypes define where JavaScript should look for static methods. For instance, consider the following code:
class Foo {
static identity() {
console.log('This is Foo.identity()');
}
}
class Bar {
static whoAmI() {
console.log('This is Bar.whoAmI()');
}
}
// Foo and Bar initially have Function.prototype as their
// object prototypes.
// The names `FooProto` and `BarProto` are not consistent
// with JS conventions. We've done this intentionally for
// clarity.
let FooProto = Object.getPrototypeOf(Foo);
console.log(FooProto === Function.prototype); // true
let BarProto = Object.getPrototypeOf(Bar);
console.log(BarProto === Function.prototype); // true
// Let's change Bar's object prototype to Foo, which lets
// Bar inherit Foo's static methods. However, Bar does
// *not* inherit Foo's instance methods.
Object.setPrototypeOf(Bar, Foo);
// Bar can access both Bar.whoAmI and Foo.identity
Bar.whoAmI(); // This is Bar.whoAmI()
Bar.identity(); // This is Foo.identity()
The object prototypes for functions and classes ultimately chain to Function.prototype
, which has some useful static methods such as apply
, call
, and bind
. We discuss these in the Core curriculum.
Arrow functions do not have function prototypes. However, they do have object prototypes.
let foo = () => console.log('foo');
let fooProto = Object.getPrototypeOf(foo);
console.log(foo.prototype); // undefined
console.log(fooProto); // {}
console.log(fooProto === Function.prototype); // true
As we've seen, Object.getPrototypeOf
returns the object prototype of an object. In this case, the object is a function, and the function's object prototype is Function.prototype
. That happens to be a function prototype, but it is not the function prototype for foo
. The function prototype, which would be referenced by foo.prototype
if it existed, is undefined
since foo
doesn't have a prototype
property.
__proto__
PropertyYou will probably encounter code that uses the __proto__
property to access and reassign the object prototype:
class Animal {}
class Cat extends Animal {}
let cat = new Cat();
console.log(cat.__proto__); // Animal {}
class Foo {}
cat.__proto__ = Foo;
console.log(cat.__proto__); // [class Foo]
While this code works, __proto__
is officially deprecated. You should not use it in production code. While it's reasonably safe to use when testing or debugging, you should use Object.getPrototypeOf
and Object.setPrototypeOf
instead.
A function prototype can be accessed or changed by using the prototype
property on the function or class:
class Animal {}
Animal.prototype.foo = function() {
console.log('this is foo');
};
Animal.prototype.bar = function() {
console.log('this is bar');
};
console.log(Animal.prototype);
// { foo: [Function (anonymous)],
// bar: [Function (anonymous)] }
function Vehicle() {}
Vehicle.prototype.drive = function() {
console.log('off we go');
};
console.log(Vehicle.prototype);
// { drive: [Function (anonymous)] }
When JavaScript looks for a method associated with an instance object, JavaScript first looks for it in the object itself. If it doesn't find the method, it looks at the object prototype next. Thus, we can call the foo
, bar
, and drive
methods above as follows:
let animal = new Animal();
let vehicle = new Vehicle();
animal.foo(); // this is foo
animal.bar(); // this is bar
vehicle.drive(); // off we go
As we saw earlier, when you attempt to access a method, JavaScript first looks at the calling object. If it doesn't find the method, it searches the object prototype of the calling object. If JavaScript finds the method in either location, JavaScript will use that method.
However, JavaScript won't always find the method in one of these locations. When it doesn't find it, JavaScript searches the remainder of the object's prototype chain. It stops searching when it sees the method or reaches the end of the chain (null
).
The prototype chain begins by looking for the requested method in the calling object. If JavaScript finds the method there, it stops searching. If it doesn't find the method, JavaScript next looks in the object prototype for the calling object. If JavaScript finds the method in the prototype, it stops searching.
If the method still isn't found, JavaScript searches the object prototype of the inherited object, if any. JavaScript will continue to search inherited object prototypes until it finds the method or an object prototype of null
.
For example, consider the following classes:
class Mammal {
foo() { console.log("Mammal.foo"); }
}
class Whale extends Mammal {
foo() { console.log("Whale.foo"); }
}
class Beluga extends Whale {}
let beluga = new Beluga();
beluga.foo(); // Whale.foo
beluga.bar(); // TypeError: beluga.bar is not a function
In this code, the lookup process for foo
works like so:
beluga
object for foo
. It does not find it.
beluga
object (Beluga.prototype
). JavaScript does not find foo
in Beluga.prototype
, so the search continues.
Whale
class (Whale.prototype
). Since JavaScript finds foo
in Whale.prototype
, it calls that method and prints Whale.foo
. Mammal.foo
never gets called.
The lookup process for bar
works like this:
beluga
object for bar
but does not find it.
beluga
object (Beluga.prototype
). Again, it doesn't find bar
.
Whale
class (Whale.prototype
). Again, it doesn't find bar
.
Mammal
. It finds Mammal.prototype
but can't find bar
.
Object.prototype
. Since Object.prototype
is null
, it does not have a bar
function. JavaScript then raises an error.
In traditional OO languages, classes are blueprints for objects. Classes can inherit from other classes, which enables code reuse without copying methods all over the place. A class defines the structure and behaviors (methods) objects instantiated from it should have. A subclass inherits from a superclass and can add or override methods. The superclass's methods are available to instances of the subclass unless overridden.
JavaScript has a different way of doing things. In particular, objects inherit directly from other objects. There are no actual classes in pure prototypal inheritance. In fact, JavaScript doesn't have proper classes; the class
keyword is syntactic sugar that maps to a constructor function and a function prototype.
Prototypal inheritance, as implemented in JavaScript, allows any object to inherit from any other. It is dynamic, meaning objects can extend or change their prototype at runtime. Since the prototypes are all linked via pointers, prototypal inheritance can also use memory more efficiently.
In classical OO languages, inheritance is more structured and strict. It is achieved through a class hierarchy; subclasses inherit from superclasses. Class-based inheritance is static; relationships between classes and instances are established at compile time and cannot usually be altered dynamically. The lack of flexibility, however, makes classical OO more intuitive to most developers. There's no need to understand prototypes.
It's time to see what prototypal inheritance looks like in all its convoluted glory. Consider the following class
-based code:
class Animal {
constructor(name) {
this.name = name;
}
eat() {
console.log(`${this.name} is eating.`);
}
}
class Mammal extends Animal {
constructor(name, hasFur) {
super(name);
this.hasFur = hasFur;
}
sleep() {
console.log(`${this.name} is sleeping.`);
}
}
class Dog extends Mammal {
constructor(name, hasFur, breed) {
super(name, hasFur);
this.breed = breed;
}
bark() {
console.log(`${this.name} the ${this.breed} ` +
'is barking.');
}
}
let myDog = new Dog('Rex', true, 'German Shepherd');
myDog.eat(); // Rex is eating.
myDog.sleep(); // Rex is sleeping.
myDog.bark(); // Rex the German Shepherd is barking.
Let's rewrite this code using the constructor/prototype pattern. To begin, we'll need constructor functions to replace each of the three classes (for now, we'll ignore the inheritance aspect of this code for now). We'll also need to add each method (other than constructor
) to the function prototype for each type.
//highlight
function Animal(name) {
this.name = name;
}
Animal.prototype.eat = function() {
console.log(`${this.name} is eating.`);
};
function Mammal(name, hasFur) { // extends Animal
// super(name) - we meed to simulate super()
this.hasFur = hasFur;
}
Mammal.prototype.sleep = function() {
console.log(`${this.name} is sleeping.`);
};
function Dog(name, hasFur, breed) { // extends Mammal
// super(name, hasFur) - we need to simulate super()
this.breed = breed;
}
Dog.prototype.bark = function() {
console.log(`${this.name} the ${this.breed} is barking.`);
}
//endhighlight
let myDog = new Dog('Rex', true, 'German Shepherd');
myDog.eat(); // Rex is eating.
myDog.sleep(); // Rex is sleeping.
myDog.bark(); // Rex the German Shepherd is barking.
This code has two problems right now. First, we need to tell JavaScript that Dog
inherits the prototype of Mammal
, and Mammal
inherits the prototype of Animal
. Second, we must tell the constructor functions how to call the inherited constructor function. Neither problem has an intuitive solution. There is no extends
keyword we can use, nor is there a super()
function we can call.
The solution for not having an extends
keyword is to use Object.create
. It creates a new object that uses another object as its object prototype. For instance:
let obj = Object.create({
foo() {},
bar() {},
});
console.log(Object.getPrototypeOf(obj));
// { foo: [Function: foo], bar: [Function: bar] }
In the above code, we create a new object obj
whose object prototype is given by the object passed as the argument for Object.create
. Thus, when we log the object prototype of obj
, we can see both the foo
and bar
methods.
This pattern is most useful with constructor functions. For instance, to have the Mammal
constructor inherit the function prototype of Animal
, we just use Object.create
to create a function prototype for Mammal
:
// Code omitted for brevity
function Mammal(name, hasFur) { // extends Animal
// super(name) - we need to simulate super()
this.hasFur = hasFur;
}
//highlight
Mammal.prototype = Object.create(Animal.prototype);
let myMammal = new Mammal('Flipper', false);
console.log(myMammal instanceof Mammal); // true
console.log(myMammal instanceof Animal); // true
//endhighlight
// Code omitted for brevity
Using this pattern, we can now have the Dog
type inherit from the Mammal
type:
// Code omitted for brevity
function Dog(name, hasFur, breed) { // extends Mammal
// super(name, hasFur) - we need to simulate super()
this.breed = breed;
}
Dog.prototype = Object.create(Mammal.prototype);
let myDog = new Dog('Rex', true, 'German Shepherd');
//highlight
console.log(myDog instanceof Dog); // true
console.log(myDog instanceof Mammal); // true
console.log(myDog instanceof Animal); // true
//endhighlight
// Code omitted for brevity
At this point, our inheritance hierarchy is almost working, but not quite:
// Code omitted for brevity
let myDog = new Dog('Rex', true, 'German Shepherd');
console.log(myDog instanceof Dog); // true
console.log(myDog instanceof Mammal); // true
console.log(myDog instanceof Animal); // true
myDog.eat(); // undefined is eating.
myDog.sleep(); // undefined is sleeping.
myDog.bark();
// undefined the German Shepherd is barking.
Since we haven't used super
(and can't in this code), the methods in the myDog
object are unable to determine the dog's name. To fix that, we must explicitly call the Animal
and Mammal
constructors using Function.prototype.call
with this
as the first argument.
function Animal(name) {
this.name = name;
}
Animal.prototype.eat = function() {
console.log(`${this.name} is eating.`);
};
function Mammal(name, hasFur) { // extends Animal
//highlight
Animal.call(this, name);
//endhighlight
this.hasFur = hasFur;
}
Mammal.prototype = Object.create(Animal.prototype);
Mammal.prototype.sleep = function() {
console.log(`${this.name} is sleeping.`);
};
function Dog(name, hasFur, breed) { // extends Mammal
//highlight
Mammal.call(this, name, hasFur);
//endhighlight
this.breed = breed;
}
Dog.prototype = Object.create(Mammal.prototype);
Dog.prototype.bark = function() {
console.log(`${this.name} the ${this.breed} ` +
'is barking.');
}
let myDog = new Dog('Rex', true, 'German Shepherd');
myDog.eat(); // Rex is eating.
myDog.sleep(); // Rex is sleeping.
myDog.bark(); // Rex the German Shepherd is barking.
One item of note here is that the first argument to the call
method should almost always be this
. If you're creating a Dog
instance, then the this
argument will reference the Dog
instance. Thus, all of the constructors will see the Dog
instance as the calling object.
Everything is working now. However, there's one more step. By default, a constructor function's prototype.constructor
property is a reference back to the constructor function. When you reassign the prototype
property, the constructor
subproperty's value no longer references the correct constructor. Some JavaScript libraries may break if prototype.constructor
is not set correctly. To ensure it doesn't become a problem, you should assign the prototype.constructor
property for each subtype to refer to the subtype's constructor function:
function Animal(name) {
this.name = name;
}
Animal.prototype.eat = function() {
console.log(`${this.name} is eating.`);
};
function Mammal(name, hasFur) { // extends Animal
Animal.call(this, name); // emulating `super`
this.hasFur = hasFur;
}
Mammal.prototype = Object.create(Animal.prototype);
//highlight
Mammal.prototype.constructor = Mammal;
//endhighlight
Mammal.prototype.sleep = function() {
console.log(`${this.name} is sleeping.`);
};
function Dog(name, hasFur, breed) { // extends Mammal
Mammal.call(this, name, hasFur); // emulating super
this.breed = breed;
}
Dog.prototype = Object.create(Mammal.prototype);
//highlight
Dog.prototype.constructor = Dog;
//endhighlight
Dog.prototype.bark = function() {
console.log(`${this.name} the ${this.breed} ` +
'is barking.');
}
let myDog = new Dog('Rex', true, 'German Shepherd');
myDog.eat(); // Rex is eating.
myDog.sleep(); // Rex is sleeping.
myDog.bark(); // Rex the German Shepherd is barking.
In most cases, it doesn't matter whether you reset the prototype.constructor
property. However, you should do so, just in case.
And there it is in all its convoluted glory: the constructor/prototype pattern. Before ES6, JavaScript developers had to write OO code using this pattern. The complexity of inheritance with this pattern is a good reason to prefer the class
syntax.
To recap, there are three significant steps here to have a subtype inherit from a supertype when using the constructor/prototype pattern:
Object.create
with an argument referencing the supertype's function prototype, and assign it to the subtype constructor's prototype
property.
prototype.constructor
property to the subtype's constructor function.
Function.prototype.call
. You should pass this
as the first argument to call
followed by the arguments for the supertype's constructor.
This chapter delves into prototypal inheritance, a key JavaScript feature that allows objects to inherit methods and other properties from prototype objects. Unlike classical inheritance, where classes inherit from other classes, JavaScript objects inherit directly from other objects through prototype chains. If JavaScript fails to find a method on an object, it searches the prototype chain until it finds the method or reaches a null
prototype. The chapter explains this using examples and highlights differences with classical inheritance. It also introduces the constructor/prototype pattern, showing how classes simplify the traditional prototypal setup by acting as syntactic sugar, making code easier to write and understand.
This exercise re-examines exercise 1 from the previous chapter. In that exercise, you wrote a class to instantiate smartphone objects. In this exercise, we'll rewrite that solution using the constructor/prototype pattern.
Using the constructor/prototype pattern, create a type that represents smartphones. Each smartphone should have a brand, model, and release year. Add methods that display the smartphone's information and check its battery level. Create objects that represent the following two smartphones:
Brand | Model | Release Year |
---|---|---|
Apple | iPhone 12 | 2020 |
Samsung | Galaxy S21 | 2021 |
function Smartphone(brand, model, releaseYear) {
this.brand = brand;
this.model = model;
this.releaseYear = releaseYear;
}
Smartphone.prototype.checkBatteryLevel = function() {
return `${this.brand} ${this.model} has 75% ` +
'battery remaining.';
};
Smartphone.prototype.displayInfo = function() {
return `${this.releaseYear} ${this.brand} ` +
`${this.model}`;
};
let iphone12 = new Smartphone('Apple',
'iPhone 12',
2020);
let galaxyS21 = new Smartphone('Samsung',
'Galaxy S21',
2021);
console.log(iphone12.checkBatteryLevel());
// Apple iPhone 12 has 75% battery remaining.
console.log(iphone12.displayInfo());
// 2020 Apple iPhone 12
console.log(galaxyS21.checkBatteryLevel());
// Samsung Galaxy S21 has 75% battery remaining.
console.log(galaxyS21.displayInfo());
// 2021 Samsung Galaxy S21
This exercise re-examines exercise 3 from the previous chapter. In that exercise, you wrote a class hierarchy to represent vehicles of various types. In this exercise, we'll rewrite that solution using the constructor/prototype pattern.
Using the constructor/prototype pattern, create some types that represent vehicles, including cars, boats, and planes as specific kinds of vehicles. All vehicles should be able to accelerate and decelerate. Cars should be able to honk, boats should be able to drop anchor, and planes should be able to take off and land. Test your code.
function Vehicle(color, weight) {
this.color = color;
this.weight = weight;
}
Vehicle.prototype.accelerate = function() {
console.log("Accelerate");
};
Vehicle.prototype.decelerate = function() {
console.log("Decelerate");
};
function Car(color, weight, licenseNumber) {
Vehicle.call(this, color, weight);
this.licenseNumber = licenseNumber;
}
Car.prototype = Object.create(Vehicle.prototype);
Car.prototype.constructor = Car;
Car.prototype.honk = function() {
console.log("Honk");
};
function Boat(color, weight, homePort) {
Vehicle.call(this, color, weight);
this.homePort = homePort;
}
Boat.prototype = Object.create(Vehicle.prototype);
Boat.prototype.constructor = Boat;
Boat.prototype.dropAnchor = function() {
console.log("Drop anchor");
};
function Plane(color, weight, airline) {
Vehicle.call(this, color, weight);
this.airline = airline;
}
Plane.prototype = Object.create(Vehicle.prototype);
Plane.prototype.constructor = Plane;
Plane.prototype.takeOff = function() {
console.log("Take off");
};
Plane.prototype.land = function() {
console.log("Land");
};
let car = new Car('red', 3300, 'BXY334');
car.accelerate(); // Accelerate
car.honk(); // Honk
car.decelerate(); // Decelerate
console.log(car.color, car.weight, car.licenseNumber);
// red 3300 BXY334
let boat = new Boat('yellow', 12000, 'Bahamas');
boat.accelerate(); // Accelerate
boat.decelerate(); // Decelerate
boat.dropAnchor(); // Drop anchor
console.log(boat.color, boat.weight, boat.homePort);
// yellow 12000 Bahamas
let plane = new Plane('blue', 83000, 'Southwest');
plane.accelerate(); // Accelerate
plane.takeOff(); // Take off
plane.land(); // Land
plane.decelerate(); // Decelerate
console.log(plane.color, plane.weight, plane.airline);
// blue 83000 Southwest