js中不存在“类”,js除基本类型外,其余都是对象。如果试图在js身上使用“类”思维来编程,会使程序变得异常复杂,且晦涩难懂。由于对象的存在,在js中更适合使用“委托”方式来实现类的“继承”(其实此时已不能称为继承)。
先来看“类”的思想:
1 function Hello(){ 2 } 3 Hello.prototype.helloWorld = function() { 4 console.log("hello "); 5 } 6 function World(){ 7 } 8 World.prototype = Object.create(Hello.prototype);//继承Hello 9 World.prototype.helloWorld = function(){ 10 Hello.prototype.helloWorld.call(this);//伪多态 11 console.log("world !"); 12 } 13 var helloWorld = new World();//实例化World 14 helloWorld.helloWorld(); 15 console.log(helloWorld instanceof World);//true 16 console.log(helloWorld instanceof Hello);//true 17 console.log(World.prototype.isPrototypeOf(helloWorld));//true 18 console.log(Hello.prototype.isPrototypeOf(helloWorld));//true
再来看“委托”的思想:
1 var Hello = { 2 hello(){ 3 console.log("hello "); 4 } 5 }; 6 var World = Object.create(Hello);//World关联到Hello 7 World.world = function(){ 8 this.hello(); 9 console.log("world !"); 10 } 11 var helloWorld = Object.create(World);//产生一个关联到World的新对象,可以在新对象上做进一步操作,比如生成新属性 12 helloWorld.world();//hello world ! 13 console.log(World.isPrototypeOf(helloWorld));//true 14 console.log(Hello.isPrototypeOf(World));//true
使用“委托”后,不再需要伪多态,同时由于省去了晦涩的prototype(prototype的存在就是为了提供“原型继承”机制),使得代码更加简洁。而且,在原型链的判断上也更加清楚。
ES6引入了全新的class关键字,使得js看上去拥有了“类”的能力,但其实则是语法糖。
1 class Animal{ 2 constructor(variety) { 3 this.variety = variety; 4 } 5 variety(){ 6 return this.variety; 7 } 8 } 9 class Dog extends Animal{ 10 constructor(variety){ 11 super(variety); 12 } 13 roar(){ 14 console.log(super.variety() + ": " + "wang wang");//这里也可以不需要super,直接调用variety() 15 } 16 } 17 var dog = new Dog("dog"); 18 dog.roar();//dog: wang wang
下面就是dog对象的内部结构,可以看到其还是使用了原型链。
再来看一下Dog“类”,其和function的结构类似(都有arguments、caller、length、name),且原型链上都有Function.prototype。最关键的是Dog.prototype.__proto__其实就是Animal.prototype,这很好理解了。
通过上面观察,发现class关键字并不神奇,真正神奇的是super关键字。查阅资料发现,class中的每一个方法,js引擎内部都维护着一个叫做[[HomeObject]]的引用,该引用指向方法的宿主对象,通过该对象就可以产生super语义,其内部机制如下:
- 首先根据被调用方法的
[[HomeObject]]
属性值(即当前方法的归属对象),通过Object.getPrototypeOf()
获取原型; - 获取到原型后,检索同名方法;
- 绑定this指向并执行方法函数。
比如,针对Dog类的roar方法,js引擎就维护着一个[[HomeObject]](可以看成是roar方法内部的一个属性,但是无法获取):
super关键字与this关键字不同,this在运行时动态确定,super在声明时确定,也就是说[[HomeObject]]在声明一个类时就已被赋值了(这很好理解):
1 class Animal{ 2 constructor(variety) { 3 this.variety = variety; 4 } 5 variety(){ 6 return this.variety; 7 } 8 } 9 class Dog extends Animal{ 10 constructor(variety){ 11 super(variety); 12 } 13 roar(){ 14 console.log(super.variety() + ": " + "wang wang");//这里也可以不需要super,直接调用variety() 15 } 16 } 17 var dog = new Dog("dog"); 18 dog.roar();//dog: wang wang 19 var roar = dog.roar; 20 var variety = "dog2"; 21 roar.call(window);//es6默认采取“use strict”,输出dog2: wang wang
虽然class关键字使js有了“类”的模样,但其仍然有很多限制,比如class中无法直接定义类属性(java中的static字段),如果有需求,还是需要通过prototype来定义(注意属性屏蔽)。