`
happmaoo
  • 浏览: 4309241 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

What ASP.NET Developers Should Know About JavaScript

阅读更多
What ASP.NET Developers Should Know About JavaScript

Posted by on Tuesday, May 08, 2007

This article looks at JavaScript from the perspective of a C# or Visual Basic programmer. See how to apply object oriented techniques to your JavaScript code.

JavaScript – It's beat up, put down, shrugged off and kicked around. Cursed by the web browser's inconsistency yet blessed by a pervasive ubiquity -it's a technology many try to disregard even when its potential is something few can ignore. If you want to write an interactive application for the web today, then you'll need some JavaScript code on your side.

This article approaches JavaScript from the perspective of an ASP.NET developer who is comfortable with the paradigms and patterns of either C# or Visual Basic. The article doesn't look at how to use JavaScript from ASP.NET exactly, but it does look at why JavaScript is so different from the two languages we commonly use with the .NET CLR. The article assumes you already know that JavaScript is a loosely-typed language (because you don't have to declare the type of data you store in a variable), and that the syntax is similar to the C family of languages (with charming curly braces and stunningly beautiful semi-colons).

What Is Wrong With JavaScript?

The introduction didn't paint a flattering picture of the JavaScript language, but the truth is JavaScript is a good language. The biggest sources of pain when programming with JavaScript aren't because of the language. The biggest pains come from:

  • The tools
  • The implementations
  • The bad practices

JavaScript Tools

Most of the tools we use in a Visual Studio environment are geared to languages targeting the CLR. If you program in C# or Visual Basic, you'll be assisted by Intellisense, class browsers, class diagrams, code snippets, code analysis, and a world class debugger. The menus will list refactoring commands, and unit testing is only a few keystrokes away.

Contrast the above with the experience of programming with JavaScript. There is no Intellisense (although the feature is coming in the next version of Visual Studio), the debugger is finicky, and most of the other tools listed above are missing entirely. Of course, the majority of the code we write in Visual Studio is not JavaScript, but as the demands of the web have required more scripting, we've started to need better tools. The lack of tools makes JavaScript more difficult to work with.

Bad Implementations

JavaScript is hosted by many different types of web browsers, and is generally our primary means to manipulate a browser's DOM. While ostensibly governed by W3C standards, we all know each browser contains variations and idiosyncrasies. Script code that works on one version of a browser might not work on a different version of the same browser. These scenarios cause a lot of pain in testing and re-writing of JavaScript code. This pain isn't the language's fault – we will never see the day when all browsers implement web standards with 100% accuracy.

Bad Practices

JavaScript is an accessible language. We don't need special tools or compilers. We can view the source code of any page on the Internet and copy the script code for our own purposes – and many people do. Of course, not everyone who uses JavaScript is a software developer with an eye for good code. All sorts of people use JavaScript, and all sorts of ugly JavaScript code perpetuates itself on the Internet.

Even software developers (the author included) have taken a quick and dirty approach to writing JavaScript. It's only script code, after all, and just slapping the code into a text editor to get the desired result is all we need. It's not until we have to untangle a mess that we realize a more disciplined approach would have ultimately saved us time.

With all of these problems in the JavaScript environment – why do we still want to torture ourselves by writing JavaScript code?

What is Right with JavaScript?

Over the last few years, the amount of JavaScript code you'll find in the typical web application has surged. There are a couple good reasons for the surge:

  • JavaScript is ubiquitous
  • JavaScript is mature

Ubiquity

If you want to write an application that will reach as many users as possible, then you'll be writing a web application. You can reach users on Windows, Macintosh, Linux, and hundreds of other platforms on devices both large and small.

How will you make a web application interactive? You'll use JavaScript. Your users won't have to install a runtime, or an ActiveX control, or download some interpreting engine. They'll install a web browser that includes JavaScript support, as so many do, and they'll happily use your application. JavaScript is the most ubiquitous programming language on the planet.

Maturity

As the demand for JavaScript code has increased, the frameworks and libraries of well-tested and robust JavaScript code have begun to emerge. Many of these frameworks abstract away the browser idiosyncrasies we discussed earlier, and can greatly reduce the amount of time we invest in writing and debugging cross-platform JavaScript code. Here are some of the most popular frameworks:

We've also begun to see the emergence of proven practices and design patterns. The practices put the object oriented features of JavaScript to good use. What's that? You didn't know JavaScript was object oriented? We might not have applied OOP practices to JavaScript code over the last few years, but the capability does exist.

Object Oriented JavaScript

JavaScript does have an object data type – but these objects can behave differently from the objects we create in C# and VB code. In C# and VB we create new objects by telling the runtime which class we want to instantiate. A class is a template for object creation. A class defines the properties and methods an object will have, and these properties and methods are forever fixed. We can't manipulate an object by adding or removing properties and methods at runtime.

In JavaScript there are no classes, so we have no template for object creation. Then how do properties and methods become part of an object? One approach is to dynamically create new properties and methods on an object after construction. To create a new property, all we need to do is assign a new value for a property. The following code creates a new object, then adds an x and y property to the object. Finally, the script writes the values of the two new properties into an area in the HTML document.

var p = new Object();
p.x = 3;
p.y = 5;
message.innerHTML = p.x +
"," + p.y;

JavaScript objects are entirely different from C# and VB objects because they are ultimately a collection of name and value pairs. We can access an object's values using the dot operator "." followed by the name of the value. A value isn't constrained to holding simple integer types as we have shown in the first example, but could hold an array, function, or even other object.

If you are thinking a JavaScript object sounds like a Dictionary from the .NET framework class library, then you are heading in the right conceptual direction.

JavaScript Objects Are Dictionaries

A JavaScript object is similar to a Dictionary<string, object> in the sense we can associate any arbitrary piece of data with any arbitrary string. In fact, there is an alternate syntax for accessing the values inside an object which makes the object look exactly like a dictionary. Instead of using the . operator to access a value, we can use the [] operator. The [] operator is a common sight when we work with collections like arrays, dictionaries, and hashtables.

Let's rewrite our first example using the [] operator.

var p = new Object();
p[
"x"] = 3;
p[
"y"] = 5;
message.innerHTML = p[
"x"] + "," + p.y;

The above piece of script produces the same result as our first script. It creates a new object, then adds x and y properties to the object, then writes out the values. Note that if we access a property that does not exist, we'll get back a value of "undefined". For instance, the line of code "alert(p.z);" would force a dialog box to appear with the string "undefined" inside.

Creating Object Methods

We can also add functions into the collection of values inside an object. Functions associated with an object become methods of the object. The following code sample shows how to create and use a method with the name of "print".

var p = new Object();
p[
"x"] = 3;
p.y = 5;
p[
"print"] = function()
{
message.innerHTML = p.x +
"," + p.y;
}
p.print();

Notice we alternate use of the . operator and the [] operator. We can use these two operators interchangeably, for the most part, to create and access an object's properties and methods. Sometimes these operators lead to confusion, because it's not clear if a particular piece of code is trying to create new properties on an object, or if it's trying to set existing properties to new values. Fortunately, there is a third syntax available that makes our intent explicitly clear.

Object Literals

The object literal syntax of JavaScript allows us to create an object and specify its properties using shorthand. The syntax uses a comma-separated list of name and value pairs, where the name and the value themselves are separated by a colon. Let's rewrite our code and create our object using this object initialization syntax.

var p =
{
x : 5,
y : 3,
print :
function() { message.innerHTML = p.x + ',' + p.y; }
}

p.print();

In the above code it becomes clear where object initialization begins and ends. Also note that we can nest object literals, and that the property values inside the object literals do not need to be constants – we can use any legal JavaScript expression. The following code will contains a nested object (address), and assigns the current date to a new createdDate property.

var person =
{
name:
"Scott Allen",
createdDate: new Date()
website:
"OdeToCode.com",
address: { state:
"MD", postalCode: "21044" },
};

alert(person.address.state);
alert(person.createdDate);

The object literal syntax is popular because of its explicit intent and compact size. If you look at the source code for many of today's popular JavaScript frameworks, you'll see they are using object literals inside. Frameworks, however, aren't the ones using object literals.

Object Literals and JSON

JavaScript Object Notation (JSON) is a lightweight data-interchange format based on a subset of the object literal syntax. Technically, JSON is a stricter version of the object literal syntax. For example, string literals must be enclosed in double quotes – no single quotes are allowed.

JSON allows JavaScript to exchange data over the network (typically with the XmlHttpRequest object) and interoperate with other applications. Many web service providers offer JSON as a serialization format and as an alternative to XML. When our JavaScript contacts the web service, the web service will return its data in JSON. There is no need for our code to manipulate XML data with an XML API - instead our code can use JavaScript's eval statement to convert JSON into an object graph.

var jsonString = "({ x : 3, y: 5 })";

var p = eval(jsonString);
alert(p.x +
',' + p.y);

JSON is becoming hugely popular on the web. JSON is human readable and easily consumable in JavaScript. Also, exchanging data with JSON typically results in smaller payloads than using XML. ASP.NET AJAX includes a JavaScriptSerializer class to use JSON on the server-side in managed code.

Where Are We With Object Oriented JavaScript?

With our brief diversion into JSON complete, let's return to the topic of object oriented JavaScript. So far we've learned the following:

  1. Every JavaScript object is a dictionary.

This is useful information for constructing objects, but it's only a starting point. To get to the next level of abstraction, we'll need to add a second piece of knowledge:

  1. Every JavaScript function is an object.

#2 is what we will discuss in the next topic.

JavaScript Functions

A JavaScript function is a chunk of executable code, but it's also a first class object. This is fundamentally different from methods in C# and Visual Basic. We can invoke methods in C# and VB, but we can't treat those methods as datatypes (although delegates and lamda expressions in C# make this area a little bit fuzzy). In JavaScript, we can manipulate functions using other JavaScript code, assign functions to variables, store functions inside arrays, nest functions inside other functions, and pass functions as a parameter to other functions. This might sound strange, so let's walk through a simple example.

function add(point1, point2)
{
var result = {
x : point1.x + point2.x,
y : point1.y + point2.y
}
return result;
}

The above code defines a function named "add". The function expects two parameters, and expects that these two parameters will both have x and y properties that it can add together. It returns the result as a new object (created in object notation) with x and y properties. We could use invoke this function as in the following sample:

var p1 = { x: 1, y: 1 };
var p2 = { x: 1, y: 1 };

// use our add function
var p3 = add(p1, p2);

alert(p3.x +
"," + p3.y);

The resulting dialog box will display "2,2".

Technically, what we've done with the add function is create a new function object, and assigned the function object to a variable named add. We could take the same function object and assign it to different variables and invoke the function through those variables.

function add(point1, point2)
{
var result = {
x : point1.x + point2.x,
y : point1.y + point2.y
}
return result;
}

var foo = add
var bar = add

var p1 = { x: 1, y:1 };
var p2 = { x: 1, y:1 };

// invoke add through foo variable
// p3 should be 2,2
var p3 = foo(p1, p2);

// invoke add again through bar variable
// 2,2 + 1,1 = 3,3
p3 = foo(p3, p1);

alert(p3.x +
"," + p3.y);

The resulting dialog box should now display "3,3".

Functions as Methods

We can also assign a function object to an object property. As we noted before, this promotes the function to the status of "method".

var point1 =
{
x: 3,
y: 5,
add:
function(otherPoint)
{
this.x = this.x + otherPoint.x;
this.y = this.y + otherPoint.y;
}
};

var point2 =
{
x: 1,
y: 1
};

// add 3,5 to 1,1
point1.add(point2);
// shows 4,6
alert(point1.x +
"," + point1.y);

The first part of the code uses object notation to create an object with x, y, and add properties. The add property is a function object, and inside we've introduced the "this" keyword. Just as every instance method in C# has an implicit "this" parameter (and every instance method in VB has "Me" parameter), every JavaScript method has an implicit "this" parameter that represents the object through which the method was invoked. "this.x" will reference the x property of point1, because we invoke the add method using point1.

What is a problem is that we have two "point" objects, but one has an add method and one does not. Remember, we are not defining classes like we would in C# or VB, we are simply creating objects and adding properties and methods on the fly. If we wanted to same add method in both point1 and point2, we could write the following code.

function addPoints(otherPoint)
{
this.x = this.x + otherPoint.x;
this.y = this.y + otherPoint.y;
}

var point1 =
{
x: 3,
y: 5,
add: addPoints
};

var point2 =
{
x: 1,
y: 1,
add: addPoints
};

// add 3,5 to 1,1
point1.add(point2);

// shows 4,6
alert(point1.x + "," + point1.y);

Now we've defined a function object and assigned the object to a variable named addPoints. We use addPoints to create new add methods in both the point1 and point2 objects. Does the "this" reference still work in addPoints? Yes it does, because "this" will still reference the object through which the method was invoked. "this" is a bit ephemeral in JavaScript, as we see later on, but we can can now invoke the add method on either the point1 or point2 object.

This syntax is feeling uncomfortable, however. It looks as if we are trying to create a Point class that will define the properties and methods for all Point objects. But JavaScript doesn't have classes, so that would be a dream, right? We'll forever need to include all this object literal code every time we need a point object, right? Let's hope we never move into 3 dimensions where the definition of our point objects will change.

Fortunately, there is a better solution.

Constructor Functions

In JavaScript, a constructor function works in conjunction with the new operator to initialize objects. A constructor function can improve our previous code, because we can use the function to initialize every object we want to use as a Point.

function Point(x,y)
{
this.x = x;
this.y = y;
}

var p1 = new Point(3,5);
var p2 = new Point(4,6);

// shows 3,5
alert(p1.x, p1.y);

Constructor functions are just regular functions. It's just we've designed the function to be used with the new operator. By convention, we generally capitalize constructor functions to make other programmers aware of their significance.

When we use the new operator with the Point function, the new operator will first create a new object. The new operator then invokes the Point function and passes a reference to the newly created object in the implicit "this" parameter. Inside the Point function we are creating new name/value pairs using the parameter values passed to the function.

Constructor Functions and Object Methods

We can also create methods on an object inside a constructor function.

function Point(x,y)
{
this.x = x;
this.y = y;
this.add = function(point2)
{
this.x += point2.x;
this.y += point2.y;
}
}

var p1 = new Point(3,5);
var p2 = new Point(4,6);

p1.add(p2);

// shows 7,11
alert(p1.x + ',' + p1.y);

This approach works well, but there is an alternate approach we can use which is more in favor today. To understand this approach, we'll need to introduce a new piece of knowledge. Let's review the first two:

  1. Every JavaScript object is a dictionary.
  2. Every JavaScript function is an object.

Now for number 3:

  1. Every JavaScript object references a prototype object.

Let's talk about prototypes.

Object Prototypes

Prototypes are a distinguishing feature of the JavaScript language. C#, Visual Basic, C++, and Java are all examples of class-based programming languages. To create objects, we must first write a class that defines fields, properties, methods, and events. When we create a new object, we are creating an instance of that class.

In JavaScript, there are no classes. JavaScript is a prototype-based programming language. Every object has a prototype property that references its prototype object. Any properties and methods that are a part of an object's prototype will appear as properties and methods of the object itself.

Remember that every function is an object, and every object references a prototype object. That means every constructor function references a prototype object. This is extremely useful when used in conjunction with the "new" operator, because of steps taken by the new operator:

  1. Create an empty object.
  2. Assign the value of the constructor function's prototype property to the new object's prototype property.
  3. Invoke the constructor function, passing the new object as the "this" reference.

The above 3 steps mean that all objects created by a constructor function will have the same prototype – the prototype object for the constructor function. If we can modify the constructor function's prototype object, we will modify all objects the constructor function ever creates (or has already created). You can almost think of every object as inheriting from it's prototype, because it will include all the properties and methods defined by its prototype. I say "almost" because this thinking can be dangerous in some edge cases.

Fortunately, there is an easy syntax we can use to add new properties and methods into a prototype object.

Prototype Programming

Remember, all objects in JavaScript are dictionaries, and a prototype object is no exception. We can modify an object' s prototype simply by referencing it's prototype property, and we can add properties and methods to that prototype object. Let's rewrite our Point "class" one more time.

function Point(x,y)
{
this.x = x;
this.y = y;
}

Point.prototype.add =
function(point2)
{
this.x += point2.x;
this.y += point2.y;
}

var p1 = new Point(3,5);
var p2 = new Point(4,6);

p1.add(p2);

// shows 7,11
alert(p1.x + ',' + p1.y);

The methods and properties we add to Point.prototype will be shared by all objects that are constructed from the Point constructor function. When we add methods to an object using the constructor function – each object gets a new property referencing a function object, so the prototype approach is more efficient (shared function objects) as well as being a little easier to read. This prototype approach is used by many of today's JavaScript frameworks.

Putting It All Together

The topics we've discussed so far put us close to "simulating" classes in JavaScript. There are just a couple more topics we need to introduce before wrapping up.

One topic is encapsulation. In JavaScript, every name/value pair we add to an object becomes a public property. There are no keywords in JavaScript to restrict accessibility (like the internal, protected, and private keywords in C#). Nevertheless, we can simulate private members.

Private Members

Douglas Crockford published an article "Private Members In JavaScript" that demonstrates how to add private members to a JavaScript object. Information hiding is an important technique in object oriented programming, and many JavaScript toolkits use Crockford's approach to private members.

Private members have to be made in an object's constructor function. Both local vars and parameters are eligible to become private members using a closure. A closure in JavaScript is an inner function that references a local var or parameter in its outer function. Those local variables and parameters, which typically go out of scope when the outer function finishes execution are now "enclosed" by the inner function, which can continue to reference and use those variables.

Let's re-write our sample once more, this time providing public "get" and "set" accesors for our points.


function Point(x, y)
{
this.get_x = function() { return x; }
this.set_x = function(value) { x = value; }
this.get_y = function() { return y; }
this.set_y = function(value) { y = value; }
}

Point.prototype.print =
function()
{
return this.get_x() + ',' + this.get_y();
}

var p = new Point(2,2);
p.set_x(4);
alert(p.print());

Client code can no longer access the x and y values of a point object directly. Instead, the code has to go through the set_ and get_ methods.

Namespaces

Namespaces are crucial for avoiding type name collisions, which can be a bad thing in JavaScript. Unlike a compiled language like C# or VB, where a type name collision will result in a compiler error and an un-shippable product, in JavaScript you can still ship the code and might not find out about the collision until it's too late. JavaScript will happily overwrite one value with another. Since we are now including JavaScript code from all over the place, the practice of using namespace is important.

There is just one problem.

JavaScript doesn't support namespaces.

This is ok, because we can "simulate" namespace using objects. Let's put our "Point class" into a Geometry namespace.

var Geometry = {}

Geometry.Point =
function(x,y)
{
this.x = x;
this.y = y;
}

Geometry.Point.prototype =
{
print:
function()
{
return this.x + ',' + this.y;
}
}

var p1 = new Geometry.Point(5,2);
alert(p1.print());

Essentially, we are adding our constructor function to the Geometry object. By adding other constructor functions (Rectangle, Square, etc), we could keep all of our types inside Geometry and not pollute the global namespace. Most JavaScript frameworks use a similar technique.

In Conclusion

This article presented three key pieces of knowledge:

  1. Every JavaScript object is a dictionary.
  2. Every JavaScript function is an object.
  3. Every JavaScript object references a prototype object.

Those are three fundamental facts about JavaScript that also make JavaScript different from mainstream CLR languages like C# and VB.NET. Embracing and internalizing these differences will put you ahead of the game in understanding modern JavaScript frameworks, toolkits, and libraries.

This article is by K. Scott Allen. Questions? Comments? Bring them to my blog.

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics