1、es6中推目的是什么?
为了更好的实现面向对象编程,降低代码的冗余度,es6推出了class类的概念。他实际上是一种语法糖,本质上利用的还是原型和构造函数的概念。
2、类定义
两种方式:
A)类声明
class Person {}
B)类表达式
let Person = class [PersonName] {}
//表达式中这个名称PersonName是可选的,可通过Person.name访问
console.log(Person.name) //PersonName
//创建对象只能用Perosn类名
let p=new Person()
注意:函数受函数作用域的限制,类受块作用域的限制。
{
class Person{}
function fun(){}
}
console.log(fun)
console.log(Person)

3、类的构成
js中class类主要分为五类:构造函数方法,实例方法,静态方法,获取函数,设置函数。
class Person {
//构造函数方法
constructor(name,age){
this.name=name;
this.age=age;
}
//实例方法
getName(){
console.log(this.name)
}
//静态方法
static sayHello(){
console.log('你好,陌生人')
}
//设置函数
set _name(val){
console.log('有人试图改名字',val)
this.name=val
}
//获取函数
get _name(){
console.log('有人想获取名字')
return this.name
}
}
let p =new Person('zhangsan',18)
p.getName()
Person.sayHello()
p._name='lisi'
console.log(p._name)

4、类构造函数
constructor关键字用于在类定义块内部创建类的构造函数。方法名constructor会告诉解释器在使用new操作符创建类实例的时候,应该调用这个函数。构造函数的定义不是必须的,不定义构造函数相当于将构造函数定义为空函数。
使用new创建类实例的过程:
A)创建一个新对象
B)对象的[[prototype]]属性指向构造函数的原型
C)构造函数中的this指向该对象
D) 执行构造函数,初始化实例对象
E) 构造函数若返回引用类型的值,将返回该值;否则,将返回新对象。
类构造函数和构造函数的区别:
类构造函数只能通过new 操作符调用,构造函数可以直接调用,也能用new调用。
类的本质:
类的本质就是一个函数,只不过只能用new调用。
console.log(typeof Person)
console.log(Person.constructor===Function)

如上面所说,Person的本质上是一个函数(类)对象,他是Function的一个实例,他自身上是没有constructor属性的,但是他的__proto__原型对象指向Function.prototype是有constructor,并且指向Function。
因为Person.constructor就是Function,因此调用该函数会返回一个函数,如下:
A)使用new操作符调用

B)直接调用

因为类是函数,所以可以向立即执行函数那样立即实例化:
let p=new class Person {
//构造函数方法
constructor(name,age){
this.name=name;
this.age=age;
console.log('调用了构造函数')
}
//实例方法
getName(){
console.log(this.name)
}
//静态方法
static sayHello(){
console.log('你好,陌生人')
}
//设置函数
set _name(val){
console.log('有人试图改名字',val)
this.name=val
}
//获取函数
get _name(){
console.log('有人想获取名字')
return this.name
}
}('liji',999)
console.log(p)

5、实例,原型和类成员
实例数据成员和方法:
定义在类构造函数中的变量和方法,在用new实例化之后,会进入到不同的对象实例当中,他们是相互独立的。
class Person {
sex=‘男’ //也会作为实例属性,不推荐这么用
//构造函数方法
constructor(name,age){
this.name=name;
this.age=age;
this.sayName=()=>{
console.log(this.name)
}
}
}
let p1=new Person('zhangsan',17)
let p2=new Person('lisi',18)
console.log(p1.sayName===p2.sayName) //输出false,实例方法在不同实例间是相互独立的
原型数据成员和方法:
A)原型方法:
定义在类块中的方法,为原型方法,可在不同的实例之间共享。
class Person {
//构造函数方法
constructor(name,age){
this.name=name;
this.age=age;
this.sayName=()=>{
console.log(this.name)
}
}
//原型方法
getName(){
console.log(this.name)
}
}
let p1=new Person('zhangsan',17)
let p2=new Person('lisi',18)
console.log(p1.getName===p2.getName) //输出true,原型方法共享
B)原型数据成员:
class Person2 {
sex:'nan'
}
![]()
如上,类中不支持直接添加原型数据成员,需要手动添加:
Person2.prototype.sex='男'
类方法和类数据成员
A)类方法:
class Person {
//静态方法
static sayHello(){
console.log('你好,陌生人')
}
static sayGoodBye(){
console.log('再见,陌生人')
}
}
B)类数据成员:
两种方式:
//第一种
class Person {
static sex='男'
}
//第二种
Person.sex='男'
小结:
类成员和原型成员都是实现共享,只是从不同层面上的共享。类成员是类层面的共享,原型成员是实例层面的共享。
6、迭代器和生成器方法
可以在类本身,或原型上定义生成器函数,使实例化出来的对象成为一个可迭代对象。(虽然方法能定义在类本身或原型上,但是只有在原型上实现[Symbol.iterator],实例化出来的对象才是可迭代对象)
//迭代器方法
class Person {
constructor(){
this.nickNames=['jack','jake','J-dog']
}
[Symbol.iterator](){
return this.nickNames.entries()
}
}
let p=new Person();
for (let item of p){
console.log(item)
}
//生成器方法
class Person {
constructor(){
this.nickNames=['jack','jake','J-dog']
}
*[Symbol.iterator](){
yield *this.nickNames.entries()
}
}
let p=new Person();
for (let item of p){
console.log(item)
}

7、类继承
class类通过extends关键字实现继承,他可以继承自任何拥有[[Construct]]和原型的对象(不就是函数对象吗),这意味着,他既可以继承自类,亦可以继承自构造函数。
//继承自类
class Person {}
class Driver extends Person{}
let d=new Driver();
console.log(d instanceof Person)
//继承自构造函数
function Person() {}
class Driver extends Person{}
let d=new Driver();
console.log(d instanceof Person)
注意:extends可以用于类表达式中,extends关键字后面也可以接表达式(求值结果为一个函数或一个类)
let Bar = class extends Foo {} //extends用于类表达式中
//extends后面接表达式
let getZoom=()=>{
return class Zoom {
test(){
console.log('test')
}
}
}
class Foo extends getZoom(){}
let foo=new Foo()
foo.test() //test
super关键字的用法:
首先要记住,super关键字必须用在派生类中,且不能单独使用。
A)用在派生类构造函数中:
class Parent {
constructor(name) {
this.name = name;
}
}
class Child extends Parent {
constructor(name, age) {
super(name); // 调用父类的构造函数,并传递参数
this.age = age;
}
}
const child = new Child('Alice', 10);
console.log(child.name); // 输出: Alice
console.log(child.age); // 输出: 10
注意点:
如果子类没有显示定义构造函数,在实例化的时候,会通过super调用父类构造函数。
在子类的构造函数中,super必须用在this之前。
在子类构造函数中,要么调用super,要么必须返回一个对象。
B)用在派生类原型方法中
class Parent {
greet() {
return 'Hello from Parent';
}
}
class Child extends Parent {
greet() {
return super.greet() + ' and Hello from Child';
}
}
const child = new Child();
console.log(child.greet());
// 输出: Hello from Parent and Hello from Child
C)用在派生类的静态方法中
class Parent {
static staticMethod() {
return 'Static method from Parent';
}
}
class Child extends Parent {
static staticMethod() {
return super.staticMethod() + ' and Static method from Child';
}
}
console.log(Child.staticMethod());
// 输出: Static method from Parent and Static method from Child
D)在getter和setter中使用
class Parent {
get value() {
return 'Parent value';
}
}
class Child extends Parent {
get value() {
return super.value + ' and Child value';
}
}
const child = new Child();
console.log(child.value);
// 输出: Parent value and Child value
抽象基类:
有时候我们可能需要这样一个类,他只能被继承,但是自身不能被实例化。这时候我们就用到了抽象基类,我们可以要求子类必须要实现某个方法。如下:
class Parent {
constructor(){
if(new.target=='Parent'){
throw new Error('Parent不能被实例化')
}
if(!this.foo){
throw new Error('派生类必须实现foo方法')
}
console.log('success!')
}
}
class Child1 extends Parent {
foo(){}
}
class Child2 extends Parent {}
let child1=new Child1()
let child2=new Child2()

以上代码中,在父类的构造函数中使用new.target判断通过new操作符调用的函数或类是不是父类本身,以此保证父类不能被实例化。
子类实例化时,如果子类中没有显示定义构造函数,则默认用super调用父类的构造函数,父类构造函数中的this指向创建出来的子类实例。因此this.foo判断了子类的实例或原型上有没有foo方法,没有就会抛出异常。
继承内置类型:
class MyArray extends Array {
constructor(...args) {
super(...args); // 调用父类构造函数,传递参数
}
// 添加一个自定义方法
first() {
return this[0];
}
// 重写一个方法(可选)
// 注意:重写内置方法时要小心,以免破坏内置行为
// 这里只是演示,通常不建议重写如 push、pop 等关键方法
// push(...items) {
// console.log('Adding items:', items);
// return super.push(...items);
// }
}
// 使用自定义数组类
const myArr = new MyArray(1, 2, 3);
console.log(myArr.first()); // 输出: 1
console.log(myArr instanceof MyArray); // 输出: true
console.log(myArr instanceof Array); // 输出: true
类混入:
js提供的Object.assign()方法实现了对象混入的功能,类混入的话需要你自己编写相应的表达式。
js中,一个class无法同时继承自多个类,但是通过现有的东西,我们是可以实现的
class A {
a(){
console.log('a')
}
}
class B {
b(){
console.log('b')
}
}
class C {
c(){
console.log('c')
}
}
如上,有A,B,C三个类,我们要定义一个目标类,同时继承他们三个怎么做呢?我们可以让A先继承B,B在继承C,最后让目标类继承A。
let extC=()=>{
return class extends C {
b(){
console.log('b')
}
}
}
let extB=(supClass)=>{
return class extends supClass {
a(){
console.log('a')
}
}
}
let Target= extB(extC())
let target = new Target()
target.a() //a
target.b() //b
target.c() //c
以上这种方式很麻烦的,读着代码也挺绕的。首先你要定义一个类,在后面定义函数,继承这个类,然后加上自己的东西,返回一个新类。上面代码中,extC继承了C类,返回了B类。extB继承了B类,返回了A类。最终Target相当于A类。
现在很多的js框架实现继承,已抛弃混入模式,转向组合模式(把方法提取到独立的类和辅助对象中,然后把他们组合起来,但不使用继承),如下:
// 定义一个混入对象
const myMixin = {
greet() {
console.log(`Hello, my name is ${this.name}`);
},
setName(name) {
this.name = name;
}
};
// 定义一个混入对象
const myMixin2 = {
sayAge() {
console.log(`我今年${this.age}岁了`)
}
};
// 定义一个类
class Person {
constructor(age) {
this.name = '';
this.age=age
}
}
// 使用 Object.assign 将混入对象的方法添加到 Person.prototype
Object.assign(Person.prototype, myMixin, myMixin2);
// 使用类
const person = new Person(18);
person.setName('Alice');
person.greet(); // 输出: Hello, my name is Alice
person.sayAge();

3万+

被折叠的 条评论
为什么被折叠?



