JavaScript插件化开发

2023-10-20 18:44:59 浏览数 (1)

为什么要做插件化开发

这个是一个必然,因为我们要协作开发、代码重用

代码语言:javascript复制
function httpGet(apiUrl, params) {
  // doSomething
}

httpGet('http://localhost/api/demo/test');

上面这个代码示例很简单,实现了一个get方法调用api的函数,后来陆陆续续加上了postputdelete

代码语言:javascript复制
function httpGet(apiUrl, params) {
  // doSomething
}

function httpPost(apiUrl, params) {
  // doSomething
}

function httpDelete(apiUrl, params) {
  // doSomething
}

function httpPut(apiUrl, params) {
  // doSomething
}

然后,我们想把这几个函数提供给别的项目上用,于是我们把这些函数放到一个httpUtil.js文件中

代码语言:javascript复制
function httpGet(apiUrl, params) {
  // doSomething
}

function httpPost(apiUrl, params) {
  // doSomething
}

function httpDelete(apiUrl, params) {
  // doSomething
}

function httpPut(apiUrl, params) {
  // doSomething
}

这个时候我们进入了最原始的插件开发了,因为我们写的代码被跨时间、跨空间供不同的开发人员使用,这份代码也做了重用,减少了重复劳动

完善之路

我们罗列了一下上面这段代码的问题,如下

待优化:

  • 方法直接暴露在全局作用域,容易命名冲突
  • 没有面向对象编程,纯程式化的函数罗列

JavaScript面向对象的设计使用,是一个难点,我们从对象的生成慢慢说起

对象生成的方式

谈起对象的生成,可能很多人只能想到声明式的构造,这不怪你们,因为声明式是最好用且最通用的构造方式

声明模式

我们最常使用声明式构造对象

代码语言:javascript复制
var person = {
  age: 20,
  name: '王二'
}

var person = new Object();
person.age = 20;
person.name = '王二'
工厂模式

声明式虽然能创建对象,但是有个缺点,如果要创建多个对象,就需要重复写以上的代码。工厂模式解决了这个问题

代码语言:javascript复制
function createPerson(name, age) {
  var person = new Object();
  person.name = name;
  person.age = age;
  return person;
}

var person1 = createPerson('王二', 20);
var person2 = createPerson('张三', 13);
构造模式

工厂模式已经满足我们一般开发需求了,但是工厂模式太繁琐了

代码语言:javascript复制
function Person(name, age) {
  this.name = name;
  this.age = age;
}
var person1 = new Person('王二', 20);
var person2 = new Person('张三', 13);
对比

我们从工厂模式跨越到了构造模式,相比工厂模式,构造模式有几处不同

  • 没有显示创建对象
  • 直接将属性赋予了this对象
  • 没有return

构造函数模式隐含了很多步骤,虽然代码相对来说简洁了很多,但是对于初学者来说不太友好。假如你熟悉面向对象编程,那么你一定更倾向于使用new Person()

还有一个重要的好处:使用构造函数模式产生的对象式该函数的实例。也就是说工厂模式下,得到的personObject的实例,而构造函数模式下得到的person是更为具体的Person的实例。我们可以用instanceof关键字来判断一个实例属于哪个类型,或者我们可以通过实例的constructor来看构造函数是谁

到此为止,如果不考虑构造函数模式的一些细节问题,这种插件的写法已经可以应用到我们当前项目里了,代码如下:

代码语言:javascript复制
function HttpUtil() {
  this.get = function(apiUrl, params) {
    // ...
  }
  this.post = function(apiUrl, params) {
    // ...
  }
  this.delete = function(apiUrl, params) {
    // ...
  }
  this.put = function(apiUrl, params) {
    // ...
  }
}

// 使用方法
var httpUtil = new HttpUtil();
httpUtil.get('http://localhost/api/Demo/test');

这样一来插件就实现了面向对象的抽象过程,且将需要的各种变量封装了起来

看到这里,似乎已经解决了,其实不然,构造函数模式还是有一些小问题

避免重复创建

问题在于httpUtil函数体中的方法都是对象(函数也是对象),每当我们new一个新实例时,实例方法一样,但是重复了创建了,显然这是没必要的。对于精益求精的前端开发,这点不能容忍

最简单粗暴的方法时把相关函数提取出来

代码语言:javascript复制
function httpGet(apiUrl, params) {
  // doSomething
}

function httpPost(apiUrl, params) {
  // doSomething
}

function httpDelete(apiUrl, params) {
  // doSomething
}

function httpPut(apiUrl, params) {
  // doSomething
}

function HttpUtil() {
  this.get = httpGet;
  this.post = httpPost;
  this.delete = httpDelete;
  this.put = httpPut;
}

以上改造之后,如果new多个HttpUtil的实例后,get函数只有一个实例,那就是指向了和HttpUtil平级的get函数,其他函数也一样

但是从实际操作来讲,还不如多消耗一点性能,不将插件的函数提取出来。因为这样依赖暴露出来的变量急剧增加,很不好控制

那有没有办法解决这个问题?即多个实例的共同方法、对象指向一个,复用方法。答案就是:原型模式

代码语言:javascript复制
var HttpUtil = function() {};

HttpUtil.prototype = {
  constructor: HttpUtil,
  get: function(apiUrl, params) {
    // ...
  },
  post: function(apiUrl, params) {
    // ...
  },
  delete: function(apiUrl, params) {
    // ...
  },
  put: function(apiUrl, params) {
    // ...
  }
}

看上去原型模式很完美,但是还是有两个比较突出的问题:

  • 无初始化变量入口
  • 属性篡改

我们现在想在HttpUtil上增加版本信息和根据报错code自定义错误信息

代码语言:javascript复制
var HttpUtil = function() {};

HttpUtil.prototype = {
  constructor: HttpUtil,
  version: '1.0',
  errorMessageList: [{ code: 401, message: '未授权' }, { code: 500, message: '服务器内部错误' }],
  get: function(apiUrl, params) {
    // ...
  },
  post: function(apiUrl, params) {
    // ...
  },
  delete: function(apiUrl, params) {
    // ...
  },
  put: function(apiUrl, params) {
    // ...
  }
}

对于HttpUtil这个函数,无初始化变量入口意思就是在new出一个新实例时想直接将version传入,覆盖默认值。但是,以上的设计明显是做不到

再说属性篡改问题,导致这个问题的原因是引用类型浅拷贝。也就是说如果声明多个HttpUtil实例,它们将共享errorMessageList这个数组

解决方法很简单,我们改造一下构造函数接受参数,并将引用类型errorMessageList放到构造函数里:

代码语言:javascript复制
var HttpUtil = function(version, errorMessageList) {
  this.version = version;
  this.errorMessageList = errorMessageList;
}

HttpUtil.prototype = {
  constructor: HttpUtil,
  get: function(apiUrl, params) {
    // ...
  },
  post: function(apiUrl, params) {
    // ...
  },
  delete: function(apiUrl, params) {
    // ...
  },
  put: function(apiUrl, params) {
    // ...
  }
}

参考

  • [1] JavaScript插件开发从入门到精通系列
  • [2] 《你不知道的JavaScript》

0 人点赞