es6中的类(五)

2022-09-15  本文已影响0人  未路过

1. 认识类

64.PNG
// 类的声明
class Person {

}

// babel



// 类的表达式(用的比较少)
// var Animal = class {
// }


// 研究一下类的特性
console.log(Person.prototype)
console.log(Person.prototype.__proto__)
console.log(Person.prototype.constructor)
console.log(typeof Person) // function

var p = new Person()
console.log(p.__proto__ === Person.prototype) // true

//总结
/* 

class Person{

}
就是 function Person(){

}
构造函数的语法糖。

2. class的构造方法

// 类的声明
class Person {
  // 类的构造方法
  // 注意: 一个类只能有一个构造函数,这个构造函数在new的时候就会被调用,然后按照以下步骤进行运行
  // 1.在内存中创建一个对象 moni = {}
  // 2.将类的原型prototype赋值给创建出来的对象 moni.__proto__ = Person.prototype
  // 3.将对象赋值给函数的this: new绑定 this = moni
  // 4.执行函数体(构造函数constructor)中的代码
  // 5.如果构造函数没有返回非空对象,则返回创建出来的新对象;
  constructor(name, age) {
    this.name = name
    this.age = age
  }
}


var p1 = new Person("why", 18)
var p2 = new Person("kobe", 30)
console.log(p1, p2)

3. JavaScript 对象访问器(补充)

ECMAScript 5 (2009) 引入了 Getter 和 Setter。

JavaScript Getter(get 关键词)
//本例使用 lang 属性来获取 language 属性的值。
// 创建对象:
var person = {
  firstName: "Bill",
  lastName : "Gates",
  language : "en",
  get lang() {
    return this.language;
  },
  set lang(newLang) {
    console.log('setting lang');
    this.language = newLang;
  }
};

console.log(person.lang);//en
//意味着不对langeuage直接进行操作,而是通过lange这个方法进行对language的操作
person.lang = "CN";
console.log(person.lang);//CN

优点1:提供更简洁的语法

下面两个例子的区别在哪里?

例子 1

var person = {
  firstName: "Bill",
  lastName : "Gates",
  fullName : function() {
    return this.firstName + " " + this.lastName;
  }
};

// 使用方法来显示来自对象的数据:
document.getElementById("demo").innerHTML = person.fullName();

例子 2

var person = {
  firstName: "Bill",
  lastName : "Gates",
  get fullName() {
    return this.firstName + " " + this.lastName;
  }
};

// 使用 getter 来显示来自对象的数据:
document.getElementById("demo").innerHTML = person.fullName;

例子 1 以函数形式访问 fullName:person.fullName()。

例子 2 以属性形式访问 fullName:person.fullName。

第二个例子提供了更简洁的语法。

优点2: 提高数据质量

使用 getter 和 setter 时,JavaScript 可以确保更好的数据质量。

在本例中,使用 lang 属性以大写形式返回 language 属性的值:

// Create an object:
var person = {
  firstName: "Bill",
  lastName : "Gates",
  language : "en",
  get lang() {
    return this.language.toUpperCase();
  }
};

// 使用 getter 来显示来自对象的数据:
document.getElementById("demo").innerHTML = person.lang;

4.class中的方法定义

var names = ["abc", "cba", "nba"];
class Person{
  constructor(name, age){
    this.name = name;
    this.age = age;
    this._address = "广州市";
  }

  //这里的eating和running,还有get和set本质上就是放在Person的prototype上的
//console.log(Object.getOwnPropertyDescriptors(Person.prototype))

  eating(){
    console.log(this.name + "eating");
  }

  runnning(){
    console.log(this.name + "running");
  }

  //类的访问器方法
  get address(){
    console.log("访问器拦截操作");
    return this._address
  }

  set address(newAddress){
    console.log("拦截设置操作");
    this._address = newAddress;
  } 

    //以上eating和runnning是通过创建实例对象,让实例对象进行访问,比如var p = new Person() p.eating() 
//访问器方法也是通过实例对象去访问的 
    
  //类的静态方法(类方法) ,不需要去创建实例对象,通过类名直接去访问
  // Person.createPerson()
  //静态方法通常用于定义直接使用类来执行的方法,不需要有类的实例,使用static关键字来定义:
//静态方法不会被实例继承

  static randomPerson(){
    var nameIndex = Math.floor(Math.random()* names.length);
    var name = names[nameIndex];
    var age = Math.floor(Math.random()*100);
    return new Person(name, age)
  }
}

var p = new Person("why", 18);
p.eating();
p.runnning();

console.log(p.address)
p.address = "北京市"
console.log(p.address)//北京市

var p1 = Person.randomPerson();//我们想创建出来一个对象,但是名字和年龄都是随机出来的
console.log(p1);
for (var i = 0; i < 50; i++) {
  console.log(Person.randomPerson())//创建出来50个person对象
}

5 class中实现继承extends

1.1.父类属性的继承(super)

class Person {
  constructor(name, age) {
    this.name = name
    this.age = age
  }
}

// Student称之为子类(派生类)
/* class Student extends Person {
  constructor(name, age, sno) {
    this.name = name;
    this.age = age;
    this.sno = sno
  }

} */

//会报错
//test.html:60 Uncaught ReferenceError: Must call super constructor in derived class before accessing
// 'this' or returning from derived constructor
//子类必须调用父类的构造方法
 // JS引擎在解析子类的时候就有要求, 如果我们有实现继承
//那么子类在子类的构造方法中, 在使用this之前,需要调用父类的构造方法
  //使用super关键字


  class Student extends Person {
  constructor(name, age, sno) {
    super(name, age)
    this.sno = sno
  }

}

var stu = new Student("why", 18, 111);
console.log(stu);
//Student {name: 'why', age: 18, sno: 111}

如果子类没有自己的构造函数

class Person {
        constructor(name, age) {
          this.name = name;
          this.age = age;
        }
      }

      class Student extends Person {}

      var stu = new Student("why", 18);
      console.log(stu);
      //Student {name: 'why', age: 18}
      //如果子类没有定义constructor()方法,这个方法会默认添加,
//并且里面会调用super()。也就是说,不管有没有显式定义,任何一个子类都有constructor()方法。
      class Student extends Person {}

      // 等同于
      class Student extends Person {
        constructor(...args) {
          super(...args);
        }
      }
67.PNG

2. 父类原型方法的继承

class Person {
  constructor(name, age) {
    this.name = name
    this.age = age
  }
  running() {
    console.log(this.name + " running~")
  }

  eating() {
    console.log(this.name + " eating~")
  }

}

  class Student extends Person {
  constructor(name, age, sno) {
    super(name, age)
    this.sno = sno
  }
  studying() {
    console.log(this.name + " studying~")
  }

}

var stu = new Student("why", 18, 111);
console.log(stu);
//Student {name: 'why', age: 18, sno: 111}
stu.studying();
stu.running();
stu.eating();

//在这里,studing是在Student的原型对象上的,running和eating是在Person的原型对象上的。
console.log(Object.getOwnPropertyDescriptors(stu.__proto__))//{constructor: {…}, studying: {…}}
console.log(Object.getOwnPropertyDescriptors(stu.__proto__.__proto__))//{constructor: {…}, running: {…}, eating: {…}}
//Student的原型对象的__proto—__指向Person的原型对象
console.log(Student.prototype.__proto__ === Person.prototype);//ture
console.log(stu.__proto__.__proto__ === Person.prototype);//ture

3. 子类对父类原型方法的重写

情况1:重写

class Person {
  constructor(name, age) {
    this.name = name
    this.age = age
  }
  running() {
    console.log(this.name + " running~")
  }

  eating() {
    console.log(this.name + " eating~")
  }

}

  class Student extends Person {
  constructor(name, age, sno) {
    super(name, age)
    this.sno = sno
  }
  studying() {
    console.log(this.name + " studying~")
  }

   // 类对父类的方法的重写
   running() {
    console.log("student " + this.name + " running")
  }

}

var stu = new Student("why", 18, 111);
console.log(stu);
//如果子类不满意父类的running方法,可以在自己里面重新实现running方法
//到时候就会优先找自己的,而不是父类里面的

stu.running();//student why running

情况2:基于父类的逻辑重写

比如父类有下面一个方法

  personMethod() {
    console.log("处理逻辑1")
    console.log("处理逻辑2")
    console.log("处理逻辑3")
  }

但是子类基于这个方法还有更多的逻辑

personMethod() {
    console.log("处理逻辑1")
    console.log("处理逻辑2")
    console.log("处理逻辑3")
    console.log("处理逻辑4")
    console.log("处理逻辑5")
    console.log("处理逻辑6")
  }

这时候完全像上面一样重写的话,就会出现重复代码,没有必要

这时候就需要使用super的第二个用法:

在子类的实例方法里面调用父类的方法

 // 重写personMethod方法
  personMethod() {
    // 复用父类中的处理逻辑
    super.personMethod()

    console.log("处理逻辑4")
    console.log("处理逻辑5")
    console.log("处理逻辑6")
  }

整体代码:

class Person {
  constructor(name, age) {
    this.name = name
    this.age = age
  }
  running() {
    console.log(this.name + " running~")
  }

  eating() {
    console.log(this.name + " eating~")
  }
  personMethod() {
    console.log("处理逻辑1")
    console.log("处理逻辑2")
    console.log("处理逻辑3")
  }

}

  class Student extends Person {
  constructor(name, age, sno) {
    super(name, age)
    this.sno = sno
  }
  studying() {
    console.log(this.name + " studying~")
  }

   // 重写personMethod方法
   personMethod() {
    // 复用父类中的处理逻辑
    super.personMethod()

    console.log("处理逻辑4")
    console.log("处理逻辑5")
    console.log("处理逻辑6")
  }

}

var stu = new Student("why", 18, 111);
console.log(stu);

stu.personMethod();
/* 
处理逻辑1
test.html:64 处理逻辑2
test.html:65 处理逻辑3
test.html:84 处理逻辑4
test.html:85 处理逻辑5
test.html:86 处理逻辑6
*/

4.静态方法的继承

class Person {
  constructor(name, age) {
    this.name = name
    this.age = age
  }
  running() {
    console.log(this.name + " running~")
  }

  eating() {
    console.log(this.name + " eating~")
  }

  static staticMethod() {
    console.log("PersonStaticMethod");
  }

}

  class Student extends Person {
  constructor(name, age, sno) {
    super(name, age)
    this.sno = sno
  }

 

}

var stu = new Student("why", 18, 111);

//调用Person的静态方法
Student.staticMethod();//PersonStaticMethod

5.子类对父类静态方法的重写

情况1:完全重写

class Person {
  constructor(name, age) {
    this.name = name
    this.age = age
  }
  running() {
    console.log(this.name + " running~")
  }

  eating() {
    console.log(this.name + " eating~")
  }

  static staticMethod() {
    console.log("PersonStaticMethod")
  }

}

  class Student extends Person {
  constructor(name, age, sno) {
    super(name, age)
    this.sno = sno
  }

    // 重写静态方法
    static staticMethod() {
    console.log("StudentStaticMethod")
  }
}

var stu = new Student("why", 18, 111);

var p = new Person();
console.log(Person.hasOwnProperty("name")); //true

//调用Person的静态方法
Student.staticMethod();//StudentStaticMethod

情况2:基于父类的重写

class Person {
  constructor(name, age) {
    this.name = name
    this.age = age
  }
  running() {
    console.log(this.name + " running~")
  }

  eating() {
    console.log(this.name + " eating~")
  }

  static staticMethod() {
    
    console.log("PersonStaticMethod")
  }

}

  class Student extends Person {
  constructor(name, age, sno) {
    super(name, age)
    this.sno = sno
  }

    // 重写静态方法
    static staticMethod() {
    super.staticMethod()
    console.log("StudentStaticMethod")
  }

 

}

var stu = new Student("why", 18, 111);

//调用Person的静态方法
Student.staticMethod();
//PersonStaticMethod
//StudentStaticMethod

6. ES6转ES5的代码

Babel · The compiler for next generation JavaScript (babeljs.io)
Babel · The compiler for next generation JavaScript (babeljs.io)

IE:Edge 15可以支持 96% 的 ES6 新特性。Edge 14 可以支持 93% 的 ES6 新特性。(IE7~11 基本不支持 ES6)

es5

class Person{}

es5

function _classCallCheck(instance, Constructor) {
  if (!(instance instanceof Constructor)) {
    throw new TypeError("Cannot call a class as a function");
  }
}

var Person = function Person() {
  "use strict";
//检查this是不是Person创建出来的,
//是的话,那么就可以用instanceof来判断,instanceof就是判断某个对象是不是另一个对象的实例
  _classCallCheck(this, Person);
};

//_classCallCheck的作用就是不让你把Person当成一个普通的函数去调用它
//比如Person() 这种情况下this是window或者undifined
//而是希望new Person()

es6

class Person{
  constructor(name, age){
    this.name = name;
    this.age = age;
  }
  eating(){
    console.log(this.name + "eating")
  }
}

es5

function _classCallCheck(instance, Constructor) {
  if (!(instance instanceof Constructor)) {
    throw new TypeError("Cannot call a class as a function");
  }
}

function _defineProperties(target, props) {
  for (var i = 0; i < props.length; i++) {
    //获取当前对象
    var descriptor = props[i];
    //console.log(descriptor);//{key: 'eating', value: ƒ}
    //console.log(descriptor.enumerable);//undefined
    
    descriptor.enumerable = descriptor.enumerable || false;
    descriptor.configurable = true;
    if ("value" in descriptor) descriptor.writable = true;
    Object.defineProperty(target, descriptor.key, descriptor);
   //console.log(descriptor);
      /* configurable: true
      enumerable: false
      key: "eating"
      value: ƒ eating()
      writable: true */
  }
}

/*

  相当于给Person.prototype里面加入eating方法
*/

function _createClass(Constructor, protoProps, staticProps) {
     //如果是构造函数的话,直接给Person.prototype添加属性。
  if (protoProps) _defineProperties(Constructor.prototype, protoProps);
    //如果是构造函数的话,直接给Person添加方法。
  if (staticProps) _defineProperties(Constructor, staticProps);
  return Constructor;
}

//在这里,为什么要使用立即执行函数呢?两个原因
//1.它可以独立创建一个作用域,不会污染到全局,每个立即执行函数内的变量都是局部变量,不会有命名冲突的情况。
//2.方便进行进行tree-shaking处理
//标记这个函数为纯函数/*#__PURE__*/--》没有副作用
//纯函数被webpack做压缩的时候,是要做tree-shaking处理
//到时候对代码进行压缩的时候,发现这个函数没有被用过,在tree-shaking的时候,进行优化,直接删除掉这些代码,打包出来的包size小
var Person = /*#__PURE__*/ (function () {
  "use strict";

  function Person(name, age) {
    _classCallCheck(this, Person);

    this.name = name;
    this.age = age;
  }

  _createClass(Person, [
    {
      key: "eating",
      value: function eating() {
        console.log(this.name + "eating");
      }
    }
  ]);

  return Person;
})();

8.7 es6转es5的继承的代码

es6

class Person{
  constructor(name, age){
    this.name = name;
    this.age = age;
  }

  running(){
    console.log(this.name + " runnning");
  }
}

class Student extends Person{
  constructor(name, age, sno){
    super(name, age);
    this.sno = sno;
  }
  studying(){
    console.log(this.name + " studying");
  }


}

es5


function _typeof(obj) {
  "@babel/helpers - typeof";
  if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
    _typeof = function _typeof(obj) {
      return typeof obj;
    };
  } else {
    _typeof = function _typeof(obj) {
      return obj &&
        typeof Symbol === "function" &&
        obj.constructor === Symbol &&
        obj !== Symbol.prototype
        ? "symbol"
        : typeof obj;
    };
  }
  return _typeof(obj);
}

function _inherits(subClass, superClass) {
  if (typeof superClass !== "function" && superClass !== null) {
    throw new TypeError("Super expression must either be null or a function");
  }
  subClass.prototype = Object.create(superClass && superClass.prototype, {
    constructor: { value: subClass, writable: true, configurable: true }
  });

  /* 这里的目的是让Student.__proto__ = Person 
为了实现静态方法的继承
比如Student.staticMethod()
这里staticMathod没有在Student对象里面,
我们就要去Student.__proto__里面去找
Student.__proto__默认指的是Function.prototype,但是Function.prototype里面也没这个方法
我们就通过Student.__proto__ = Person , 这里Person里面有这个方法
*/
  if (superClass) _setPrototypeOf(subClass, superClass);
}


function _setPrototypeOf(o, p) {
  _setPrototypeOf =
    Object.setPrototypeOf ||
    function _setPrototypeOf(o, p) {
      o.__proto__ = p;
      return o;
    };
  return _setPrototypeOf(o, p);
}

function _createSuper(Derived) {
  var hasNativeReflectConstruct = _isNativeReflectConstruct();
  return function _createSuperInternal() {
    var Super = _getPrototypeOf(Derived),
      result;
    if (hasNativeReflectConstruct) {
      var NewTarget = _getPrototypeOf(this).constructor;
      result = Reflect.construct(Super, arguments, NewTarget);
    } else {
      result = Super.apply(this, arguments);
    }
    return _possibleConstructorReturn(this, result);
  };
}

function _possibleConstructorReturn(self, call) {
  if (call && (_typeof(call) === "object" || typeof call === "function")) {
    return call;
  } else if (call !== void 0) {
    throw new TypeError(
      "Derived constructors may only return object or undefined"
    );
  }
  return _assertThisInitialized(self);
}

function _assertThisInitialized(self) {
  if (self === void 0) {
    throw new ReferenceError(
      "this hasn't been initialised - super() hasn't been called"
    );
  }
  return self;
}

function _isNativeReflectConstruct() {
  if (typeof Reflect === "undefined" || !Reflect.construct) return false;
  if (Reflect.construct.sham) return false;
  if (typeof Proxy === "function") return true;
  try {
    Boolean.prototype.valueOf.call(
      Reflect.construct(Boolean, [], function () {})
    );
    return true;
  } catch (e) {
    return false;
  }
}

function _getPrototypeOf(o) {
  _getPrototypeOf = Object.setPrototypeOf
    ? Object.getPrototypeOf
    : function _getPrototypeOf(o) {
        return o.__proto__ || Object.getPrototypeOf(o);
      };
  return _getPrototypeOf(o);
}

function _classCallCheck(instance, Constructor) {
  if (!(instance instanceof Constructor)) {
    throw new TypeError("Cannot call a class as a function");
  }
}

function _defineProperties(target, props) {
  for (var i = 0; i < props.length; i++) {
    var descriptor = props[i];
    descriptor.enumerable = descriptor.enumerable || false;
    descriptor.configurable = true;
    if ("value" in descriptor) descriptor.writable = true;
    Object.defineProperty(target, descriptor.key, descriptor);
  }
}

function _createClass(Constructor, protoProps, staticProps) {
  if (protoProps) _defineProperties(Constructor.prototype, protoProps);
  if (staticProps) _defineProperties(Constructor, staticProps);
  return Constructor;
}

var Person = /*#__PURE__*/ (function () {
  "use strict";

  function Person(name, age) {
    _classCallCheck(this, Person);

    this.name = name;
    this.age = age;
  }

  _createClass(Person, [
    {
      key: "running",
      value: function running() {
        console.log(this.name + " runnning");
      }
    }
  ]);

  return Person;
})();

var Student = /*#__PURE__*/ (function (_Person) {
  "use strict";

  _inherits(Student, _Person);
  /* 
  
  和以前寄生时候实现的方法是一样的,让Student继承Person

function inheritPrototype(SubType, SuperType) {
    SubType.prototype = Object.create(SubType.prototype);
    Object.defineProperty(SuperType.prototype, "constructor", {
      enumerable: false,
      configurable: true,
      writable: true,
      value: SubType
    })
  }

  */

  var _super = _createSuper(Student);

  function Student(name, age, sno) {
    var _this;

    //以前直接Person.call(this, name, age, friend)来实现的
    //但是Person不能直接调用Person()在es6里面
    _classCallCheck(this, Student);

    _this = _super.call(this, name, age);
    _this.sno = sno;
    return _this;
  }

  _createClass(Student, [
    {
      key: "studying",
      value: function studying() {
        console.log(this.name + " studying");
      }
    }
  ]);

  return Student;
})(Person);


// Super: Person
// arguments: ["why", 18]
// NewTarget: Student
// 会通过Super创建出来一个实例, 但是这个实例的原型constructor指向的是NewTarget
// 会通过Person创建出来一个实例, 但是这个实例的原型constructor指向的Person
//Reflect.construct(Super, arguments, NewTarget);

8.8 继承内置类

我们也可以让我们的类继承自内置类,比如Array

// class Person {

// }

// class Student extends Person {

// }

class HYArray extends Array {
  firstItem() {
    return this[0]
  }

  lastItem() {
    return this[this.length-1]
  }
}

var arr = new HYArray(1, 2, 3)
console.log(arr.firstItem())//1
console.log(arr.lastItem())//3

8.9 类的混入mixin

JavaScript的类只支持单继承:也就是只能有一个父类

  1. 那么在开发中我们我们需要在一个类中添加更多相似的功能时,应该如何来做呢?
  2. 这个时候我们可以使用混入(mixin);
class Person {

}

/* class Runner{
running(){

}
}

*/
function mixinRunner(BaseClass) {
  class NewClass extends BaseClass {
    running() {
      console.log("running~")
    }
  }
  return NewClass
}


// 在JS中类只能有一个父类: 单继承
class Student extends Person {

}

var NewStudent = mixinRunner(Student)
var ns = new NewStudent()
ns.running()//running~
68.PNG

混入两个类

class Person {

}

/* class Runner{
running(){

}
}

class Eater{
eatting(){

  }

} */
function mixinRunner(BaseClass) {
  class NewClass extends BaseClass {
    running() {
      console.log("running~")
    }
  }
  return NewClass
}

function mixinEater(BaseClass) {
  return class extends BaseClass {
    eating() {
      console.log("eating~")
    }
  }
}

// 在JS中类只能有一个父类: 单继承
class Student extends Person {

}

var NewStudent = mixinEater(mixinRunner(Student))
var ns = new NewStudent()
ns.running()
ns.eating()

弊端:以上方法没办法混入属性,或者在构造函数内传入一些参数

10 JavaScript中的多态

69.PNG
// 传统的面向对象多态是有三个前提:
// 1> 必须有继承(是多态的前提)
// 2> 必须有重写(子类重写父类的方法),如果不重写,调用的都是父类的getArea()(同一种行为),认为是没有多态的体现的。重写的话,传入r的情况,调入的是Rectangle类里面的getArea()方法,
//传入c的情况下,调用的是Circle类里面的getArea()方法,
//这样,就可以认为不同的数据类型执行同一个操作时, 如果表现出来的行为(形态)不一样,
// 3> 必须有父类引用指向子类对象

// Shape形状
class Shape {
  getArea() {}
}

class Rectangle extends Shape {
  getArea() {
    return 100
  }
}

class Circle extends Shape {
  getArea() {
    return 200
  }
}

var r = new Rectangle()
var c = new Circle()

// 多态: 当对不同的数据类型执行同一个操作时, 如果表现出来的行为(形态)不一样, 那么就是多态的体现.
function calcArea(shape: Shape) {
  console.log(shape.getArea())
}

calcArea(r)
calcArea(c)

export {}
// 多态: 当对不同的数据类型执行同一个操作时, 如果表现出来的行为(形态)不一样, 那么就是多态的体现.
function calcArea(foo) {
  console.log(foo.getArea())
}

var obj1 = {
  name: "why",
  getArea: function() {
    return 1000
  }
}

class Person {
  getArea() {
    return 100
  }
}

var p = new Person()

calcArea(obj1)
calcArea(p)


// 也是多态的体现
function sum(m, n) {
  return m + n
}

sum(20, 30)
sum("abc", "cba")

上一篇下一篇

猜你喜欢

热点阅读