如何继承 Date 对象?由一道题彻底弄懂 JS 继承

By admin in 计算机教程 on 2019年4月5日

如何继承 Date 对象?由一道题彻底弄懂 JS 继承

2018/01/25 · JavaScript
· Date,
继承

原文出处: 撒网要见鱼   

继承6种套餐

参照红皮书,JS继承一共6种

前言

见解有限,如有描述不当之处,请帮忙及时指出,如有错误,会及时修正。

———-长文+多图预警,需要花费一定时间———-

故事是从一次实际需求中开始的。。。

某天,某人向我寻求了一次帮助,要协助写一个日期工具类,要求:

  • 此类继承自Date,拥有Date的所有属性和对象
  • 此类可以自由拓展方法

形象点描述,就是要求可以这样:

// 假设最终的类是 MyDate,有一个getTest拓展方法 let date = new MyDate();
// 调用Date的方法,输出GMT绝对毫秒数 console.log(date.getTime()); //
调用拓展的方法,随便输出什么,譬如helloworld!
console.log(date.getTest());

1
2
3
4
5
6
7
// 假设最终的类是 MyDate,有一个getTest拓展方法
let date = new MyDate();
 
// 调用Date的方法,输出GMT绝对毫秒数
console.log(date.getTime());
// 调用拓展的方法,随便输出什么,譬如helloworld!
console.log(date.getTest());

于是,随手用JS中经典的组合寄生法写了一个继承,然后,刚准备完美收工,一运行,却出现了以下的情景:

图片 1

但是的心情是这样的: 😳囧

以前也没有遇到过类似的问题,然后自己尝试着用其它方法,多次尝试,均无果(不算暴力混合法的情况),其实回过头来看,是因为思路新奇,凭空想不到,并不是原理上有多难。。。

于是,借助强大的搜素引擎,搜集资料,最后,再自己总结了一番,才有了本文。

———-正文开始前———-

正文开始前,各位看官可以先暂停往下读,尝试下,在不借助任何网络资料的情况下,是否能实现上面的需求?(就以10分钟为限吧)

1.原型链继承

核心思想:子类的原型指向父类的一个实例

Son.prototype=new Father();

大纲

  • 先说说如何快速快速寻求解答
    • stackoverflow上早就有答案了!
    • 倘若用的是中文搜索。
  • 分析问题的关键
    • 经典的继承法有何问题
    • 为什么无法被继承?
  • 该如何实现继承?
    • 暴力混合法
    • ES5黑魔法
    • ES6大法
    • ES6写法,然后babel打包
  • 几种继承的细微区别
  • ES6继承与ES5继承的区别
  • 构造函数与实例对象
  • [[Class]]与Internal slot
  • 如何快速判断是否继承?
  • 写在最后的话

2.构造函数继承

核心思想:借用apply和call方法在子对象中调用父对象

function Son(){Father.call(this);}

先说说如何快速快速寻求解答

遇到不会的问题,肯定第一目标就是如何快速寻求解决方案,答案是:

  • 先去stackoverflow上看看有没有类似的题。。。

于是,借助搜索引擎搜索了下,第一条就符合条件,点开进去看描述

图片 2

3.组合继承(1+2)(常用)

核心思想:1+2,但记得修正constructor

function Son(){Father.call(this);}

Son.prototype=new Father();

Son.prototype.constructor = Son;

stackoverflow上早就有答案了!

先说说结果,再浏览一番后,确实找到了解决方案,然后回过头来一看,惊到了,因为这个问题的提问时间是6 years, 7 months ago
也就是说,2011年的时候就已经有人提出了。。。

感觉自己落后了一个时代>_。。。

图片 3

而且还发现了一个细节,那就是viewed:10,606 times,也就是说至今一共也才一万多次阅读而已,考虑到前端行业的从业人数,这个比例惊人的低。
以点见面,看来,遇到这个问题的人并不是很多。

4.原型式继承

核心思想:返回一个临时类型的一个新实例,现提出了规范的原型式继承,使用Object.create()方法。

var person={name:”xiaoming”,age:16}

var anotherperson=Object.create(person,{name:”xiaowang”})

倘若用的是中文搜索。

用中文搜索并不丢人(我遇到问题时的本能反应也是去百度)。结果是这样的:

图片 4

嗯,看来英文关键字搜索效果不错,第一条就是符合要求的。然后又试了试中文搜索。
图片 5

图片 6效果不如人意,搜索前几页,唯一有一条看起来比较相近的(segmentfault上的那条),点进去看

图片 7
图片 8

怎么说呢。。。这个问题关注度不高,浏览器数较少,而且上面的问题描述和预期的有点区别,仍然是有人回答的。
不过,虽然说问题在一定程度上得到了解决,但是回答者绕过了无法继承这个问题,有点未竟全功的意思。。。

5.寄生式继承

核心思想:创建一个仅用于封装继承过程的函数,该函数在内部使用某种方式增强对象

function createAnother(original){

var clone=object(original);

clone.name=”ahaha”;

return clone;

}

分析问题的关键

借助stackoverflow上的回答

6.寄生组合继承

核心思想:3+5

function inheritPropertype(son,father){

var prototype=object(father.prototype);//创建

prototype.constructor=son;//增强

son.prototype=prototype;//指定

}

在阮一峰老师的解说下,他将继承分成了两种,构造函数的继承非构造函数的继承

构造函数的继承:

1.apply或call

2.prototype,即子类原型属性指向父类实例

3.直接的prototype,子类原型=父类原型

4.利用空对象作为中介,这种方法类似寄生继承,但是会变成子类->中介->父类这样的继承关系。好处是当子类对原型进行变动时,对父类没有影响。

function extend(Child, Parent) {

    var F = function(){};

    F.prototype = Parent.prototype;//继承方法2

    Child.prototype = new F();//继承方法1

    Child.prototype.constructor = Child;//修正

    Child.uber =
Parent.prototype;//为子对象设一个uber属性,这个属性直接指向父对象的prototype属性。只是为

                                                       
//了实现继承的完备性,纯属备用性质。

  }

5.拷贝继承,将父对象的prototype对象中的属性,一一拷贝给Child对象的prototype对象。

  function extend2(Child, Parent) {

    var p = Parent.prototype;

    var c = Child.prototype;

    for (var i in p) {

      c[i] = p[i];

      }

    c.uber = p;

  }

非构造函数的继承:

1.原型式继承。

2.浅拷贝

  function extendCopy(p) {

    var c = {};

    for (var i in p) {

      c[i] = p[i];

    }

    c.uber = p;

    return c;

  }

子对象获得的只是一个内存地址,而不是真正拷贝,因此存在父对象被篡改的可能。

3.深拷贝

  function deepCopy(p, c) {

    var c = c || {};

    for (var i in p) {

      if (typeof p[i] === ‘object’) {

        c[i] = (p[i].constructor === Array) ? [] : {};

        deepCopy(p[i], c[i]);

      } else {

         c[i] = p[i];

      }

    }

    return c;

  }

经典的继承法有何问题

先看看本文最开始时提到的经典继承法实现,如下:

/** * 经典的js组合寄生继承 */ 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
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* 经典的js组合寄生继承
*/
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());

就是这段代码⬆,这也是JavaScript高程(红宝书)中推荐的一种,一直用,从未失手,结果现在马失前蹄。。。

我们再回顾下它的报错:

图片 9

再打印它的原型看看:

图片 10

怎么看都没问题,因为按照原型链回溯规则,Date的所有原型方法都可以通过MyDate对象的原型链往上回溯到。
再仔细看看,发现它的关键并不是找不到方法,而是this is not a Date object.

嗯哼,也就是说,关键是:由于调用的对象不是Date的实例,所以不允许调用,就算是自己通过原型继承的也不行

ES6的class语法糖

不知道为什么标题都是跟吃的有关

可能是因为到了半夜吧(虚

在学ES6之前,我们苦苦背下JS继承的典型方法

学习ES6后,发现官方鸡贼地给我们一个语法糖——class。它可以看作是构造函数穿上了统一的制服,所以class的本质依然是函数,一个构造函数。

class是es6新定义的变量声明方法(复习:es5的变量声明有var
function和隐式声明 es6则新增let const class
import),它的内部是严格模式。class不存在变量提升

例:

//定义类

classPoint{

    constructor(x,y){

        this.x=x;

        this.y=y;

    }

    toString(){

        return'(‘+this.x+’, ‘+this.y+’)’;

    }

}

constructor就是构造函数,不多说,跟c++学的时候差不多吧,this对象指向实例。

类的所有方法都定义在类的prototype属性上面,在类的内部定义方法不用加function关键字。在类的外部添加方法,请指向原型,即实例的__proto__或者类的prototype。

Object.assign方法可以很方便地一次向类添加多个方法。

Object.assign(Point.prototype,{toString(){},toValue(){}});

为什么无法被继承?

首先,看看MDN上的解释,上面有提到,JavaScript的日期对象只能通过JavaScript Date作为构造函数来实例化。

图片 11

然后再看看stackoverflow上的回答:

图片 12

有提到,v8引擎底层代码中有限制,如果调用对象的[[Class]]不是Date,则抛出错误。

总的来说,结合这两点,可以得出一个结论:

要调用Date上方法的实例对象必须通过Date构造出来,否则不允许调用Date的方法

私有的,静态的,实例的

该如何实现继承?

虽然原因找到了,但是问题仍然要解决啊,真的就没办法了么?当然不是,事实上还是有不少实现的方法的。

私有方法,私有属性

类的特性是封装,在其他语言的世界里,有private、public和protected来区分,而js就没有

js在es5的时代,尝试了一些委婉的方法,比如对象属性的典型的set和get方法,在我之前说的JS的数据属性和访问器属性

现在es6规定,可以在class里面也使用setter和getter:

class MyClass {

constructor() { // … }

get prop() { return ‘getter’; }

set prop(value) { console.log(‘setter: ‘+value); }

}

let inst = new MyClass();

inst.prop = 123; // setter: 123

inst.prop // ‘getter’

那么在这次es6的class里面,如何正式地去表示私有呢?

方法有叁:

1,老办法,假装私有。私有的东西,命名前加个下划线,当然了这只是前端程序员的自我暗示,实际上在外部应该还是可以访问得到私有方法。

2,乾坤大挪移。把目标私有方法挪出class外,class的一个公有方法内部调用这个外部的“私有”方法。

class Widget {

foo (baz) { bar.call(this, baz); } // …

}

function bar(baz) { return this.snaf = baz; }

3,ES6顺风车,SYMBOL。利用Symbol值的唯一性,将私有方法的名字命名为一个Symbol值。Symbol是第三方无法获取的,所以外部也就无法偷看私有方法啦。

const bar = Symbol(‘bar’);

const snaf = Symbol(‘snaf’);

export default class myClass{

// 公有方法

foo(baz) { this[bar](baz); }

// 私有方法

[bar](baz) { return this[snaf] = baz; }

// … };

那属性怎么私有化呢?现在还不支持,但ES6有一个提案,私有属性应在命名前加#号。

暴力混合法

首先,说说说下暴力的混合法,它是下面这样子的:

图片 13

说到底就是:内部生成一个Date对象,然后此类暴露的方法中,把原有Date中所有的方法都代理一遍,而且严格来说,这根本算不上继承(都没有原型链回溯)。

静态方法,静态属性

类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。如果静态方法包含this关键字,这个this指的是类,而不是实例。父类的静态方法,可以被子类继承。

 ES6 明确规定,Class 内部只有静态方法,没有静态属性。

声明一个静态属性,目前只支持以下写法,定义在外部:

class Foo {

}

Foo.prop = 1;

Foo.prop // 1

ES6当然也有提案,静态属性的声明采用static关键字,不过也是只提案。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图
Copyright @ 2010-2019 澳门金沙30064在线网站 版权所有