首页 > 代码库 > js学习总结----设计模式

js学习总结----设计模式

一、单例模式

  对象数据类型的作用

  把描述同一个事物(同一个对象)的属性和方法放在一个内存空间下,起到了分组的作用,这样不同事物之间的属性即使属性名相同 ,相互也不会发生冲突

  我们把这种分组编写代码的模式叫做"单例模式"

  在单例模式中我们把person1或者person2也叫做"命名空间"

        var person1 = {
            name:‘张三‘,
            age:18
        }

        var person2 = {
            name:"李四",
            age:34
        }            

  单例模式是一种项目开发中经常使用的模式,因为项目中我们可以使用单例模式来进行我们的"模块化开发"

  模块化开发:对于一个相对来说比较大的项目,需要多人协作的开发的,我们一般情况下会根据当前项目的需求划分成几个功能模块,每个人负责一部分,同时开发,最后把每个人的代码进行合并。

     var utils = {
            select:function(){

            }
        }
        var searchRender = {
            change:function(){
                utils.select();//调用外部的方法
                this.clickEven();
            },
            clickEven:function(){
                
            }
        }

二、工厂模式

  单例模式虽然解决了分组的作用,但是不能实现批量的生产,属于手工作业模式,所以出现了工厂模式

  把实现同一件事情的代码放到一个函数中,以后如果再想实现这个功能,不需要重新的编写这些代码了,只需要执行当前的函数即可->函数的封装

  低耦合高内聚:减少页面中的冗余代码,提高代码的重复利用率

    function createJsPerson(name,age){
            var obj = {};
            obj.name = name;
            obj.age = age;
            obj.writeJs = function(){
                console.log(‘ok‘)
            }
            return obj;
        }
        var p1 = createJsPerson("小王",12);

    js是一门轻量级的脚本“编程语言”(html+css不属于编程语言,属于标记语言)

    所有的编程语言都是面向对象开发的->类的继承、封装、多态。

    继承:子类继承父类中的属性和方法

    多态:当前方法的多种形态  后台语言中:多态包含重载和重写(重载:方法名相同,参数类型不同)

     js中不存在重载,方法名一样的话,后面的会把前面的覆盖掉,最后只保留一个

     js中有一个操作类似重载但是不是重载:我们可以根据传递的参数不一样,实现不同的功能。    

     function sum(num){
            if(typeof num === "undefined"){
                return 0
            }
            return num
        }
        sum(100);
        sum()

    重写:子类重写父类的方法

三、构造函数模式

  构造函数模式的目的就是为了创建一个自定义类,并且创建这个类的实例

  构造函数模式和工厂模式的区别:

  1、执行的时候

  普通函数执行->createJsPerson()

  构造函数模式->new CreateJsPerson() 通过new执行后,我们的CreateJsPerson就是一个类,而函数执行的返回值(p1)就是CreateJsPerson()这个类的一个实例

  2、在函数代码执行的时候

  相同:都是形成一个私有的作用域,然后形参赋值->预解释->代码从上到下执行(类和普通函数一样,他也有函数的一面)

  不同:在代码执行之前,不用自己在手动的创建obj对象了,浏览器会默认的创建一个对象数据类型的值,这个对象其实就是我们当前类的一个实例,接下来代码从上到下执行,以当前的实例为执行的主体(this代表的就是当前的实例),然后分别的把属性名和属性值赋值给当前的实例。浏览器会默认的把创建的实例返回。

    function CreateJsPerson(name,age){
       //浏览器默认创建的对象就是我们的实例p1 -> this
this.name = name; this.age = age; this.writeJs = function(){ console.log(‘ok‘) } }
    var p1 = new CreateJsPerson(‘张三‘,12);
    var p2 = new CreateJsPerson(‘李四‘,13);
    
    var res = CreateJsPerson("类似",7);//这样写不是构造函数模式执行而是普通的函数执行 由于没有写return 所以res = undefined
    并且CreateJsPerson这个方法中的this是window
 

 

  创建一个数组:

  var ary = [];//字面量方式

  var ary = new Array();//实例创建的方式->构造函数模式执行的方式

  不管哪一种方式ary都是Array这个类的实例

  注意:

  js中所有的类都是函数数据类型的,它通过new执行变成了一个类,但是它本身也是一个普通的函数。

  js中的所有的实例都是对象数据类型的

  在构造函数模式中,类中(函数体中)出现的this.xxx = xxx中的this是当前类的一个实例

  虽然p1和p2都是CreateJsPerson这个类的实例,所以都拥有writeJs这个方法,但是不同实例之间的方法是不一样的。

  在类中给实例增加的属性(this.xxx = xxx)属于当前实例的私有的属性,实例和实例之间是单独的个体,所以私有的属性之间是不相等的。

 

 

  扩展代码如下:

function Fn(){
            var num = 10;
            this.x = 100;
            this.getX = function(){
                //this->需要看getX执行的时候才知道
                console.log(this.x)
            }
        }
        var f1 = new Fn;
        var f2 = new Fn;
        f1.getX();// this 是f1 ->100
        var ss = f1.getX;
        ss()//this 代表window ->undefined
        console.log(f1.num)//undefined
        /*
        1、在构造函数模式中new Fn()执行,如果Fn不需要传递参数的话,后面的小括号可以省略
        2、this的问题:在类中出现的this.xxx = xxx中的this都是当前类的实例,而某一个属性值(方法),方法中的this需要看方法执行的时候,前面是否有".",才知道this是谁
        3、类有普通函数的一面,当函数执行的时候,var num其实只是当前形成的私有作用域的私有变量而已,它和我们的f1这个实例没有任何的关系,只有this.xxx = xxx 才相当于给f1这个实例增加私有的属性和方法,才和我们的f1有关系...
        4、在构造函数模式中,浏览器会默认的把我们的实例返回(返回的是一个对象数据类型的值);如果我们自己手动写了return 返回:
            返回的是一个基本数据类型的值,当前实例是不变的,例如:return 100。我们的f1还是当前Fn的实例
            返回的是一个引用数据类型的值,当前的实例会被自己返回的值替换掉,例如:return{name:‘李四‘}我们的f1就不在是Fn的实例了,而是对象{name:‘李四‘}
        5、检测某一个实例是否属于这个类->instanceof
            console.log(f1 instanceof Fn)//true
            console.log(f1 instanceof Array)//false
            console.log(f1 instanceof Object)//true 因为所有的实例都是对象数据类型的,而每一个对象数据类型都是Object这个内置类的一个实例,所以f1也是它的一个实例
            对于检测数据类型来说,typeof有自己的局限性,不能细分object下的对象、数组、正则...
        6、f1和f2都是Fn这个类的一个实例,都拥有x和getX两个属性,但是这两个属性是各自私有的属性,所以:
        console.log(f1.getX===f2.getX);//false
        7、in:检测某一个属性是否属于这个对象 attr in object ,不管是私有的属性还是公有的属性,只要存在,用in来检测都是true
           console.log(‘getX‘ in f1)// true
           hasOwnProperty:用来检测某一个属性是否为这个对象的"私有"属性,这个方法只能检测私有的属性
           console.log(f1.hasOwnProperty(‘getX‘))// true
     8、isPrototypeOf:函数用于指示对象是否存在于另一个对象的原型链中。如果存在,返回true,否则返回false 思考:检测某一个属性是否为该对象的"公有属性" function hasPubProperty(obj,attr){ return (attr in obj) && !obj.hasOwnProperty(attr) }
*/

四、原型链模式

  构造函数模式中拥有了类和实例的概念,并且实例和实例之间是相互独立开的->实例识别

  基于构造函数模式的原型模式解决了 方法或者属性公有的问题 ->把实例之间相同的属性和方法提取成公有的属性和方法 ->想让谁公有就把它放在CreateJsPerson.prototype上即可。

  1、每一个函数数据类型(普通函数、类)都有一个天生自带的属性:prototype(原型),并且这个属性是一个对象数据类型的值

  2、并且在prototype上浏览器天生给它加了一个属性constructor(构造函数),属性值是当前函数(类)本身

  3、每一个对象数据类型(普通的对象、实例、prototype...)也天生自带一个属性:__proto__(两个下划线),属性值是当前实例所属类的原型(prototype

     function Fn(){
            this.x = 100
       this.sum = function(){
       }
} Fn.prototype.getX = function(){ console.log(this.x); }      Fn.prototype.sum = function(){} var f1 = new Fn; var f2 = new Fn; console.log(Fn.prototype.constructor===Fn)// true

  下面结合图来理解这三句话

技术分享

  注意:

    object是JS中所有对象数据类型的基类(最顶层的类)

    1、f1 instanceof object ->true 因为f1通过__proto__可以向上级查找,不管有多少级,最后总能找到Object

    2、在Object.prototype上没有__proto__这个属性

    3、原型链模式

      f1.hasOwnProperty(‘x‘);// ->hasOwnProperty是f1的一个属性

      但是我们发现在f1的私有属性上并没有这个方法,那如何处理的呢?

      通过对象名.属性名的方式获取属性值的时候,首先在对象的私有属性上进行查找,如果私有中存在这个属性,则获取的是私有的属性值。如果私有的没有,则通过__proto__找到所属类的原型(类的原型上定义的属性和方法都是当前实例的公有的属性和方法),原型上存在的话,获取的是公有的属性值,如果原型上也没有,则继续通过原型上的__proto__继续向上查找,一直找到Object.prototype 为止...

    这种查找的机制就是我们的"原型链模式"

    f1.getX === f2.getX ->true

    f1.__proto__.getX ===f2.getX->true

    f1.getX === Fn.prototype.getX -> true

    f1.sum === f2.__proto__.sum ->false

    f1.sum === Fn.prototype.sum ->false

  在ie浏览器中,我们原型模式也是同样的原理,但是ie浏览器怕你通过__proto__把公有的修改,禁止我们使用__proto__

  扩展代码:

/*
        在原型模式中,this常用的有两种情况
        在类中this.xxx = xxx;this->当前类的实例
        某一个方法中的this->看执行的时候"."前面是谁this就是谁
            1、需要先确定this的指向(this是谁)
            2、把this替换成对应的代码
            3、按照原型链查找,得出结果
        */
        function Fn(){
            this.x = 100;
            this.y = 200;
            this.getY = function(){
                console.log(this.y)
            }
        }
        Fn.prototype = {
            constructor:Fn,
            y:300,
            getX:function(){
                console.log(this.x)
            },
            getY:function(){
                console.log(this.y)
            }
        }

        var f = new Fn
        f.getX();//100
        f.__proto__.getX();//undefined  console.log(f.__proto__.x)
        //在内置类的原型上扩展我们的方法: 数组去重
        Array.prototype.myUnique = function(){
            //this->ary
            var obj = {}
            for(var i = 0;i<this.length;i++){
                var cur = this[i];
                if(obj[cur] == cur){
                    this[i] = this[this.length-1];
                    this.length--;
                    continue
                }
                obj[cur] = cur;
            }
            obj = null;
            return this;//目的为了实现链式写法
        }
        //链式写法:执行完成数组的一个方法可以紧接着执行下一个方法
        //原理:ary为什么可以使用sort方法:因为sort是Array.prototype上的公有的方法,而数组ary是Array的一个实例,所以ary可以使用sort方法->数组才能使用我们Array原型上定义的属性和方法。
        //sort执行完成的返回值是一个排序后的"数组",可以执行reverse
        //reverse执行完成的返回值是一个排序后的"数组",可以执行pop
        //pop执行完成的返回值是删除的数据,不是一个数组了
        ary.sort(function(a,b){
            return a - b;
        }).reverse().pop();

  扩展-批量设置公有属性 

//1、起一个别名
        function Fn(){
            this.x = 100;
        }
        var pro = Fn.prototype;//把原来原型指向的地址赋值给pro,现在他们操作的是同一个内存空间
        pro.getX = function(){

        }
        pro.getY = function(){
            
        }
        pro.getZ = function(){
            
        }
        //2、重构原型对象的方法->自己新开辟一个堆内存,存储我们公有的属性和方法,把浏览器原来给Fn.prototype开辟的那个替换掉
        function Fn(){
            this.x = 100;
        }
        Fn.prototype = {
            constructor:Fn,
            a:function(){

            },
            b:function(){

            }
        }
        var f = new Fn()
        //1、只有浏览器天生给Fn.prototype开辟的堆内存里面才有constructor,而我们自己开辟的这个堆内存没有这个属性,这样constructor指向就不在是Fn而是Object了
        console.log(f.constructor)//没做任何处理之前
        //为了和原来的保持一致,我们需要手动的增加constructor的指向
        //2、用这种方式给内置类增加公有的属性
        //给内置类Array增加数组去重的方法
        Array.prototype.unique = function(){

        }
        Array.prototype = {
            constructor:Array,
            unique:function(){

            }
        }
        console.log(Array.prototype);
        //我们这种方式会把之前已经存在于原型上的属性和方法给替换掉,所以我们用这种方式修改内置类的话,浏览器会给屏蔽掉的
        //但是我们可以一个个的修改内置的方法,当我们通过下述方式在数组的原型上增加方法,如果方法名和原来内置的重复,会把内置的修改掉-->我们以后再内置类的原型上增加方法,命名都需要加特殊的前缀
        Array.prototype.sort = function(){
            console.log(‘ok‘)
        }

js学习总结----设计模式