prototype 剖析(2)

1、Type - 类型

1)Object类型。在Prototype的基本类型中,因为js是无类型语言这种无类型,纵观全局,Object提供一些供全局静态调用的一些方法。它们包括:检测,扩展。且没有污染到Object的原型对象。(这一点很重要)

2)String类型。它将String对象原型进行进一步的扩展,扩展的方法都是一些常见的strip, stripTags, truncate之类的,有几个方法值得一提:gsub方法,这个方法用来作一个全局的替换,传入的参数有两个,一个是pattern,为正则表达式,第二个是replacement,要替换的数符串。

3)Array类型。它并无特别之处,只是将一些常用功能进行扩展。

4)Hash类型。并非是JS内置内型,是一个属于Prototype自定义的一种类型,支持json类型的序列化和反序列化

5)Function类型。可以说这是几个类型里扩展得比较特别的地方。
argumentNames。得到函数的形参名称,并返回一个形参(字符串型)的一个数组,主要用于动态的调用。与invoke可以形成一个simple Factory pattern。
bind,通俗的说,bind不会让this指针被劫持。说得理论一些,就是用于绑定上下文。关于这一点,我在下面的Context节中会有详细一些的描述。bindAsEventListener,与bind类似,主要区别在于它传递了event对象。
curry方法克里化方法。用于动态的传递参数。比如
Copy Code(拷贝代码)-Run HTML(运行代码)-Save Code(另存代码)
<script type="text/javascript">
Function.prototype.curry = function() {
if (!arguments.length) return this;
var __method = this, args = Array.prototype.slice.call(arguments,0);
return function() {
return __method.apply(this, args.concat(Array.prototype.slice.call(arguments,0)));
}
}
var F=function(){alert(Array.prototype.slice.call(arguments,0).join(' '))};
F.curry('I').curry('am').curry('never-online').curry('http://www.never-online.net')();
</script>
delay函数延迟执行。
wrap把自身函数进行包装,并把包装好的函数作为形参函数中所传递的第一个参数,简单的说就是自身封装。动态改变的将已包装函数的上下文(包装函数的this与执行时的this是一致的)看个示例吧。
Copy Code(拷贝代码)-Run HTML(运行代码)-Save Code(另存代码)
<script>
Function.prototype.bind = function() {
if (arguments.length < 2 && typeof(arguments[0])=='undefined') return this;
var __method = this, args = Array.prototype.slice.call(arguments,0), object = args.shift();
return function() {
return __method.apply(object, args.concat(Array.prototype.slice.call(arguments,0)));
}
}
  Function.prototype.wrap = function(wrapper) {
var __method = this;
return function() {
return wrapper.apply(this, [__method.bind(this)].concat(Array.prototype.slice.call(arguments,0)));
}
}

var a = {
  b: {
  c: function () {
  }
  },
  
  f: function () {
  alert(this==a.b);
  }
};

a.b.c = a.f.wrap(function (F) {
  F();
});
a.b.c();
</script>
a.f被wrap,在匿名函数中,F参数就是a.f,这个被包装好的函数被赋给a.b.c这个函数。我们最后一行调用了a.b.c这个函数,从源代码里可以看到this原来是属于a这个对象,但根据示例之前所说的,这个wrap会动态的替换this。所谓动态,就是看你是怎么调用的。a.f方法的this 被谁替换的答案就是,因为a.f从上面的a.b.c()执行,它的对象就是a.b,如果是这样
Copy Code(拷贝代码)-Run HTML(运行代码)-Save Code(另存代码)
var f = a.f.wrap(function (F) {
  F();
});
f();
this就会是window,自然上面运行的结果就是false。这个奥秘就是在返回的闭包里。如果你理解闭包,理解apply,就可以从wrap原型中找到答案了。具体的要用书面来解释的话,还是有太多题外话要说,有不清楚的话就在评论中留言吧。

methodize
将动态的指针this作为参数传入闭包中。这个方法,我不是很清楚它的具体作用。看代码是比较简单的,但为何要这样做我也不得而知。

2、Hack
我把这个词从css hack中取过来,暂且放到这里,意指,灵活运用js机制从而实现简化代码的作用。

1) 先不说这个Hack的具体内容,我们不妨思考一个问题,在可列举型的对象上,如:Array, Object等对象,如何实现一个迭代器接口,即each, 利用each实现all(并在每次迭代中执行函数,如果执行函数返回false,则跳出each迭代,也跳出all函数)等方法。

首先, 先看普通的实现方法, 它将会有一些限制.
Copy Code(拷贝代码)-Run HTML(运行代码)-Save Code(另存代码)
<script>
/**
* 如上文所说,先实现each,参数有一个fn,
* 用apply去动态的调用这个函数并给予形参value还有index
*
* @method each
* @param {function}
* @return void
*/
Array.prototype.each = function (fn) {
  var self = this; var i = this.length;
  while(i--) fn.apply(null,[self[i],i]);
}

/**
* 但在all的实现中就不好实现了
* 因为each是循环的执行某一函数,
* 不论函数何值都会执行到最后一个元素
* 因此我们不能够从函数中安全的返回
*
* @method each
* @param {function}
* @return void
*/
Array.prototype.all = function (fn) {
  this.each(
  function (value, index) {
  if (false===fn.apply(null,[value,index])) {
  return false;
  }
  alert('this is a test for outer Function run count as ' +(value));
  //执行为false时已经将这层函数返回,因此这里将会出现9个alert,很明显不符合我们预期(预期是执行为false时将all函数也返回)
  }
  );
};
var arr = [1,2,3,4,5,6,7,8,9,10];
arr.all(function (value, index) {
  var result = value==6?false:true;
  return result;
});
</script>

从上面的例子来看,我们为什么不在all中单独的实现呢,而是调用each来实现? 当然, 我们可以在all中直接而显式的编码代码
Copy Code(拷贝代码)-Run HTML(运行代码)-Save Code(另存代码)
Array.prototype.all = function (fn) {
  var self = this; var i = this.length;
  while(i--) if (!fn.apply(null,[self[i],i])) return;
};

现在再来解释为何不单独实现. 我们可以单独的把each作为一个迭代器, 它是抽象的, 设计模式中说:一个类可以扩展,但不要修改它.另外一点,用each来实现all(当然可以是其它的), 可以节省很多代码

下面看Prototype实现, 用throw 。这个throw,通常用于抛出一个异常给上层代码,让上层代码捕获并加以处理。Prototype给了我们(无论是否是其发明的这种Hack)另外一种用法,从而改变了所有可列举对象接口的实现。
Copy Code(拷贝代码)-Run HTML(运行代码)-Save Code(另存代码)
<script>
var $exception = '$$EXCEPTION$$';
Array.prototype.each = function (fn) {
  var self = this; var i = this.length;
  try { while(i--) fn.apply(null,[self[i],i]); }
  catch (e) { if (e!=$exception) throw e; }
}
Array.prototype.all = function (fn) {
  this.each(
  function (value, index) {
  if (false===fn.apply(null,[value,index])) {
  throw $exception;
  }
  alert('this is a test for outer Function run count as ' +(value));
  //执行为false时已经将这层函数返回,因此这里将会出现9个alert,很明显不符合我们预期(预期是执行为false时将all函数也返回)
  }
  );
};
var a = [1,2,3,4,5,6,7,8,9,10];
a.all(function (value, index) {
  var result = value==6?false:true;
  return result;
});
</script>

这个Hack也利用了异常机制, 在要返回自身的上层函数时,抛出异常,在each时捕获。从而可以用each来all。

2)extend。最原始的目的,为了扩展对象方法或属性而实现的一个函数,它是Object的静态方法。这里我们讲述的是Hack方法。比如:
Copy Code(拷贝代码)-Run HTML(运行代码)-Save Code(另存代码)
//原始的
var defaultConfig = {
  duration: 72,
  delay: 10
}
function jsclass (objConfig) {
  objConfig.duration = defaultConfig.duration||72;
  objConfig.delay = defaultConfig.delay||10;
  return objConfig;
}
//Hack版
var defaultConfig = {
  duration: 72,
  delay: 10
}
function jsclass (objConfig) {
  Object.extend(objConfig||{}, defaultConfig);
  return objConfig;
}
上面的代码可以看出,用extend可以进行一个简单的验证过程。从而简化了代码。

三、闭包 Closure
在Prototype中,闭包随处可见。最常见的就是在扩展Function原型时,几乎每一个扩展原型的实现,都是一个闭包,且。该扩展是可任意组合的。比如克里化可以有这样的调用方法(上文中已经提到了此方法的使用):
Copy Code(拷贝代码)-Run HTML(运行代码)-Save Code(另存代码)
<script>
Function.prototype.curry = function() {
if (!arguments.length) return this;
var __method = this, args = Array.prototype.slice.call(arguments,0);
return function() {
return __method.apply(this, args.concat(Array.prototype.slice.call(arguments,0)));
}
}
var F = function () { alert(Array.prototype.slice.call(arguments,0).join(' ')); }
F.curry('I').curry('am').curry('never-online')();
</script>
当然,bind, wrap这些都是返回闭包。因为使用闭包,js才更吸引人,也正是因为使用闭包,js才更灵活,可以实现很多在传统编程里意想不到的东西。但反思后会发现,闭包的滥用会导致debug的困难,可读性差。这个问题留到后面再来讨论。

四、上下文 context
在我的理解中,上下文简单的说就是指针。在js的动态特性中,用apply和call是最有趣的地方之一,也是实现改变上下文的关键所在。举个简单的例子:
Copy Code(拷贝代码)-Run HTML(运行代码)-Save Code(另存代码)
var F = function () {
  this.name = 'never-online';
  this.alert = function () {
  alert(this.name);
  }
  return this;
}

var C = function () {
  this.name = 'Rank';
  this.alert = function () {
  alert(this.name);
  }
  return this;
}

var oF = new F();
var oC = new C();
关于改变context在很多时候是有用的。这里就不再赘述一些代码的例子了。

五、Lazy function pattern
有人称为惰性函数模式。貌似很理论化的东西,事实上看下面一个例子就明白了,注意看在getXMLHttpRequest里执行了几次alert
Copy Code(拷贝代码)-Run HTML(运行代码)-Save Code(另存代码)
var browser = {
  ie: !!window.ActiveXObject,
  ie7: /msie\s*?7\.0/i.test(window.navigator.userAgent)
}
var getXMLHttpRequest = function () {
  if (browser.ie && !browser.ie7)
  getXMLHttpRequest = function () {
  return new ActiveXObject('Microsoft.XMLHTTP');
  }
  else
  getXMLHttpRequest = function () {
  return new XMLHttpRequest();
  }
  alert('看看在getXMLHttpRequest里执行了几次alert');
  return getXMLHttpRequest();
}
var req = getXMLHttpRequest();
alert(req)
var http = getXMLHttpRequest();
alert(http)
上面的在getXMLHttpRequest里只执行了一次alert,由此可以看出,通过js的动态重写函数,达到兼容的目的。第一次调用后,重写自身。动态的重写这是js里又一个很有意思的地方。

我的体会是,Prototype的设计上考虑得很全面。包括层次,接口,模式都用得很到位。这一点是我觉得Prototype优雅的地方,新颖的语法是一大亮点(虽然现在都知道他的$, Try These extend...),它借鉴了许多语言的许多语法,包括Ruby,python等。
Prototype里的相互依赖太紧密。藕合太高,也许Prototype的团队开发的想法是把整个Prototype看作是一个大module(这个粒度是不是有点大-_-!)。闭包大量的使用,调试是一个问题(也许是我水平有限,Prototype的实现某些方法有点“丑陋”),闭包毕竟是不可大量使用的。所以使用时需要注意。因为把可列举对象抽象Enumerable。实现接口_each。用_each来实现each。再用each来实现各集合的方法。把迭代器封装,造成的缺点是让使用者无需考虑迭代细节,如果数据量大,建议不要用Prototype的迭代器。上文中的lazy function pattern这样的东西还是少用为妙。

阅读更多
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页