JavaScript高手转正学习(2)-原型的理解

时间: 2015-11-16 00:00 栏目: JavaScript 浏览: 4144 赞: 1 踩: 0 字体:

以下为本篇文章全部内容:

大家好,我叶子,接下来准备讲一讲 Js的原型和基于原型的一些扩展。

可能很多人多 JS的原型理解 仅限于 对 prototype 的扩展。

大家都知道 我们的JS 中有  字符串 String 数字 Number  数组 Array 对象 Object 等 类型。

比如 我们 有时候 会这样用 

   var s='1a';
   alert(s.length);
   或者
   alert(s.toUpperCase());

那么 这个 length 和 toUpperCase() 是从哪里来的?它不是字符串吗?怎么会有 这些属性或者方法呢?
在看我们的PHP 字符串 就是字符串 一些符号的集合。并没有相应的方法或者属性。

当我们打印

console.dir(String.prototype);

blob.png
发现 这些属性都是来自这个String 中的 prototype
当然 这个是 JS引擎为我们加上去的~~ 在我们的数据中凡是类型数据 都会有这个属性 prototype 而这个属性 是一个对象集合。
在js 中 如果我们对 类型的实例调用一个方法时! 如果改实例数据中并没有对应的方法 那么它将会去寻找这个 prototype 中是否有对应的方法。

这样 当我们调用  

s.toUpperCase()

时  因为s本身是一种数据结构 这个结构并不是对象 而是一种内置的实例数据,所以也没有 toUpperCase() 方法可用,而这个方法就在 String的prototype 中。

那么 如果说我们添加或者改变这个 prototype  如下


 String.prototype.sayhello=function(){
     alert(this+' '+'hello');
 }
 
 var s="wj008";
 s.sayhello();

我们将为这个 字符串追加了一个方法;

我们还可以修改相应的方法如下

 //先保存原来的方法为旧的方法 否则 这个方法被覆盖了
 String.prototype.OldtoUpperCase  = String.prototype.toUpperCase; 
 String.prototype.toUpperCase=function(){
     alert(this.OldtoUpperCase()+' '+'hello');
 }
 
 var s="wj008";
 s.toUpperCase();



那么我们就可以知道 要想在某类型的实例上获得相应的方法 我们可以修改对应类型的 prototype 如 我们有类型 Test

 function Test(){}
    Test.prototype.sayhello=function(){ alert('hello'); };
    var a=new Test();
    a.sayhello();
    
    var Test2=Test;
    var b=new Test();
    b.sayhello();
    
    var Test4;
    var Test3=Test4= function(){}
    Test3.prototype.sayhello=function(){ alert('匿名 hello'); };
    var c=new Test4();
    c.sayhello();

从上面的例子中我们可以看出 修改 改数据类型的原型 可以达到 为该类型数据 添加方法属性的功效。

可是直接修改原型 可能会对我们的编程照成相应的污染;

比方说 上例 

我们修改了 String.prototype.toUpperCase 会导致 别人在使用这个 toUpperCase 时出现莫名其妙的错误。这个错误来自你修改后的 toUpperCase 

所以一般直接修改原型看起来不是那么必要!可是原型操作在我们的JS又极为重要。

如何修改原型但不照成污染成为我们要重点讨论的问题。

为了解决这个问题 我们想到 可以从原来的类型中copy 他们的方法过来 如下

function Base() {}
    

    Base.prototype.say1 = function () {
        alert(1);
    };

    function Test() {}
    

    Test.prototype.say2 = function () {
        alert(2);
    };

    for (var i in Base.prototype) {
        Test.prototype[i] = Base.prototype[i];
    }

    var a = new Test();
    a.say1();
    a.say2();

    var b = new Base();
    b.say1();
    b.say2(); //这里是会报错的因为 base 是没有 say2的


但是 我们又会想 其实 Base 的实例中 也一样有和 Base.prototype 对应的方法



    function Base() {}
    

    Base.prototype.say1 = function () {
        alert(1);
    };

    function Test() {}
    

    Test.prototype.say2 = function () {
        alert(2);
    };
    var cb=new Base();
    
    for (var i in cb) {
        Test.prototype[i] = cb[i];
    }

    var a = new Test();
    a.say1();
    a.say2();

    var b = new Base();
    b.say1();
    b.say2(); //这里是会报错的

这样做 一样达到效果 而不同的是我们初始化了Base 一个实例 而遍历其实例的方法,然而在一些情况下 new Base() 的时候 Base 应该不会有任何动作 比如一些初始化动作,否则 在构造Test方法的时候会多做一些Base初始化工作。

按照上面的思路 我原本是要copy 对象的方法 但是 其实我们可以直接把 Test中的prototype 直接设置成 Base 的一个实例 然后再为这个实例添加 say2 的方法 如下

    function Base() {}
    

    Base.prototype.say1 = function () {
        alert(1);
    };

    function Test() {}
    Test.prototype=new Base();
    Test.prototype.say2 = function () {
        alert(2);
    };
    var a = new Test();
    a.say1();
    a.say2();

    var b = new Base();
    b.say1();
    b.say2(); //这里是会报错的

也可以这样做

    function Base() {}
    

    Base.prototype.say1 = function () {
        alert(1);
    };

    function Test() {}
    var baseobj=new Base();
    baseobj.say2 = function () {
        alert(2);
    };
    Test.prototype=baseobj;
    var a = new Test();
    a.say1();
    a.say2();

    var b = new Base();
    b.say1();
    b.say2(); //这里是会报错的



说到这里的时候  可以有朋友会想到,可以利用原型来实现我们类似PHP的类继承。

(function(){
    function Base() {
        this.name='张三';
    }
    
    Base.prototype.say1 = function () {
        alert('say1:'+this.name);
    };

    function Test() {
         this.name='李四';
    }
    Test.prototype=new Base();
    Test.prototype.say2 = function () {
        alert('say2:'+this.name);
    };
    
    function Test2() {}
    Test2.prototype=new Test();
    Test2.prototype.say2 = function () {
        this.name='111';
        alert('say2:'+this.name);
    };


    var b1 = new Base();
    b1.say1();
    console.log(b1);
    
    var a1 = new Test();
    a1.say1();
    a1.say2();
    console.log(a1);

    var a2 = new Test2();
    
    a2.say2();
    a2.say1();
    console.log(a2);
 })();


这样的继承 有两个缺点,第一 在构造函数中我们无法实现构造参数 第2 一个类 要分内外两部分来完成。

为了实现构造函数 很多人对此作出了很多办法 如下:

(function(){
    function Base() {
        this.init=function(name){
            console.log(name);
            this.name='Base'+name;
        };
        this.init.apply(this,arguments);
    }
    
    Base.prototype.say1 = function () {
        alert('say1:'+this.name);
    };

    function Test() {
        this.init.apply(this,arguments);
    }
    
    Test.prototype=new Base();// 构造函数 init 处会输出 undefind
    Test.prototype.say2 = function () {
        alert('say2:'+this.name);
    };
    
    function Test2() {
         this.init.apply(this,arguments);
    }
    
    Test2.prototype=new Test(); // 构造函数 init 处会输出 undefind
    Test2.prototype.say2 = function () {
        alert('say2:'+this.name);
    };


    var b1 = new Base('张三');
    b1.say1();
    console.log(b1);
    
    var a1 = new Test('李四');
    a1.say1();
    a1.say2();
    console.log(a1);

    var a2 = new Test2('王5');
    
    a2.say2();
    a2.say1();
    console.log(a2);
 })();

在这种情况下  由于继承要创建一个实例用于赋值给原型,所以会出现在构造函数中执行 this.init 这个方法。 假如这个方法下面进行了HMTL元素操作 显然不是我们需要的,那么我们就希望 在 赋值给原型时不要运行这个 this.init 方法。
1 判断参数数量 把上面的设置如下。

(function(){
    function Base() {
        this.init=function(name){
            console.log(name);
            this.name='Base'+name;
        };
       arguments.length==0 || this.init.apply(this,arguments);
    }
    
    Base.prototype.say1 = function () {
        alert('say1:'+this.name);
    };

    function Test() {
       arguments.length==0 ||  this.init.apply(this,arguments);
    }
    
    Test.prototype=new Base();
    Test.prototype.say2 = function () {
        alert('say2:'+this.name);
    };
    
    function Test2() {
        arguments.length==0 ||  this.init.apply(this,arguments);
    }
    
    Test2.prototype=new Test();
    Test2.prototype.say2 = function () {
        alert('say2:'+this.name);
    };


    var b1 = new Base('张三');
    b1.say1();
    console.log(b1);
    
    var a1 = new Test('李四');
    a1.say1();
    a1.say2();
    console.log(a1);

    var a2 = new Test2('王5');
    
    a2.say2();
    a2.say1();
    console.log(a2);
 })();

那么 要求我们每次创建实例时必须传人参数 否则 我们需要手动运行 this.init() 并给定参数。

2 首参数类型判断

我们在继承的时候在创建对象之前 把 自身类型 传入,如下:

(function(){
    function Base() {
        this.init=function(name){
            console.log(name);
            if(name){
                this.name='Base'+name;
            }
        };
       (arguments.length==1 && Base===arguments[0]) || this.init.apply(this,arguments);
    }
    
    Base.prototype.say1 = function () {
        alert('say1:'+this.name);
    };

    function Test() {
       (arguments.length==1 && Test===arguments[0]) ||  this.init.apply(this,arguments);
    }
    
    Test.prototype=new Base(Base);
    Test.prototype.say2 = function () {
        alert('say2:'+this.name);
    };
    
    function Test2() {
        (arguments.length==1 && Test2===arguments[0]) ||  this.init.apply(this,arguments);
    }
    
    Test2.prototype=new Test(Test);
    Test2.prototype.say2 = function () {
        alert('say2:'+this.name);
    };


    var b1 = new Base('张三');
    b1.say1();
    console.log(b1);
    
    var a1 = new Test();
    a1.say1();
    a1.say2();
    console.log(a1);

    var a2 = new Test2('王5');
    
    a2.say2();
    a2.say1();
    console.log(a2);
 })();


因为 执行 this.init 的前提条件是 第一个参数 不恒等于 类名,那么在后续实例中 我们不在第一参数传入类名即可,也就是说 如果第一个参数是本类类名的 那么我们认为是用于继承的。

上面我们的继承已经可以基本实现,但是为了解决代码 内外统一这个问题 我们做了如下安排:


    (function () {
        //继承用的函数
        function Extends(func, base) {
            var temp = function () {
                (arguments.length == 1 && temp === arguments[0]) || this.init.apply(this, arguments);
            };
            console.log(base);
            if (typeof (base) === 'function') {
                temp.prototype = new base(base);
            }
            func.call(temp.prototype);
            return temp;
        }
        //构造Base类
        var Base = Extends(function () {
            //这里的this 是原型
            this.init = function (name) {
                console.log(name);
                if (name) {
                    //这里的this 实例对象本身
                    this.name = 'Base' + name;
                }
            };
            //这里的this 是原型
            this.say1 = function () {
                alert('say1:' + this.name);
            };
            
        });
        //构造Test 继承Base
        var Test = Extends(function () {
            //这里的this 是原型
            this.say2 = function () {
                alert('say2:' + this.name);
            };
        }, Base);
        
        //构造Test2 继承Test
        var Test2 = Extends(function () {
            this.say2 = function () {
                alert('Test2say2:' + this.name);
            };
        }, Test);


        var b1 = new Base('张三');
        b1.say1();
        console.log(b1);

        var a1 = new Test();
        a1.say1();
        a1.say2();
        console.log(a1);

        var a2 = new Test2('王5');

        a2.say2();
        a2.say1();
        console.log(a2);
    })();

然而 我们看似已经成功了,可是在编程获取类名是 却是这样的:

9F0VKE]I4}XZ0J2PCQHAK96.png
为了解决这个类名问题 我们还需要一步

(function () {
        //继承用的函数
        function Extends(name,func, base) {
            eval(' var temp = function '+name+'() {(arguments.length == 1 && temp === arguments[0]) || this.init.apply(this, arguments);};')
            if (typeof (base) === 'function') {
                temp.prototype = new base(base);
            }
            func.call(temp.prototype);
            return temp;
        }
        //构造Base类
        var Base = Extends('Base',function () {
            //这里的this 是原型
            this.init = function (name) {
                console.log(name);
                if (name) {
                    //这里的this 实例对象本身
                    this.name = 'Base' + name;
                }
            };
            //这里的this 是原型
            this.say1 = function () {
                alert('say1:' + this.name);
            };
            
        });
        //构造Test 继承base
        var Test = Extends('Test',function () {
            //这里的this 是原型
            this.say2 = function () {
                alert('say2:' + this.name);
            };
        }, Base);

        var Test2 = Extends('Test2',function () {
            this.say2 = function () {
                alert('Test2say2:' + this.name);
            };
        }, Test);


        var b1 = new Base('张三');
        b1.say1();
        console.log(b1);

        var a1 = new Test();
        a1.say1();
        a1.say2();
        console.log(a1);

        var a2 = new Test2('王5');

        a2.say2();
        a2.say1();
        console.log(a2);
    })();


原型继承这种方法在一定情况下还是比较高效的,但是在操作上 或者 代码编排上 感觉很多啰嗦的地方,而且稍微不注意可能会得到一些意想不到的结果。



未完待续...