最近在处理时间的时候发现一个问题,不能继承Date
上面的方法,之前使用其它的对象都没出现过问题,我们先看下面这个例子,使用经典的寄生组合继承
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 function MyDate ( ) { Date .apply(this , arguments ); this .abc = 1 ; } function inherits (subClass, superClass ) { function Inner ( ) {} Inner.prototype = superClass.prototype; subClass.prototype = new Inner(); subClass.prototype.constructor = subClass; } inherits(MyDate, Date ); MyDate.prototype.getTest = function ( ) { return this .getTime(); }; let date = new MyDate();console .log(date.getTest());
运行之后,可以在控制台里面看到如下输出:
1 2 3 4 5 VM2356:17 Uncaught TypeError : this is not a Date object. at MyDate.getTime (<anonymous>) at MyDate.getTest (<anonymous>:17 :17 ) at <anonymous>:23 :18us>:20 :17 ) at <anonymous>:26 :18
这是什么原因呢,MDN中有提到,JavaScript的日期对象只能通过JavaScript Date
作为构造函数来实例化,那么,是不是就无法使用Date来实现继承来呢?
通过在网上查找,发现了网友给出的很多有趣的答案,现将网上的答案整理如下,不外乎有三种:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function MyDate ( ) { var d = new Date (); function init (that ) { var i; var methods = ['getDate' ,'getTime' ,'getYear' ,,'toString' ]; for (i = 0 ; i < methods.length; i++) { that[methods[i]] = d[methods[i]] } } init(this ) this .MyTest() = function ( ) { console .log('dd' ) } }
这种方法使用了方法代理,并不能称之为继承
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 Object .setPrototypeOf = Object .setPrototypeOf ||function (obj, proto ) { obj.__proto__ = proto; return obj; }; function MyDate ( ) { var dateInst = new (Function .prototype.bind.apply(Date , [Date ].concat(Array .prototype.slice.call(arguments ))))(); Object .setPrototypeOf(dateInst, MyDate.prototype); dateInst.abc = 1 ; return dateInst; } Object .setPrototypeOf(MyDate.prototype, Date .prototype);MyDate.prototype.getTest = function getTest ( ) { return this .getTime(); }; let date = new MyDate();console .log(date.getTest());
可以看到,用的是非常巧妙的一种做法:
正常继承的情况如下:
new MyDate()
返回实例对象date
是由MyDate
构造的 原型链回溯是: date(MyDate对象)->date.__proto__->MyDate.prototype->MyDate.prototype.__proto__->Date.prototype
这种做法的继承的情况如下:
new MyDate()
返回实例对象date是由Date构造的 原型链回溯是: date(Date对象)->date.__proto__->MyDate.prototype->MyDate.prototype.__proto__->Date.prototype
可以看出,关键点在于:
构造函数里返回了一个真正的Date
对象(由Date
构造,所以有这些内部类中的关键[[Class]]
标志),所以它有调用Date
原型上方法的权利
构造函数里的Date
对象的[[ptototype]]
(对外,浏览器中可通过__proto__
访问)指向MyDate.prototype
,然后MyDate.prototype
再指向Date.prototype
。
所以最终的实例对象仍然能进行正常的原型链回溯,回溯到原本Date的所有原型方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class MyDate extends Date { constructor ( ) { super (); this .abc = 1 ; } getTest ( ) { return this .getTime(); } } let date = new MyDate();console .log(date.getTest());
这里的正常输出环境是直接用ES6运行,不经过babel打包,打包后实质上是转化成ES5的,所以效果完全不一样