【React基础】class实现继承

154 阅读8分钟

前言:

先体会下class类继承的过程:

// es5写法
function Student(name, age) {
  this.name = name;
  this.age = age;
  Student.prototype.showMsg = function () {
    console.log(`这个学生的姓名:${this.name};年龄:${this.age}`);
  };
}
let liu_xiao = new Student("柳潇", 23);

console.log(liu_xiao); // Student { name: '柳潇', age: 23 }
liu_xiao.showMsg() // 这个学生的姓名:柳潇;年龄:23

// es6写法
// 类的声明没有提升,所以先声明一个类,接着才能使用;而函数有函数声明提升
// 类的声明不能重名重复;函数的声明可以重名重复,同名你跟函数中后面的函数会覆盖前面的函数,不会发生重载
// 类的声明需要new调用;而函数不需要
class Student{
    constructor(name,age){
        this.name = name
        this.age = age
    }
    showMsg(){
        console.log(`这个学生的姓名:${this.name};年龄:${this.age}`);
    }
}


let liu_xiao = new Student('柳潇', 23)
liu_xiao.showMsg()
console.log(liu_xiao); // Student { name: '柳潇', age: 23 }

进行优化

// es6写法
class Person{
    constructor(name){
        this.name = name
    }
    common(){
        console.log('同属于人类');
    }
}

class Student extends Person{
    constructor(name,age){
        // this.name = name
        super(name)
        this.age = age
    }
    showMsg(){
        console.log(`这个学生的姓名:${this.name};年龄:${this.age}`);
    }
}

class Teather extends Person{
    constructor(name,location){
        // this.name = name
        super(name)
        this.location = location
    };
    sendMsg(){
        console.log(`这个老师的姓名:${this.name};地址:${this.location}`);
    }
}

let liu_xiao = new Student('柳潇', 23)
liu_xiao.showMsg() // 这个学生的姓名:柳潇;年龄:23
console.log(liu_xiao); // Student { name: '柳潇', age: 23 }

let xiao_li =  new Teather('李子','湖北省武汉市')
xiao_li.sendMsg() //这个老师的姓名:李子;地址:湖北省武汉市
console.log(xiao_li); // Teather { name: '李子', location: '湖北省武汉市' }

liu_xiao.common() // 同属于人类
xiao_li.common() // 同属于人类

console.log(liu_xiao instanceof Person);//true
console.log(xiao_li instanceof Person);//true

类的语法基本的定义和使用

  • class可以看作只是一个function的语法糖
class User {}
console.log(typeof User); // function  
console.dir(User) // class User
console.log(User == User.prototype.constructor); // true

function People() {}
console.log(typeof People); // function  
console.dir(People) // ƒ People()
console.log(People == People.prototype.constructor); // true
  • 一个基本的类
class User{
  // constructor 是一种用于创建和初始化class创建的对象的特殊方法。
  constructor(name){
    this.name = name
  }
  getName(){
    return this.name
  }
}

let hd = new User('拜振华')
console.log(hd.name); // 拜振华
console.log(hd.getName()); // 拜振华
  • 类和函数的相似之处
// 类
class User{
    constructor(name){
        this.name = name
    }
    show(){} // 这个方法会放在protoptype上
}
console.dir(User)

// 函数
function People(name){
    this.name = name
}
People.prototype.show = function(){} // // 这个方法会放在protoptype上
console.dir(People);

图解:

image.png

// 类自动开启严格模式
class User {
  constructor(name) {
    this.name = name;
  }
  show() {
    function test() {
      console.log(this,'this'); // undefined ,因为自动开启了严格模式所以这里是undefined而不是window
    }
    test();
  }
}
let p1 = new User();
p1.show();

顺带提及下,hasOwnPropertyin的区别使用: hasOwnProperty:包含本身数据。 in 包含本身 和 原型原型链上的数据(也叫继承父类的数据)。


// 类
class User{
    constructor(name){
        this.name = name
    }
}
User.prototype.lastName = "原型上面的名字";
let p1 = new User('对象本身的名字')
console.log(p1);
console.log('name' in p1); // true
console.log(p1.hasOwnProperty('name')); // true
console.log(p1.hasOwnProperty('lastName')); // false

// 函数
function People(name){
    this.name = name
}
People.prototype.lastName = "原型上面的名字";
let p2 = new User('对象本身的名字')
console.log(p2);   
console.log('name' in p2); // true
console.log(p2.hasOwnProperty('name')); // true
console.log(p2.hasOwnProperty('lastName')); // false
  • 类声明的方法不能被遍历
class User{
    constructor(name){
        this.name = name
    }
    show(){
        console.log(123);
    }
}
User.prototype.lastName = "原型上面的名字";
let p1 = new User('对象本身的名字')
let whyShow = Object.getOwnPropertyDescriptor(User.prototype,'show')
let whyName = Object.getOwnPropertyDescriptor(User.prototype,'name')
let whyLastName = Object.getOwnPropertyDescriptor(User.prototype,'lastName')
console.log(whyShow && whyShow.enumerable); // false
console.log(whyName && whyName.enumerable); // undefined
console.log(whyLastName && whyLastName.enumerable); // true
for (const key in p1) {
   console.log(key); // name lastName
}
  • 类的静态属性
// 函数中的静态属性
function Web(url){
    this.url = url
}
Web.url = "first.com" // 分配给构造函数的属性是静态属性
console.dir(Web);

// 类中的静态属性
class OtherWeb{
    static url = 'second.com'
}
let obj = new OtherWeb()
console.dir(OtherWeb);

图解:

image.png

  • 类的静态方法
// 函数中的静态方法
function User(url) {
  //    this.show = function(){} //不建议这么用
}
let a = new User();

// 可以当成函数、实例对象和构造函数,或者笼统的说是按"对象"来使用
// 1.函数的静态方法,当成对象来使用.show()
User.show = function () {
  console.log("static.show");
};
User.show() // static.show

console.dir(User);

// 2.实例对象的静态方法,当成对象来使用.see()
User.prototype.see = function () {
    console.log("static.see");
}
User.prototype.see() // static.see
 
// 3.构造函数的静态方法,当成对象来使用.say()
a.__proto__.say =function(){
    console.log("static.say");
}
a.__proto__.say() // static.say

// 由此可验证:原型链中构造函数和实例对象的关系
console.log(a.__proto__ == User.prototype); // true 实例对象a的隐式原型指向的是构造函数User的显示原型
console.log(User.prototype.constructor == User); // 构造函数的显示原型的constructor指向这个构造函数本身
a.say() // static.say 实例对象a没有就会向上找,即a.__proto__一层一层向上找

图解:

image.png

class User{
    //一般方法,存放在prototype上
    show(){
        console.log('prototype.show');
    }
    // 静态方法,存放在它本身
    static see(){
        console.log('static.show');
    }
}
let a = new User()
// 1.类的方式
a.show() // prototype.show
// 2.对象的方式
User.see() //static.show
console.log(User.prototype == a.__proto__); // true
console.log(User.prototype.constructor == User); // true
console.log(a);

图解:

image.png

顺带提及下,Object.create()new的区别使用: Object.create只继承原型属性和方法,继承不了构造函数的属性和方法。而通过new操作符创建的实例,既可以继承原型的属性和方法,又可以继承构造函数的属性和方法。。

class Person {
  constructor(name) {
    this.name = name;
  }
  say(){
    console.log('prototype say');
  }
}
let p1 = new Person("lwx");
let p2 = Object.create(Person.prototype, { name: { value: "liu_xiao" } });
// Object.create() 第一个参数是必选的,是新建对象的原型对象,第二个参数是可选的,是新建的实例对象的属性
console.log(p1);
console.log(p2);

图解:

image.png

  • 访问器属性:get和set
class Person {
  constructor(name) {
    this.name = name;
  }
  get say() {
    return "prototype say";
  }
  see() {
    return "prototype see";
  }
  // 访问器属性:set
  set eat(val) {
    console.log("Set " + val);
  }
  // 访问器属性:get
  get eat() {
    return "Get prototype eat";
  }
}
let p1 = new Person("lwx");
console.log(p1.say); // prototype say
console.log(p1.see()); // prototype see
console.log(p1.eat); // Get prototype eat
p1.eat = "prototype eat"; // Set prototype eat
console.log(p1);

图解:

image.png

  • 类的属性继承

MDN官网上说: 在一个子构造函数中,你可以通过调用父构造函数的 call 方法来实现继承,类似于 Java 中的写法。使用 call 方法调用父构造函数

// 基本用法:
let arr = [1, 3, 5];
let a = [].slice.call(arr, 1);
let b = arr.slice(1);
console.log(a); // [ 3, 5 ]
console.log(b); // [ 3, 5 ]

// 理解call的继承作用
function add(){
  let a = [].slice.call(arguments, 1); // 同 Array.prototype.slice.call(arguments, 1); 
  // 因为arguments是一个类数组,只具备类数组的方法且方法很少,所以怎么让它转成数组呢? 修改this指针让Arguments作为子类继承父类Array, 就可以使用数组中的各种方法了。
  let b = [...arguments].slice(1)
  console.log(a); // [ 2, 3 ]
  console.log(b); // [ 2, 3 ]
}
add(1,2,3)

理解了call的继承用法后,再看下面的代码:

// 函数中的继承
function User(name) {
  this.name = name;
}
function Admin(name) {
  console.log(this); // 构造函数的this指向指向这个新的对象
  User.call(this, name); // User的this指向这里的this,然后传入一个name的参数
  // MDN官网上说: 在一个子构造函数中,你可以通过调用父构造函数的 call 方法来实现继承,类似于 Java 中的写法
}
Admin.prototype = Object.create(User.prototype);
Admin.prototype.show = function () {
  console.log("protptype show");
};
let p = new Admin("liu_xiao");
p.show() // protptype show
console.log(p);

图解:

image.png

// 类中的继承
class User{
    constructor(name){
        this.name = name
    }
    show(){
        console.log('protptype show');
    }
}
class Admin extends User{
    // constructor(name){
    //     super(name)
    // }
    // 等同于如下写法
    constructor(...args){
        super(...args)
    }
}
let p = new Admin('lwx')
p.show() // protptype show
console.log(p);

图解:

image.png

Tips: 从这个过程可以再次验证:class可以理解为是语法糖,内部就是通过函数的原型链和原型来实现的

  • 类的方法继承
// 函数的方法继承
function User(){}
function Admin(){}
User.prototype.show = function(){}
Admin.prototype = Object.create(User.prototype) // User.prototype并不是函数,所以是在[[Prototype]]里面显示show方法
console.dir(Admin);

图解:

image.png

// 类的方法继承
class User{
    show(){}
}
class Admin extends User{
}
console.dir(Admin);

图解:

image.png

  • 类的super关键字
// super关键字用于访问和调用一个对象的父对象上的函数。
// 类的super(函数也可以实现super一样的功能,这里就先不做介绍了)
class Father {
  static age = 30;
  constructor(fatherName) {
    this.fatherName = fatherName;
  }
  show() {
    console.log("show Father");
  }
}
class Daughter extends Father {
  // static age = 5
  constructor(fatherName, mySex, myName) {
    super(fatherName); // super调用属性 ①
    this.mySex = mySex; // ②
    this.myName = myName; // ③
    // 设计思想:先super调用父类,再开始调用子类,设计子类的优先级要高,在代码的执行过程中作为子类的②和③会覆盖父类的①,如果颠倒顺序,会强制抛错也就不符合设计的用意了
  }
  show() {
    console.log("show Daughter");
  }
  otherShow() {
    super.show(); // super调用方法
  }
}
let s = new Daughter("王小波", "female", "小莉");
s.show(); // show Daughter ,当父类和子类有同样的方法,它用的是子类自身的show方法
s.otherShow(); // show Father
console.log(Daughter.age); // 30 ,当子类没有定义静态属性age,它用的是父类定义的静态属性age
console.log(s.fatherName); // 王小波 ,当子类没有定义name属性,它用的是父类定义的name属性
console.log(s.mySex); // female
console.log(s.myName); // 小莉
  • 类的静态继承原理
// 构造函数形式
function People(site,name){
    this.site = site
    this.name = name
}
let p = new People('浙江杭州','柳潇')
console.log(p); // liu_xiao
console.log(p.site); // 浙江杭州
console.log(p.name); // 柳潇

// 对象形式
function Person(){}
Person.site = '浙江杭州'
Person.name = '柳潇'
console.dir(Person);
console.dir(Person.site); // 浙江杭州
console.dir(Person.name); // Person ,函数的name属性是有值的所以naem字段被占用了!

图解:

image.png

网上说:任何对象都有属性[[Prototype]]),[[Prototype]](一般用[[]]表示内部属性,不能直接被访问)是内部属性指向此对象,Object.create()的数据就存放在[[Prototype]]上,但只有函数有属性prototype可以理解为显式原型,实例对象有属性__proto__可以理解为隐式原型。

image.png

  • 检测类的继承关系
// 检测继承关系
class Teacher {}
class Student extends Teacher {}
let person = new Student()
// 1. instanceof
console.log(person instanceof Teacher); // true
// 2. isPrototypeOf
console.log(Teacher.prototype.isPrototypeOf(person)); // true
  • 类的this指向问题
// 在类中容易混淆的this指向
class Student {
  say() {
    console.log(this); // ①、通过实例调用构造函数(类)的方法时,this指向的是这个实例本身
    function add(){
        console.log(this); // ②、js中的类自动开启严格模式(通过Babel也可以开启严格模式),所以这里函数的独立调用不是 window 而是 undefined
    }
    add()
  }
  
}
let p = new Student();
console.log("方法中的实例", p.say());

后续

关于js的class类就先介绍到这里,文章参考了阮一峰的文档后盾人关于class类的相关视频,看阮es6文档很有启发,希望也能帮到你