第17节:class类的使用
songStar0904 opened this issue · comments
简介
JavaScript 语言中, 生成实例对象的传统方法是通过构造函数。
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.add = function() {
return this.x + this.y;
}
var p = new Point(1, 2);
p.add(); // 3
ES6 提供了更接近传统语言的写法, 引入了Class 类这个概念, 作为对象的模板。 通过class 关键字, 可以定义类。
{
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
add () {
return this.x + this.y
}
}
var p = new Point(1, 2);
p.add(); // 3
}
上面定义了一个类, 可以看到里面有一个constructor
方法, 这就是构造方法, 而this
关键字则代表实例对象。
Point
类除了构造方法, 还定义了add
方法, 注意:
- 类的方法前面不需要加
function
这个关键字, - 另外方法之间不需要
,
隔开,否则会报错。 - 使用的时候必须要加new, 不加会报错(构造函数可省略)
ES6的类, 完全可以看作构造函数的另一种写法。
typeof Point // function
Point === Point.prototype.constructor // true
类的所有方法都定义在类的'prototype'属性上。所有属性都定义在它的实例(定义在this变量上)上。
class Point{
constructor () { ... }
add () { ... }
}
// 等同于
Point.prototype = {
function constructor () { ... },
function add () { ... }
}
在类的实例上面调用方法, 其实是调用原型上的方法。
prototype
对象的constructor
属性, 直接指向类的本身, 这与ES5 的行为一致。
{
class B {};
let b = new B();
b.constructor === B.prototype.constructor; // true
B.prototype.constructor === B; // true
}
由于类的方法定义在prototype
对象上面, 所以类的新方法可以添加在prototype
对象上。Object.assign
方法可以很方便的添加多个方法。
{
class Point {
constructor () { ... }
}
Object.assign(Point.prototype, {
add () {},
toString () {}
});
}
类的内部所有方法都是不可枚举的。这一点与ES5行为不一致。
{
class Point {
constructor () {}
add () {}
}
Object.keys(Point.prototype); // []
Object.getOwnPropertyNames(Point.prototype); // ["constructor", "add"]
var Point2 = function () {}
Point2.prototype.add = function () {}
Object.keys(Point2.prototype); // ["add"]
Object.getOwnPropertyNames(Point2.prototype); // ["constructor", "add"]
}
类的属性名, 可以采用表达式。
{
let name = 'add';
class Point {
constructor () {}
[name] () {}
}
}
严格模式
类和模块的内部,默认就是严格模式,所以不需要使用use strict
指定运行模式。只要你的代码写在类或模块之中,就只有严格模式可用。
constructor 方法
constructor
方法是类的默认方法, 通过new
命令生成对象实例时, 自动调用该方法。一个类必须要有constructor
方法, 如果没有显示定义, 则会被默认添加一个空的constructor
方法。
constructor
方法默认返回实例对象(this), 完全可以制定返回其他对象。
class Foo {
constructor () {
return Object.create(null);
}
}
new Foo() instanceof Foo // false
类的实例对象
与ES5一致, 共享一个原型对象。
{
class Point {};
let p1 = new Point();
let p2 = new Point();
p1.prototype === p2.prototype; // true
p1.__proto__ === p2.__proto__; // true
}
class 表达式
与函数一样, 类也可以使用表达式是形式定义。
{
let MyClass = class Me {
getClassName () {
return Me.name;
}
};
let inst = new MyClass();
inst.getClassName(); // Me
MyClass.name; // Me
Me.name; // Me is not defined
}
上面代码使用表达式定义类。 这个类的名字是MyClass
而不是Me
, Me
只在Class内部使用, 指代当前类。
如果类的内部没有用到的话, 可以省略Me
。
let MyClass = class { ... }
可以像函数一样立即执行。
{
let p = new class {
constructor(name) {
this.name = name;
}
sayName () {
console.log(this.name);
}
}('songStar');
p.sayName(); // songStar
}
不存在变量提升
类不存在变量提升(hoist),这一点与 ES5 完全不同。
new Foo(); // 报错
class Foo {}
私有方法和私有属性
私有方法是常见需求, 但是ES6不提供, 只能通过方法模拟实现。
- 在命名上加以区别。
{
class Foo {
// 共有方法
baz () {
this._bar();
}
// 私有方法
_bar () {
return '_bar';
}
}
}
上面代码中, _bar
方法前面的下划线, 表示这是一个只限于内部使用的私有方法。 但是, 这种命名不是保险的, 在类的外部, 还是可以调用这个方法。
- 另一种方法索性将私有方法洗出模块, 因为模块内部的所有方法都是对外可见的。
{
class Foo {
baz () {
bar.call(this);
}
}
function bar() {
return 'bar';
}
}
- 利用
Symbol
值的唯一性, 将私有方法的命名为一个Symbol
值。
{
let bar Symbol('bar');
let snaf = Symbol('snaf');
export default class MyClass {
// 公有方法
foo (baz) {
this[bar](baz);
}
// 私有方法
[bar](baz) {
return this[snaf] = baz;
}
}
}
上面代码中, baz
和snaf
都是Symbol
值, 导致第三方无法获取到它们, 因此达到了私有方法和私有属性的效果。
私有属性提案
与私有方法一样, ES6不支持私有属性, 目前有一个提案, 为class
加了私有属性。 在属性名前加#
。
{
class Point{
#x;
constructor (x = 0) {
#x = +x; // === this.#x = +x;
}
get x () {
return #x;
}
set x (value) {
#x = +value;
}
#add (value) {
#x += value;
}
}
}
上面代码中, #x
表示私有属性x
, 在类之外是读取不到这个属性的。 还可以看到私有属性与实例的属性可以同名的(#x
和get x()
)。#sum()
就是一个私有方法。
之所以要引入一个新的前缀#
表示私有属性,而没有采用private
关键字,是因为 JavaScript
是一门动态语言,使用独立的符号似乎是唯一的可靠方法,能够准确地区分一种属性是否为私有属性。
另外,私有属性也可以设置 getter
和 setter
方法。
this 指向
类的方法内部如果含有this
, 它默认指向类的实例。
一旦单独使用该方法, 很可能报错。
{
class Point{
sayName (name) {
this.print(`hello${name}`);
}
print (text) {
console.log(text);
}
}
let p = new Point();
let {sayName} = p;
sayName('songStar'); // TypeError: Cannot read property 'print' of undefined
}
上面代码中, sayName
方法中this
, 默认指向Point
类的实例。 但是如果将这个方法图取出来单独使用,this
会指向该方法的运行所在运行环境, 因此找不到print
方法。
- 通过在构造方法中绑定
this
。
{
class Point {
constructor () {
this.sayName = this.sayName.bind(this)
}
}
}
- 使用箭头函数解决。
{
class Point {
constructor () {
this.sayName = (name) => {
this.print(`hello ${name}`);
}
}
}
}
- 使用
Proxy
, 获取方法的时候, 自动绑定this
。
{
function selfish (target) {
const cache = new WeakMap();
const handler = {
get (target, key) {
const value = Reflect.get(target, key);
if (typeof value !== 'function') {
return value;
}
if (! cache.has(value)) {
cache.set(value, value.bind(target));
}
return cache.get(value);
}
};
const proxy = new Proxy(target, handler);
return proxy;
}
let p = new selfish(new Point());
}