web前端开发必懂之一:JS继承和继承基础总结

  首先,推荐一篇博客豪情的博客JS提高篇: http://www.cnblogs.com/jikey/p/3604459.html ,里面的链接全是精华, 一般人我不告诉他;

  我们会先从JS的基本的设计模式开始,由浅入深:

  工厂模式:因为使用用一个接口创建很多对象会产生大量的重复代码,为了解决这个问题,人们就开始使用工厂模式:

  

<!DOCTYPE html>
<html>
<head>
    <title></title>
    <meta charset="utf-8">
</head>
<body>
    <script>
        //最好所有的代码自己打一遍, 增加印象;
        function l ( arg ) {
            console.log( arg );
        };
    </script>

    <script>
        //工厂模式:因为使用用一个接口创建很多对象会产生大量的重复代码,为了解决这个问题,人们就开始使用工厂模式:
        function Person(hairs,face, eye) {
            var o = new Object();
            o.hairs = hairs;
            o.face = face;
            o.eye = eye;
            o.say = function(){
                console.log("say someting to me!");
            };
            return o;
        };
        //我们通过 Person(0,1,2)就可以创建一个包含特定信息的Person对象, 以后要生成对象直接执行Person然后给他传参数就好了;
        //比如我们要生成10个Person, 很方便很快;
        var c = 10;
        while( c-- ) {
            console.log(Person(c,c,c))
        };
    </script>
</body>
</html>

  


 

  构造函数模式:使用构造函数模式我们能少些更多代码,比如上面的工厂模式我们可以改写成: 

<!DOCTYPE html>
<html>
<head>
    <title></title>
    <meta charset="utf-8">
</head>
<body>
    <script>
        //最好所有的代码自己打一遍, 增加印象;
        function l ( arg ) {
            console.log( arg );
        };
    </script>

    <script>
        function Person(hairs, face, eye) {
            this.hairs = hairs;
            this.face = face;
            this.eye = eye;
        };
        //同样, 我们再生成10个小朋友
        var c = 10;
        while( c-- ) {
            console.log( new Person(c,c,c) );
        };

    </script>
</body>
</html>

 

  //知识点1: 那么工厂模式和构造函数模式有什么区别呢:

  /*
  * 没有显式地创建对象
  * 直接把属性和方法赋值给了this
  * 没有return语句
  * 构造函数的前面有一个new;
  * */

  // 知识点2:

  /*
  * 通过new的构造函数会经过四个阶段:
  * 1:创建一个新对象;
  * 2:把this赋值给这个新对象
  * 3:执行构造函数中的代码
  * 4:返回新对象
  * */

 


 

  原型是神马? 原型就是公用的方法或者属性,这么理解最简单, 当然:

      1、prototype本质上还是一个JavaScript对象;

      2、每个函数都有一个默认的prototype属性;

      3、通过prototype我们可以扩展Javascript的内建对象

  一些代码弄懂原型的本质:

<!DOCTYPE html>
<html>
<head>
    <title></title>
    <meta charset="utf-8">
</head>
<body>
    <script>
        //最好所有的代码自己打一遍, 增加印象;
        function l ( arg ) {
            console.log( arg );
        };
    </script>

    <script>
        function Memo() {};
        Memo.prototype.hehe = 1;
        Memo.prototype.shut = function() {
            console.log("miaomiao")
        };
        var m0 = new Memo();
        var m1 = new Memo();
        console.log(m0.shut === m1.shut); //ture, 因为m0的shut和m1的shut就是指向了Memo.prototype.shut这个方法,所以他们相等;
        //Object.getPrototypeOf会返回实例的原型;
        console.log(Object.getPrototypeOf(m0)) //输出了:Memo {hehe: 1, shut: function}
        console.log( Memo.prototype.isPrototypeOf(m0) )// 输出: true;

        //原型对象有一个很大的问题要非常注意 ==》》 原型的属性和方法是被共享的, 比如:
        Memo.prototype.shut = "W_W";
        l(m0.shut) //输出:W_W, 悲剧了吧, 本来原型上的shut是个方法, 被改成字符串以后, 实例上的shut也发生了改变;
        l(m1.shut) //输出:W_W;

        m0.__proto__.shut = 1111; //m0的__proto__指向了Memo.prototype.shut,__proto__在标准浏览器下面是不能枚举到的,但确实是存在的一个属性;
        l(m1.shut) //输出了1111
        //只要原型上的属性或者方法被改了, 实例上的也会发生改变;
    </script>

</body>

</html>

 

  知识点:Object.getPrototypeOf;

      Fn.prototype.isPrototypeOf( instance );

 


  好的, 现在再来了解一下constructor, 如果你已经知道constructor是什么的话,  这段略过, constructor是默认指向创建当前对象的构造函数, 但是这里面有一些坑要注意, 比如你的原型prototype被改了, 实例的constructor就变了 ,

<!DOCTYPE html>
<html>
<head>
    <title></title>
    <meta charset="utf-8">
</head>
<body>
    <script>
        //最好所有的代码自己打一遍, 增加印象;
        function l ( arg ) {
            console.log( arg );
        };
    </script>

    <script>
        var Fn = function(){
            //我是构造函数Fn;
        };
        l("Fn的constructor");
        l(Fn.prototype.constructor);
        /*输出function (){
         //我是构造函数Fn;
         } */

        var f = new Fn;
        l("f的constructor");
        l(f.constructor);
        /*输出;
         * function (){
         //我是构造函数Fn;
         }*/
        //当函数创建的时候默认就为prototype新建一个constructor指向自己;

        l(Fn.constructor === Function); //输出true, Function这个构造函数是Function;
        l(Fn.constructor) // 输出function Function() { [native code] } ,意思是Function这个构造函数 ;
        l(Fn.constructor === Fn.__proto__.constructor) //输出true; Fn的constructor实际上是指向Fn.__proto__的;

        //好的, 现在重新定义一个Fn;
        var Fn = function(){
            //我是构造函数Fn;
        };
        Fn.prototype = {};
        l(Fn.prototype.constructor)
        /*打印出了内部的代码, 为什么呢? 因为我们改变了Fn的原型, Fn的constructor指向了{}空对象的Contructor;
         * function Object() { [native code] }  */
    </script>
</body>
</html>

 


  大家一定要懂的是实例上的__proto__就是指向原型上的prototype, 这样会少走一些弯路,可以节约更多的时间用来看片, 你懂的;
  每一个函数新建的时候都有一个默认的prototype,  prototype这个对象上面默认有一个指向自己的constructor;
  prototype和__proto__的区别:__proto__是实例和Person.prototype之间的关系,而constructor是实例和Person之间的关系

<!DOCTYPE html>
<html>
<head>
    <title></title>
    <meta charset="utf-8">
</head>
<body>
    <script>
        //最好所有的代码自己打一遍, 增加印象;
        function l ( arg ) {
            console.log( arg );
        };
    </script>

    <script>
        var Fn = function() {};
        Fn.prototype.hehe = 1;
        Fn.prototype.lala = 2;
        var f = new Fn;
        l(f) //输出了Fn {hehe: 1, lala: 2} ;
        l(f.__proto__) //输出了Fn {hehe: 1, lala: 2} ;
        l(Fn.prototype === f.__proto__) //输出了true, 这里要懂的东西是实例上的__proto__这个属性指向了构造函数的prototype;
        l(f === f.__proto__) //输出false; 这里要懂的是f是new出来的实例;

        //因为f上面的hehe和lala都是继承的属性, 所以这里面的log并没有被输出;
        for(var p in f){
            l("输出所以属性:" + p); //输出所以属性:hehe demo.html:11;   输出所以属性:lala
            //过滤非私有属性;
            if( f.hasOwnProperty(p) )
                l("输出私有属性" + p); //因为f没有私有属性,所以这边没有log出来;
        };
    </script>
</body>
</html>

  


 

  有了上面的基础, 我们开始说说JS的面向对象和继承吧;
  

  1:组合使用构造器和原型模式, 这种模式是构造函数和原型混合的模式, 使用最广泛, 认同度也最高的一种模式, 也是最基础的模式;

  

<!DOCTYPE html>
<html>
<head>
    <title></title>
    <meta charset="utf-8">
</head>
<body>
    <script>
        //最好所有的代码自己打一遍, 增加印象;
        function l ( arg ) {
            console.log( arg );
        };
    </script>

    <script>
        function Duck( name ,word) {
            this.name = name;
            this.word = word;
        };
        Duck.prototype.say = function() {
            console.log( this.name+" say : " + this.word )
        };
        var duck = new Duck("nono","hehe");
        duck.say();
    </script>
</body>
</html>

 

   寄生构造模式; 听名字真的很玄乎..其实跟工厂模式一模一样的, 其实就是自定义的模型封装;

<!DOCTYPE html>
<html>
<head>
    <title></title>
    <meta charset="utf-8">
</head>
<body>
    <script>
        //最好所有的代码自己打一遍, 增加印象;
        function l ( arg ) {
            console.log( arg );
        };
    </script>

    <script>
        function Foxy(name , word) {
            var result = new Object();
            result.name = name;
            result.word = word;
            return result;
        };
        l( new Foxy("nono","say someting") ); //输出:Object {name: "nono", word: "say someting"} ;
        function Monkey( ) {
            var aResult = [];
            //技巧:通过apply把arguments保存到数组;
            aResult.push.apply(aResult, arguments);
            return aResult;
        };
        l( new Monkey("nono","dino","kite","sam") );  //打印出了这个:["nono", "dino", "kite", "sam"];
        //要注意的是使用寄生模式的返回的对象和构造函数一点关系都没有;
    </script>
</body>
</html>

 

 

  JS的原型继承, 继承是依赖于原型链的;那么JS原型链是什么呢:
  /* 这段话慢慢读, 从搞基3抄的,很重要, 最好自己看一看哇;
  * ECMAScript中描述了原型链的概念, 并将原型链作为实现继承的主要方法, 基本思想是利用引用类型继承另一个引用类型的属性和方法。
  * 简单回顾一下构造函数,原型和实例的关系:每一个函数都有一个原型对象, 每一个原型对象都有一个指向构造函数的指针,
  * 而实例包含了一个指向原型对象的内部(不可见的)指针。 那么我们让原型对象等于另一个类型的实例, 那么这个原型对象将会包含指向
  * 另一个原型对象的指针,如果另一个原型对象又是指向了别的原型的一个实例, 这样层层嵌套, 就形成了原型链;
  * */
  那么我们利用原型的原型链相互继承来写一个基本的例子:  

<!DOCTYPE html>
<html>
<head>
    <title></title>
    <meta charset="utf-8">
</head>
<body>
    <script>
        //最好所有的代码自己打一遍, 增加印象;
        function l ( arg ) {
            console.log( arg );
        };
    </script>

    <script>
        var Fn = function() {
            this.property = true;
        }
        Fn.prototype.getFnProperty = function() {
            console.log( this.property );
        };
        var SubFn = function() {
            this.subProperty = false;
        };
        //SubFn继承了Fn的实例
        SubFn.prototype = new Fn();
        //为实例添加额外的实例方法;
        SubFn.prototype.getSubProperty = function(){
            console.log(this.subProperty);
        };
        var subFn = new SubFn();
        subFn.getFnProperty(); //输出了true
        subFn.getSubProperty(); //输出了false
        /*现在subFn的constructor 是
         function () {
         this.property = true;
         };
         所以要修正SubFn.prototype.constructor = SubFn
         */
    </script>
</body>
</html>

 

  原型式继承第二个例子:

<!DOCTYPE html>
<html>
<head>
    <title></title>
    <meta charset="utf-8">
</head>
<body>
    <script>
        //最好所有的代码自己打一遍, 增加印象;
        function l ( arg ) {
            console.log( arg );
        };
    </script>

    <script>
        // 首先, 准备一个方法;
        var inherit = function(o) {
            if(!typeof o === "object")return;
            function F () {}
            F.prototype = o;
            F.prototype.constructor = F;
            return new F();
        };

        var Fn = function() {
            this.property = true;
        }
        Fn.prototype.getFnProperty = function() {
            console.log( this.property );
        };
        var SubFn = function() {
            this.subProperty = false;
        };
        //SubFn继承了Fn的实例
        SubFn.prototype = new Fn();
        //为实例添加额外的实例方法;
        SubFn.prototype.getSubProperty = function(){
            console.log(this.subProperty);
        };
        var subFn = new SubFn();
        
        //这个方法的内部, 临时创建了一个构造函数, 然后将传入的对象作为这个构造函数的原型, 最后返回一个临时的新实例;
        //ECMASscript 5 有新增了一个Object.create 效果和inherit一模一样, 它可以接收第二个参数,
        // 第二个参数要通过defineProperties的方式设置,会覆盖原型的属性, 比如:
        Object.create(subFn, {
            getFnProperty: {
                value:1
            }
        });
        var Fn = function() {};
        //如果我们inherit传对象;
        Fn.prototype = inherit( {0:0,1:1,2:2,3:3,4:4} );
        l( new Fn ) //==>Fn {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, constructor: function(){....} 继承了哦
        //如果我们给inherit传一个构造函数的实例;
        Fn.prototype = inherit( new SubFn );
        l(new Fn);
    </script>
</body>
</html>

  有时候看到原型的各种引用会尿失禁, 引用来引用去的,坑爹啊, 不说了去洗裤子了....

  技术分享

  

  寄生组合式继承  

  组合继承是JS的常用继承模式, 但是也有自己的不足, 组合继承最大的问题的无论是什么情况下, 都会两次调用超类的构造函数;
  一个是在创建子类原型的时候, 另一个是在子类构造函数的内部,  那么子类的原型会包含所有超类实例的全部属性
  寄生组合式继承就是为了解决子类原型包含所有超类实例全部属性这个问题而存在的;
  

<!DOCTYPE html>
<html>
<head>
    <title></title>
    <meta charset="utf-8">
</head>
<body>
    <script>
        //最好所有的代码自己打一遍, 增加印象;
        function l ( arg ) {
            console.log( arg );
        };
    </script>

    <script>
        var inherit = function(o) {
            if(!typeof o === "object")return;
            function F () {}
            F.prototype = o;
            F.prototype.constructor = F;
            return new F();
        };
        //首先要准备inheritPrototype方法;
        var util = util || {};
        util.inherit = inherit;
        util.inheritPrototype = function(subType, superType) {
            var _prototype = this.inherit( superType.prototype );
            _prototype.constructor = subType;
            subType.prototype = _prototype;
        };
        function F( name ) {
            this.name = name;
            this.type = "human";
            this.habits = ["dance","code"];
        };
        F.prototype.laugh = function() {
            console.log("heha!");
        };
        var InheritF = function() {
            F.apply( this, arguments );
        };
        util.inheritPrototype(InheritF, F);
        InheritF.prototype.letsGo = function() {
            l("1,2,3,4")
        };
        var nono = new InheritF("nono");
        nono.habits.push("read books");
        l(nono.habits)
        var nonono = new InheritF("nono");
        l( nonono.habits );
        //继承的方法千万种,万变不离其宗;
    </script>
</body>
</html>

 

  JS各种继承的库推荐, 可以加深印象:

  首先是:

    JS.Class 是一个mootools式的类工厂 基于 lunereaper<![[dawid.kraczkowski[at]gmail[dot]com]]>的项目进行修改, 让子类的实现更简洁;

<!DOCTYPE html>
<html>
<head>
    <title></title>
    <meta charset="utf-8">
</head>
<body>
    <script>
        JS = {};
        // 这个是库的源代码;
        JS.Class = function(classDefinition) {

            //返回目标类的真正构造器
            function getClassBase() {
                return function() {
                    //它在里面执行用户传入的构造器construct
                    //preventJSBaseConstructorCall是为了防止在createClassDefinition辅助方法中执行父类的construct
                    if (typeof this[‘construct‘] === ‘function‘ && preventJSBaseConstructorCall === false) {
                        this.construct.apply(this, arguments);
                    }
                };
            }
            //为目标类添加类成员与原型成员
            function createClassDefinition(classDefinition) {
                //此对象用于保存父类的同名方法
                var parent = this.prototype["parent"] || (this.prototype["parent"] = {});
                for (var prop in classDefinition) {
                    if (prop === ‘statics‘) {
                        for (var sprop in classDefinition.statics) {
                            this[sprop] = classDefinition.statics[sprop];
                        }
                    } else {
                        //为目标类添加原型成员,如果是函数,那么检测它还没有同名的超类方法,如果有
                        if (typeof this.prototype[prop] === ‘function‘) {
                            var parentMethod = this.prototype[prop];
                            parent[prop] = parentMethod;
                        }
                        this.prototype[prop] = classDefinition[prop];
                    }
                }
            }

            //其实就是这样的Base = function() {};别想太多了;
            var preventJSBaseConstructorCall = true;
            var Base = getClassBase();
            preventJSBaseConstructorCall = false;

            createClassDefinition.call(Base, classDefinition);

            //用于创建当前类的子类
            Base.extend = function(classDefinition) {

                //其实就是这样的Base = function() {};别想太多了;
                preventJSBaseConstructorCall = true;
                var SonClass = getClassBase();
                SonClass.prototype = new this();//将一个父类的实例当作子类的原型
                preventJSBaseConstructorCall = false;

                createClassDefinition.call(SonClass, classDefinition);
                SonClass.extend = this.extend;

                return SonClass;
            };
            return Base;
        };
    </script>
    <script>
        //这是实际案例;
        var Animal = JS.Class({
            construct: function(name) {
                this.name = name;
            },
            shout: function(s) {
                console.log(s);
            }
        });
        var animal = new Animal();
        animal.shout(‘animal‘); // animal

        var Dog = Animal.extend({
            construct: function(name, age) {
                //调用父类构造器
                this.parent.construct.apply(this, arguments);
                this.age = age;
            },
            run: function(s) {
                console.log(s);
            }
        });
        var dog = new Dog("dog", 4);
        console.log(dog.name);
        dog.shout("dog"); // dog
        dog.run("run"); // run
        console.log(dog.constructor + "")
        var Shepherd = Dog.extend({
            statics: {//静态成员
                TYPE: "Shepherd"
            },
            run: function() {//方法链,调用超类同名方法
                this.parent.run.call(this, "fast");
            }
        });
        console.log(Shepherd.TYPE);//Shepherd
        var shepherd = new Shepherd("shepherd", 5);
        shepherd.run();//fast
        var a = new Animal("xx");
        console.log(a.run);
    </script>
    </body>
</html>

 

  这款继承创建的作者是jQ的作者,你懂的, 不饶;

  https://github.com/html5crew/simple-inheritance

<!DOCTYPE html>
<html>
<head>
    <title></title>
    <meta charset="utf-8">
</head>
<body>
    <script>
        /* source: https://gist.github.com/shakyShane/5944153
         *
         * Simple JavaScript Inheritance for ES 5.1 ( includes polyfill for IE < 9 )
         * based on http://ejohn.org/blog/simple-javascript-inheritance/
         *  (inspired by base2 and Prototype)
         * MIT Licensed.
         */
        (function (global) {
            "use strict";

            if (!Object.create) {
                Object.create = (function () {
                    function F() {
                    }

                    return function (o) {
                        if (arguments.length !== 1) {
                            throw new Error("Object.create implementation only accepts one parameter.");
                        }
                        F.prototype = o;
                        return new F();
                    };
                })();
            }

            var fnTest = /xyz/.test(function () {
                /* jshint ignore:start */
                xyz;
                /* jshint ignore:end */
            }) ? /\b_super\b/ : /.*/;

            // The base Class implementation (does nothing)
            function BaseClass() {
            }

            // Create a new Class that inherits from this class
            BaseClass.extend = function (props) {
                var _super = this.prototype;

                // Instantiate a base class (but only create the instance,
                // don‘t run the init constructor)
                var proto = Object.create(_super);

                // Copy the properties over onto the new prototype
                for (var name in props) {
                    // Check if we‘re overwriting an existing function
                    proto[name] = typeof props[name] === "function" &&
                            typeof _super[name] === "function" && fnTest.test(props[name]) ?
                            (function (name, fn) {
                                return function () {
                                    var tmp = this._super;

                                    // Add a new ._super() method that is the same method
                                    // but on the super-class
                                    this._super = _super[name];

                                    // The method only need to be bound temporarily, so we
                                    // remove it when we‘re done executing
                                    var ret = fn.apply(this, arguments);
                                    this._super = tmp;

                                    return ret;
                                };
                            })(name, props[name]) :
                            props[name];
                }

                // The new constructor
                var newClass = function () {
                    if (typeof this.init === "function") {
                        this.init.apply(this, arguments);
                    }
                };


                // Populate our constructed prototype object
                newClass.prototype = proto;

                // Enforce the constructor to be what we expect
                proto.constructor = newClass;

                // And make this class extendable
                newClass.extend = BaseClass.extend;

                return newClass;
            };

            // export
            global.Class = BaseClass;
        })(this);
    </script>
    <script>
        var Fn = function() {};
        //对继承的插件进行引用;
        Fn.extend = window.Class.extend;
        Fn.prototype.lala = 1;
        Fn.prototype.say = function() {
            alert( this.lala );
        };
        var Foo = Fn.extend({
            dudu:2,
            init:function(){
                this.name="nono"
            },
            dayDudu:function(){
                alert(this.dudu);
            }
        })
    </script>
    </body>
</html>

 

  执行 new Foo打印出来对象结构如下:

技术分享

  吃饭去了, 爽歪歪;

 

 

 

  

  最后提供一些参考的链接:

    javascript 类属性、类方法、类实例、实例属性、实例方法、prototype、__proto__ 测试与小结: 

      http://www.cnblogs.com/mrsunny/archive/2011/05/09/2041185.html

     JS的构造函数:

      http://www.cnblogs.com/jikey/archive/2011/05/13/2045005.html

    浅析Javascript原型继承:

      http://blog.csdn.net/kittyjie/article/details/4380918

end

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。