文章目录
一:构造函数
在了解class (类)的使用之前,需要先了解一下什么是构造函数
1:定义:通常首字母大写,用于和普通函数区分(不大写也可以,但是前端为了更好区分使用大写)。
2:区别:构造函数和普通函数的区别就是调用方式的不同,普通函数是直接调用,而构造函数需要使用new关键字来调用。
3、执行流程
- 立即创建一个新的对象
- 将新建的对象赋值给函数中的this
- 从上至下依次执行函数中的代码
- 将新建的对象作为返回值返回
4:返回值:默认是返回this,也就是new 出来的实例对象,如果有返回值就根据返回值来
5:作用:新建实例对象,并且给实例对象内的成员(属性或方法)进行赋值:。
functionPerson(name){
console.log('我是'+ name);}let p =newPerson('张三');
console.log('p', p);
打印如下
二:类(class)
0:类里面为什么要使用构造函数?
classPerson{// 每new一个对象后都会执行这个函数constructor(name, age, gender){this.name = name;this.age = age;this.gender = gender;}}
console.log(typeof Person);// function// 不使用构造函数let p0 =newPerson();
p0.name='张三';
p0.age=18;
p0.gender='男';
console.log(typeof p0);// object// 使用构造函数let p1 =newPerson('李四',19,'男');
console.log(typeof p1);// objectlet p2 =newPerson('王五',20,'女');
console.log(typeof p2);// object// 综上:class中使用构造函数的作用就是减少了代码的赋值。
综上:class中使用构造函数的作用就是减少了代码的赋值。
1:类的由来
JavaScript 语言中,生成实例对象的传统方法是通过构造函数。新的class写法是为了让对象原型的写法更加清晰,更新面向编程的写法。
// 构造函数生成实例对象(传统方法)functionPerson(x, y){this.x = x;this.y = y;}Person.prototype.toString=function(){return'('+this.x +', '+this.y +')';};var p =newPerson(1,2);// class 关键字创建的实例对象(es6)classPerson{constructor(x, y){this.x = x;this.y = y;}toString(){return'('+this.x +', '+this.y +')';}};
2:类的constructor函数
(1)constructor()方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor()方法,如果没有显式定义,一个空的constructor()方法会被默认添加。
// 如下两行代码的效果其实是一样的classPerson{}classPerson{constructor()}
(2)值得注意的是:类必须使用new调用,否则会报错。这是它跟普通构造函数的一个主要区别,后者不用new也可以执行。
classPerson{// ... }var person=Person('张三',18);// 报错var person =newPerson('李四',19);// 正确
3:类的实例(__ proto __)
类的所有实例共享一个原型对象
var p1=newPerson('张三',18);var p2=newPerson('李四',19);
p1.__proto__ === p2.__proto__ // true
上诉代码的p1和p2都是person的实例,原型都是person.prototype,因此__proto__也是相等的。这说明,你可以通过实例__proto__属性为类添加方法。
注意1:**__proto__是厂商添加的私有属性,即使有很多浏览器都提供 了这个属性,但是仍然不推荐你使用在生成环境中**。生成环境中可以使用 Object.getPrototypeOf() 方法来获取实例对象的原型,然后再来为原型添加方法/属性。
注意2:__proto__上添加属性,这会改变类的原始定义,因而使用必须谨慎。
4:类的原型(prototype)
类的所有方法都定义在类的prototype属性上面。
classPerson{constructor(){// ...}toString(){// ...}toValue(){// ...}}// 等同于Person.prototype ={constructor(){},toString(){},toValue(){},};
也就是说:在类的实例上面调用方法,其实就是调用原型上的方法(如下代码)
classB{}const b =newB();
b.constructor ===B.prototype.constructor // true
prototype对象的constructor属性,直接指向“类”的本身,这与 ES5 的行为是一致的。
注意:toString()方法是Person类内部定义的方法,它是不可枚举的。另外,类的内部所有定义的方法,都是不可枚举的。
5:类的取值函数(getter)和存值函数(setter)
作用:为了能够在类中拦截该属性的存取行为
classMyClass{constructor(name, age){this.name = name;this.age = age;}getprop(){return'getter';}setprop(value){
console.log('setter: '+value);}}let p =newMyClass('张三',18);
p.prop =123;// setter: 123
存值函数和取值函数是设置在属性的 Descriptor 对象上的。
classCustomHTMLElement{constructor(element){this.element = element;}gethtml(){returnthis.element.innerHTML;}sethtml(value){this.element.innerHTML = value;}}var descriptor = Object.getOwnPropertyDescriptor(CustomHTMLElement.prototype,"html");"get"in descriptor // true"set"in descriptor // true
6:类的表达式
1:使用语法
(1)class Person {} // 类的定义
(2)const MyClass = class Me {} // 类的表达式
2:定义
假设在const MyClass = class Me {} 代码中
(1)在Class内部,这个类的名字是Me,Me只在Class 的内部可用
(2)在Class外部,这个类只能用MyClass引用。
const MyClass =classMe{getClassName(){return Me.name;}};let my=newMyClass();
my.getClassName()// Me
Me.name // ReferenceError: Me is not defined
(3)区别
函数声明会提升,类声明不会。需要先声明类,然后再访问它,否则就会出现ReferenceError
// Person is not definedconst test =newPerson();classPerson{}// undefinedclassPerson{}const test =newPerson();
7:类的属性表达式
这没啥好说的,就是通过定义变量,然后在类里面通过[]的方式来获取变量,即Person的方法名getName,是从表达式得到的。
let methodName ='getName';classPerson{constructor(length){// ...}[methodName](){// ...}}
8:静态属性、静态方法和静态块
静态属性、静态方法和静态块都是通过关键字static来定义的
(1)静态方法
①定义:如果在一个方法前,加上static关键字,这就称为“静态方法”。
②作用:表示该方法不会被实例继承,而是直接通过类来调用。如果在实例上调用静态方法,会抛出一个错误,表示不存在该方法。
③注意点:如果静态方法包含this关键字,这个this指的是类,而不是实例。
classFoo{staticbar(){this.baz();}staticbaz(){
console.log('hello');}baz(){
console.log('world');}}
Foo.bar()// hello
上面代码中,静态方法bar调用了this.baz,这里的this指的是Foo类,而不是Foo的实例,等同于调用Foo.baz。另外,从这个例子还可以看出,静态方法可以与非静态方法重名。
④父类的静态方法,可以被子类继承。
⑤静态方法也是可以从super对象上调用的。
(2)静态属性
①定义:静态属性指的是 Class 本身的属性,即Class.propName,而不是定义在实例对象(this)上的属性。
②新老写法的对比
老写法:定义在类的外部,整个类生成以后再生成静态属性。容易忽略。
新写法:显式声明,语义更好。
// 老写法classPerson{// ...}
Person.personName="张三";// 新写法classPerson{static personName="张三";}
(3)静态块
①由来:如果有初始化逻辑,这个逻辑要么写在类的外部,要么写在constructor()方法里面。前者是将类的内部逻辑写到了外部,后者则是每次新建实例都会运行一次。ES2022 引入了静态块(static block),允许在类的内部设置一个代码块,在类生成时运行且只运行一次,主要作用是对静态属性进行初始化。以后,新建类的实例时,这个块就不运行了。
②作用:为了解决初始化逻辑存放位置的问题。
③使用:每个类允许有多个静态块,每个静态块中只能访问之前声明的静态属性。另外,静态块的内部不能有return语句。静态块内部可以使用类名或this,指代当前类。除了静态属性的初始化,静态块还有一个作用,就是将私有属性与类的外部代码分享。
let getNme;exportclassC{// 每个类允许有多个静态块,每个静态块中只能访问之前声明的静态属性static personName="张三";static{this.personName;// 张三// 或者C.personName;// 张三}
#personName="张三";static{getX=obj=> obj.#personName;}}
console.log(getX(newC()));// 张三
上面示例中,#x是类的私有属性,如果类外部的getX()方法希望获取这个属性,以前是要写在类的constructor()方法里面,这样的话,每次新建实例都会定义一次getX()方法。现在可以写在静态块里面,这样的话,只在类生成时定义一次。
9:私有方法和私有属性
(1)老方法
早期私有方法和私有属性通过方法或属性前面添加_来表示这是一个内部使用的私有方法,或者使用Symbol值的唯一性,将私有方法的名字命名为一个Symbol值。
const bar =Symbol('bar');const snaf =Symbol('snaf');exportdefaultclassmyClass{// 公有方法foo(baz){this[bar](baz);}// 私有方法[bar](baz){returnthis[snaf]= baz;}// ...};
上面代码中,bar和snaf都是Symbol值,一般情况下无法获取到它们,因此达到了私有方法和私有属性的效果。但是也不是绝对不行,Reflect.ownKeys()依然可以拿到它们。
const inst =newmyClass();
Reflect.ownKeys(myClass.prototype)// [ 'constructor', 'foo', Symbol(bar) ]
(2)新写法(ES2022)
①定义:ES2022正式为class添加了私有属性,私有属性和私有方法,是只能在类的内部访问的方法和属性,外部不能访问,不可以直接通过 Class 实例来引用。
②使用:在方法或者是属性名之前使用#表示。
classPerson{
#personName='张三';// #personName就是私有属性,只能在类的内部使用(this.#personName)。如果在类的外部使用,就会报错。getvalue(){
console.log('Getting the current value!');returnthis.#personName;}}const getPersonName=newPerson();
gName.#personName// 报错
gName.#personName=42// 报错
③注意点:私有方法只能内部调用,在外部调用就会报错。
10:其他
1:类不存在变量提升
2:name属性:name属性总是返回紧跟在class关键字后面的类名。
classPerson{}
Person.name // "Person"
3:this指向
问题:类的方法内部如果含有this,它默认指向类的实例。但是如果将这个方法提取出来单独使用,this会指向该方法运行时所在的环境(由于 class 内部是严格模式,所以 this 实际指向的是undefined),从而导致找不到方法而报错。
解决方法:在构造方法中绑定this或者使用箭头函数
版权归原作者 前端张三 所有, 如有侵权,请联系我们删除。