zoukankan      html  css  js  c++  java
  • 5.类工厂

    类与继承在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);//true
        console.log(a.method === b.method)//true
     

    一般地,我们把定义在原型上的方法叫原型方法,它为所有的实例所共享,这有好也有不好,为了实现差异化,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.prototype, proto) ;//添加子类的特有的原型成员
        extend(Son, Parent) ;//继承父类的类成员
        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
     

    JS.Class

    从它的设计来看,让是继承Base2,相似的类工厂还有mootools。
    Base2的base2.__prototyping, mootools的klass.$protyping。它创建子类时页不通过中间的函数断开双方的原型链,而是使用父类的实例来做子类的原型,这点实现的非常精巧。

    simple-inheritance

    作者为john Resig ,特点是方法链实现的十分优雅,节俭。

    体现javascript的灵活性的库 def.js

    如果有什么库能体现javascript的灵活性,此库肯定名列前茅。它试图在形式上模拟Ruby的继承形式。让使用过ruby的人一眼看出,那个是父类,那个是子类。

    下面就是Ruby的继承示例:

        class child < Father
        # 略
        end

    def.js能做到这个程度

     
        def("Animal")({
            init:function(name){
                this.name = name;
            },
            speak:function(text){
                console.log('this is a' + this.name)
            }
        });
        var animal = new Animal("Animal");
        console.log(animal.name)
    
        def('Dog') < Animal({
            init:function(name,age){
                this._super();//魔术般的调运了父类
                this.age = age;
            },
            run:function(s){
                console.log(s)
            }
        });
        var dog = new Dog('wangwang');
        console.log(dog.name); //wangwang
    
        //在命名空间上创建子类
        var namespace = {};
        def(namespace,"Shepherd") < Dog({
            init:function(){
                this._super();
            }
        });
        var shepherd = new namespace.Shepherd("Shepherd")
        console.log(shepherd.name); 
     

    3.es5属性描述符对oo库的冲击

    es5最受人瞩目的升级是为对象引入属性描述符,让我们对属性有了更精细的控制,如这个属性是否可以修改,是否可以在for in中循环出来,是否可以删除。这些新增的API都集中定义在Object下,基本上除了Object.keys这个方法外,其它新API,旧版本的IE都无法模拟。于是新的API,基本很少有讲解的,我们在这里稍微解读下:

    Obejct提供以下几种新方法。

    Object.keys
    Object.getOwnPropertyNames
    Object.getPrototypeOf
    Object.defineProperty
    Object.defineProperties
    Object.getOwnPropertyDescriptor
    Object.create
    Object.seal
    Object.freeze
    Object.preventExtensions
    Object.isSealed
    Object.isFrozen
    Object.isExtensible

    其中,除了Object.keys外,旧版本的IE都无法模拟这些新API。旧版式的标准浏览器,可以用__peototype__实现Object.getPrototypeOf,结合__defineGetter__与defineSetter__来模拟Object.defineProperty。

    Obejct.keys用于收集当前对象的可遍历属性(不包括原型链上的)以数组形式返回。

    Object.getOwnPropertyNames用于收集当前对象不可遍历属性与可遍历属性,以数组形式返回。

     
        var obj = {
            aa : 1,
            toString : function() {
                return "1"
            }
        }
        if (Object.defineProperty && Object.seal) {
            Object.defineProperty(obj,"name",{
                value:2
            })
        }
        console.log(Object.getOwnPropertyNames(obj)); //=> ["aa", "toString", "name"]
        console.log(Object.keys(obj));//=> ["aa", "toString"]
    
        function fn(aa, bb){};
        console.log(Object.getOwnPropertyNames(fn));// => ["length", "name", "arguments", "caller", "prototype"]
        console.log(Object.keys(fn));//[]
        var reg = /w{2,}/i;
        console.log(Object.getOwnPropertyNames(reg)); //=> ["source", "global", "ignoreCase", "multiline", "lastIndex"]
        console.log(Object.keys(reg));//[]
     

    Object.prototypeOf返回参数对象内部属性[[Prototype]],它在标准浏览器中一直使用一个私有属性__proto__获取(IE9 10,opera都没有)。需要补充一下,Object的新API(除了Object.create外)有一个统一的规定,要求第一个参数不能为数字 ,字符串,布尔,null,undefeind这五种字面量,否则抛出TypeError异常。

        console.log(Object.getPrototypeOf(function(){}) == Function.prototype ); //=>true
        console.log(Object.getPrototypeOf({}) === Object.prototype);//=>true

    Object.definePrototype暴露了属性描述的接口,之前许多内建属性都是由JavaScript引擎在属下操作。如,for in循环为何不能遍历出函数的arguments、length、name等属性名,delete window.a为何返回false. 这些现象终究有个解释。它一共涉及六个可组合的配置项。
    是否可重写writable,当前值value
    读取时内部调用的函数set
    写入时内部调用函数get,
    是否可遍历enumerable,
    是否可以再次改动这些配置项configurable.

    比如我们随便写个对象

    var obj = {x:1}

    有了属性描述符,我们就清楚它在底下做的更多细节,它相当于es5的这个创建对象的式子:

     
        var obj = Object.create(Object.prototype,{
            x : {
                value : 1,
                writable : true,
                enumerable : true,
                configurable : true
            }
        })
     

    效果对比es3和es5,就很快明白,曾经的[[ReadOnly]] , [[DontEnum]], [[DontDlelete]]改成[[writable]], [[enumerable]],[[Configurable]]了。因此,configurable还有兼顾能否删除的职能。

    这六个配置项将原有的本地属性拆分为两组。数据属性与访问器属性。我们之前的方法可以像数据属性那样定义。

    es3时代,我们的自定义类的属性可以统统看做是数据属性。

    像DOM中的元素节点的 innerHTML innerText cssText 数组的length则可归为访问器属性,对它们赋值不是单纯的赋值,还会引发元素其它功能的触发,而取值不一定直接返回我们之前给予的值。

    数据的属性有1、2、5、6这四个配置项,访问器有3、4、5、6这四个配置项、如果你设置了value与writable,就不能设置set,get,反之亦然。如果没有设置。2,3,4默认为false。第1,5,6项默认为false.

     关于对象属性特征,更多请参阅http://www.cnblogs.com/ahthw/p/4272663.html 第7小节:7.属性的特征

     
    var obj = {};
        Object.defineProperty(obj,"a",{
            value: 37,
            writable :true,
            enumerable :true,
            configurable: true
        });
    
        console.log(obj.a) //=> 37;
        obj.a = 40;
        console.log(obj.a) //=>40
        var name = "xxx";
        for(var i in obj){
            name = i
        }
        console.log(name);//=> a
    
        Object.defineProperty(obj,"a",{
            value:55,
            writable:false,
            enumerable:false,
            configurable:true
        })
    
        console.obj(obj.a);//=>55
        obj.a = 50;
    
        console.log(obj.a);//55
        name = "b";
        for (var i in obj){
            name = i
        }
        console.log(name); //b
     

    Object.defineProperties就是Object.defineProperty的加强版,它能一下子处理多个属性。因此,如果你能模拟Object.defineProperty,它就不是问题。

     
        if (typeof Object.defineProperties !== 'function'){
            Object.defineProperties = function(obj, descs){
                for(var prop in descs) {
                    if (descs.hasOwnProperty(porop)){
                        Object.defineProperty(obj, prop, descs[prop]);
                    }
                }
                return obj;
            }
        }
     

    使用示例

     
        var obj = {};
        Object.defineProperties(obj, {
            "value":{
                value :true,
                writable:false,
            },
            "name":{
                value:"John",
                writable:false
            }
        });
        var a = 1;
        for (var p in obj){
            a = p
        };
        console.log(a);// 1
     

    Object.getOwnPropertDescriptor用于获得某对象的本地属性的配置对象。其中,configurable,enumerable肯定包含其中。视情况再包括value,wirtable或set,get.
    ....
    Object.preventExtensions,它是三个封锁对象修改的API中程度最轻的,就是阻止添加本地属性,不过如果本地属性都被删除了,也无法再加回来。以前javascript对象的属性都是任意添加的,删除,修改其值。如果它原型改动。我们访问它还会有意外的惊喜。

     
    var a = {
            aa: "aa"
        }
        Object.preventExtensions(a)
        a.bb = 2;
        console.log(a.bb);//=> undefined
        a.aa = 3; 
        console.log(a.aa); //=>3 允许修改原有的属性
        delete a.aa;
        console.log(a.aa); //=> undefined 但允许它删除已有的属性
    
        Object.prototype.ccc = 4;
        console.log(a.ccc); //4 不能阻止添加原型属性
        a.aa = 5;
        console.log(a.aa); //=> dundeined 不吃回头草,估计里边是以白名单的方式实现的
     

    Object.seal比Object.preventExtensions更严格,它不准删除已有的本地属性,内部实现就是遍历一下,把本地属性的configurable改为false

     
    var a = {
            a : "aa"
        }
        Object.seal(a)
        a.bb = 2;
        console.log(a.bb); // =>undefined添加本地属性失败
        a.aa = 3;
        console.log(a.aa); //3 允许修改已有的属性
        delete a.aa;
        console.log(a.aa) ;//=>3 但不允许删除已有的属性
     

    Object.freeze无疑是最专制的(因此有人说过程式程序很专制,OO程序则自由些,显然道格拉斯的ecma262v5想把javascript引向前者),它连原有的本地属性也不让修改了。内部的实现就是遍历一下,把每个本地属性的writable也改成false.

     
        var a = {
            aa : "aa"
        };
        Object.freeze(a);
        a.bb = 2;
        console.log(a.bb) //undefined 添加本地属性失败
        a.aa =3;
        console.log(a.aa) //aa 不允许它修改已有的属性
        delete a.aa;
        console.log(a.aa); //aa 不允许删除已经有的属性
     

    (isPainObject用于判定目标是不是纯净的javascript对象,且不是其它自定义类的实例。用法与prototype.js的class.create一样,并参照 jQuery UI提供了完美的方法链与静态成员的继承。)

    总结:es5对javascript对象产生深刻的影响,Object.create让原型继承更方便了,但在增添的字类的专有原型成员或类成员时,如果它们的属性enumerable为false,单纯的for in循环已经不管用了,完美就要用到Object.getOwnPropertyNames。另外,访问器属性的复制只有通过Object.getOwnPropertyDescriptpor与Object.defineProperty才能完成。

  • 相关阅读:
    python Database Poll for SQL SERVER
    SQLAlchemy表操作和增删改查
    flask动态url规则
    flask配置管理
    一个Flask运行分析
    Function Set in OPEN CASCADE
    Happy New Year 2016
    Apply Newton Method to Find Extrema in OPEN CASCADE
    OPEN CASCADE Multiple Variable Function
    OPEN CASCADE Gauss Least Square
  • 原文地址:https://www.cnblogs.com/wingzw/p/7360224.html
Copyright © 2011-2022 走看看