【ES6】Ruan Yifeng ES6 Learning Class (1)

1. The origin of the class

In the JavaScript language, the traditional way to generate instance objects is through constructors.

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

The above style of writing is very different from traditional object-oriented languages ​​(such as C++ and Java), and it is easy to confuse programmers who are new to this language.

ES6 provides a way of writing closer to traditional languages, introducing the concept of Class (class) as a template for objects. With the class keyword, a class can be defined.

Basically, the ES6 class can be regarded as just a syntactic sugar. Most of its functions can be achieved by ES5. The new class writing method only makes the object prototype writing method clearer and more like the syntax of object-oriented programming.

Rewrite the above example in ES6

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

ES6 classes can be regarded as another way of writing constructors.

// 类的数据类型就是函数,类本身就指向构造函数。
class Point {
    
    
  // ...
}

typeof Point // "function"
Point === Point.prototype.constructor // true

When using it, it also directly uses newthe command on the class, which is exactly the same as the usage of the constructor.

class Bar {
    
    
    doSomething(){
    
    
        console.log('something');
    }
}

const b = new Bar();
b.doSomething();   // something

Constructor prototypeproperties continue to exist on ES6 "classes". In fact, all methods of a class are defined on prototypethe properties of the class.

class Point {
    
    
  constructor() {
    
    
    // ...
  }

  toString() {
    
    
    // ...
  }

  toValue() {
    
    
    // ...
  }
}

// 等同于

Point.prototype = {
    
    
  constructor() {
    
    },
  toString() {
    
    },
  toValue() {
    
    },
};

In the above code, constructor()、toString()、toValue()these three methods are actually defined Point.prototypeabove.

Calling a method on an instance of a class is actually calling a method on the prototype.

// b是B类的实例,它的constructor()方法就是B类原型的constructor()方法。
class B {
    
    }
const b = new B();

b.constructor === B.prototype.constructor // true

prototypeThe properties of the object constructorpoint directly to the "class" itself, which is consistent with the behavior of ES5.

Point.prototype.constructor === Point // true

Note: All methods defined inside the class are non-enumerable.

class Point {
    
    
  constructor(x, y) {
    
    
    // ...
  }

  toString() {
    
    
    // ...
  }
}

Object.keys(Point.prototype)
// []
Object.getOwnPropertyNames(Point.prototype)
// ["constructor","toString"]

The code is written in ES5, and toString()the method is enumerable.

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

Point.prototype.toString = function () {
    
    
  // ...
};

Object.keys(Point.prototype)
// ["toString"]
Object.getOwnPropertyNames(Point.prototype)
// ["constructor","toString"]

2. constructor() method

The constructor() method is the default method of the class. When an object instance is generated through the new command, this method is automatically called. A class must have a constructor() method, if not explicitly defined, an empty constructor() method will be added by default.

class Point {
    
    
}

// 等同于
class Point {
    
    
  constructor() {
    
    }
}

In the above code, an empty class Point is defined, and the JavaScript engine will automatically add an empty constructor() method to it.

The constructor() method returns the instance object (that is, this) by default, and it is completely possible to specify to return another object.

class Foo {
    
    
  constructor() {
    
    
    return Object.create(null);
  }
}

new Foo() instanceof Foo
// false

In the above code, the constructor() function returns a brand new object, which results in the instance object not being an instance of the Foo class.

The class must be called with new, otherwise an error will be reported. This is one of the main differences between it and ordinary constructors, which can be executed without new.

class Foo {
    
    
  constructor() {
    
    
    return Object.create(null);
  }
}

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

3. Instances of classes

newone time

The properties and methods of a class, unless explicitly defined on itself (that is, defined on this object), are defined on the prototype (that is, defined on the 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

In the above code, both x and y are properties of the instance object pointitself (because it is defined on thisthe object), so hasOwnProperty()the method returns true, toString()but are properties of the prototype object (because it is defined on Pointthe class), so hasOwnProperty()the method returns false. These are consistent with ES5 behavior.

As in ES5, all instances of a class share a prototype object.

var p1 = new Point(2,3);
var p2 = new Point(3,2);

p1.__proto__ === p2.__proto__
//true

p1p2Both are instances of and Pointtheir prototypes are Point.prototype, so __proto__the properties are equal.

4. Value function (getter) and storage function (setter)

getLike ES5, the and keyword can be used inside the "class" setto set a storage function and a value function for a certain property, and intercept the access behavior of the property.

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'

In the above code, propthe attribute has a corresponding storage function and value function, so the assignment and reading behaviors are customized.

The storage function and the value function are set on the Descriptor object of the property.

class CustomHTMLElement {
    
    
  constructor(element) {
    
    
    this.element = element;
  }

  get html() {
    
    
    return this.element.innerHTML;
  }

  set html(value) {
    
    
    this.element.innerHTML = value;
  }
}

var descriptor = Object.getOwnPropertyDescriptor(
  CustomHTMLElement.prototype, "html"
);

"get" in descriptor  // true
"set" in descriptor  // true

In the above code, the storage function and the value function are defined on the description object of the html attribute, which is completely consistent with ES5.

5. Static methods

static method:A class is equivalent to the prototype of an instance, and all methods defined in the class will be inherited by the instance. If you add a keyword before a method, staticit means that the method will not be inherited by the instance, but will be called directly through the class, which is called a "static method".

class Foo {
    
    
  static classMethod() {
    
    
    return 'hello';
  }
}

Foo.classMethod() // 'hello'

var foo = new Foo();
foo.classMethod(); // TypeError: foo.classMethod is not a function

Note that if the static method contains the this keyword, this this refers to the class, not the instance.

class Foo {
    
    
  static bar() {
    
    
    this.baz();
  }
  static baz() {
    
    
    console.log('hello');
  }
  baz() {
    
    
    console.log('world');
  }
}

Foo.bar() // hello

In the above code, the static method baris called this.baz, which thisrefers to Foothe class, not Foothe instance, which is equivalent to calling Foo.baz. In addition, it can also be seen from this example that static methods can have the same name as non-static methods.

6. Private methods and private properties

Private methods and private properties are methods and properties that can only be accessed inside the class and cannot be accessed outside.

ES2022 officially added private properties to classes by using #the notation before the property name.

class IncreasingCounter {
    
    
  #count = 0;
  get value() {
    
    
    console.log('Getting the current value!');
    return this.#count;
  }
  increment() {
    
    
    this.#count++;
  }
}
// #count就是私有属性,只能在类的内部使用(this.#count)。如果在类的外部使用,就会报错。
const counter = new IncreasingCounter();
counter.#count // 报错
counter.#count = 42 // 报错

7. Points to note about classes

1. Strict mode

Inside classes and modules, strict mode is the default, so there is no need to use a use strictspecified runtime mode. As long as your code is written in a class or module, only strict mode is available. Considering that all future codes will actually run in modules, ES6 actually upgrades the entire language to strict mode.

2. There is no variable hoisting

Classes do not have variable hoisting (hoist), which is completely different from ES5.

new Foo(); // ReferenceError
class Foo {
    
    }

In the above code, Foothe class is used before the definition, which will cause an error, because ES6 will not upgrade the class declaration to the code header. The reason for this provision is related to the inheritance mentioned below, which must ensure that the subclass is defined after the parent class.

{
    
    
  let Foo = class {
    
    };
  class Bar extends Foo {
    
    
  }
}

The above code will not report an error, because it has already been defined when it is Barinherited . However, if there is a promotion, the above code will report an error, because it will be promoted to the head of the code, and the command will not be promoted, so when it leads to inheritance , it has not yet been defined.FooFooclassclassletBarFooFoo

3. Generator method

If a method is preceded by an asterisk (*), it means that the method is a Generator function.

class Foo {
    
    
  constructor(...args) {
    
    
    this.args = args;
  }
  * [Symbol.iterator]() {
    
    
    for (let arg of this.args) {
    
    
      yield arg;
    }
  }
}

for (let x of new Foo('hello', 'world')) {
    
    
  console.log(x);
}
// hello
// world

In the above code, there is an asterisk before the Symbol.iterator method of the Foo class, indicating that the method is a Generator function. The Symbol.iterator method returns a default iterator of the Foo class, which is automatically called by the for...of loop.

4.This orientation

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

printNameIn the method this, it points to Loggerthe instance of the class by default. However, if this method is extracted and used alone, thisit will point to the environment where the method runs (because classthe internal is strict mode, so thisit actually points to undefined), which will cause printan error to be reported because the method cannot be found.

A relatively simple solution is to bind this in the constructor, so that the print method will not be found.

class Logger {
    
    
  constructor() {
    
    
    this.printName = this.printName.bind(this);
  }

  // ...
}

Another workaround is to use arrow functions.

class Obj {
    
    
  constructor() {
    
    
    this.getThis = () => this;
  }
}

const myObj = new Obj();
myObj.getThis() === myObj // true

Guess you like

Origin blog.csdn.net/Bon_nenul/article/details/128238488
Recommended