这里有新鲜出炉的 Javascript 教程,程序狗速度看过来!
Javascript 是一种由 Netscape 的 LiveScript 发展而来的原型化继承的基于对象的动态类型的区分大小写的客户端脚本语言,主要目的是为了解决服务器端语言,比如 Perl,遗留的速度问题,为客户提供更流畅的浏览效果。
在 C++ 或 C# 中,在谈论对象时,是指类或结构的实例。对象有不同的属性和方法,具体取决于将它们实例化的模板(即类)。
JavaScript 对象是词典
在 C++ 或 C# 中,在谈论对象时,是指类或结构的实例。对象有不同的属性和方法,具体取决于将它们实例化的模板(即类)。而 JavaScript 对象却不是这样。在 JavaScript 中,对象只是一组名称 / 值对,就是说,将 JavaScript 对象视为包含字符串关键字的词典。我们可以使用熟悉的 "."(点)运算符或 "[]" 运算符,来获得和设置对象的属性,这是在处理词典时通常采用的方法。以 下代码段
- var userObject = new Object();
- userObject.lastLoginTime = new Date();
- alert(userObject.lastLoginTime);
的功能与下面的代码段完全相同:
- var userObject = {}; // equivalent to new Object()
- userObject["lastLoginTime"] = new Date();
- alert(userObject["lastLoginTime"]);
我们还可以直接在 userObject 的定义中定义 lastLoginTime 属性,如下所示:
- var userObject = { "lastLoginTime": new Date() };
- alert(userObject.lastLoginTime);
注意,它与 C# 3.0 对象初始值非常相似。而且,熟悉 Python 的人会发现在第二和第三个代码段中实例化 userObject 的方法与在 Python 中指定词典的方法完全相同。唯一的差异是 JavaScript 对象 / 词典只接受字符串关键字,而不是像 Python 词典那样接受可哈希化的对象。
这 些示例还显示 JavaScript 对象比 C++ 或 C# 对象具有更大的可延展性。您不必预先声明属性 lastLoginTime — 如果 userObject 没有该名称的属性,该属性将被直接添加到 userObject。如果记住 JavaScript 对象是词典,您就不会对此感到吃惊了,毕竟,我们一直在向词典添加新关键字(和其各自的值)。
这 样,我们就有了对象属性。对象方法呢?同样,JavaScript 与 C++/C# 不同。若要理解对象方法,首先需要仔细了解一下 JavaScript 函数。
JavaScript 函数是最棒的
在 很多编程语言中,函数和对象通常被视为两样不同的东西。在 JavaScript 中,其差别很模糊 — JavaScript 函数实际上是具有与它关联的可执行代码的对象。请如此看待普通函数:
- function func(x) {
- alert(x);
- }
- func("blah");
这就是通常在 JavaScript 中定义函数的方法。但是,还可以按以下方法定义该函数,您在此创建匿名函数对象,并将它赋给变量 func
- var func = function(x) {
- alert(x);
- };
- func("blah2");
甚至也可以像下面这样,使用 Function 构造函数:
- var func = new Function("x", "alert(x);");
- func("blah3");
此示例表明函数实际上只是支持函数调用操作的对象。最后一个使用 Function 构造函数来定义函数的方法并不常用,但它展示的可能性非常有趣,因为您可能注意到,该函数的主体正是 Function 构造函数的 String 参数。这意味着,您可以在运行时构造任意函数。
为 了进一步演示函数是对象,您可以像对其他任何 JavaScript 对象一样,在函数中设置或添加属性:
- function sayHi(x) {
- alert("Hi, " + x + "!");
- }
- sayHi.text = "Hello World!";
- sayHi["text2"] = "Hello World... again.";
- alert(sayHi["text"]); // displays "Hello World!"
- alert(sayHi.text2); // displays "Hello World... again."
作为对象,函数还可以赋给变量、作为参数传递给其他函数、作为其他函数的值返回,并可以作为对象的属性或数组的元素进行存储等等。图 1 提供了这样一个示例。
JavaScript 中的函数是最棒的
- // assign an anonymous function to a variable
- var greet = function(x) {
- alert("Hello, " + x);
- };
- greet("MSDN readers");
- // passing a function as an argument to another
- function square(x) {
- return x * x;
- }
- function operateOn(num, func) {
- return func(num);
- }
- // displays 256
- alert(operateOn(16, square));
- // functions as return values
- function makeIncrementer() {
- return function(x) {
- return x + 1;
- };
- }
- var inc = makeIncrementer();
- // displays 8
- alert(inc(7));
- // functions stored as array elements
- var arr = [];
- arr[0] = function(x) {
- return x * x;
- };
- arr[1] = arr[0](2);
- arr[2] = arr[0](arr[1]);
- arr[3] = arr[0](arr[2]);
- // displays 256
- alert(arr[3]);
- // functions as object properties
- var obj = {"toString": function() {
- return"This is an object.";
- }
- };
- // calls obj.toString()
- alert(obj);
记住这一点后,向对象添加方法将是很容易的事情:只需选择名称,然后将函数赋给该名称。因此,我通过将匿名函数分别赋给相应的方法名称,在对象中定义了三 个方法:
代码
- var myDog = {"name": "Spot",
- "bark": function() {
- alert("Woof ! ");
- },
- "displayFullName": function() {
- alert(this.name + "The Alpha Dog");
- },
- "chaseMrPostman": function() {
- // implementation beyond the scope of this article
- }
- };
- myDog.displayFullName();
- myDog.bark(); // Woof!
C++/C# 开发人员应当很熟悉 displayFullName 函数中使用的 "this" 关键字 — 它引用一个对象,通过对象调用方法(使用 Visual Basic 的开发人员也应当很熟悉它,它在 Visual Basic 中叫做 "Me")。因此在上面的示例中,displayFullName 中的 "this" 的值是 myDog 对象。但是,"this" 的值不是静态的。通过不同对象调用 "this" 时,它的值也会更改以便指向相应的对象,如图 2 所示。
"this" 随对象更改而更改
- function displayQuote() {
- // the value of "this" will change; depends on
- // which object it is called through
- alert(this.memorableQuote);
- }
- var williamShakespeare = {
- "memorableQuote": "It is a wise father that knows his own child.",
- "sayIt" : displayQuote
- };
- var markTwain = {
- "memorableQuote": "Golf is a good walk spoiled.",
- "sayIt" : displayQuote
- };
- var oscarWilde = {
- "memorableQuote": "True friends stab you in the front."
- // we can call the function displayQuote
- // as a method of oscarWilde without assigning it
- // as oscarWilde's method.
- //"sayIt" : displayQuote
- };
- williamShakespeare.sayIt(); // true, true
- markTwain.sayIt(); // he didn't know where to play golf
- // watch this, each function has a method call()
- // that allows the function to be called as a
- // method of the object passed to call() as an
- // argument.
- // this line below is equivalent to assigning
- // displayQuote to sayIt, and calling oscarWilde.sayIt().
- displayQuote.call(oscarWilde); // ouch!
图 2 中的最后一行表示的是将函数作为对象的方法进行调用的另一种方式。请记住,JavaScript 中的函数是对象。每个函数对象都有一个名为 call 的方法,它将函数作为第一个参数的方法进行调用。就是说,作为函数第一个参数传递给 call 的任何对象都将在函数调用中成为 "this" 的值。这一技术对于调用基类构造函数来说非常有用,稍后将对此进行介绍。
有 一点需要记住,绝不要调用包含 "this"(却没有所属对象)的函数。否则,将违反全局命名空间,因为在该调用中,"this" 将引用全局对象,而这必然 会给您的应用程序带来灾难。例如,下面的脚本将更改 JavaScript 的全局函数 isNaN 的行为。一定不要这样做!
代码
- alert("NaN is NaN: " + isNaN(NaN));
- function x() {
- this.isNaN = function() {
- return "not anymore!";
- };
- }
- // alert!!! trampling the Global object!!!
- x();
- alert("NaN is NaN: " + isNaN(NaN));
到这里,我们已经介绍了如何创建对象,包括它的属性和方法。但如果注意上面的所有代码 段,您会发现属性和方法是在对象定义本身中进行硬编码的。但如果需要更好地控制对象的创建,该怎么做呢?例如,您可能需要根据某些参数来计算对象的属性 值。或者,可能需要将对象的属性初始化为仅在运行时才能获得的值。也可能需要创建对象的多个实例(此要求非常常见)。
在 C# 中,我们使用类来实例化对象实例。但 JavaScript 与此不同,因为它没有类。您将在下一节中看到,您可以充分利用这一情况:函数在与 "new" 运算符一起使用时,函数将充当构造函数。
构造函数而不是类
前 面提到过,有关 JavaScript OOP 的最奇怪的事情是,JavaScript 不像 C# 或 C++ 那样,它没有类。在 C# 中,在执行类似下面的操作时:
Dog spot = new Dog();
将返回一个对象,该对象是 Dog 类的实例。但在 JavaScript 中,本来就没有类。与访问类最近似的方法是定义构造函数,如下所示:
代码
- function DogConstructor(name) {
- this.name = name;
- this.respondTo = function(name) {
- if(this.name == name) {
- alert("Woof");
- }
- };
- }
- var spot = new DogConstructor("Spot");
- spot.respondTo("Rover"); // nope
- spot.respondTo("Spot"); // yeah!
那么,结果会怎样呢?暂时忽略 DogConstructor 函数定义,看一看这一行:
var spot = new DogConstructor("Spot");
"new" 运算符执行的操作很简单。首先,它创建一个新的空对象。然后执行紧随其后的函数调用,将新的空对象设置为该函数中 "this" 的值。换句话说, 可以认为上面这行包含 "new" 运算符的代码与下面两行代码的功能相当:
- // create an empty object
- var spot = {};
- // call the function as a method of the empty object
- DogConstructor.call(spot, "Spot");
正如在 DogConstructor 主体中看到的那样,调用此函数将初始化对象,在调用期间关键字 "this" 将引用此对象。这样,就可以为对象创建模板!只要需要创建类似的对象,就可以与 构造函数一起调用 "new",返回的结果将是一个完全初始化的对象。这与类非常相似,不是吗?实际上,在 JavaScript 中构造函数的名称通常就是所模拟的类的名称,因此在上面的示例中,可以直接命名构造函数 Dog:
代码
- // Think of this as class Dog
- function Dog(name) {
- // instance variable
- this.name = name;
- // instance method? Hmmm...
- this.respondTo = function(name) {
- if(this.name == name) {
- alert("Woof");
- }
- };
- }
- var spot = new Dog("Spot");
在上面的 Dog 定义中,我定义了名为 name 的实例变量。使用 Dog 作为其构造函数所创建的每个对象都有它自己的实例变量名称副本(前面提到过,它就是对象词典的条目)。这就是希望的结果。毕竟,每个对象都需要它自己的实 例变量副本来表示其状态。但如果看看下一行,就会发现每个 Dog 实例也都有它自己的 respondTo 方法副本,这是个浪费;您只需要一个可供各个 Dog 实例共享的 respondTo 实例!通过在 Dog 以外定义 respondTo,可以避免此问题,如下所示:
- function respondTo() {
- // respondTo definition
- }
- function Dog(name) {
- this.name = name;
- // attached this function as a method of the object
- this.respondTo = respondTo;
- }
这样,所有 Dog 实例(即用构造函数 Dog 创建的所有实例)都可以共享 respondTo 方法的一个实例。但随着方法数的增加,维护工作将越来越难。最后,基本代码中将有很多全局函数,而且随着 "类" 的增加,事情只会变得更加糟糕(如果它们的 方法具有相似的名称,则尤甚)。但使用原型对象可以更好地解决这个问题,这是下一节的主题。
原型
在 使用 JavaScript 的面向对象编程中,原型对象是个核心概念。在 JavaScript 中对象是作为现有示例(即原型)对象的副本而创建的,该名称就来自于这一概念。此原型对象的任何属性和方法都将显示为从原型的构造函数创建的对象的属性和 方法。可以说,这些对象从其原型继承了属性和方法。当您创建如下所示的新 Dog 对象时:
var buddy = new Dog("Buddy");
buddy 所引用的对象将从它的原型继承属性和方法,尽管仅从这一行可能无法明确判断原型来自哪里。对象 buddy 的原型来自构造函数(在这里是函数 Dog)的属性。
在 JavaScript 中,每个函数都有名为 "prototype" 的属性,用于引用原型对象。此原型对象又有名为 "constructor" 的属性,它反过来引用函数本身。这 是一种循环引用,图 3 更好地说明了这种循环关系。
- var spot = new Dog("Spot");
- // Dog.prototype is the prototype of spot
- alert(Dog.prototype.isPrototypeOf(spot));
- // spot inherits the constructor property
- // from Dog.prototype
- alert(spot.constructor == Dog.prototype.constructor);
- alert(spot.constructor == Dog);
- // But constructor property doesn't belong
- // to spot. The line below displays "false"
- alert(spot.hasOwnProperty("constructor"));
- // The constructor property belongs to Dog.prototype
- // The line below displays "true"
- alert(Dog.prototype.hasOwnProperty("constructor"));
静态属性和方法
- function GreatDane() { }
- var rover = new GreatDane();
- var spot = new GreatDane();
- GreatDane.prototype.getBreed = function() {
- return "Great Dane";
- };
- // Works, even though at this point
- // rover and spot are already created.
- alert(rover.getBreed());
- // this hides getBreed() in GreatDane.prototype
- spot.getBreed = function() {
- return "Little Great Dane";
- };
- alert(spot.getBreed());
- // but of course, the change to getBreed
- // doesn't propagate back to GreatDane.prototype
- // and other objects inheriting from it,
- // it only happens in the spot object
- alert(rover.getBreed());
在 JavaScript 中调用静态方法的语法与在 C# 中几乎完全相同。这不应当让人感到吃惊,因为构造函数的名称实际上是类的名称。这样,就有了类、公用属性 / 方法,以及静态属性 / 方法。还需要其他什么吗? 当然,私有成员。但 JavaScript 本身并不支持私有成员(同样,也不支持受保护成员)。任何人都可以访问对象的所有属性和方法。但我们有办法让类中包含私有成员,但在此之前,您首先需要理 解闭包。
- function DateTime() { }
- // set static method now()
- DateTime.now = function() {
- return new Date();
- };
- alert(DateTime.now());
但是,现在要创建不同的筛选条件,假设这次只有大于 300 的数字才能通过筛选,则可以编写下面这样的函数:
- function filter(pred, arr) {
- var len = arr.length;
- var filtered = []; // shorter version of new Array();
- // iterate through every element in the array...
- for(var i = 0; i < len; i++) {
- var val = arr[i];
- // if the element satisfies the predicate let it through
- if(pred(val)) {
- filtered.push(val);
- }
- }
- return filtered;
- }
- var someRandomNumbers = [12, 32, 1, 3, 2, 2, 234, 236, 632,7, 8];
- var numbersGreaterThan100 = filter(
- function(x) { return (x > 100) ? true : false; },
- someRandomNumbers);
- // displays 234, 236, 632
- alert(numbersGreaterThan100);
然后,也许需要筛选大于 50、25、10、600 如此等等的数字,但作为一个聪明人,您会发现它们全部都有相同的谓词 "greater than",只有数字不同。因此,可以用类似下面的函数分开各个数字:
- var greaterThan300 = filter(
- function(x) { return (x > 300) ? true : false; },
- someRandomNumbers);
这样,您就可以编写以下代码:
- function makeGreaterThanPredicate(lowerBound) {
- return function(numberToCheck) {
- return (numberToCheck > lowerBound) ? true : false;
- };
- }
通过观察函数 makeGreaterThanPredicate 返回的内部匿名函数,可以发现,该匿名内部函数使用 lowerBound,后者是传递给 makeGreaterThanPredicate 的参数。按照作用域的一般规则,当 makeGreaterThanPredicate 退出时,lowerBound 超出了作用域!但在这里,内部匿名函数仍然携带 lowerBound,甚至在 makeGreaterThanPredicate 退出之后的很长时间内仍然如此。这就是我们所说的闭包:因为内部函数关闭了定义它的环境(即外部函数的参数和本地变量)。
- var greaterThan10 = makeGreaterThanPredicate(10);
- var greaterThan100 = makeGreaterThanPredicate(100);
- alert(filter(greaterThan10, someRandomNumbers));
- alert(filter(greaterThan100, someRandomNumbers));
参数 name 和 age 是构造函数 Person 的本地变量。Person 返回时,name 和 age 应当永远消失。但是,它们被作为 Person 实例的方法而分配的四个内部函数捕获,实际上这会使 name 和 age 继续存在,但只能严格地通过这四个方法访问它们。因此,您可以:
- function Person(name, age) {
- this.getName = function() {
- return name;
- };
- this.setName = function(newName) {
- name = newName;
- };
- this.getAge = function() {
- return age;
- };
- this.setAge = function(newAge) {
- age = newAge;
- };
- }
未在构造函数中初始化的私有成员可以成为构造函数的本地变量,如下所示:
- var ray = new Person("Ray", 31);
- alert(ray.getName());
- alert(ray.getAge());
- ray.setName("Younger Ray");
- // Instant rejuvenation!
- ray.setAge(22);
- alert(ray.getName() + " is now " + ray.getAge() +
- " years old.");
注意,这些私有成员与我们期望从 C# 中产生的私有成员略有不同。在 C# 中,类的公用方法可以访问它的私有成员。但在 JavaScript 中,只能通过在其闭包内拥有这些私有成员的方法来访问私有成员(由于这些方法不同于普通的公用方法,它们通常被称为特权方法)。因此,在 Person 的公用方法中,仍然必须通过私有成员的特权访问器方法才能访问私有成员:
- function Person(name, age) {
- var occupation;
- this.getOccupation = function() {
- return occupation;
- };
- this.setOccupation = function(newOcc) {
- occupation = newOcc;
- };
- // accessors for name and age
- }
Douglas Crockford 是著名的发现(或者也许是发布)使用闭包来模拟私有成员这一技术的第一人。他的网站 javascript.crockford.com 包含有关 JavaScript 的丰富信息,任何对 JavaScript 感兴趣的开发人员都应当仔细研读。
- Person.prototype.somePublicMethod = function() {
- // doesn't work!
- // alert(this.name);
- // this one below works
- alert(this.getName());
- };
现在,如何创建从 Pet 派生的类 Dog 呢?在图 9 中可以看到,Dog 有另一个属性 breed,它改写了 Pet 的 toString 方法(注意,JavaScript 的约定是方法和属性名称使用 camel 大小写,而不是在 C# 中建议的 Pascal 大小写)。图 10 显示如何这样做。
- // class Pet
- function Pet(name) {
- this.getName = function() {
- return name;
- };
- this.setName = function(newName) {
- name = newName;
- };
- }
- Pet.prototype.toString = function() {
- return"This pet 's name is: " + this.getName();
- };
- // end of class Pet
- var parrotty = new Pet("Parrotty the Parrot");
- alert(parrotty);
- '
所使用的原型 — 替换技巧正确设置了原型链,因此假如使用 C#,测试的实例将按预期运行。而且,特权方法仍然会按预期运行。
- // class Dog : Pet
- // public Dog(string name, string breed)
- function Dog(name, breed) {
- // think Dog : base(name)
- Pet.call(this, name);
- this.getBreed = function() {
- return breed;
- };
- // Breed doesn't change, obviously! It's read only.
- // this.setBreed = function(newBreed) { name = newName; };
- }
- // this makes Dog.prototype inherits
- // from Pet.prototype
- Dog.prototype = new Pet();
- // remember that Pet.prototype.constructor
- // points to Pet. We want our Dog instances'
- // constructor to point to Dog.
- Dog.prototype.constructor = Dog;
- // Now we override Pet.prototype.toString
- Dog.prototype.toString = function() {
- return"This dog 's name is: " + this.getName() +
- ", and its breed is: " + this.getBreed();
- };
- // end of class Dog
- var dog = new Dog("Buddy", "Great Dane");
- // test the new toString()
- alert(dog);
- // Testing instanceof (similar to the is operator)
- // (dog is Dog)? yes
- alert(dog instanceof Dog);
- // (dog is Pet)? yes
- alert(dog instanceof Pet);
- // (dog is Object)? yes
- alert(dog instanceof Object);
- '
命名空间的一个级别可能不是唯一的,因此可以创建嵌套的命名空间:
- var MSDNMagNS = {};
- MSDNMagNS.Pet = function(name) { // code here };
- MSDNMagNS.Pet.prototype.toString = function() { // code };
- var pet = new MSDNMagNS.Pet("Yammer");
可以想象,键入这些冗长的嵌套命名空间会让人很累。 幸运的是,库用户可以很容易地为命名空间指定更短的别名:
- var MSDNMagNS = {};
- // nested namespace "Examples"
- MSDNMagNS.Examples = {};
- MSDNMagNS.Examples.Pet = function(name) { // code };
- MSDNMagNS.Examples.Pet.prototype.toString = function() { // code };
- var pet = new MSDNMagNS.Examples.Pet("Yammer");
如果看一下 Microsoft AJAX 库的源代码,就会发现库的作者使用了类似的技术来实现命名空间(请参阅静态方法 Type.registerNamespace 的实现)。有关详细信息,请参与侧栏 "OOP 和 ASP.NET AJAX"。
- // MSDNMagNS.Examples and Pet definition...
- // think "using Eg = MSDNMagNS.Examples;"
- var Eg = MSDNMagNS.Examples;
- var pet = new Eg.Pet("Yammer");
- alert(pet);
- function object(o) {
- function F() {}
- F.prototype = o;
- return new F();
- }
(adsbygoogle = window.adsbygoogle || []).push({});
来源: http://www.phperz.com/article/17/0629/285538.html