Open OPY-bbt opened 4 years ago
// unnamed let Rectangle = class {}; console.log(Rectangle.name); // output: "Rectangle"
// named let Rectangle = class Rectangle2 {} console.log(Rectangle.name); // output: "Rectangle2"
class 的内容定义在大括号中,并且其中开启了严格模式
constructor 用于创建和初始化类,一个类中有多个constructor时,会报语法错误(SyntaxError: 解析代码时,Javascript引擎发现了不符合语法规范的tokens或token顺序时抛出SyntaxError)。
定义在原型上的方法
Rectangle.staticWidth = 20; Rectangle.prototype.prototypeWidth = 25;
基本的类的定义到此为止。看一下babel是如何处理的
class Animal {
constructor() {
this.name = 'animal';
}
speak() {
console.log('speak');
}
static eat() {
console.log('eat');
}
}
Animal.staticValue = 1;
Animal.prototype.prototypeWidth = 2;
"use strict";
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
function _defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
return Constructor;
}
var Animal =
/*#__PURE__*/
(function() {
function Animal() {
_classCallCheck(this, Animal);
this.name = "animal";
}
_createClass(
Animal,
[
{
key: "speak",
value: function speak() {
console.log("speak");
}
}
],
[
{
key: "eat",
value: function eat() {
console.log("eat");
}
}
]
);
return Animal;
})();
Animal.staticValue = 1;
Animal.prototype.prototypeWidth = 2;
ES6中是不支持在类中直接声明字段的,所有的声明必须在构造函数内。虽然新的草案还没有正式通过,我们可以使用babel-plugin-proposal-class-properties.先来体验一下。
修改demo里的代码如下:
class Animal {
constructor() {
this.name = 'animal';
}
speak1() {
console.log('speak');
}
speak2 = () => {
console.log('speak2');
}
speak3 = function (){
console.log('speak3');
}
}
经过babel之后,可以看出,声明的字段方法全部放到了构造函数内,而不是原型上。如果有多个实例,性能相比原型方法就会下降。
var Animal =
/*#__PURE__*/
function () {
function Animal() {
_classCallCheck(this, Animal);
this.speak2 = function () {
console.log('speak2');
};
this.speak3 = function () {
console.log('speak3');
};
this.name = 'animal';
}
_createClass(Animal, [{
key: "speak1",
value: function speak1() {
console.log('speak');
}
}]);
return Animal;
}();
为什么不为了性能,把箭头函数也绑定到原型上呢?这和箭头中的this不会创建自己的this,而是从自己的作用域链的上一层继承this有关。例如:
class Animal {
constructor() {
this.name = 'animal';
}
speak1() {
console.log(this);
}
speak2 = () => {
console.log(this);
}
speak3 = function (){
console.log(this);
}
}
var a = new Animal();
var speak1 = a.speak1;
var speak2 = a.speak2;
var speak3 = a.speak3;
speak1(); //undefined
speak2(); //Animal{}
speak3(); //undefined
为了实现这一点,babel需要将两种声明方式做区分。为了做到这一点,做了不少hack,JS真的是太男了。babel转换后代码如下:
var Animal =
/*#__PURE__*/
function () {
function Animal() {
var _this = this;
_classCallCheck(this, Animal);
this.speak2 = function () {
console.log(_this);
};
this.speak3 = function () {
console.log(this);
};
this.name = 'animal';
}
_createClass(Animal, [{
key: "speak1",
value: function speak1() {
console.log(this);
}
}]);
return Animal;
}();
没有什么岁月静好,只是有人替你负重前行, 感谢babel
继续扩展原来的例子, 新建Animal的子类
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(this.name + ' makes a noise.');
}
}
class Dog extends Animal {
constructor() {
super();
}
speak() {
console.log(this.name + ' barks.');
}
}
var d = new Dog('Mitzie');
d.speak();
babel编译之后的代码,省略了部分无关代码。
"use strict";
function _typeof(obj) { }
function _possibleConstructorReturn(self, call) {
if (
call &&
(_typeof(call) === "object" || typeof call === "function")
) {
return call;
}
return _assertThisInitialized(self);
}
// 判断是否先调用了父类构造函数。
// 构造函数内不调用super就会报错??? 和我想的不太一样
function _assertThisInitialized(self) {
if (self === void 0) {
throw new ReferenceError(
"this hasn't been initialised - super() hasn't been called"
);
}
return self;
}
function _getPrototypeOf(o) {
return Object.getPrototypeOf(o);
}
// 组合寄生式继承
function _inherits(subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError(
"Super expression must either be null or a function"
);
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: { value: subClass, writable: true, configurable: true }
});
// 设置子类构造函数的原型对象为父类
if (superClass) _setPrototypeOf(subClass, superClass);
}
function _setPrototypeOf(o, p) {
return Object.setPrototypeOf(o, p);
}
function _classCallCheck(instance, Constructor) { }
function _defineProperties(target, props) {}
function _createClass(Constructor, protoProps, staticProps) {}
var Animal =
/*#__PURE__*/
(function() {
function Animal(name) {
_classCallCheck(this, Animal);
this.name = name;
}
_createClass(Animal, [
{
key: "speak",
value: function speak() {
console.log(this.name + " makes a noise.");
}
}
]);
return Animal;
})();
var Dog =
/*#__PURE__*/
(function(_Animal) {
_inherits(Dog, _Animal);
function Dog() {
_classCallCheck(this, Dog);
return _possibleConstructorReturn(
this,
_getPrototypeOf(Dog).call(this)
);
}
_createClass(Dog, [
{
key: "speak",
value: function speak() {
console.log(this.name + " barks.");
}
}
]);
return Dog;
})(Animal);
var d = new Dog("Mitzie");
d.speak();
假如Animal 和 Dog都有static speak方法,并且在Dog的speak方法中还调用了Animal的speak方法,如下:
static speak() {
super.speak();
console.log('dog speak');
}
这时super应该指向的是构造函数,而不是实例了。经过babel转化为下:
_createClass(Dog, null, [
{
key: "speak",
value: function speak() {
// 获取构造函数原型对象即构造函数Animal上的speak方法。
_get(_getPrototypeOf(Dog), "speak", this).call(this);
console.log("dog speak");
}
}
]);
重点在_get 方法
function _get(target, property, receiver) {
if (typeof Reflect !== "undefined" && Reflect.get) {
// 类似于target[property],只不过是通过调用函数来实现,最关键的是第三个参数,如果target对象中指定了getter,receiver则为getter调用时的this值。
_get = Reflect.get;
} else {
_get = function _get(target, property, receiver) {
// 按照原型链向上寻找,直到找到对应的property
var base = _superPropBase(target, property);
if (!base) return;
var desc = Object.getOwnPropertyDescriptor(base, property);
if (desc.get) { // getter,执行getter并且指定其中this值
return desc.get.call(receiver);
}
return desc.value; // 普通方法
};
}
return _get(target, property, receiver || target);
}
Child.prototype = new Parent(); 缺点:引用类型的原型会被所有实例共享, 例如
function Parent() {
this.colors = [1, 2];
}
function Child(){}
Child.prototype = new Parent();
var c1 = new Child();
var c2 = new Child();
c1.colors.push(3);
console.log(c2.colors); // [1, 2, 3]
function Child() {
Parent.call(this);
}
缺点:无法复用原型上的函数
使用借用构造函数继承属性,原型链继承方法
缺点:会调用两次父类构造函数,并且在子类原型上有多余的属性(来自原型链继承),例如
function Child() {
Parent.call(this);
}
// 这里存在多余属性
Child.prototype = new Parent();
这种方法没有严格意义上的构造函数,借助已有的对象创建新对象
function object(o) {
function F(){}
F.prototype = o;
return new F();
}
function object(o) {
return Object.create(o);
}
创建一个仅用于封装继承过程的函数
function createChild(o) {
var child = object.create(o);
// 增强对象
child.sayHi = function() {};
reutrn child;
}
是对组合继承的一种优化,优化组合继承中的原型链部分,少调用一次父类构造函数
function inherit(child, parent) {
// 创建父类原型副本
var prototype = Object.create(parent.prototype);
prototype.constructor = child;
child.prototype = prototype;
}
function Parent() {}
function Child() {
Parent.call(this);
}
inherit(Child, Parent);
class 没有变量名提升,无法在定义之前使用。