永利402游戏网站

当前位置:永利402游戏网站-永利402com官方网站 > 永利402游戏网站 > 如何继承 Date 对象?由一道题彻底弄懂 JS 继承

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

来源:http://www.xtcsyb.com 作者:永利402游戏网站-永利402com官方网站 时间:2019-11-07 17:16

几种继承的细微区别

虽然上述提到的三种方法都可以达到继承Date的目的-混合法严格说不能算继承,只不过是另类实现。

于是,将所有能打印的主要信息都打印出来,分析几种继承的区别,大致场景是这样的:

可以参考:( 请进入调试模式)

从上往下,1, 2, 3, 4四种继承实现分别是:(排出了混合法)

  • ES6的Class大法
  • 经典组合寄生继承法
  • 本文中的取巧做法,Date构造实例,然后更改__proto__的那种
  • ES6的Class大法,Babel打包后的实现(无法正常调用的)

~~以下是MyDate们的prototype~~~ Date {constructor: ƒ, getTest: ƒ} Date {constructor: ƒ, getTest: ƒ} Date {getTest: ƒ, constructor: ƒ} Date {constructor: ƒ, getTest: ƒ} ~~以下是new出的对象~~~ Sat Jan 13 2018 21:58:55 GMT+0800 (CST) MyDate2 {abc: 1} Sat Jan 13 2018 21:58:55 GMT+0800 (CST) MyDate {abc: 1} ~~以下是new出的对象的Object.prototype.toString.call~~~ [object Date] [object Object] [object Date] [object Object] ~~以下是MyDate们的__proto__~~~ ƒ Date() { [native code] } ƒ () { [native code] } ƒ () { [native code] } ƒ Date() { [native code] } ~~以下是new出的对象的__proto__~~~ Date {constructor: ƒ, getTest: ƒ} Date {constructor: ƒ, getTest: ƒ} Date {getTest: ƒ, constructor: ƒ} Date {constructor: ƒ, getTest: ƒ} ~~以下是对象的__proto__与MyDate们的prototype比较~~~ true true true true

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
~~~~以下是MyDate们的prototype~~~~~~~~~
Date {constructor: ƒ, getTest: ƒ}
Date {constructor: ƒ, getTest: ƒ}
Date {getTest: ƒ, constructor: ƒ}
Date {constructor: ƒ, getTest: ƒ}
 
~~~~以下是new出的对象~~~~~~~~~
Sat Jan 13 2018 21:58:55 GMT+0800 (CST)
MyDate2 {abc: 1}
Sat Jan 13 2018 21:58:55 GMT+0800 (CST)
MyDate {abc: 1}
 
~~~~以下是new出的对象的Object.prototype.toString.call~~~~~~~~~
[object Date]
[object Object]
[object Date]
[object Object]
 
~~~~以下是MyDate们的__proto__~~~~~~~~~
ƒ Date() { [native code] }
ƒ () { [native code] }
ƒ () { [native code] }
ƒ Date() { [native code] }
 
~~~~以下是new出的对象的__proto__~~~~~~~~~
Date {constructor: ƒ, getTest: ƒ}
Date {constructor: ƒ, getTest: ƒ}
Date {getTest: ƒ, constructor: ƒ}
Date {constructor: ƒ, getTest: ƒ}
 
~~~~以下是对象的__proto__与MyDate们的prototype比较~~~~~~~~~
true
true
true
true

看出,主要差别有几点:

  1. MyDate们的__proto__指向不一样
  2. Object.prototype.toString.call的输出不一样
  3. 对象本质不一样,可以正常调用的1, 3都是Date构造出的,而其它的则是MyDate构造出的

我们上文中得出的一个结论是:由于调用的对象不是由Date构造出的实例,所以不允许调用,就算是自己的原型链上有Date.prototype也不行

但是这里有两个变量:分别是底层构造实例的方法不一样,以及对象的Object.prototype.toString.call的输出不一样
(另一个MyDate.__proto__可以排除,因为原型链回溯肯定与它无关)

万一它的判断是根据Object.prototype.toString.call来的呢?那这样结论不就有误差了?

于是,根据ES6中的,Symbol.toStringTag,使用黑魔法,动态的修改下它,排除下干扰:

// 分别可以给date2,date3设置 Object.defineProperty(date2, Symbol.toStringTag, { get: function() { return "Date"; } });

1
2
3
4
5
6
// 分别可以给date2,date3设置
Object.defineProperty(date2, Symbol.toStringTag, {
    get: function() {
        return "Date";
    }
});

然后在打印下看看,变成这样了:

[object Date] [object Date] [object Date] [object Object]

1
2
3
4
[object Date]
[object Date]
[object Date]
[object Object]

可以看到,第二个的MyDate2构造出的实例,虽然打印出来是[object Date],但是调用Date方法仍然是有错误

图片 1

此时我们可以更加准确一点的确认:由于调用的对象不是由Date构造出的实例,所以不允许调用

而且我们可以看到,就算通过黑魔法修改Object.prototype.toString.call,内部的[[Class]]标识位也是无法修改的。
(这块知识点大概是Object.prototype.toString.call可以输出内部的[[Class]],但无法改变它,由于不是重点,这里不赘述)。

ES6 面向对象

ES6中引入了Class(类)这个概念,通过关键字class可以创建一个类。类的数据类型就是函数,类的所有方法都定义在prototype属性上。

class Person () {
        constructor (x,y) {
              this.name= x
              this.age= y
        }
        sayName () {
                alert("快乐")
        }
}
var liHua= new Person("张俊泽",26)

注: 可以理解为constuctor中的属性和方法为ES5中的构造函数部分,和constructor同级的是ES5中原型上的方法和属性。


ES6的继承通过extends关键字实现

class Father(){}
class Child extends Father {
        constructor(x,y,color){
                  super(x,y)
                  this.color= color
        }
        toString() {
                retunr "世界和平!"
        }
}

上面代码中,constructor方法和toString方法之中,都出现了super关键字,它在这里表示父类的构造函数,用来新建父类的this对象。

子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象。


类的prototype和__proto__属性

Class作为构造函数的语法唐,同时有prototype和__proto__属性,因此存在两条继承链:

①  子类的__proto__,表示构造函数的继承,总是指向父类

②  子类的prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性。

class Father {

}

class Child extends Father{

          constructor () {

                  super()

          }

}

var childOne= new Child()

Child.__proto__ ==  Father        //  true

childOne.__proto__ ==  Child.prototype        //  true

Child.prototype.__proto__ ==  Fahter.prototype            //  true

ES5黑魔法

然后,再看看ES5中如何实现?

// 需要考虑polyfill情况 Object.setPrototypeOf = Object.setPrototypeOf || function(obj, proto) { obj.__proto__ = proto; return obj; }; /** * 用了点技巧的继承,实际上返回的是Date对象 */ function MyDate() { // bind属于Function.prototype,接收的参数是:object, param1, params2... var dateInst = new(Function.prototype.bind.apply(Date, [Date].concat(Array.prototype.slice.call(arguments))))(); // 更改原型指向,否则无法调用MyDate原型上的方法 // ES6方案中,这里就是[[prototype]]这个隐式原型对象,在没有标准以前就是__proto__ Object.setPrototypeOf(dateInst, MyDate.prototype); dateInst.abc = 1; return dateInst; } // 原型重新指回Date,否则根本无法算是继承 Object.setPrototypeOf(MyDate.prototype, Date.prototype); MyDate.prototype.getTest = function getTest() { return this.getTime(); }; let date = new MyDate(); // 正常输出,譬如1515638988725 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
27
28
29
30
31
32
33
34
35
// 需要考虑polyfill情况
Object.setPrototypeOf = Object.setPrototypeOf ||
function(obj, proto) {
    obj.__proto__ = proto;
 
    return obj;
};
 
/**
* 用了点技巧的继承,实际上返回的是Date对象
*/
function MyDate() {
    // bind属于Function.prototype,接收的参数是:object, param1, params2...
    var dateInst = new(Function.prototype.bind.apply(Date, [Date].concat(Array.prototype.slice.call(arguments))))();
 
    // 更改原型指向,否则无法调用MyDate原型上的方法
    // ES6方案中,这里就是[[prototype]]这个隐式原型对象,在没有标准以前就是__proto__
    Object.setPrototypeOf(dateInst, MyDate.prototype);
 
    dateInst.abc = 1;
 
    return dateInst;
}
 
// 原型重新指回Date,否则根本无法算是继承
Object.setPrototypeOf(MyDate.prototype, Date.prototype);
 
MyDate.prototype.getTest = function getTest() {
    return this.getTime();
};
 
let date = new MyDate();
 
// 正常输出,譬如1515638988725
console.log(date.getTest());

一眼看上去不知所措?没关系,先看下图来理解:(原型链关系一目了然)

图片 2

可以看到,用的是非常巧妙的一种做法:

  • 正常继承的情况如下:
    • 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的所有原型方法

  • 这样通过一个巧妙的欺骗技巧,就实现了完美的Date继承。不过补充一点,MDN上有提到尽量不要修改对象的[[Prototype]],因为这样可能会干涉到浏览器本身的优化。

如果你关心性能,你就不应该在一个对象中修改它的 [[Prototype]]

图片 3

面向对象的语言都有一个类的概念,通过类可以创建多个具有相同方法和属性的对象,ES6之前并没有类的概念,在ES6中引入类class.

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

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

原文出处: 撒网要见鱼   

ES5 面向对象

ES6大法

当然,除了上述的ES5实现,ES6中也可以直接继承(自带支持继承Date),而且更为简单:

class MyDate extends Date { constructor() { super(); this.abc = 1; } getTest() { return this.getTime(); } } let date = new MyDate(); // 正常输出,譬如1515638988725 console.log(date.getTest());

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();
 
// 正常输出,譬如1515638988725
console.log(date.getTest());

对比下ES5中的实现,这个真的是简单的不行,直接使用ES6的Class语法就行了。

而且,也可以正常输出。

注意:这里的正常输出环境是直接用ES6运行,不经过babel打包,打包后实质上是转化成ES5的,所以效果完全不一样

创建对象(四种模式简介,此外还有动态原型模式、寄生构造函数模式、稳妥构造函数模式等)

一、工厂模式


function createPerson (Name,Age,Job) {

      var man= new Object();

      man.name= Name;

      man.age= Age;

      man.job= Job;

      man.sayName= function () {

              alert(this.name)

    }

  return  man;

}

var personOne=  createPerson ("Erric",26,"Engineer");

var personTwo=  createPerson ("Lori",26,"teacher");

优点:解决了多个相似对象的创建问题

缺点: ①  对象识别问题无法解决(即怎么知道一个对象的类型)

二、构造函数模式

function Person (Name,Age,Job) {

      this.name = Name;

      this.age = Age;

      this.job= Job;

      this.sayName= function () {

              alert(this.name)

      }

}

var personOne=  new Person("Erric",26,"Engineer");

var personTwo=  new Person("Lori",26,"teacher");

注一: 若不使用new操作符直接调用函数,那么其属性和方法都会被添加到window对象里面(因为在全局作用域调用一个方法时,this总是指向window对象)

如: Person("Erric",26,"Enginee")

        window.sayName()  //  弹出 "Erric"

          window.name            //  "Erric"

          window.age              //  26

注二: new 操作符实际上进行了以下操作

          ① 创建一个新的对象

          ② 将构造函数的作用域赋给新对象(this指向了这个新的对象)

          ③ 执行构造函数中的代码(为这个新对象添加属性)

          ④ 返回这个新的对象

优点:① 不用显式的创建对象

            ② 将属性和方法赋给了this对象

            ③ 没有return语句

缺点:①  每个方法都要在每个实例上重新创建一遍(personOne和personTwo中的sayName方法不是同一个方法,每个函数都是一个对象,故每  定义了一个函数就实例化了一个对象)。

            此问题也可以通过将方法单独抽出来解决(但是方法一多,都移到全局的话封装性就无从谈起),如下:

            function Person (Name,Age,Job) {

                    this.name = Name;

                      this.age = Age;

                      this.job= Job;

                      this.sayName= sayName

            }

            function sayName() {

                    alert(this.name)

              }

            var personOne=  new Person("Erric",26,"Engineer");

            var personTwo=  new Person("Lori",26,"teacher");

            ② 若是将公共的sayName方法移到全局,那么又没有封装性可言了。


三、原型模式

function Person () {

}

Person.prototype.name= "Erric"

Person.prototype.age= "28"

Person.prototype.job= "Job"

Person.prototype.sayName= function () {

        alert(this.sayName)

}

优点:①  解决了函数共用的问题,不用每个实例都创建一遍方法。

缺点:①  不能传参

            ② 如果实例中修改了原型中的属性(引用类型)或方法,那么这个属性或方法会被彻底的修改,而影响到其他实例。


四、构造函数+原型组合模式

function Person (Name,Age,Job) {

          this.name= Name

          this.age= Age

          this.job= Job

}

Person.prototype.sayName= function () {

          alert(this.name)

}

// 上面往原型上添加属性和方法的也可如下写,但是此时原型的constructor不指向Person构造函数,而是指向Object,因为Person.prototype就像一个新的对象实例,它的__proto__指向Object原型。

//  Person.prototype= {

          constructor: Person,            // 重新再实例中定义constructor的指向,覆盖Object原型中的constructor指向

          sayName: function () {

                  alert(this.name)

          }

}

var personOne=  new Person("Erric",26,"Engineer");

var personTwo=  new Person("Lori",26,"teacher");


原型对象的理解(重要)

1.首先得明白以下三点:

① 每个函数(含构造函数)都有一个prototype属性,指向Person原型

② 每个实例都有一个__proto__属性,也指向Person原型

③ 每个原型都有一个constructor属性,指向其对应的构造函数

构造函数、实例、原型三者关系如下图:

图片 4

2.万物皆对象,说明原型链的最开始点都是Object,所以任何一个引用类型的 instanceof Object都会返回true。


如何快速判断是否继承?

其实,在判断继承时,没有那么多的技巧,就只有关键的一点:[[prototype]]__ptoto__)的指向关系

譬如:

console.log(instance instanceof SubClass); console.log(instance instanceof SuperClass);

1
2
console.log(instance instanceof SubClass);
console.log(instance instanceof SuperClass);

实质上就是:

  • SubClass.prototype是否出现在instance的原型链上
  • SuperClass.prototype是否出现在instance的原型链上

然后,对照本文中列举的一些图,一目了然就可以看清关系。有时候,完全没有必要弄的太复杂。

类的继承(两种方式)

一、原型链继承

        对于什么是原型链?

        每个构造函数都有一个原型对象,原型对象的constructor指向这个构造函数本身,而实例的__proto__属性又指向原型对象。这个假设一个实例的__proto__内部指针指向其原型,而它的原型又是另一个类型的实例,那么它的原型又将指向另一个原型,另一个原型也包含一个指向它的构造函数的指针,假设另一个原型又是另一个类型的实例,这样层层递进,就构成了实例与原型的链条,这就是原型链的基本概念。

实现原型链的继承方式基本如下:

function Father () {

      this.appearance = "beautiful"

}

Father.prototype.sayHappy = function () {

        alert("快乐")

}

function Child () {

          this.name= "Jhon"

}

Child.prototype= new Father()        //  继承了父类的方法和属性

Child.prototype.addArr= [1,2,3,4,5]

var child= new Child()
child.sayHappy()          //  弹出“快乐”
child.appearance        //  "beautiful"

child.addArr                      //  [1,2,3,4,5]

原型链继承的缺点:①  不能传参  ② 若原型上的方法时引用类型的话,不小心被修改了的话会影响其他实例。


二、借助构造函数继承(利用calll和apply改变this指针)

基本思路:在子类型构造函数的内部调用超类型的构造函数。

function Father (Hobby){

      this.hobby= Hobby

}

Father.prototype.sayHappy = function () {

      alert("快乐")

}

function Child () {

      this.name= "Jhon"

      Father.call(this,"Play Games")          //  或者Father.apply(this,["Play Games"]),继承了Father的属性和方法

}

var child =  new Child()
child.sayHappy                // 没有反应,原型上的方法和属性不会继承
child.hobby                      //  "Play Games"

借助构造函数继承的缺点:①  方法都在构造函数中定义,函数的复用无从谈起    ②  超类中的方法对子类不可见。


三、组合继承(也叫经典继承,将原型链和借助构造函数继承相结合)

思路:1.原型链实现对原型属性和方法的继承;

            2.构造函数实现对实例属性的继承,且调用基类的构造函数;

function Father(Hobby) {

          this.hobby= Hobby;

          this.exGF = ['cuihua', 'erya']

}

Father.prototype.sayHappy = function () {

          alert("快乐")

}

function Child () {

          this.name= "Jhon"

          Father.call(this,"Play Games")          //  或者Father.apply(this,["Play Games"]),继承了Father的属性和方法

}

Child.prototype= new Father()

Student.prototype.sayName= function () {

          alert(this.name);

}

var liHua= new Child()

liHua.sayHappy()

liHua.sayName()


检测对象属性的两种方法:

object.hasOwnProperty(属性名),这个方法检测的是对象实例的属性(若是返回true),不能检测原型上的属性。

in操作符,检测对象所有的属性,包含原型和实例上的额,有的话就返回true.


判断一个原型是否在某个实例的原型链上:

Person.prototype.isPropotypeOf(personOne)    //  true

Object.prototype.isPropotypeOf(personOne)      //  true

判断一个构造函数是否在实例的原型链中出现过:

personOne instanceof Person                //  true

personOne instanceof Object                //  true


new MyClass()中,都做了些什么工作

function MyClass() { this.abc = 1; } MyClass.prototype.print = function() { console.log('this.abc:' + this.abc); }; let instance = new MyClass();

1
2
3
4
5
6
7
8
9
function MyClass() {
    this.abc = 1;
}
 
MyClass.prototype.print = function() {
    console.log('this.abc:' + this.abc);
};
 
let instance = new MyClass();

譬如,上述就是一个标准的实例对象生成,都发生了什么呢?

步骤简述如下:(参考MDN,还有部分关于底层的描述略去-如[[Class]]标识位等)

  1. 构造函数内部,创建一个新的对象,它继承自MyClass.prototypelet instance = Object.create(MyClass.prototype);
  2. 使用指定的参数调用构造函数MyClass,并将 this绑定到新创建的对象,MyClass.call(instance);,执行后拥有所有实例属性
  3. 如果构造函数返回了一个“对象”,那么这个对象会取代整个new出来的结果。如果构造函数没有返回对象,那么new出来的结果为步骤1创建的对象。

(一般情况下构造函数不返回任何值,不过用户如果想覆盖这个返回值,可以自己选择返回一个普通对象来覆盖。当然,返回数组也会覆盖,因为数组也是对象。)

结合上述的描述,大概可以还原成以下代码:(简单还原,不考虑各种其它逻辑)

let instance = Object.create(MyClass.prototype); let innerConstructReturn = MyClass.call(instance); let innerConstructReturnIsObj = typeof innerConstructReturn === 'object' || typeof innerConstructReturn === 'function'; return innerConstructReturnIsObj ? innerConstructReturn : instance;

1
2
3
4
5
let instance = Object.create(MyClass.prototype);
let innerConstructReturn = MyClass.call(instance);
let innerConstructReturnIsObj = typeof innerConstructReturn === 'object' || typeof innerConstructReturn === 'function';
 
return innerConstructReturnIsObj ? innerConstructReturn : instance;
  • 注意⚠️:
    • 普通的函数构建,可以简单的认为就是上述步骤
    • 实际上对于一些内置类(如Date等),并没有这么简单,还有一些自己的隐藏逻辑,譬如[[Class]]标识位等一些重要私有属性。
      • 譬如可以在MDN上看到,以常规函数调用Date(即不加 new 操作符)将会返回一个字符串,而不是一个日期对象,如果这样模拟的话会无效

觉得看起来比较繁琐?可以看下图梳理:

图片 5

那现在再回头看看。

什么是构造函数?

如上述中的MyClass就是一个构造函数,在内部它构造出了instance对象

什么是实例对象?

instance就是一个实例对象,它是通过new出来的?

实例与构造的关系

有时候浅显点,可以认为构造函数是xxx就是xxx的实例。即:

let instance = new MyClass();

1
let instance = new MyClass();

此时我们就可以认为instanceMyClass的实例,因为它的构造函数就是它

[[Class]]与Internal slot

这一部分为补充内容。

前文中一直提到一个概念:Date内部的[[Class]]标识

其实,严格来说,不能这样泛而称之(前文中只是用这个概念是为了降低复杂度,便于理解),它可以分为以下两部分:

  • 在ES5中,每种内置对象都定义了 [[Class]] 内部属性的值,[[Class]] 内部属性的值用于内部区分对象的种类
    • Object.prototype.toString访问的就是这个[[Class]]
    • 规范中除了通过Object.prototype.toString,没有提供任何手段使程序访问此值。
    • 而且Object.prototype.toString输出无法被修改
  • 而在ES5中,之前的 [[Class]] 不再使用,取而代之的是一系列的internal slot
    • Internal slot 对应于与对象相关联并由各种ECMAScript规范算法使用的内部状态,它们没有对象属性,也不能被继承
    • 根据具体的 Internal slot 规范,这种状态可以由任何ECMAScript语言类型或特定ECMAScript规范类型值的值组成
    • 通过Object.prototype.toString,仍然可以输出Internal slot值
    • 简单点理解(简化理解),Object.prototype.toString的流程是:如果是基本数据类型(除去Object以外的几大类型),则返回原本的slot,如果是Object类型(包括内置对象以及自己写的对象),则调用Symbol.toStringTag
    • Symbol.toStringTag方法的默认实现就是返回对象的Internal slot,这个方法可以被重写

这两点是有所差异的,需要区分(不过简单点可以统一理解为内置对象内部都有一个特殊标识,用来区分对应类型-不符合类型就不给调用)。

JS内置对象是这些:

"Arguments", "Array", "Boolean", "Date", "Error", "Function", "JSON", "Math", "Number", "Object", "RegExp", "String"

1
"Arguments", "Array", "Boolean", "Date", "Error", "Function", "JSON", "Math", "Number", "Object", "RegExp", "String"

ES6新增的一些,这里未提到:(如Promise对象可以输出[object Promise]

而前文中提到的:

Object.defineProperty(date, Symbol.toStringTag, { get: function() { return "Date"; } });

1
2
3
4
5
Object.defineProperty(date, Symbol.toStringTag, {
    get: function() {
        return "Date";
    }
});

它的作用是重写Symbol.toStringTag,截取date(虽然是内置对象,但是仍然属于Object)的Object.prototype.toString的输出,让这个对象输出自己修改后的[object Date]

但是,仅仅是做到输出的时候变成了Date,实际上内部的internal slot值并没有被改变,因此仍然不被认为是Date

stackoverflow上早就有答案了!

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

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

图片 6

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

经典的继承法有何问题

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

/** * 经典的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高程(红宝书)中推荐的一种,一直用,从未失手,结果现在马失前蹄。。。

我们再回顾下它的报错:

图片 7

再打印它的原型看看:

图片 8

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

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

ES6写法,然后Babel打包

虽然说上述ES6大法是可以直接继承Date的,但是,考虑到实质上大部分的生产环境是:ES6 + Babel

直接这样用ES6 + Babel是会出问题的

不信的话,可以自行尝试下,Babel打包成ES5后代码大致是这样的:

图片 9

然后当信心满满的开始用时,会发现:

图片 10

对,又出现了这个问题,也许这时候是这样的⊙?⊙

因为转译后的ES5源码中,仍然是通过MyDate来构造
MyDate的构造中又无法修改属于Date内部的[[Class]]之类的私有标志,
因此构造出的对象仍然不允许调用Date方法(调用时,被引擎底层代码识别为[[Class]]标志不符合,不允许调用,抛出错误)

由此可见,ES6继承的内部实现和Babel打包编译出来的实现是有区别的。
(虽说Babel的polyfill一般会按照定义的规范去实现的,但也不要过度迷信)。

前言

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

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

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

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

  • 此类继承自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中经典的组合寄生法写了一个继承,然后,刚准备完美收工,一运行,却出现了以下的情景:

图片 11

但是的心情是这样的:

本文由永利402游戏网站-永利402com官方网站发布于永利402游戏网站,转载请注明出处:如何继承 Date 对象?由一道题彻底弄懂 JS 继承

关键词:

上一篇:JavaScript 浓烈之继续的有余方法和优劣点

下一篇:没有了