ES6定义类及其原型的理解

2722 4 年前
以前,JavaScript 使用构造函数来生成一个对象的实例,ES6标准引入 class 类的概念,通过class关键字来定义类。

回忆一下javascript的原型链。

每个函数且只有函数有一个prototype的属性,这个属性指向一个对象,即原型对象。默认情况下,原型对象又包含一个指向这个函数的constractor属性。

每个对象都有一个__proto__的属性,指向生成该对象的构造函数的原型对象,函数也是对象,所以它也有这个属性。

下面我们来看看ES6class定义类及其原型的东西

定义一个类A:

class A {
    constructor(name){
        this.name = name;
    }
    getName(){
        return this.name
    }
    toString(){
        return 'my name is ' + this.name
    }
}

使用class关键字申明一个类,并为其定义了两个方法getNametoString。注意,类的constructor构造函数是必须的,不写时会javascript会默认一个空的函数constructor() {},定义的方法之间不能用逗号,(分号;可以)。

接下来,我们用typeof操作符来看看A的类型:

console.log(typeof A) //function

如上,定义的类A其实是一个函数,同时函数也是一个对象,对象都有__proto__属性,指向其构造函数的原型对象。

console.log(Reflect.getPrototypeOf(A) === Function.prototype); //true

getPrototypeOf即返回对象__proto__指向的原型prototype

前面讲到“每一个函数且只有函数有一个prototype属性”,那么我们来看看类A是否也有。

console.log(Reflect.getOwnPropertyDescriptor(A,'prototype'))

返回如下结果:

{ value: A {},
  writable: false,
  enumerable: false,
  configurable: false
}

Reflect.getOwnPropertyDescriptor基本等同于Object.getOwnPropertyDescriptor,用于得到指定属性的描述对象,没报错,说明类A是有prototype这个属性的。

下面我们再来看看A.prototype这个属性的相关信息

console.log(Object.getOwnPropertyDescriptors(A.prototype))

打印出如下信息:

{
  constructor:
   { value: [Function: A],
     writable: true,
     enumerable: false,
     configurable: true },
  getName:
   { value: [Function: getName],
     writable: true,
     enumerable: false,
     configurable: true },
  toString:
   { value: [Function: toString],
     writable: true,
     enumerable: false,
     configurable: true
   }
 }

也就是表明,类A的原型对象A.prototype有一个constructor属性指向A,在类A中定义的方法是其原型对象的方法。

使用new运算符生成一个类A的实例:

let a = new A('tensweets');
console.log(a.toString()) //my name is tensweets

我们测试一下实例a的与类A的原型关系

console.log(Reflect.getPrototypeOf(a) === A.prototype);//true
console.log(a.constructor === A);//true
console.log(a.constructor === A.prototype.constructor);//true

以上表明,用class定义类其实就是使用构造函数定义的语法糖。

function A(name) {
    this.name = name
}
A.prototype.getName = function () {
    return this.name
};
A.prototype.toString = function () {
    return 'my name is ' + this.name
};

前面我们说到“每个对象都有一个__proto__的属性,指向生成该对象的构造函数的原型对象”,对象的__proto__属性我们叫做原型,函数的prototype指向的对象我们叫做原型对象

函数的prototype属性(原型对象)及对象的原型都是可以修改的,例如:

let o = {
    name:"tensweets.com",
    toString:function () {
        return this.name
    }
};
let obj = {};
console.log(Object.getPrototypeOf(obj) === Object.prototype);//true
Object.setPrototypeOf(obj,o);
console.log(Object.getPrototypeOf(obj) === Object.prototype);//false
console.log(Object.getPrototypeOf(obj) === o);//false

上面,我们使用对象字面量建造一个obj对象,其内部的构建是通过Object函数来进行的,其原型(——proto——)自动指向了Objectprototype属性Object.prototype。后面我们使用Object.setPrototypeOf方法把obj对象的原型指向了另一个对象o

我们再来看看修改原型对象例子:

function Animal(name) {
    this.name = name;
}
let obj = {
  say:function () {
      console.log('I don\'t want to tell you')
  }
};
Animal.prototype.say = function () {
    console.log('my name is ' + this.name)
};
let dog = new Animal('Pluto');
dog.say();//my name is Pluto
Animal.prototype = obj;
let cat = new Animal('Tom')
dog.say();//my name is Pluto
cat.say();//I don't want to tell you

当我们把Animal的原型对象指向另一个对象后,生成的实例cat的原型(__proto__)也指向了这个对象,但在未修改之前生成的实例dog其原型仍然指向以前的。

ES5 里我们通过修改原型链实现继承,ES6里使用关键字extends来实现类的继承。

class A {
    constructor(name){
        this.name = name;
    }
    static fn(){
        return '静态方法'
    }
    getName(){
        return this.name
    }
    toString(){
        return 'my name is ' + this.name
    }
}
class B extends A {
    constructor(name,age){
        super(name);
        this.age = age;
    }
}
let a = new A('a-name');
let b = new B('b-name');

我们来看一下B的类型:

console.log(typeof B)//function

B跟类A一样,类型为函数,那么B一定有一个prototype属性指向其原型对象B.prototype。同B也是一个对象(函数对象),其__proto__指向创建时其构造函数的prototype属性,也就是说B.__proto__应该等于Function.prototype,测试一下

console.log(Object.getPrototypeOf(B) === Function.prototype);//false

然而这里并不相等,说明在extents实现继承的时候,修改了其原型,类似于使用Object.setPrototypeOf(B,新原型)

那我们再来看看为B赋予了那个新原型

console.log(Object.getOwnPropertyDescriptors(Reflect.getPrototypeOf(B)))

输出以下结果:

{ length: 
   { value: 1,
     writable: false,
     enumerable: false,
     configurable: true },
  prototype: 
   { value: A {},
     writable: false,
     enumerable: false,
     configurable: false },
  fn: 
   { value: [Function: fn],
     writable: true,
     enumerable: false,
     configurable: true },
  name: 
   { value: 'A',
     writable: false,
     enumerable: false,
     configurable: true } }

仔细查看发现,B的原型指向了A,正如执行的Object.setPrototypeOf(B,A)。也就是说当BA作为对象:B.proto === A;//true

再来看看B作为函数时其原型对象B.prototype

console.log(Object.getOwnPropertyDescriptors(Reflect.getPrototypeOf(B.prototype)))

输出的结果是:

{ constructor: 
   { value: [Function: A],
     writable: true,
     enumerable: false,
     configurable: true },
  getName: 
   { value: [Function: getName],
     writable: true,
     enumerable: false,
     configurable: true },
  toString: 
   { value: [Function: toString],
     writable: true,
     enumerable: false,
     configurable: true } }

明显,返回的结果是Aprototype指向的对象,也就是说B.prototype这个对象的__proto__指向A.prototype,相当于执行了Object.setPrototypeOf(B.prototype,A.prototype),即B.prototype.__proto__ === A.prototype

作为一个对象,子类B的原型(__proto__属性)指向父类A;作为一个构造函数,子类B的原型对象(prototype属性的原型(__proto__属性)指向父类Aprototype属性。

总结,class类的继承相当于如下代码的处理过程

Object.setPrototypeOf(B.prototype, A.prototype);
Object.setPrototypeOf(B, A);

所以,子类B对象可以使用父类A对象的静态属性,子类B的实例可以使用父类A的原型对象的属性。

© 2018邮箱:11407215#qq.comGitHub沪ICP备12039518号-6