开源原生 JavaScript 插件--CJPCD(省市区联动)

一、前言

上两篇博客笔者对 JavaScript Module 模式,闭包等知识点做了简单介绍之后,我们今天开始正式开发一款属于自己的 JavaScript 插件。由于最近项目刚好用到地区选择这一块的功能。网上有许多类似插件,但是有些需求还是有些出入,所以就自己动手写了一个。思路是共通的但是实现和细节肯定会有所不同,我们重点放在代码介绍上。笔者已经将其上传到 github,大家可以下载使用,也可以把源码拷下来参考,路过的朋友顺手 star 哦。

二、补充知识

当前插件版本为1.0.1,能满足最常见的使用方式,后续笔者将会继续完善该插件。包括优化或者功能拓展,也希望使用过程中发现问题,或者有改进意见的朋友,可以帮忙指出。

源码浅析

我们先来看下核心代码(部分伪代码)

;
(function(){
    'use strict';
    var CJPCD = function (provinceId,cityId,districtId){
        if (!(this instanceof CJPCD)) return new CJPCD(provinceId,cityId,districtId); 

        var c = this;

        //code...

        return {
         setUp:c.set_value,//设置值
         reSet:c.builder_init,//重置
         getValue:c.get_value//获取值
        };
    };

    CJPCD.prototype={
        cj_areas_json: xxx(省市区json),
        cj_municipalities: xxx(直辖市数组)
    };

    window.CJPCD = CJPCD;
}());

可以看出我们采用的是闭包 + 面向对象的思维模式来进行开发的。这里所涉及的重复的知识点笔者将不再介绍,如果有不是很了解的朋友请移步上两篇文章《浅析 JavaScript 中的闭包(Closures)》《如何开发原生的 JavaScript 插件(知识点+写法)》。我们把讨论的重点放在前两篇文章中没有介绍到的知识点上。

我们先来看下下面这句代码:

'use strict';

字面上的意义来理解就是:“使用严格模式”,顾名思义这行代码开启了 Javascript 的严格模式。开启后有下面几个作用:

1.消除一些不严谨的语法,例如:

num = 1;//报错

不开启严格模式前上面的代码相当于隐式声明了一个全局变量,但是开启之后,全局变量只能显示声明,所以代码报错

2.提编译效率,增加运行速度

该模式在 ECMAscript 5 添加,包括IE 10及更高版本的IE,以及其他主流浏览器都支持该模式。而老版本的浏览器会将其识别为普通字符串,而进行忽略。

调用方式(两种):
第一种是放在脚本文件的第一行,表示整个脚本以严格模式运行。
第二种是放在函数内部第一行,表示整个函数以严格模式运行。

由此可以看出我们的插件(组件)是使用严格模式运行的。

3.注意点: 使用严格模式之后 this 关键字不允许指向全局对象。

我们通过代码来看可能会直观点。我们先来看下非严格模式下的代码

function test(){
     console.log(this);
}
test();

运行结果如下

运行结果

我们将代码加上严格模式的限制

function test(){
     'use strict';
     console.log(this);
 }
 test();//undefined
运行结果

所以当我们使用了构造函数后,我们就应当注意需要使用 new 运算

 function test(){
     'use strict';
     console.log(this);
 }
 var t = new test();
运行结果

限于篇幅,关于严格模式的介绍先到这了,有兴趣深入了解的朋友可以参考下面文章:

Strict Model

三、代码部分

基本代码

;
(function(){
  'use strict';
  var CJPCD = function (provinceId,cityId,districtId) {
    if (!(this instanceof CJPCD)) return new CJPCD(provinceId,cityId,districtId);
    
    var c = this;

    //初始化
    c.province = document.getElementById(provinceId);
    c.city = document.getElementById(cityId);
    c.district = document.getElementById(districtId);    

    //创建省市区数据
    c.builder_init = function(){      
      //初始化先创建省份数据
      c.province_builder();

      c.bindChangeEvent();

      //初始化触发
      c.province.onchange();
    };

    //设置自定义值
    c.set_value = function(province,city,district){

      //增强容错性-这里有点不严谨,后续完善
      if(province.indexOf("省")== -1)
        province = _type_format(province,"省");
      if (city.indexOf("市")== -1)
        city += "市";    

      //初始化先创建省份数据
      c.province_builder();
      c.province.value = province;

      c.city_builder();
      c.city.value = city;

      c.district_builder();
      c.district.value = district;
      
      //绑定事件
      c.bindChangeEvent();
    };

    //绑定事件
    c.bindChangeEvent = function(){
      c.province.onchange=function(){
        c.city_builder();
        c.city.onchange();
      };
      c.city.onchange=function(){
        c.district_builder();
      };
    };

    //生成省数据
    c.province_builder = function(){
      c.province.innerHTML="";
      //创建子元素
      c.optionFactory(c.cj_areas_json,c.province,"prov_index","省");
    };

    //生成市数据
    c.city_builder = function(){ 
      c.city.innerHTML = "";
      //获取当前选中省份的索引
      var pro_Id = c.province.options[c.province.selectedIndex].getAttribute("prov_index");
      var city_obj = c.cj_areas_json[pro_Id].city;
      //创建子元素
      c.optionFactory(city_obj,c.city,"city_index","市");
    };

    //生成区数据
    c.district_builder = function(){ 
      c.district.innerHTML = "";
      var pro_Id = c.province.options[c.province.selectedIndex].getAttribute("prov_index");
      var city_Id = c.city.options[c.city.selectedIndex].getAttribute("city_index");
      var district_obj = c.cj_areas_json[pro_Id].city[city_Id].area;

      //创建子元素
      c.optionFactory(district_obj,c.district,"");
    };

    //通用创建子函数方法
    c.optionFactory = function(json_data,parent_obj,attr_name,type){   
      /*
        数据源没有 "省","市"(节省空间)
        我们需要自己加(排除四个直辖市)
        区,县没有规律可循,所以数据源是完整的
      */
      var option;
      for (var i = 0; i < json_data.length; i++) {
        option = document.createElement("option"); 
        if(attr_name)
          option.setAttribute(attr_name,i);

        var name = !json_data[i].name ? json_data[i] : json_data[i].name;

        //加上后缀之前要排除直辖市和自治区
        option.innerHTML =  c.type_format(name,type);
        parent_obj.appendChild(option);
      };
      option = null;
    };

    //加上后缀之前要排除直辖市,还有数据源上的特殊元素"省"
    c.type_format = function(name,type){
      if (!type)
        return name;
      
      for(var item in c.cj_municipalities){
        if(c.cj_municipalities[item] == name){
          type = "";
          break
        };
      };
      return name + type;
    };

    c.get_value = function(valueType = "json"){

      var province_val = c.province.options[c.province.selectedIndex].value;
      var city_val = c.city.options[c.city.selectedIndex].value;
      var district_val = c.district.options[c.district.selectedIndex].value;
      if(province_val == "省"||city_val == "市"||district_val == "区")
        return null;
      if(valueType == "json")
        return {"province":province_val,"city":city_val,"district":district_val};
      else
        return province_val + valueType + city_val + valueType + district_val;
    };
    
    //内部初始化
    c.builder_init();

    //提供API给外部使用
    return {
     setUp:c.set_value,//设置值
     reSet:c.builder_init,//重置
     getValue:c.get_value//获取值
    };
  };

  //属性
  CJPCD.prototype={
    //篇幅限制只贴出部分(北京和天津)数据
    cj_areas_json : [{ "name": "省", "city": [{ "name": "市", "area": ["区"] }] },{ "name": "北京", "city": [{ "name": "北京", "area": ["东城区", "西城区", "崇文区", "宣武区", "朝阳区", "丰台区", "石景山区", "海淀区", "门头沟区", "房山区", "通州区", "顺义区", "昌平区", "大兴区", "平谷区", "怀柔区", "密云县", "延庆县"] }] }, { "name": "天津", "city": [{ "name": "天津", "area": ["和平区", "河东区", "河西区", "南开区", "河北区", "红桥区", "塘沽区", "汉沽区", "大港区", "东丽区", "西青区", "津南区", "北辰区", "武清区", "宝坻区", "宁河县", "静海县", "蓟  县"] }] }],

    cj_municipalities : ["北京","上海","天津","重庆","内蒙古","新疆","宁夏","西藏","广西","澳门","香港","台湾","钓鱼岛","省","市"]
  };
    
  window.CJPCD = CJPCD;

}());

源码已经基本都有加上注释了,在这里我就不累述了。我们来看下如何使它。

四、CJPCD的使用

如果你是从 github 下载完整的文件包的话,你可以看到介绍文档。内容同下:

最简单的使用只需要两个步骤:

  1. 放置容器,引入依赖

     <!--存放省市区数据的三个 select-->
     <select id="province"></select>
     <select id="city"></select>
     <select id="district"></select>
     
     <!--引入插件-->
     <script src="./cj-pcd-1.0.1.min.js"></script>
    
  2. 调用方法

      var pcd = new CJPCD('province','city','district');
    

    效果如下图

界面

API

  1. 初始化

     /**
         provinceId :省份 select id
         cityId     :城市 select id
         districtId :县区 select id
     */
     var pcd = new CJPCD(provinceId,cityId,districtId);
    
  2. 设置初始值

     /**
         provinceName :省份名称
         cityName     :城市名称
         districtName :县区名称
     
         注意:前提是必须初始化
     */
     pcd.setUp('provinceName','cityName','districtName');
    

    eg:

     var pcd = new CJPCD('provinceId','cityId','districtId');//先初始化
     pcd.setUp("广东省","广州市","越秀区");//设置值    
    

    使用场景:设置值一般用于地址信息的修改

  3. 重置

     pcd.reSet();
    
  4. 获取值

     pcd.getValue('json');//指定以json对象返回
     pcd.getValue();//默认以json对象返回,效果同上
    

    json 对象返回如下:

     {"province":"广东省","city":"广州市","district":"越秀区"};
    

    指定拼接成一定格式的字符串进行返回,以逗号为例(当然也可以使用其他字符串)

     pcd.getValue(',');//结果使用逗号拼接成字符串
    

    返回值如下

     "广东省,广州市,越秀区"
    
  5. 补充说明

    如果业务中涉及多组地址选择,请创建多个 CJPCD 实例 如

     var pcd1 = new CJPCD(provinceId1,cityId1,districtId1);
     var pcd2 = new CJPCD(provinceId2,cityId2,districtId2);
     ...
     var pcdn = new CJPCD(provinceIdn,cityIdn,districtIdn);
    

    使用案例如下:

使用案例

限于笔者技术,文章观点难免有不当之处,希望发现问题的朋友帮忙指正,笔者将会及时更新。也请转载的朋友注明文章出处并附上原文链接,以便读者能及时获取到文章更新后的内容,以免误导读者。笔者力求避免写些晦涩难懂的文章(虽然也有人说这样显得高逼格,专业),尽量使用简单的用词和例子来帮助理解。如果表达上有好的建议的话也希望朋友们在评论处指出。

本文为作者原创,转载请注明出处! Cboyce

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容

  • 工厂模式类似于现实生活中的工厂可以产生大量相似的商品,去做同样的事情,实现同样的效果;这时候需要使用工厂模式。简单...
    舟渔行舟阅读 7,718评论 2 17
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,598评论 18 139
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,577评论 18 399
  • 原文: https://github.com/ecomfe/spec/blob/master/javascript...
    zock阅读 3,370评论 2 36
  • 今天,爸爸早上去了深圳,后来他在深圳买了今天晚上回来的机票。然而他很搞笑的,他居然买了下周五的机票。所以他今...
    九五自尊阅读 131评论 0 0