`

JS真的错了吗——Object-Oriented JavaScript(Part 2)

 
阅读更多

 

一、js是世界上最容易被误解的语言


javascript本质上是基于原型的语言,但是却引入了基于类的语言的new关键字和constructor模式,导致javascript饱受争议。

javascript的作者Brendan Eich 1994年研发这门语言的时候,C++语言是最流行的语言,java1.0即将发布,面向对象编程势不可挡,于是他认为,引入new关键字可以使习惯C++/java程序员更容易接受和使用javascript。

实际上,事实证明引入new是个错误的决定。

C++/java程序员看到new一个 function的时候,会认为js通过function创建对象.他们认为function相当于类,接着他们会尝试在js挖掘类似java/C++面向类的编程特性,结果他们发现function没有extends,反而有个很奇怪的prototype对象,于是他们开始咒骂,js的面向对象太糟糕了。确实,new的引入让他们以为js的面向对象与java/C++类似,实际上并不是,如果不是以原型本质去理解js的面向对象,注定要遭受挫折,new,prototype,__proto__都是javascript实现原型的具体手段。

另一方面,理解原型的程序员,抱怨没有最基本的通过对象创建对象的函数。他们很不高兴,因为居然要使用new function的语法来间接实现原型继承,三行代码才做到最基本的原型继承,下面是实现对象newObject继承对象oldObject的代码,
	function F(){};
	F.prototype = oldObject;
	var newObject = new F();
这太繁琐了。基于原型语言理论上应该存在一个函数create(prototypeObject),功能是基于原型对象产生新对象,例如,
var newObject = create(oldObject);
看到这样的代码,人们就会自然很清晰地联想到,newObject是以oldObject模板构造出来的。

js是世界上最容易被误解的语言,原因主要有两个:

1) 作为基于原型的语言中,却连最基本的一个通过原型产生对象的函数create(prototypeObject)也没有,让人不知道js根本上是以对象创建对象。应该添加该函数,现在Chrome和IE9的Object对象就有这个create函数。

2) 使用new func形式创建对象,让人误会js是以类似java类的构造函数创建对象,实际上,构造函数根本上在创建对象上起到次要的作用,甚至不需要,重要的只有函数的属性prototype引用的原型对象,新对象以此为模板生成,生成之后才调用函数做初始化的操作,而初始化操作不是必要的。应该把废弃new 操作符,把new func分解为两步操作,
var newObject = create(func.prototype); 
func.call(newObject);
这样程序员才好理解。如果想把这两个步骤合二为一,应该使用new以外的关键字。

到这里,我们务必要牢牢印入脑海的是,js的面向对象是基于原型的面向对象,对象创建的方式根本上只有一种,就是以原型对象为模板创建对象,newObject = create(oldObject)。new function不是通过函数创建对象,只是刻意模仿java的表象。

js在面向对象上遭遇的争议,完全是因为商业因素导致作者失去了自己的立场。就像现在什么产品都加个云一样,如果那时候不加个new关键字来标榜自己面向对象,产生"js其实类似c++/java"的烟幕,可能根本没有人去关注javascript。更令人啼笑皆非的是,原本称作LiveScript的javascript,因为 后期和SUN合作,并且为了沾上当时被SUN炒得火热的Java的光,发布的时候居然改名成Javascript。

二、让我们一起来研发JavaScript语言

既然js遭受那么多批评,那么我们就搞一个大家都满意的JS吧!
假想我们是当时研发javascript的Brendan Eich,我们会怎么设计js的面向对象呢?
现在javascript开发到这样的阶段
1) 拥有基本类型,分支和循环,基本的数学运算,
2) 所有数据都是对象
3) 拥有类似C语言的function
4) 可以用var obj = {}语句生成一个空对象,然后使用obj.xxx或obj[xxx]设置对象属性
5) 没有继承,没有this关键字,没有new
我们任务是,实现javascript的面向对象,最好能达到类似java的创建对象和继承效果。更具体一点,我们要扩充js语言,实现类似下面的java代码。
	class Empolyee{
		String name;
		public Employee(String name){
			this.name = name;
		}
		public getName(){
			return this.name;
		}
	}
	class Coder extends Employee {
		String language;
		public Coder(name,language){
			super(name);
			this.language = language;
		}
		public getLanguage(){
			return this.language;
		}
	}
  1 实现创建对象
现有的对象都是基本类型,怎么创建用户自定义的对象呢?
(解释:
var i = 1;
这里的i是解释器帮忙封装的Number对象,虽然看起来跟C的int没区别,但实际上可以i.toString()。
)
java使用构造函数来产生对象,我们尝试把java的Empolyee的构造函数代码拷贝下来,看看可不可以模仿
		function Empolyee(name){
			this.name = name;
		}
 
我们只要生成一个空对象obj,再把函数里面的this换成obj,执行函数,就可以生成自定义对象啦!我们把Employee这样用来创建对象的函数称作构造函数。
1) 首先我们用原生的方式为function添加方法call和apply,实现把把函数里面的this替换成obj。call,apply在Lisp语言中已经有实现,很好参考和实现。
2) 然后实现生成实例 
			function Empolyee(name){
				this.name = name;
			}
			var employee = {};
			Employee.call(employee,'Jack');
 
    
3) 到这里,以类似java方式产生对象基本完成了,但是这个employee对象没有方法
我们的function是第一类对象,可以运行时创建,可以当做变量赋值,所以没有问题。
		function Empolyee(name){
			this.name = name;
			this.getName = function(){return this.name};
		}
很好,我们团队顺利向前走了一步,今晚大家不用加班了!
  2 实现继承
创建对象成功了,接着考虑实现继承。现在我们所有数据都是对象,没有类,有两种方案摆在我们的面前
a.类继承
b.原型继承

2.a 实现类继承
a方案是首选方案,因为跟java相似的话,JS更容易被接受
先粘贴Java构造函数的代码
		function Coder extends Employee(name,language){
			super(name);
			this.language = language;
		}
 
1) 把extends后面的函数自动记录下来,放到function对象的parentFunc变量
2) 如果第一行是super(),替换成var parent = newInstance(Coder.parentFunc,XXX),这样内部保留一个名为parent父对象;
3) 把this替换为obj,super替换换成parent
4) "."和"[]"重新定义,需要支持在对象内部parent对象查找属性。
这四步都属于比较大的改动,只要认真想一想都觉得不是太容易。
更重要的是,即使把这4步实现了,不但语言变得太复杂了,而且产生的对象根本享受不了继承带来的好处——内存中的代码复用,因为这样产生的每个对象都有"父类(函数)"的代码而不是仅有一份。这时候该注意到java中使用类的意义了,java类的代码在内存只有一份,然后每个对象执行方法都是引用类的代码,所有子类对象调用父类方法的时候,执行的代码都是同一份父类的方法代码。但是JS没有类,属性和方法都是存在对象之中,根本没有办法做到java那样通过类把代码共享给所有对象!
a方案宣告失败
         2.b 实现原型继承
看b方案。我们现在的js语言,一切都是对象,显然非常适合使用基于原型的继承方式,就看具体如何实现了。
我们新建一个topObject来代表顶层对象,那么创建employee对象的时候,应该在employee对象内部设置一个属性引用topObject;同理,创建coder对象的时候,应该在coder对象内部设置一个属性引用employee对象,我们把这个引用原型对象的属性命名约定为"__proto__"。更进一步,为了构建一个对象的过程更自然,构建时候应该先在新对象中设置引用原型对象的属性,以表示先用模板制作出一个和模板一致的对象,然后再才执行构造函数初始化这个新对象自身的属性,以添加个性化的东西。具体实现代码如下:
		var topObject = {
			__version__ : 1.0;
		};
		
		function Empolyee(name){
			this.name = name;
			this.getName = function(){return this.name};
		}
		var employee = {};
		employee.__proto__ = topObject;
		Employee.call(employee,'Jack');
		
		function Coder(name,language){
			this.name = name;
			this.language = this.language;
			this.getLanguage = function(){return this.language};
		}
		
		var coder = {};
		coder.__proto__ = employee;
		Coder.call(coder,'Coder Jack','Java');
 
当然我们还要做的工作就是在javascript解释器中增加对__proto__的支持,当一个对象访问一个自身没有的属性的时候,就通过__proto__属性查找原型链上是否存在该属性。

          2.c 优化实现

               优化1:函数封装
这一切看起来并不是那么美好,我们创建一个employee对象需要3行代码,我们需要这么一个函数封装这3行代码
function newInstance(prototype,constructor,arg1,arg2,....);
//第一个参数是原型对象,第二个是构造函数,后面的是构造函数的参数
可以这么实现
			function sliceArguments(argumentsObj,n){
				var args = [];
				for(var i=0;i<argumentsObj.length;i++){
					if(i>=n){
						args.push(argumentsObj[i]);
					}
				}
				return args;
			}
			function newInstance(prototype,constructor){
				var obj = {};
				obj.__proto__ = prototype;
				constructor.apply(obj,sliceArguments(arguments,2));
			}
			var employee = newInstance(topObject,Employee,'Jack');
			var coder = newInstance(employee,Coder,'Coder Jack','Java');
               优化2:缩减参数
仔细一看,function newInstance的参数可以更少,我们可以把原型对象prototype作为属性放在constructor,那样我们的函数就可以只有一个参数了。属性名就约定为prototype吧。
2.1 我们修改解释器,把topObject写入语言作为原生的顶级对象;再修改function的源代码,让每一个新建的function都默认具有属性prototype = topObject
2.2 优化后的代码如下
			function newInstance(constructor){
				var obj = {};
				obj.__proto__ = constructor.prototype;
				constructor.apply(obj,sliceArguments(arguments,1));
				return obj;
			}
			function Employee(name){
				this.name = name;
				this.getName = function(){return this.name};
			}
			var employee = newInstance(Empolyee,'Jack');
			var employee2 = newInstance(Empolyee,'Jack2');
			var employee3 = newInstance(Empolyee,'Jack3');
			function Coder(name,language){
				this.name = name;
				this.language = language;
				this.getLanguage = function(){return this.language};
			}
			Coder.prototype = newInstance(Empolyee,'');
			
			var coder = newInstance(Coder,'Coder Jack','Java');
			var coder2 = newInstance(Coder,'Coder Lee','C#');
			var coder3 = newInstance(Coder,'Coder Liu','C++');
			var coder4 = newInstance(Coder,'Coder Liu','JavaScript');
		
 
至此,我们利用已有的设施,简单有效地开发出一个面向对象的javascript版本!Congratulations!

好像有些什么不妥——

突然,我好像明白了什么...

4
1
分享到:
评论
5 楼 lazy_ 2012-11-09  
iame 写道
能推荐一个比较成熟的的基于原型的无依赖仿OO库吗?
支持extends,parent,static,init等特性。

我所知道的有prototype,模仿了Class, extends等。应该大多数JS库都会有类似的功能的,YUI,mootools,ext等等。

我更建议的你读完JS权威的这几篇文章后再考虑怎么使用和选择这写封装。目前为止,我只使用过prototype的Class.create。其他情况还是自己搞定,例如利用函数返回一个对象,或者利用原始的new去搞定(参见我博客大技术文f分类下的JAVASCRIPT文章下的后面的章节)。

http://javascript.crockford.com/javascript.html
http://javascript.crockford.com/prototypal.html
http://javascript.crockford.com/inheritance.html
http://www.crockford.com/javascript/private.html


还有。static在面向原型的JS中不存在这个概念。JAVA所谓静态成员就是类共享给所有的对象的成员,每个对象共享同一份静态成员,实际上,JS的原型继承天生就实现了这一点,每个对象都继承父亲对象属性,修改父对象的成员,子对象也马上做出改变,除非子对象重写了该成员。
4 楼 iame 2012-11-08  
能推荐一个比较成熟的的基于原型的无依赖仿OO库吗?
支持extends,parent,static,init等特性。
3 楼 lazy_ 2012-10-11  
iame 写道
不错,第三篇呢?

不好意思,最近工作比较忙,所以暂时是没有足够的时间和精力去写了。可以关注我。
推荐一些我认为是最优秀的JS文章,让你过把瘾吧。

crockford大师
http://javascript.crockford.com/javascript.html
http://javascript.crockford.com/prototypal.html
http://javascript.crockford.com/inheritance.html
http://www.crockford.com/javascript/private.html

MDN
https://developer.mozilla.org/en-US/docs/JavaScript/Guide/Details_of_the_Object_Model

微软杂志
http://msdn.microsoft.com/zh-cn/magazine/cc163419.aspx#S4

MSDN
http://msdn.microsoft.com/en-us/library/dd282900(v=vs.85).aspx
http://msdn.microsoft.com/en-us/library/dd229916(v=vs.85)

阮一峰
http://www.ruanyifeng.com/blog/2010/05/object-oriented_javascript_encapsulation.html
http://www.ruanyifeng.com/blog/2012/07/three_ways_to_define_a_javascript_class.html?20120830102326#comment-last
http://www.ruanyifeng.com/blog/2010/05/object-oriented_javascript_inheritance.html
http://www.ruanyifeng.com/blog/2010/05/object-oriented_javascript_inheritance_continued.html

others
http://bonsaiden.github.com/JavaScript-Garden/zh/
2 楼 iame 2012-10-10  
不错,第三篇呢?
1 楼 fearthenight 2012-09-11  
不错哦,解释的很清楚。

相关推荐

    Speaking JavaScript

    JavaScript quick start: Familiar with object-oriented programming? This part helps you learn JavaScript quickly and properly. JavaScript in depth: Learn details of ECMAScript 5, from syntax, variables...

    程序语言设计原理习题解答

    Interview: Larry Wall—Part 2: Scripting Languages in General and Perl in Particular 388 History Note 396 History Note 397 History Note 401 9.6 Parameters That Are Subprogram Names 408 History...

    Ajax for Web Application Developers(Ajax网站开发)

    Object-Oriented JavaScript Object-Oriented Approaches Using the new Operator Literal Notation Associative Arrays JScript.NET Object Constructors Prototypes Chapter 6. Creating the ...

    皇家墨尔本理工大学Web Programming教材

    世界名校 澳大利亚皇家墨尔本理工大学网页编程课程教材,内容覆盖HTML,CSS,JavaScript, PHP等: ...Chapter 2: More CSS; HCI, Usability and GUI design ...Chapter 11: Object-Oriented Programming in PHP

    javascirpt权威指南中英版

    that is well-suited to object-oriented and functional programming styles. JavaScript derives its syntax from Java, its first-class functions from Scheme, and its prototype- based inheritance from Self...

    [removed] Moving to ES2015 (AZW3格式)

    Code using the powerful object-oriented feature in JavaScript Master DOM manipulation, cross-browser strategies, and ES6 Harness the power of patterns for tasks ranging from application building to ...

    Beginning HTML5 Games with CreateJS

    Extending EaselJS DisplayObjects using object-oriented JavaScript JavaScript debugging Wrapping HTML5 games and publishing them to app store Who this book is for Beginning ...

    C# 2010 for Programmers 4ed part1

    Written for programmers with a background in C++, Java or other high-level, object-oriented languages, this book applies the Deitel signature live-code approach to teaching programming and explores ...

    PHP和MySQL Web Development第五版(2016)原版完整英文版

    6 Object-Oriented PHP 7 Error and Exception Handling Part II: Using MySQL 8 Designing Your Web Database 9 Creating Your Web Database 10 Working with Your MySQL Database 11 Accessing Your MySQL ...

    javascript权威指南(第六版)

    9.6 Object-Oriented Techniques in JavaScript 215 9.7 Subclasses 228 9.8 Classes in ECMAScript 5 238 9.9 Modules 246 10. Pattern Matching with Regular Expressions . . . . . . . . . . . . . . . . . . . ...

    Ajax in Action

    - **Description**: This appendix is aimed at programmers transitioning from other object-oriented languages to JavaScript. It covers key concepts and patterns for effective JavaScript programming. Key...

    Developing Large Web Applications: Producing Code That Can Grow and Thrive

    For several years, he has taught object-oriented programming part-time at the University of California, Santa Cruz while working as a software developer in Silicon Valley. Kyle received a B.S. in ...

    PHP.and.MySQL.Web.Development.5th.Edition

    Chapter 6 Object-Oriented PHP Chapter 7 Error and Exception Handling Part II: Using MySQL Chapter 8 Designing Your Web Database Chapter 9 Creating Your Web Database Chapter 10 Working with Your MySQL...

    PHP and MYSQL Bilbe [英文原版]

    Chapter 20: Object-Oriented Programming with PHP 365 Chapter 21: Advanced Array Functions 409 Chapter 22: String and Regular Expression Functions 421 Chapter 23: Filesystem and System Functions 439 ...

Global site tag (gtag.js) - Google Analytics