时间: 2015-11-16 00:00 栏目: JavaScript 浏览: 7378 赞: 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 方法。
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);
})();然而 我们看似已经成功了,可是在编程获取类名是 却是这样的:![1714477335661132.png 9F0VKE]I4}XZ0J2PCQHAK96.png](/upfiles/images/201511/1714477335661132.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);
})();原型继承这种方法在一定情况下还是比较高效的,但是在操作上 或者 代码编排上 感觉很多啰嗦的地方,而且稍微不注意可能会得到一些意想不到的结果。
未完待续...
总赞数量:18276
总踩数量:128089
文章数量:29