封装

封装的本质是将具有关联的代码组合在一起,其优势是能够保证代码复用且易于维护,函数是最典型也是最基础的代码封装形式,面向对象思想中的封装仍以函数为基础,但提供了更高级的封装形式。

字面量对象

先来回顾一下以往代码封装的形式:

  1. <script>
  2. // 普通对象(字面量对象)形式的封装
  3. let beats = {
  4. name: '狼',
  5. setName: function (name) {
  6. this.name = this.name;
  7. },
  8. getName() {
  9. console.log(this.name);
  10. }
  11. }
  12. beats.setName('熊');
  13. beats.getName();
  14. </script>

以往以普通对象(字面量)形式封装的代码只是单纯把一系列的变量或函数组合到一起,所有的数据变量都被用来共享(使用 this 访问)。

构造函数

对比以下通过面向对象的构造函数实现的封装:

  1. <script>
  2. function Person() {
  3. this.name = '佚名';
  4. // 设置名字
  5. this.setName = function (name) {
  6. this.name = name;
  7. }
  8. // 读取名字
  9. this.getName = () => {
  10. console.log(this.name);
  11. }
  12. }
  13. // 实例对像,获得了构造函数中封装的所有逻辑
  14. let p1 = new Person();
  15. p1.setName('小明');
  16. console.log(p1.--name);// 小明
  17. // 实例对象
  18. let p2 = new Person();
  19. console.log(p2.name); // 佚名
  20. </script>

构造函数相当于一个”模子”,能够像字面量那样创建出对象来,所不同的是借助构造函数创建出来的实例对象之间是彼此不影响的。
总结:

  1. 构造函数体现了面向对象的封装特性
  2. 构造函数实例创建的对象彼此独立、互不影响
  3. 命名空间式的封装无法保证数据的独立性

注:可以举一些例子,如女娲造人等例子,加深对构造函数的理解。

原型对象

实际上每一个构造函数都有一个名为 prototype 的属性,译成中文是原型的意思,prototype 的是对象类据类型,称为构造函数的原型对象,每个原型对象都具有 constructor 属性代表了该原型对象对应的构造函数。

  1. <script>
  2. // 定义构造函数
  3. function Person(n) {
  4. this.name = n
  5. }
  6. // 给Person的原型对象添加方法
  7. Person.prototype.say = function () {
  8. console.log('会说话')
  9. }
  10. // 实例化对象
  11. let obj = new Person('zhangsan')
  12. //
  13. // console.log(Person.prototype) // 输出 原型对象 (里面有say方法、里面有constructor属性)
  14. // console.log(Person.prototype.say)
  15. // console.log(Person.prototype.constructor)
  16. // console.log(obj.__proto__) // 输出 原型对象
  17. console.log(Person.prototype === obj.__proto__) // true
  18. </script>

如下图所示
image.png
了解了 JavaScript 中构造函数与原型对象的关系后,再来看原型对象具体的作用,如下代码所示:

  1. <script>
  2. function Person() {
  3. // 此处未定义任何方法
  4. }
  5. // 为构造函数的原型对象添加方法
  6. Person.prototype.say = function () {
  7. console.log('Hi~');
  8. }
  9. // 实例化
  10. let p1 = new Person();
  11. p1.sayHi(); // 输出结果为 Hi~
  12. </script>

其结构如图所示:
image.png
构造函数 Person 中未定义任何方法,这时实例对象调用了原型对象中的方法 say,接下来改动一下代码:

  1. <script>
  2. function Person() {
  3. // 此处定义同名方法 sayHi
  4. this.say = function () {
  5. console.log('嗨!');
  6. }
  7. }
  8. // 为构造函数的原型对象添加方法
  9. Person.prototype.say = function () {
  10. console.log('Hi~');
  11. }
  12. let p1 = new Person();
  13. p1.say(); // 输出结果为 嗨!
  14. </script>

构造函数 Person 中定义与原型对象中相同名称的方法,这时实例对象调用则是构造函中的方法 say
通过以上两个简单示例不难发现 JavaScript 中对象的工作机制:当访问对象的属性或方法时,先在当前实例对象是查找,然后再去原型对象查找,并且原型对象被所有实例共享。

什么是原型对象??
答:是构造函数的一个属性(prototype),它的数据类型是对象
原型对象有啥用??
答:原型对象对应的构造函数的实例方法或属性不存在时会去查找原型对象
总结:结合构造函数原型的特征,实际开发重往往会将封装的功能函数添加到原型对象中。