关于javascript:认识JS中的Class

5次阅读

共计 5049 个字符,预计需要花费 13 分钟才能阅读完成。

1.JS 中没有真正的类!

JavaScript 和面向类的语言不同,它并没有类来作为对象的形象模式。JavaScript 中只有对象,而并没有真正的类 。JS 只是利用了函数的一种非凡个性——所有的函数默认都会领有一个名为 prototype 的 私有 并且 不可枚举 的属性,它会指向另一个对象,来模仿类的行为。

要留神的是,如果应用内置的 bind函数来生成一个硬绑定函数的话,该函数是没有 prototype 属性的,指标函数的 prototype 会代替硬绑定函数的 prototype。在这样的函数上应用 instanceof 或者 new 的话,相当于间接对指标函数应用。

function Animal() {};
console.log(Animal.prototype);// {}

在 JS 中,new Animal()看起来像是实例化了 Animal 类,然而事实上并非如此。

const a = new Animal();
console.log(Object.getPrototypeOf(a) === Animal.prototype);// true

在面向类的语言中,类能够被复制(或者说实例化)屡次。实例化一个类就意味着“把类的行为复制到物理对象中”,对于每一个新实例来说都会反复这个过程。

然而在 JS 中,并没有相似的复制机制。你不能创立一个类的多个实例,只能创立多个对象,它们 [[[Prototype]]](https://segmentfault.com/a/11…) 关联的是同一个对象。然而在默认状况下并不会进行复制,因而这些对象之间并不会齐全失去分割,它们是相互关联的。new Animal() 会生成一个新对象(咱们称之为 a),这个新对象的外部链接 [[[Prototype]]](https://segmentfault.com/a/11…) 关联的是 Animal.prototype 对象。咱们并没有初始化一个类,实际上咱们并没有从“类”中复制任何行为到一个对象中,只是让两个对象相互关联。

2.JS 中的构造函数是什么?

function Animal() {};
const a = new Animal();

在 JS 中并没有真正的类,然而在看到这两行代码时,我仍然会感觉 Animal 是一个类。这是为什么呢?在我看来,一个起因在于呈现了 new 操作符,而在面向类的语言中,须要应用 new 操作符。另一个起因是,在 new Animal() 中,Animal 调用形式 特地像是调用形式很像实例化类时类 构造函数的调用形式

然而实际上,Animal和你程序中的其余函数没有任何区别。函数自身并不是构造函数,只是当咱们在一般的函数调用后面加上 new 关键字之后,就会把这个函数调用变成一个“结构函数调用”(new 会劫持所有一般函数并用结构对象的模式来调用它)。

简略地说,在 JS 中,“构造函数”能够解释为应用 new 操作符调用的函数。然而,咱们须要晓得的是,JS 中的函数并不是构造函数,只有应用 new 时,函数调用会变成 结构函数调用

3.JS 中的“面向类”

function Animal(name) {this.name = name;}
Animal.prototype.sayName = function () {console.log(this.name);
};

const dog = new Animal('dog');
const cat = new Animal('cat');

dog.sayName(); // dog
cat.sayName(); // cat
console.log(dog.constructor); //  [Function: Animal]
  1. this.name = name 通过 this 的隐式绑定 给每个对象都增加了 name 属性,有点像类实例封装的数据值。
  2. Animal.prototype.sayName = ... 会给 Animal.prototype 对象增加一个属性(函数)。在创立的过程中,dogcat 的内),这个新对象的外部链接 [[[Prototype]]](https://segmentfault.com/a/11…) 都会关联到 Animal.prototype 上。当 dogcat 中无奈找到 sayName 时,它会在 Animal.prototype 上找到。

    须要留神的是,dog.constructor指向了 Animal 函数,所以 dogconstructor属性,仿佛在代表着 dog 是由谁结构的。然而事实上,这仅仅是看起来如此,因为 dog 自身并没有 constructor 属性,constructor属性和 sayName 一样,同样是Animal.prototype 的属性,这个属性和dog(或者cat)之间没有什么分割。

    对于 Animal.prototype 而言,constructor也仅仅是 Animal 函数在申明时所产生的一个默认属性而已(它是不可枚举的,但它是能够更改的),

    Animal.prototype.constructor = 'animal';
    console.log(dog.constructor); // 'animal'

    当扭转 Animal.prototype 的指向时,constructor属性的指向同样变得令人蛊惑。这是因为 fish 上不存在 constructor 属性,所以查找的是 Animal.prototype(即{})上的constructor 属性,然而 {} 也没有 constructor 属性,所以会持续查找到Object.prototype。这个对象有 constructor 属性,指向内置的 Object 函数。

    Animal.prototype = {};
    const fish = new Animal();
    console.log(fish.constructor); // [Function: Object]

    当然,咱们能够手动指定 constructor 属性。

    Animal.prototype = {};
    Object.defineProperty(Animal.prototype, 'constructor', {
     enumerable: false, // 不可枚举
     writable: true,
     configurable: true,
     value: Animal, // 让 constructor 指向 Animal
    });
    const fish = new Animal();
    console.log(fish.constructor); // [Function: Animal]
    

    总而言之,constructor属性仅仅是一个一般的,可能会被更改的属性,dog.constructor这种援用是不牢靠的。

4. 继承

JS 中原型格调的[继承](https://segmentfault.com/a/11…):

function Animal(name) {this.name = name;}
Animal.prototype.sayName = function () {console.log(this.name);
};

function Dog(name, color) {Animal.call(this, name);
    this.color = color;
}
// 创立了一个新的 Dog.prototype 对象并关联到 Animal.prototype
Dog.prototype = Object.create(Animal.prototype);
//Object.setPrototypeOf(Dog.prototype, Animal.prototype)
// 留神!当初 Dog.prototype.constructor 的指向曾经变为了 Animal
Dog.prototype.sayName = function () {console.log('重写 sayName');
    // 显式多态,调用 Animal.prototype.sayName
    Animal.prototype.sayName.call(this);
};

const teddy = new Dog('泰迪', '棕色');
teddy.sayName(); // 重写 sayName 泰迪

在申明 Dog 时,和所有的函数一样,Dog会有一个 prototype 属性指向默认对象(假如该对象名为 originObj),然而originObj 并不是咱们想要的 Foo.prototype。因而咱们创立了一个新对象并把这个新对象关联到Foo.prototype,摈弃默认对象originObj。下面代码是通过Object.create 实现的。当然也能够通过 ES6 的 Object.setPrototypeOf 实现,Object.setPrototypeOf(Dog.prototype, Animal.prototype),这个函数是批改 originObj,而不是放弃originObj,也因而通过Object.setPrototypeOf 的话,Dog.prototype.constructor指向是没有发生变化的。

咱们能够应用 instanceof 来查看 teddyDog或者 Animal 等的关系。

console.log(teddy instanceof Animal);

instanceof左侧是一个 一般的对象 a ,右侧是一个 函数 B ,该操作符会查看 B.prototype 是否存在于 a 的[[[prototype]]](https://segmentfault.com/a/11…)链上。

如果想要查看 两个一般对象 之间的关系的话,能够应用isPrototypeOf

console.log(Animal.prototype.isPrototypeOf(teddy));

5.ES6 中的 class 语法

你可能会认为 ES6 的 class 语法是向 JS 中引入了一种新的“类”机制,其实不是这样。class 基本上只是 [[[prototype]](原型链)](https://segmentfault.com/a/11…)机制的一种语法糖。

class Animal {constructor(name) {this.name = name;}
    sayName() {console.log(this.name);
    }
}

class Dog extends Animal {constructor(name, color) {super(name);
        this.color = color;
    }
    sayName() {console.log('重写 sayName');
        // 绝对多态
        super.sayName();}
}

const teddy = new Dog('泰迪', 'brown');
teddy.sayName();

除了语法更好看之外,ES6 还解决了什么问题呢?

  1. 不再援用芜杂的 prototype 了。
  2. Dog声 明 时 直 接“继 承”了 Animal,不 再 需 要 通 过 Object.create来 替
    prototype 对象,也不须要设置 __proto__ 或者 Object.setPrototypeOf
  3. 能够通过 super来实现绝对多态,这样任何办法都能够援用原型链下层的同名方
    法。不用应用显使多态的写法了,Animal.prototype.sayName.call(this);
  4. class 字面语法 不能申明属性(只能申明办法)。看起来这是一种限度,然而它会排除
    掉许多不好的状况,否则,原型链末端的“实例”可能会意外地获取
    其余中央的属性(这些属性隐式被所有“实例”所“共享”)。所以,class 语法实际上
    能够帮忙你 防止犯错
  5. 能够通过 extends 很天然地扩大对象(子)类型,甚至是内置的对象(子)类型,比方
    Array 或 RegExp。没有 class ..extends 语法时,想实现这一点是十分艰难的。

须要留神的是,superthis 不同,super不是 动静绑定的。上面的 testObj.sayName 中的 super 并非指向了它以后的 [[[prototype]]](https://segmentfault.com/a/11…) 对象testParen,而是指向了Animal

const testObj = {
 name: 'test',
 sayName: Dog.prototype.sayName,
};
const testParen = {sayName() {console.log('testParen');
 },
};
Object.setPrototypeOf(testObj, testParen);
testObj.sayName();// 重写 sayName test
正文完
 0