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);
这些示例还显示 JavaScript 对象比 C# 对象具有更大的可延展性。您不必预先声明属性 lastLoginTime — 如果 userObject 没有该名称的属性,该属性将被直接添加到 userObject。如果记住 JavaScript 对象是词典,您就不会对此感到吃惊了,毕竟,我们一直在向词典添加新关键字(和其各自的值)。
这样,我们就有了对象属性。对象方法呢?同样,JavaScript 与C# 不同。若要理解对象方法,首先需要仔细了解一下 JavaScript 函数。
javascript函数
  在很多编程语言中,函数和对象通常被视为两样不同的东西。在 JavaScript 中,其差别很模糊 — JavaScript 函数实际上是具有与它关联的可执行代码的对象。请如此看待普通函数:
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”);
此示例表明函数实际上只是支持函数调用操作的对象。最后一个使用 Function 构造函数来定义函数的方法并不常用,但它展示的可能性非常有趣,因为您可能注意到,该函数的主体正是 Function 构造函数的 String 参数。这意味着,您可以在运行时构造任意函数。
为了进一步演示函数是对象,您可以像对其他任何 JavaScript 对象一样,在函数中设置或添加属性:
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”);
原型
  在使用 JavaScript 的面向对象编程中,原型对象是个核心概念。在 JavaScript 中对象是作为现有示例(即原型)对象的副本而创建的,该名称就来自于这一概念。此原型对象的任何属性和方法都将显示为从原型的构造函数创建的对象的属性和方法。可以说,这些对象从其原型继承了属性和方法。当您创建如下所示的新 Dog 对象时:
var buddy = new Dog(“Buddy“);
buddy 所引用的对象将从它的原型继承属性和方法,尽管仅从这一行可能无法明确判断原型来自哪里。对象 buddy 的原型来自构造函数(在这里是函数 Dog)的属性。
在 JavaScript 中,每个函数都有名为“prototype”的属性,用于引用原型对象。此原型对象又有名为“constructor”的属性,它反过来引用函数本身。这是一种循环引用,图 3 更好地说明了这种循环关系。
现在,通过“new”运算符用函数(上面示例中为 Dog)创建对象时,所获得的对象将继承 Dog.prototype 的属性。可以看到 Dog.prototype 对象有一个回指 Dog 函数的构造函数属性。这样,每个 Dog 对象(从 Dog.prototype 继承而来)都有一个回指 Dog 函数的构造函数属性。
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);

     

     

 


 
原文地址:https://www.cnblogs.com/sandianbaozi/p/2657065.html