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

By 水木神風 at 2016-11-08 • 0人收藏 • 2042人看过

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

大家好,我叶子,接下来准备讲一讲 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);    })();


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


登录后方可回帖