canvas教程

深入浅出javascript的封装与继承

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

JS虽然是一个面向对象的语言,但是不是典型的面向对象语言。Java/C++的面向对象是object - class的关系,而JS是object - object的关系,中间通过原型prototype连接,父类和子类形成一条原型链。本文通过分析JS的对象的封装,再探讨正确实现继承的方式,然后

  JS虽然是一个面向对象的语言,但是不是典型的面向对象语言。Java/C++的面向对象是object - class的关系,而JS是object - object的关系,中间通过原型prototype连接,父类和子类形成一条原型链。本文通过分析JS的对象的封装,再探讨正确实现继承的方式,然后讨论几个问题,最后再对ES6新引入的类class关键字作一个简单的说明。

  JS的类其实是一个函数function,由于不是典型的OOP的类,因此也叫伪类。理解JS的类,需要对JS里的function有一个比较好的认识。首先,function本身就是一个object,可以当作函数的参数,也可以当作返回值,跟普通的object无异。然后function可以当作一个类来使用,例如要实现一个String类
 var MyString = function(str){
   this.content = str;
 };

 var name = new MyString("hanMeimei");
 var addr = new MyString("China");
 console.log(name.content + " live in " + addr.content);

  第一行声明了一个MyString的函数,得到一个MyString类,同时这个函数也是MyString的构造函数。第5行new一个对象,会去执行构造函数,this指向新产生的对象,第2行给这个对象添加一个content的属性,然后将新对象的地址赋值给name。第6行又去新建一object,注意这里的this指向了新的对象,因此新产生的content和前面是不一样的。

 上面的代码在浏览器运行有一点问题,因为这段代码是在全局作用域下运行,定义的name变量也是全局的,因此实际上执行var name = new MyString("")等同于window.name = new MyString(""),由于name是window已经存在的一个变量,作为window.open的第二个参数,可用来跨域的时候传数据。但由于window.name不支持设置成自定义函数的实例,因此设置无效,还是保持默认值:值为"[object Object]"的String。解决办法是把代码的运行环境改成局部的,也就是说用一个function包起来:

 (function(){
   var name = new MyString("hanMeimei");
   console.log(name.content); //正确,输出hanMeimei
  })();

所以从此处看到,代码用一个function包起来,不去污染全局作用域,还是挺有必要的。接下来,回到正题。

  JS里的每一个function都有一个prototype属性,这个属性指向一个普通的object,即存放了这个object的地址。这个function new出来的每个实例都会被带上一个指针(通常为__proto__)指向prototype指向的那个object。其过程类似于:

 var name = new MyString(); //产生一个对象,执行构造函数
 name.__proto__ = MyString.prototype; //添加一个__proto__属性,指向类的prototype(这行代码仅为说明)

 如下图所示,name和addr的__proto__指向MyString的prototype对象:



可以看出在JS里,将类的方法放在function的prototype里面,它的每个实例都将获得类的方法。 现在为MyString添加一个toString的方法:
  MyString.prototype.toString = function(){
    return this.content;
  };

 MyString的prototype对象(object)将会添加一个新的属性。



这个时候实例name和addr就拥有了这个方法,调用这个方法:
  console.log(name.toString()); //输出hanMeimei
  console.log(name + " lives in " + addr); //“+”连接字符时,自动调用toString,输出hanMeimei lives in China

  这样就实现了基本的封装——类的属性在构造函数里定义,如MyString的content;而类的方法在函数的prototype里添加,如MyString的toString方法。

  这个时候,考虑一个基础的问题,为什么在原型上添加的方法就可以被类的对象引用到呢?因为JS首先会在该对象上查找该方法,如果没有找到就会去它的原型上查找。例如执行name.toString(),第一步name这个object本身没有toString(只有一个content属性),于是向name的原型对象查找,即__proto__指向的那个object,发现有toString这个属性,因此就找到了。

要是没有为MyString添加toString方法呢?由于MyString实际上是一个Function对象,上面定义MyString语法作用等效于:
  //只是为了示例,应避免使用这种语法形式,因为会导致两次编译,影响效率
  var MyString = new Function("str", "this.content = str");

 通过比较MyString和Function的__proto__,可以从侧面看出MyString其实是Function的一个实例:

 console.log(MyString.__proto__); //输出[Function: Empty]
 console.log(Function.__proto__); //输出[Function: Empty]

MyString的__proto__的指针,指向Function的prototype,通过浏览器的调试功能,可以看到,这个原型就是Object的原型,如下图所示:


因为Object是JS里面的根类,所有其它的类都继承于它,这个根类提供了toString、valueOf等6个方法。因此,找到了Object原型的toString方法,查找完成并执行:
  console.log(name.toString()); //输出{ content: 'hanMeimei' }

到这里可以看到,JS里的继承就是让function(如MyString)的原型的__proto__指向另一个function(如Object)的原型。基于此,写一个自定义的类UnicodeString继承于MyString
  var UString = function(){ };

实现继承:
  UString.prototype = MyString.prototype; //错误实现

 

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

网友点评
m