Class 的基本语法

类的由来

JavaScript 语言中,生成实例对象的传统方法是通过构造函数。

function Point(x, y) {
  this.x = x;
  this.y = y;
}

Point.prototype.toString = function () {
  return '(' + this.x + ', ' + this.y + ')';
};

var p = new Point(1, 2);

上面这种写法跟传统的面向对象语言(比如 C++ 和 Java)差异很大,很容易让新学习这门语言的程序员感到困惑。

ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。

ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。上面的代码用 ES6 的class改写,就是下面这样。

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}
  • 类完全可以看作构造函数的另一种写法:Point === Point.prototype.constructor

  • 使用类的时候直接像对象一样new即可

  • 构造函数的prototype属性在类上依然存在,实际上,类中所有的方法都定义在类的prototype属性上

  class Point {
  	constructor() {
  		// ...
  	}
  	toString() {
  		// ...
  	}
  	toValue() {
  		// ...
  	}
  }
  // 等同于
  Point.prototype = {
  	constructor() {},
  	toString() {},
  	toValue() {},
  };

  • 在类的实例上调用方法实际上就是调用原型上的方法
class B {}
let b = new B();

b.constructor === B.prototype.constructor // true
  • 类的内部定义的方法都是不可枚举的(ES6中如此,ES5中可以枚举)

  • 类的属性名可以采用表达式

  let methodName = 'getArea';
  class Square {
  	constructor(length) {
  		// ...
  	}
  	[methodName]() {
  		// ...
  	}
  }
  • 类和模块的内部默认都是严格模式,所以不需要use strict指定运行模式

1、constructor 方法

  1. constructor方法是类的默认方法,通过new命令生成实例对象时,自动调用该方法.
  2. 一个类必须有constructor方法,如果没有显式定义,会默认添加一个空constructor方法
class Point {
}

// 等同于
class Point {
  constructor() {}
}
  1. constructor默认返回实例对象(this),完全可以指定返回另外一个对象
 //改变返回的对象为空对象,因此通过new Foo()创建的实例不是继承自Foo
 class Foo {
 	constructor() {
 		return Object.create(null);
 	}
 }
 new Foo() instanceof Foo
 // false
  1. 类必须使用new调用,否则会报错.普通构造函数不用new也可以执行(虽然没有太大意义),但类不行
class Foo {
  constructor() {
    return Object.create(null);
  }
}

Foo()
// TypeError: Class constructor Foo cannot be invoked without 'new'

2、类的实例

  1. 生成类的实例对象的写法与ES5完全一致,使用new命令即可.但是如果没加new则会报错。
class Point {
  // ...
}

// 报错
var point = Point(2, 3);

// 正确
var point = new Point(2, 3);
  1. 与ES5一样,实例的属性除非显式定义在this对象上,否则都是定义在原型上(也就是class上)。
//定义类
class Point {

  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }

}

var point = new Point(2, 3);

point.toString() // (2, 3)

point.hasOwnProperty('x') // true
point.hasOwnProperty('y') // true
point.hasOwnProperty('toString') // false
point.__proto__.hasOwnProperty('toString') // true
  1. 类的所有实例共享一个原型对象
 var p1 = new Point(2,3);
 var p2 = new Point(3,2);
 p1.__proto__ === p2.__proto__
 //true	
 //p1.__proto__===Point.prototype

p1和p2都是Point的实例,它们的原型都是Point.prototype,所以__proto__属性是相等的。
这也意味着,可以通过实例的__proto__属性为“类”添加方法。

3、Class 表达式

  1. 类也可以使用表达式的形式定义
 //这个类的名称是MyClass.Me是在类的内部使用,指代当前类,如果内部没用到可以省略
 const MyClass = class Me {
 	getClassName() {
 		return Me.name;
 	}
 };
  1. 采用class表达式可以写出立即执行的class
 let person = new class {
 	constructor(name) {
 		this.name = name;
 	}
 	sayName() {
 		console.log(this.name);
 	}
 }('张三');
 person.sayName(); // "张三"
  • 不存在变量提升:类不存在变量提升,必须先声明后使用.因此以后用到的类的继承必须保证子类在父类之后定义

4、私有方法

  1. 私有方法ES6不提供,只能模拟实现
  2. 可以在命名上加上特殊标识区别(可以在外部调用,不安全,不建议)
 class Widget {
 	// 公有方法
 	foo (baz) {
 		this._bar(baz);
 	}
 	// 私有方法
 	_bar(baz) {
 		return this.snaf = baz;
 	}
 	// ...
 }
  1. 将私有方法移出模块,因为模块内部的方法都是对外可见的
 //通过
 class Widget {
 	foo (baz) {
 		bar.call(this, baz);
 	}
 	// ...
 }
 function bar(baz) {
 	return this.snaf = baz;
 }
  1. 利用Symbol值的唯一性,将私有方法的名字命名为一个Symbol值
 const bar = Symbol('bar');
 const snaf = Symbol('snaf');
 export default class myClass{
 	// 公有方法
 	foo(baz) {
 		this[bar](baz);
 	}
 	// 私有方法
 	[bar](baz) {
 		return this[snaf] = baz;
 	}
 	// ...
 };

5、私有属性

  1. ES6不支持私有属性.有一个方案为class添加了私有属性。就是在属性名之前使用#表示.同时,私有属性可以和实例的属性同名(例如 #x和get x(){})。
  2. 同时,也可以用#来表示私有方法
 class Foo {
 	#a;
 	#b;
 	#sum() { return #a + #b; }
 	printSum() { console.log(#sum()); }
 	constructor(a, b) { #a = a; #b = b; }
 }

6、this的指向

  1. 类的方法内部如果有this,它默认指向类的实例.如果要单独使用类的方法,很可能会报错
 //本来this指向logger,但是因为单独获取了printName方法,再使用的时候this就指向了window,不能正确调用
 class Logger {
 	printName(name = 'there') {
 		this.print(`Hello ${name}`);
 	}
 	print(text) {
 		console.log(text);
 	}
 }
 const logger = new Logger();
 const { printName } = logger;
 printName(); // TypeError: Cannot read property 'print' of undef
 ined
  1. 可以在构造方法中绑定this
 class Logger {
 	constructor() {
 		this.printName = this.printName.bind(this);
 	}
 	// ...
 }
  1. 或者使用箭头函数,因为箭头函数有绑定this 的功能
 class Logger {
 	constructor() {
 		this.printName = (name = 'there') => {
 			this.print(`Hello ${name}`);
 		};
 	}
 	// ...
 }
  1. 也可以用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;
}

const logger = selfish(new Logger());
  1. name属性:总是返回紧跟class关键字后面的类名

  2. class的取值函数(getter)和存值函数(setter)

    1. 可以在类的内部使用get和set关键字拦截属性的存取行为
 class MyClass {
 	constructor() {
 		// ...
 	}
 	get prop() {
 		return 'getter';
 	}
 	set prop(value) {
 		console.log('setter: '+value);
 	}
 }
 let inst = new MyClass();
 inst.prop = 123;
 // setter: 123
 inst.prop
 // 'getter'
  1. 存值函数和取值函数是设置在属性的 Descriptor 对象上的

  2. class的Generator方法

    1. 如果某个方法之前加上*号,就表示该方法是一个Generator函数
 class Foo {
 	constructor(...args) {
 		this.args = args;
 	}
 	* [Symbol.iterator]() {		//[Symbol.iterator]方法返回一个Foo类的默认遍历器,for...of循环会自动调用
 		for (let arg of this.args) {
 			yield arg;
 		}
 	}
 }
 for (let x of new Foo('hello', 'world')) {
 	console.log(x);
 }
 // hello
 // world

  • class的静态方法
  1. 类相当于实例的原型,所有在类中定义的方法,都会被实例继承.如果在一个方法前,加上 static 关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为"静态方法"
 class Foo {
 	static classMethod() {
 		return 'hello';
 	}
 }
 Foo.classMethod() // 'hello'
 var foo = new Foo();
 foo.classMethod()
 // TypeError: foo.classMethod is not a function
  1. 如果静态方法上包含this,这个this指的的是类,而不是实例
 //这里bar的this指的是Foo类,而不是Foo的实例,等同于调用Foo.baz.此外,静态方法和非静态方法可以重名
 //调用的是同为静态方法的baz,如果没有静态的baz方法会报错
 class Foo {
 	static bar () {
 		this.baz();
 	}
 	static baz () {
 		console.log('hello');
 	}
 	baz () {
 		console.log('world');
 	}
 }
 Foo.bar() // hello

  1. 父类的静态方法可以被子类继承
 class Foo {
 	static classMethod() {
 		return 'hello';
 	}
 }
 class Bar extends Foo {
 }
 Bar.classMethod() // 'hello'

  1. 静态方法也可以从super对象上调用的
 class Foo {
 	static classMethod() {
 		return 'hello';
 	}
 }
 class Bar extends Foo {
 	static classMethod() {
 		return super.classMethod() + ', too';
 	}
 }
 Bar.classMethod() // "hello, too"

  1. class的静态属性和实例属性

静态属性是指class本身的属性,即Class.propName,而不是定义在实例对象(this)上的属性

 class Foo {
 }
 Foo.prop = 1;	//为Foo定义了一个静态属性prop
 Foo.prop // 1
 //目前只有这一种写法,因为ES6规定Class内部只有静态方法,没有静态属性
 //以下写法都无效
 class Foo {
 // 写法一
 prop: 2
 // 写法二
 static prop: 2
 }
 Foo.prop // undefined

类的实例属性:可以使用等式写入类的定义之中

 class MyClass {
 	myProp = 42;	//定义实例属性myProp=42
 	constructor() {
 		console.log(this.myProp); // 42
 	}
 }
  1. 对于在 constructor 里面已经定义的实例属性,新写法允许直接列出
  class ReactCounter extends React.Component {
  	state;
  	constructor(props) {
  		super(props);
  		this.state = {
  			count: 0
  		};
  	}
  }

7、类的静态属性:

  1. 只要在实例属性写法前面加上static即可
  class MyClass {
  	static myStaticProp = 42;
  	constructor() {
  		console.log(MyClass.myStaticProp); // 42
  	}
  }

8、new.target 属性

一般用于构造函数之中,返回new命令作用域哪个构造函数.如果构造函数不是通过new命令调用的,new.target返回undefined.因此这个属性可以用来确定构造函数是怎么调用的

  1. Class内部调用new.target,返回的是当前Class
 class P{
 	constructor{
 		console.log(new.target === P);	//true
 	}
 }

  1. 子类继承父类时,new.target会返回子类
 //不能独立使用、必须继承后才能使用的类.也就是Shape不能实例化,只能用于继承
 class Shape {
 	constructor() {
 		if (new.target === Shape) {
 			throw new Error('本类不能实例化');
 		}
 	}
 }
 class Rectangle extends Shape {
 	constructor(length, width) {
 		super();
 		// ...
 	}
 }
 var x = new Shape(); // 报错
 var y = new Rectangle(3, 4); // 正确

  1. 在函数外部使用new.target会报错
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值