class的实现
一谈到编程,我们首先想到的就是O(Object)O(Oriented)P(Programming)编程,也就是面向对象编程。面向对象编程是一种设计思想。如果把程序当做一个人,那么对象就是各个器官,对象里面的各种操作函数就是细胞。
很多语言中面向对象的蓝图都是基于类,比如Pytho、C++、Java。js在es6语法中也引入了类的概念,不过还是基于es5的原型链实现的语法糖。而为什么js一开始并没有用类的概念呢?这都是因为Brendan Eich一开始设计js的时候只是想设计出一门运行在浏览器上能解决他手头上的需求的语言,所以他没有引入类的概念,而是用原型模式来实现js的继承。
既然谈到原型,那肯定大家都对prototype不陌生,这个属性实际是一个指针,指向一个对象,而这个对象包含有特定类型的所有实例共享的属性和方法。讲人话就是:“这个家伙的一些东西,他的儿子孙子也都能同时拥有。这些东西是共享的。”而class就是基于这个原型实现的,至于怎么实现的,我们可以看看代码:
//class
class Hello {
constructor(x) {
this.x = x;
}
greet() {
console.log("Hello, " + this.x)
}
}
let world = new Hello("world");
world.greet()
//es5
var Hello = (function () {
function Hello(x) {
this.x = x;
}
Hello.prototype.greet = function () {
console.log("Hello, " + this.x);
};
return Hello;
}());
var world = new Hello("world");
world.greet();
复制代码
上述的例子很简单地诠释了class的语法在es5中是怎么实现的,有意思的是class的constructor其实就是扮演了构造函数的角色,本质上还是利用构造函数和原型模式来实现class的继承。类的实例调用方法,实际上就是调用原型上的方法。(这里要注意,类的内部默认就是严格模式,所以不需要使用use strict指定运行模式)
原型对象
es5语法
让我们先打印出es5语法实现的world来看看:
var Hello = (function () {
function Hello(x) {
this.x = x;
}
Hello.prototype.greet = function () {
console.log("Hello, " + this.x);
};
return Hello;
}());
var world = new Hello("world");
console.log(world)
复制代码
var Hello = (function () {
function Hello(x) {
this.x = x;
}
Hello.prototype.greet = function () {
console.log("Hello, " + this.x);
};
return Hello;
}());
var world = new Hello("world");
world.__proto__.constructor === Hello //true
world.__proto__ === Hello.prototype //true
Hello.prototype.constructor === Hello //true
复制代码
class语法
再让我们来打印出class实现的world看看:
class Hello {
constructor(x) {
this.x = x;
}
greet() {
console.log("Hello, " + this.x)
}
}
let world = new Hello("world");
console.log(world)
复制代码
class Hello {
constructor(x) {
this.x = x;
}
greet() {
console.log("Hello, " + this.x)
}
}
let world = new Hello("world");
world.__proto__.constructor === Hello //true
world.__proto__ === Hello.prototype //true
Hello.prototype.constructor === Hello //true
复制代码
看完上面的解析,充分证明class确实是es5的语法糖,class的继承还是基于原型链,所以还是建议大家认真去了解js的原型模式和原型链,才能充分理解js的class,不过在实际工作中还是建议使用class,这样会更直观,代码也方便维护。
静态方法
类就是实例的原型,所有在类中定义的方法,都会被实例继承,而类的静态方法不会被实例继承,那么是怎么实现的呢?
// class
class Hello {
constructor(x) {
this.x = x;
}
static greet() {
console.log("Hello, world")
}
}
// es5
var Hello = (function () {
function Hello(x) {
this.x = x;
}
Hello.greet = function () {
console.log("Hello, world");
};
return Hello;
}());
复制代码
我相信有些人看到这的反应就是:
继承
class通过关键字extends来实现继承,这也是比es5通过原型链实现继承的一个优势:清晰方便。那么我们来看看es5语法如何实现继承:
//class
class Hello {
constructor(x) {
this.x = x;
}
static greet() {
console.log("Hello, world")
}
}
class Hi extends Hello {
constructor(x) {
super(x)
}
}
// es5
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var Hello = (function () {
function Hello(x) {
this.x = x;
}
Hello.greet = function () {
console.log("Hello, world");
};
return Hello;
}());
var Hi = (function (_super) {
__extends(Hi, _super);
function Hi(x) {
return _super.call(this, x) || this;
}
return Hi;
}(Hello));
复制代码
还记得阮一峰的《ECMAScript 6 入门》的有关super的介绍吗?
子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super方法,子类就得不到this对象。
上面的代码可以充分诠释阮老师的这几句话,其实最后还是利用call函数来完成实例this的绑定。
结语
以上只是大概讲述一下class是怎么实现的,其实还有很多属性和方法是更骚地操作,以后有时间我会写出跟大家分享。建议大家还是深入理解js的原型模式和原型链,毕竟不论出es6、es7语法,这些都是基于js的基本设计模式实现的。