时间: 2015-11-16 00:00 栏目: JavaScript 浏览: 6457 赞: 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);
发现 这些属性都是来自这个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 方法。
(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() 并给定参数。
(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); })();
然而 我们看似已经成功了,可是在编程获取类名是 却是这样的:
为了解决这个类名问题 我们还需要一步
(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); })();
原型继承这种方法在一定情况下还是比较高效的,但是在操作上 或者 代码编排上 感觉很多啰嗦的地方,而且稍微不注意可能会得到一些意想不到的结果。
未完待续...
总赞数量:18241
总踩数量:128083
文章数量:28