zoukankan      html  css  js  c++  java
  • 关于javascript中原型方法访问私有变量的探索

      今天闲来无事,突然想到一个关于javascript中原型方法访问私有变量的问题,代码示例如下:

     1     function Person() {
     2         var name ;
     3         var age ;
     4 
     5         this.setName = function(newName) {
     6             name = newName ;
     7         };
     8 
     9         this.getName = function() {
    10             return name ;
    11         };
    12 
    13         this.setAge = function(newAge) {
    14             age = newAge ;
    15         };
    16 
    17         this.getAge = function() {
    18             return age ;
    19         };
    20     }

      上面的代码声明了一个叫做Person的构造函数,在构造函数中会为每个实例化的对象提供四个公有方法,分别是setName、getName、setAge和getAge。这四个方法通过js的闭包机制来访问和修改局部变量name和age,同时由于函数作用域的问题,name和age这两个局部变量在构造函数外是不可见的,也就是说只有这四个函数能够访问到这两个局部变量,我们将这两个变量称为私有变量。

      为了进一步弄清楚这四个实例方法的作用,我写了如下的测试代码:

     1    var zhaojian = new Person() ;
     2     zhaojian.setName("zhaojian") ;
     3     console.log("The name of zhaojian is " + zhaojian.getName()) ;  //这一步输出zhaojian
     4 
     5     var epson= new Person() ;
     6     epson.setName("epson") ;
     7     console.log("The name of epson is " + epson.getName()) ;       //这一步输出epson
     8     console.log("The name of zhaojian is " + zhaojian.getName()) ;  //这一步输出zhaojian
     9 
    10 
    11     zhaojian.setAge(18) ;
    12     console.log("The age of zhaojian is " + zhaojian.getAge()) ;    //这一步输出18
    13     epson.setAge(28) ;
    14     console.log("The age of epson is " + epson.getAge()) ;          //这一步输出28
    15     console.log("The age of zhaojian is " + zhaojian.getAge()) ;    //这一步输出18

      由此可见,每一次调用构造函数实例化对象的时候都会生成一个闭包,每个闭包中都含有两个局部的私有变量name和age,因此每个对象实例的私有变量name和age都是不相同的。

      但是问题来了,如果我把其中的两个实例方法改成原型方法,那会出现什么情况呢?如下所示:

     1   function Person() {
     2         var name ;
     3         var age ;
     4 
     5         Person.prototype.setName = function(newName) {
     6             name = newName ;
     7         };
     8 
     9         Person.prototype.getName = function() {
    10             return name ;
    11         };
    12         
    13         this.setAge = function(newAge) {
    14             age = newAge ;
    15         };
    16 
    17         this.getAge = function() {
    18             return age ;
    19         };
    20     }    

      继续对上述代码调用之前的测试代码,得到结果如下:

     1    var zhaojian = new Person() ;
     2     zhaojian.setName("zhaojian") ;
     3     console.log("The name of zhaojian is " + zhaojian.getName()) ; //这一步输出zhaojian
     4 
     5     var epson= new Person() ;
     6     epson.setName("epson") ;
     7     console.log("The name of epson is " + epson.getName()) ;      //这一步输出epson
     8     console.log("The name of zhaojian is " + zhaojian.getName()) ; //注意,这一步现在输出的是epson
     9 
    10 
    11     zhaojian.setAge(18) ;
    12     console.log("The age of zhaojian is " + zhaojian.getAge()) ;   //这一步输出18
    13     epson.setAge(28) ;
    14     console.log("The age of epson is " + epson.getAge()) ;         //这一步输出28
    15     console.log("The age of zhaojian is " + zhaojian.getAge()) ;   //这一步还是输出18

      问题来了,如果去掉上述代码中的epson.setName("epson");一句,结果会发生什么改变呢?

     1    var zhaojian = new Person() ;
     2     zhaojian.setName("zhaojian") ;
     3     console.log("The name of zhaojian is " + zhaojian.getName()) ;  //这一步输出zhaojian
     4 
     5     var epson= new Person() ;
     6     //epson.setName("epson") ;
     7     console.log("The name of epson is " + epson.getName()) ;        //这一步输出undefined
     8     console.log("The name of zhaojian is " + zhaojian.getName()) ;  //这一步输出undefined
     9 
    10 
    11     zhaojian.setAge(18) ;
    12     console.log("The age of zhaojian is " + zhaojian.getAge()) ;    //这一步输出18
    13     epson.setAge(28) ;
    14     console.log("The age of epson is " + epson.getAge()) ;          //这一步输出28
    15     console.log("The age of zhaojian is " + zhaojian.getAge()) ;    //这一步还是输出18

      对象zhaojian和epson的getName函数返回的都是undefined,可是zhaojian之前确实是有调用过setName方法设置过name变量的值的啊,怎么就变成了undefined?为了弄清疑问,再加上一句测试代码看看:

     1    var zhaojian = new Person() ;
     2     zhaojian.setName("zhaojian") ;
     3     console.log("The name of zhaojian is " + zhaojian.getName()) ;  //这一步输出zhaojian
     4 
     5     var epson= new Person() ;
     6     //epson.setName("epson") ;
     7     zhaojian.setName("aaa") ;
     8     console.log("The name of epson is " + epson.getName()) ;        //这一步输出aaa
     9     console.log("The name of zhaojian is " + zhaojian.getName()) ;  //这一步输出aaa
    10 
    11 
    12     zhaojian.setAge(18) ;
    13     console.log("The age of zhaojian is " + zhaojian.getAge()) ;    //这一步输出18
    14     epson.setAge(28) ;
    15     console.log("The age of epson is " + epson.getAge()) ;          //这一步输出28
    16     console.log("The age of zhaojian is " + zhaojian.getAge()) ;    //这一步还是输出18

      诡异的现象出现了,调用epson对象的setName方法能够影响到zhaojian对象的name变量,调用zhaojian对象的setName方法同样也能够影响到zhaojian对象的name变量,但是两个对象的age变量之间却是井水不犯河水,相处得非常和谐,这是为什么呢?

      这里涉及到实例方法、原型方法和函数作用域的问题,我们知道实例方法会被每一个实例化的对象复制一份,即每个实例化后的对象都拥有一份独立的实例方法的代码体。而原型方法则是在对象的原型对象(prototype)上,原型方法的代码体只有一份,且为所有的对象实例所共享。因此在调用方法的时候,每个对象引用的都是不同的实例方法,而所有对象引用的都是同一个原型方法。至于函数作用域,在javascript中的函数作用域指的是函数的声明域而不是调用域,因此原型方法setName和getName引用的都是在其被声明的作用域内的name变量。那么如果有实例化多个对象的话,setName引用的是哪一个闭包中的name变量呢?对构造函数Person进行分析,我们可以进行猜测,每一次调用构造函数都会声明一次setName方法,也就是说每调用一次构造函数setName方法的声明域都会发生改变,且setName方法的声明域始终是在最后一次调用构造函数所形成的闭包之中,因此所有的对象实例在调用setName方法时引用的都是最后被实例化的对象的name变量。为了验证以上的猜测,我们给Person类增加一个getSelfName实例方法,如下所示:

    1     this.getSelfName = function() {
    2             return name ;
    3     };

      该实例方法返回的是当前对象的name变量,于是对测试代码再做一点修改,来验证我们的想法:

     1    var zhaojian = new Person() ;
     2     zhaojian.setName("zhaojian") ;
     3     console.log("The name of zhaojian is " + zhaojian.getName()) ;  //这一步输出zhaojian
     4 
     5     var epson= new Person() ;
     6     //epson.setName("epson") ;
     7     zhaojian.setName("aaa") ;
     8     console.log("The name of zhaojian itself is " + zhaojian.getSelfName() ) ;//输出zhaojian
     9     console.log("The name of epson is " + epson.getName()) ;        //输出aaa
    10     console.log("The name of zhaojian is " + zhaojian.getName()) ;  //输出aaa
    11     console.log("The name of epson itself is " + epson.getSelfName()) ;//输出aaa
    12 
    13     zhaojian.setAge(18) ;
    14     console.log("The age of zhaojian is " + zhaojian.getAge()) ;    //输出18
    15     epson.setAge(28) ;
    16     console.log("The age of epson is " + epson.getAge()) ;          //输出28
    17     console.log("The age of zhaojian is " + zhaojian.getAge()) ;    //输出18

      由此可见,在zhaojian对象调用了setName方法之后,epson对象的name变量被改变成了aaa,但是zhaojian对象的name变量的值还是zhaojian。因此原型方法setName所引用的是epson对象的name变量,而zhaojian对象的name变量在epson对象被实例化之后就被setName方法弃之不顾了,原型方法setName和getName所引用的始终都是最后一个被构造函数Person实例化的对象的name变量。上述的猜测是正确的,大功告成。

      

  • 相关阅读:
    关于Ubuntu中passwd、shadow、group等文件
    Android colors.xml 颜色列表
    【设计】线框图、原型和视觉稿的区别
    【设计】24款线框图相关工具及资源大放送
    【辅助工具】20款优秀的移动产品原型和线框图设计工具(二)
    【辅助工具】20款优秀的移动产品原型和线框图设计工具(一)
    GET RESTful With Python
    VRRP、Track与NQA联动配置举例(Master监视上行链路)
    静态路由、Track与NQA联动配置举例
    ROS-MikroTik-RouterOS-培训认证各种证书
  • 原文地址:https://www.cnblogs.com/ZJAJS/p/2680640.html
Copyright © 2011-2022 走看看