songStar0904 / es6

learn es6 by jspang

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

第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而不是MeMe 只在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;
		}
	}
}

上面代码中, bazsnaf都是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, 在类之外是读取不到这个属性的。 还可以看到私有属性与实例的属性可以同名的(#xget x())。#sum()就是一个私有方法。
之所以要引入一个新的前缀#表示私有属性,而没有采用private关键字,是因为 JavaScript 是一门动态语言,使用独立的符号似乎是唯一的可靠方法,能够准确地区分一种属性是否为私有属性。
另外,私有属性也可以设置 gettersetter 方法。

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());
}