本文为译文, 初次翻译, 如有误导, 请多多包含, 如阅读英文, 可直接戳链接即可 js 之工程构造函数模式
类模式
前言
在面向 (oriented) 对象编程中, 一个类是一个可扩展的程序代码的模板, 用于创建对象, 为状态 (成员变量) 和行为实现 (implementations)(成员函数或方法) 提供初始值
JavaScript 中有一个特殊的语法结构和关键字类但在学习之前, 我们应该考虑类这个术语来自于面向对象编程的理论定义在上面引用, 它是语言无关独立的
在 JavaScript 中有几个众所周知的编程模式, 即使不使用 class 关键字也可以编写类在这里, 我们首先来谈谈他们
这个类的构造将在下一章中描述, 但是在 JavaScript 中它是一个语法糖, 是我们在这里学习的一种模式的扩展
功能类模式
根据定义, 下面的构造器函数可以被认为是类
- /*
- *
- * 用 new 关键字 + 函数名(), 那么这个函数为构造器函数
- * @construtor: User
- * @methods:sayHi
- *
- *
- */
- functoin User(name) {
- this.sayHi = function() {
- alert(name);
- }
- }
- let user = new User("john");
- user.sayHi(); // john
结果如下所示:
它遵循定义的所有部分
它是一个用于创建对象的程序代码模板(可以用 new 来调用)
它提供了状态的初始值(参数名称)
它提供了方法(sayHi)
在函数类模式中, 用户内部的局部变量和嵌套函数, 没有分配给它, 从内部可见, 但在外部代码无法访问
所以我们可以很容易地添加内部函数和变量, 比如 calcAge()
- /*
- * 添加内部函数和变量
- * @constructor: User
- * @parameter: 参数 name,birthday
- * @function calcAge
- * @methods: sayHi
- * @return 时间戳
- *
- */
- function User(name,birthday){
- // only visible from other methods inside User
- function calcAge(){
- return new Date().getFullYear()-birthday.getFullYear();
- }
- this.sayHi = function(){
- alert(`${name,age:${calcAge()}}`);
- }
- }
- let user = new User("john",new Date(2000,0,1));
- user.sayHi(); // john,age:18
结果如下所示:
在这个代码中, 变量名, 生日和函数 calcAge()是内部, 对象是私有的他们只能从里面看到
另一方面, 说 Hi 就是外在的, 公开的方法创建用户的外部代码可以访问它
这样我们就可以从外部代码中隐藏内部实现 (internal implementation) 细节和辅助方法只有分配给这个构造函数才可以看得见外面的
工厂类模式
我们可以创建一个班级, 而不使用新的
像这样
- /*
- * 工厂类模式
- * @constructor User
- * @parameter 形式参数: name,birthday
- * @function calcAge
- * @return 当前的年份 - 出生的年份
- * @return User 函数返回一个 sayHi 函数, 将名字和年龄结果进行输出
- *
- *
- */
- function User(name,birthday){
- // only visible from other methods inside User
- function calcAge(){
- return new Date().getFullYear()-birthday.getFullYear();
- }
- return{
- sayHi(){
- alert(`${name},age:${calcAge()}`);
- }
- }
- }
- let user = User("john",new Date(2000,0,1)); // 函数名的调用, 函数表达式赋值
- user.sayHi();
实现的效果如下所示
正如我们所看到的, 函数 User 返回一个具有公共属性和方法的对象这种方法的唯一好处是我们可以省略 new:write let user = User(...)而不是 let user = new User(...)在其他方面, 它几乎与功能模式相同
基于原型的类
基于原型的课程是最重要的, 也是最好的功能和工厂类模式在实践中很少使用
不久你就会明白为什么
这是用原型重写的同一个类
- /*
- *
- * 基于原型重写的一个类
- * @function User
- * @parameter name,birthday
- * @prototype
- * @methods: _calcAge,sayHi
- *
- *
- */
- function User(name,birthday){
- this._name = name;
- this._birthday = birthday;
- }
- User.prototype._calcAge = function(){
- return new Date().getFullYear()-this._birthday.getFullYear();
- }
- User.prototype._calcAge = function(){
- return new Date().getFullYear()-this_birthday.getFullYear();
- }
- User.prototype.sayHi = function(){
- alert(`${this._name},age:${this._calcAge()}`);
- }
- let user = new User("john",new Date(2000,0,1));
- user.sayHi();
实现效果如下所示:
代码结构
构造函数 User 仅初始化当前的对象状态
方法被添加到 User.prototype 中
正如我们所看到的, 方法在词法作用域上不在函数 User 内部, 它们并不共享一个通用的作用域环境. 如果我们在函数 User 中声明变量, 那么它们将不会被方法看到
所以, 有一个众所周知的协议, 内部属性和方法前缀为下划线像_name 或_calcAge()从技术上讲, 这只是一个协议, 外部代码仍然可以访问它们但大多数开发人员认识到的含义, 并尽量不要触摸外部代码中的前缀属性和方法
以下是功能模式的优点:
在功能模式中, 每个对象都有自己的每个方法的副本我们在构造函数中分配了 this.sayHi = function(){...}和其他方法的单独副本
在原型模式中, 所有的方法都是在所有用户对象之间共享的 User.prototype 中一个对象本身只存储数据
所以原型模式更具有记忆效率
但不仅如此原型允许我们以非常有效的方式设置继承内置的 JavaScript 对象都使用原型还有一个特殊的语法结构: 类, 为他们提供漂亮的语法还有更多, 所以让我们继续他们
类基于原型的继承
假设我们有两个基于原型的类
兔子:
- /*
- *
- * 类基于原型的继承
- *
- * @function Rabbit
- * @paraterm name
- * @method jump
- * @constructor: Rabbit
- *
- */
- function Rabbit(name){
- this.name = name;
- }
- Rabbit.prototype.jump = function(){
- alert(`${this.name}jimp!`);
- }
- let rabbit = new Rabbit("my rabit");
- rabbit.jump(); // my rabit jimp
实现的效果图如下
和动物
- /*
- * @constructor:Animal
- * @methods:eat
- *
- *
- */
- function Animal(name){
- this.name = name;
- }
- Animal.prototype.eat = function(){
- alert(`${this.name}eats`);
- }
- let animal = new Animal("My animal");
- animal.eat();
代码实例效果如下:
现在他们完全独立了
但是我们希望兔子能扩展动物换句话说, 兔子应该以动物为基础, 可以使用动物的方法, 并用自己的方法扩展它们
原型语言是什么意思?
现在兔子对象的方法在 Rabbit.prototype 中如果在 Rabbit.prototype 中找不到方法, 我们希望兔子使用 Animal.prototype 作为后备
所以原型链应该是 RabbitRabbit.prototypeAnimal.prototype
像这样
要实现的代码
- /*
- * @constructor Animal,Rabbit
- * @paraterm name
- * @methods:eat,jump
- *
- *
- *
- */
- // same Animal as before
- function Animal(name){
- this.name = name;
- }
- // All animals can eat,right?
- Animal.prototype.eat = function(){
- alert(`${this.name}eats`);
- }
- // same Rabbit as before
- function Rabbit(name){
- this.name = name;
- }
- Rabbit.prototype.jump = function(){
- alert(`${this.name}jumps`);
- }
- // setup the inheritance chain
- Rabbit.prototype._proto_= Animal.prototype; // (*)
- let rabbit = new Rabbit("white Rabbit");
- rabbit.eat(); // rabbits can eat too
- rabbit.jump();
该行 (*) 设置了原型链所以兔子首先在 Rabbit.prototype 中搜索方法, 然后是 Animal.prototype 然后, 为了完整起见, 如果在 Animal.prototype 中没有找到该方法, 则继续在 Object.prototype 中进行搜索, 因为 Animal.prototype 是一个普通的普通对象, 所以它继承了它
所以这是完整的画面
小结
术语类来自面向对象编程在 JavaScript 中, 它通常意味着功能类模式或原型模式原型模式更强大, 更高效, 所以它建议坚持下去
根据原型模式
方法存储在 Class.prototype 中
原型相互继承
总结
在本节当中, 主要讲的是工厂构造函数模式, 用于创建对象的模板, 其中模板可以粗俗的理解模具, 它是基于一份模具创建很多个不同的对象, 工厂构造函数就是用于创建多个共享特性和行为的对象, 通过构造函数生成的对象具有默认的属性和方法, 而原型就是更改对象下面公用的属性和方法, 让公用的属性和方法达到共用一份, 一是为了减少内存的开销, 提高性能, 另一方面是为了拓展, 当需要在代码的其余所有部分通过屏蔽较为复杂的的对象创建方法来简化某些特定对象的创建过程时, 使用工厂模式最为合适, 其实它也就是面向对象的一种写法
来源: https://juejin.im/post/5a4f98e1f265da3e4a6f49be