zoukankan      html  css  js  c++  java
  • JS 学习笔记--13---原型

     练习中使用的是IE11,如果有错误之处,还请各位朋友多多指教。本文关于原型难以描述,故多用代码展示

      原型是JS中一个很重要的概念,也是JS中一个难点,语言上难以描述,原型对象的属性和方法叫做原型属性和原型方法,构造函数中的属性和方法叫做实例属性和实例方法,它们的区别就是:对于一个对象的多个实例之间,它们的实例属性和实例方法是各不一样的,前面的面向对象中已经证明,而他们的原型属性和原型方法是一模一样的,完全相等的

    构造函数方式声明原型对象:                                                                      

     1 //构造函数定义的成员变量时实例成员
     2     function Box(user,age){
     3         this.user=user;        //实例属性
     4         this.age=age;        
     5         this.run=function(){    //实例方法
     6             return this.user+" "+this.age+" "+"运行中...";
     7         }
     8     }
     9 
    10 //原型声明,构造函数中什么也不写,然后通过prototype对象来添加属性和方法,调用都是一样的
    11     function Box1(){};
    12     Box1.prototype.user='abc';    //通过prototype定义的叫做  原型属性
    13     Box1.prototype.age=22;        //下面这个方法叫做原型方法
    14     Box1.prototype.run=function(){ return this.user+" "+this.age+" "+"运行中..."; };
    15 
    16     var box=new Box1();
    17     alert(box.user);    // abc
    18     alert(box.age);        // 22
    19     alert(box.run());    // abc 22 运行中...
    20 
    21 //声明了两个对象   从下面的结果可以知道,这两个对象的引用不相等,但是他们中间的方法和属性是完全相等的  包括引用
    22     var box1=new Box();
    23     var box2=new Box();
    24     alert(box1.user);    //abc
    25     alert(box2.user);    //abc
    26     alert(box1.age);    //22
    27     alert(box2.age);    //22
    28         
    29     //如果是实例方法,不同的实例化,他们的方法的引用地址是不一样的,是唯一的
    30     //但是原型对象,不同的实例化,他们的方法的引用地址也是一样的,共享的,大家都一样
    31     alert(box1.run==box2.run);    // true
    32     alert(box1==box2);    //false

    1、  原型的作用主要作用

      用来共享一些属性和方法,我们每创建一个函数,都会自动的创建一个原型对象,原型对象是由函数下面的一个属性[__proto__]来指向的,这个属性是一个指针,它指向了原型对象的constructor属性,我们可以通过这两个属性就可以访问原型对象中的属性和方法了。

      constructor是一个构造属性,是可以获取构造函数的本身的,它的作用其实就是被原型指针[__proto__]定位,然后获取到构造函数的本身

    1    alert(Box.prototype);    //访问方法的属性prototype
    2     alert(box1.prototype);    //undefined        这个属性是一个对象,是访问不到的    
    3     alert(box1.__proto__);    //object Object      低版本的IE可能打印不出来
    4    alert(box1.constructor);    //function Box(){}; //构造属性
    5     alert(Box.constructor);        // function Function(){...};    Box 本身就是一个Function类型
    6     //alert(box1.prototype.constructor)    //error

      

      

    2、isPrototypeOf() 方法

      判断一个实例对象是否是指向了该构造函数的原型对象,可以用 isPrototypeOf() 方法来判断,如果指向了返回为true,没有则返回为false,一切对象都是继承自Object对象

    1     alert(Box.prototype.isPrototypeOf(box1)); //true    只要是实例化的,都指向了原型对象
    2     alert(Box.prototype.isPrototypeOf(box2)); //true
    3     alert(Object.prototype.isPrototypeOf(box1));//true  因为一切对象都是 Object 类型的,故指向了
    4     var box=new Object();
    5     alert(Box.prototype.isPrototypeOf(box)); // false    box对象是Object类型的对象,Box其实类似于是继承自Object类型
    

     

    3、原型模型的执行流程:遵循JS中的就近原则

      先查找构造函数实例里面的方法和属性,即先查找实例属性,如果有,立即返回值或者执行对应的方法

      如果实例属性或者实例方法中没有,则去原型对象中查找相应的属性和方法,有就返回或执行方法,如果没有就返回undefined或者报错

    1     var box1=new Box();
    2     var box2=new Box();    
    3     box1.name='kkk';    //给对象box1添加一个实例属性,
    4     alert(box1.name);    //访问的是box1的实例属性,box2是访问不到的,因为原型属性中也没有
    5     alert(box2.name);
    6     box1.user='jjj';    //其实是实例属性,并没有重写原型属性的值
    7     alert(box1.__proto__.user);//abc    访问原型属性中的值
    8     alert(box1.user);//jjj    访问的是实例属性中的值
    9     alert(box2.user);//abc    box2 中不存在实例属性 user  就返回的是原型属性,它访问不到box1中的实例属性,因为他们之间共享的只是原型属性和方法

    4、属性的删除和修改
      可以通过 delete 关键字来删除实例属性和原型属性,删除和修改原型属性可以通过两种方式:(1) 通过实例对象的指针修改:box1.__proto__.age; (2) 定义原型属性一样用构造函数修改 prototype: Box.prototype.age;

      构造函数中是改了就生效,不管何时声明的对象,而在后面的字面量形式中只有在声明实例对象之前该才有效(详细的见后面字面量形式创建中)

    1     delete box1.user;    //删除对象box1中的实例属性
    2     alert(box1.user);    //abc    因为前面删除了实例属性中的user属性,返回的就是原型属性中的user属性
    3     //delete box1.__proto__.age;    //通过这种方式可以删除原型对象中的属性,但是别这样弄,牵一发而动全身
    4     //delete Box.prototype.age;    //也是删除了原型对象中的属性 age
    5     alert(box2.age);    //undefined     因为前面删除了原型属性中的age属性
    6     box1.__proto__.age=33;    //修改了原型属性中的值
    7     alert(box2.age);    //33
    8     Box.prototype.age=44;    //修改了原型属性中的值
    9     alert(box2.age);    //44


    5、hasProperty() 方法和 in 操作符

      判断某个对象是否拥有某个实例属性,可以通过 hasOwnProperty() 方法来测试,有就返回true,否则返回false

      in 操作符可以判断对象中是否包含某个属性,不管这个属性是原型属性还是实例属性,包含则返回true

      可以通过 hasOwnProperty() 方法和 in 操作符 共同来判断某个对象是否包含某个原型属性
     

        var box1 = new Box();
        var box2 = new Box();
    
     1    box1.name='jjj';
     2     alert(box1.hasOwnProperty('name'));    //true
     3     alert(box2.hasOwnProperty('name'));    //false        box2 并没有实例属性name
     4     alert(box1.hasOwnProperty('user'));    //false        user 属性是原型属性,不是实例属性
     5 
     6     alert('name' in box1);    //false
     7     alert('user' in box1);    //true    原型属性
     8     box1.name='jjj';
     9     alert('name' in box1);    //true    实例属性
    10 
    11 //通过 hasOwnProperty() 方法和 in 操作符 共同来判断某个对象是否包含某个原型属性
    12 function checkPropo(object,element){    //判断的是在某个对象中的属性,故要将对象和属性传递过来
    13         if(!object.hasOwnProperty(element)){    //先判断是否存在实例属性,如果存在就返回false
    14             if(element in object){    //如果存在就返回true
    15                 return true;    
    16             }else{
    17                 return false;
    18             }
    19         }else{
    20             return false;
    21         }
    22         //上面的代码可以用一个表达式来表示:return !object.hasOwnProperty(element)&& (element in object);
    23     }
    24     
    25     alert(checkPropo(box1,'name'));    //false
    26     box1.name='name';
    27     alert(checkPropo(box1,'name'));    //false        虽然有属性 name,但是这是一个实例属性
    28     alert(checkPropo(box1,'user'));    //true        //原型属性 user 是存在的


    字面量形式创建原型对象:                                                      

     1     function Box(){};
     2     Box.prototype={        // 通过字面方法来创建原型属性和方法,这样有点封装的感觉
     3         user:'abc',
     4         age:123,
     5         run:function(){return this.user+" "+this.age+" 运行中...";}
     6     }
     7 
     8 //创建两个对象
     9     var box1=new Box();
    10     var box2=new Box();
    11     //运行结果是一样的
    12     alert(box1.run());    //abc 123 运行中...
    13     alert(box2.run());    //abc 123 运行中...
    14     alert(box1.run==box2.run);    //true


    6、字面量方式创建原型对象注意的问题一:构造属性的指向

      字面量创建的方式 用constructor 属性指向的是Object对象,而不是实例本身,但是构造函数方式创建的这相反;

      如果想让constructor指向实例[Box],可以采用强制指向的方式

     1     var box1 = new Box();
     2 
     3     alert(box1.constructor);    //function Object() { [native code] }
     4     alert(box1.constructor == Box);        //false
     5     alert(box1.constructor == Object);    //true
     6     alert(box1 instanceof Box);        //true
     7     alert(box1 instanceof Object);    //true
     8     alert(Box.constructor);    //function Function...
     9     alert(Box.prototype);    //object Object    //使用构造函数名(对象名)访问prototype
    10     alert(box1.__proto__);    //object Object    //使用对象实例访问prototype的指针
    11 
    12 // 如果想让constructor指向实例[Box],可以采用强制指向的方式
    13     function Box(){};
    14     Box.prototype={        
    15         constructor:Box,    //强制原型对象来指向Box
    16         user:'abc',
    17         age:123,
    18         run:function(){return this.user+" "+this.age+" 运行中...";}
    19     }
    20 
    21     //创建两个对象
    22     var box1=new Box();
    23     var box2=new Box();
    24 
    25     //强制constructor指向Box的时候,返回结果如下
    26     alert(box1.constructor == Box);        //true
    27     alert(box1.constructor == Object);    //false

      之所以出现上面的原因是因为:通过Box.prototype={..};方式创建的时候,都创建一个新的对象,而每次创建一个函数,都会同时创建它自己的prototype,那么这个新的对象也就会自动获取它自己的constructor属性,这样新对象的constructor属性重写了Box实例的constructor属性,因此会执行新的对象,而这个新的对象又没有指定构造函数,故默认的就是Object

    7、字面量形式创建原型对象的问题二:原型属性的重写

      原型的声明是有先后顺序的,所以,重写的原型会切断之前的原型,故应该注意避免此种情况的发生

     1 var box2=new Box();    
     2 Box.prototype={    //字面量方式不存在修改原型属性,直接是覆盖掉原来的,因为每次都是创建一个新的对象
     3     user:444    //这里不会保留之前原型的任何信息
     4                 //把原来的原型对象和构造函数对象之间的关系给切断了
     5 }
     6 //字面量方式创建,只要声明了,以后随便怎么重写原型对象,已经创建的实例对象的值不会改变
     7     var box1=new Box();    
     8 
     9     alert(box1.user);    // 444    重写中赋值为444
    10     alert(box1.age);    //undefined        因为第二次重写中没有这个属性
    11 
    12     alert(box2.age);//123    因为在对象的定义是发生在用字面量形式重写原型属性之前,故以后的原型属性的修改和box2无关


    9、通过原型模式扩展类型的方法

      原型对象不仅仅可以在自定义对象的情况下使用, 而 ECMAScript 内置的引用类型都可以使用这种方式,并且内置的引用类型本身也使用了原型。

      通过prototype来添加方法,访问的时候要用这种类型的变量点这个方法,和访问系统内置的方法是一样的,但是最好不要用这种方式来添加方法,因为可能会存在命名冲突的问题,特别是在代码量大的时候,容易照成命名冲突问题。

     1     String.prototype.addString=function(s){        //传递一个参数过来,
     2         return '【' + s + '】';
     3     }
     4 
     5     alert('abc'.addString('111'));// 【111】
     6 
     7     String.prototype.addString=function(){    //可以不进行传参,通过this来代表当前调用这个方法 的字符串
     8         return this+"被添加了!";
     9     }
    10 
    11     alert("abcd".addString());    // abcd被添加了!


    原型模式创建对象的缺点以及采用的方式:                          

      原型模式创建对象也有自己的缺点, 它省略了构造函数传参初始化这一过程, 带来的缺点就是初始化的值都是一致的。而原型最大的缺点就是它最大的优点,那就是共享。

      原型中所有的属性都能被很多实例共享,共享对于函数非常适合,对于包含基本值的属性页还可以,但是如果属性包含引用类型就一定存在问题,见代码中

     1 function Box(){};
     2     Box.prototype={
     3         constructor:Box,
     4         user:'abc',
     5         age:22,
     6         family:['哥哥','姐姐','妹妹'],
     7         run:function(){
     8             return this.user+" "+this.age+" 运行中...";
     9         }
    10     }
    11 
    12     //var box=new Box(123);    //缺点之一就是不能够传递参数
    13 
    14     var box1=new Box();    //缺点二就是原型的共享性,这也是它最大的优点
    15     alert(box1.family);    //哥哥,姐姐,妹妹
    16     box1.family.push('弟弟');    //在第一个实例后修改了引用类型,保持了共享,但本质上不希望它共享
    17     alert(box1.family);    //哥哥,姐姐,妹妹,弟弟
    18 
    19     var box2=new Box();
    20     alert(box2.family);    //哥哥,姐姐,妹妹,弟弟    共享了box1引用类型添加后的原型


    11、组合构造函数 + 原型模式

      这种方式能够很好的解决传参和引用共享的问题,是创建对象比较好的方法

     1 function Box(user,age){    //构造函数,这里面声明一些会变的属性和引用类型的属性
     2         this.user=user;
     3         this.age=age;
     4         this.family=['哥哥','姐姐','妹妹'];
     5     }
     6 
     7     Box.prototype={    //原型模式
     8         constructor:Box,
     9         run:function(){
    10             return this.user+" "+this.age+" 运行中...";
    11         }
    12     }
    13 
    14     //下面可以看出通过构造函数总写一些自己会变的属性等,即使值改变了也不会被共享出去
    15     var box1=new Box('abc',22);
    16     alert(box1.run());    // abc 22 运行中...
    17     alert(box1.family);    //哥哥,姐姐,妹妹    
    18     box1.family.push("弟弟");
    19     alert(box1.family);    //哥哥,姐姐,妹妹,弟弟
    20 
    21     var box2=new Box('jack',33);
    22     alert(box2.family);    //哥哥,姐姐,妹妹    并没有共享对象box1中的引用类型
    23     alert(box2.run());    //jack 33 运行中...        和box1 中的输出结果是不一样的


    11、动态原型模型

      将构造函数和原型模型封装在一起,也能够解决共享的问题,但是要注意两个问题,一是资源的浪费,还有就是要注意,不可以再使用字面量的方式重写原型,因为会切断实例和新原型之间的联系

      在下面的代码中,当第一次调用构造函数时,run()方法发现不存在,然后初始化原型。当第二次调用, 就不会初始化, 并且第二次创建新对象, 原型也不会再初始化了。 这样及得到了封装, 又实现了原型方法共享,并且属性都保持独立

    function Box(user,age){
            this.user=user;
            this.age=age;
            this.family=['哥哥','姐姐','妹妹'];
    
            if(typeof this.run != 'function'){//判断this.run是否存在,因为执行一次后类型返回值就为为function
                alert("原型初始化开始");//没有判断之前,每new一次Box都会执行一次,
                Box.prototype.clss='person';
                Box.prototype.run=function(){
                    return this.user+" "+this.age+" 运行中...";
                };
                alert("原型初始化结束");
            }
        }
    
        var box1=new Box('abc',22);    
        var box2=new Box('jack',33);
    //也可以判断原型中任意一个属性的类型返回值,是否等于undefined,若等于原型就还没有创建 //因为原型中的属性一般都是一开始就有特定的值的,除非故意赋值为undefined[这样没意义了] function Box(user,age){ this.user=user; this.age=age; this.family=["哥哥","姐姐","妹妹"]; if(typeof this.clss == "undefined"){ alert("原型初始化开始");//没有判断之前,每new一次Box都会执行一次, Box.prototype.clss="person"; Box.prototype.run=function(){ return this.user+" "+this.age+" 运行中..."; }; alert("原型初始化结束"); } } var box1=new Box('abc',22); var box2=new Box('jack',33);


    12、寄生构造函数

      寄生构造函数,其实就是工厂模式+构造函数模式。这种模式比较通用,但不能确定对象关系,所以,在可以使用之前所说的模式时,不建议使用此模式;在什么情况下使用寄生构造函数比较合适呢?假设要创建一个具有额外方法的引用类型。由于之前说明不建议直接 String.prototype.addstring,可以通过寄生构造的方式添加

        function myString(string){
            var str = new String(string);
            str.addString = function(){
                return this + ",被添加了!";//this 指的是对象str下的值string
            }  
            return str;  
        }
    
        var box = new myString("abcd");
        alert(box.addString());


    13、稳妥构造函数

      在一些安全的环境中, 比如禁止使用 this 和 new, 这里的 this 是构造函数里不使用 this,这里的 new 是在外部实例化构造函数时不使用 new。这种创建方式叫做稳妥构造函数。

     1     function Box(name, age) {
     2         var obj = new Object();
     3         obj.name = name;
     4         obj.age = age;
     5         obj.run = function () {
     6             return name + age + '运行中...';
     7         };
     8         return obj;
     9     }
    10 
    11     var box1 = Box('Lee', 100);
    12     alert(box1.run());
    13 
    14     var box2 = Box('Jack', 200);
    15     alert(box2.run());

    用的最多的应该是组合原型模式+构造函数以及动态原型模式

  • 相关阅读:
    SQL Server 简单事务学习
    C# TreeView,递归循环数据加载到treeView1中
    javascript;Jquery;获取JSON对象,无刷新分页,异步加载,异步删除,实例。
    jquery获取当前页面的URL信息
    java的数据类型相关知识点
    java 图书馆初级编写
    1.1 弹出窗口控制
    轮播图片切换(函数传参)
    商品计算
    js中的作用域
  • 原文地址:https://www.cnblogs.com/qigang/p/3526241.html
Copyright © 2011-2022 走看看