javascript讲解
javascript对象是字典
在 C# 中,在谈论对象时,是指类或结构的实例。对象有不同的属性和方法,具体取决于将它们实例化的模板(即类)。而 JavaScript 对象却不是这样。在 JavaScript 中,对象只是一组名称/值对,就是说,将 JavaScript 对象视为包含字符串关键字的词典。我们可以使用熟悉的“.”(点)运算符或“[]”运算符,来获得和设置对象的属性,这是在处理词典时通常采用的方法。以下代码段
1 var userObject = new Object(); 2 userObject.lastLoginTime = new Date(); 3 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);
function func(x) { alert(x); } func(“blah”);
这就是通常在 JavaScript 中定义函数的方法。但是,还可以按以下方法定义该函数,您在此创建匿名函数对象,并将它赋给变量 func
1 var func = function(x) { 2 alert(x); 3 }; 4 func(“blah2”);
甚至也可以像下面这样,使用 Function 构造函数:
1 var func = new Function(“x”, “alert(x);”); 2 func(“blah3”);
1 function sayHi(x) { 2 alert(“Hi, “ + x + “!”); 3 } 4 sayHi.text = “Hello World!”; 5 sayHi[“text2”] = “Hello World... again.”; 6 7 alert(sayHi[“text”]); // displays “Hello World!” 8 alert(sayHi.text2); // displays “Hello World... again.”
作为对象,函数还可以赋给变量、作为参数传递给其他函数、作为其他函数的值返回,并可以作为对象的属性或数组的元素进行存储等等。
1 // assign an anonymous function to a variable 2 var greet = function(x) { 3 alert(“Hello, “ + x); 4 }; 5 greet(“MSDN readers”); 6 7 // passing a function as an argument to another 8 function square(x) { 9 return x * x; 10 } 11 function operateOn(num, func) { 12 return func(num); 13 } 14 // displays 256 15 alert(operateOn(16, square)); 16 17 // functions as return values 18 function makeIncrementer() { 19 return function(x) { return x + 1; }; 20 } 21 var inc = makeIncrementer(); 22 // displays 8 23 alert(inc(7)); 24 25 // functions stored as array elements 26 var arr = []; 27 arr[0] = function(x) { return x * x; }; 28 arr[1] = arr[0](2); 29 arr[2] = arr[0](arr[1]); 30 arr[3] = arr[0](arr[2]); 31 // displays 256 32 alert(arr[3]); 33 34 // functions as object properties 35 var obj = { “toString” : function() { return “This is an object.”; } }; 36 // calls obj.toString() 37 alert(obj);
记住这一点后,向对象添加方法将是很容易的事情:只需选择名称,然后将函数赋给该名称。因此,我通过将匿名函数分别赋给相应的方法名称,在对象中定义了三个方法:
1 var myDog = { 2 “name” : “Spot”, 3 “bark” : function() { alert(“Woof!”); }, 4 “displayFullName” : function() { 5 alert(this.name + “ The Alpha Dog”); 6 }, 7 “chaseMrPostman” : function() { 8 // implementation beyond the scope of this article 9 } 10 }; 11 myDog.displayFullName(); 12 myDog.bark(); // Woof!
构造函数不是类
前面提到过,有关 JavaScript OOP 的最奇怪的事情是,JavaScript 不像 C# 那样,它没有类。在 C# 中,在执行类似下面的操作时:
Dog spot = new Dog();
将返回一个对象,该对象是 Dog 类的实例。但在 JavaScript 中,本来就没有类。与访问类最近似的方法是定义构造函数,如下所示:
1 function DogConstructor(name) { 2 this.name = name; 3 this.respondTo = function(name) { 4 if(this.name == name) { 5 alert("Woof"); 6 }else{ 7 alert(name);//如果这里是this.name,输出的将是函数DogConstructor实例化所传入的参数name 8 } 9 }; 10 } 11 12 var spot = new DogConstructor("hello"); 13 spot.respondTo("hello"); // hello 14 spot.respondTo("word"); // word
那么,结果会怎样呢?暂时忽略 DogConstructor 函数定义,看一看这一行:
var spot = new DogConstructor(“hello”);
“new”运算符执行的操作很简单。首先,它创建一个新的空对象。然后执行紧随其后的函数调用,将新的空对象设置为该函数中“this”的值。换句话说,可以认为上面这行包含“new”运算符的代码与下面两行代码的功能相当:
// create an empty object var spot = {}; // call the function as a method of the empty object DogConstructor.call(spot, “hello”);
var buddy = new Dog(“Buddy“);
var spot = new DogConstructor("Spot"); alert(DogConstructor.prototype.isPrototypeOf(spot));//true,证明spot存在于DogConstructor的原型链,
alert(spot.constructor == DogConstructor.prototype.constructor);//true,spot的constructor方法继承自DogConstructor alert(spot.constructor == DogConstructor);//true,spot的constructor指向DogConstructor alert(spot.hasOwnProperty("constructor"));//false,spot本身不具有constructor方法,hasOwnProperty指出一个对象本身是否具有指定的属性 alert(DogConstructor.prototype.hasOwnProperty("constructor"));//true
每个 JavaScript 对象都继承一个原型链,而所有原型都终止于 Object.prototype。注意,迄今为止您看到的这种继承是活动对象之间的继承。它不同于继承的常见概念,后者是指在声明类时类之间的发生的继承。因此,JavaScript 继承动态性更强。它使用简单算法实现这一点,如下所示:当您尝试访问对象的属性/方法时,JavaScript 将检查该属性/方法是否是在该对象中定义的。如果不是,则检查对象的原型。如果还不是,则检查该对象的原型的原型,如此继续,一直检查到 Object.prototype。
JavaScript 动态地解析属性访问和方法调用的方式产生了一些特殊效果:
- 继承原型对象的对象上可以立即呈现对原型所做的更改,即使是在创建这些对象之后。
- 如果在对象中定义了属性/方法 X,则该对象的原型中将隐藏同名的属性/方法。例如,通过在 Dog.prototype 中定义 toString 方法,可以改写 Object.prototype 的 toString 方法。
- 更改只沿一个方向传递,即从原型到它的派生对象,但不能沿相反方向传递。
function GreatDane() { } var rover = new GreatDane(); var spot = new GreatDane(); GreatDane.prototype.getBreed = function() { return "Great Dane"; }; alert(rover.getBreed());//"Great Dane"; spot.getBreed = function() { return "Little Great Dane"; }; alert(spot.getBreed());//"Little Great Dane"; alert(rover.getBreed());//"Great Dane";
闭包
JavaScript 的更高级功能之一是它支持闭包,这是 C# 2.0 通过它的匿名方法支持的功能。闭包是当内部函数(或 C# 中的内部匿名方法)绑定到它的外部函数的本地变量时所发生的运行时现象。很明显,除非此内部函数以某种方式可被外部函数访问,否则它没有多少意义。示例可以更好说明这一点。假设需要根据一个简单条件筛选一个数字序列,这个条件是:只有大于 100 的数字才能通过筛选,并忽略其余数字。1 function filter(pred, arr) { 2 var len = arr.length; 3 var filtered = []; 4 for(var i = 0; i < len; i++) { 5 var val = arr[i]; 6 if(pred(val)) { 7 filtered.push(val); 8 } 9 } 10 return filtered; 11 } 12 13 var someRandomNumbers = [12, 32, 1, 3, 2, 2, 234, 236, 632,7, 8]; 14 var numbersGreaterThan100 = filter( 15 function(x) { return (x > 100) ? true : false; }, 16 someRandomNumbers); 17 18 // displays 234, 236, 632 19 alert(numbersGreaterThan100);
如果要判断大于10或者600,就要写numbersGreaterThan10或者numbersGreaterThan600这样的函数:因此可以这样处理
1 function filter(pred, arr) { 2 var len = arr.length; 3 var filtered = []; 4 for(var i = 0; i < len; i++) { 5 var val = arr[i]; 6 if(pred(val)) { 7 filtered.push(val); 8 } 9 } 10 return filtered; 11 } 12 13 var someRandomNumbers = [12, 32, 1, 3, 2, 2, 234, 236, 632,7, 8]; 14 function makeGreaterThanPredicate(lowerBound) { 15 return function(numberToCheck) { 16 return (numberToCheck > lowerBound) ? true : false; 17 }; 18 } 19 var greaterThan10 = makeGreaterThanPredicate(10); 20 var greaterThan100 = makeGreaterThanPredicate(100); 21 alert(filter(greaterThan10, someRandomNumbers)); 22 alert(filter(greaterThan100, someRandomNumbers));
通过观察函数 makeGreaterThanPredicate 返回的内部匿名函数,可以发现,该匿名内部函数使用 lowerBound,后者是传递给 makeGreaterThanPredicate 的参数。按照作用域的一般规则,当 makeGreaterThanPredicate 退出时,lowerBound 超出了作用域!但在这里,内部匿名函数仍然携带 lowerBound,甚至在 makeGreaterThanPredicate 退出之后的很长时间内仍然如此。这就是我们所说的闭包:因为内部函数关闭了定义它的环境(即外部函数的参数和本地变量)。开始可能感觉不到闭包的功能很强大。但如果应用恰当,它们就可以非常有创造性地帮您将想法转换成代码,这个过程非常有趣。在 JavaScript 中,闭包最有趣的用途之一是模拟类的私有变量。模拟私有属性现在介绍闭包如何帮助模拟私有成员。正常情况下,无法从函数以外访问函数内的本地变量。函数退出之后,由于各种实际原因,该本地变量将永远消失。但是,如果该本地变量被内部函数的闭包捕获,它就会生存下来。这一事实是模拟 JavaScript 私有属性的关键。假设有一个 Person 类:1 function Person(name, age) { 2 this.getName = function() { return name; }; 3 this.setName = function(newName) { name = newName; }; 4 this.getAge = function() { return age; }; 5 this.setAge = function(newAge) { age = newAge; }; 6 }
参数 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.");//Younger Ray is now 22 years old.
未在构造函数中初始化的私有成员可以成为构造函数的本地变量,如下所示:
1 function Person(name, age) { 2 var occupation; 3 //this.getOccupation = occupation;函数将会输出什么了,undefined 4 this.getOccupation = function() { return occupation; }; 5 this.setOccupation = function(newOcc) { occupation = newOcc; }; 6 7 // accessors for name and age 8 } 9 var person =new Person(); 10 person.setOccupation("a0"); 11 alert(person.getOccupation());//a0,occupation为在构造函数中初始化的私有成员,可以通过这种方式成为构造函数的本地变量
从类继承
到这里,我们已经了解了构造函数和原型对象如何使您在 JavaScript 中模拟类。您已经看到,原型链可以确保所有对象都有 Object.prototype 的公用方法,以及如何使用闭包来模拟类的私有成员。但这里还缺少点什么。您尚未看到如何从类派生,这在 C# 中是每天必做的工作。遗憾的是,在 JavaScript 中从类继承并非像在 C# 中键入冒号即可继承那样简单,它需要进行更多操作。另一方面,JavaScript 非常灵活,可以有很多从类继承的方式。例如,有一个基类 Pet,它有一个派生类 Dog,这个在 JavaScript 中如何实现呢?Pet 类很容易。您已经看见如何实现它了:1 // class Pet 2 function Pet(name) { 3 this.getName = function() { return name; }; 4 this.setName = function(newName) { name = newName; }; 5 } 6 7 Pet.prototype.toString = function() { 8 return “This pet’s name is: “ + this.getName(); 9 }; 10 // end of class Pet 11 12 var parrotty = new Pet(“Parrotty the Parrot”); 13 alert(parrotty);
如何派生类了
1 // class Pet 2 function Pet(name) { 3 this.getName = function() { return name; }; 4 this.setName = function(newName) { name = newName; }; 5 } 6 7 Pet.prototype.toString = function() { 8 return "This pet’s name is: " + this.getName(); 9 }; 10 function Dog(name, breed) { 11 // think Dog : base(name) 12 Pet.call(this, name); 13 this.getBreed = function() { return breed; }; 14 // Breed doesn’t change, obviously! It’s read only. 15 // this.setBreed = function(newBreed) { name = newName; }; 16 } 17 18 // this makes Dog.prototype inherits 19 // from Pet.prototype 20 Dog.prototype = new Pet(); 21 22 // remember that Pet.prototype.constructor 23 // points to Pet. We want our Dog instances’ 24 // constructor to point to Dog. 25 Dog.prototype.constructor = Dog; 26 27 // Now we override Pet.prototype.toString 28 Dog.prototype.toString = function() { 29 return 'This dog’s name is: ' + this.getName() + 30 ', and its breed is: ' + this.getBreed(); 31 }; 32 // end of class Dog 33 34 var dog = new Dog('Buddy', 'Great Dane'); 35 // test the new toString() 36 alert(dog); 37 38 // Testing instanceof (similar to the is operator) 39 // (dog is Dog)? yes 40 alert(dog instanceof Dog); 41 // (dog is Pet)? yes 42 alert(dog instanceof Pet); 43 // (dog is Object)? yes 44 alert(dog instanceof Object);
模拟命名空间
在 C# 中,命名空间用于尽可能地减少名称冲突。例如,在 .NET Framework 中,命名空间有助于将 Microsoft.Build.Task.Message 类与 System.Messaging.Message 区分开来。JavaScript 没有任何特定语言功能来支持命名空间,但很容易使用对象来模拟命名空间。如果要创建一个 JavaScript 库,则可以将它们包装在命名空间内,而不需要定义全局函数和类,如下所示:
1 var MSDNMagNS = {}; 2 3 MSDNMagNS.Pet = function(name) { // code here }; 4 MSDNMagNS.Pet.prototype.toString = function() { // code }; 5 6 var pet = new MSDNMagNS.Pet(“Yammer”);
命名空间的一个级别可能不是唯一的,因此可以创建嵌套的命名空间:
1 var MSDNMagNS = {}; 2 // nested namespace “Examples” 3 MSDNMagNS.Examples = {}; 4 5 MSDNMagNS.Examples.Pet = function(name) { // code }; 6 MSDNMagNS.Examples.Pet.prototype.toString = function() { // code }; 7 8 var pet = new MSDNMagNS.Examples.Pet(“Yammer”);
可以想象,键入这些冗长的嵌套命名空间会让人很累。 幸运的是,库用户可以很容易地为命名空间指定更短的别名:
// MSDNMagNS.Examples and Pet definition... // think “using Eg = MSDNMagNS.Examples;” var Eg = MSDNMagNS.Examples; var pet = new Eg.Pet(“Yammer”); alert(pet);