类 introducing javascript classes
es5 中的类结构 class-like structures in ecmascript5
// 创建一个构造函数
function PersonType(name) {
this.name = name;
}
// 在构造函数的原型上添加方法
PersonType.prototype.sayName = function() {
console.log(this.name);
};
// 创建一个PersonType的实例person
let person = new PersonType("kk");
// 沿着原型链向上查找,直到找到sayName方法
person.sayName(); // 输出 kk
console.log(person instanceof personType); // true
console.log(person instanceof Object); // true
类声明 class declarations
一个基本的类声明 a basic class declaration
class PersonClass {
// 构造函数
constructor(name) {
this.name = name;
}
sayName() {
console.log(this.name);
}
}
let person = new PersonClass("kk");
person.sayName(); // 输出 kk
console.log(person instanceof PersonClass); // true
console.log(person instanceof Object); // true
console.log(typeof PersonClass); // function
console.log(typeof PersonClass.prototype.sayName); // function
为何使用类声明 why use the class syntax
- 类声明和函数不同,他们是不会被提升的,当执行流作用到类声明之前类会存在于暂存性死区中。
- 类声明的代码自动运行在严格模式下,同时没有任何办法可以手动的切换到非严格模式下。
- 类的所有方法都是不可枚举的。
- 不能再类的内部重写类名。
所以,PersonClass 声明等同如下未使用类语法的代码:
// 完全等效于PersonClass
let PersonType2 = function() {
"use strict";
const PersonType2 = function(name) {
// 确定方法是被new调用的
if (typeof new.target === "undefined") {
throw new Error("Constructor must be called with new.");
}
this.name = name;
};
Object.defineProperties(PersonType2.prototype, "sayName", {
value: function() {
// 检查new.target以确保类是由new来调用的。
if (typeof new.target === "undefined") {
throw new Error("Constructor must be called with new.");
}
console.log(this.name);
},
writable: true,
configurable: true,
enumerable: false
});
return PersonType2;
};
类表达式 class expressions
类和函数都有两种存在形式:声明和表达式。
一个基本的类表达式 basic class expression
// anonymous class expressions
let PersonClass = class {
constructor(name) {
this.name = name;
}
// ....
};
console.log(PersonClass.name); // ''
具名表达式 named class expressions
let PersonClass = class PersonClass1 {
constructor(name) {
this.name = name;
}
// 相当于
// const PersonClass1 = function (name) {
// if(typeof new.target == 'undefined'){
// throw new Error('err');
// }
// this.name = name;
// }
// return PersonClass1;
};
再来看一种现象:
console.log(typeof PersonClass1); // "undefined"
/**
* 原因: PersonClass1只能在类的内部使用,外部是访问不到的
*/
总结:在该匿名类表达式中,PersonClass.name 是个空的字符串。如果使用了类声明,那么 PersonClass.name 的值为 “PersonClass”。
注:译者在这里测试发现 Edge,Chrome 及 Opera 的匿名表达式都会返回类名,只有 FireFox 返回空字符串
一等公民,一等类
在编程中,如果某些东西能够作为值使用,那么他被成为一等公民。js 中的函数就是一等公民。又叫做一等函数。
// 使用 1. 作为参数
function createObj(classDef) {
return new classDef();
}
let obj = createObj(
class {
sayHi() {
console.log("hi");
}
}
);
obj.sayHi(); // 'hi'
// 2. 立即执行
let person = new class {
constructor(name) {
this.name = name;
}
sayName() {
console.log(this === person); // true
console.log(this.name);
}
}("kk");
person.sayName(); // 'kk'
访问器属性 accessor properties
class CreateHtmlElement {
constructor(ele) {
this.ele = ele;
}
get html() {
return this.ele.innerHtml;
}
set html(value) {
this.ele.innerHtml = value;
}
}
let descriptor = Object.getOwnPropertyDescriptor(
CreateHtmlElement.prototype,
"html"
);
console.log("get" in descriptor); // true
console.log("set" in descriptor); // true
console.log(descriptor.enumerable); // false
如果不使用类的写法,感受一下 完全等效于 CreateHtmlElement
let CreateHtmlElement = (function() {
"use strict";
const CreateHtmlElement = function(ele) {
if (new.target == "undefined") {
throw new Error("err!");
}
this.ele = ele;
};
Object.getOwnPropertyDescriptor(CreateHtmlElement.prototype, "html", {
writable: true,
configurable: true,
enumerable: false,
get: function() {
return this.ele.innerHtml;
},
set: function(value) {
this.ele.innerHtml = value;
}
});
return CreateHtmlElement;
})();
动态命名 computed member names
// 例子1 动态命名方法
let className = "sayname";
class PersonClass {
constructor(name) {
this.name = name;
}
[className]() {
console.log(this.name);
}
}
let person = new PersonClass("jj");
person.sayName(); // 'jj'
// 例子2 动态命名属性
let animalName = "animalName";
class Animal {
constructor(animalKind) {
this.animalKind = animalKind;
}
set [animalName](name) {
this.animalKind.animalName = name;
}
get [animalName]() {
return this.animalKind.animalName;
}
}
let dog = new Animal("dog");
dog.set("huahua");
dog.get(); // 'huahua'
生成器方法 generator methods
该方法返回了一个经过硬编码的迭代器,当你想要一个具有集合性质的对象并能轻松迭代包含值得时候,生成器方法相当有用。
class MyClass {
*createIterator() {
yield 1;
yield 2;
yield 3;
}
}
let myclass = new MyClass();
let iterator = myclass.createIterator();
for (const i of iterator) {
console.log(i);
}
// 依次输出1 2 3
设置类的默认迭代器
通过给类的 Symbol.iterator 定义一个生成器方法来设置类的默认迭代器。
class Collection {
constructor() {
this.items = [];
}
*[Symbol.iterator]() {
yield* this.items.values();
}
}
var collection = new Collection();
collection.items.push(1);
collection.items.push(2);
collection.items.push(3);
for (let x of collection) {
console.log(x);
}
// 输出:
// 1
// 2
// 3
静态成员 static members
es5 中的实现:
function PersonType(name) {
this.name = name;
}
// 静态方法 create
PersonType.create = function(name) {
return new PersonType(name);
};
// 实例方法 sayName
PersonType.prototype.sayName = function() {
console.log(this.name);
};
var person = PersonType.create("Nicholas");
es6 中的实现:
class PersonClass {
// 等效于 PersonType 构造函数
constructor(name) {
this.name = name;
}
// 等效于 PersonType.prototype.sayName
sayName() {
console.log(this.name);
}
// 等效于 PersonType.create
static create(name) {
return new PersonClass(name);
}
}
let person = PersonClass.create("Nicholas");
静态成员不能被实例访问。你必须通过类本身来使用它们。
类的继承
es5 中的实现:
function Rectangle(length, width) {
this.length = length;
this.width = width;
}
Rectangle.prototype.getArea = function() {
return this.length * this.width;
};
function Square(length) {
Rectangle.call(this, length, length);
}
Square.prototype = Object.create(Rectangle.prototype, {
constructor: {
value: Square,
enumerable: true,
writable: true,
configurable: true
}
});
var square = new Square(3);
console.log(square.getArea()); // 9
console.log(square instanceof Square); // true
console.log(square instanceof Rectangle); // true
es6 中的实现:
class Rectangle {
constructor(length, width) {
this.length = length;
this.width = width;
}
getArea() {
return this.length * this.width;
}
}
class Square extends Rectangle {
constructor(length) {
// 等效于 Rectangle.call(this, length, length)
super(length, length);
}
}
var square = new Square(3);
console.log(square.getArea()); // 9
console.log(square instanceof Square); // true
console.log(square instanceof Rectangle); // true
派生类使用父类的静态方法
class A {
constructor(width, height) {
this.width = width;
this.height = height;
}
getArea() {
return this.width * this.height;
}
// 静态成员 静态方法
static create(width, height) {
return width * height;
}
}
class B extends A {
constructor(length) {
// 使用super和指定的参数来调用A的构造函数
super(length, length);
}
getArea() {
// 调用父类中的方法 使用super.方法名
return super.getArea();
}
}
let b = new B(3);
b.getArea(); // 9
// 派生类可以使用父类的静态方法
B.create(2, 3); // 6
注意: 只有派生类本身可以使用他的静态方法,派生类的实例不可以使用