1、前言
JavaScript本质上是基于原型继承的一种编程语言,在ES6标准出现以前,JavaScript定义类的方式往往让人很难理解。而Dojo则很好地解决了这个问题。开发者可以通过dojo/_base/declare模块定义类,也可以通过define引用各个类模块。本文就来介绍一下如何在Dojo中实现面向对象的相关操作。
2、定义一个类
在Dojo中,开发者只需要引入dojo/_base/declare模块即可定义类,declare定义类的其中一种写法如下所示,第一个参数表示要创建的类的名称,第二个参数表示该类的父类名称,如果父类不存在则赋值为null,第三个参数是一个json对象,主要用于定义类的成员变量和成员方法方法。
return declare('类名称', 父类, {
    // 成员变量
    // 构造函数
    // 成员方法
})
如果希望直接在require方法中直接定义类,也可以参考如下写法:
var 类 = declare(父类, {
    // 成员变量
    // 构造函数
    // 成员方法
})
下面代码定义了一个简单的Person类:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>demo</title>
    <script src="http://localhost/arcgis_js_api/library/4.15/dojo/dojo.js"></script>
</head>
<body>
    <script>
        require(['dojo/_base/declare'], function (declare) {
            // 定义Person类
            var Person = declare(null, {
                name: null,      // 姓名
                gender: null,    // 性别
                age: null,       // 年龄
                // 构造函数
                constructor: function (name, gender, age) {
                    this.name = name;
                    this.gender = gender;
                    this.age = age;
                },
                // 定义方法,打印输出
                print: function () {
                    console.log('姓名:' + this.name + '\r\n性别:' + this.gender + '\r\n年龄:' + this.age);
                }
            })
            // 创建Person类
            var person = new Person('张三', '男', 30);
            person.print();
        });
    </script>
</body>
</html>
在上面的代码中,name、gender、age是Person类的成员变量,constructor表示Person类的构造函数,该函数主要用处初始化成员变量,print是一个成员方法,实例化Person类后即可在外部进行调用。程序运行结果如下所示:
姓名:张三
性别:男
年龄:30
3、类的构造函数
构造函数一般用来初始化成员变量,上面的代码采用了如下写法来定义构造函数:
constructor: function (name, gender, age) {
    this.name = name;
    this.gender = gender;
    this.age = age;
}
熟悉Java或C#的同志对这种写法应该不会陌生,但这种写法存在一个问题,那就是一旦构造函数中的参数较多,则代码的可读写会变得很差。例如下面这种情况:
constructor: function (name, gender, age, email, phone, birthOfTime, address) {
    this.name = name;
    this.gender = gender;
    this.age = age;
    this.email = email;
    this.phone = phone;
    this.birthOfTime = birthOfTime;
    this.address = address;
}
当前很多JavaScript框架在实例化类时,都会在构造函数中传入一个json对象去初始化变量,那么Dojo是否也支持这种做法呢?答案当然是可以的,我们可以调用declare.safeMixin方法实现,现在将上面的代码修改一下:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>demo</title>
    <script src="http://localhost/arcgis_js_api/library/4.15/dojo/dojo.js"></script>
</head>
<body>
    <script>
        require(['dojo/_base/declare'], function (declare) {
            // 定义Person类
            var Person = declare(null, {
                name: null,      // 姓名
                gender: null,    // 性别
                age: null,       // 年龄
                // 构造函数
                constructor: function (args) {
                    declare.safeMixin(this, args);
                },
                // 定义方法,打印输出
                print: function () {
                    console.log('姓名:' + this.name + '\r\n性别:' + this.gender + '\r\n年龄:' + this.age);
                }
            })
            // 创建Person类
            var person = new Person({
                name: '张三',
                gender: '男',
                age: 30
            });
            person.print();
        });
    </script>
</body>
</html>
declare.safeMixin(this, args)大大简化了构造函数的定义流程,我们只需要在创建Person类时传入一个json对象即可实现成员变量的初始化操作:
var person = new Person({
    name: '张三',
    gender: '男',
    age: 30
});
程序运行结果如下所示:
姓名:张三
性别:男
年龄:30
4、类的模块化
在上面的代码中,我们直接在require方法中定义Person类。而实际工程往往包含众多的类,如果采用上面的做法,require方法将会变得异常臃肿,因此我们需要对类进行模块化处理。Dojo中使用define方法定义模块,创建一个JavaScript文件,取名为Person.js,在Person.js中加入如下代码:
define(['dojo/_base/declare'], function (declare) {
    return declare('Person', null, {
        name: null,      // 姓名
        gender: null,    // 性别
        age: null,       // 年龄
        // 构造函数
        constructor: function (args) {
            declare.safeMixin(this, args);
        },
        // 定义方法,打印输出
        print: function () {
            console.log('姓名:' + this.name + '\r\n性别:' + this.gender + '\r\n年龄:' + this.age);
        }
    })
})
找到IIS下部署ArcGIS API for JavaScript开发包的路径:C:\inetpub\wwwroot\arcgis_js_api\library\4.15\dojo,在该路径下新建一个文件夹,取名为js,如下图所示:

将Person.js文件放入新建的js文件夹

最后在require方法中引用Person.js即可,代码如下:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>demo</title>
    <script src="http://localhost/arcgis_js_api/library/4.15/dojo/dojo.js"></script>
</head>
<body>
    <script>
        require(['dojo/_base/declare', 'js/Person'], function (declare, Person) {
            var person = new Person({
                name: '张三',
                gender: '男',
                age: 30
            });
            person.print();
        });
    </script>
</body>
</html>
程序运行结果如下所示:
姓名:张三
性别:男
年龄:30
5、类的自定义模块配置
上面的类模块化操作很好地解决了类的创建和管理问题。但相信你也发现它的问题:难道开发调试阶段每次新建或修改一个js文件都要把它复制到IIS目录下吗?这未免也太繁琐了一点。在实际开发过程中,我们一般会借助dojoConfig进行自定义模块配置。首先在工程中创建一个js文件夹,然后新建一个Person.js文件,如下图所示:

在Person.js中加入如下代码:
define(['dojo/_base/declare'], function (declare) {
    return declare('Person', null, {
        name: null,      // 姓名
        gender: null,    // 性别
        age: null,       // 年龄
        // 构造函数
        constructor: function (args) {
            declare.safeMixin(this, args);
        },
        // 定义方法,打印输出
        print: function () {
            console.log('姓名:' + this.name + '\r\n性别:' + this.gender + '\r\n年龄:' + this.age);
        }
    })
})
然后在html页面中加入dojoConfig配置,代码如下:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>demo</title>
    <script>
        var dojoConfig = {
            async: true,
            packages: [{
                name: "js",
                location: location.pathname.replace(/\/[^/]*$/, '') + '/js'
            }]
        };
    </script>
    <script src="http://localhost/arcgis_js_api/library/4.15/dojo/dojo.js"></script>
</head>
<body>
    <script>
        require(['dojo/_base/declare', 'js/Person'], function (declare, Person) {
            var person = new Person({
                name: '张三',
                gender: '男',
                age: 30
            });
            person.print();
        });
    </script>
</body>
</html>
程序运行结果如下所示:
姓名:张三
性别:男
年龄:30
dojoConfig中的packages参数为数组类型,它可以定义一个或多个自定义的存放js文件的路径。在上面的代码中,location参数用于获取当前项目下js文件夹的相对路径。name可理解为命名空间,它的名称可以自行定义,下面的代码将name设置为ABCD,相对应的require中也许要改为'ABCD/Person'。
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>demo</title>
    <script>
        var dojoConfig = {
            async: true,
            packages: [{
                name: "ABCD",
                location: location.pathname.replace(/\/[^/]*$/, '') + '/js'
            }]
        };
    </script>
    <script src="http://localhost/arcgis_js_api/library/4.15/dojo/dojo.js"></script>
</head>
<body>
    <script>
        require(['dojo/_base/declare', 'ABCD/Person'], function (declare, Person) {
            var person = new Person({
                name: '张三',
                gender: '男',
                age: 30
            });
            person.print();
        });
    </script>
</body>
</html>
程序运行结果如下所示:
姓名:张三
性别:男
年龄:30
6、类的继承
类的继承机制也是Dojo的一大亮点,在ArcGIS API for JavaScript的开发中,我们经常会继承dijit中的一些组件,然后对其进行扩展。此时就需要使用类的继承机制。Dojo中允许单继承和多继承,先来看一段单继承的代码:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>demo</title>
    <script src="http://localhost/arcgis_js_api/library/4.15/dojo/dojo.js"></script>
</head>
<body>
    <script>
        require(['dojo/_base/declare'], function (declare) {
            // 定义父类ClassA
            var ClassA = declare(null, {
                propertyA: 'This is A',
                print: function () {
                    console.log(this.propertyA);
                }
            })
            // 定义子类ClassB
            var ClassB = declare(ClassA, {
                propertyB: 'This is B',
                print: function () {
                    console.log(this.propertyA + '\r\n' + this.propertyB);
                }
            })
            // 创建ClassB实例
            var obj = new ClassB();
            obj.print();
        });
    </script>
</body>
</html>
在上面的代码中,ClassA为父类,子类ClassB继承ClassA,这意味着ClassB中会包含ClassA中的propertyA属性和print方法,同时ClassB又定义了属于自身的propertyB属性,并且重写了print方法,因此程序运行结果如下所示:
This is A
This is B
Dojo中的多继承也很简单,代码如下:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>demo</title>
    <script src="http://localhost/arcgis_js_api/library/4.15/dojo/dojo.js"></script>
</head>
<body>
    <script>
        require(['dojo/_base/declare'], function (declare) {
            // 定义父类ClassA
            var ClassA = declare(null, {
                propertyA: 'This is A'
            })
            // 定义父类ClassB
            var ClassB = declare(null, {
                propertyB: 'This is B'
            })
            // 定义子类ClassC
            var ClassC = declare([ClassA, ClassB], {
                propertyC: 'This is C',
                print: function () {
                    console.log(this.propertyA + '\r\n' + this.propertyB + '\r\n' + this.propertyC);
                }
            })
            // 创建ClassC实例
            var obj = new ClassC();
            obj.print();
        });
    </script>
</body>
</html>
程序运行结果如下所示:
This is A
This is B
This is C
总的来说,Dojo中的继承机制还是比较简单的,只需要记住一个原则即可,那就是继承链中的子类会不断覆盖父类的成员变量和成员方法。
7、结语
本文主要介绍了Dojo中关于类的相关操作。在Dojo中,类的定义十分简单,只需要引入dojo/_base/declare模块即可,而类的继承机制则能方便开发者对原有的组件进行功能扩展。因此个人觉得ArcGIS API for JavaScript选择基于Dojo构建并不是没有道理,这种严格面向对象的思想不仅严谨,同时也能方便项目的维护和管理。如果希望玩转ArcGIS API for JavaScript开发,同志们很有必要深入了解Dojo中的模块化思想。



















