【JavaScript】OOP/Prototype/类/Setters & Getters/继承/封装

8 篇文章 0 订阅

OOP四要素

抽象,封装,继承,多态

原型继承Prototypal Inheritance(委托Delegation)

原型下的所有对象都可以使用原型定义的方法//对象将方法委托到原型链上
如定义一个数组num时,他的所有方法被定义在了-Array.prototype,定义时num数组被链接到了原型上,就可以访问其所有方法

区别于其它语言的类继承,类继承提供了一个抽象的方法,而实现在实例中,方法由类中被复制到了实现处

构造函数

箭头函数没有this关键字,因此无法作为抽象函数
使用new关键词调用构造函数:

const Person = function (firstName, birthYear) {
  // Instance properties
  this.firstName = firstName;
  this.birthYear = birthYear;

  // Never to this!//会建立多个同样函数的副本
  // this.calcAge = function () {
  //   console.log(2037 - this.birthYear);
  // };
};

const jonas = new Person('Jonas', 1991);//调用
console.log(jonas);//


console.log(jonas instanceof Person);//返回Boolean,检查前者是否是后者的实例

使用new关键字后发生的事情

  1. New {} is created//新的对象被创建
  2. function is called, this = {}//this被指向新对象
  3. The new object is linked (__proto__property) to the construtor function’s prototype property//
  4. {} linked to prototype//新对象链接到原型
  5. function automatically return {}//方法自动返回对象

原型 Prototype

原型对象默认有一些内置属性,例如:

  • constructor: 表示该对象的构造函数。
  • proto: 表示该对象的原型对象。

此外,可以通过给原型对象添加方法和属性来扩展该对象的行为。


在原型定义函数后,所有实例化的对象都共享这个方法,避免了建立多个副本的缺点

console.log(Person.prototype);

//定义一个共有方法
Person.prototype.calcAge = function () {
  console.log(2037 - this.birthYear);
};

jonas.calcAge();
matilda.calcAge();

console.log(jonas.__proto__);//查看原型
console.log(jonas.__proto__ === Person.prototype);//Person定义的prototype是其下所有对象的原型

console.log(Person.prototype.isPrototypeOf(jonas));//返回boolean
console.log(Person.prototype.isPrototypeOf(Person));//返回false,区分person的原型

//定义一个共同特性
Person.prototype.species = 'Homo Sapiens';
console.log(jonas.species, matilda.species);

console.log(jonas.hasOwnProperty('firstName'));

Person的原型是Person.prototype == Person.prototype的构造函数是Person()

在这里插入图片描述

原型链

  • 原型Prototype是一个对象,他的原型是Object.prototype
  • Object.prototype的原型为null
  • 如果一个链中有同名函数,那么由下向上第一个出现的将被执行

在这里插入图片描述


proto 和 prototype 是 JavaScript 中两个概念,它们都与对象的继承有关。但是它们本质上是不同的。

proto 是每个对象都有的隐式原型属性,表示该对象继承自哪个对象。它是一个指针,指向该对象的原型对象。

而 prototype 则是构造函数的一个属性,表示该构造函数创建的所有实例的原型。它是一个对象,可以拥有属性和方法,而所有通过该构造函数创建的实例,将继承 prototype 对象上的属性和方法。

总的来说,proto 是每个实例对象的隐式原型,而 prototype 则是构造函数的显式原型。


关于这个比较的解释: console.log(jonas.proto === Person.prototype)//true

__proto__ 是每个对象内置的属性,它指向该对象的父对象,即该对象的原型对象。

prototype 是函数对象(类)的属性,它指向该函数(类)的原型对象,即创建的所有实例的父对象。

因此,当一个实例对象的 proto 属性和该实例的构造函数的 prototype 属性相等时,就会返回 true。


console.log(jonas.__proto__);
// Object.prototype (top of prototype chain)
console.log(jonas.__proto__.__proto__);
console.log(jonas.__proto__.__proto__.__proto__);

//使用dir查看构造函数的所有属性和属性值.
console.dir(Person.prototype.constructor);

使用数组举例:

const arr = [3, 6, 6, 5, 6, 9, 9]; // new Array === []
console.log(arr.__proto__);
console.log(arr.__proto__ === Array.prototype);//true

console.log(arr.__proto__.__proto__); //回到object上

JS中的classes-ES6

实质上还是原型链,只是为更易理解加上的语法糖(Syntactic sugar)

其中定义的函数被加到了原型上,而不是成为传统的类
这种结构使原型定义更紧凑,有他自己的优势

// Class expression 声明方法一:
// const PersonCl = class {}

// Class declaration 方法二:更常用
class PersonCl {
  constructor(fullName, birthYear) {
    this.fullName = fullName;
    this.birthYear = birthYear;
  }//句间无逗号!

  // Instance methods
  // Methods will be added to .prototype property
  calcAge() {
    console.log(2037 - this.birthYear);
  }

  greet() {
    console.log(`Hey ${this.fullName}`);
  }

  get age() {
    return 2037 - this.birthYear;
  }

}

验证:


const jessica = new PersonCl('Jessica Davis', 1996);
console.log(jessica);
jessica.calcAge();
console.log(jessica.age);

console.log(jessica.__proto__ === PersonCl.prototype);//true

  1. Classes are NOT hoisted. 并未被提升,不是任意处定义,全局可用
  2. Classes are first-class citizens. 可被传入函数并被函数返回
  3. Classes are executed in strict mode. 无论js脚本激活严格模式与否,其中代码都会由严格模式执行

Setters & Getters与静态方法

  1. 在普通对象中:
    可以用赋值的方式调用函数
// Setters and Getters
const account = {
  owner: 'Jonas',
  movements: [200, 530, 120, 300],

  get latest() {
    return this.movements.slice(-1).pop();
  },

  set latest(mov) {
    this.movements.push(mov);
  },
};

console.log(account.latest);

account.latest = 50;
console.log(account.movements);
  1. 类中也可以实现
    应用:数据验证—需要传送一个包含空格的完整名字:
class PersonCl {
  constructor(fullName, birthYear) {
    this.fullName = fullName;
    this.birthYear = birthYear;
  }//句间无逗号!

  // Instance methods
  // Methods will be added to .prototype property
  calcAge() {
    console.log(2037 - this.birthYear);
  }
 
  // Set a property that already exists
  //为同名属性创建set时会与构造函数造成冲突,因此可以将其加一个下划线_fullName
  set fullName(name) {
    if (name.includes(' ')) this._fullName = name;
    else alert(`${name} is not a full name!`);
  }

  get fullName() {
    return this._fullName;
  }

  // Static method
  static hey() {
    console.log('Hey there 👋');
    console.log(this);
  }
}

此时传入

const jessica = new PersonCl('Jessica Davis', 1996);

this.fullName = fullName; 中,等号右边的 fullName 会调用对应的 setter 方法,

类中的_fullName被设置为Jessica Davis,并且fullName也被赋值

由于有getter返回fullname,因此可以继续使用jessica.fullName

静态方法

static hey() { }

  • jessica.hey();无法被调用,因为hey是静态方法
  • 应使用PersonCl.hey();

Object.create()

很少用
底层实现:

Object.create =  function (o) {
    var F = function () {};
    F.prototype = o;
    return new F();
  };

使用这个方法手动将一个原型链委托给一个对象:

const PersonProto = {
  calcAge() {
    console.log(2037 - this.birthYear);
  },
  //任意名字都可
  init(firstName, birthYear) {
    this.firstName = firstName;
    this.birthYear = birthYear;
  },
};

const steven = Object.create(PersonProto);//steven连接了PersonProto
console.log(steven);
steven.name = 'Steven';
steven.birthYear = 2002;
steven.calcAge();

console.log(steven.__proto__ === PersonProto);

const sarah = Object.create(PersonProto);
//实现类似构造函数的作用,但并没有使用new关键字等,并不是构造函数
sarah.init('Sarah', 1979);
sarah.calcAge();

继承

创建一个类

const Person = function (firstName, birthYear) {
  this.firstName = firstName;
  this.birthYear = birthYear;
};
//声明原型
Person.prototype.calcAge = function () {
  console.log(2037 - this.birthYear);
};

令学生类继承person类,此时调用person构造函数需要使用call方法绑定this关键字。
现在想使学生的原型链也继承person的,需要使用Object.create,这样就让person的原型委托给了学生
示意:
在这里插入图片描述

const Student = function (firstName, birthYear, course) {
  Person.call(this, firstName, birthYear);
  this.course = course;
};

// Linking prototypes
Student.prototype = Object.create(Person.prototype);

为什么Student.prototype = Person.prototype;行不通:
这样会造成二者共用同一个原型链
即student.prototype = person.prototype
而实际上我们想要:student.prototype.prototype = person.prototype

Student.prototype.introduce = function () {
  console.log(`My name is ${this.firstName} and I study ${this.course}`);
};

创建一个对象检查其原型链

const mike = new Student('Mike', 2020, 'Computer Science');
mike.introduce();
mike.calcAge();

console.log(mike.__proto__);
console.log(mike.__proto__.__proto__);

console.log(mike instanceof Student);//true
console.log(mike instanceof Person);//true
console.log(mike instanceof Object);//true

console.dir(Student.prototype.constructor) 输出了 Student 对象的 prototype 对象的 constructor 属性的指向,即它的构造函数。

在上面的代码中,我们使用了 Object.create(Person.prototype) 来将 Student 对象的原型指向 Person 对象的原型,这样 Student 对象就可以继承 Person 对象的方法。因此,如果我们查询 Student 对象的 prototype 对象的 constructor 属性,它会返回 Person 构造函数。

这是因为我们没有重写 Student 对象的 prototype 对象的 constructor 属性,所以它仍然保持默认值,指向了 Person 构造函数。

如果想要让 Student 对象的 constructor 属性指向 Student 构造函数,可以在创建完 Student 对象的 prototype 对象之后重写该属性:

Student.prototype.constructor = Student;
console.dir(Student.prototype.constructor);

ES6中的继承Extends& Super

class StudentCl extends PersonCl//自动创建继承关系

super(fullName, birthYear);//直接调用父类构造函数

如果没有新参数,也可以直接不使用构造函数,super会被自动调用


class StudentCl extends PersonCl {
  constructor(fullName, birthYear, course) {
    // Always needs to happen first!
    super(fullName, birthYear);
    this.course = course;
  }
  //重写两方法,因为他们在原型链上更早出现
  introduce() {
    console.log(`My name is ${this.fullName} and I study ${this.course}`);
  }

  calcAge() {
    console.log(
      `I'm ${
        2037 - this.birthYear
      } years old, but as a student I feel more like ${
        2037 - this.birthYear + 10
      }`
    );
  }
}

const martha = new StudentCl('Martha Jones', 2012, 'Computer Science');
martha.introduce();
martha.calcAge();

封装

假封装

直接在属性前加下划线,后设置get和set方法获取真正值,如前面的fullName和_fullName,但这样并不会真正保护数据,_fullName仍可被访问到。

私有类-类字段Class field

  1. Public fields — locale = navigator.language;—后面要加分号!
  2. Private fields — #movements = [];
  3. Public methods
  4. Private methods
    (there is also the static version)

class Account {
  // 1) Public fields (instances)
  //公共属性
  locale = navigator.language;

  // 2) Private fields (instances)
  //私有属性,私有域
  #movements = [];
  #pin;

  constructor(owner, currency, pin) {
    this.owner = owner;
    this.currency = currency;
    this.#pin = pin;

    // Protected property
    // this._movements = [];
    // this.locale = navigator.language;

    console.log(`Thanks for opening an account, ${owner}`);
  }

  // 3) Public methods

  // Public interface
  getMovements() {
    return this.#movements;
  }

  deposit(val) {
    this.#movements.push(val);
    return this;
  }

  withdraw(val) {
    this.deposit(-val);
    return this;
  }

  requestLoan(val) {
    // if (this.#approveLoan(val)) {
    if (this._approveLoan(val)) {
      this.deposit(val);
      console.log(`Loan approved`);
      return this;
    }
  }

  static helper() {
    console.log('Helper');
  }

  // 4) Private methods
  //部分浏览器未支持#
  // #approveLoan(val) {
  _approveLoan(val) {
    return true;
  }
}

总结

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值