关于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变量。上述的猜测是正确的,大功告成。

  

原文地址:https://www.cnblogs.com/ZJAJS/p/2680640.html