javascript框架设计之类工厂


Posted in Javascript onJune 23, 2015

类与继承在javascript的出现,说明javascript已经达到大规模开发的门槛了,在之前是ECMAScript4,就试图引入类,模块等东西,但由于过分引入太多的特性,搞得javascript乌烟瘴气,导致被否决。不过只是把类延时到ES6.到目前为止,javascript还没有正真意义上的类。不过我们可以模拟类,曾近一段时间,类工厂是框架的标配,本章会介绍各种类实现,方便大家在自己的框架中或选择时自己喜欢的那一类风格。

1.javascript对类的支持

在其它语言中 ,类的实例都要通过构造函数new出来。作为一个刻意模仿java的语言。javascript存在new操作符,并且所有函数都可以作为构造器。构造函数与普通的方法没有什么区别。浏览器为了构建它繁花似锦的生态圈,比如Node,Element,HTMLElement,HTMLParagraphElement,显然使用继承关系方便一些方法或属性的共享,于是javascript从其它语言借鉴了原型这种机制。Prototype作为一个特殊的对象属性存在于每一个函数上。当一个函数通过new操作符new出其“孩子”——“实例”,这个名为实例的对象就拥有这个函数的Prototype对象所有的一切成员,从而实现实现所有实例对象都共享一组方法或属性。而javascript所谓的“类”就是通过修改这个Prototype对象,以区别原生对象及其其它定义的“类”。在浏览器中,node这个类基于Object修改而来的,而Element则是基于Node,而HTMLElement又基于Element....相对我们的工作业务,我们可以创建自己的类来实现重用与共享。

function A(){

  }
  A.prototype = {
    aa:"aa",
    method:function(){
    }
  };
  var a = new A;
  var b = new A;
  console.log(a.aa === b.aa);
  console.log(a.method === b.method)

一般地,我们把定义在原型上的方法叫原型方法,它为所有的实例所共享,这有好也有不好,为了实现差异化,javascript允许我们直接在构造器内指定其方法,这叫特权方法。如果是属性,就叫特权属性。它们每一个实例一个副本,各不影响。因此,我们通常把共享用于操作数据的方法放在原型,把私有的属性放在特权属性中。但放于this上,还是让人任意访问到,那就放在函数体内的作用域内吧。这时它就成为名副其实的私有属性。

function A() {
    var count = 0;
    this.aa = "aa";
    this.method = function() {
      return count;
    }
    this.obj = {}
  }
  A.prototype = {
    aa:"aa",
    method:function(){

    }
  };
  var a = new A;
  var b = new A;
  console.log(a.aa === b.aa);//true 由于aa的值为基本类型,比较值
  console.log(a.obj === b.obj) //false 引用类型,每次进入函数体都要重新创建,因此都不一样。
  console.log(a.method === b.method); //false

特权方法或属性只是只是遮住原型的方法或属性,因此只要删掉特权方法,就能方法到同名的原型方法或属性。

delete a.method;
  delete b.method;
  console.log(a.method === A.prototype.method);//true
  console.log(a.method === b.method); //true

用java的语言来说,原型方法与特权方法都属性实例方法,在java中还有一种叫类方法与类属性的东西。它们用javascript来模拟也非常简单,直接定义在函数上就行了。

A.method2 = function(){} //类方法
  var c = new A;
  console.log(c.method2); //undefined

接下来,我们看下继承的实现,上面说过,Prototype上有什么东西,它的实例就有什么东西,不论这个属性是后来添加的,还是整个Prototype都置换上去的。如果我们将这个prototype对象置换为另一个类的原型,那么它就轻而易举的获得那个类的所有原型成员。

function A() {};
  A.prototype = {
    aaa : 1
  }
  function B() {};
  B.prototype = A.prototype;
  var b = new B;
  console.log(b.aaa); //=> 1;
  A.prototype.bb = 2;
  console.log(b.bb) //=> 2;

由于是引用着同一个对象,这意味这,我们修改A类的原型,也等同于修该了B类的原型。因此,我们不能把一个对象赋值给两个类。这有两种办法,

方法1:通过for in 把父类的原型成员逐一赋给子类的原型
方法2是:子类的原型不是直接由父类获得,先将父类的原型赋值给一个函数,然后将这个函数的实例作为子类的原型。

方法一,我们通常要实现mixin这样的方法,有的书称之为拷贝继承,好处就是简单直接,坏处就是无法通过instanceof验证。Prototype.js的extend方法就用来干这事。

function extend (des, source) { //des = destination
    for (var property in source)
      des[property] = source[property];
    return des;
  }

方法二,就在原型上动脑筋,因此称之为原型继承。下面是个范本

function A() {};
  A.prototype = {
    aa:function(){
      alert(1)
    }
  }
  function bridge() {

  };
  bridge.prototype = A.prototype;

  function B() {}
  B.prototype = new bridge();

  var a = new A;
  var b = new B;

  console.log(a == b) //false 证明成功分开原型
  console.log(A.prototype == B.prototype) //true 子类共享父类的原型方法
  console.log(a.aa === b.aa); //为父类动态添加新的方法
  A.prototype.bb = function () {
    alert(2)
  }
  //true,继承父类的方法
  B.prototype.cc = function (){
    alert(3)
  }
  //false 父类未必有子类的new实例
  console.log(a.cc === b.cc)
  //并且它能够正常通过javascript自带的验证机制instanceof
  console.log(b instanceof A) ;//true
  console.log(b instanceof B) ; //true

方法二能通过instanceof验证,es5就内置了这种方法来实现原型继承,它就是Object.create,如果不考虑第二个参数,它约等于下面的代码。

Object.create = function (o) {
    function F() {}
    F.prototype = o;
    return new F();
  }

上面的方法,要求传入一个父类的原型作为参数,然后返回子类的原型

不过,我们这样还是遗漏了一点东西——子类不只是继承父类的遗产,还应该有自己的东西,此外,原型继承并没有让子类继承父类的成员与特权成员。这些我们都得手动添加,如类成员,我们可以通过上面的extend方法,特权成员我们可以在子类构造器中,通过apply实现。

function inherit(init, Parent, proto){
    function Son(){
      Parent.apply(this, argument); //先继承父类的特权成员
      init.apply(this, argument); //在执行自己的构造器
    }
  }
  //由于Object.create是我们伪造的,因此避免使用第二个参数
  Son.prototype = Object.create(Parent.prototype,{});
  Son.prototype.toString = Parent.prototype.toString; //处理IEbug
  Son.prototype.valueOf = Parent.prototype.valueOf; //处理IEbug
  Son.prototype.constructor = Son; //确保构造器正常指向,而不是Object
  extend(Son, proto) ;//添加子类的特有的原型成员
  return Son;

下面,做一组实验,测试下实例的回溯机制。当我们访问对象的一个属性,那么他先寻找其特权成员,如果有同名就返回,没有就找原型,再没有,就找父类的原型...我们尝试将它的原型临时修改下,看它的属性会变成那个。

function A(){

  }
  A.prototype = {
    aa:1
  }
  var a = new A;
  console.log(a.aa) ; //=>1

  //将它的所有原型都替换掉
  A.prototype = {
    aa:2
  }
  console.log(a.aa); //=>1

  //于是我们想到每个实例都有一个constructor方法,指向其构造器
  //而构造器上面正好有我们的原型,javascript引擎是不是通过该路线回溯属性呢
  function B(){

  }
  B.prototype = {
    aa:3
  }
  a.constructor = B;
  console.log(a.aa) //1 表示不受影响

因此类的实例肯定通过另一条通道进行回溯,翻看ecma规范可知每一个对象都有一个内部属性[[prototype]],它保存这我们new它时的构造器所引用的Prototype对象。在标准浏览器与IE11里,它暴露了一个叫__proto__属性来访问它。因此,只要不动__proto__上面的代码怎么动,a.aa始终坚定不毅的返回1.

再看一下,new时操作发生了什么。

1.创建了一个空对象 instance
2.instance.__proto__ = intanceClass.prototype
3.将构造函数里面的this = instance
4.执行构造函数里的代码
5.判定有没有返回值,没有返回值就返回默认值为undefined,如果返回值为复合数据类型,则直接返回,否则返回this
于是有了下面的结果。

function A(){
    console.log(this.__proto__.aa); //1
    this.aa = 2
  }
  A.prototype = {aa:1}
  var a = new A;
  console.log(a.aa)
  a.__proto__ = {
    aa:3
  }
  console.log(a.aa) //=>2
  delete a. aa; //删除特权属性,暴露原型链上的同名属性
  console.log(a.aa) //=>3

有了__proto__,我们可以将原型设计继承设计得更简单,我们还是拿上面的例子改一改,进行试验

function A() {}
  A.prototype = {
    aa:1
  }
  function bridge() {}
  bridge.prototype = A.prototype;

  function B(){}
  B.prototype = new bridge();
  B.prototype.constructor = B;
  var b = new B;
  B.prototype.cc = function(){
    alert(3)
  }
  //String.prototype === new String().__proto__ => true
  console.log(B.prototype.__proto__ === A.prototype) //true
  console.log(b.__proto__ == B.prototype); //true 
  console.log(b.__proto__.__proto__ === A.prototype); //true 得到父类的原型对象

因为b.__proto__.constructor为B,而B的原型是从bridge中得来的,而bride.prototype = A.prototype,反过来,我们在定义时,B.prototype.__proto__ = A.prototype,就能轻松实现两个类的继承.

__proto__属性已经加入es6,因此可以通过防止大胆的使用

2.各种类工厂的实现。

上节我们演示了各种继承方式的实现,但都很凌乱。我们希望提供一个专门的方法,只要用户传入相应的参数,或按照一定简单格式就能创建一个类。特别是子类。

由于主流框架的类工厂太依赖他们庞杂的工具函数,而一个精巧的类工厂也不过百行左右

相当精巧的库,P.js

https://github.com/jiayi2/pjs

使用版:https://github.com/jiayi2/factoryjs

这是一个相当精巧的库,尤其调用父类的同名方法时,它直接将父类的原型抛在你面前,连_super也省了。

var P = (function(prototype, ownProperty, undefined) {
 return function P(_superclass /* = Object */, definition) {
  // handle the case where no superclass is given
  if (definition === undefined) {
   definition = _superclass;
   _superclass = Object;
  }

  // C is the class to be returned.
  //
  // When called, creates and initializes an instance of C, unless
  // `this` is already an instance of C, then just initializes `this`;
  // either way, returns the instance of C that was initialized.
  //
  // TODO: the Chrome inspector shows all created objects as `C`
  //    rather than `Object`. Setting the .name property seems to
  //    have no effect. Is there a way to override this behavior?
  function C() {
   var self = this instanceof C ? this : new Bare;
   self.init.apply(self, arguments);
   return self;
  }

  // C.Bare is a class with a noop constructor. Its prototype will be
  // the same as C, so that instances of C.Bare are instances of C.
  // `new MyClass.Bare` then creates new instances of C without
  // calling .init().
  function Bare() {}
  C.Bare = Bare;

  // Extend the prototype chain: first use Bare to create an
  // uninitialized instance of the superclass, then set up Bare
  // to create instances of this class.
  var _super = Bare[prototype] = _superclass[prototype];
  var proto = Bare[prototype] = C[prototype] = C.p = new Bare;

  // pre-declaring the iteration variable for the loop below to save
  // a `var` keyword after minification
  var key;

  // set the constructor property on the prototype, for convenience
  proto.constructor = C;

  C.extend = function(def) { return P(C, def); }

  return (C.open = function(def) {
   if (typeof def === 'function') {
    // call the defining function with all the arguments you need
    // extensions captures the return value.
    def = def.call(C, proto, _super, C, _superclass);
   }

   // ...and extend it
   if (typeof def === 'object') {
    for (key in def) {
     if (ownProperty.call(def, key)) {
      proto[key] = def[key];
     }
    }
   }

   // if no init, assume we're inheriting from a non-Pjs class, so
   // default to using the superclass constructor.
   if (!('init' in proto)) proto.init = _superclass;

   return C;
  })(definition);
 }

 // as a minifier optimization, we've closured in a few helper functions
 // and the string 'prototype' (C[p] is much shorter than C.prototype)
})('prototype', ({}).hasOwnProperty);

我们尝试创建一个类:

var Dog = P (function(proto, superProto){
    proto.init = function(name) { //构造函数
      this.name = name;
    }
    proto.move = function(meters){ //原型方法
      console.log(this.name + " moved " + meters + " m.")
    }
  });
  var a = new Dog("aaa")
  var b = new Dog("bbb"); //无实例变化
  a.move(1);
  b.move(2);

我们在现在的情况下,可以尝试创建更简洁的定义方式

var Animal = P (function(proto, superProto){
    proto.init = function(name) { //构造函数
      this.name = name;
    }
    proto.move = function(meters){ //原型方法
      console.log(this.name + " moved " + meters + " m.")
    }
  });
  var a = new Animal("aaa")
  var b = new Animal("bbb"); //无实例变化
  a.move(1);
  b.move(2);
  //...............
  var Snake = P (Animal, function(snake, animal){
    snake.init = function(name, eyes){
      animal.init.call(this, arguments); //调运父类构造器
      this.eyes = 2;
    }
    snake.move = function() {
      console.log('slithering...');
      animal.move.call(this, 5); //调运父类同名方法
    }
  });
  var s = new Snake("snake", 1);
  s.move();
  console.log(s.name);
  console.log(s.eyes);

私有属性演示,由于放在函数体内集中定义,因此安全可靠!

var Cobra = P (Snake, function(cobra){
    var age = 1;//私有属性
    //这里还可以编写私有方法
    cobra.glow = function(){ //长大
      return age++;
    }
  });
  var c = new Cobra("cobra");
  console.log(c.glow()); //1
  console.log(c.glow()); //2
  console.log(c.glow()); //3

以上所述就是本文的全部内容了,希望大家能够喜欢。

Javascript 相关文章推荐
解决extjs在firefox中关闭窗口再打开后iframe中js函数访问不到的问题
Nov 06 Javascript
FileUpload 控件 禁止手动输入或粘贴的实现代码
Apr 07 Javascript
JavaScript高级程序设计 读书笔记之八 Function类及闭包
Feb 27 Javascript
JavaScript var声明变量背后的原理示例解析
Oct 12 Javascript
jQuery中:radio选择器用法实例
Jan 03 Javascript
JS判断客服QQ号在线还是离线状态的方法
Jan 13 Javascript
最常用的jQuery表单验证(简单)
May 23 jQuery
es6+angular1.X+webpack 实现按路由功能打包项目的示例
Aug 16 Javascript
webpack构建换肤功能的思路详解
Nov 27 Javascript
Vue的elementUI实现自定义主题方法
Feb 23 Javascript
vue-cli 3.x 配置Axios(proxyTable)跨域代理方法
Sep 19 Javascript
利用JavaScript缓存远程窃取Wi-Fi密码的思路详解
Nov 05 Javascript
jQuery判断多个input file 都不能为空的例子
Jun 23 #Javascript
javascript框架设计之浏览器的嗅探和特征侦测
Jun 23 #Javascript
简述AngularJS相关的一些编程思想
Jun 23 #Javascript
javascript框架设计之种子模块
Jun 23 #Javascript
在JavaScript的AngularJS库中进行单元测试的方法
Jun 23 #Javascript
javascript框架设计之框架分类及主要功能
Jun 23 #Javascript
js的flv视频播放器插件使用方法
Jun 23 #Javascript
You might like
解析php安全性问题中的:Null 字符问题
2013/06/21 PHP
PHP实现带重试功能的curl连接示例
2016/07/28 PHP
PHP简单判断手机设备的方法
2016/08/23 PHP
学习ExtJS 访问容器对象
2009/10/07 Javascript
js 图片随机不定向浮动的实现代码
2013/07/02 Javascript
jquery滚动组件(vticker.js)实现页面动态数据的滚动效果
2013/07/03 Javascript
详解javascript高级定时器
2015/12/31 Javascript
prototype.js常用函数详解
2016/06/18 Javascript
js设置和获取自定义属性的方法
2016/10/20 Javascript
微信小程序 数据绑定详解及实例
2016/10/25 Javascript
JS 全屏和退出全屏详解及实例代码
2016/11/07 Javascript
提高Web性能的前端优化技巧总结
2017/02/27 Javascript
JavaScript实现隐藏省略文字效果的方法
2017/04/27 Javascript
在vue中使用Autoprefixed的方法
2018/07/27 Javascript
Node批量爬取头条视频并保存方法
2018/09/20 Javascript
vue scroll滚动判断的实现(是否滚动到底部、滚动方向、滚动节流、获取滚动区域dom元素)
2020/06/11 Javascript
vue 动态生成拓扑图的示例
2021/01/03 Vue.js
[01:23]2014DOTA2国际邀请赛 球迷无处不在Ti现场世界杯受关注
2014/07/10 DOTA
[02:57]DOTA2亚洲邀请赛小组赛第四日 赛事回顾
2015/02/02 DOTA
python中字典dict常用操作方法实例总结
2015/04/04 Python
Python开发之快速搭建自动回复微信公众号功能
2016/04/22 Python
深入理解python对json的操作总结
2017/01/05 Python
解决Shell执行python文件,传参空格引起的问题
2018/10/30 Python
Python cookie的保存与读取、SSL讲解
2020/02/17 Python
如何使用python传入不确定个数参数
2020/02/18 Python
基于python实现图片转字符画代码实例
2020/09/04 Python
Django自定义YamlField实现过程解析
2020/11/11 Python
收集的22款给力的HTML5和CSS3帮助工具
2012/09/14 HTML / CSS
Merchant 1948澳大利亚:新西兰领先的鞋类和靴子供应商
2018/03/24 全球购物
Etam俄罗斯:法国女士内衣和家居服网上商店
2019/10/30 全球购物
《草原》教学反思
2014/02/15 职场文书
外贸员简历中的自我评价
2014/03/04 职场文书
汽车技术服务与贸易专业求职信
2014/07/20 职场文书
为什么你写的height:100%不起作用
2021/05/10 HTML / CSS
python机器学习创建基于规则聊天机器人过程示例详解
2021/11/02 Python
解决Oracle数据库用户密码过期
2022/05/11 Oracle