前端要给力之:分解对象构造过程new()
2010-12-29 14:57
267 查看
本文讨论JavaScript中的对象创建运算new。需要说明的是,本文所讨论的“将new()过程分解为多个步骤”,并非一般js开发中的所须技巧,而是在js来构建OOP系统的必要技术。
一、JavaScript构造器与构造过程的特点
JavaScript中通过以下方式声明和使用构造器:
其中xxx与yyy的不同在于:对于obj1和obj2来说,yyy是相同的属性的不同引用,不同对象实例的初始值总是相同的;而xxx则是各自不同的引用,每个对象实例都不同。上例中使用1、2这样的值类型数据,并不足以体现二者的区别。当我们而使用引用类型(例如数组),就很容易看出二者的区别来了。例如:
使用原型的另一个必要在于继承树的建立。这一过程要求new运算符的参与,简单说来,就是"子类.prototype"必须赋值为"new 父类()"所产生的一个实例。例如:
在这个过程中,最后面的是一行修正代码。这行修正的必要性在于:new MyObject()所产生的实例(例如x)的属性x.constructor总是指向MyObject,而在子类MyObjectEx()中,它应该是指向MyObjectEx的。
OK。这就是全部的基础知识,下面我们来分解这一过程。
二、new()过程中的原型链维护
new总是因为建立原型继承树而存在的,如果没有new过程参与,则当
时,我们无法通过instanceof运算:
来了解obj在继承树上的关系。
但是许多人并不知道,事实上这一过程并不需要MyObject的参与。因为instanceof只检查prototype链,并不检查函数本身。举例来说:
在这个示例中,obj创建自Y(),但只因为X.prototype(或者它的原型链中)指向原型P,所以最后显示obj也是X的一个实例。可见instanceof的检测与obj是否创建自函数X是无关的,并不检查函数自身。
换而言之,new()在上述过程中,只有“维护原型链”的作用。那么,我们事实上也可以手工来维护这个原型链。这一点,事实上也就是ECMA Script 5th中的Object.create()出现的原因。
Object.create(O[, Properties]) returns an object with a prototype of O and properties according to Properties (as if called through Object.defineProperties).
不考虑Properties的部分,那么Object.create可以用以下过程来描述:
通过这个create方法,我们可以创建任意的、足够长的原型链,然后把它“赋值给”某个构造器函数,使“原型链创建过程”与“构造器中的初始化过程”二者分解开来。例如:
这样一来,我们没有显式地声明MyObject()与MyObjectEx()之间的继承关系,但B与C的原型关系维护了它们之间的类属关系。因此:
三、new()过程中的构造器调用
对于构造器MyObject()来说,在new()过程中会被调用一次。例如此前提到的:
new()过程将以刚刚创建的实例为this来调用MyObject(),这使得我们有机会为这个实例(this)做一些初始化操作。这个行为其实来自于最最早期的JavaScript设计。在1995年底发布的Netscapes Navigator 2.0 beta以及其后的NN2.0正式版中,这个最原始版本的JavaScript都还没有“原型继承”的设计,但已经有了new这个运算。这时所谓新对象的创建,就是不断地为this赋值而已,只不过new会为产生的对象维护<obj>.constructor属性。
这件事是很容易做到的。在高版本中的Function已经提供了Function.call()和Function.apply()方法,能很方便的重现这一过程。因此:
就可以被分解为如下两个步骤(fixed at 2010.12.30. note: 接受book_LoveLiness的意见,将constructor的改写过程放在原型阶段):
对于上述{constructor: MyObject},内部隐含了如下两个过程:
当用户使用自己的构造器来创建MyObject的原型链——例如MyObject()的父类是ExtObject(),而不是Object()的时候,就会用到这样的分解了。
即使对于最早期没有call()/apply()方法,也没有原型继承的JavaScript,上述过程也可以分解为:
当然,这一过程也就与原型继承没有了关系——我们假定这就是JavaScript 1.0的时代吧。
四、构造过程分解的意义
因为原型继承(包括原型链维护)与构造器调用是可以分开的,所以我们事实上也可以只使用二者之一来创建任何复杂的过程。对于大型OOP框架来说,“是否需要维护原型链”是一个深入的话题。例如一些早期的OOP框架就不管不顾,只考虑子类对象对父类的“相似性”,而无视instanceof运算的效果,这样的时代大概可以追溯到2004年以及之前。后来OOP框架意识JavaScript的原型系统的重要性,因此又回到正途上来,
- 通过类似于Object.create()的方案,来保证原型链的有效性;又
- 通过独立的类创建或对象创建过程,来保证系统的可扩展性。
这一切,就是我们现在的JS OOP的来源了。
在一些具体的方法中,有许多变形的实现。在QoBean中的Unique()函数综合地重现了整个过程(fixed at 2010.12.30,考虑到无args参数的情况):
对于任何一个对象A来说,可以用它为原型创建一个新的实例:
或在创建之后,使用函数MyObject()来初始化:
或在上述初始化中使用特定的参数:
最后,考虑到reutrn valueType的new的情况,根据Unique()给出一个新的_new函数(添加于2013.06.03):
最最最后,考虑到Object可能被第三方代码重写导致instanceof判断失误的情况,可以给出强力版本的_new(添加于2013.06.03):
一、JavaScript构造器与构造过程的特点
JavaScript中通过以下方式声明和使用构造器:
function MyObject() { this.xxx = 1; } MyObject.prototype.yyy = 2; obj1 = new MyObject(); obj2 = new MyObject();
其中xxx与yyy的不同在于:对于obj1和obj2来说,yyy是相同的属性的不同引用,不同对象实例的初始值总是相同的;而xxx则是各自不同的引用,每个对象实例都不同。上例中使用1、2这样的值类型数据,并不足以体现二者的区别。当我们而使用引用类型(例如数组),就很容易看出二者的区别来了。例如:
function MyObject() { this.xxx = new Array(); } MyObject.prototype.yyy = new Array(); obj1 = new MyObject(); obj2 = new MyObject(); obj1.xxx.push('abc'); obj1.yyy.push('abc'); // 显示1,0,表明obj2.xxx并没有变化 alert([obj1.xxx.length, obj2.xxx.length]); // 显示1,1,表明obj2.yyy同时变化,与obj1.yyy是同一个数组 alert([obj1.yyy.length, obj2.yyy.length]);
使用原型的另一个必要在于继承树的建立。这一过程要求new运算符的参与,简单说来,就是"子类.prototype"必须赋值为"new 父类()"所产生的一个实例。例如:
function MyObjectEx() { } MyObjectEx.prototype = new MyObject(); MyObjectEx.prototype.constructor = MyObjectEx;
在这个过程中,最后面的是一行修正代码。这行修正的必要性在于:new MyObject()所产生的实例(例如x)的属性x.constructor总是指向MyObject,而在子类MyObjectEx()中,它应该是指向MyObjectEx的。
OK。这就是全部的基础知识,下面我们来分解这一过程。
二、new()过程中的原型链维护
new总是因为建立原型继承树而存在的,如果没有new过程参与,则当
obj = new MyObjecEx()
时,我们无法通过instanceof运算:
obj instanceof MyObject
来了解obj在继承树上的关系。
但是许多人并不知道,事实上这一过程并不需要MyObject的参与。因为instanceof只检查prototype链,并不检查函数本身。举例来说:
P = {}; X = function() {} Y = function() {} X.prototype = P; Y.prototype = P; obj = new Y(); alert(obj instanceof X); // 显示true
在这个示例中,obj创建自Y(),但只因为X.prototype(或者它的原型链中)指向原型P,所以最后显示obj也是X的一个实例。可见instanceof的检测与obj是否创建自函数X是无关的,并不检查函数自身。
换而言之,new()在上述过程中,只有“维护原型链”的作用。那么,我们事实上也可以手工来维护这个原型链。这一点,事实上也就是ECMA Script 5th中的Object.create()出现的原因。
Object.create(O[, Properties]) returns an object with a prototype of O and properties according to Properties (as if called through Object.defineProperties).
不考虑Properties的部分,那么Object.create可以用以下过程来描述:
Object.create = function(O) { function F() {}; F.prototype = O; return new F(); }
通过这个create方法,我们可以创建任意的、足够长的原型链,然后把它“赋值给”某个构造器函数,使“原型链创建过程”与“构造器中的初始化过程”二者分解开来。例如:
A = {}; B = Object.create(A); C = Object.create(B); A.x = 1; B.y = 2; C.z = 3; function MyObject() { } function MyObjectEx() { } MyObject.prototype = B; MyObjectEx.prototype = C;
这样一来,我们没有显式地声明MyObject()与MyObjectEx()之间的继承关系,但B与C的原型关系维护了它们之间的类属关系。因此:
obj1 = new MyObject(); obj2 = new MyObjectEx(); // 显示true, obj2是MyObject()的子类的实例 alert(obj2 instanceof MyObject); // 显示false, z属性不会出现在MyObject()的实例中 alert('z' in obj1);
三、new()过程中的构造器调用
对于构造器MyObject()来说,在new()过程中会被调用一次。例如此前提到的:
function MyObject() { this.xxx = 1; }
new()过程将以刚刚创建的实例为this来调用MyObject(),这使得我们有机会为这个实例(this)做一些初始化操作。这个行为其实来自于最最早期的JavaScript设计。在1995年底发布的Netscapes Navigator 2.0 beta以及其后的NN2.0正式版中,这个最原始版本的JavaScript都还没有“原型继承”的设计,但已经有了new这个运算。这时所谓新对象的创建,就是不断地为this赋值而已,只不过new会为产生的对象维护<obj>.constructor属性。
这件事是很容易做到的。在高版本中的Function已经提供了Function.call()和Function.apply()方法,能很方便的重现这一过程。因此:
obj = new MyObject(x,y,z);
就可以被分解为如下两个步骤(fixed at 2010.12.30. note: 接受book_LoveLiness的意见,将constructor的改写过程放在原型阶段):
obj = Object.create({ constructor: MyObject }); MyObject.call(obj, x, y, z);
对于上述{constructor: MyObject},内部隐含了如下两个过程:
o = new Object(); o.constructor = MyObject;
当用户使用自己的构造器来创建MyObject的原型链——例如MyObject()的父类是ExtObject(),而不是Object()的时候,就会用到这样的分解了。
即使对于最早期没有call()/apply()方法,也没有原型继承的JavaScript,上述过程也可以分解为:
obj = new Object(); obj.constructor = MyObject; obj.constructor(a, b, c);
当然,这一过程也就与原型继承没有了关系——我们假定这就是JavaScript 1.0的时代吧。
四、构造过程分解的意义
因为原型继承(包括原型链维护)与构造器调用是可以分开的,所以我们事实上也可以只使用二者之一来创建任何复杂的过程。对于大型OOP框架来说,“是否需要维护原型链”是一个深入的话题。例如一些早期的OOP框架就不管不顾,只考虑子类对象对父类的“相似性”,而无视instanceof运算的效果,这样的时代大概可以追溯到2004年以及之前。后来OOP框架意识JavaScript的原型系统的重要性,因此又回到正途上来,
- 通过类似于Object.create()的方案,来保证原型链的有效性;又
- 通过独立的类创建或对象创建过程,来保证系统的可扩展性。
这一切,就是我们现在的JS OOP的来源了。
在一些具体的方法中,有许多变形的实现。在QoBean中的Unique()函数综合地重现了整个过程(fixed at 2010.12.30,考虑到无args参数的情况):
function Unique(obj, func, args) { function F() {} F.prototype = obj; return func ? func.apply(new F, args||[]) : new F; }
对于任何一个对象A来说,可以用它为原型创建一个新的实例:
x = Unique(A);
或在创建之后,使用函数MyObject()来初始化:
y = Unique(A, MyObject);
或在上述初始化中使用特定的参数:
z = Unique(A, MyObject, [a, b, c]);
最后,考虑到reutrn valueType的new的情况,根据Unique()给出一个新的_new函数(添加于2013.06.03):
function _new(obj, func, args) { function F() {} F.prototype = obj, F = new F; obj = func ? func.apply(F, args||[]) : F; return (obj instanceof Object) ? obj || F : F; }
最最最后,考虑到Object可能被第三方代码重写导致instanceof判断失误的情况,可以给出强力版本的_new(添加于2013.06.03):
function _new(obj, func, args) { function F() {} F.prototype = obj, F = new F; obj = func ? func.apply(F, args||[]) : F; return (typeof obj).match(/object|function/) ? obj || F : F; }
相关文章推荐
- 前端要给力之:分解对象构造过程new()
- 前端要给力之:分解对象构造过程new()
- java 使用new新建一个对象时的操作过程
- java对象构造过程
- 对象的构造和初始化过程
- JavaScript new对象的四个过程实例浅析
- 深入理解利用new创建对象的执行过程以Person p=new Person("张三",20);为例
- 面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了;面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为(转)
- 对象的初始化、继承时,对象的初始化过程、关于构造方法、抽象类……
- 子类对象构造过程5_2
- javascript中构造函数的返回值问题和new对象的过程
- 当类中的方法全部都是 static 关键字修饰时 ,它的构造方法最好作为 private 私有化,理由是方法全是 static, 不知道的人会去new对象去调用,需要调用构造方法。 但 static的方法直接用类名调用就行!
- .ctor,.cctor 以及 对象的构造过程(上)
- Java第二课 Java面向对象编程,面向对象编程和面向过程编程的区别,我们如何才能掌握面向对象的编程,类和对象的关系;讲解了Java中的构造、重载、this和super变量、静态变量、Java中的常
- 深入理解利用new创建对象的执行过程以Person p=new Person("张三",20);为例
- Java之对象构造过程
- Java中用字符串常量赋值和使用new构造String对象的区别
- static静态方法可以被继承吗?Student a= new Student(); new一个对象的过程发生了什么?
- new一个对象的过程发生了什么
- 构造方法以及对象初始化过程