JavaScript prototypes: instance-level vs prototype-level functions
A prototype is an original form or a typical example, a basis or a standard.
Every object has a hidden internal [[Prototype]].
Put simply, objects and arrays are both linked through the prototype chain, so Array inherits Object's methods. That is why an array can use array methods like concat(), filter(), and map() as well as Object's toString(), valueOf(), and watch() methods.
Below is what I practiced with prototypes in VSC.
const dog = { name: "멍멍이", age: 3, owner: { name: "jun" } };
console.log(Object.keys(dog)); // returns the object's keys as an array
console.log(Object.values(dog)); // returns only the object's values as an array
console.log(Object.entries(dog)); // returns each key and value paired in an array
console.log("name" in dog); // returns a boolean for whether the dog object has a 'name' key
console.log(dog.hasOwnProperty("name")); // returns a boolean for whether the dog object has a 'name' key
Object.freeze: can't add, can't delete, can't write, can't redefine properties
Object.freeze(dog); // freeze for object immutability !! applies only to 1Depth
dog.name = "댕댕이";
console.log(dog);
dog.age = 10;
console.log(dog);
delete dog.name;
console.log(dog); // deleting doesn't work either
But what if you change a value at 2 Depth!?
dog.owner.name = "hun";
console.log(dog); // the owner's name does change to 'hun'
const cat = {};
Object.assign(cat, dog); // same as const cat = {...dog} // copies the dog object into the cat object
console.log(cat);
Object.seal(cat); // values in the object can be changed, but adding, deleting, or redefining properties doesn't work
cat.plus = "추가"; // adding a value doesn't work
delete cat.name; // deleting doesn't work
cat.name = "고양이"; // changing a value works
console.log(cat);
console.log(Object.isFrozen(dog)); // checks whether the object is frozen
console.log(Object.isSealed(cat)); // checks whether the object is sealed
Preventing extension: preventExtensions
const tiger = { sound: "어흥" };
Object.preventExtensions(tiger); // prevents extension with preventExtensions
console.log(Object.isExtensible(tiger)); // checks whether the object can be extended
tiger.name = "호랭이";
console.log(tiger); // only adding properties to the object is blocked
const dog1 = { name: "뽀삐", owner: "성준" };
const dog2 = { name: "도기", owner: "성훈" };
function Dog(name, owner) {
// constructor function
this.name = name;
this.owner = owner;
// instance-level function: it is included in every object created by the constructor, which wastes memory
this.printName = () => {
console.log(this.name);
};
}
// prototype-level function: saves memory
Dog.prototype.printName = () => {
console.log(this.name);
};
const dog1 = new Dog("뽀삐", "성준");
const dog2 = new Dog("도기", "성훈");
console.log(dog1, dog2); // objects created with the constructor function
dog1.printName(); // the method can be used through the prototype
dog2.printName();
The difference between instance-level and prototype-level functions: if you use a prototype-level function,
every object made with the constructor function can use the prototype method
Overriding
If you redefine a function with the same name at the instance level (child), that is, override it,
the prototype-level (parent) function property gets shadowed
dog1.printName = () => {
console.log("안녕하세요");
};
dog1.printName(); // prints "안녕하세요" (hello)
Object-oriented programming => benefit: code reuse through inheritance
function Dog(name, owner) {
// constructor function
this.name = name;
this.owner = owner;
// instance-level function: it is included in every object created by the constructor, which wastes memory
}
Dog.prototype.printName = function () {
console.log(this.name);
};
function Cat(name, owner, age) {
Dog.call(this, name, owner); // similar to super
this.age = age;
}
Cat.prototype = Object.create(Dog.prototype); // it normally inherits Object's prototype, but this changes it to inherit Dog's prototype
Cat.prototype.play = () => {
console.log("고양이와 놀기!");
};
const cat = new Cat("고양이", "엄성준", 25);
cat.play();
cat.printName(); // cat can use printName, Dog's prototype function, because through Object's create()
// it inherited Dog's prototype
console.log(cat instanceof Cat); // instanceof tells you which constructor function an object inherits from
console.log(cat instanceof Dog); // cat was created with the Cat constructor, and Cat inherits from Dog, so this prints true
const sleep = {
sleep: () => {
console.log("쿨쿨쿨");
},
};
const play = {
play: () => {
console.log("우리같이 놀자");
},
};
function Human(name) {
this.name = name;
}
Object.assign(Human.prototype, play, sleep); // because Human's prototype was made to inherit the play and sleep objects
const man = new Human("엄성준");
console.log(man);
man.sleep();
man.play();So, because Array inherits Object's prototype, it can also use object methods.
Object has the Object.freeze() method, which freezes elements at 1 Depth (shallow copy), Object.seal(), which seals the object so you can't add or delete values but can still change them, and lastly Object.preventExtensions(), which prevents extension.
What I learned from this practice is the difference between an instance-level function created inside a constructor function and a function declared on the constructor's prototype. Here is how they differ: an instance-level function declared inside the constructor gets assigned to every object the constructor creates, and that can waste a lot of memory.
But if you declare a prototype-level function through prototype, you save memory, and every object created by the constructor function can still use the prototype method.
In one line: the biggest difference between a function inside the constructor and a function created through prototype is memory!
- #JavaScript prototype
- #Array inherits Object's prototype
- #benefits of prototype
- #saving memory
- #instance level
- #prototype level