We all know JavaScript is a prototype-based language. When we try to access a property that an object doesn’t directly possess, JavaScript looks for that property on the object’s prototype. If the prototype also doesn’t have the property, it then looks on the prototype’s prototype, continuing this search until it reaches the end of the prototype chain, which is null
, the prototype of Object.prototype
. This method of property lookup is what we call the prototype chain.
Class Implementation
Since JavaScript itself doesn’t inherently have the concept of classes, we typically simulate class implementation using constructor functions:
function Person(name, age) {
//实现一个类
this.name = name;
this.age = age;
}
var you = new Person("you", 23); //通过 new 来新建实例
First, we create a Person
constructor function. To distinguish it from regular functions, we use CamelCase for naming constructor functions.
Then, we create instances using the new
operator. The new
operator essentially performs the following actions:
- Creates a new object inheriting from
Person.prototype
. - Executes the
Person
constructor function, passing in the relevant arguments, and setsthis
to the newly created object. - If the constructor function returns an object, that object replaces the result of
new
. If the constructor returns a non-object value, that return value is ignored.
返回值不是对象;
function Person(name) {
this.name = name;
return "person";
}
var you = new Person("you");
// you 的值: Person {name: "you"}
返回值是对象;
function Person(name) {
this.name = name;
return [1, 2, 3];
}
var you = new Person("you");
// you的值: [1,2,3]
If instances of a class need to share methods, then methods should be added to the constructor’s prototype
property. This is because objects created with the new
operator all inherit from the constructor’s prototype
property. They can share methods and properties defined on the class’s prototype
.
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype = {
sayName: function () {
console.log("My name is", this.name);
},
};
var you = new Person("you", 23);
var me = new Person("me", 23);
you.sayName(); // My name is you.
me.sayName(); // My name is me.
Inheritance Implementation
A commonly used inheritance pattern in JavaScript is combination inheritance, which simulates inheritance by combining constructor inheritance and prototype chain inheritance.
//Person 构造函数如上
function Student(name, age, clas) {
Person.call(this, name, age);
this.clas = clas;
}
Student.prototype = Object.create(Person.prototype); // Mark 1
Student.prototype.constructor = Student; //如果不指明,则 Student 的 constructor 是 Person
Student.prototype.study = function () {
console.log("I study in class", this.clas);
};
var liming = new Student("liming", 23, 7);
liming instanceof Person; //true
liming instanceof Student; //true
liming.sayName(); // My name is liming
liming.study(); // I study in class 7
In the code, Mark 1 uses the Object.create
method. This method, introduced in ES5, is used to create a new object with a specified prototype. If the environment is not compatible, the following Polyfill can be used (only implements the first argument).
if (!Object.create) {
Object.create = function (obj) {
function F() {}
F.prototype = obj;
return new F();
};
}
Essentially, it assigns obj
to a temporary function F
and then returns an instance of F
. This way, Student
(via code Mark 1) inherits all properties from Person.prototype
. One might ask, why not simply assign Person.prototype
directly to Student.prototype
?
Yes, direct assignment would allow the child class to share the parent class’s prototype
, but it breaks the prototype chain. That is, the child class and parent class would share the exact same prototype
. If a child class modifies this prototype
, it would simultaneously modify the parent’s prototype
, thereby affecting all child classes created based on that parent. This is not the desired outcome. Consider this example:
//Person 同上
//Student 同上
Student.prototype = Person.prototype;
Student.prototype.sayName = function () {
console.log("My name is", this.name, "my class is", this.clas);
};
var liming = new Student("liming", 23, 7);
liming.sayName(); //My name is liming,my class is 7;
//另一个子类
function Employee(name, age, salary) {
Person.call(name, age);
this.salary = salary;
}
Employee.prototype = Person.prototype;
var emp = new Employee("emp", 23, 10000);
emp.sayName(); //Mark 2
What do you think Mark 2 will output?
We would expect Mark 2 to output “My name is emp”. However, it actually throws an error. Why? Because when we overwrote Student.prototype
, we also modified Person.prototype
. This ultimately led to emp
inheriting an unexpected prototype
, where its sayName
method became My name is',this.name,'my class is',this.clas
, which naturally results in an error.
ES6 Inheritance
With the release of ECMAScript 6, we gained new ways to implement inheritance, specifically through the class
keyword.
Class Implementation
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
sayHello() {
console.log(`My name is ${this.name},i'm ${this.age} years old`);
}
}
var you = new Person("you", 23);
you.sayHello(); //My name is you,i'm 23 years old.
Inheritance
Inheritance in ES6 is also very convenient, implemented using the extends
keyword.
class Student extends Person {
constructor(name, age, cla) {
super(name, age);
this.class = cla;
}
study() {
console.log(`I'm study in class ${this.class}`);
}
}
var liming = new Student("liming", 23, 7);
liming.study(); // I'm study in class 7.
This inheritance method is much more convenient than the ES5 implementation discussed above. However, the underlying principle remains the same; these keywords and methods are merely syntactic sugar and do not change the fact that JavaScript is prototype-based. Nevertheless, inheritance implemented with extends
has a limitation: it cannot define properties directly, only methods. To add new properties, you still need to modify the prototype
.
Student.prototype.teacher = "Mr.Li";
var liming = new Student("liming", 23, 7);
var hanmeimei = new Student("hanmeimei", 23, 7);
liming.teacher; //Mr.Li
hanmeimei.teacher; //Mr.Li
Static Methods
ES6 also provides the static
keyword to implement static methods. Static methods can be inherited but can only be called by the class itself, not by instances.
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
static say() {
console.log("Static");
}
}
class Student extends Person {}
Person.say(); // Static
Student.say(); // Static
var you = new Person("you", 23);
you.say(); // TypeError: liming.say is not a function
As you can see, calling it on an instance directly results in an error.
Super Keyword
In a child class, super
can be used to call the parent class, and its behavior differs based on the call location. When called within a constructor
, it’s equivalent to calling the parent class’s constructor
method. When called within a regular method, it refers to the parent class itself.
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
sayHello() {
console.log(`My name is ${this.name},i'm ${this.age} years old`);
}
}
class Student extends Person {
constructor(name, age, cla) {
super(name, age); // 必须在子类调用 this 前执行,调用了父类的 constructor
this.class = cla;
}
sayHello() {
super.sayHello; // 调用父类方法
console.log("Student say");
}
}
var liming = new Student("liming", 23, 7);
liming.say(); // My name is liming,i'm 23 years old.\n Student say.
Summary
To summarize, with the release of ES6, JavaScript gained a standard way to implement inheritance. While these are merely syntactic sugar, the underlying essence is still achieved through prototype chains and constructor functions. However, the new syntax makes it easier to understand and clearer to write.
References:
This article was published on March 22, 2016 and last updated on March 22, 2016, 3485 days ago. The content may be outdated.