`
xplazy
  • 浏览: 43666 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论
阅读更多

OOP in JS, Part 1 : Public/Private Variables and Methods

This page shows how to create private variables and methods in classes in Javascript through the rather simple example of a person. Part 2 covers inheritance.

Summary

  • private variables are declared with the 'var' keyword inside the object, and can only be accessed by private functions and privileged methods.
  • private functions are declared inline inside the object's constructor (or alternatively may be defined via var functionName=function(){...} ) and may only be called by privileged methods (including the object's constructor).
  • privileged methods are declared with this.methodName=function(){...} and may invoked by code external to the object.
  • public properties are declared with this.variableName and may be read/written from outside the object.
  • public methods are defined by Classname.prototype.methodName = function(){...} and may be called from outside the object.
  • prototype properties are defined by Classname.prototype.propertyName = someValue
  • static properties are defined by Classname.propertyName = someValue

Example

In this example, a person's name and race are set at birth and may never be changed. When created, a person starts out at year 1 and a hidden maximum age is determined for that person. The person has a weight which is modified by eating (tripling their weight) or exercising (halfing it). Every time the person eats or exercises, they grow a year older. The person object has a publicly accessible 'clothing' property which anyone can modify, as well as a dirtFactor which can be modified manually (throwing dirt on or scrubbing it off), but which increases every time the person eats or exercises, and is reduced by the use of the shower() method.

Run Gavin's Life <- click to run the example code below (the code block being run appears after the Person constructor).

The Example Code

function Person(n,race){ 	this.constructor.population++;  	// ************************************************************************ 	// PRIVATE VARIABLES AND FUNCTIONS 	// ONLY PRIVELEGED METHODS MAY VIEW/EDIT/INVOKE 	// *********************************************************************** 	var alive=true, age=1; 	var maxAge=70+Math.round(Math.random()*15)+Math.round(Math.random()*15); 	function makeOlder(){ return alive = (++age <= maxAge) }  	var myName=n?n:"John Doe"; 	var weight=1;   	// ************************************************************************ 	// PRIVILEGED METHODS 	// MAY BE INVOKED PUBLICLY AND MAY ACCESS PRIVATE ITEMS 	// MAY NOT BE CHANGED; MAY BE REPLACED WITH PUBLIC FLAVORS 	// ************************************************************************ 	this.toString=this.getName=function(){ return myName }  	this.eat=function(){ 		if (makeOlder()){ 			this.dirtFactor++; 			return weight*=3; 		} else alert(myName+" can't eat, he's dead!"); 	} 	this.exercise=function(){ 		if (makeOlder()){ 			this.dirtFactor++; 			return weight/=2; 		} else alert(myName+" can't exercise, he's dead!"); 	} 	this.weigh=function(){ return weight } 	this.getRace=function(){ return race } 	this.getAge=function(){ return age } 	this.muchTimePasses=function(){ age+=50; this.dirtFactor=10; }   	// ************************************************************************ 	// PUBLIC PROPERTIES -- ANYONE MAY READ/WRITE 	// ************************************************************************ 	this.clothing="nothing/naked"; 	this.dirtFactor=0; }   // ************************************************************************ // PUBLIC METHODS -- ANYONE MAY READ/WRITE // ************************************************************************ Person.prototype.beCool = function(){ this.clothing="khakis and black shirt" } Person.prototype.shower = function(){ this.dirtFactor=2 } Person.prototype.showLegs = function(){ alert(this+" has "+this.legs+" legs") } Person.prototype.amputate = function(){ this.legs-- }   // ************************************************************************ // PROTOTYOPE PROERTIES -- ANYONE MAY READ/WRITE (but may be overridden) // ************************************************************************ Person.prototype.legs=2;   // ************************************************************************ // STATIC PROPERTIES -- ANYONE MAY READ/WRITE // ************************************************************************ Person.population = 0;    // Here is the code that uses the Person class function RunGavinsLife(){ 	var gk=new Person("Gavin","caucasian");       //New instance of the Person object created. 	var lk=new Person("Lisa","caucasian");        //New instance of the Person object created. 	alert("There are now "+Person.population+" people");  	gk.showLegs(); lk.showLegs();                 //Both share the common 'Person.prototype.legs' variable when looking at 'this.legs'  	gk.race = "hispanic";                         //Sets a public variable, but does not overwrite private 'race' variable. 	alert(gk+"'s real race is "+gk.getRace());    //Returns 'caucasian' from private 'race' variable set at create time. 	gk.eat(); gk.eat(); gk.eat();                 //weight is 3...then 9...then 27 	alert(gk+" weighs "+gk.weigh()+" pounds and has a dirt factor of "+gk.dirtFactor);  	gk.exercise();                                //weight is now 13.5 	gk.beCool();                                  //clothing has been update to current fashionable levels 	gk.clothing="Pimp Outfit";                    //clothing is a public variable that can be updated to any funky value 	gk.shower(); 	alert("Existing shower technology has gotten "+gk+" to a dirt factor of "+gk.dirtFactor);  	gk.muchTimePasses();                          //50 Years Pass 	Person.prototype.shower=function(){           //Shower technology improves for everyone 		this.dirtFactor=0; 	} 	gk.beCool=function(){                         //Gavin alone gets new fashion ideas 		this.clothing="tinfoil"; 	};  	gk.beCool(); gk.shower(); 	alert("Fashionable "+gk+" at " 		+gk.getAge()+" years old is now wearing " 		+gk.clothing+" with dirt factor " 		+gk.dirtFactor);  	gk.amputate();                                //Uses the prototype property and makes a public property 	gk.showLegs(); lk.showLegs();                 //Lisa still has the prototype property  	gk.muchTimePasses();                          //50 Years Pass...Gavin is now over 100 years old. 	gk.eat();                                     //Complains about extreme age, death, and inability to eat. }

Notes

  • maxAge is a private variable with no privileged accessor method; as such, there is no way to publicly get or set it.
  • race is a private variable defined only as an argument to the contructor. Variables passed into the constructor are available to the object as private variables.
  • The 'tinfoil' beCool() fashion method was applied only to the gk object, not the entire Person class. Other people created and set to beCool() would still use the original 'khakis and black shirt' clothing that Gavin eschewed later in life.
  • Note the implicit call to the gk.toString() method when using string concatenation. It is this which allows the code alert(gk+' is so cool.') to put the word 'Gavin' in there, and is equivalent to alert(gk.toString()+' is so cool.') . Every object of every type in JS has a .toString() method, but you can override it with your own.
  • You cannot (to my knowledge) assign public methods of a class inside the main object constructor...you must use the prototype property externally, as above with the beCool() and shower() methods.
  • As I attempted to show with the Person.prototype.legs property and the amputate() function, prototype properties are shared by all object instances. Asking for lk.legs yields '2' by looking at the single prototype property. However, attempting to change this value using either gk.legs=1 or (in the Person object) this.legs=1 ends up making a new public property of the object specific to that instance. (This is why calling gk.amputate() only removed a leg from Gavin, but not Lisa.) To modify a prototype property, you must use Person.prototype.legs=1 or something like this.constructor.prototype.legs=1 . (I say 'something like' because I discovered that this.constructor is not available inside private functions of the object, since this refers to the window object in that scope.)
  • Wherever an anonymous function is declared inline with
    foo = function(p1,p2){ some code }
    the new Function() constructor is NOT equivalent, e.g.
    foo = new Function('p1','p2','code');
    since the latter runs in the global scope--instead of inheriting the scope of the constructor function--thus preventing it from accessing the private variables.
  • As noted above in the code comments, the act of setting gk.race to some value did NOT overwrite the private race variable. Although it would be a dumb idea, you can have both private and public variables with the same name. For example, the yell() method in the following class will yield different values for foo and this.foo :
    function StupidClass(){   var foo = "internal";   this.foo = "external";   this.yell=function(){ alert("Internal foo is "+foo+"\nExternal foo is "+this.foo) } }
    
    
  • Private functions and privileged methods, like private variables and public properties, are instantiated with each new object created. So each time new Person() is called, new copies of makeOlder() , toString() , getName() , eat() , exercise() , weigh() , getRace() , getAge() , and muchTimePasses() are created. For every Person, each time. Contrast this with public methods (only one copy of beCool() and shower() exist no matter how many Person objects are created) and you can see that for memory/performance reasons it can be preferable to give up some degree of object protection and instead use only public methods.

    Note that doing so requires making private variables public (since without privileged accessor methods there would be no way to use them) so the public methods can get at them...and which also allows external code to see/destroy these variables. The memory/performance optimization of using only public properties and methods has consequences which may make your code less robust.

    For example, in the above age and maxAge are private variables; age can only be accessed externally through getAge() (it cannot be set) and maxAge cannot be read or set externally. Changing those to be public properties would allow any code to do something like gk.maxAge=1; gk.age=200; which not only does it not make sense (you shouldn't be able to manipulate someone's age or lifespan directly), but by setting those values directly the alive variable wouldn't properly be updated, leaving your Person object in a broken state.

<script type="text/javascript"><!----></script>

OOP in JS, Part 2 : Inheritance

In Part 1 we saw how to create classes in JS, including private, privileged, and public properties and methods. This section discusses inheritance in Javascript.

Summary

  • You cause a class to inherit using ChildClassName.prototype = new ParentClass(); .
  • You need to remember to reset the constructor property for the class using ChildClassName.prototype.constructor=ChildClassName .
  • You can call ancestor class methods which your child class has overridden using the Function.call() method.
  • Javascript does not support protected methods.

Example

To jump right into it, following is a sample showing inheritance between two classes:

function Mammal(name){ 	this.name=name; 	this.offspring=[]; } Mammal.prototype.haveABaby=function(){ 	var newBaby=new Mammal("Baby "+this.name); 	this.offspring.push(newBaby); 	return newBaby; } Mammal.prototype.toString=function(){ 	return '[Mammal "'+this.name+'"]'; }   Cat.prototype = new Mammal();        // Here's where the inheritance occurs Cat.prototype.constructor=Cat;       // Otherwise instances of Cat would have a constructor of Mammal function Cat(name){ 	this.name=name; } Cat.prototype.toString=function(){ 	return '[Cat "'+this.name+'"]'; }   var someAnimal = new Mammal('Mr. Biggles'); var myPet = new Cat('Felix'); alert('someAnimal is '+someAnimal);   // results in 'someAnimal is [Mammal "Mr. Biggles"]' alert('myPet is '+myPet);             // results in 'myPet is [Cat "Felix"]'  myPet.haveABaby();                    // calls a method inherited from Mammal alert(myPet.offspring.length);        // shows that the cat has one baby now alert(myPet.offspring[0]);            // results in '[Mammal "Baby Felix"]'  

Using the .constructor property

Look at the last line in the above example. The baby of a Cat should be a Cat, right? While the haveABaby() method worked, that method specifically asks to create a new Mammal . While we could make a new haveABaby() method for the Cat subclass like this.offspring.push(new Cat("Baby "+this.name)) , it would be better to have the ancestor class make an object of the correct type.

Every object instance in JS has a property named constructor that points to its parent class. For example, someAnimal.constructor==Mammmal is true. Armed with this knowledge, we can remake the haveABaby() method like this:

Mammal.prototype.haveABaby=function(){ 	var newBaby=new this.constructor("Baby "+this.name); 	this.offspring.push(newBaby); 	return newBaby; } ... myPet.haveABaby();                    // Same as before: calls the method inherited from Mammal alert(myPet.offspring[0]);            // Now results in '[Cat "Baby Felix"]' 

Calling 'super' methods

Let's extend the example now so that when baby kittens are created, they 'mew' right after being born. To do this, we want to write our own custom Cat.prototype.haveABaby() method, which is able to call the original Mammal.prototype.haveABaby() method:

Cat.prototype.haveABaby=function(){ 	Mammal.prototype.haveABaby.call(this); 	alert("mew!"); }

The above may look a little bit bizarre. Javascript does not have any sort of 'super' property, which would point to its parent class. Instead, you use the call() method of a Function object, which allows you to run a function using a different object as context for it. If you needed to pass parameters to this function, they would go after the 'this'. For more information on the Function.call() method, see the MSDN docs for call() .

Making your own 'super' property

Rather than having to know that Cat inherits from Mammal , and having to type in Mammal.prototype each time you wanted to call an ancestor method, wouldn't it be nice to have your own property of the cat pointing to its ancestor class? Those familiar with other OOP languages may be tempted to call this property 'super', but JS reserves this word for future use. The word 'parent', while used in some DOM items, is free for the JS language itself, so let's call it parent in this example:

Cat.prototype = new Mammal(); Cat.prototype.constructor=Cat; Cat.prototype.parent = Mammal.prototype; ... Cat.prototype.haveABaby=function(){ 	var theKitten = this.parent.haveABaby.call(this); 	alert("mew!"); 	return theKitten; } 

Spoofing pure virtual classes

Some OOP languages have the concept of a pure virtual class...one which cannot be instantiated itself, but only inherited from. For example, you might have a LivingThing class which Mammal inherited from, but you didn't want someone to be able to make a LivingThing without specifying what type of thing it was. You can do this in JS by making the virtual class an object instead of a function.

The following example shows how this could be used to simulate a pure virtual ancestor:

LivingThing = { 	beBorn : function(){ 		this.alive=true; 	} } ... Mammal.prototype = LivingThing; Mammal.prototype.parent = LivingThing;   //Note: not 'LivingThing.prototype' Mammal.prototype.haveABaby=function(){ 	this.parent.beBorn.call(this); 	var newBaby=new this.constructor("Baby "+this.name); 	this.offspring.push(newBaby); 	return newBaby; } 

With the above, doing something like var spirit = new LivingThing() would result in an error, since LivingThing is not a function, and hence can't be used as a constructor.

Convenient Inheritance

Rather than writing 3 lines every time you want to inherit one class from another, it's convenient to extend the Function object to do it for you:

Function.prototype.inheritsFrom = function( parentClassOrObject ){ 	if ( parentClassOrObject.constructor == Function ) 	{ 		//Normal Inheritance 		this.prototype = new parentClassOrObject; 		this.prototype.constructor = this; 		this.prototype.parent = parentClassOrObject.prototype; 	} 	else 	{ 		//Pure Virtual Inheritance 		this.prototype = parentClassOrObject; 		this.prototype.constructor = this; 		this.prototype.parent = parentClassOrObject; 	} 	return this; } // // LivingThing = { 	beBorn : function(){ 		this.alive = true; 	} } // // function Mammal(name){ 	this.name=name; 	this.offspring=[]; } Mammal.inheritsFrom( LivingThing ); Mammal.prototype.haveABaby=function(){ 	this.parent.beBorn.call(this); 	var newBaby = new this.constructor( "Baby " + this.name ); 	this.offspring.push(newBaby); 	return newBaby; } // // function Cat( name ){ 	this.name=name; } Cat.inheritsFrom( Mammal ); Cat.prototype.haveABaby=function(){ 	var theKitten = this.parent.haveABaby.call(this); 	alert("mew!"); 	return theKitten; } Cat.prototype.toString=function(){ 	return '[Cat "'+this.name+'"]'; } // // var felix = new Cat( "Felix" ); var kitten = felix.haveABaby( ); // mew! alert( kitten );                 // [Cat "Baby Felix"] 

Just make sure you call this method immediately after your constructor, before you extend the prototype for the object.

Protected methods?

Some OOP languages have the concept of 'protected' methods—methods that exist in a parent or ancestor class that can only be called by descendants of the object (on each other), but not by external objects. These are not supported in JS. If you need such, you will have to write your own framework, ensuring that each class has a 'parent' or some such property, and walking up the tree to find ancestors and checking whether or not the calling object is the same type. Doable, but not enjoyable.

分享到:
评论

相关推荐

    Modular Programming with JavaScript(PACKT,2016)

    Find out how the module design pattern is used in OOP in JavaScript Design and augment modules using both tight augmentation and loose augmentation Extend the capabilities of modules by creating sub-...

    Object-Oriented Programming in Javascript

    本书讲解如何正确的将OOP应用于Javascript语言,由雅虎著名前段工程师Nicholas C. Zakas所著。

    mkbug.js:一个基于expressjs的OOP风格的Node.js Web框架

    一个基于OOP风格的基于Express.js的Nodejs框架声明! 什么是mkbug.js 一个基于Express.js的OOP风格的Restful Api框架,使Node.js的开发既轻松又漂亮。 Mkbug.js VS Egg.js VS Think.js 项目 Mkbug.js Egg.js ...

    .Principles.of.Object-Oriented.Programming.in.JavaScript

    学习javascript的面向对象开发技术,掌握OOP思想

    Learning-Object-Oriented-Programming-Explore-and-crack-the-OOP-code-in-Python-JavaScript-and-C-.pdf.pdf

    Learning-Object-Oriented-Programming-Explore-and-crack-the-OOP-code-in-Python-JavaScript-and-C-.pdf

    JavaScript.Object.Programming.148421

    This brief book explains the advantages of the object model, inheritance, both classical and prototypical, and shows how these concepts can be implemented in JavaScript. It also shows how object ...

    Javascript.Object.Oriented.Programming.pdf

    Learn to build scalable server application in JavaScript using Node.js Generate instances in three programming languages: Python, JavaScript, and C# Work with a combination of access modifiers, ...

    Pro JavaScript Design Patterns

    But there is more power waiting to be unlocked--JavaScript is capable of full object-oriented capabilities, and by applying OOP principles, best practices, and design patterns to your code, you can ...

    OOP-2:OOP练习

    将函数命名为askUser() 这些是用户可以选择的命令• log in• sign up• exit• search• log out• follow除了可以对每个选项执行if语句外,您还可以进行切换,并且组织得更好。 阅读: : ) 如果用户选择程序...

    JavaScript Object Programming(Apress,2015)

    JavaScript’s object programming (not inheritance) is what separates it from classical OOP languages like C++ and Java. Most important, basing inheritance on JavaScript’s prototypal chain is possible...

    react.js essentials

    Learn from Artemij's real-world experience in React.js Essentials, and you'll be creating user interfaces without increasing the complexity of your web application in no time. This book has ...

    Javascript OOP之面向对象

    面向对象程序设计(Object-oriented programming,OOP)是一种程序设计范型,同时也是一种程序开发的方法。对象指的是类的实例。它将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性、灵活性和...

    mixjs:基于原型的 OOP 库

    mix.js 是一个用于基于原型的面向对象编程 (OOP) 的库。不是像基于类的OOP那样定义一个类并创建一个实例,而是实现了只有对象的OOP。最大的特点是具有通过Mix-in维护父子关系的功能。 ### 模块定义方法 在 mix.js ...

    object-oriented-programming_in_[removed]了解有关OOP的所有知识,了解JavaScript中最令人困惑的部分,并准备进行技术面试

    入门什么是OOP? 以对象而不是功能为中心的编程范例Angular是一个牢记OOP的框架示例。OOP的四大Struts封装形式在OOP中,我们将相关的变量和fn分组为obj; 这就是我们所说的封装 // We refer to this kind of ...

    Mastering JavaScript Object-Oriented Programming

    With this book, we'll provide you with a comprehensive overview of OOP principles in JavaScript and how they can be implemented to build sophisticated web applications. Kicking off with a subtle ...

    Reflection-in-OOP

    今天我们将编写我们自己JavaScript反射器! 我们将编写一个简单的实用程序,该实用程序将检查给定的对象,并向我们介绍该对象及其继承链。 ###您的任务 创建3个类: 用户 组用户 超级用户 在每个类的原型上创建...

    Design-Patterns-in-Modern-C++

    in all honesty, done to death in almost every programming language imaginable—including programming languages such as JavaScript that aren’t even properly OOP! So why another book on it?

    MooTools-1.2-Beginner

    The developers of MooTools strongly believe in applying Object-Oriented Programming (OOP) principles to JavaScript, a structural programming language. Since everything in JavaScript is an object, ...

    algor-in-js:使用Javascript实现的各种基本数据结构和算法

    Javascript中的算法 毫无疑问,用我最喜欢的语言javascript写基本数据结构和算法。 为什么选择JS ... 更好的OOP类(超过python的下划线) 最佳测试周期 话题 01 02 03二 04 05 06 07 08 09 10个 11 12 13二分

Global site tag (gtag.js) - Google Analytics