3、设计模式创建型之原型模式

设计模式创建型之原型模式

 
看到原型模式,不知道大家有没有想起来 JS 的基石:原型及原型链。对我而言,第一次看到设计模式中竟然有原型模式时,我的第一反应就是好奇这是不是就是 JS 中原型呢,这个答案容我下先卖个关子,如果有兴趣,请先接着往下看:

在 JAVA 这类强类型语言的描述下,原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这样的形容对于 JAVA 中的原型来讲是没错的,但是用来形容 JavaScript 中的原型却是远远不够的,在 JS 中,原型的重要性可以说是重中之重。

需要说明的是,本系列的设计模式是从前端角度出发,因此更多的关于 JAVA 语言体系下的原型模式可以查阅其他文章。在本文中,着重于从 JS 的角度去理解原型模式。

 

1.JS 中的 Class(类)

 
大家可能会好奇,不是讲原型吗,怎么突然讲到 class 了?

正是大家有这样的疑问,因此在这里就很有必要提出来,请先看一下这两种写法:

 
1、使用 class 定义一个 Person 类:

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

	// 默认为 void,表示无返回值
  public doEat(other, address) {
    console.log(`${this.name}${other}${address}吃饭!`);
  }
};

 
2、使用构造函数定义一个 Person 类:

var Person = (function () {
    function Person(name, age, phone) {
        this.name = name;
    		this.age = age;
    		this.phone = phone;
    }
    Person.prototype.doEat = function (other, address) {
        console.log(`${this.name}${other}${address}吃饭!`);
    };
    return Person;
}());

 
实际上,这两种写法是一样的。因为后者实际上是前者编译后的结果(做了一点调整,是方便大家阅读)

因此,从原型继承的角度上来讲,class 就是原型继承的语法糖。

注:上面提到,JS 中的 class 是语法糖,这个说法不完全正确,不过在此我们不纠结这一点,如果有兴趣,可以查阅《从头再学 JavaScript 系列》中关于 Class 的内容。

那么关于 JS 中 Class 就简单提一下,后面基本上会用构造器的方式来分析原型模式,因此对于 Class 不熟悉的同学也不用担心。

 

2. JS 中的原型

 
我们仍然用上面提到的 Person 构造函数来举例。
 
1、首先,构建一个 Person 的构造函数

function Person(name, age, phone) {
        this.name = name;
    		this.age = age;
    		this.phone = phone;
    }

Person.prototype.doEat = function (other, address) {
  console.log(`${this.name}${other}${address}吃饭!`);
};

 
2、通过 Person 创建一个实例

const zhangsan = new Person("张三", 24, "13911111111")
console.log(zhangsan);

 
那么 person 是什么样的呢?请看下图
person
 
可以看到,我们在 Person 的原型对象上绑定的 doEat 方法并没有出现在实例 zhangsan 中,那么我们能不能通过 zhangsan 来调用 doEat 方法呢?

让我们来试一试:

zhangsan.doEat("罗翔", "朝阳区"); // 张三和罗翔在朝阳区吃饭!

 
可以看到,虽然 doEat 方法没有出现在 zhangsan 这个实例中,但是却仍然可以使用到。这正是原型链的功劳,在上图中我们看到存在 [[Prototype]] 这个属性,让我们点开看看:
3、设计模式创建型之原型模式_第1张图片
 
可以看到,doEat 方法竟然在 [[Prototype]] 这个属性中,这是为什么?

如果你了解原型及原型链,那么就会清楚, [[Prototype]] 这个属性指向的是正是构造函数的原型对象:prototype。

现在让我们直接看看构造函数 Person 的原型对象 prototype 吧:

console.log(Person.prototype);

Person.prototype
 
果然上面提到的实例 zhangsan 的 [[Prototype]] 是指向其构造函数的原型对象(prototype)的。

好了,经过上面对原型的简单介绍,相信你会有一个初步的理解。不过由于关于原型的内容众多,仅仅这么几句话是不容易讲明白的,如果你有兴趣,可以读一读《从头再学 JavaScript 系列》中关于原型的部分。

 

3.JS 中的深拷贝

 
在 JS 中,拷贝分为深拷贝与浅拷贝,简单来说,如果是基本类型则通过等号可以拷贝出来一个新的值,不过如果是引用类型的值,那么实际上你拷贝的仅仅是地址,而这个地址指向的值才是真正的数据。

文章一开始我们提到,原型模式(Prototype Pattern)是用于创建重复的对象,那么在 JS 中我们怎么实现深拷贝呢?
 

下面给出一个完整的深拷贝方案:

const cloneDeep = (value) => {
  // 非数组和非对象直接返回值即可
  if (value == null || typeof value !== 'object') {
    return value;
  }
  // 初始化
  let result = Array.isArray(value) ? [] : {};
  for (let key in value) {
    if (value.hasOwnProperty(key)) {
      result[key] =  cloneDeep(value[key]);
    }
  }
  return result;
}

 
在这上面基本实现了对象和数组的深拷贝,更加详细的关于浅拷贝与深拷贝的内容可以查阅:https://blog.csdn.net/qq_40228484/article/details/118379290

 
文章最后,我们回答一下最开始提出的疑问:前端角度下的原型模式是不是就是 JS 中原型呢?
实际上,在 JS 的语言基础下,你可以这样理解,只不过 JS 中的原型更强大。在 JS 中谈原型模式,只需要你清楚的明白JS 中的原型及原型链的相关概念即可。

 
在 JS 中,原型就是用来定义对象和继承的基础。而在其他语言中,原型的重要性就不会有在 JS 中这么重,掌握好原型及原型链是学好 JS 的关键一步。建议阅读《从头再学 JavaScript 系列》中关于原型的部分,相信你一定能掌握好原型。

你可能感兴趣的:(JavaScript,设计模式系列,前端,javascript,设计模式,原型模式)