1.Generator 方法
2.this 的指向
–类的方法内部如果含有 this ,他默认指向类的实例,但是,必需要小心,一旦单独使用该方法,
很有可能会报错。
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 undefined /**
上面代码中,printName方法中的this,默认指向Logger类的实例。但是,如果将这个方法提取出来
单独使用,this会指向该方法运行时所在的环境(由于 class 内部是严格模式,所以 this 实际指向的是undefined),从而导致找不到print方法而报错。
*/
•一个简单的解决办法是,在构造方法中绑定 this, 这样就不会找不到 print 方法。
// 方法一 class Logger { constructor() { this.printName = this.printName.bind(this); } // ... } // 方法二 class Obj { constructor() { this.getThis = () => this; } } const myObj = new Obj(); myObj.getThis() === myObj // true
•箭头函数内部的 this 总是指向定义时所在的对象。上面的代码中,箭头函数位于构造函数的内部,他的定义
生效的时候,是在构造函数执行的时候。这时,箭头函数所在的运行环境,肯定是实例对象,所以 this 会总是指
向实例对象。
•还有一种方式是使用 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());
静态方法
•类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法期,加上 static 关键字,表示
该方法不会被实例继承,而是直接通过类来调用,这就称之为 静态方法。
class Foo { static classMethod() { return 'hello'; } } Foo.classMethod() // 'hello' var foo = new Foo(); foo.classMethod() // TypeError: foo.classMethod is not a function /**
上面代码中,Foo类的classMethod方法前有static关键字,表明该方法是一个静态方法,可以直接在Foo类上
调用(Foo.classMethod()),而不是在Foo类的实例上调用。如果在实例上调用静态方法,会抛出一个错误,
表示不存在该方法。
*/
•如果,静态方法中包含 this 关键字,这个 this 关键字指的是类,而不是实例。
class Foo { static bar() { this.baz(); } static baz() { console.log('hello'); } baz() { console.log('world'); } } Foo.bar() // hello
上面代码中,静态方法bar调用this.bar,这里的this指向的是Foo类,而不是Foo的实例,等同于调用Foo.baz。
另外,从这个例子中可以看出,静态方法可以与静态方法重名。
•父类的继承方法可以被子类所继承
class Foo { static classMethod() { return 'hello'; } } class Bar extends Foo { } Bar.classMethod() // 'hello'
•静态方法页可以从 super 对象上调用。
class Foo { static classMethod() { return 'hello'; } } class Bar extends Foo { static classMethod() { return super.classMethod() + ', too'; } } Bar.classMethod() // "hello, too"
实例属性的新写法
•实例属性除了定义在 constructor 方法里面的 this 上面,也可以定义在类的最顶层。
class IncreasingCounter { constructor() { this._count = 0; } get value() { console.log('Getting the current value!'); return this._count; } increment() { this._count++; } }
•上面代码中,实例属性 this._count 定义在 constructor 方法里面。另一种写法是,这个属性也可以定义在
类的最顶层,其他上面都不变。
class IncreasingCounter { _count = 0; get value() { console.log('Getting the current value!'); return this._count; } increment() { this._count++; } }
•这种新写法的好处是,所有实例对象自身的属性都定义在类的头部,看上去比较整齐,一眼就能看出这个类有哪些
实例属性
class foo { bar = 'hello'; baz = 'world'; constructor() { // ... } }
静态属性
•静态属性指的是 class 本身的属性,即 class.proName,而不是定义在实例对象 this 上的属性。
class Foo { } Foo.prop = 1; Foo.prop // 1
•ES6 明确规定,class 内部只有静态方法,没有静态属性,有一个提案提供了类的静态属性,写法是在实例属性
的前面,加上 static 关键字。
class MyClass { static myStaticProp = 42; constructor() { console.log(MyClass.myStaticProp); // 42 } } // 老写法 class Foo { // ... } Foo.prop = 1; // 新写法 class Foo { static prop = 1; }
私有属性和私有方法
•私有方法和私有属性,是只能在类的内部访问的方法和属性,外部不能访问。这是常见需求,有利于代码的封装,但
ES6 不提供,只能通过变通方法模拟实现。
•方法一:
–一种做法是在命名上加一区别
class Widget { // 公有方法 foo (baz) { this._bar(baz); } // 私有方法 _bar(baz) { return this.snaf = baz; } // ... }
•上面代码中,_bar()方法前面的下划线,表示这是一个只限于内部使用的私有方法。但是,这种命名是不保险的,
在类的外部,还是可以调用到这个方法。
•方法二:
–另一种方法就是索性将私有方法移出类,因为类内部的所有方法都是对外可见的。
class Widget { foo (baz) { bar.call(this, baz); } // ... } function bar(baz) { return this.snaf = baz; }
•方法三:
–利用 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; } // ... };
•上面代码中,bar 和 snaf 都是 Symbol 值,一般情况下无法获取到他们,因此可以达到私有方法和私有属性
的效果。但是也不是绝对不行,Reflect.ownKeys() 依然可以拿到它们。
const inst = new myClass(); Reflect.ownKeys(myClass.prototype) // [ 'constructor', 'foo', Symbol(bar) ]
// 上面代码中,Symbol 值的属性名依然可以从类的外部拿到
私有属性的提案
•目前,有一个提案,为 class 加了私有属性。方法是在属性名之前,使用 # 表示。
class IncreasingCounter { #count = 0; get value() { console.log('Getting the current value!'); return this.#count; } increment() { this.#count++; } }
上面代码中,#count 就是私有属性,只能在类的内部使用 this.#count 。如果在类的外部使用,就会报错。
eg1:
const counter = new IncreasingCounter(); counter.#count // 报错 counter.#count = 42 // 报错
eg2:
class Point { #x; constructor(x = 0) { this.#x = +x; } get x() { return this.#x; } set x(value) { this.#x = +value; } }
•#x 就是私有属性,在一个类之外是读取不到这个属性的,由于井号 # 是属性名的一部分,使用时必需带有 # 一起使用,所以 #x 和 x 是两个不同的属性。
•之所以要引入一个新的前缀 # 表示私有属性,而没有采用 private 关键字,是因为 JavaScript 是一门动态语言,没有类型声明,使用独立的符号似乎是唯一的比较方便可靠的方法,能够准确的区分一种属性是否为私有属性。
•这种写法不仅可以写私有属性,还可以写私有方法
class Foo { #a; #b; constructor(a, b) { this.#a = a; this.#b = b; } #sum() { return this.#a + this.#b; } printSum() { console.log(this.#sum()); } }
•另外,私有属性也可以设置 getter 和 setter方法。
class Counter { #xValue = 0; constructor() { super(); // ... } get #x() { return #xValue; } set #x(value) { this.#xValue = value; } }
// 上面代码中,#x 是一个私有属性,他的读写都通过 get #x() 和 set #x() 来完成。
•私有属性不限于从 this 中引用,只要是在类的内部,实例也可以引用私有属性。
class Foo { #privateValue = 42; static getPrivateValue(foo) { return foo.#privateValue; } } Foo.getPrivateValue(new Foo()); // 42
•私有属性和私有方法前面,也可以加上 static 关键字,表示这是一个静态的私有属性会私有方法。
class FakeMath { static PI = 22 / 7; static #totallyRandomNumber = 4; static #computeRandomNumber() { return FakeMath.#totallyRandomNumber; } static random() { console.log('I heard you like random numbers…') return FakeMath.#computeRandomNumber(); } } FakeMath.PI // 3.142857142857143 FakeMath.random() // I heard you like random numbers… // 4 FakeMath.#totallyRandomNumber // 报错 FakeMath.#computeRandomNumber() // 报错 // 上面代码中,#totallyRandomNumber是私有属性,#computeRandomNumber()是私有方法, // 只能在FakeMath这个类的内部调用,外部调用就会报错。
in 运算符
•try...catch 结构可以用来判断是否存在某个私有属性。
class A { use(obj) { try { obj.#foo; } catch { // 私有属性 #foo 不存在 } } } const a = new A(); a.use(a); // 报错
上面的这种写法,可读性很差,V8引擎改进了 in 运算符,使用也可以用来判断私有属性。
class A { use(obj) { if (#foo in obj) { // 私有属性 #foo 存在 } else { // 私有属性 #foo 不存在 } } }
•in 运算符判断类的实例是否有私有属性,返回的是 Boolean 值
•in 也可以跟 this 一起配合使用
class A { #foo = 0; m() { console.log(#foo in this); // true console.log(#bar in this); // false } }
•注意,判断私有属性,in 只能用在定义改私有属性类的内部。
class A { #foo = 0; static test(obj) { console.log(#foo in obj); } } A.test(new A()) // true A.test({}) // false class B { #foo = 0; } A.test(new B()) // false
•子类从父类继承的私有属性,也可以使用 in 运算符来判断。
class A { #foo = 0; static test(obj) { console.log(#foo in obj); } } class SubA extends A {}; A.test(new SubA()) // true