精华内容
下载资源
问答
  • 本程序基于组件思想编写,实现功能:缺失功能DLL,菜单变灰色,不可用。 含源代码及程序,仅供参考学习
  • Android的面向组件思想

    千次阅读 2012-09-26 09:27:34
    面向组件思想是在软件规模扩大,复杂度上升的背景下,以面向对象为基础而提出的一种软件设计思想。可以把它理解为一种更粗粒度的面向对象,其粒度一般大于对象,但具体要到什么程度,又可以根据实际情况来决定。这种...

    面向组件思想是在软件规模扩大,复杂度上升的背景下,以面向对象为基础而提出的一种软件设计思想。可以把它理解为一种更粗粒度的面向对象,其粒度一般大于对象,但具体要到什么程度,又可以根据实际情况来决定。这种思想以组件为基础,强调“服务”的概念。

    面向组件具有以下特点:

    低耦合性:组件之间一般互不依赖,一个组件只需要知道另一个组件的“名字”就可以访问它;

    高重用性:系统级的重用;

    高互操作性:不同的开发商开发出来的组件只要定义好了接口就可以互相访问;

    进程的透明性:组件可以工作在同一个进程也可以工作在不同的进程;

    语言和开发环境的独立性:组件只需要定义好服务接口,内部实现可以采用任何语言。


    面向组件的框架已经有很多了,如JavaBeanEJBCOM等。Android系统并没有声称自己是面向组件的,但从其设计思想来看,Android一开始的设计考虑就是要采用一种“无边界”的设计方式,要实现系统内资源高度的重用。


    事实上,Android完全符合面向组件的特征。首先,Android提供了ActivityServiceBroadcastReceiverContentProvider四大组件。这四大组件之间的协作是通过Binder机制和来协作的,其中ServiceManagerActivityManagerService是保证组件间协同工作的最重要的基础,同时它们本身也是一种组件。ServiceManagerBinder的守护进程,用来管理各种服务,并向调用这些服务的客户提供查询远程接口的功能。而ActivityManagerService负责所有ActivityService的启动,另外还负责系统中所有广播的注册和发布以及把广播发送给接收者。


    有了这个机制,Android各个组件之间实现了低耦合,ActivityActivityActivityServiceServiceService之间的都只需知道对方的“名字”就可以访问它。而且,它们之间的重用是系统级的,尤其是Service,任何部署在系统里的Service可以为任何一个应用,任何一个进程,任何一个组件来提供服务。它也满足高互操作性,任何开发者开发出来的组件,只要公布其接口就可以为其他开发者所用。Android对进程完全透明,取而代之的,在Android里,一般用Task的概念来作为应用的边界,一个Task可以是单个进程也可以是多个进程。ActivityService的协作既可以在同一个进程又可以在不同进程完成。如,启动ActivitystartActivity函数,如果新Activity配置了android:process这个属性,那么,它就会运行在另一个进程中 ,否则运行在同一进程中。而启动Service的方式有两种,如下:

    startService:运行在不同进程;

    bindService:运行在统一进程。

    启动过程都是通过binder机制来通知ActivitManagerService来决定是否要新建一个进程,并启动新的ActivityService

    展开全文
  • Android组件设计思想

    万次下载 热门讨论 2013-10-23 01:11:07
    Android的组件设计思想与传统的组件设计思想最大的区别在于,前者不依赖于进程。也就是说,进程即使由于内存紧张被强行杀掉了,但是运行在里面的组件还是存在的。这样就可以在组件再次需要使用时,原地满血复活,就...
  • Vue组件思想

    千次阅读 2020-05-21 21:42:29
    Vue的组件思想 ①它提供了一种抽象,让我们开发出一个个独立可复用的小组将来构造我们的应用。 ②任何的应用都会被抽象成一棵组件树。 一、注册组件的基本步骤 组件的使用分为三个步骤 创建组件构造器 注册组件...

    如果将一个页面中所有的处理逻辑全部放在一起,那么处理起来会非常复杂混乱,而且不利于后续的管理以及扩展。

    如果将页面拆分成一个个小的功能块,每个功能块完成属于自己的独立功能,那么整个页面的管理和维护就变得容易了。

    在这里插入图片描述
    在这里插入图片描述

    一个页面可以分为多个组件,每个组件又可以细分

    Vue的组件思想

    ①它提供了一种抽象,让我们开发出一个个独立可复用的小组将来构造我们的应用。

    ②任何的应用都会被抽象成一棵组件树。

    一、注册组件的基本步骤

    组件的使用分为三个步骤

    1. 创建组件构造器

    2. 注册组件(全局注册、局部注册)

    3. 使用组件

    1.1 调用Vue.extend() API:创建组件构造器:const x = Vue.extend({ })

    2.1 调用Vue.component() API:注册组件

    3.1 在Vue实例范围内使用组件

    1.1.1 创建组件模板:(旧的写法)
    x 是构造器名称,在注册是会引用此名称。组件就相当于一个html页面,最外层要用一个div来包裹,然后再是内容。
    const x = Vue.extend({
    	template:
    		`
    		<div>
    			<h2>我是组件标题</h2>
    		</div>
    		`
    })
    
    2.1.1 全局注册组件: 'my-cpn’是组件的标签名,x 是1.1.1的内容。也就是说,以后用到x 组件时,用<my-cpn></my-cpn>就可以代替了。
    Vue.component('my-cpn',x)
    
    3.1.1 在Vue实例中使用:
    <div id="app">
    	<my-cpn></my-cpn>
    </div>
    

    二、创建组件构造器步骤解析

    1、Vue.extend() 创建的是一个组件构造器,通常在创建组件的地方,传入template代表自定义组件的模板,该模板就是在使用到组件的地方,要显示的HTML代码。旧写法参考1.1.1内容。

    2、Vue.component()要传入两个参数,一个是注册组件的标签名称,一个是组件构造器的名称。如1.1.1中的 x 。

    3、可以多重嵌套使用自定义组件,但组件标签中不能有其他内容<my-cpn>用

    四、父组件和子组件

    先构造两个组件:

    第一个:
    const cpn1 = Vue.extend({
    	template :`<div><h2>组件1</h2></div>`
    })
    
    第二个:把组件1在组件2中注册:
    const cpn2 = Vue.extend({
    	template :`
    	<div>
    		<h2>组件2</h2>
    		<cpn1></cpn1>
    	</div>
    	`,
    	components:{ 
            cpn1:cpn1 // 可以简写为一个cpn1
        } 
    })
    
    组件2在实例中注册
    const app = new Vue({
          el: "#app",
          components: {
            cpn2: cpn2 // 可以简写为一个cpn1
          },
    })
    
    使用:
      <div id="app">
        <cpn1></cpn1> // 未在实例中注册,不可使用
        <cpn2></cpn2> // 已在实例中注册,可以使用
      </div>
    

    其实const app实例也算是一个组件,只不过是页面的最大组件:根组件ROOT,所以vue实例也有template。子组件必须先于父组件创建,否则识别不了子组件。

    五、注册组件的语法糖

    此语法糖是将创建组件的构造器嵌入注册步骤当中。

    原先:遵循1.1->2.1->3.1的顺序

    现在 1.1和2.1结合:(全局)

    Vue.component('my-cpn',{
    	template: `<div><h2>组件标题</h2></div>`
    })
    

    (局部)

    const app = new Vue({
          el: "#app",
          components: {
            	'my-cpn': {
            		template:`<div><h2>组件标题</h2></div>`
            }
          },
      })
    

    六、组件模板抽离的写法

    由于在template中写html语句的便捷性不好,所以将html语句抽离出来写

    6.1 写在<script>标签中:

    <script type="text/x-template" id="cpn1">
    	<div>
    		<h2>组件1</h2>
    	</div>
    </script>
    

    script标签类型为:text/x-template ,并且要指定id,以便于使用

    以全局注册为例:

    Vue.components( 'my-cpn1' , { template: '#cpn1' })
    

    在实例中使用:

    <div id="app">
    	<my-cpn1></my-cpn1>
    </div>
    

    6.2 写在<template>标签中:

    <template id="cpn2">
    	<div>
    		<h2>组件2</h2>
    	</div>
    </template>
    

    给template标签加上id,便于使用。

    以局部注册为例:

    const app = new Vue({
          el: "#app",
          components: {
            	'my-cpn2': {
            		template: '#cpn2'
            }
          },
      })
    

    在实例中使用:

    <div id="app">
    	<my-cpn2></my-cpn2>
    </div>
    

    七、注意点

    1、组件内部不能直接访问实例内部数据或方法等。

    2、组件是一个单独功能模块的封装,有属于自己的html模板。

    3、即使不是直接而是间接地让组件访问到实例中的数据,那么假设一个页面有成千上百个组件,数据全部存放到实例中,会让实例显得臃肿。

    4、vue组件要有存放自己数据的地方。

    八、组件的数据存放

    在注册的时候

    <template id="cpn3">
    	<div>
    		<h2>{{title}}</h2>
    	</div>
    </template>
    
    Vue.components( 'my-cpn3' , 
    	{ 
    		template: '#cpn3',
    			data() {
    				return {
                        title: '组件3'
    				}
    			}
    	})
    

    组件存放自己数据的地方,在与template同级下的 data()函数中,所以需要返回值,这个值是对象类型,与实例中的data:{}不太一样。其实组件的原型是指向vue实例的,所以组件中也有实例的东西,例如生命周期函数等。

    九、为什么组件中的data(){}必须是一个函数?

    1、若是以对象的形式存放

    ①先定义个对象:

    const obj = {name: "小明", age:18}
    

    ②存到data中:

    data(){ return obj }
    

    此时已分配内存空间来存这个对象,假设 内存地址:0x100

    后续操作:

    let obj1 = data() 
    let obj2 = data() 
    obj1.name = '小红'
    console.log(obj1) 
    console.log(obj2) 
    
    结果: name: '小红' age :18
          name: '小红' age :18
    

    obj1、obj2指向的是同一片内存空间的内容,obj1.name = '小红‘,相当于修改这个内存地址中的内容,所以即使obj2没有对这个对象进行修改,但是获取到的内容已经改变了。

    2、若是以函数形式存放

    data(){
    	return{
    		name: "小明",
    		age:18
    	}
    }
    

    仅是定义和声明,在内存中暂无对象存储的空间,有调用到此函数,再开辟内存空间。

    后续操作:

    let obj1 = data() 
    let obj2 = data() 
    obj1.name = '小红'
    console.log(obj1) 
    console.log(obj2) 
    
    结果: name: '小红' age :18
          name: '小明' age :18
    

    obj1调用一次data(),内存开辟空间存 name、age,假设内存地址为0x100

    obj2调用一次data(),内存开辟空间存 name、age,假设内存地址为0x200

    每调用一次data(),就会开辟新的空间来存,所以内存地址不一样,也就是说,obj1、obj2只是内容一样,但没有关系的两块内存空间。在后续操作中,obj1修改了name,只是修改了0x100中的数据,obj2还是保持和第一次获取到的内容一致,没有受影响,二者是独立使用data的。保证了组件的 独立性 和 可 复用性。

    十、父子组件的通信

    1、父组件通过props向子组件传递消息。

    2、子组件通过事件向父组件发送消息。

    在这里插入图片描述

    props的基本用法:从父组件(根组件)传递数据给子组件

    10.1 定义一个模板(子组件)
    <template id="soncpn">
    	<div>
    		<p>{{sonmessage}}</p>
    		<h2>{{sonmovies}}</h2>
    	</div>
    </template> 
    // 先留两个空位给message和movies
    
    10.2 构造组件,构造器名称:cpn
    const cpn = {
         template: "#soncpn",
         props: ['sonmessage','sonmovies']
      }
      // props用来接收父组件传来的数据
    
    10.3 将vue实例视作父组件,将子组件在父组件中注册
    const app = new Vue({
          el: "#app",
          data:{
          	message: "hello world!",
          	movies:['电影1','电影2']
          }
          components: {
            	cpn
          },
      })
    
    10.4 传递数据
    <div id="app">
    	<cpn :sonmessage="message" :sonmovies="movies"></cpn>
    </div>
    

    十一、props的第二种写法及类型验证

    1、写法

    第一种写法:参照10.2

    props:['sonmessage','sonmovies']
    

    第二种写法:

    props:{
    	sonmessage: String,
    	sonmovies: Array
    }
    

    第二种写法扩展:

    props:{
    	sonmessage: {
    		type: String,
    		default: '默认值'
    	}
    	sonmovies: {
    		type: Array,
    		default(){
    			return []
    		}
    	}
    }
    

    第二种写法是强制规定传入的数据类型,当传入类型定义是数组或是对象时,默认值必须使用工厂函数来指定。

    当传入值为多种类型:

    type: [String,Number...]
    

    2、props名注意点:驼峰命名要转换成以-连接

    props:['sonMessageInfo']
    

    在实例中:

    <cpn :son-message-info="xxx"></cpn>
    

    在<template> 中没有关系

    <template>
    	<div>
    		<h2>{{sonMessageInfo}}</h2>
    	</div>
    </template>
    

    十二、子组件向父组件传递数据(自定义事件)

    props的双向绑定

    1、子组件通过$emit发送事件和参数。

    2、父组件通过 v-on / @ 来监听

    如果要对传入的props进行双向绑定,不建议直接 v-model=“props” ,要用data中的一个新变量来代替。这里是举例input输入框的input事件,将动态绑定的value,也就是numberdata,通过$emit传递给父组件,再在父组件模板中监听此事件,并获取到传过来的值,在父组件methods中定义方法,将获取到的值赋给父组件data中数据。

    在这里插入图片描述

    <body>
      <div id="app">
        <h2>{{homeNum}}</h2>
        <cpn :numberprops="homeNum" @numinput="num1change"></cpn>
      </div>
    
      <script src="./js/vue.js"></script>
    
      <template id="cpn">
        <div>
          <h2>子组件:{{numberdata}}</h2>
          <h2>props:{{numberprops}}</h2>
          <input type="text" :value="numberdata" @input="numinput">
    
        </div>
      </template>
    
      <script>
        // 3、在页面中使用组件
        const app = new Vue({
          el: "#app",
          data: {
            homeNum: 2
          },
          methods: {
            num1change(value) {
              this.homeNum = parseInt(value)
              // console.log(value)
            }
          },
          components: {
            cpn: {
              template: "#cpn",
              props: {
                numberprops: Number
              },
              data() {
                return {
                  numberdata: this.numberprops
                }
              },
              methods: {
                numinput() {
                  this.numberdata = event.target.value
                  // console.log(this.number)
                  this.$emit('numinput', this.numberdata)
    
                }
              }
            }
          },
        })
      </script>
    </body>
    

    总结为4步:

    1、子模版添加默认事件。

    2、子组件methods中使用$emit来发送自定义事件。

    3、父模板中 @自定义事件名=“父组件处理事件名” 来监听。

    4、父组件methods中处理: 父组件处理事件名(){ 处理逻辑 }

    十三、父子组件的访问方式

    通过对象直接访问

    父组件访问子组件:$children 、 $refs

    要想使用$refs,必须在组件标签中添加ref属性。

    // 在父模板中
    <div id="app">
    	<cpn ref="aaa"></cpn>
    	<cpn1 ref="bbb"></cpn1>
    	<cpn2></cpn2>
    </div>
    

    this.$refs 就可以获取到所有添加了ref属性的子组件

    this.$refs.aaa 可以取到具体项

    子组件访问父组件:$parent​

    父组件中可能包含多个子组件,所以this.$children获取到的是一个数组类型。

    this.$parent 是单个对象,它只能获取到上一级组件

    补充:获取根实例:this.$root

    展开全文
  • JavaScript组件设计思想

    千次阅读 2016-03-19 19:44:14
    在最后一次交接会议上,田老师阐述了一个观点,“当你学会了用‘分层思想’去看待事情,任何的问题都不是问题,都可以实现”。当然,这里说的是在程序设计方面。自己觉的很有道理,但是体会不是很深。 紧跟着,...

    上个周,并肩作战的田老师离职了,尽管在一起愉快玩耍的时间不到一年,自己仍然还是从其身上学到、体会到了好多关于知识、理想的东西。对于大多数年轻人关于“晚上想想千条路,早上起来走原路”的现状,他那种敢于甩掉一切去做自己感兴趣、梦想的事的勇气是我所钦佩的。在此,祝愿田老师一切顺利。
    在最后一次交接会议上,田老师阐述了一个观点,“当你学会了用‘分层思想’去看待事情,任何的问题都不是问题,都可以实现”。当然,这里说的是在程序设计方面。自己觉的很有道理,但是体会不是很深。
    紧跟着,这个周期盼已久的“重构版热图”上线了,“低bug率、高速度”等在各方面指标瞬间秒杀“旧版热图”,让大家眼前一亮。随即,我们组织了分享讨论会,让匡哥讲述其重构过程中的设计思路。
    大致思想如下:将每个功能点最小颗粒化、然后将其封装成模块;创建数据中心,使各个模块不在互相调用嵌套,所有的依赖和调用全部通过数据中心(这里使用自定义事件实现的观察者模式);所有的网状的需求点,划点成线,最终形成操作流。
    这不就是“分层思想”的一种体现吗?我陷入了沉思~~~
    现在,大前端流行组件化、模块化。然而,我们的模块又该如何设计实现呢?

    下面的实例,参考自【javascript组件开发方式】【GitHub示例地址

    JavaScript组件设计思想
    文本框内输入内容,后面动态显示输入的字符长度。

    <div id="container">
        <input id="content" />
    </div>

    1. 函数式写法

    $(function() {
        var $content = $("#content");
        // 获取字数
        function getNum() {
            return $content.val().length;
        }
        // 渲染元素
        function render() {
            var num = getNum();
            if($("#contentCount").length === 0) {
                // 不存在统计字符的DOM元素
                $content.after("<span id='contentCount'></span>");
            }
            $("#contentCount").html(num + "个字");
        }
        // 监听时间
        $content.on("keyup", function() {
            render();
        });
    });

    缺点:变量混乱,没有很好的隔离作用域,当页面变得复杂的时候,很难维护。

    2. 使用变量模拟单个命名空间,统一入口调用方法

    var textCount = {
        input: null,
        init: function(config) {
            this.input = $(config.id);
            this.bind();
            return this;    // 方便实现链式调用
        },
        bind: function() {
            var self = this;
            this.input.on("keyup", function() {
                self.render();
            });
        },
        render: function() {
            var num = this.getNum();
            if($("#contentCount").length === 0) {
                this.input.after("<span id='contentCount'></span>");
            }
            $("#contentCount").html(num + "个字");
        },
        getNum: function() {
            return this.input.val().length;
        }
    };
    $(function() {
        textCount.init({id: '#content'}).render();
    });

    缺点:这种写法没有私有的概念。其他代码可以很随意的改动这些,容易出现变量重复,或被修改的问题。

    3. 函数闭包的写法

    把所有的东西都包在了一个自动执行的闭包里面,所以不会受到外面的影响,并且只对外公开了TextCountFun构造函数,生成的对象只能访问到init,render方法。事实上大部分的jQuery插件都是这种写法。

    var textCount = (function() {
        // 私有方法
        var _bind = function(that) {
            that.input.on("keyup", function() {
                that.render();
            });
        };
        var _getNum = function(that) {
            return that.input.val().length;
        };
        var TextCountFun = function() {};
        TextCountFun.prototype.init = function(config) {
            this.input = $(config.id);
            _bind(this);
            return this;
        };
        TextCountFun.prototype.render = function() {
            var num = _getNum(this);
            if($("#contentCount").length === 0) {
                this.input.after("<span id='contentCount'></span>");
            }
            $("#contentCount").html(num + "个字");
        };
        // 返回构造函数
        return TextCountFun;
     })();
     $(function() {
        new textCount().init({id: '#content'}).render();
     });    

    4.面向对象

    var Class = (function() {
      var _mix = function(r, s) {
            for (var p in s) {
                if (s.hasOwnProperty(p)) {
                    r[p] = s[p];
                }
            }
      }
      var _extend = function() {
            //开关 用来使生成原型时,不调用真正的构成流程init 
            this.initPrototype = true;
            var prototype = new this();
            this.initPrototype = false;
            var items = Array.prototype.slice.call(arguments) || [];
            var item;
            //支持混入多个属性,并且支持{}也支持Function 
            while (item = items.shift()) {
                _mix(prototype, item.prototype || item);
            }
          // 这边是返回的类,其实就是我们返回的子类 
            function SubClass() {
                if (!SubClass.initPrototype && this.init) {
                    this.init.apply(this, arguments); //调用init真正的构造函数 
                }
            }
          // 赋值原型链,完成继承 
            SubClass.prototype = prototype 
            // 改变constructor引用 
            SubClass.prototype.constructor = SubClass 
            // 为子类也添加extend方法 
            SubClass.extend = _extend 
            return SubClass 
        }
        //超级父类 
        var Class = function() {};
        //为超级父类添加extend方法 
        Class.extend = _extend;
        return Class;
    })();
    
    var TextCount = Class.extend({
      init: function(config){
            this.input = $(config.id);
            this._bind();
            this.render();
      },
      render: function() {
            var num = this._getNum();
            if ($('#contentCount').length == 0) {
                this.input.after('<span id="contentCount"></span>');
            }
            $('#contentCount').html(num + '个字');
      },
      _getNum: function(){
            return this.input.val().length;
      },
      _bind: function(){
            var self = this;
            self.input.on('keyup', function() {
                self.render();
            });
      }
    });
    $(function() {
      new TextCount({id:"#content"});
    });

    缺点:当一个页面特别复杂,当我们需要的组件越来越多,当我们需要做一套组件。仅仅用这个就不行了。首先的问题就是,这种写法太灵活了,写单个组件还可以。如果我们需要做一套风格相近的组件,而且是多个人同时在写。那真的是噩梦。

    5. 引入事件机制(观察者模式)

    下述创建对象采用《构造函数和原型模式组合使用》,此方式最广泛、认同度最高。

    function Event(config) {
        // 私用,外部不允许直接调用
        this._config = config;  // 存储相关配置信息
        this._events = {};      // 存储所有处理函数 
     };
     Event.prototype = {
        constructor: Event,
        // 监听事件 key:事件类型,listener:事件处理函数(可以同时绑定多个不同类型事件)
        on: function(keys, listener) {
            var keyList = keys.split(/[\,\s\;]/);   // 支持同时绑定多个事件,用【逗号、分号或空格隔开】
            var index = keyList.length;
            while (index) {
                index--;
                var key = keyList[index];
                // 不存在当前类型的事件
                if (!this._events[key]) {
                    this._events[key] = [];     // 这里指定为数组,可以多次绑定同一事件
                }
                this._events[key].push(listener);
            }
        },
        // 只能移除指定类型事件(一个)
        off: function (key, listener) {
            // 不指定事件类型,移除全部事件
            if (!key) {
                this._events = {};
                return;
            }
            var event = this._events[key];
            // 不存在要移除的事件,直接返回
            if (!event) {
                return;
            }
            // 不指定事件处理程序,移除指定类型
            if (!listener) {
                delete this._events[key];
            } else {
                var length = event.length;
                while (length > 0) {
                    length--;
                    if (event[length] === listener) {
                        event.splice(length, 1);        // 移除指定类型、指定处理程序的事件
                    }
                }
            }
        },      
        // 触发对应类型的事件,私有,外部不允许调用(为达到统一出口目的)
        _emit: function (key, args) {
            var event = this._events[key];
            if (event) {
                var length = event.length;
                var i = 0;
                while (i < length) {
                    event[i](args);
                    i++;
                }
            }
        }
     }
     Event.prototype.setConfig = function(config) {
        this._config = config;
     };
     Event.prototype.getConfig = function() {
        return this._config;
     };
     Event.prototype.setInput = function(input) {
        this.setConfig(input)
        // input信息改变,触发自定义change事件
        this._emit("inputChange");
     }
    
    var customerEvent = new Event();
    // 监听自定义inputchange事件
    customerEvent.on("inputChange", function() {
        var $input = customerEvent.getConfig();
        var num = $input.val().length;
        if ($('#contentCount').length == 0) {
            $input.after('<span id="contentCount"></span>');
        }
        $('#contentCount').html(num + '个字');
    });
    
    $("#content").on("keyup", function() {
        customerEvent.setInput($(this));
    });

    说明:由于功能比较单一,所以不能很好的体会到上述“观察者模式”的好处。试想,将上述抽离为两个业务模块,即当input内容长度发生改变(模块A),要通知另一个业务模块去改变对应显示(模块B)。如果不采用上述模式,很容易造成模块之间的互相调用。很容易造成在不知情的情况下修改了模块A导致了模板B不能正常使用。而上述方式,提供了一种分层的方式。A模块处理A的任务、B模块处理B的任务。模块之间的调用和耦合全局交给中间控制层(上述Event所在层)去控制。
    注意:所有的时间触发,都在中间控制层;而相关的事件监听和引起事件触发的动作则在相关模块。为了正常通信,相关模块需要共享同一个中间控制层实例。

    6. 加强版

    // Base封装组件的各个过程,并具有时间机制
    var Base = Class.extend({
        init:function(config){
            //自动保存配置项
            this.__config = config
            this.bind()
            this.render()
        },
        //可以使用get来获取配置项
        get:function(key){
            return this.__config[key]
        },
        //可以使用set来设置配置项
        set:function(key,value){
            this.__config[key] = value
        },
        bind:function(){},
        render:function() {},
        //定义销毁的方法,一些收尾工作都应该在这里
        destroy:function(){}
    });
    
    
    /**
     * 加强版Base
     * 事件代理:不需要用户自己去找dom元素绑定监听,也不需要用户去关心什么时候销毁。
     * 模板渲染:用户不需要覆盖render方法,而是覆盖实现setUp方法。可以通过在setUp里面调用render来达到渲染对应html的目的。
     * 单向绑定:通过setChuckdata方法,更新数据,同时会更新html内容,不再需要dom操作。
     */
    var RichBase = Base.extend({
        EVENTS: {},
        template: '',
        init: function(config){
            //存储配置项
            this.__config = config;
            //解析代理事件
            this._delegateEvent();
            this.setUp();
        },
        //循环遍历EVENTS,使用jQuery的delegate代理到parentNode
        _delegateEvent: function(){
            var self = this;
            var events = this.EVENTS || {};
            var eventObjs, fn, select, type;
            var parentNode = this.get('parentNode') || $(document.body);
            for (select in events) {
                eventObjs = events[select];
                for (type in eventObjs) {
                    fn = eventObjs[type];
                    parentNode.delegate(select,type,function(e){
                        fn.call(null,self,e);
                    })
                }
            }
        },
        //支持underscore的极简模板语法
        //用来渲染模板,这边是抄的underscore的。非常简单的模板引擎,支持原生的js语法
        _parseTemplate: function(str,data){
            /**
             * http://ejohn.org/blog/javascript-micro-templating/
             * https://github.com/jashkenas/underscore/blob/0.1.0/underscore.js#L399
             */
            var fn = new Function('obj',
                  'var p=[],print=function(){p.push.apply(p,arguments);};' +
                  'with(obj){p.push(\'' + str.replace(/[\r\t\n]/g, " ")
                                            .split("<%").join("\t")
                                            .replace(/((^|%>)[^\t]*)'/g, "$1\r")
                                            .replace(/\t=(.*?)%>/g, "',$1,'")
                                            .split("\t").join("');")
                                            .split("%>").join("p.push('")
                                            .split("\r").join("\\'") +
                  "');}return p.join('');");
            return data ? fn(data) : fn;
        },
        //提供给子类覆盖实现
        setUp: function(){
            this.render();
        },
        //用来实现刷新,只需要传入之前render时的数据里的key还有更新值,就可以自动刷新模板
        setChuckdata: function(key,value){
            var self = this;
            var data = self.get('__renderData');
            //更新对应的值
            data[key] = value;
            if (!this.template) return;
            //重新渲染
            var newHtmlNode = $(self._parseTemplate(this.template,data));
            //拿到存储的渲染后的节点
            var currentNode = self.get('__currentNode');
            if (!currentNode) return;
            //替换内容
            currentNode.replaceWith(newHtmlNode);
            self.set('__currentNode',newHtmlNode);
        },
        //使用data来渲染模板并且append到parentNode下面
        render: function(data){
            var self = this;
            //先存储起来渲染的data,方便后面setChuckdata获取使用
            self.set('__renderData', data);
            if (!this.template) return;
            //使用_parseTemplate解析渲染模板生成html
            //子类可以覆盖这个方法使用其他的模板引擎解析
            var html = self._parseTemplate(this.template,data);
            var parentNode = this.get('parentNode') || $(document.body);
            var currentNode = $(html);
            //保存下来留待后面的区域刷新
            //存储起来,方便后面setChuckdata获取使用
            self.set('__currentNode',currentNode);
            parentNode.append(currentNode);
        },
        destroy: function(){
            var self = this;
            //去掉自身的事件监听
            self.off();
            //删除渲染好的dom节点
            self.get('__currentNode').remove();
            //去掉绑定的代理事件
            var events = self.EVENTS || {};
            var eventObjs,fn,select,type;
            var parentNode = self.get('parentNode');
            for (select in events) {
                eventObjs = events[select];
                for (type in eventObjs) {
                    fn = eventObjs[type];
                    parentNode.undelegate(select,type,fn);
                }
            }
        },
        //可以使用get来获取配置项
        get: function(key){
            return this.__config[key]
        },
        //可以使用set来设置配置项
        set: function(key, value){
            this.__config[key] = value
        }
    });
    
    /**
     * (1)事件的解析跟代理,全部代理到parentNode上面。
     * (2)render抽出来,用户只需要实现setUp方法。如果需要模板支持就在setUp里面调用render来渲染模板
     * (3)可以通过setChuckdata来刷新模板,实现单向绑定。
     */
    var TextCount = RichBase.extend({
        //事件直接在这里注册,会代理到parentNode节点,parentNode节点在下面指定
        EVENTS: {
            //选择器字符串,支持所有jQuery风格的选择器
            'input':{
                //注册keyup事件
                keyup:function(self,e){
                    //单向绑定,修改数据直接更新对应模板
                    self.setChuckdata('count',self._getNum());
                }
            }
        },
        //指定当前组件的模板
        template: '<span id="contentCount"><%= count %>个字</span>',
        //私有方法
        _getNum: function(){
            return this.get('input').val().length || 0
        },
        //覆盖实现setUp方法,所有逻辑写在这里。最后可以使用render来决定需不需要渲染模板
        //模板渲染后会append到parentNode节点下面,如果未指定,会append到document.body
        setUp: function(){
            var self = this;
    
            var input = this.get('parentNode').find('#content');
            self.set('input', input);
    
            var num = this._getNum();
            //赋值数据,渲染模板,选用。有的组件没有对应的模板就可以不调用这步。
            self.render({
                count: num
            });
        }
    });
    
    $(function() {
        //传入parentNode节点,组件会挂载到这个节点上。所有事件都会代理到这个上面。
        new TextCount({parentNode: $("#container")});
    });
    展开全文
  • 前端组件思想

    万次阅读 多人点赞 2018-02-27 09:59:14
      所以,我近期觉得使用的框架有些多了,得静下心来沉淀沉淀—为什么要说写组件思想呢?因为我觉得它是伴随着前端发展的一个不可或缺的设计思想,目前几大流行框架也都非常好的实现了组件化,比如 React , Vue ...

    1.开篇

      先说说为什么要写这篇文章吧:不知从什么时候开始,大家相信前端摩尔定律:“每18个月,前端难度会增加一倍”。我并不完全认可这个数字的可靠性,但是这句话的本意我还是非常肯定的。

      是的,前端越来越简单了,但也越来越复杂了—简单到你可以用一个Githubstarter搭建一个框架,集成所有的全家桶,涵盖单元测试和功能测试,包括部署以及发布,甚至你开发时使用的UI库都让你写不了几行css;可又复杂到如此多的框架和库层出不穷,你还没来得及学会官网的doc呢,就已经有新的替代品了,那就更别提静下心去学习其中的源码或推敲原理了,跟不上脚步强行搬砖自然略显疲惫。

      正是前端飞速的发展使得前端看似简单,但若想深入却实属不易。顺便提一句,去年6月底,ES8已经发布了,没错,你没看错,是不感觉学不动了(开玩笑了,其实也没更新啥,不会再有ES5->ES6这种跨度了)。

      所以,我近期觉得使用的框架有些多了,得静下心来沉淀沉淀—为什么要说写组件化思想呢?因为我觉得它是伴随着前端发展的一个不可或缺的设计思想,目前几大流行框架也都非常好的实现了组件化,比如ReactVueReact之前用得算是比较多了,所以本篇我决定以Vue作为基础,去谈一谈前端模块化,组件化,可维护化的设计细想。

    2.什么是组件化

      组件化并不是前端所特有的,一些其他的语言或者桌面程序等,都具有组件化的先例。确切的说,只要有UI层的展示,就必定有可以组件化的地方。简单来说,组件就是将一段UI样式和其对应的功能作为独立的整体去看待,无论这个整体放在哪里去使用,它都具有一样的功能和样式,从而实现复用,这种整体化的细想就是组件化。不难看出,组件化设计就是为了增加复用性,灵活性,提高系统设计,从而提高开发效率。

    3.组件化的演变

      如果你对JS的理解还停留在jQuery的话(jQuery本身是一个非常优秀的库),那么请跳过此文(开个玩笑)。在那个时候,大部分的前端开发应该都是十分过程式的开发:操作DOM,发起ajax请求,刷新数据,局部更新页面。这样的动作反反复复,甚至在同一个项目里同样的流程也许还要重复,其实jQuery本身也有有自己模块化的设计,有时我们也会用到类似jQuery UI等不错的库来减少工作量,但请注意,这里我只认为它是模块化的。

      频繁操作DOM,过程式的开发方式的确不怎么样。这时开始流行MV*,比如MVC,前端开始学习后端的思想,讲业务逻辑,UI,功能,可以按照不同的文件去划分,结构清晰,设计明了,开发起来也不错。在这个基础上,又有了更加不错的MVVM框架,它的出现,更加简化了前端的操作,并且将前端的UI赋予了真实意义:你所看到的任何UI,应该都对应其相应的ViewModel,即你看到的view就是真实的数据,并且实现了双向绑定,只要UI改变,UI所对应的数据也改变,反之亦然。这的确很方便,但大部分的MVVM框架,并没有实现组件化,或者说没有很好的实现组件化,因为MVVM最大的问题就是:

    • 1.执行效率,只要数据改变,它下面所有监测数据上绑定的UI一般都会去更新,效率很低,如果你操作频繁,很可能调了几十万遍(有可能层次太深或者监测了太多的数据变化)。

    • 2.由于MVVM一般需要严格的ViewModel的作用域,因此大部分情况不支持多次绑定,或者只允许绑定一个根节点做为顶层DOM渲染,这就给组件化带来了困难(不能独立的去绑定部分UI)。

      而后,在此基础上,一些新的前端框架“取其精华,去其糟粕”,开始大力推广前端组件化的开发方式,从这一点来说,ReactVue是类似的。

      但从框架本身来说,ReactVue是完全不同的,前者是单向数据流管理设计的先驱,如果非让我做一个不恰当的比较的话,我觉得React+Redux是将MVC做到了极致(action->request, reducer->controller);而后者则是后起之秀,既吸取了React的数据流管理方式(Vue本身也可以用类似React的方式去开发,但难度比较大而已,不是很Vue)的设计理念,也实现了MVVM的双向绑定和数据监控(这应该是Vue的核心了),所以Vue是比较灵活的,可以按需扩展,它才敢称自己是渐进式框架。

    PS1: 并非讨论孰好孰坏,两大框架我都很喜欢,因为都非常好的实现了组件化。

    PS2: 上面有提到模块化,个人觉得如果更广义的来讲,模块化和组件化并不在一个维度上,模块化往往是代码的设计和项目结构的设计;但很多时候在狭义的场景中,比如一个很通用的功能,也完全能够将其组件化或模块化,这两者此时十分相似,最大的区别就是组件必定是模块化的,并且往往需要实例化,也应当赋有生命周期,而模块化往往是直接引用。

    4.如何实现组件化

      我就以搜房网为例(最近房价居高不下,各个大佬还在吹各种牛x说房价不久后将白菜价,我顺便mark下看以后打谁的脸)进行demo分析。随手截图如下:

    demo1.png

    4.1分析页面布局

    demo2.png

      从大体上来看,可以分为顶部搜索,中间内容展示。而中间内容又分为part1,2,3三种类型。由于篇幅问题,本文只分析part1,2,3

      每一个part中又可以分为header(title + link)和content(每个part不一样)

    demo3.png

    4.2初步开发

    如果没有经过任何设计,也许会出现下面的代码:

    
    <template>
      <div id="app">
        <div class="nav-search">...</div>
        <div class="panel">
          <div class="part1 left">
            <div>
              <span>万科城润园楼盘动态</span>
              <a href="">更多动态>></a>
            </div>
            <div>这里是每个part里面的具体内容</div>
          </div>
          <div class="part2 right">
            <div>
              <span>楼盘故事</span>
              <a href="">更多>></a>
            </div>
            <div>这里是每个part里面的具体内容</div>
          </div>
          <div class="part3">
            <div>
              <span>万科城润园户型</span>
              <a href="">二居(1)</a>
              <a href="">三居(4)</a>
              <a href="">四居(3)</a>
              <a href="">更多>></a>
            </div>
            <div>这里是每个part里面的具体内容</div>
          </div>
        </div>
      </div>
    </template>
    
    

    其中我省略了大部分的细节实现,实际代码量应该是这里的数倍。

    这段代码有几个问题:

    • 1.part1,2,3的结构很类似,有些许重复

    • 2.实际的代码量将会很多,很难快速定位问题,维护难度较大

    4.3化繁为简

    首先我们可以将part1,2,3进行分离,这样就独立出来三个文件,那么结构上将会非常清晰

    
    <template>
      <div id="app">
        <div class="nav-search">...</div>
        <div class="panel">
          <part1 />
          <part2 />
          <part3 /> 
      </div>
    </template>
    

    这有些类似将一个大函数逐步拆解成几部分的过程,不难想象part1,2,3中的代码,必然是适用性很差,确切的说只有这里能够引用。(但我看过很多项目的代码,就是这么干的,认为自己做了组件化,抽象还不错(@_@))

    4.4组件抽象

    仔细观察part1,2,3,正如我上面所说,它们其实是很相似的:都具有相同的外层border并附有shadow,都具有抬头和显示更多,各自内容部分暂不细说的话,这三个完全就是一模一样。

    如此,我们将具有高度相似的业务数据进行抽离,实现组件的抽象。

    part.vue

    
    <template>
      <div class="part">
        <div class="hearder">
          <span>{{ title }}</span>
          <a :href="linkForMore">{{ showMore || '更多>>' }}</a>
        </div>
        <slot name="content" />
      </div>
    </template>
    

    我们将part内可以抽象的数据都做成了props,包括利用slot去做模版,同时showMore || '更多>>'也考虑到了part1的link名字和其他几个part不一致的情况。

    这样一来app.vue就更加清晰化

    
    <template>
      <div id="app">
        <div class="nav-search">...</div>
        <div class="panel">
          <part
            title="万科城润园楼盘动态"
            linkForMore="#1"
            showMore="更多动态>>"
          >
            <div slot="content">这里是part1里面的具体内容</div>
          </part>
          <part
            title="楼盘故事"
            linkForMore="#2"
          >
            <div slot="content">这里是part2里面的具体内容</div>
          </part>
          <part
            title="万科城润园户型"
            linkForMore="#3"
          >
            <div slot="content">这里是part3里面的具体内容</div>
          </part>
      </div>
    </template>
    

    这里有几点需要说明一下:

    • 1.三个part中部分UI差异应该在哪里定义?

    比如三个part的宽度都不一样,并且part1和part2可能要需要进行浮动。

    必须要记住,这种差异并不是组件本身的,<part />的设计本身应该是无浮动并且宽度占100%的,至于占谁的100%,那就取决于谁引用它,至于向左还是向右浮动,同样也取决于引用它的container需要自己去定义,在上面的代码中,app.vue就应该是<part />的container,app想要的是一个左浮动且宽度为80%的part(part1),右浮动且宽度为20%的part(part2)和一个宽度为100%的part(part3),但它们都是part,所以应该由app来设置这些差异。

    记住这一点,将给你的抽象和扩展但来事半功倍的效果。

    • 2.三个part中的数据差异应该在哪里定义?

    比如part3中,其他的part只有一个类似更多>>的link,但是它却有多个(一居,二居...)。

    这里我推荐将这种差异体现在组件内部,设计方法也很多:

    比如可以将link数组化为links;

    比如可以将更多>>看作是一个default的link,而多余的部分则是用户自定义的特殊link,这两者合并组成了links。用户自定义的默认是没有的,需要引用组件时进行传入。

    总之,只要有数据差异化,就应该结合组件本身和业务上下文将差异合理的消除在内部。

    • 3.注意组件内数据的命名方式

    一个通用的,可扩展性高的组件,必然是有非常合理的命名的,比如观察一些组件库的命名,总会出现类似list,data,content,name,key,callback,className等名词,绝对不会出现我们系统中的类似iterationList, projectName等业务名词,这些名词和任一产品和应用都无关,它与自身抽象的组件有关,只表明组件内部的数据含义,偶尔也会代表其结构,所以只有这样,才能让用户通用。

    我们在组件化时,也需要遵循这种设计原则,但库往往是想让广大开发者通用,而我们可以降低scope,做到在整个app内通用即可。所以从这个角度来说,好的组件化必然有好的BA和UX,这是大实话

    5.写在最后

    你也许会认为这样抽象没有太大的必要性,毕竟它只是一段静态UI(pure component),但任何的设计都是基于一定的复杂度才衍生出来的,其实大部分情况下这种设计都是需要将功能逻辑代码也纳入其中的,并不光只是UI(如antd, element-ui等),我这里举的例子也相对比较简单,并不想有太多的代码。

    个人认为在一个大型前端项目中,这种组件化的抽象设计是很重要的,不仅增加了复用性提高了工作效率,从某种程度上来说也反应了程序员对业务和产品设计的理解,一旦有问题或者需要功能扩展时,你就会发现之前的设计是多么的make sense(毕竟需求总是在变哪)。

    展开全文
  • JavaScript组件设计思想(二)

    千次阅读 2017-01-02 20:08:16
    2016年3月份曾写过一篇文章《JavaScript组件设计思想》其中描述了一些实现组件化的方式,以及降低各组件耦合度的说明。其中“事件机制”不失为好的选择!经过了更多实践给我带来了更多的思考。 事件实现,日常开发中...
  • 组件思想 组件化是Vue.js中的重要思想 它提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用 任何的应用都会被抽象成一颗组件组件思想的应用: 有了组件化的思想,我们在...
  • Android视频:组件化封装思想实战 Android App,学习掌握Android组件化开发的方法。
  • 组件化的工作方式信奉独立、完整、自由组合。目标就是尽可能把设计与开发中的元素独立化,使它具备完整的局部功能,通过自由组合来构成整个产品。 从页面元素的可复用性角度考虑,我们将将组件按类型分为公众组件、...
  • WEB组件思想

    千次阅读 2017-06-13 10:40:37
    JS不同层的职责和API ...2 回调的绑定时间和组件实例化时间耦合在一起.(传入回调函数的时机是一致的,没有灵活性.) 关键的问题是 没有将事件抽象分离出来 连缀语法 发展 Promise优雅的组件
  • Javascript组件化开发设计思想

    千次阅读 2018-09-20 15:52:39
    项目中经常用web弹层组件-layer,其常见的代码如下:   使用的时候很方便,弹窗的宽高、内容、标题、关闭按钮等弹窗的状态我们都可以通过配置参数配置。layer弹层组件用同一套代码来满足不同的弹窗层表现的需求...
  • COM面向组件的编程思想

    千次阅读 2016-11-29 09:08:40
    浅析COM的思想及原理 ...它也代表了一种软件开发思想,那就是面向组件编程的思想。  一、COM编程思想--面向组件编程思想(COP)  众所周知,由C到C++,实现了由面向过程编程到面向对象编程的过渡。而COM的出
  • 前端组件思想及实践,黄思远,詹舒波,本文将通过总结前端组件思想,将前后端的耦合分离,封装重复性的操作,从而实现一种包含数据绑定、逻辑运算等众多功能的模块化组件
  • 传统测控软件有着重复利用率低、不易维护、开发周期长并且成本高等缺点,通用测控软件开发平台的出现为解决上述问题提供了一种崭新的方法,该平台基于组件思想,使用工厂、状态机等多种设计模式,在降低模块之间...
  • vue:组件思想的理解

    千次阅读 2020-03-23 00:50:49
    vue组件系统提供了一种抽象,让我们可以使用独立可复用的组件来构建大型应用,任意类型的应用界面都可以抽象为一个组件树。组件化能提高开发效率,方便重复使用(复用),简化调试步骤,提升项目可维护性,便于多人...
  • 初识HTML(五)---组件思想

    千次阅读 多人点赞 2020-11-10 19:44:03
    组件思想 本文我们来聊聊组件思想 好比我们的衣服 我们总不能一下子穿一套(内衣,内裤,袜子,棉裤,外套等等,我们总不能缝在一起穿吧,这样就只有这一套搭配了) 而且你做出来好看的衣服了,可以拿给别人去参考...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 208,120
精华内容 83,248
关键字:

组件的思想