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
中的模块化思想。