ECMAScript6 从入门到入坟,你敢来挑战吗???(三)

简介: ECMAScript6 从入门到入坟,你敢来挑战吗???(三)

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 就是私有属性,在一个类之外是读取不到这个属性的,由于井号 # 是属性名的一部分,使用时必需带有 # 一起使用,所以 #xx 是两个不同的属性。

之所以要引入一个新的前缀 # 表示私有属性,而没有采用 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());
      }
    }

另外,私有属性也可以设置 gettersetter方法。

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


相关文章
|
4月前
|
算法 C++
惊爆!KPM算法背后的秘密武器:一行代码揭秘字符串最小周期的终极奥义,让你秒变编程界周期大师!
【8月更文挑战第4天】字符串最小周期问题旨在找出字符串中最短重复子串的长度。KPM(实为KMP,Knuth-Morris-Pratt)算法,虽主要用于字符串匹配,但其生成的前缀函数(next数组)也可用于求解最小周期。核心思想是构建LPS数组,记录模式串中每个位置的最长相等前后缀长度。对于长度为n的字符串S,其最小周期T可通过公式ans = n - LPS[n-1]求得。通过分析周期字符串的特性,可证明该方法的有效性。提供的C++示例代码展示了如何计算给定字符串的最小周期,体现了KPM算法在解决此类问题上的高效性。
90 0
|
JavaScript 前端开发
ECMAScript6 从入门到入坟,你敢来挑战吗???(四)
ECMAScript6 从入门到入坟,你敢来挑战吗???(四)
|
前端开发 JavaScript
ECMAScript6 从入门到入坟,你敢来挑战吗???(二)
ECMAScript6 从入门到入坟,你敢来挑战吗???(二)
|
JSON 前端开发 JavaScript
ECMAScript6 从入门到入坟,你敢来挑战吗???(一)
ECMAScript6 从入门到入坟,你敢来挑战吗???
|
缓存 JavaScript 前端开发
ECMAScript6 从入门到入坟,你敢来挑战吗???(六)
ECMAScript6 从入门到入坟,你敢来挑战吗???(六)
|
缓存 JavaScript 前端开发
ECMAScript6 从入门到入坟,你敢来挑战吗???(五)
ECMAScript6 从入门到入坟,你敢来挑战吗???(五)
程序媛才能读懂的高级情话
程序媛才能读懂的高级情话
174 0
|
前端开发 JavaScript
#yyds干货盘点# 前端歌谣的刷题之路-第二十三题-检测复杂数据类型
#yyds干货盘点# 前端歌谣的刷题之路-第二十三题-检测复杂数据类型
98 0
#yyds干货盘点# 前端歌谣的刷题之路-第二十三题-检测复杂数据类型
|
存储 前端开发
草系前端手摸手带你实现正则引擎,点燃夏日最热情的烟火(三)🔥
草系前端手摸手带你实现正则引擎,点燃夏日最热情的烟火(三)🔥
112 0
|
前端开发
草系前端手摸手带你实现正则引擎,点燃夏日最热情的烟火(一)🔥
草系前端手摸手带你实现正则引擎,点燃夏日最热情的烟火(一)🔥
170 0
下一篇
DataWorks