Object, class and object oriented programming
1, Understanding object
ECMA-262 defines an object as an unordered set of attributes. Strictly speaking, this means that an object is a set of values without a specific order. Each property or method of an object is identified by a name that maps to a value.
The usual way to create a custom Object is to create a new instance of the Object, and then add properties and methods to it.
The code is as follows (example):
let person=new Object(); person.name="Nicholas"; person.age=29; person.job="Software Engineer"; person.sayName=function(){ console.log(this.name); };
Literal creation method:
The code is as follows (example):
let person={ name:"Nicholas"; age:29; job:"Software Engineer"; sayName(){ console.log(this.name); } };
1. Type of attribute
ECMA-262 uses some internal features to describe the characteristics of attributes. These features are defined by the specification of the JavaScript implementation engine, so developers cannot access these features directly in JavaScript. To identify a feature as an internal feature, the specification encloses the name of the feature in two square brackets.
There are two types of attributes: data attributes and accessor attributes.
Data properties
A data attribute contains a location where data values are saved.
Data attributes have four characteristics that describe their behavior:
- [[Configurable]]: indicates whether the attribute can be deleted and redefined through delete, whether its characteristics can be modified, and whether it can be changed into an accessor attribute.
- [[Enumerable]]: indicates whether the attribute can be returned through a for in loop.
- [[Writable]]: indicates whether the value of the property can be modified.
- [[Value]]: contains the actual Value of the property.
To modify the default properties of a property, you must use the Object.defineProperty() method. This method takes three parameters: the object to which the attribute is added, the name of the attribute, and a descriptor object.
The code is as follows (example):
let person={}; Object.defineProperty(person,"name",{ writable:false, value:"Nicholas"; }); console.log(person.name);//"Nicholas" person.name="Greg"; console.log(person.name);//"Nicholas"
When calling Object.defineProperty(), if the values of configurable, enumerus, and writable are not specified, they all default to false.
After a property is defined as non configurable, it can no longer become configurable. Calling Object.defineProperty() again and modifying any non writable properties will cause an error.
The code is as follows (example):
let person={}; Object.defineProperty(person,"name",{ configurable:false, value:"Nicholas"; }); console.log(person.name);//"Nicholas" delete person.name; console.log(person.name);//"Nicholas" Object.defineProperty(person,"name",{ configurable:true,//report errors value:"Nicholas"; });
Accessor properties
Accessor properties do not contain data values. Instead, they contain an get function and a set function, but they are not required. When reading the accessor property, the get function will be called. The responsibility of this function is to return a valid value. When writing accessor properties, the setting function is called and a new value is passed in. This function must decide what changes to the data.
Get function and access function do not have to be defined. Only one location property defining the get function is read-only, and attempts to modify the property will be ignored. In strict mode, an attempt to write a property specified to get a function throws an error.
Data attributes have four characteristics that describe their behavior:
- [[Configurable]]: indicates whether the attribute can be deleted and redefined through delete, whether its characteristics can be modified, and whether it can be changed into an accessor attribute.
- [[Enumerable]]: indicates whether the attribute can be returned through a for in loop.
- [[Get]]: Get function, called when reading properties.
- [[Set]]: Set function, called when writing property.
When calling Object.defineProperty(), the values of configurable, enumerus, and writable are all false by default.
The code is as follows (example):
let book={ //Two data attributes are defined year_:2017,//Underscores are used to indicate that properties do not want to be accessed outside the object method edition:1 }; //year is the accessor property Object.defineProperty(book,"year",{ get(){ return this.year_; }; set(newValue){ if(newValue>2017){ this.year_=newValue; this.edition+=newValue-2017; } } }); book.year=2018; console,.log(book.edition);
2. Define multiple attributes
The object. Definepoerties() method can define multiple attributes at one time through multiple descriptors. It takes two parameters: the object for which you want to add or modify properties and another descriptor object.
The code is as follows (example):
//Same as before let book={}; Object.defineProperties(book,{ year_:{ value:2017; }, edition:{ value:1 }, year:{ get(){ return this.year_; }, set(newValue){ if(newValue>2017){ this.year_=newValue; this.edition+=newValue-2017; } } } });
3. Read attribute characteristics
The Object.getOwnPropertyDescriptor() method can obtain the property descriptor of the specified property. This method takes two parameters: the object where the property is located and the property name for which the descriptor is to be obtained. The return value is an object.
The code is as follows (example):
let book={}; Object.getOwnPropertyDescriptor(book,{ year_:{ value:2017; }, edition:{ value:1; }, year:{ get:function(){ return this.year_; }, set:function(newValue){ if(newValue>2017){ this.year_=newValue; this.edition+=newValue-2017; } } } }); let descriptor=Object.getOwnPropertyDescriptor(boo,"year_"); console.log(descriptor.value);//2017 console.log(descriptor.configurable);//false console.log(typeof descriptor.get);//undefined let descriptor=Object.getOwnPropertyDescriptor(boo,"year"); console.log(descriptor.value);//undefined console.log(descriptor.enumerous);//false console.log(typeof descriptor.get);//function
The Object.getOwnPropertyDescriptors() method is a static method. This method will actually call object. Get ownpropertydescriptors () on each of its own properties and return them in a new object.
The code is as follows (example):
let book={}; Object.getOwnPropertyDescriptor(book,{ year_:{ value:2017; }, edition:{ value:1; }, year:{ get:function(){ return this.year_; }, set:function(newValue){ if(newValue>2017){ this.year_=newValue; this.edition+=newValue-2017; } } } }); console.log(Object.getOwnPropertyDescriptors(book)); /*{ edition:{ configurable:false, enumerable:false, value:1, writable:false; }, year_:{ configurable:false, enumerable:false, value:2017, writable:false; }, year:{ configurable:false, enumerous:false; get:f(), set:f(newValue), }} */
4. Consolidation object
ECMAScript6 specifically provides an Object.assign() method for merging objects. This method receives a target object and one or more source objects as parameters, and then copies the enumerable (Object.propertyIsEnumerable() returns true) and self owned (Object.hasOwnProperty() returns true) properties in each source object to the target object.
The code is as follows (example):
let dest,src,result; //Simple copy dest={}; src={id='src'}; result=Object.assign(dest,src); console.log(dest===src);//true console.log(dest!==src);//true console.log(result);//{id='src'} console.log(dest);//{id='src'} //Multiple source objects dest={}; resukt=Object.assign(dest,{a:'foo'},{b:'bar'}); console.log(result);//{a:'foo',b:'bar}
Object.assign() actually performs a shallow copy of each source object. If multiple source objects have the same properties, the last copied value is used. If an error occurs during the assignment, the operation terminates and exits with an error thrown.
The code is as follows (example):
let dest,src,result; dest={id:'desk'}; result=Object.assign(dest,{id:'src1',a:'foo'},{id:'src2',b:'bar'}); console.log(result);//{id:src2,a:foo,b:bar}
5. Object identification and equality determination
ECMAScript6 specification adds Object.is():
The code is as follows (example):
console.log(Object.is(true,1));//false console.log(Object.is({},{}));//false console.log(Object.is("2",2));//false console.log(Object.is(+0,-0));//false console.log(Object.is(+0,0));//true console.log(Object.is(0,-0));//false console.log(Object.is(NaN,NaN));//true
6. Enhanced object syntax
Attribute value abbreviation
Short attribute names are automatically interpreted as attribute keys with the same name as long as they use variable names (no longer colon).
The code is as follows (example):
//original let name='Matt'; let person={ name:name }; console.log(person);//{name:'Matt'} //Abbreviation, equivalent let person={ name };
Computable attribute
Abbreviated method name
7. Object deconstruction
ECMAScript6 can use nested data to realize one or more assignment operations in a statement, that is, to realize object attribute assignment by using the structure matching with the object.
The code is as follows (example):
//Do not use object deconstruction let person={ name:'Matt', age:26, }; let personName=person.name, personAge=person.age; console.log(personName);//Matt console.log(personAge);//26 //Using object deconstruction let person={ name:'Matt', age:26; } let {name:personName,age:personAge}=person; console.log(personName);//Matt console.log(personAge);//26
The deconstruction assignment does not necessarily match the properties of the object. Some attributes can be ignored during assignment. If the referenced attribute does not exist, the value of the variable is undefined.
The code is as follows (example):
let person={ name:'Matt', age:26; } let {name job}=person; console.log(name);//'Matt' console.log(job);//undefined
If you assign a value to a previously declared variable, the assignment expression must be contained in a pair of parentheses.
The code is as follows (example):
let personName,personAge; let person={ name:'Matt', age:26; } ({name:personName,age:personAge})=person; console.log(personName,personAge);//Matt,26
Nested structure
Deconstruction assignments can use nested structures to match nested attributes. Nested deconstruction cannot be used when the outer attribute is not defined.
The code is as follows (example):
let person ={ name:'Matt', age:27, job:{ title:'Software engineer' } }; let {job:{title}}=person; console.log(title};//Software engineer
The code is as follows (example):
let person={ job:{ title:'Software engineer' } }; let personCopy={}; ({ //fooundefined foo:{ bar:personCopy.bar//TypeError } }=person);
Partial deconstruction
If a deconstruction expression involves multiple assignments, the initial assignment is successful and the subsequent assignment is wrong, the whole deconstruction assignment will only be completed partially.
The code is as follows (example):
let person={ name:'Matt', age:27 }; let personName,personAge,personBar; try{ ({name:personName,foo:{bar:personBar},age:personAge}=person); }catch(e){} console.log(personName,personBar,personAge); //Matt,undefined,undefined
Parameter context matching
Deconstruction assignment can also be performed in the function parameter list. Deconstruction and assignment of parameters will not affect the arguments object, but local variables can be used in the function body in the function signature.
2, Create object
1. General
ECMAScript6 supports classes and inheritance. The classes in ES6 are designed to fully cover the prototype based inheritance pattern designed by the previous specification.
2. Factory mode
Factory pattern is a well-known design pattern for the process of creating specific objects.
3. Constructor mode
Constructors in ECMAScript are used to create specific types of objects.
The code is as follows (example):
//Function declaration form function Person(name,age,job){ this.name:name; this.age:age; this.job:job; this.sayName=function(){ console.log(this.name); }; } let person1=Person("Nicholas",29,"Software Engineer"); let person2=Person("Greg",27,"Doctor"); person1.sayName();//Nicholas person2.sayName();//Greg //Function expression assigned to variable let person=function Person(name,age,job){ this.name:name; this.age:age; this.job:job; this.sayName=function(){ console.log(this.name); }; }
Constructors are also functions
The only difference between constructors and ordinary functions is to call methods. Any function that is called with the new operator is a constructor, and a function that is not called with the new operator is an ordinary function.
4. Prototype mode
Each function creates a prototype property, which is an object containing properties and methods that should be shared by instances of a specific reference type. In fact, this object is the prototype of the object created by calling the constructor. The advantage of using a prototype object is that the properties and methods defined on it can be shared by object instances.
The code is as follows (example):
function Person(){) Person.prototype.name="Nicholas"; Person.prototype.age=29; Person.prototype.job="Software Engineer"; Person.prototype.sayName=function(){ console.log(this.name); }; let person1=new Person(); person1.sayName();//Nicholas let person2=new Person(); person2.sayName();//Nicholas console.log(person1.sayName==person2.sayName);//true //person1 and person2 access the same attributes and functions
Understanding prototype
In any case, whenever a function is created, a prototype attribute (pointing to the prototype) is created for the function according to specific rules. By default, all prototype objects automatically get a property called constructor, which refers back to the constructor associated with it.
Isprotype() returns true when the [[Prototype]] of the passed in parameter points to the object that calls it.
Object.getPrototypeOf() returns the value of the internal attribute [[Prototype]] of the parameter.
setPrototypeOf can write a new value to the instance's private attribute [[Prototype]].
Object.create() creates a new object and assigns a prototype to it.
Prototype level
When a property is accessed through an object, the search starts by the name of the property. The search begins with the object instance itself. If the given name is found on this instance, the value corresponding to the name is returned. If this property is not found, the search will enter the prototype object along the pointer, find the property on the prototype object, and then return the corresponding value.
Although the values on the prototype object can be read through the instance, it is not possible to recreate them through the instance. If a property with the same name as that in the prototype object is added to the instance, this property will be created on the instance, and this property will mask the properties on the prototype object. Using the delete operator, you can completely delete this attribute on the instance, so that the identifier resolution process can continue with the four element prototype object.
The code is as follows (example):
function Person(){} Person.prototype.name="Nicholas"; Person.prototype.age=29; Person.prototype.job="Software Engineer"; Person.prototype.SayName=function(){ console.log(this.name); } let person1=new Person(); let person2=new Person(); person1.name="Greg"; console.log(person1.name);//Greg, from instance console.log(person2.name);//Nicholas, from the prototype delete person1.name; console.log(person1.name);//Nicholas, from the prototype
The hasOwnProperty() method is used to determine whether a property is on an instance or on a prototype Object. This method inherits from Object and returns true when the property exists on the Object instance calling it.
Prototype and in operator
There are two ways to use the in operator: alone and in a for in loop.
When used alone, the in operator returns true when the specified property can be accessed through the object, whether the property is on the instance or prototype.
The code is as follows (example):
function Person(){} Person.prototype.name="Nicholas"; Person.prototype.age=29; Person.prototype.job="Software Engineer"; Person.prototype.SayName=function(){ console.log(this.name); } let person1=new Person(); let person2=new Person(); console.log(person1.hasOwnProperty("name"));//false console.log("name" in person1);//true person1.name="Greg"; console.log(person1.name);//"Greg" console.log(person1.hasOwnProperty("name"));//true console.log("name" in person1);//true
When the in operator is used in the for in loop, all properties that can be accessed through the object and can be enumerated will be returned, including instance properties and prototype properties. Instance properties that cannot be enumerated ([[enumerus]] attribute is set to false) in the masking prototype will also be returned in the for in loop. To get all enumerable instance properties on an object, you can use the Object.keys() method.
The Object.keys() method returns all enumerable properties and key names of the object itself, excluding inherited.
The code is as follows (example):
function Person(){} Person.prototype.name="Nicholas"; Person.prototype.age=29; Person.prototype.job="Software Engineer"; Person.prototype.sayName=function(){ console.log(this.name); } let keys=Object.keys(Person.prototype);//The constructor object and the properties added later are returned console.log(keys);//"name,age,job,sayName" let p1=new Person(); p1.name="Rob"; p1.age=31; let p1keys=Object.keys(p1);//It returns its own properties, excluding those on the prototype object console.log(p1keys);//"[name,age]"
The Object.getOwnPropertyNames() method can list the properties of all instances, including the non enumerable property constructor, whether enumerable or not.
The Object.getOwnPropertySymbols() method is for properties with symbols as keys.
Property enumeration order
The enumeration order of the for in loop and Object.keys() is uncertain. The enumeration order of Object.getOwnPropertyNames(), Object.getOwnPropertySymbols(), and Object.assign() is deterministic. Enumerate numeric keys in ascending order, and then enumerate string and symbol keys in insertion order.
The code is as follows (example):
let k1=Symbol('k1'), k2=Symbol('k2'); let o={ 1:1, first:'first', [k1]:'sym2', second:'second', 0:0, }; o[k2]='sym2'; o[3]=3; o.third='third'; o[2]=2; console.log(Object.getOwnPropertyNames(o); //["0","1","2","3","first","second","third"] console.log(Object.getOwnPropertySymbols(o); //[Symbol(k1),Symbol(k2)]
5. Object iteration
ECMAScript2017 adds two static methods for converting object content into a serialized -- and more importantly, iterative -- format. Object.values() returns an array of object values, and Object.entries() returns an array of key / value pairs.
Other prototype syntax
In this example, Person.prototype is set to be equal to a new Object created by Object literal. Its constructor property does not point to Person, but points to a completely different new Object (Object constructor) and no longer points to the original constructor.
The code is as follows (example):
function Person(){} Person.prototype={ name:"Nicholas", age:29, job:"Software Engineer", sayName(){ console.log(this.name); } };
You can use the Object.defineProperty() method to define the constructor property:
The code is as follows (example):
function Person(){} Person.prototype={ name:"Nicholas", age:29, job:"Software Engineer", sayName(){ console.log(this.name); } }; //Restore constructor properties Object.defineProperty(Person.prototype,"constructor",{ enumerable:false, value:Person });
Dynamics of prototype
Even if the instance exists before modifying the prototype, any changes made to the prototype object will be reflected on the instance. The instance created after overriding the prototype on the constructor will reference the new prototype, so the instance created before will still reference the original prototype.
The code is as follows (example):
let friend=new Person(); Person.prototype.sayHi=function(){ console.log("hi"); } friend.sayHi();//"hi"
The code is as follows (example):
function Person(){} let friend=new Person(); Person.prototype={ constructor:Person, name:"Nicholas", age:29, job:"Software Engineer", sayName(){ console.log(this.name); } }; //friend points to the original prototype friend.sayName();//error
Prototype problem
The prototype pattern weakens the ability to pass initialization parameters to the constructor, resulting in all instances obtaining the same property value by default. The main problem of prototype comes from its sharing feature.
The code is as follows (example):
function Person(){} let friend=new Person(); Person.prototype={ constructor:Person, name:"Nicholas", age:29, job:"Software Engineer", friends:["Shelby","Court"], sayName(){ console.log(this.name); } }; let person1=new Person(); let person2=new Person(); person1.friends.push("Van");//friends exist on Person.prototype //person1.name="Greg" is a modification of person1.name, not on the prototype console.log(person1.friends);//"Shelby","Court","Van" console.log(person2.friends);//"Shelby","Court","Van"
3, Inherit
Implementation inheritance is the only inheritance method supported by ECMAScript, which is mainly realized through prototype chain.
1. Prototype chain
ECMA-262 defines the prototype chain as the main inheritance mode of ECMAScript. Its basic idea is to inherit the properties and methods of multiple reference types through the prototype.
The code is as follows (example):
function SuperType(){ this.property=true; } SuperType.prototype.getSuperValue=function(){ return this.property; } function SubType=(){ this.subproperty=false; } //Inherit SuperType SubType.prototype=new SuperType(); SubType.prototype.getSubValue=function(){ return this.subproperty; } let instance=new SubType(); console.log(instance.getSuperValue());//true
Default prototype
The default prototype of any function is an instance of Object, which means that the instance has an internal pointer to Object.prototype.
Prototype and inheritance relationship
The relationship between prototype and instance can be determined in two ways. The first method is to use the instanceof operator. If the corresponding constructor has appeared in the prototype chain of an instance, instanceof returns true. Technically speaking, instance is an instance of Object, SuperType and SubType, because the prototype chain of instance contains the prototypes of these constructors. The result is that instanceof returns true for all of these constructors. The second is to use the isPrototypeOf() method, which returns true as long as the prototype is included in the prototype chain.
About methods
To override the parent class or add methods that the parent class does not have, a subclass must be added to the prototype after the prototype assignment.
Prototype chain problem
The main problem of prototype chain occurs when the prototype contains reference values. When you use a prototype to implement inheritance, the prototype actually becomes an instance of another type. This means that the original instance property becomes the prototype property. The second problem is that subtypes cannot pass parameters to the constructor of the parent type when instantiated.
The code is as follows (example):
function SuperType(){ this.color=["red","blue","green"]; } function SubType(){} SubType.prototype=new SuperType(); let instance1=new SubType(); instance1.colors.push("black"); console.log(instance1.colors);//"red","blue","green","black" let instance2=new SubType(); console.log(instance2.colors);//"red","blue","green","black"
2. Embezzle constructor
The basic idea of embezzling the constructor is to call the parent class constructor in the subclass constructor.
The code is as follows (example):
function SuperType(){ this.color=["red","blue","green"]; } function SubType(){ //Inherit SuperType SuperType.call(this); } let instance1=new SubType(); instance1.colors.push("black"); console.log(instance1.colors);//"red","blue","green","black" let instance2=new SubType(); console.log(instance2.colors);//"red","blue","green"
One advantage of stealing constructor is that it can pass parameters to the parent constructor in the subclass constructor. The disadvantage is that the method must be defined in the constructor, so the function cannot be reused, and the subclass cannot access the defined method on the parent prototype.
The code is as follows (example):
function SuperType(name){ this.name=name; } function SubType(){ SuperType.call(this,"Nicholas"); this.age=29; } let instance=new SubType(); console.log(instance.name);//Nicholas console.log(instance.age);//29
3. Combination inheritance
Combinatorial inheritance combines prototype chain and stealing constructor. The basic idea is to use prototype chain to inherit properties and methods on prototype, and inherit instance properties through stealing constructor.
The code is as follows (example):
function SuperType(name){ this.name=name; this.colors=["red","green","blue"]; }; SuperType.prototype.sayName=function(){ console.log(this.name); }; function SubType(name,age){ //Stealing constructor inheritance properties SuperType.call(this,name); this.age=age; }; //Prototype chain inheritance method SubType.property=new SuperType(); SuperType.prototype.sayAge=function(){ console.log(this.age); }; let instance1=new SubType("Nicholas",29); instance1.colors.push("black"); console.log(instance1.colors);//"red","green","blue","black" instance1.sayName();//Nicholas instance1.sayAge();//29 let instance2=new SubType("Greg",27); console.log(instance2.colors);//"red","green","blue" instance2.sayName();//Greg instance2.sayAge();//27
4. Prototype inheritance
The object() function will create a temporary constructor, assign the incoming object to the prototype of the constructor, and then return an instance of the temporary type. In essence, object() is a shallow copy of the incoming object.
The code is as follows (example):
function object(o){ function F(){} F.prototype=o; return new F(); }
The code is as follows (example):
let person={ name:"Nicholas", friends:["Shelby","Court","Van"]; }; let anotherPerson=object(person); anotherPerson.name="Greg"; anotherPerson.friends.push("Rob"); let yetanotherPerson=object(person); yetanotherPerson.name="Linda"; yetanotherPerson.friends.push("Barbie"); console.log(person.friends);//"Shelby","Court","Van","Rob","Barbie"
ECMAScript5 adds an Object.create() method to normalize the concept of prototype inheritance.
The code is as follows (example):
let person={ name:"Nicholas", friends:["Shelby","Court","Van"]; }; let anotherPerson=Object.create(person); anotherPerson.name="Greg"; anotherPerson.friends.push("Rob"); let yetanotherPerson=Object.create(person); yetanotherPerson.name="Linda"; yetanotherPerson.friends.push("Barbie"); console.log(person.friends);//"Shelby","Court","Van","Rob","Barbie"
5. Parasitic inheritance
The idea behind parasitic inheritance is similar to parasitic constructor and factory pattern: create a function that implements inheritance, enhance the object in some way, and then return the object.
The code is as follows (example):
function createAnother(original){ let clone=object(original); clone.sayHi=function(){ console.log("hi"); } }; let person={ name:"Nicholas", friends:["Shelby","Court","Van"]; }; let anotherPerson=createAnother(person); anotherPerson.sayHi();//hi //Otherperson has all the attributes and methods of person and a new method of sayHi
6. Parasitic combinatorial inheritance
Parasitic composite inheritance inherits properties by stealing constructors, but uses hybrid prototype chain inheritance methods. The basic idea is not to assign a value to the child class prototype by calling the parent class constructor, but to obtain a copy of the parent class prototype.
The code is as follows (example):
function inheritPrototype(subType,superType){ //Create a new object proto based on the prototype of Super let prototype=object(superType.prototype); //Enhance the new object and assign it a constructor value prototype.constructor=subType; //Assign the new object to the prototype of the subclass. subType.prototype=prototype; } function SuperType(name){ this.name=name; this.colors:["red","blue","green"]; } SuperType.prototype.sayName=function(){ console.log(this.name); } function SubType(name,age){ SuperType.call(this,name); this.age=age; } //Call function to realize inheritance. Replace the previous Sub.prototype = new Super(); Statement to prevent the Super constructor from being called twice inheritPrototype(SubType,SuperType); SubType.prototype.sayAge=function(){ console.log(this.age); }
4, Class
1. Class definition
There are two main ways of class definition: class declaration and class expression.
The code is as follows (example):
//Class declaration class Person{} //Class expression const Animal=class{};
Class expressions cannot be referenced until they are evaluated, and class definitions cannot be promoted.
The code is as follows (example):
console.log(FunctionExpression);//undefined var FunctionExpresstion=function() {}; conasole.log(FunctionExpresstion);//function() {}
Function is limited by function scope, and class is limited by block scope:
The code is as follows (example):
{ function FunctionDeclaration() {} class ClassDeclaration{} } console.log(FunctionDeclaration); console.log(ClassDeclaration);
2. Class constructor
The constructor keyword is used to create a constructor for a class within a class definition block.
instantiation
The JavaScript interpreter knows that using new and classes means that it should be instantiated using the constructor function.
The code is as follows (example):
class Person{ constructor(name){ console.log(arguments.length); this.name=name||null; } } let p1=new Person;//0 console.log(p1.name);//null let p2=new Person();//0 console.log(p2.name);//null let p3=new Person("Jake");//1 console.log(p3.name);//Jake
The main difference between a class constructor and a constructor is that the new operator must be used to call the class constructor.. If ordinary functions do not use new calls, they will take the global this as the internal object. Calling the class constructor will throw an error if new is not used.
The code is as follows (example):
function Person(){} class Animal{} let p=Person(); let a=Animal();//error
Treat classes as special functions
3. Instances, prototypes, and class members
Instance member
The class constructor is executed every time the class identifier is called through new. Inside this constructor, you can add a "own" attribute to the newly created instance (this).
Each instance corresponds to a unique member object, which means that all members will not be shared on the prototype.
Prototype method and accessor
In order to share methods between instances, class definition syntax takes the methods defined in class blocks as prototype methods. Methods can be defined in class constructors or class blocks, but original values or objects cannot be added to the prototype as member data in class blocks.
The code is as follows (example):
class Person{ constructor(){ //Everything added to this will exist on different instances this.locate = () => console.log('instance'); } //Everything defined in the class block is defined on the prototype of the class locate(){ console.log('prototype'); } } let p=new Person(); p.locate();//instance Person.prototype.locate();//prototype
Static class method
Static class methods are usually used to perform operations that are not specific to instances, and do not require the existence of instances of classes. There can only be one static member on each class.
The code is as follows (example):
class Person{ constructor(){ //Everything added to this will exist on different instances this.locate = () =>console.log('instance',this); } //Defined on the prototype object of the class locate(){ console.log('prototype',this); } //Defined on the class itself static locate(){ console.log('class',this); } } let p=new Person(); p.locate();//instance,Person{} Person.prototype.locate();//prototype,{constructor:...} Person.locate();//class,class Person{}
Non function prototypes and class members
Although the class definition does not show support for adding member data on prototypes or classes, it can be added manually outside the class definition.
The code is as follows (example):
class Person{ sayName(){ console.log(`${Person.greeting} ${this.name}`); } } //Defining data members on a class Person.greeting='My name is'; //Define data members on prototypes Person.prototype.name='Jake'; let p=new Person(); p.sayName();//My name is Jake
Iterators and generators
Class definition syntax supports defining generator methods on prototypes and classes themselves.
4. Succession
Inheritance basis
ES6 class supports single inheritance. Using the extends keyword, you can inherit any object with [[Constructor]] and prototype.
Derived classes will access the party defined on the class and prototype through the prototype chain. this value reflects the instance or class that called the corresponding method.
The code is as follows (example):
class Vehicle{ identifyPrototype(id){ console.log(id,this); } static identifyClass(id){ console.log(id,this); } } class Bus extends Vehicle{} let v=new Vehicle(); let b=new Bus(); b.identifyPrototype('bus');//bus,Bus{} v.identifyPrototype('vehicle');//vehicle,Vehicle{} Bus.identifyClass('bus');//bus,class Bus{} Vehicle.identifyClass('vehicle');//vehicle,class Vehicle{}
Constructors, HomeObject, and super()
Methods of derived classes can reference their prototypes through the super keyword. This keyword can only be used in derived classes and is limited to class constructors, instance methods, and static methods.
Use super in the class constructor to call the parent class constructor:
The code is as follows (example):
class Vehicle{ constructor(){ this.hasEngine=true; } } class Bus extends Vehicle{ constructor(){ //Do not reference this before calling super, otherwise ReferenceError will be thrown super();//Equivalent to super.constructor() console.log(this instanceof Vehicle);//true console.log(this);//Bus {hasEngine:true} } } new Bus();
In the static method, you can call the static method defined on the inherited class through super:
The code is as follows (example):
class Vehicle{ static identify(){ console.log('vehicle'); } } class Bus extends Vehicle{ static identify(){ super.identify(); } } Bus.identify();//vehicle
When using super, you should pay attention to several problems:
- super can only be used in derived class constructors and static methods.
- You cannot refer to the super keyword alone, either call the constructor with it, or reference a static method with it.
- Calling super() will call the parent constructor and assign the returned instance to this.
- super() acts like calling a constructor. If you need to pass parameters to the parent constructor, you need to enter them manually.
- If the class constructor is not defined, super() will be called when instantiating the derived class, and all parameters passed to the derived class will be passed in.
- In a class constructor, you cannot reference this before calling super().
- If the constructor is defined in the derived class, it is not necessary to invoke super() in it, or to return an object in it.
Abstract base class
New.target saves the class or function called through the new keyword. By detecting whether new.target is an abstract base class during instantiation, instantiation of the abstract base class can be prevented.
The code is as follows (example):
//Abstract base class class Vehicle{ constructor(){ console.log(new.target); if(new.target===Vehicle){ throw new Error('Vehicle cannot be directly instantiated'); } } } //Derived class class Bus extends Vehicle{} new Bus();//class Bus{} new Vehicle();//class Vehicle //Error:Vehicle cannot be directly instantiated
Inherit built-in types
The code is as follows (example):
class SuperArray extends Array{ shuffle(){ for(let i=this.length-1;i>0;i--){ const j=Math.floorI+(Math.random()*(i+1)); [this[i],this[j]]=[this[j],this[i]]; } } } let a=new SuperArray(1,2,3,4,5); console.log(a instanceof Array); console.log(a instanceof SuperArray); console.log(a);//[1,2,3,4,5] a.shuffle(); console.log(a)//[3,1,4,5,2]
Class blending
Centralizing the behavior of different classes into one class is a common JavaScript pattern. The mixed mode can be realized by concatenating multiple mixed elements in an expression, and the expression will eventually be resolved to a class that can be inherited.
summary
Objects can be created and enhanced at any time during code execution. They are very dynamic and are not strictly defined entities. The following pattern applies to creating objects:
the factory pattern is a simple function that creates an object, adds properties and methods to it, and then returns the object. This pattern has rarely been used since the constructor pattern appeared.
you can customize the reference type by using the constructor mode, and you can use the new keyword to create an instance of a custom type like creating an instance of a built-in type. However, the constructor pattern also has some shortcomings, mainly because its members cannot be reused, including functions. Considering that the function itself is loose and weakly typed, there is no reason why the function cannot be shared among multiple object instances.
the prototype pattern solves the problem of member sharing. As long as the properties and methods added to the constructor prototype can be shared. The combined constructor and prototype pattern define instance properties through the constructor and shared properties and methods through the prototype.
JavaScript inheritance is mainly realized through prototype chain. The prototype chain involves assigning the prototype of a constructor to an instance of another type. In this way, the subclass can access all the properties and methods of the parent class, just like class based inheritance. The problem with the prototype chain is that all inherited properties and methods will be shared among object instances, and the instance cannot be private. Embezzling the constructor function can avoid this problem by calling the parent class constructor in the subclass constructor. In this way, the inherited properties of each instance can be private, but the type can only be defined through the constructor pattern (because the subclass cannot access the functions on the parent class prototype). At present, the most popular inheritance mode is composite inheritance, that is, inheriting shared properties and methods through prototype chain and inheriting instance properties by stealing constructor.
In addition to the above modes, there are several inheritance modes:
prototype inheritance can inherit first without explicitly defining the constructor, which is essentially a shallow copy of a given object. The results of this operation can be further enhanced later.
closely related to prototype inheritance is parasitic inheritance, that is, first create a new object based on an object, then enhance the new object, and finally return the new object. This pattern is also used in composite inheritance to avoid waste caused by repeated calls to the parent constructor.
parasitic composite inheritance is considered to be the most effective way to implement type based inheritance.
The new classes in ECMAScript6 are largely based on the syntax sugar of the existing prototype mechanism. Class syntax allows developers to gracefully define backward compatible classes, which can inherit both built-in types and custom types. Class effectively spans the gap between object instance, object prototype and object class.