Showing posts with label __proto__. Show all posts
Showing posts with label __proto__. Show all posts

Sunday, June 20, 2010

relationship of constructor and prototype in javascript


var animal = function(status) {
this.status = status;
this.breathes = "yes";
this.action = function() {
console.log('flying...')
};
},
human = function() {
this.name = 'human';
},
cat = function() {
this.type = 'cat';
};

// javascript支持原型继承,这种方式比类继承更强大,类继承中一个对象可以继承结构和行为,而原型继承可以继承结构和行为之外,并可以继承一个对象的状态
// new一个animal的实例对象作为cat.prototype的原型,这个animal实例对象就成为cat的实例对象原型链上的一员
// __proto__这个魔法属性在这些浏览器不能工作: ie 6/7/8, safari < 5, opera < 10.50
//当在cat的某个实例上检索一个属性时,如果在其本身中没有找到,则会延着原型链向上检索,如下例子中的c.__proto__即为一个animal对象
//如果检索c.breathes,如果在c对象本身没有找到此属性,则会检索t.__proto__.breathes、t.__proto__.__proto__.breathes等原型链上的对象,直到找到为止,没找到返回undefined
cat.prototype = new animal("live");
//cat继承的原型对象是具有特定状态的animal对象
var c = new cat();
console.log(cat.prototype);
console.log(cat.prototype.constructor.tostring());
console.log(c.constructor.tostring());
console.log("cat breathes:" + c.breathes);
console.log("c.__proto__:", c.__proto__);
//ie不支持此属性
//你可以利用object.__proto__这个魔法属性修改当前对象的原型,下面将一只猫猫化为人形
var d = new cat();
d.__proto__ = new human();
console.log("d.__proto__:", d.__proto__);
//从上面结果可以看到cat的实例c.constructor不是指向cat这个构造函数,而是animal构造函数
//需要修改对象的constructor为其构造函数本身
//当一个函数对象被创建时,function构造器产生的函数对象会运行类似这样的一些代码:this.prototype = {constructor: this},参考javascript: the good parts 5.1节说明
//新函数对象被赋予一个prototype属性,其值是包含一个constuctor属性,并且其属性值为此新函数对象本身
//但是通过原型方式继承时,会给prototype重新赋予一个新对象,此prototype对象中的constructor是指向其自身的构造函数,而不是新函数的,所以需要重置其fn.prototype.constructor = this
//参考javascript权威指南第五版example 9-3. subclassing a javascript class
cat.prototype.constructor = cat;
console.log(cat.prototype.constructor.tostring());
console.log(c.constructor.tostring());
console.log(c.__proto__);
var tostring = object.prototype.tostring;
language = function() {
this.type = "programming";
return {
"locale": "en",
"class-free": function() {
return false
},
"tostring": function() {
return tostring.apply(this, arguments)
}
// 如果tostring方法被重写成非function对象,则后面console中无法输出对象j
}
},
javascript = function() {
this.value = "javascript";
this["class-free"] = function() {
return true
};
};
language.prototype = {
a: 1,
b: 2
};
javascript.prototype = new language();
var j = new javascript();
console.log(j);
console.log(j.__proto__);
//locale: en,此处因为language构造函数返回不是this,而是另一个object直接量,而object直接的构造方法为object(),因此language的原型被丢失了
console.log(language.prototype);
console.log(javascript.prototype);
console.log(j.constructor.tostring());
//function object() { [native code] }


构造函数与其返回值


构造函数会返回一个对象,如果没有直接return语句,构造函数会自动返回当前对象:"return this;",也可以返回一个对象直接量,而不返回this,这样会中断正常的原型链。

prototype.js中class对象定义是封装在一个匿名函数里的,从而使得其内部变量和方法与外界隔离,其中有二句代码为:

function subclass() {};
subclass.prototype = parent.prototype;

因为parent的构造可能返回语句不是返回this对象,而是返回了一个其他的对象,如{tostring:true},如果不用subclass.prototype=parent.prototype这样写,可能这样会丢失原型链上的方法和属性,通过subclass这个空构造将parent.prototype引用到自身的prototype上,从而保持住部分原型链。
这其实也已经不是原型继承了,因为它不是通过new parent()来获取原型对象,丢失了new parent所得对象中的属性和方法。
prototype中的class其实放弃了原型对象,只是简单的继承了parent.prototype对象,已经失去原型继承可以继承对象状态的功能,这样操作其实是很好的模似了类继承方式。

var class = (function() {
function subclass() {};
function create() {
var parent = null,
properties = $a(arguments);
if (object.isfunction(properties[0])) parent = properties.shift();

function klass() {
this.initialize.apply(this, arguments);
}

object.extend(klass, class.methods);
klass.superclass = parent;
klass.subclasses = [];

if (parent) {
// 因为parent的构造可能返回对象直接量,而不是返回this,如{tostring:true}
subclass.prototype = parent.prototype;
klass.prototype = new subclass;
parent.subclasses.push(klass);
}

for (var i = 0; i < properties.length; i++) klass.addmethods(properties[i]);
if (!klass.prototype.initialize) klass.prototype.initialize = prototype.emptyfunction;
klass.prototype.constructor = klass;
return klass;
}
function addmethods(source) {
var ancestor = this.superclass && this.superclass.prototype;
var properties = object.keys(source);
if (!object.keys({
tostring: true
}).length) {
if (source.tostring != object.prototype.tostring) properties.push("tostring");
if (source.valueof != object.prototype.valueof) properties.push("valueof");
}
for (var i = 0, length = properties.length; i < length; i++) {
var property = properties[i],
value = source[property];
if (ancestor && object.isfunction(value) && value.argumentnames().first() == "$super") {
var method = value;
value = (function(m) {
return function() {
return ancestor[m].apply(this, arguments);
};
})(property).wrap(method);
value.valueof = method.valueof.bind(method);
value.tostring = method.tostring.bind(method);
}
this.prototype[property] = value;
}
return this;
}
return {
create: create,
methods: {
addmethods: addmethods
}
};
})();

Wednesday, July 25, 2007

instance __proto__ chain of javascript


The term instance has a specific technical meaning in class-based languages. In these languages, an instance is an individual member of a class and is fundamentally different from a class. In JavaScript, "instance" does not have this technical meaning because JavaScript does not have this difference between classes and instances. However, in talking about JavaScript, "instance" can be used informally to mean an object created using a particular constructor function.
Object Properties
This section discusses how objects inherit properties from other objects in the prototype chain and what happens when you add a property at run time.
Inheriting Properties
Suppose you create the mark object as a WorkerBee (as shown in image) with the following statement:
mark = new WorkerBee;



When JavaScript sees the new operator, it creates a new generic object and passes this new object as the value of the this keyword to the WorkerBee constructor function. The constructor function explicitly sets the value of the projects property, and implicitly sets the value of the internal __proto__ property to the value of WorkerBee.prototype. (That property name has two underscore characters at the front and two at the end.) The __proto__ property determines the prototype chain used to return property values. Once these properties are set, JavaScript returns the new object and the assignment statement sets the variable mark to that object. This process does not explicitly put values in the mark object (local values) for the properties that mark inherits from the prototype chain. When you ask for the value of a property, JavaScript first checks to see if the value exists in that object. If it does, that value is returned. If the value is not there locally, JavaScript checks the prototype chain (using the __proto__ property). If an object in the prototype chain has a value for the property, that value is returned. If no such property is found, JavaScript says the object does not have the property. In this way, the mark object has the following properties and values:
mark.name = "";
mark.dept = "general";
mark.projects = [];
The mark object inherits values for the name and dept properties from the prototypical object in mark.__proto__. It is assigned a local value for the projects property by the WorkerBee constructor. This gives you inheritance of properties and their values in JavaScript. Some subtleties of this process are discussed in Property Inheritance Revisited. Because these constructors do not let you supply instance-specific values, this information is generic. The property values are the default ones shared by all new objects created from WorkerBee. You can, of course, change the values of any of these properties. So, you could give specific information for mark as follows:
mark.name = "Doe, Mark";
mark.dept = "admin";
mark.projects = ["navigator"];


Adding Properties
In JavaScript, you can add properties to any object at run time. You are not constrained to use only the properties provided by the constructor function. To add a property that is specific to a single object, you assign a value to the object, as follows:
mark.bonus = 3000;
Now, the mark object has a bonus property, but no other WorkerBee has this property.
If you add a new property to an object that is being used as the prototype for a constructor function, you add that property to all objects that inherit properties from the prototype. For example, you can add a specialty property to all employees with the following statement:
Employee.prototype.specialty = "none";
As soon as JavaScript executes this statement, the mark object also has the specialty property with the value of "none". The following figure shows the effect of adding this property to the Employee prototype and then overriding it for the Engineer prototype.


Determining Instance Relationships
You may want to know what objects are in the prototype chain for an object, so that you can tell from what objects this object inherits properties.
Starting with JavaScript 1.4, JavaScript provides an instanceof operator to test the prototype chain. This operator works exactly like the instanceof function discussed below. As discussed in Inheriting Properties, when you use the new operator with a constructor function to create a new object, JavaScript sets the __proto__ property of the new object to the value of the prototype property of the constructor function. You can use this to test the prototype chain. For example, suppose you have the same set of definitions already shown, with the prototypes set appropriately. Create a __proto__ object as follows:
chris = new Engineer("Pigman, Chris", ["jsd"], "fiji");
With this object, the following statements are all true:
chris.__proto__ == Engineer.prototype;
chris.__proto__.__proto__ == WorkerBee.prototype;
chris.__proto__.__proto__.__proto__ == Employee.prototype;
chris.__proto__.__proto__.__proto__.__proto__ == Object.prototype;
chris.__proto__.__proto__.__proto__.__proto__.__proto__ == null;
Given this, you could write an instanceOf function as follows:
function instanceOf(object, constructor) {
while (object != null) {
if (object == constructor.prototype)
return true;
object = object.__proto__;
}
return false;
}
With this definition, the following expressions are all true:
instanceOf (chris, Engineer)
instanceOf (chris, WorkerBee)
instanceOf (chris, Employee)
instanceOf (chris, Object)
But the following expression is false:
instanceOf (chris, SalesPerson)


Global Information in Constructors
When you create constructors, you need to be careful if you set global information in the constructor. For example, assume that you want a unique ID to be automatically assigned to each new employee. You could use the following definition for Employee:
var idCounter = 1;function Employee (name, dept) {
this.name = name || "";
this.dept = dept || "general";
this.id = idCounter++;
}
With this definition, when you create a new Employee, the constructor assigns it the next ID in sequence and then increments the global ID counter. So, if your next statement is the following, victoria.id is 1 and harry.id is 2:
victoria = new Employee("Pigbert, Victoria", "pubs")
harry = new Employee("Tschopik, Harry", "sales")
At first glance that seems fine. However, idCounter gets incremented every time an Employee object is created, for whatever purpose. If you create the entire Employee hierarchy shown in this chapter, the Employee constructor is called every time you set up a prototype. Suppose you have the following code:
var idCounter = 1;
function Employee (name, dept) {
this.name = name || "";
this.dept = dept || "general";
this.id = idCounter++;
}
function Manager (name, dept, reports) {...}
Manager.prototype = new Employee;
function WorkerBee (name, dept, projs) {...}
WorkerBee.prototype = new Employee;
function Engineer (name, projs, mach) {...}
Engineer.prototype = new WorkerBee;
function SalesPerson (name, projs, quota) {...}
SalesPerson.prototype = new WorkerBee;
mac = new Engineer("Wood, Mac");
Further assume that the definitions omitted here have the base property and call the constructor above them in the prototype chain. In this case, by the time the mac object is created, mac.id is 5. Depending on the application, it may or may not matter that the counter has been incremented these extra times. If you care about the exact value of this counter, one possible solution involves instead using the following constructor:
function Employee (name, dept) {
this.name = name || "";
this.dept = dept || "general";
if (name)
this.id = idCounter++;
}
When you create an instance of Employee to use as a prototype, you do not supply arguments to the constructor. Using this definition of the constructor, when you do not supply arguments, the constructor does not assign a value to the id and does not update the counter. Therefore, for an Employee to get an assigned id, you must specify a name for the employee. In this example, mac.id would be 1.


function doc(arg){
document.write(arg);
document.write('
');
}

function Shape() {
this.name = 'shape';
this.borderWidth = 5;
}

function Square() {
this.edge = 12;
}

Square.prototype = new Shape;

myPicture = new Square;

doc($H(Square.prototype).inspect().escapeHTML());
doc($H(myPicture.__proto__).inspect().escapeHTML());

doc(myPicture.constructor);
doc(myPicture.__proto__.constructor);
doc(myPicture.__proto__.__proto__.constructor);
doc(myPicture.__proto__.__proto__.__proto__.constructor);

doc(myPicture.borderWidth);