Note: The implementation of jClass in this chapter refers to the practice of Simple JavaScript Inheritance.
First let us review the examples introduced in Chapter 1:
function Person(name) {<br> this.name = name;<br> }<br> Person.prototype = {<br> getName: function() {<br> return this.name;<br> }<br> }<br><br> function Employee(name, employeeID) {<br> this.name = name;<br> this.employeeID = employeeID;<br> }<br> Employee.prototype = new Person();<br> Employee.prototype.getEmployeeID = function() {<br> return this.employeeID;<br> };<br> var zhang = new Employee("ZhangSan", "1234");<br> console.log(zhang.getName()); // "ZhangSan" <br>
From the description of the constructor in the previous article, we know that the constructor of the Employee instance will have a pointing error, as shown below:
var zhang = new Employee("ZhangSan", "1234");<br> console.log(zhang.constructor === Employee); // false<br> console.log(zhang.constructor === Object); // true <br>
function Employee(name, employeeID) {<br> this.name = name;<br> this.employeeID = employeeID;<br> }<br> Employee.prototype = new Person();<br> Employee.prototype.constructor = Employee;<br> Employee.prototype.getEmployeeID = function() {<br> return this.employeeID;<br> };<br> var zhang = new Employee("ZhangSan", "1234");<br> console.log(zhang.constructor === Employee); // true<br> console.log(zhang.constructor === Object); // false<br>
But on the other hand, we must rely on this mechanism to implement inheritance. The solution is not to initialize the data in the constructor, but to provide a prototype method (such as init) to initialize the data.
// 空的构造函数<br> function Person() {<br> }<br> Person.prototype = {<br> init: function(name) {<br> this.name = name;<br> },<br> getName: function() {<br> return this.name;<br> }<br> }<br> // 空的构造函数<br> function Employee() {<br> }<br> // 创建类的阶段不会初始化父类的数据,因为Person是一个空的构造函数<br> Employee.prototype = new Person();<br> Employee.prototype.constructor = Employee;<br> Employee.prototype.init = function(name, employeeID) {<br> this.name = name;<br> this.employeeID = employeeID;<br> };<br> Employee.prototype.getEmployeeID = function() {<br> return this.employeeID;<br> };<br>
var zhang = new Employee();<br> zhang.init("ZhangSan", "1234");<br> console.log(zhang.getName()); // "ZhangSan"<br>
Two effects must be achieved: do not call the init function when constructing a class and automatically call the init function when instantiating an object. It seems that we need to have a status indicator when calling the empty constructor.
// 创建一个全局的状态标示 - 当前是否处于类的构造阶段<br> var initializing = false;<br> function Person() {<br> if (!initializing) {<br> this.init.apply(this, arguments);<br> }<br> }<br> Person.prototype = {<br> init: function(name) {<br> this.name = name;<br> },<br> getName: function() {<br> return this.name;<br> }<br> }<br> function Employee() {<br> if (!initializing) {<br> this.init.apply(this, arguments);<br> }<br> }<br> // 标示当前进入类的创建阶段,不会调用init函数<br> initializing = true;<br> Employee.prototype = new Person();<br> Employee.prototype.constructor = Employee;<br> initializing = false;<br> Employee.prototype.init = function(name, employeeID) {<br> this.name = name;<br> this.employeeID = employeeID;<br> };<br> Employee.prototype.getEmployeeID = function() {<br> return this.employeeID;<br> };<br><br> // 初始化类实例时,自动调用类的原型函数init,并向init中传递参数<br> var zhang = new Employee("ZhangSan", "1234");<br> console.log(zhang.getName()); // "ZhangSan"<br>
We need to introduce a global function to simplify the class creation process, while encapsulating internal details to avoid introducing global variables.
// 当前是否处于创建类的阶段<br> var initializing = false;<br> function jClass(baseClass, prop) {<br> // 只接受一个参数的情况 - jClass(prop)<br> if (typeof (baseClass) === "object") {<br> prop = baseClass;<br> baseClass = null;<br> }<br> // 本次调用所创建的类(构造函数)<br> function F() {<br> // 如果当前处于实例化类的阶段,则调用init原型函数<br> if (!initializing) {<br> this.init.apply(this, arguments);<br> }<br> }<br> // 如果此类需要从其它类扩展<br> if (baseClass) {<br> initializing = true;<br> F.prototype = new baseClass();<br> F.prototype.constructor = F;<br> initializing = false;<br> }<br> // 覆盖父类的同名函数<br> for (var name in prop) {<br> if (prop.hasOwnProperty(name)) {<br> F.prototype[name] = prop[name];<br> }<br> }<br> return F;<br> };<br>
var Person = jClass({<br> init: function(name) {<br> this.name = name;<br> },<br> getName: function() {<br> return this.name;<br> }<br> });<br> var Employee = jClass(Person, {<br> init: function(name, employeeID) {<br> this.name = name;<br> this.employeeID = employeeID;<br> },<br> getEmployeeID: function() {<br> return this.employeeID;<br> }<br> });<br><br> var zhang = new Employee("ZhangSan", "1234");<br> console.log(zhang.getName()); // "ZhangSan"<br>
We can point to the prototype of the parent class (constructor) by providing a base attribute for the instantiated object, as follows:
// 当前是否处于创建类的阶段<br> var initializing = false;<br> function jClass(baseClass, prop) {<br> // 只接受一个参数的情况 - jClass(prop)<br> if (typeof (baseClass) === "object") {<br> prop = baseClass;<br> baseClass = null;<br> }<br> // 本次调用所创建的类(构造函数)<br> function F() {<br> // 如果当前处于实例化类的阶段,则调用init原型函数<br> if (!initializing) {<br> // 如果父类存在,则实例对象的base指向父类的原型<br> // 这就提供了在实例对象中调用父类方法的途径<br> if (baseClass) {<br> this.base = baseClass.prototype;<br> }<br> this.init.apply(this, arguments);<br> }<br> }<br> // 如果此类需要从其它类扩展<br> if (baseClass) {<br> initializing = true;<br> F.prototype = new baseClass();<br> F.prototype.constructor = F;<br> initializing = false;<br> }<br> // 覆盖父类的同名函数<br> for (var name in prop) {<br> if (prop.hasOwnProperty(name)) {<br> F.prototype[name] = prop[name];<br> }<br> }<br> return F;<br> };<br>
var Person = jClass({<br> init: function(name) {<br> this.name = name;<br> },<br> getName: function() {<br> return this.name;<br> }<br> });<br> var Employee = jClass(Person, {<br> init: function(name, employeeID) {<br> // 调用父类的原型函数init,注意使用apply函数修改init的this指向<br> this.base.init.apply(this, [name]);<br> this.employeeID = employeeID;<br> },<br> getEmployeeID: function() {<br> return this.employeeID;<br> },<br> getName: function() {<br> // 调用父类的原型函数getName<br> return "Employee name: " + this.base.getName.apply(this);<br> }<br> });<br><br> var zhang = new Employee("ZhangSan", "1234");<br> console.log(zhang.getName()); // "Employee name: ZhangSan"<br>
So far, we have corrected the shortcomings of manual implementation of inheritance in Chapter 1. Create classes and subclasses through our custom jClass function, initialize data through the prototype method init, and call the prototype function of the parent class through the instance attribute base.
The only shortcoming is that the code for calling the parent class is too long and difficult to understand. Wouldn’t it be better if it could be called as follows:
var Employee = jClass(Person, {<br> init: function(name, employeeID) {<br> // 如果能够这样调用,就再好不过了<br> this.base(name);<br> this.employeeID = employeeID;<br> }<br> });<br>
// 当前是否处于创建类的阶段<br> var initializing = false;<br> function jClass(baseClass, prop) {<br> // 只接受一个参数的情况 - jClass(prop)<br> if (typeof (baseClass) === "object") {<br> prop = baseClass;<br> baseClass = null;<br> }<br> // 本次调用所创建的类(构造函数)<br> function F() {<br> // 如果当前处于实例化类的阶段,则调用init原型函数<br> if (!initializing) {<br> // 如果父类存在,则实例对象的baseprototype指向父类的原型<br> // 这就提供了在实例对象中调用父类方法的途径<br> if (baseClass) {<br> this.baseprototype = baseClass.prototype;<br> }<br> this.init.apply(this, arguments);<br> }<br> }<br> // 如果此类需要从其它类扩展<br> if (baseClass) {<br> initializing = true;<br> F.prototype = new baseClass();<br> F.prototype.constructor = F;<br> initializing = false;<br> }<br> // 覆盖父类的同名函数<br> for (var name in prop) {<br> if (prop.hasOwnProperty(name)) {<br> // 如果此类继承自父类baseClass并且父类原型中存在同名函数name<br> if (baseClass &&<br> typeof (prop[name]) === "function" &&<br> typeof (F.prototype[name]) === "function") {<br><br> // 重定义函数name - <br> // 首先在函数上下文设置this.base指向父类原型中的同名函数<br> // 然后调用函数prop[name],返回函数结果<br><br> // 注意:这里的自执行函数创建了一个上下文,这个上下文返回另一个函数,<br> // 此函数中可以应用此上下文中的变量,这就是闭包(Closure)。<br> // 这是JavaScript框架开发中常用的技巧。<br> F.prototype[name] = (function(name, fn) {<br> return function() {<br> this.base = baseClass.prototype[name];<br> return fn.apply(this, arguments);<br> };<br> })(name, prop[name]);<br><br> } else {<br> F.prototype[name] = prop[name];<br> }<br> }<br> }<br> return F;<br> };<br>
var Person = jClass({<br> init: function(name) {<br> this.name = name;<br> },<br> getName: function() {<br> return this.name;<br> }<br> });<br> var Employee = jClass(Person, {<br> init: function(name, employeeID) {<br> this.base(name);<br> this.employeeID = employeeID;<br> },<br> getEmployeeID: function() {<br> return this.employeeID;<br> },<br> getName: function() {<br> return "Employee name: " + this.base();<br> }<br> });<br><br> var zhang = new Employee("ZhangSan", "1234");<br> console.log(zhang.getName()); // "Employee name: ZhangSan"<br>
So far, we have created a complete function jClass to help us implement classes and inheritance in JavaScript in a more elegant way.
In the following chapters, we will successively analyze the implementation of some of the more popular JavaScript classes and inheritance on the Internet. However, everything remains the same, and those implementations are nothing more than "hype" that shakes up the concepts we mentioned in this chapter, just for the sake of a more elegant way of calling.