`
shake863
  • 浏览: 638551 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

prototype 剖析(2)

    博客分类:
  • JS
阅读更多

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这样的东西还是少用为妙。

分享到:
评论

相关推荐

    Spring实战之Bean的作用域singleton和prototype用法分析

    主要介绍了Spring实战之Bean的作用域singleton和prototype用法,结合实例形式分析了Bean的作用域singleton和prototype相关使用方法及操作注意事项,需要的朋友可以参考下

    使用Prototype框架.pdfprototype源码分析.doc

    例解Prototype框架.doc 第21章__使用Prototype框架.pdf Prototype源码注释版.pdf proprototype源码分析.doc totype_1.3_源码解读.txt

    prototype框架分析

    prototype框架分析.xls

    prototype,__proto,constructor

    分析javascript中 prototype __proto__ constructor之间的关系

    K-prototype源代码

    采用MATLAB编写,根据原作者论文的思路编写,代码里面存在注释,适合聚类分析学习者查看

    Robot RV reducer virtual prototype simulation and analysis

    机器人用RV减速器虚拟样机仿真与分析,关天民,张迎辉,论文基于多体系统动力学原理建立了机器人用RV减速器的虚拟样机模型,建立模型中,在分析RV 减速器的工作原理基础上,定义三个行星�

    prototype框架中美元符号$用法分析

    本文实例讲述了prototype框架中美元符号$用法。分享给大家供大家参考,具体如下: prototype是实现面向对象的一个重要工具,是javascript的一个不错的框架。 用jquery的人都知道,jquery中也有$美元符号,prototype...

    深入分析js中的constructor和prototype

    我们在定义函数的时候,函数定义的时候函数本身就会默认有一个prototype的属性,而我们如果用new 运算符来生成一个对象的时候就没有prototype属性。我们来看一个例子,来说明这个 代码如下: function a(c){ this.b =...

    jQuery.prototype.init选择器构造函数源码思路分析

    jQuery的核心思想可以简单概括为“查询和操作dom”,今天主要是分析一下jQuery.prototype.init选择器构造函数,处理选择器函数中的参数,感兴趣的朋友可以了解下,或许本文所提供的知识对你学习有所帮助

    JS原型prototype和__proto__用法实例分析

    本文实例讲述了JS原型prototype和__proto__用法。分享给大家供大家参考,具体如下: 先来看一个实例 function Foo() { } var foo = new Foo(); console.log(foo.prototype);// undefined console.log(foo.__proto__...

    jQuery prototype冲突的2种解决方法(附demo示例下载)

    本文实例分析了jQuery prototype冲突的2种解决方法。分享给大家供大家参考,具体如下: jquery和prototype怎么会冲突,归根到底就是因为他们二个都用到了$,同时用,混淆了。这个问题解决过不下5次,每次解决都要查...

    Javascript Function.prototype.bind详细分析

    Function.prototype.bind分析 bind()方法会创建一个新的函数,成为绑定函数。当调用这个绑定函数时,绑定函数会以创建它时传入的第一个参数作为this,传入bind()方法的第二个以及以后的参数加上绑定函数运行时本身的...

    Function.prototype.call.apply结合用法分析示例

    分析步骤如下: 1、将Function.prototype.call当成整体,call方法是由浏览器实现的本地方法,是函数类型的内部方法 var a = (Function.prototype.call).apply(function(a){return a;}, [0,4,3]); 2、fun

    JS利用prototype给类添加方法操作详解

    主要介绍了JS利用prototype给类添加方法操作,结合实例形式分析了javascript使用prototype实现给类添加方法的相关操作技巧,需要的朋友可以参考下

    prototype源码下载

    prototype源码下载,没有压缩过,请各位看官下载分析理解

    JS中prototype的用法实例分析

    本文实例讲述了JS中prototype的用法。分享给大家供大家参考。具体分析如下: JS中的phototype是JS中比较难理解的一个部分   本文基于下面几个知识点:   1 原型法设计模式 在.Net中可以使用clone()来实现原型法 ...

Global site tag (gtag.js) - Google Analytics