canvas教程

深入浅出javascript的封装与继承(2)

字号+ 作者:H5之家 来源:H5之家 2015-11-11 09:06 我要评论( )

注意上面的继承方法是错误的,这样只是简单的将UString的原型指向了MyString的原型,即UString和MyString使用了相同的原型,子类UString增删改原型的方法,MyString也会相应地变化,另外一个继承MyString如AsciiStr

注意上面的继承方法是错误的,这样只是简单的将UString的原型指向了MyString的原型,即UString和MyString使用了相同的原型,子类UString增删改原型的方法,MyString也会相应地变化,另外一个继承MyString如AsciiString的类也会相应地变化。依照上文分析,应该是让UString的原型里的的__proto__属性指向MyString的原型,而不是让UString的原型指向MyString。也就是说,得让UString有自己的独立的原型,在它的原型上添加一个指针指向父类的原型:

 UString.prototype.__proto__ = MyString.prototype; //不是正确的实现

因为__proto__不是一个标准的语法,在有些浏览器上是不可见的,如果在Firefox上运行上面这段代码,Firefox会给出警告:

mutating the [[Prototype]] of an object will cause your code to run very slowly; instead create the object with the correct initial [[Prototype]] value using Object.create

合理的做法应该是让prototype等于一个object,这个object的__proto__指向父类的原型,因此这个object须要是一个function的实例,而这个function的prototype指向父类的原型,所以得出以下实现:
 Object.create = function(o){
   var F = function(){};
   F.prototype = o;
   return new F();
  };

  UString.prototype = Object.create(MyString.prototype);

 代码第2行,定义一个临时的function,第3行让这个function的原型指向父类的原型,第4行返回一个实例,这个实例的__proto__就指向了父类的prototype,第7行再把这个实例赋值给子类的prototype。继承的实现到这里基本上就完成了。

但是还有一个小问题。正常的prototype里面会有一个constructor指向构造函数function本身,例如上面的MyString:



这个constructor的作用就在于,可在原型里面调用构造函数,例如给MyString类增加一个copy拷贝函数:
 MyString.prototype.copy = function(){
  // return MyString(this.content); //这样实现有问题,下面再作分析
  return new this.constructor(this.content); //正确实现
 };

 var anotherName = name.copy();
 console.log(anotherName.toString()); //输出hanMeimei
 console.log(anotherName instanceof MyString); //输出true

问题就于:Object.create的那段代码里第7行,完全覆盖掉了UString的prototype,取代的是一个新的object,这个object的__proto__指向父类即MyString的原型,因此UString.prototype.constructor在查找的时候,UString.prototype没有constructor这个属性,于是向它指向的__proto__查找,找到了MyString的constructor,因此UString的constructor实际上是MyString的constuctor,如下所示,ustr2实际上是MyString的实例,而不是期望的UString。而不用constructor,直接使用名字进行调用(上面代码第2行)也会有这个问题。

 var ustr = new UString();
 var ustr2 = ustr.copy();
 console.log(ustr instanceof UString); //输出true
 console.log(ustr2 instanceof UString); //输出false
 console.log(ustr2 instanceof Mystring); //输出true

所以实现继承后需要加多一步操作,将子类UString原型里的constructor指回它自己:
  UString.prototype.constructor = UString;

在执行copy函数里的this.constructor()时,实际上就是UString()。这时候再做instanseof判断就正常了:
  console.log(ustr2 instanceof Ustring); //输出true

可以把相关操作封装成一个函数,方便复用。基本的继承核心的地方到这里就结束了,接下来还有几个问题需要考虑。

  第一个是子类构造函数里如何调用父类的构造函数,直接把父类的构造函数当作一个普通的函数用,同时传一个子类的this指针:
 var UString = function(str){
  // MyString(str); //不正确的实现
   MyString.call(this, str);
 };

 var ustr = new UString("hanMeimei");
 console.log(ustr + ""); //输出hanMeimei

  注意第3行传了一个this指针,在调用MyString的时候,这个this就指向了新产生的UString对象,如果直接使用第2行,那么执行的上下文是window,this将会指向window,this.content = str等价于window.content = str。

第二个问题是私有属性的实现,在最开始的构造函数里定义的变量,其实例是公有的,可以直接访问,如下:
  var MyString = function(str){
    this.content = str;
  };

  var str = new MyString("hello");
  console.log(str.content); //直接访问,输出hello

但是典型的面向对象编程里,属性应该是私有的,操作属性应该通过类提供的方法/接口进行访问,这样才能达到封装的目的。在JS里面要实现私有,得借助function的作用域:
  var MyString = function(str){
     this.sayHi = function(){
       return "hi " + str;
     }
  };

  var str = new MyString("hanMeimei");
  console.log(str.sayHi()); //输出hi, hanMeimei

 但是这样的一个问题是,必须将函数的定义放在构造函数里,而不是之前讨论的原型,导致每生成一个实例,就会给这个实例添加一个一模一样的函数,造成内存空间的浪费。所以这样的实现是内存为代价的。如果产生很多实例,内存空间会大幅增加,这个问题是不可忽略的,因此在JS里面实现属性私有不太现实,即使在ES6的class语法也没有实现。但是可以给类添加静态的私有成员变量,这个私有的变量为类的所有实例所共享,如下面的案例:
 var Worker;
 (function(){
   var id = 1000;
   Worker = function(){
     id++;
   };
   Worker.prototype.getId = function(){
     return id;
   };
  })();

 var worker1 = new Worker();
 console.log(worker1.getId()); //输出1001
 var worker2 = new Worker();
 console.log(worker2.getId()); //输出1002

上面的例子使用了类的静态变量,给每个worker产生唯一的id。同时这个id是不允许worker实例直接修改的。

 

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

相关文章
  • canvas的神奇用法 javascript技巧笔记 CFEI.NET

    canvas的神奇用法 javascript技巧笔记 CFEI.NET

    2017-04-30 12:00

  • Canvas学习:绘制矩形

    Canvas学习:绘制矩形

    2017-04-24 17:02

  • npm install canvas简明指南

    npm install canvas简明指南

    2017-04-23 18:02

  • 《JavaScript库EaselJS教程》(LYNDA.COM EASELJS FIRST LOOK)[光盘镜像]

    《JavaScript库EaselJS教程》(LYNDA.COM EASELJS FIRST LOOK)[光盘镜

    2017-04-22 17:00

网友点评
"