类、原型和构造函数 ECMAScript没有类的概念,但我们可以模拟出“类”
对象通过原型继承 在JavaScript中,类的所有实例对象都是从同一个原型对象上继承属性,可以通过如下inherit()方法实现
1 2 3 4 5 6 7 8 9 10 11 12 function inherit (p ) { if (p == null ) throw TypeError ; if (Object .create) return Object .create(p); var t = typeof p; if (t !== 'object' && t !== 'function' ) throw TypeError ; function f ( ) {}; f.prototype = p; return new f(); }
举个简单的例子:
1 2 3 4 5 6 7 8 9 10 11 12 var obj1 = { x : 1 };var obj2 = inherit(obj1);obj2.y = 2 ; console .log(obj2.x + obj2.y); obj1.x++; console .log(obj2.x + obj2.y);
通常,类的实例还要进一步初始化,可以通过定义工厂函数创建和初始化类的实例对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 function range (from, to ) { var r = inherit(range.methods); r.from = from ; r.to = to; return r; } range.methods = { includes: function (x ) { return this .from <= x && x <= this .to; }, foreach: function (f ) { for (var x = Math .ceil(this .from); x <= this .to; x++) { f(x); } }, toString: function ( ) { return "(" + this .from + ", " + this .to + ")" ; } }; var r = range(1 , 5 );r.includes(2 ) r.foreach(console .log) r.toString()
构造函数 调用构造函数的一个重要特征——构造函数的prototype属性被用作新对象的原型
通过同一个构造函数创建的所有对象都继承自同一个对象,构造函数初始化这个新建的对象,并将这个对象用做其调用上下文
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 function Range (from, to ) { this .from = from ; this .to = to; } Range.prototype = { includes: function (x ) { return this .from <= x && x <= this .to; }, foreach: function (f ) { for (var x = Math .ceil(this .from); x <= this .to; x++) { f(x); } }, toString: function ( ) { return "(" + this .from + ", " + this .to + ")" ; } }; var r = new Range(1 , 5 );r.includes(2 ) r.foreach(console .log) r.toString()
构造函数和类的标识 原型对象是类的唯一标识,当且仅当两个对象继承自同一个原型对象时,它们才是同一个类的实例
通常我们使用instanceof检测某个对象是否为类的实例,本质上也是检测构造函数的prototype属性是否出现在对象的原型链中的任何位置
1 2 r instanceof Range r instanceof Object
constructor属性 所有JavaScript函数都有一个原型对象属性prototype,这个对象包含唯一一个不可枚举的属性constructor
1 2 3 var Func = function ( ) {};var obj = Func.prototype;obj.constructor === Func;
可以看出构造函数的原型中存在预先定义好的constructor属性,对象通常继承的constructor属性均指代其构造函数
1 2 3 var Func = function ( ) {};var obj = new Func();obj.constructor === Func
在上面定义的Range类中,新定义的prototype对象不含constructor属性
可以通过手动添加来修正
1 2 3 4 Range.prototype = { constructor : Range, // code... };
也可以使用预定义的prototype对象,依次添加方法
1 2 3 4 5 6 7 8 9 10 11 Range.prototype.includes = function (x ) { return this .from <= x && x <= this .to; }; Range.prototype.foreach = function (f ) { for (var x = Math .ceil(this .from); x <= this .to; x++) { f(x); } }; Range.prototype.toString = function ( ) { return "(" + this .from + ", " + this .to + ")" ; };
JavaScript中Java式类的模拟 JavaScript中的类与三种不同的对象有关:
在JavaScript中定义一个类有以下三步:
定义一个构造函数,并设置初始化新对象的实例属性
给构造函数的prototype对象定义实例的方法
给构造函数定义类字段和类属性
下面的例子展现了实现一个复数类的完整过程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 function Complex (real, imaginary ) { if (isNaN (real) || isNaN (imaginary)) throw new TypeError ; this .r = real; this .i = imaginary; } Complex.prototype.add = function (complex ) { return new Complex(this .r + complex.r, this .i + complex.i); } Complex.prototype.mag = function ( ) { return Math .sqrt(this .r * this .r + this .i * this .i); } Complex.prototype.isEqual = function (complex ) { return complex != null && complex.constructor === Complex && this .r === complex.r && this .i === complex.i; } Complex.prototype.toString = function ( ) { return '{' + this .r + ', ' + this .i + '}' ; } Complex.ZERO = new Complex(0 , 0 ); Complex.ONE = new Complex(1 , 0 ); Complex.I = new Complex(0 , 1 ); Complex.parse = function (str ) { try { var m = Complex._format.exec(str); return new Complex(parseFloat (m[1 ]), parseFloat (m[2 ])); } catch (e) { throw new TypeError ("Can't parse '" + str + "' as a complex" ); } }; Complex._format = /^\{([^,]+),([^}]+)\}$/ ; var c = new Complex(2 , 3 );var d = new Complex(c.i, c.r);console .log(c.toString(), d.toString()); var e = Complex.parse('{1, 2}' )console .log(e.add(c).toString())
JavaScript基于原型的继承机制是动态的,如果创建对象之后原型的属性发生改变,也会影响继承这个原型的所有实例对象
可以通过给原型对象添加新方法扩充JavaScript类
1 2 3 4 5 Complex.prototype.conj = function ( ) { return new Complex(this .r, -this .i); } console .log(c.conj().toString())