<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>岁月如歌 &#187; OO</title>
	<atom:link href="http://lifesinger.org/blog/tag/oo/feed/" rel="self" type="application/rss+xml" />
	<link>http://lifesinger.org/blog</link>
	<description>关注用户体验、前端开发，记录生活点滴、岁月足迹。</description>
	<lastBuildDate>Mon, 06 Sep 2010 15:05:09 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0.1</generator>
		<item>
		<title>Crockford uber方法中的陷阱</title>
		<link>http://lifesinger.org/blog/2008/10/bug-in-crockford-uber-function/</link>
		<comments>http://lifesinger.org/blog/2008/10/bug-in-crockford-uber-function/#comments</comments>
		<pubDate>Sat, 04 Oct 2008 21:01:19 +0000</pubDate>
		<dc:creator>lifesinger</dc:creator>
				<category><![CDATA[开发]]></category>
		<category><![CDATA[classical]]></category>
		<category><![CDATA[inheritance]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[OO]]></category>
		<category><![CDATA[uber]]></category>

		<guid isPermaLink="false">http://lifesinger.org/blog/?p=192</guid>
		<description><![CDATA[先来看Douglas Crockford的经典文章：Classical Inheritance in JavaScript. 此文的关键技巧是给Function.prototype增加inherits方法，代码如下（注释是我的理解）： Function.prototype.method = function (name, func) { this.prototype[name] = func; return this; }; Function.method('inherits', function (parent) { var d = {}, // 递归调用时的计数器 // 下面这行已经完成了最简单的原型继承：将子类的prototype设为父类的实例 p = (this.prototype = new parent()); // 下面给子类增加uber方法（类似Java中的super方法），以调用上层继承链中的方法 this.method('uber', function uber(name) { if (!(name in d)) { d[name] = 0; } var f, r, t [...]]]></description>
			<content:encoded><![CDATA[<p>先来看Douglas Crockford的经典文章：<a href="http://javascript.crockford.com/inheritance.html">Classical Inheritance in JavaScript</a>. 此文的关键技巧是给Function.prototype增加inherits方法，代码如下（注释是我的理解）<span id="more-192"></span>：</p>
<pre>
Function.prototype.method = function (name, func) {
	this.prototype[name] = func;
	return this;
};

Function.method('inherits', function (parent) {
	var d = {}, // 递归调用时的计数器
		// 下面这行已经完成了最简单的原型继承：将子类的prototype设为父类的实例
		p = (this.prototype = new parent());

	// 下面给子类增加uber方法（类似Java中的super方法），以调用上层继承链中的方法
	this.method('uber', function uber(name) {
		if (!(name in d)) {
			d[name] = 0;
		}
		var f, r, t = d[name], v = parent.prototype;
		if (t) {
			while (t) {
				// 往上追溯一级
				v = v.constructor.prototype;
				t -= 1;
			}
			f = v[name];
		} else {
			f = p[name];
			if (f == this[name]) {
				f = v[name];
			}
		}
		// 因为f函数中，可能存在uber调用上层的f
		// 不设置d[name]的话，将导致获取的f始终为最近父类的f（陷入死循环）
		d[name] += 1;

		// slice.apply的作用是将第2个及其之后的参数转换为数组
		// 第一个参数就是f的名字，无需传递
		// 这样，通过uber调用上层方法时可以传递参数：
		// sb.uber(methodName, arg1, arg2, ...);
		r = f.apply(this, Array.prototype.slice.apply(arguments, [1]));

		// 还原计数器
		d[name] -= 1;

		return r;
	});
	// 返回this, 方便chain操作
	return this;
});
</pre>
<p>上面<span class="code">d[name]</span>不好理解，我们来创建一些测试代码：</p>
<pre>
function println(msg) {
	document.write(msg + '&lt;br /&gt;');
}

// 例1
function A() { }
A.prototype.getName = function () { return 'A'; }; // @1

function B() { }
B.inherits(A);
B.prototype.getName = function () { return this.uber('getName') + ',B'; }; // @2

function C() { }
C.inherits(B);
C.prototype.getName = function () { return this.uber('getName') + ',C'; }; // @3

var c = new C();
println(c.getName()); // => A,B,C
println(c.uber('getName')); // => A,B
</pre>
<p><span class="code">c.getName()</span>调用的是@3, @3中的<span class="code">uber</span>调用了@2. 在@2中，又有<span class="code">this.uber(&#8216;getName&#8217;)</span>, 这时下面这段代码发挥作用：</p>
<pre>
while (t) {
	// 往上追溯一级
	v = v.constructor.prototype;
	t -= 1;
}
f = v[name];
</pre>
<p>可以看出，<span class="code">d[name]</span>表示的是递归调用时的层级。如果不设此值，@2中的<span class="code">this.uber</span>将指向@2本身，这将导致死循环。Crockford借助<span class="code">d[name]</span>实现了<span class="code">uber</span>对同名方法的递归调用。</p>
<p><span class="code">uber</span>只是一个小甜点。类继承中最核心最关键的是下面这一句：</p>
<pre>
p = (this.prototype = new parent());
</pre>
<p><strong>将子类的原型设为父类的一个实例，这样子类就拥有了父类的成员，从而实现了一种最简单的类继承机制。</strong>注意JavaScript中，获取<span class="code">obj.propName</span>时，会自动沿着prototype链往上寻找。这就让问题变得有意思起来了：</p>
<pre>
// 例2
function D1() {}
D1.prototype.getName = function() { return 'D1' }; // @4

function D2() {}
D2.inherits(D1);
D2.prototype.getName = function () { return this.uber('getName') + ',D2'; }; // @5

function D3() {}
D3.inherits(D2);

function D4() {}
D4.inherits(D3);

function D5() {}
D5.inherits(D4);
D5.prototype.getName = function () { return this.uber('getName') + ',D5'; }; // @6

function D6() {}
D6.inherits(D5);

var d6 = new D6();
println(d6.getName()); // => ?
println(d6.uber('getName')); // => ?
</pre>
<p>猜猜最后两行输出什么？按照<span class="code">uber</span>方法设计的原意，上面两行都应该输出<span class="code">D1,D2,D5</span>, 然而实际结果是：</p>
<pre>
println(d6.getName()); // => D1,D5,D5
println(d6.uber('getName')); // => D1,D5
</pre>
<p>这是因为Crockford的inherits方法中，考虑的是一种理想情况（如例1），对于例2这种有“断层”的多层继承，<span class="code">d[name]</span>的设计就不妥了。我们来分析下调用链：</p>
<p><span class="code">d6.getName()</span>首先在d6对象中寻找是否有getName方法，发现没有，于是到D6.prototype(一个d5对象)中继续寻找，结果d5中也没有，于是到D5.protoype中寻找，这次找到了getName方法。找到后，立刻执行，注意this指向的是d6. <span class="code">this.uber(&#8216;getName&#8217;)</span>此时表示的是<span class="code">d6.uber(&#8216;getName&#8217;)</span>. 获取f的代码可以简化为：</p>
<pre>
// 对于d6来说, parent == D5
var f, v = parent.prototype;
f = p[name];
// 对于d6来说，p[name] == this[name]
if (f == this[name]) {
    // 因此f = D5.prototype[name]
    f = v[name];
}

// 计数器加1
d[name] += 1;

// 等价为 D5.prototype.getName.apply(d6);
f.apply(this);
</pre>
<p>至此，一级调用<span class="code">d6.getName()</span>跳转进入二级递归调用<span class="code">D5.prototype.getName.apply(d6)</span>. 二级调用的代码可以简化为：</p>
<pre>
var f, t = 1, v = D5.prototype;
while (t) {
	// 这里有个陷阱，v.constructor == D1
	// 因为 this.prototype = new parent(), 形成了下面的指针链：
	// D5.prototype = d4
	// D4.prototype = d3
	// D3.prototype = d2
	// D2.prototype = d1
	// 因此v.constructor == d1.constructor
	// 而d1.constructor == D1.prototype.constructor
	// D1.prototype.constructor指向D1本身，因此最后v.constructor = D1
	v = v.constructor.prototype;
	t -= 1;
}
// 这时f = D1.prototype.getName
f = v[name];

d[name] += 1;
// 等价为 D1.prototype.getName.apply(d6)
f.apply(this);
</pre>
<p>上面的代码产生最后一层调用：</p>
<pre>
return 'D1';
</pre>
<p>因此<span class="code">d6.getName()</span>的输出是<span class="code">D1,D5,D5</span>.<br />
同理分析，可以得到<span class="code">d6.uber(&#8216;getName&#8217;)</span>的输出是<span class="code">D1,D5</span>.</p>
<p>上面分析了“断层”时uber方法中的错误。注意上面提到的<span class="code">v.constructor.prototype</span>产生的陷阱，这个陷阱在“非断层”的理想继承链中也会产生错误：</p>
<pre>
// 例3
function F1() { }
F1.prototype.getName = function() { return 'F1'; };

function F2() { }
F2.inherits(F1);
F2.prototype.getName = function() { return this.uber('getName') + ',F2'; };

function F3() { }
F3.inherits(F2);
F3.prototype.getName = function() { return this.uber('getName') + ',F3'; };

function F4() { }
F4.inherits(F3);
F4.prototype.getName = function() { return this.uber('getName') + ',F4'; };

var f3 = new F3();
println(f3.getName()); // => F1,F2,F3

var f4 = new F4();
println(f4.getName()); // => F1,F3,F4
</pre>
<p>很完美的一个类继承链，但<span class="code">f4.getName()</span>没有产生预料中的输出，这就是<span class="code">v.constructor.prototype</span>这个陷阱导致的。</p>
<h4>小结</h4>
<ol>
<li>在JavaScript中，模拟传统OO模型来实现类继承不是一个很好的选择（上面想实现一个uber方法都困难重重）。</li>
<li>在JavaScript中，考虑多重继承时，要非常小心。尽可能避免多重继承，保持简单性。</li>
<li>理解JavaScript中的普通对象，Function对象，Function对象的prototype和constructor, 以及获取属性时的原型追溯路径非常重要。（比如上面提到的constructor陷阱）</li>
<li>Crockford是JavaScript界的大仙级人物，但其代码中依旧有陷阱和错误。刚开始我总怀疑是不是自己理解错了，费了牛劲剖析了一把，才敢肯定是Crockford考虑不周，代码中的错误是的的确确存在的。学习时保持怀疑的态度非常重要。</li>
</ol>
<h4>后续</h4>
<p>上面的分析花了一个晚上的时间，今天google了一把，发现对Crockford的<a href="http://blog.csdn.net/chensheng913/archive/2006/12/28/1465741.aspx">uber方法中的错误</a>能搜到些零星文章，还有人给出了<a href="http://hax.pie4.us/2006/12/bug-of-douglas-crockfords-uber.html">修正方案</a>（忍不住八卦一把：从链接上看，是CSDN上的一位兄弟第一次指出了Crockford uber方法中的这个bug，然后John Hax（估计也是个华人）给出了修正方案。更有趣的是，Crockford不知从那里得知了这个bug, 如今<a href="http://javascript.crockford.com/inheritance.html">Classical Inheritance in JavaScript</a>这篇文章中已经是修正后的版本^o^）。</p>
<p>这里发现的uber方法中的<strong>constructor陷阱</strong>，尚无人提及。导致constructor陷阱的原因是：</p>
<pre>
p = (this.prototype = new parent());
</pre>
<p>上面这句导致<span class="code">while</span>语句中v.constructor始终指向继承链最顶层的constructor. 分析出了原因，patch就简单了：</p>
<pre>
// patched by lifesinger@gmail.com 2008/10/4
Function.method('inherits', function (parent) {
    var d = { },
        p = (this.prototype = new parent());
        // 还原constructor
        p.constructor = this;
        // 添加superclass属性
        p.superclass = parent;

    this.method('uber', function uber(name) {
        if (!(name in d)) {
            d[name] = 0;
        }
        var f, r, t = d[name], v = parent.prototype;
        if (t) {
            while (t) {
                // 利用superclass来上溯，避免contructor陷阱
                v = v.superclass.prototype;
                // 跳过“断层”的继承点
                if(v.hasOwnProperty(name)) {
                    t -= 1;
                }
            }
            f = v[name];
        } else {
            f = p[name];
            if (f == this[name]) {
                f = v[name];
            }
        }
        d[name] += 1;
        if(f == this[name]) { // this[name]在父类中的情景
            r = this.uber.apply(this, Array.prototype.slice.apply(arguments));
        } else {
            r = f.apply(this, Array.prototype.slice.apply(arguments, [1]));
        }
        d[name] -= 1;
        return r;
    });
    return this;
});
</pre>
<p>测试页面：<a href="/blog/wp-content/uploads/2008/10/crockford_classic_inheritance_test.html">crockford_classic_inheritance_test.html</a></p>
<p>最后以Douglas Crockford的总结结尾：</p>
<blockquote><p>
我编写JavaScript已经8个年头了，从来没有一次觉得需要使用uber方法。在类模式中，super的概念相当重要；但是在原型和函数式模式中，super的概念看起来是不必要的。现在回顾起来，我早期在JavaScript中支持类模型的尝试是一个错误。
</p></blockquote>
]]></content:encoded>
			<wfw:commentRss>http://lifesinger.org/blog/2008/10/bug-in-crockford-uber-function/feed/</wfw:commentRss>
		<slash:comments>10</slash:comments>
		</item>
		<item>
		<title>谈谈淘宝上常用JS组件的实现</title>
		<link>http://lifesinger.org/blog/2008/09/talk-on-js-widgets-on-taobao/</link>
		<comments>http://lifesinger.org/blog/2008/09/talk-on-js-widgets-on-taobao/#comments</comments>
		<pubDate>Fri, 12 Sep 2008 13:21:20 +0000</pubDate>
		<dc:creator>lifesinger</dc:creator>
				<category><![CDATA[开发]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[OO]]></category>
		<category><![CDATA[taobao]]></category>
		<category><![CDATA[TBra]]></category>

		<guid isPermaLink="false">http://lifesinger.org/blog/?p=143</guid>
		<description><![CDATA[这是淘宝懒懒交流会第五期的内容，大纲为： 淘宝上常用的JS组件（Widgets） Tbra的实现与优缺点分析 再思考，我的尝试 LiquidView实战 花花世界，原样的蝴蝶 下载：talk_on_tb_js_widgets.zip (2.9M) 所有相关的js代码请用SVN下载： Unicorn SVN, TBra SVN. 欢迎大家交流讨论。]]></description>
			<content:encoded><![CDATA[<p>这是淘宝懒懒交流会第五期的内容，大纲为：</p>
<ol>
<li>淘宝上常用的JS组件（Widgets）</li>
<li>Tbra的实现与优缺点分析</li>
<li>再思考，我的尝试</li>
<li>LiquidView实战</li>
<li>花花世界，原样的蝴蝶</li>
</ol>
<div class="download">下载：<a href="http://lifesinger.org/blog/wp-content/uploads/2008/09/talk_on_tb_js_widgets.zip">talk_on_tb_js_widgets.zip</a> (2.9M)</div>
<p>所有相关的js代码请用SVN下载： <a href="http://iunicorn.googlecode.com/svn/trunk/">Unicorn SVN</a>, <a href="http://tbra.googlecode.com/svn/trunk/">TBra SVN</a>.</p>
<p>欢迎大家交流讨论。</p>
]]></content:encoded>
			<wfw:commentRss>http://lifesinger.org/blog/2008/09/talk-on-js-widgets-on-taobao/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
	</channel>
</rss>
