类和模块
一、类
JavaScript中类的实现是基于原型机制的。如果两个实例都从同一个原型上继承属性,则他们它们是同一个类的实例,类的所有实例对象都从同一个原型对象上继承属性。
使用构造函数定义类
1 //使用构造函数定义一个范围类
2 function Range(from,to){
3 this.from=from;
4 this.to=to;
5 }
6 Range.prototype={
7 //定义构造器属性
8 constructor:Range,
9 //判断x是否在范围内
10 include:function (x){
11 return this.from<=x&&x<=this.to;
12 },
13 //对范围内的每一个整数执行f;
14 foreach:function (f){
15 for(var i=Math.ceil(this.from);i<=this.to;i++)
16 {
17 f(i);
18 }
19 },
20 //返回表示范围的字符串
21 toString(){
22 return this.from+'...'+this.to;
23 }
24 }
25 //调用构造函数
26 var range=new Range(1,10);
27 console.log(range.include(5));//true
28 range.foreach((ele)=>{console.log(ele)});//1 2 3 4 5..10
29 console.log(range.toString());//1...10
构造函数是类的公共表示(往往代表类明),而原型对象是类的唯一标识。
类的实例是一个独立的对象,直接给实例定义的属性不会被所有实例共享。可以用来模拟实例字段。
原型对象的属性和方法或被类的实例所共享,可以用来模拟类的实例方法。
构造函数上定义的方法可以直接用构造函数对象调用,可以用来模拟类的方法(通过类名调用)
java中用final表示声明字段为常量。JavaScript可以使用属性名字全部大写来暗示这是一个常量。
java中可以把字段设置为私有成员(类的外部不可见)。JavaScript中使用属性名以下划线开始暗示这是一个私有变量。
1 //使用构造函数定义一个范围类 2 function Range(from,to){ 3 //定义实例方法:对象字段;保存对象的独立状态 4 this.from=from; 5 this.to=to; 6 } 7 //定义实例方法:所有实例共享 8 Range.prototype={ 9 //定义构造器属性 10 constructor:Range, 11 //判断x是否在范围内 12 include:function (x){ 13 return this.from<=x&&x<=this.to; 14 }, 15 //对范围内的每一个整数执行f; 16 foreach:function (f){ 17 for(var i=Math.ceil(this.from);i<=this.to;i++) 18 { 19 f(i); 20 } 21 }, 22 //返回表示范围的字符串 23 toString(){ 24 return this.from+'...'+this.to; 25 } 26 } 27 //类的方法和属性 28 Range.NAME='Range';//大写表示只读的。 29 Range._format="";//下划线表示它是私有变量
可以将上面的过程封装成一个defineClass方法。
1 //将p对象的属性复制到o对象,覆盖同名方法
2 //构造器属性只能复制值
3 function extend(o, p) {
4 for (var s in p) {
5 if (p.hasOwnProperty(s))
6 o[s] = p[s];
7 }
8 return o;
9 }
10 function defineClass(constructor, method, static) {
11 //constructor代表创建类的构造函数
12 //method 实例方法
13 //static 类方法
14 if (method)
15 extend(constructor.prototype, method);
16 if (static)
17 extend(constructor, static);
18 return constructor;
19 }
20 //定义一个SimpleRange
21 var SimpleRange = defineClass(function () { this.x = 1; this.y = 2 },
22 { add: function () { return 10 } },
23 { pi: 3.14 });
24 var s=new SimpleRange();
25 console.log(SimpleRange.pi)//3,14
26 console.log(s.x);//1
27 console.log(s.add());//10
二、类的扩充
JavaScript基于原型的继承是动态的,给对象的原型添加的方法,继承这个原型对象也会继承这些方法。
1 Pange.prototype.toJson=function(){ 2 return ....// 3 }
类的实例对象都会添加toJson();
我们可以内置对象的原型上添加方法,是内置对象都可以使用这个方法。
三、检测对象类的方法
intanceof、对象的constructor属性、isprototype()
1 var o={};
2 console.log(o instanceof Object);//true
3 console.log(Object.prototype.isPrototypeOf(o));//true
4 console.log(o.constructor);//Object函数的引用
intanceof和constructor属性会在web应用存在问题,web的框架和多窗中会有独立的上限下文,独立上下文包含独立的变量和构造方法。一个框架页面中的数组不是另一个框架页面中Array()的实例,所以instance和constructor返回false;
四. 面向对象技术
定义一个集合类Set
1 //定义集合类 :将传入的值建立一个唯一的到字符串的映射,然后将字符串和值保存进对象中
2
3 //定义一个构造函数
4 function Set() {
5 //用于将值保存在对象中
6 this.value = {};
7 //集合中值得个数
8 this.n = 0;
9 //初始化集合中的值。
10 this.add.apply(this, arguments);
11 }
12 //定义一个类内部使用的私有属性;用来产生值对应的字符串
13 Set._v2s = function (val) {
14 switch (val) {
15 case undefined: return "u";
16 case null: return 'n';
17 case true: return 't';
18 case false: return 'f';
19 default: switch (typeof val) {
20 case "number": return "#" + val;
21 case "string": return '"' + val;
22 default: return "@" + objectid(val);
23 }
24 }
25 }
26 //生成对象的id方法
27 function objectid(val) {
28 //相同的对象产生相同的id,不同的对象产生不同的id
29 var prop = "|*prop*|";//定义一个私有属性
30 if (!val.hasOwnProperty(prop))
31 val[prop] = Set._v2s.next++;
32 return val[prop];
33 }
34 //定义一个类属性
35 Set._v2s.next = 100;
36 //给集合添加实例方法
37 Set.prototype.add = function () {
38 for (var i = 0; i < arguments.length; i++) {
39 var val = arguments[i];
40 //得到值到字符串的映射
41 var str = Set._v2s(val);
42 //检验集合中是否存在这个字符串
43 if (!this.value.hasOwnProperty(str)) {
44 //当集合中不存在这个值时,将值保存近集合
45 this.value[str] = val;
46 this.n++;
47 }
48 }
49 return this;//支持链式编程
50 }
51 //删除集合中的值
52 Set.prototype.remove = function () {
53 for (var i = 0; i < arguments.length; i++) {
54 var val = arguments[i];
55 //得到值到字符串的映射
56 var str = Set._v2s(val);
57 //检验集合中是否存在这个字符串
58 if (this.value.hasOwnProperty(str)) {
59 //当集合中存在这个值时,将值删除
60 delete this.value[str]
61 this.n--;
62 }
63 }
64 return this;//支持链式编程
65 }
66 //判断集合中是否有这个值
67 Set.prototype.contains = function (val) {
68 var str = Set._v2s(val);
69 return val.hasOwnProperty(str);
70 }
71 //遍历集合,使用指定的上下文执行f函数
72 Set.prototype.forEach=function(f,content){
73 for(var p in this.value){
74 if(this.value.hasOwnProperty(p)){
75 f.call(content,this.value[p]);
76 }
77 }
78 return this;
79 }
80 //返回集合的大小
81 Set.prototype.size=function(){
82 return this.n;
83 }
84 //创建一个集合
85 var set=new Set(1,2,3);
86 set.add(4,5,6,{},{},{x:1});
87 set.remove(4,5);
88 set.forEach((ele)=>{console.log(ele)});
89 console.log(set.size());
90 console.log(set.contains(1));
五、标准转化方法
应该给类的原型上定义一些标准转发方法:toString()、valueOf()、toJSON()、toLocalString()。这些方法在对象的类型转换时会用到。如果没有定义最好是刻意为之而不是忘了定义。
对象的比较是引用的比较,我们可以自己在原型定义一类比较方法。
六、方法借用
多个类中的方法可以共用一个函数,我们称之为方法借用。
如;Set.prototype.equals=A.equals;
七、可以利用闭包实现变量的私有
1 //变量只能被类的实例方法访问,类的外面不可见
2 function Range(from,to){
3 this.from=function(){
4 return from;
5 }
6 this.to=function(){
7 return to;
8 }
9 }
10 Range.prototype.include=function(x){
11 return this.from()<=x&&x<=this.to;
12 }
八、构造方法的重载和工厂方法
有时候,我们希望对象的初始化有多种方式,比如我们用构造函数创建初始化一个数组。