科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道基础软件使用面向对象技术创建高级Web应用程序

使用面向对象技术创建高级Web应用程序

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

 用 JavaScript 很容易做些简单的事情。入门的门槛很低,该语言很宽松,它不需要您知道很多细节就可以开始用它进行编码。

作者:MSDN 来源:IT专家网 2008年5月20日

关键字: 高级 创建 web net Windows

  • 评论
  • 分享微博
  • 分享邮件
最近,我面试了一个有五年 Web 应用程序开发经验的软件开发人员。四年半来她一直在从事 JavaScript 相关的工作,她自认为 JavaScript 技能非常好,但在不久之后我就发现实际上她对 JavaScript 知之甚少。话虽这样说,但我确实没有责备她的意思。JavaScript 真的是很有趣。很多人(包括我自己,直到最近!)都认为自己很擅长 JavaScript 语言,因为他们都知道 C/C++/C#,或者有一些以前的编程经验。

  在某种程度上,这种假设并不是完全没有根据的。用 JavaScript 很容易做些简单的事情。入门的门槛很低,该语言很宽松,它不需要您知道很多细节就可以开始用它进行编码。甚至非编程人员也可能用它在几个小时内为主页编写一些有用的脚本。

  的确,直到最近,仅仅凭借 MSDN® DHTML 参考资料和我的 C++/C# 经验,我也总能勉强利用这点 JavaScript 知识完成一些任务。只是当我开始编写真实的 AJAX 应用程序时,我才意识到实际上我的 JavaScript 知识还非常不够。这个新一代的 Web 应用程序的复杂性和交互性需要程序员以完全不同的方法来编写 JavaScript 代码。它们是真正的 JavaScript 应用程序!我们在编写一次性脚本时一直采用的方法已完全不再有效。

  面向对象编程 (OOP) 是一种流行的编程方法,很多 JavaScript 库中都使用这种方法,以便更好地管理和维护基本代码。JavaScript 支持 OOP,但与诸如 C++、C# 或 Visual Basic® 等流行的 Microsoft® .NET Framework 兼容语言相比,它支持 OOP 的方式非常不同,因此主要使用这些语言的开发人员开始可能会觉得在 JavaScript 中使用 OOP 很奇怪而且不直观。我写本文就是为了深入讨论 JavaScript 语言实际上如何支持面向对象编程,以及您如何使用这一支持在 JavaScript 中高效地进行面向对象开发。下面首先讨论对象(还能先讨论其他别的什么呢?)。

  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.”

作为对象,函数还可以赋给变量、作为参数传递给其他函数、作为其他函数的值返回,并可以作为对象的属性或数组的元素进行存储等等。

// 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”时,它的值也会更改以便指向相应的对象,如下所示。

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!

  最后一行表示的是将函数作为对象的方法进行调用的另一种方式。请记住,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”的属性,它反过来引用函数本身。这是一种循环引用,图 1 更好地说明了这种循环关系。

图 3 每个函数的原型都有一个 Constructor 属性
图 1 每个函数的原型都有一个 Constructor 属性

  现在,通过“new”运算符用函数(上面示例中为 Dog)创建对象时,所获得的对象将继承 Dog.prototype 的属性。在图 1 中,可以看到 Dog.prototype 对象有一个回指 Dog 函数的构造函数属性。这样,每个 Dog 对象(从 Dog.prototype 继承而来)都有一个回指 Dog 函数的构造函数属性。图 2 显示了构造函数、原型对象以及用它们创建的对象之间的这一关系。

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”));

图 5 实例继承其原型
图 2 实例继承其原型
某些读者可能已经注意到代码中对 hasOwnProperty 和 isPrototypeOf 方法的调用。这些方法是从哪里来的呢?它们不是来自 Dog.prototype。实际上,在 Dog.prototype 和 Dog 实例中还可以调用其他方法,比如 toString、toLocaleString 和 valueOf,但它们都不来自 Dog.prototype。您会发现,就像 .NET Framework 中的 System.Object 充当所有类的最终基类一样,JavaScript 中的 Object.prototype 是所有原型的最终基础原型。(Object.prototype 的原型是 null。)

  在此示例中,请记住 Dog.prototype 是对象。它是通过调用 Object 构造函数创建的(尽管它不可见):

  Dog.prototype = new Object();

  因此,正如 Dog 实例继承 Dog.prototype 一样,Dog.prototype 继承 Object.prototype。这使得所有 Dog 实例也继承了 Object.prototype 的方法和属性。

  每个 JavaScript 对象都继承一个原型链,而所有原型都终止于 Object.prototype。注意,迄今为止您看到的这种继承是活动对象之间的继承。它不同于继承的常见概念,后者是指在声明类时类之间的发生的继承。因此,JavaScript 继承动态性更强。它使用简单算法实现这一点,如下所示:当您尝试访问对象的属性/方法时,JavaScript 将检查该属性/方法是否是在该对象中定义的。如果不是,则检查对象的原型。如果还不是,则检查该对象的原型的原型,如此继续,一直检查到 Object.prototype。图 3 说明了此解析过程。

图 6 在原型链中解析 toString() 方法
图 3 在原型链中解析 toString() 方法

  JavaScript 动态地解析属性访问和方法调用的方式产生了一些特殊效果:

  继承原型对象的对象上可以立即呈现对原型所做的更改,即使是在创建这些对象之后。

  如果在对象中定义了属性/方法 X,则该对象的原型中将隐藏同名的属性/方法。例如,通过在 Dog.prototype 中定义 toString 方法,可以改写 Object.prototype 的 toString 方法。

  更改只沿一个方向传递,即从原型到它的派生对象,但不能沿相反方向传递。代码 说明了这些效果。显示了如何解决前面遇到的不需要的方法实例的问题。通过将方法放在原型内部,可以使对象共享方法,而不必使每个对象都有单独的函数对象实例。在此示例中,rover 和 spot 共享 getBreed 方法,直至在 spot 中以任何方式改写 toString 方法。此后,spot 有了它自己版本的 getBreed 方法,但 rover 对象和用新 GreatDane 创建的后续对象仍将共享在 GreatDane.prototype 对象中定义的那个 getBreed 方法实例。

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 中很容易做到这一点,因为函数是可以按需要设置其属性和方法的对象。由于在 JavaScript 中构造函数表示类,因此可以通过在构造函数中设置静态方法和属性,直接将它们添加到类中,如下所示:

  function DateTime() { }
  // set static method now()
  DateTime.now = function() {
  return new Date();
  };
  alert(DateTime.now());

  在 JavaScript 中调用静态方法的语法与在 C# 中几乎完全相同。这不应当让人感到吃惊,因为构造函数的名称实际上是类的名称。这样,就有了类、公用属性/方法,以及静态属性/方法。还需要其他什么吗?当然,私有成员。但 JavaScript 本身并不支持私有成员(同样,也不支持受保护成员)。任何人都可以访问对象的所有属性和方法。但我们有办法让类中包含私有成员,但在此之前,您首先需要理解闭包。

  闭包

  我没有自觉地学习过 JavaScript。我必须快点了解它,因为我发现如果没有它,在实际工作中编写 AJAX 应用程序的准备就会不充分。开始,我感到我的编程水平好像降了几个级别。(JavaScript!我的 C++ 朋友会怎么说?)但一旦我克服最初的障碍,我就发现 JavaScript 实际上是功能强大、表现力强而且非常简练的语言。它甚至具有其他更流行的语言才刚刚开始支持的功能。

  JavaScript 的更高级功能之一是它支持闭包,这是 C# 2.0 通过它的匿名方法支持的功能。闭包是当内部函数(或 C# 中的内部匿名方法)绑定到它的外部函数的本地变量时所发生的运行时现象。很明显,除非此内部函数以某种方式可被外部函数访问,否则它没有多少意义。示例可以更好说明这一点。假设需要根据一个简单条件筛选一个数字序列,这个条件是:只有大于 100 的数字才能通过筛选,并忽略其余数字。为此,可以编写类似的函数。

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);

  但是,现在要创建不同的筛选条件,假设这次只有大于 300 的数字才能通过筛选,则可以编写下面这样的函数:

  var greaterThan300 = filter(
  function(x) { return (x > 300) ? true : false; },
  someRandomNumbers);

  然后,也许需要筛选大于 50、25、10、600 如此等等的数字,但作为一个聪明人,您会发现它们全部都有相同的谓词“greater than”,只有数字不同。因此,可以用类似下面的函数分开各个数字:

  function makeGreaterThanPredicate(lowerBound) {
  return function(numberToCheck) {
  return (numberToCheck > lowerBound) ? true : false;
  };
  }

  这样,您就可以编写以下代码:

  var greaterThan10 = makeGreaterThanPredicate(10);
  var greaterThan100 = makeGreaterThanPredicate(100);
  alert(filter(greaterThan10, someRandomNumbers));
  alert(filter(greaterThan100, someRandomNumbers));

  通过观察函数 makeGreaterThanPredicate 返回的内部匿名函数,可以发现,该匿名内部函数使用 lowerBound,后者是传递给 makeGreaterThanPredicate 的参数。按照作用域的一般规则,当 makeGreaterThanPredicate 退出时,lowerBound 超出了作用域!但在这里,内部匿名函数仍然携带 lowerBound,甚至在 makeGreaterThanPredicate 退出之后的很长时间内仍然如此。这就是我们所说的闭包:因为内部函数关闭了定义它的环境(即外部函数的参数和本地变量)。

  开始可能感觉不到闭包的功能很强大。但如果应用恰当,它们就可以非常有创造性地帮您将想法转换成代码,这个过程非常有趣。在 JavaScript 中,闭包最有趣的用途之一是模拟类的私有变量。模拟私有属性

  现在介绍闭包如何帮助模拟私有成员。正常情况下,无法从函数以外访问函数内的本地变量。函数退出之后,由于各种实际原因,该本地变量将永远消失。但是,如果该本地变量被内部函数的闭包捕获,它就会生存下来。这一事实是模拟 JavaScript 私有属性的关键。假设有一个 Person 类:

  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; };
  }

  参数 name 和 age 是构造函数 Person 的本地变量。Person 返回时,name 和 age 应当永远消失。但是,它们被作为 Person 实例的方法而分配的四个内部函数捕获,实际上这会使 name 和 age 继续存在,但只能严格地通过这四个方法访问它们。因此,您可以:

  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.”);

  未在构造函数中初始化的私有成员可以成为构造函数的本地变量,如下所示:

  function Person(name, age) {
  var occupation;
  this.getOccupation = function() { return occupation; };
  this.setOccupation = function(newOcc) { occupation =
  newOcc; };
  // accessors for name and age
  }

  注意,这些私有成员与我们期望从 C# 中产生的私有成员略有不同。在 C# 中,类的公用方法可以访问它的私有成员。但在 JavaScript 中,只能通过在其闭包内拥有这些私有成员的方法来访问私有成员(由于这些方法不同于普通的公用方法,它们通常被称为特权方法)。因此,在 Person 的公用方法中,仍然必须通过私有成员的特权访问器方法才能访问私有成员:

  Person.prototype.somePublicMethod = function() {
  // doesn’t work!
  // alert(this.name);
  // this one below works
  alert(this.getName());
  };

  Douglas Crockford 是著名的发现(或者也许是发布)使用闭包来模拟私有成员这一技术的第一人。他的网站 javascript.crockford.com 包含有关 JavaScript 的丰富信息,任何对 JavaScript 感兴趣的开发人员都应当仔细研读。从类继承

  到这里,我们已经了解了构造函数和原型对象如何使您在 JavaScript 中模拟类。您已经看到,原型链可以确保所有对象都有 Object.prototype 的公用方法,以及如何使用闭包来模拟类的私有成员。但这里还缺少点什么。您尚未看到如何从类派生,这在 C# 中是每天必做的工作。遗憾的是,在 JavaScript 中从类继承并非像在 C# 中键入冒号即可继承那样简单,它需要进行更多操作。另一方面,JavaScript 非常灵活,可以有很多从类继承的方式。

  例如,有一个基类 Pet,它有一个派生类 Dog,如图 4 所示。这个在 JavaScript 中如何实现呢?Pet 类很容易。您已经看见如何实现它了:

图 9 类
图 4 类

  // 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);

  现在,如何创建从 Pet 派生的类 Dog 呢?在图 4 中可以看到,Dog 有另一个属性 breed,它改写了 Pet 的 toString 方法(注意,JavaScript 的约定是方法和属性名称使用 camel 大小写,而不是在 C# 中建议的 Pascal 大小写)。

// 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);

  所使用的原型 — 替换技巧正确设置了原型链,因此假如使用 C#,测试的实例将按预期运行。而且,特权方法仍然会按预期运行。模拟命名空间

  在 C++ 和 C# 中,命名空间用于尽可能地减少名称冲突。例如,在 .NET Framework 中,命名空间有助于将 Microsoft.Build.Task.Message 类与 System.Messaging.Message 区分开来。JavaScript 没有任何特定语言功能来支持命名空间,但很容易使用对象来模拟命名空间。如果要创建一个 JavaScript 库,则可以将它们包装在命名空间内,而不需要定义全局函数和类,如下所示:

  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”);

  可以想象,键入这些冗长的嵌套命名空间会让人很累。 幸运的是,库用户可以很容易地为命名空间指定更短的别名:

  // MSDNMagNS.Examples and Pet definition...
  // think “using Eg = MSDNMagNS.Examples;”
  var Eg = MSDNMagNS.Examples;
  var pet = new Eg.Pet(“Yammer”);
  alert(pet);

  如果看一下 Microsoft AJAX 库的源代码,就会发现库的作者使用了类似的技术来实现命名空间(请参阅静态方法 Type.registerNamespace 的实现)。有关详细信息,请参与侧栏“OOP 和 ASP.NET AJAX”。

  应当这样编写 JavaScript 代码吗?

  您已经看见 JavaScript 可以很好地支持面向对象的编程。尽管它是一种基于原型的语言,但它的灵活性和强大功能可以满足在其他流行语言中常见的基于类的编程风格。但问题是:是否应当这样编写 JavaScript 代码?在 JavaScript 中的编程方式是否应与 C# 或 C++ 中的编码方式相同?是否有更聪明的方式来模拟 JavaScript 中没有的功能?每种编程语言都各不相同,一种语言的最佳做法,对另一种语言而言则可能并非最佳。

  在 JavaScript 中,您已看到对象继承对象(与类继承类不同)。因此,使用静态继承层次结构建立很多类的方式可能并不适合 JavaScript。也许,就像 Douglas Crockford 在他的文章 Prototypal Inheritance in JavaScript 中说的那样,JavaScript 编程方式是建立原型对象,并使用下面的简单对象函数建立新的对象,而后者则继承原始对象:

  function object(o) {
  function F() {}
  F.prototype = o;
  return new F();
  }

  然后,由于 JavaScript 中的对象是可延展的,因此可以方便地在创建对象之后,根据需要用新字段和新方法增大对象。

  这的确很好,但它不可否认的是,全世界大多数开发人员更熟悉基于类的编程。实际上,基于类的编程也会在这里出现。按照即将颁发的 ECMA-262 规范第 4 版(ECMA-262 是 JavaScript 的官方规范),JavaScript 2.0 将拥有真正的类。因此,JavaScript 正在发展成为基于类的语言。但是,数年之后 JavaScript 2.0 才可能会被广泛使用。同时,必须清楚当前的 JavaScript 完全可以用基于原型的风格和基于类的风格读取和写入 JavaScript 代码。

  展望

  随着交互式胖客户端 AJAX 应用程序的广泛使用,JavaScript 迅速成为 .NET 开发人员最重要的工具之一。但是,它的原型性质可能一开始会让更习惯诸如 C++、C# 或 Visual Basic 等语言的开发人员感到吃惊。我已发现我的 JavaScript 学习经历给予了我丰富的体验,虽然其中也有一些挫折。如果本文能使您的体验更加顺利,我会非常高兴,因为这正是我的目标。

    • 评论
    • 分享微博
    • 分享邮件
    闂傚倸鍊搁崐鎼佸磹閹间礁纾归柟闂寸绾惧綊鏌熼梻瀵割槮缁炬儳缍婇弻鐔兼⒒鐎靛壊妲紒鎯у⒔閹虫捇鈥旈崘顏佸亾閿濆簼绨绘い鎺嬪灪閵囧嫰骞囬鍡欑厯闂佸搫琚崝鎴﹀箖閵忋倕浼犻柛鏇熷灟閸ㄤ粙寮婚敐澶樻晣闁绘洑鐒﹂悿浣肝旈悩闈涗沪闁绘娲濊ぐ浣割渻閵堝棗鍧婇柛瀣尭閳规垿妾遍柛瀣姉濡叉劙骞掑Δ鈧粻娑欍亜閹炬瀚弶褰掓⒒娴e憡鎯堥柣顓烆槺閹广垹鈹戦崱娆愭闂佸壊鍋呭ú鏍偂濞戞◤褰掓晲婢跺鏆犻梺娲诲亜缁绘ê顫忕紒妯诲闁告稑锕ラ崕鎾愁渻閵堝棗鐏ラ柟铏耿瀵粯绻濋崶銊︽珳婵犮垼娉涢敃銊╁箺閺囥垺鈷戦柛婵嗗缁侇偆绱掓潏銊︾闁诡喖娼¢崺鈧い鎺戝閻撶喖骞栭幖顓炵仯缂佸娼ч湁婵犲﹤鎳庢禒锔剧磼閸屾稑娴柟顔界懇椤㈡宕掑⿰鍛潓濠电姷鏁搁崑娑㈡偋閸℃稒鍊舵繝闈涱儐閸婂爼鏌eΟ鑲╁笡闁绘挸绻橀弻娑㈩敃閵堝懏鐎鹃梺绋匡攻閸旀鍩€椤掍緡鍟忛柛鐘崇墵閳ワ箑鐣¢柇锕€娈ㄩ梺鍦檸閸犳寮查鍕厱闁哄洢鍔岄獮妤佺節閵忊€崇伌婵﹨娅i幏鐘诲箵閹烘垶鐦g紓鍌氬€哥粔鏉懳涘▎鎴犵焿鐎广儱顦崘鈧銈嗘尵閸嬬喖鏁嶅☉銏♀拺閻熸瑥瀚粈鍐┿亜閺囧棗娲ょ粈鍐煃瑜滈崜娆撯€旈崘顔嘉ч柛鈩兠拕濂告⒑閸涘﹥鐓ユ繛鎾棑閸掓帒鈻庨幒鏃傛澑濠电偞鍨堕悷銏ゅ箯濞差亝鐓熼柣妯哄帠閼割亪鏌涢弬鑳闁宠绉归獮鍥偋閸垹骞嶉梻浣告啞閸垶宕愰弽顐熷亾濮樼偓瀚�

    婵犵數濮烽弫鍛婃叏閻戣棄鏋侀柛娑橈攻閸欏繘鏌i幋锝嗩棄闁哄绶氶弻娑樷槈濮楀牊鏁鹃梺鍛婄懃缁绘﹢寮婚敐澶婄婵犲灚鍔栫紞妤呮⒑闁偛鑻晶顕€鏌涙繝鍌涜础缂侇喖顑夐獮鎺楀棘閸濆嫪澹曢梺鎸庣箓缁ㄨ偐鑺辨禒瀣厱闁哄啯鎸鹃悾杈ㄣ亜椤忓嫬鏆e┑鈥崇埣瀹曞崬螖閳ь剙岣块幋锔解拺缂佸顑欓崕鎰版煙閻熺増鍠樼€殿喛顕ч埥澶愬閳ュ厖绨婚梻鍌欑閻忔繈顢栭崨顔绢浄闁圭虎鍠楅埛鎴犵磼椤栨稒绀冮柡澶婄秺閺屾稓鈧綆鍋呯亸顓熴亜椤忓嫬鏆e┑鈥崇埣瀹曞崬螖閳ь剙岣块幋锔解拺缂佸顑欓崕鎰版煙缁嬪灝鈷旀俊鍙夊姍楠炴﹢骞囨担鍛婂€梻浣告啞缁矂宕幎钘夎Е妞ゆ劏鎳¢弮鍫熷亹闂傚牊绋愮划鍫曟⒑閸濄儱娅忛柛瀣樀閹﹢骞掑Δ浣哄幗闂佺粯锚瀵墎绮氶崸妤佸€堕煫鍥ㄦ⒒閹冲懐绱掗鍡欑М闁诡喗鐟╅幃婊兾熼柨瀣伖闂佽崵鍠愮划搴㈡櫠濡ゅ啯鏆滈柟鐑樻尵椤╂彃霉閻撳海鎽犻柣鎾存礋閺岀喖骞嗚閸ょ喐绻涢崣澶嬪唉闁哄瞼鍠栭、娆撴寠婢跺﹤袘闂佺粯鎸堕崐鏍Φ閸曨喚鐤€闁圭偓娼欏▍婵嬫⒑瑜版帗鏁辨俊鐐舵椤繑绻濆顒傦紲濠殿喗锚瀹曨剟路閳ь剟姊绘担鍛婂暈妞ゃ劌妫楃叅闁绘棃顥撻弳锕€鈹戦崒婧撶懓鏁梻渚€娼ч敍蹇涘礋椤撶偛鍙婂┑鐘垫暩婵兘寮崨濠冨弿闁圭虎鍠楅弲婵嬫煏閸繃绀岄柛瀣尭椤繈鎮℃惔锛勭潉闁诲氦顫夊ú鈺冪礊閳ь剛绱掗悩宕囨创鐎殿噮鍣e畷鎺戔堪閸涱噮娼撳┑鐘垫暩婵兘寮崨濠冨弿闁圭虎鍠栫壕鍦磽娴h偂鎴濃枍閻樺磭绠鹃柟瀛樼懃閻忣亪鏌i幘瀛樼闁诡喗顨婇弫鎰板礃閵娿儺鐎冲┑鐘殿暯閳ь剝灏欓惌娆撴煛鐏炵偓绀嬬€规洘鍎奸ˇ鍙夈亜韫囷絽骞橀柍褜鍓氶鏍窗閺囩姴鍨濇繛鍡樺姃缁诲棙鎱ㄥ┑鍡欑劸婵℃彃缍婂娲焻閻愯尪瀚板褜鍨堕幃浠嬵敍閵堝洨鐦堥悗娈垮枛椤兘骞冮姀銈呯闁绘挸娴峰顔尖攽閻橆喖鐏辨繛澶嬬洴閺佸啴濡烽妷銏$亖婵犵數濮电喊宥夊煕閹达附鐓曟繝闈涙椤忣偊鏌h箛娑楁喚闁哄本鐩幃鈺佺暦閸パ€鎷版俊銈囧Х閸嬫盯宕锔哄亼濞村吋娼欓柋鍥ㄧ節闂堟稓澧曟鐐村姍濮婄粯鎷呴崨闈涚秺瀵敻顢楅崒婊呯厯闂佽鍎崇粻宥嗙瑜版帗鍊甸柨婵嗛閺嬬喖鏌i幘璺烘灈妤犵偞鐗曡彁妞ゆ巻鍋撻柍褜鍏欓崐鏇炵幓閸ф绠涙い鎾跺Х椤旀洟姊洪崨濠勬噧妞ゃ儯鍨介、鏃堝醇閻旇渹鎮f繝鐢靛█濞佳囨偋韫囨稒鍎楁繛鍡樻尰閸嬶綁鏌熼鐔风瑨濠德ゅГ缁绘盯宕奸妷褏鏆┑顔硷攻濡炶棄鐣峰⿰鍫濈闁瑰搫绉堕崙瑙勭節閻㈤潧浠滅€殿喖鐖奸幃褔鎮╃拠鑼暫闂佺偨鍎查弸鐓幬i崼銉︾厪闊洢鍎崇壕鍧楁煕濮椻偓娴滃爼寮婚敐鍫㈢杸闁哄洨鍋樼划鑸电節閳封偓閸屾粎鐓撻梺璇″灠閻ジ鍩€椤掑﹦绉甸柛瀣缁粯銈i崘鈺冨幈濡炪倖鍔戦崐鏇㈠几閹达附鐓曞┑鐘插暙婵牓鏌熸笟鍨缂佺粯绻堝畷鐔碱敇閻愭鍋ч梻鍌欒兌閹虫捇宕查弻銉ョ疇闁归偊鍠栭崹婵嬫煟閵忋埄鏆柛瀣崌閺佹劖鎯斿┑鍫濆毈闂備椒绱徊濂稿础閸愬樊娼栫紓浣股戞刊鎾偡濞嗗繐顏╁ù鐘櫊濮婃椽宕ㄦ繝鍐弳濡炪倖鍨甸ˇ顖炴偩閻戣姤鏅搁柣妯垮皺椤︺劑姊洪懖鈹炬嫛闁告挻鑹鹃埢鎾淬偅閸愨斁鎷洪梻鍌氱墛缁嬫挾绮婚悙鐑樼厱濠电姴鍟粈瀣偓瑙勬礃閿曘垽宕洪埀顒併亜閹哄棗浜惧銈庝簻閸熷瓨淇婇崼鏇炲耿婵°倐鍋撴い顐㈡喘濮婅櫣绮欓崸妤娾偓妤冣偓瑙勬处閸撶喖骞嗛崘顕呮晢闁告洦鍏橀幏濠氭⒑缁嬫寧婀伴柣鐔濆洤绀夌€广儱顦伴崐鐢电磼濡や胶鈽夐柟铏姍閹苯鈻庨幘瀵稿幍闁诲海鏁告灙鐞氥儵姊虹粙娆惧剱闁瑰憡鎮傞敐鐐测攽鐎n偄浜楅柟鑹版彧缁辨洟鎯堥崟顖涚厽閹兼番鍊ゅḿ鎰箾閸欏鐏寸€规洩绻濆畷姗€鎳犻浣诡啎闂備礁鎲¢〃鍫ュ磹閿濆鍋勯柛蹇撶毞閹峰綊姊鸿ぐ鎺戜喊闁哥姵鑹鹃埢鏃堝锤濡や讲鎷婚梺绋挎湰閻熝呯玻閺冣偓缁绘稒鎷呴崘鍙夊闁稿顑夐弻娑㈠焺閸愵亖妲堢紓浣哄Х婵炩偓妤犵偞鐗曡彁妞ゆ巻鍋撻柣蹇d邯閺屾稑鈻庤箛鏇狀唹闂侀潧娲ょ€氫即鐛幒妤€骞㈡俊鐐村劤椤ユ岸姊婚崒娆戭槮闁汇倕娲敐鐐村緞閹邦剙鐎梺绉嗗嫷娈旈柡鍕╁劦閺屾洘寰勯崱妯荤彅濡ょ姷鍋戦崹鐑樼┍婵犲洦鍊烽柟缁樺坊閹稿啴姊洪崨濠傜伇妞ゎ偄顦遍幑銏犫攽鐎n偄浠洪梻鍌氱墛閸掆偓闁绘劗鍎ら悡鏇㈡煏婵犲繘妾柕鍥ㄧ箞閺屸剝鎷呴崜鎻掑壎闂佸搫鐫欓崶銊ユ闂侀潧鐗嗛幊宥呪柦椤忓懐绠鹃柨婵嗘噺閹兼劙鏌ㄩ弴銊ょ凹濞e洤锕畷濂稿即閻愯尙鐛╂俊鐐€栭弻銊╁触鐎n噮鏁傞柨鐔哄У閳锋帒霉閿濆懏鍤堢憸鐗堝俯閺佸嫰鏌涘☉娆愮稇缁炬儳缍婇弻娑㈡晜鐠囨彃绠洪悗瑙勬礀瀵墎鎹㈠☉銏犵婵炲棗绻掓禒鐓幬旈悩闈涗杭闁搞劎鍎ょ粚杈ㄧ節閸ヨ埖鏅┑鐘茬仛閸旀洖鈻撻鐘电<闁绘劦鍓氱欢鑼磼婢跺﹦绉虹€殿喖顭烽弫鎰板幢濡搫濡抽梻渚€娼ф蹇曞緤娴犲瑤澶娾攽閸♀晜瀵岄梺闈涚墕缁绘劙銆呴鍕厸濞达絿鐡旈崵娆愩亜閺囶亞鎮奸柟椋庡Т椤斿繘顢欓崗鐓庘偓顖炴⒒娴gǹ顥忛柛瀣浮瀹曟垿宕ㄩ幖顓熸櫅闂佹悶鍎洪崜姘跺煕閹烘嚚褰掓晲閸涱喖鏆堥梺鍝ュ枔閸嬨倝寮婚悢鐓庣濞达綀娅i弳顐︽倵鐟欏嫭绀堝褎顨婇獮鍡涘籍閸埃鍋撻敃鍌氱闁绘劕鐡ㄩ悵銊╂⒒閸屾瑧鍔嶉悗绗涘懏宕查柛宀€鍊涢崶銊ь浄閻庯綆浜濋悗顒勬⒑缁洖澧查柕鍥ㄧ矒閸┾偓妞ゆ帊绀佺粭鎺撱亜椤愶絿绠炴い銏☆殕缁绘繈宕掑鍏兼祮闂傚倸鍊烽悞锕€顪冮崸妤€鍌ㄥù鐘差儍閳ь剙鍟村畷鍗炩枎閹邦剙绨ユ繝鐢靛仦閸垶宕归崷顓犱笉濠电姵纰嶉悡娑橆熆鐠轰警鍎忛柣蹇婃櫊閺屾盯寮幆褍绁┑顔硷龚濞咃絿鍒掑▎蹇婃瀻闁诡垎鍐棊闂傚倷绀佸﹢閬嶅箠閹捐秮娲敇閻戝棗娈ㄦ繝鐢靛У绾板秹寮查弻銉︾厱婵炴垵褰夌花鍏笺亜椤愩垻孝闁宠鍨块幃娆撳矗婢舵ɑ锛侀梻浣告啞濮婄懓煤濠婂嫮鐝堕柡鍥ュ灩缁狀噣鏌ら幁鎺戝姉闁归攱妞藉娲川婵犲嫮鐣甸柣搴㈠嚬閸樺ジ鏁冮姀銈呯妞ゆ柨澧介敍婊堟煟鎼搭垳绉甸柛瀣閹﹢骞橀鐣屽幈闂佸啿鎼崯顐g鏉堫煈娈介柣鎰▕閸庢棃鏌℃担绋挎殻闁糕斁鍋撳銈嗗坊閸嬫挻銇勯弬璺ㄧ闁宠鍨垮畷鍫曞Ψ閵堝洨娉块梻鍌欑閹碱偄煤閵娾晛纾婚柣鎰惈闂傤垱绻涘顔荤凹闁绘挸鍟撮幃褰掑炊椤忓秴娈濋梺鍛婃⒒閸犳挾妲愰幒鎾寸秶闁靛⿵绠戠壕鎶芥倵鐟欏嫭绀冮悽顖涘浮閸┿垺鎯旈妸銉ь吅濠电娀娼уΛ顓㈡倵閺夋垟鏀介柨娑樺娴滃ジ鏌涙繝鍐ㄧ伌鐎规洘绻傞悾婵嬪礋椤愩倗鏋冩繝娈垮枟閵囨盯宕戦幘鎼闁绘劕寮堕ˉ婊堟煟閿濆棛绠炵€规洜鍠栭、鏇㈠Χ韫囨洖绨ラ梻鍌氬€烽懗鍓佸垝椤栫偛绀夋繛鍡楃箘缂傛氨鎲歌箛鏇炲灊濠电姴娲﹂弲婵嬫煕鐏炲墽銆掗柛妯兼暬濮婅櫣绮欑捄銊ь啈闂佺ǹ顑嗛崝娆忣嚕閸愬樊娼ㄩ柍褜鍓熷璇测槈閵忕姵顥濋柣鐘充航閸斿酣宕濋鐐粹拺閻犲洠鈧櫕鐏撻梺绋款儍閸婃洟鎮鹃悿顖樹汗闁圭儤绻冮弲婵嬫⒑缂佹〒瑙勭椤掑嫭鍎庨幖娣妽閳锋帒霉閿濆懏鍟為悹鎰剁節閺屾稒鎯斿☉妯峰亾濠靛棛鏆︾憸鐗堝笚閸嬨劑鏌涢幘鍐茬骇闁哄懏绮撳娲礃閸欏鍎撻梺鐟板暱缁绘ê鐣烽幇鏉垮瀭妞ゆ劧绲藉鍨攽椤旂瓔娈旀俊顐n殕閺呭墎绱掑Ο鍦畾闂佸湱绮敮鐐电矓濞差亝鐓涢悘鐐靛亾缁€瀣偓瑙勬礃閸庡ジ藝閸欏浜滈煫鍥风到楠炴﹢鏌嶈閸撴岸顢欓弽顓炵獥闁哄洨濮撮崹婵囩箾閸℃ê濮冪紒璇叉閹便劌鈹戦崱娆戝姼濠碘剝褰冮妶绋款潖濞差亝顥堥柍鍝勫暟閵堢兘姊虹粙鍧楊€楃痪缁㈠幖鍗遍柟鐗堟緲缁犲鎮楀☉娅亪顢撻幘鍓佺=濞达絽婀遍埥澶嬨亜閹存繃鍣归柍缁樻崌椤㈡﹢鎮欓埡鍌涙澑闂備礁鐤囧Λ鍕涘Δ浣侯洸婵犻潧鐗忕壕濂告偣閸ヮ亜鐨哄褎娲橀妵鍕即椤忓棛蓱缂備胶绮换鍌烇綖濠靛鏁囬柣鎰閻╁海绱撻崒娆掑厡闁稿鎸婚崚濠囨嚍閵夛絼绮撻梺褰掓?缁€渚€鎮挎ィ鍐╃叆婵犻潧妫Σ褰掓煕鐎Q冨⒉缂佺粯绻冪换婵嬪磼濮橆厽顔嶉梺杞扮閻楁挸顫忛搹瑙勫珰闁炽儴娅曢悘宥夋⒑閹稿孩纾搁柛銊ょ矙閻涱喗绻濋崶銊ヤ汗濠电偠灏欑划顖炲疾閵忋倖鈷戠紒瀣濠€鎵磼椤曞棛鎮兼俊鍙夊姍瀵濡烽敂瑙勫闂備礁鎲$换鍌溾偓姘槻鍗遍柟闂寸劍閻撶喐淇婇妶鍌氫壕闁诲孩纰嶅姗€鎮惧畡鎵虫斀闁搞儯鍔岄崝鍛存⒑閹稿海绠撴繛璇х悼缁厽寰勯幇顓涙嫼缂傚倷鐒﹂敋濠殿喖顦甸弻娑欐償濞戞ǚ鍋撳┑瀣畺闁炽儲鏋煎Σ鍫ユ煏韫囧ň鍋撻弬銉ヤ壕闁割偅娲橀悡鐔兼煙闁箑鐏$痪顓炲⒔閹喖顫濋懜纰樻嫼闂佸憡绺块崕杈ㄧ墡闂備焦瀵уú蹇涘垂閽樺鍤曞┑鐘宠壘閻掓椽鏌涢幇銊︽珔妞ゅ孩鎹囧娲嚒閵堝懏鐎惧┑鐘灪閿曘垽鏁愰悙鍝勭婵°倓绀侀埀顒傛暬閹嘲鈻庤箛鎿冧痪缂備讲鍋撻柛顐犲劜閻撴洟鏌eΟ铏癸紞濠⒀呮暬閺屾洟宕遍弴鐙€妲銈庡亝缁捇宕洪埀顒併亜閹烘垵顏╃紒鐘崇墵閺屽秹宕崟顒€娅ら梺璇″灣閸嬬偤濡甸崟顖氱疀闁宠桨璁查崑鎾斥攽鐎n亞顔嗘繝鐢靛У绾板秹鍩涢幒妤佺厱閻忕偞宕樻竟姗€鏌嶈閸撴瑩宕查弻銉︾畳闂備焦鎮堕崕婊堝川椤旂瓔鍟庨梻鍌欑婢瑰﹪鎮¢崼銉ョ;闁告稒娼欓惌妤呯叓閸ャ劎鈯曢柣鎾寸懇閺岀喖顢涘☉娆戝嚒閻炴碍宀稿娲箮閼恒儲鏆犲┑顔硷工椤兘宕洪埀顒併亜閹哄棗浜剧紓浣哄Т缁夌懓鐣烽弴銏$劶鐎规挶鍎卞ú顓€€佸☉銏″€烽柟缁樺笧閳ь剦鍙冨铏圭矙鐠恒劎浼囬梺绋款儑閸嬨倝骞冮敓鐘差潊闁炽儴灏欑粻姘渻閵堝棛澧紒顔肩焸閹啴骞嬮悩鐢碉紲闁哄鐗勯崝宀勫传濞差亝鐓涢悘鐐额嚙婵″ジ鏌嶇憴鍕伌鐎规洟浜堕崺锟犲磼閸岋箑顩紒杈ㄦ尰閹峰懘骞撻幒宥咁棜闂備浇顕ч崙鐣岀礊閸℃稑纾诲ù锝呮贡椤╁弶绻濇繝鍌滃闁绘挻绋撻埀顒€绠嶉崕閬嶅疮椤愶絼绻嗛柛褎顨嗛悡鍐煢濡警妯堟俊鎻掓贡缁辨帞绱掑Ο鍏煎垱閻庤娲栭妶鎼佸箖閵忋倖鎯為柛锔诲幖鐢劌鈹戦悩鎰佸晱闁哥姵鐗犻弫鍐Ω閵夈垺鐎洪梺鎸庣箓閹冲危閸儲鐓熼柕蹇婃嚉閻熼偊鍟呮繝闈涙储娴滄粓鏌¢崒娑樺姢閻庢艾閰i弻宥堫檨闁告挻宀稿畷婵嬪即閳垛斁鍋撻弮鍫濈妞ゆ柨妲堣楠炴牜鍒掗崗澶婁壕闁肩⒈鍓欓崵顒€鈹戦悩鍨毄闁稿濮锋禍绋库枎閹惧磭鐛ラ梺鍝勮癁閸涱喗顔曟繝鐢靛仜濡﹥绂嶅⿰鍫熷€块柛顭戝亖娴滄粓鏌熼崫鍕ф俊鎯у槻闇夋繝濠傚閻帡鏌″畝鈧崰鏍х暦椤愶箑绀嬫い鎺戭槹椤ワ絽鈹戦悙鑼憼缂侇喗鎸剧划濠氬冀瑜滃ḿ鏍ㄧ箾瀹割喕绨荤紒鐘卞嵆楠炴牕菐椤掆偓閻忣噣鏌嶇憴鍕姇缂佺粯绻勯崰濠偽熷畡棰佸闂佹儳娴氶崑鍛村箖濞嗘挻鐓曟俊銈呮噸閹查箖鏌″畝鈧崰鏍箖瑜斿畷濂告偄閸濆嫬娈ョ紓鍌氬€风欢锟犲磻閸曨厾鐭撶憸鐗堝笒閽冪喐绻涢幋娆忕労闁轰礁鍟撮弻銊モ攽閸℃ê绐涢梺浼欑悼閸樠団€旈崘顔嘉ч柛鈩兠棄宥呪攽閿涘嫬浠╂俊顐㈠閿濈偠绠涘☉娆愬劒闂侀潻瀵岄崢楣冩晬濠婂啠鏀介柍钘夋閻忕娀鏌熺喊鍗炰喊闁挎繄鍋涢悾婵嬪礋椤掆偓閳ь剙鐏氱换娑㈠醇濠靛牅铏庡┑鐐叉噺閿曘垽寮诲☉銏犵閻庨潧鎲¢崳顖炴⒑鐠囪尙绠伴柛鐔告綑椤繐煤椤忓嫬绐涙繝鐢靛С閼冲墎鎹㈡笟鈧娲传閵夈儛锝団偓鍏夊亾缂佸娉曢弳锕傛煏韫囧鈧洜绮婚悷鎳婂綊鏁愰崨顓炶緟闂佸綊鏀卞钘夘潖濞差亜宸濆┑鐘插暙闂夊秹鎮峰⿰鍕凡闁哥噥鍨崇划瀣吋閸℃瑯娴勯柣搴秵閸嬪棝宕㈤柆宥嗙厽閹兼惌鍨崇粔闈浢瑰⿰鍕煉妞ゃ垺妫冮、姗€濮€閿涘嫬骞嶆俊鐐€栧褰掑几婵犳碍鍤€闁秆勵殕閻撴稑霉閿濆毥褰掑汲閿濆洠鍋撶憴鍕閻㈩垱甯¢崺銉﹀緞婵犲孩鍍靛銈嗗坊閸嬫挾绱掗悩闈涗槐婵﹦绮幏鍛驳鐎n亝顔勯梻浣告啞閸ㄥ綊寮查銈嗩潟闁绘劕鎼獮銏$箾閹寸偍缂氶柨娑欑洴濮婅櫣鍖栭弴鐐测拤闁藉啳椴哥换娑㈠川椤栨锝夋煏閸パ冾伃妤犵偞甯¢獮瀣攽閸愩劋澹曢梺鐓庮潟閸婃洟藟濮橆兘鏀介柛灞剧閸熺偤鏌i幘瀛樼闁诡喗锕㈤幃娆撳箵閹哄棙瀵栭梻浣哥枃濡嫰藝鏉堚晜顫曢柟鐑樺焾濞撳鏌ㄩ弴妤€浜惧┑鐐额嚋缁犳捇骞冮垾鏂ユ瀻闁规儳顕崢浠嬫⒑缂佹ɑ鐓ラ柟璇х磿濞嗐垽宕f径鍫滅盎闂婎偄娴勭徊钘夘嚕椤曗偓閺屸€崇暆閳ь剟宕伴弽顓炵畺闁斥晛鍟崕鐔兼煏韫囥儳鎮兼い銏犳嚇濮婄粯鎷呴悜妯烘畬閻庢鍠栭悥濂哥嵁閹版澘绀冮柤纰卞墯濞堥箖姊虹紒妯荤叆闁告艾顑夎棢闁割偆鍠撶粻楣冩煙鐎电ǹ浠ч柟鍐插閺岋絽鈹戦幘鍓佺槇濠殿喖锕ら…宄扮暦閹烘埈娼╂い鎴f娴滈箖鏌熼梻瀵割槮缁惧墽鎳撻—鍐偓锝庝簼閹癸綁鏌i鐐搭棞闁靛棙甯掗~婵嬫晲閸涱剙顥氬┑掳鍊楁慨鐑藉磻閻愮儤鍋嬮柣妯荤湽閳ь兛绶氬鎾閻樻爠鍥ㄧ厱閻忕偛澧介悡顖氼熆鐟欏嫭绀€闁宠鍨块、娆撴儗椤愵偂绨婚柣锝囧厴椤㈡宕熼銏犱憾闂佽娴烽弫鍝ユ兜閸洖纾婚柟鎹愬煐閸犲棝鏌涢弴銊ュ妞わ负鍎崇槐鎾诲磼濮樻瘷銏ゆ煥閺囥劋绨绘い鏇稻缁绘繂顫濋鈧懓鍨攽鎺抽崐鏇㈡晝閵堝绠栭柟杈鹃檮閳锋垿鏌涘☉姗堟缂佸爼浜堕弻娑㈠Ω瑜庡▍鏇熶繆閸欏濮嶉柡灞芥椤撳ジ宕卞▎蹇撶闂備胶顢婃竟鍫ュ箵椤忓棙顫曢柡鍥ュ灪閸嬧晜銇勯幘鍗炵仾闁绘挻娲熼獮鏍庨鈧俊娲煙閺屻儳鐣洪柟绋匡工閳规垿宕伴姀鐘电Ш鐎规洘顨婂畷妤呮嚃閳哄啠鏋忕紓鍌欒兌閸嬫挸鐣峰Ο琛℃灃闁哄洢鍨归悞鍨亜閹哄棗浜剧紓浣哄Т缁夌懓鐣烽弴銏$劶鐎规挶鍎卞ú锔锯偓浣冨亹閳ь剚绋掗敋濞存粍顨婂娲传閸曨剙鍋嶉梺鎼炲妽濡炰粙骞冮垾鏂ユ闁靛骏绱曢崢閬嶆⒑闂堟侗妾х紒韫矙瀹曟繂顫濋鑺ユ杸闂佹寧绋戠€氼剛鏁懜鐐逛簻妞ゆ挴鈧啿濮峰銇卞倻绐旈柡灞剧洴婵℃悂濡堕崨顓犮偖婵犵數濮崑鎾炽€掑锝呬壕濠殿喖锕ㄥ▍锝囨閹烘嚦鐔煎箻閾忣偉纭€缂備緡鍠楀Λ鍐€佸璺虹劦妞ゆ帒瀚ㄩ埀顑跨窔瀵噣宕煎┑瀣暪闂備胶绮Λ浣糕枍閿濆姹查柕蹇嬪€栭埛鎺楁煕鐏炴崘澹橀柍褜鍓涢崗姗€骞冮悙鐑樻櫆闂佹鍨版禍鍓х磼濡や胶鈽夐柟鍐茬箰閻g兘寮婚妷锔惧幈闁诲繒鍋熼搹搴ㄥ吹閵堝棛绠鹃柟瀵稿剱閻掓悂鏌¢崘銊у鐎瑰憡绻冮妵鍕箻鐠虹洅銏☆殽閻愭潙娴慨濠勭帛閹峰懘宕ㄦ繝鍐ㄥ壍婵犵數鍋涢惇浼村垂閽樺鏆﹂柣鐔稿櫞濞差亶鏁傞柛娑卞幒缁ㄧ敻姊绘担鍛婂暈闁告棑闄勭粋宥呪攽鐎n亞鐛ラ梺褰掑亰閸樺墽寮ч埀顒佺節閻㈤潧孝闁稿﹦绮弲鍫曞即閻樼數锛滈梺閫炲苯澧寸€规洖銈搁幃銏㈢矙閸喕绱熷┑鐘殿暯濡插懘宕规潏鈹惧亾缁楁稑鎳忛崗婊兾旈敐鍛殲闁绘挶鍨介弻娑㈠箛閸忓摜鐩庨梺鍝勵儐閻╊垶寮婚敍鍕勃闁绘劦鍓涢ˇ銊х磽娴h櫣甯涚紒瀣墵钘濋柡灞诲劜閸婂灚鎱ㄥΟ鍝勮埞闁告ê顕埀顒冾潐濞叉﹢宕归幐搴濈箚闁归棿绀侀悡娑㈡煕鐏炵虎娈曢悗姘冲亹缁辨捇宕掑顑藉亾閻戣姤鍊块柨鏇炲€归弲顏勨攽閻樻剚鍟忛柛鐘崇墪鐓ら柨鏇炲€告闂佸憡娲﹂崹浼村础閹惰姤鐓忓┑鐐茬仢閸旀氨绱掗銏⑿ф慨濠呮閸栨牠寮撮悢鍛婄翻闂備焦鎮堕崝蹇撐涢崟顖f晪闁挎繂顦粻缁樸亜閺冨倵鎷℃繛鐓庯躬濮婃椽鎮欓挊澶婂闂佸搫顑呴妶绋跨暦閹达箑绠荤紓浣姑禒娲⒑閸涘﹦鈽夐柨鏇畵瀹曪綁宕卞缁樻杸闂佺粯岣跨划顖氣槈瑜庢穱濠囶敃閿濆孩鐤侀梺绯曟杹閸嬫挸顪冮妶鍡楃瑨闁稿﹤缍婂畷鐢稿焵椤掑嫭鈷戦悗鍦閸ゆ瑧绱掓径灞惧殌闁伙絿鍏樺畷濂稿即閻愬秮鏅犻弻銊モ攽閸℃浼€闂佺儵鏅涢柊锝咁潖缂佹ḿ绡€閹肩补鈧尙鐩庢繝鐢靛仩椤曟粍淇婇崶鈺佸灊婵炲棙鎸哥粻娑㈡煛婢跺孩纭堕柣銈呮噺娣囧﹪濡惰箛鏇炲煂闂佸摜鍣ラ崹鍫曞箖閳ユ枼妲堥柕蹇娾偓鏂ュ亾閸洘鐓熼柟鎵濞懷兠瑰⿰鍐ㄢ挃缂佽鲸甯¢崺鈧い鎺戝€甸崑鎾绘晲鎼粹€冲箣闂佺ǹ顑嗛幐楣冨箟閹绢喖绀嬫い鎺戝亞濡茬懓鈹戦悙鑼憼缂侇喖鐭傞幃銉︾附缁嬭銉ッ归敐鍛棌婵炵鍔戦弻宥堫檨闁告挾鍠栭悰顕€宕橀纰辨綂闂佹寧绋戠€氭澘顬婇鐣岀瘈闁靛骏绲剧涵鐐亜閿曚線鍝虹€垫澘瀚伴弫鎾绘晸閿燂拷

    重磅专题
    往期文章
    最新文章