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(); // dogcat.sayName(); // catconsole.log(dog.constructor); // [Function: Animal]
this.name = name
通过this的隐式绑定给每个对象都增加了 name 属性,有点像类实例封装的数据值。Animal.prototype.sayName = ...
会给Animal.prototype
对象增加一个属性(函数)。在创立的过程中,dog
和cat
的内 ),这个新对象的外部链接 [[[Prototype]]](https://segmentfault.com/a/11...) 都会关联到Animal.prototype
上。当dog
和cat
中无奈找到sayName
时,它会在Animal.prototype
上找到。须要留神的是,
dog.constructor
指向了Animal
函数,所以dog
的constructor
属性,仿佛在代表着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.prototypeDog.prototype = Object.create(Animal.prototype);//Object.setPrototypeOf( Dog.prototype, Animal.prototype )// 留神!当初 Dog.prototype.constructor 的指向曾经变为了AnimalDog.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
来查看teddy
和Dog
或者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 还解决了什么问题呢?
- 不再援用芜杂的
prototype
了。 Dog
声 明 时 直 接“ 继 承 ” 了Animal
, 不 再 需 要 通 过Object.create
来 替
换prototype
对象,也不须要设置__proto__
或者Object.setPrototypeOf
。- 能够通过
super
来实现绝对多态,这样任何办法都能够援用原型链下层的同名方
法。不用应用显使多态的写法了,Animal.prototype.sayName.call(this);
class
字面语法不能申明属性(只能申明办法)。看起来这是一种限度,然而它会排除
掉许多不好的状况,否则,原型链末端的“实例”可能会意外地获取
其余中央的属性(这些属性隐式被所有“实例”所“共享”)。所以, class 语法实际上
能够帮忙你防止犯错。- 能够通过
extends
很天然地扩大对象(子)类型,甚至是内置的对象(子)类型,比方
Array 或 RegExp 。没有 class ..extends 语法时,想实现这一点是十分艰难的。
须要留神的是,
super
和this
不同,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