前端开发
北京前端外包 电话:186-2237-7561
服务客户有中国移动、方正国际、中百集团等
Search:js代码
ECMAScript 定义类或对象
使用预定义对象只是面向对象语言的能力的一部分,它真正强大之处在于能够创建自己专用的类和对象。
ECMAScript 拥有很多创建对象或类的方法。
工厂方式ECMAScript 定义类或对象使用预定义对象只是面向对象语言的能力的一部分,它真正强大之处在于能够创建自己专用的类和对象。ECMAScript 拥有很多创建对象或类的方法。工厂方式原始的方式因为对象的属性可以在对象创建后动态定义,所有许多开发者都在 JavaScript 最初引入时编写类似下面的代码:var oCar = new Object;oCar.color = "blue";oCar.doors = 4;oCar.mpg = 25;oCar.showColor = function() {alert(this.color);};TIY在上面的代码中,创建对象 car。然后给它设置几个属性:它的颜色是蓝色,有四个门,每加仑油可以跑 25 英里。最后一个属性实际上是指向函数的指针,意味着该属性是个方法。执行这段代码后,就可以使用对象 car。不过这里有一个问题,就是可能需要创建多个 car 的实例。解决方案:工厂方式要解决该问题,开发者创造了能创建并返回特定类型的对象的工厂函数(factory function)。例如,函数 createCar() 可用于封装前面列出的创建 car 对象的操作:function createCar() {var oTempCar = new Object;oTempCar.color = "blue";oTempCar.doors = 4;oTempCar.mpg = 25;oTempCar.showColor = function() {alert(this.color);};return oTempCar;}var oCar1 = createCar();var oCar2 = createCar();TIY在这里,第一个例子中的所有代码都包含在 createCar() 函数中。此外,还有一行额外的代码,返回 car 对象(oTempCar)作为函数值。调用此函数,将创建新对象,并赋予它所有必要的属性,复制出一个我们在前面说明过的 car 对象。因此,通过这种方法,我们可以很容易地创建 car 对象的两个版本(oCar1 和 oCar2),它们的属性完全一样。为函数传递参数我们还可以修改 createCar() 函数,给它传递各个属性的默认值,而不是简单地赋予属性默认值:function createCar(sColor,iDoors,iMpg) {var oTempCar = new Object;oTempCar.color = sColor;oTempCar.doors = iDoors;oTempCar.mpg = iMpg;oTempCar.showColor = function() {alert(this.color);};return oTempCar;}var oCar1 = createCar("red",4,23);var oCar2 = createCar("blue",3,25);oCar1.showColor();//输出 "red"oCar2.showColor();//输出 "blue"TIY给 createCar() 函数加上参数,即可为要创建的 car 对象的 color、doors 和 mpg 属性赋值。这使两个对象具有相同的属性,却有不同的属性值。在工厂函数外定义对象的方法虽然 ECMAScript 越来越正式化,但创建对象的方法却被置之不理,且其规范化至今还遭人反对。一部分是语义上的原因(它看起来不像使用带有构造函数 new 运算符那么正规),一部分是功能上的原因。功能原因在于用这种方式必须创建对象的方法。前面的例子中,每次调用函数 createCar(),都要创建新函数 showColor(),意味着每个对象都有自己的 showColor() 版本。而事实上,每个对象都共享同一个函数。有些开发者在工厂函数外定义对象的方法,然后通过属性指向该方法,从而避免这个问题:`function showColor() {alert(this.color);}`function createCar(sColor,iDoors,iMpg) {var oTempCar = new Object;oTempCar.color = sColor;oTempCar.doors = iDoors;oTempCar.mpg = iMpg;`oTempCar.showColor = showColor;`return oTempCar;}var oCar1 = createCar("red",4,23);var oCar2 = createCar("blue",3,25);oCar1.showColor();//输出 "red"oCar2.showColor();//输出 "blue"TIY在上面这段重写的代码中,在函数 createCar() 之前定义了函数 showColor()。在 createCar() 内部,赋予对象一个指向已经存在的 showColor() 函数的指针。从功能上讲,这样解决了重复创建函数对象的问题;但是从语义上讲,该函数不太像是对象的方法。所有这些问题都引发了开发者定义的构造函数的出现。构造函数方式创建构造函数就像创建工厂函数一样容易。第一步选择类名,即构造函数的名字。根据惯例,这个名字的首字母大写,以使它与首字母通常是小写的变量名分开。除了这点不同,构造函数看起来很像工厂函数。请考虑下面的例子:function Car(sColor,iDoors,iMpg) {`this`.color = sColor;`this`.doors = iDoors;`this`.mpg = iMpg;`this`.showColor = function() {alert(this.color);};}var oCar1 = `new` Car("red",4,23);var oCar2 = `new` Car("blue",3,25);TIY下面为您解释上面的代码与工厂方式的差别。首先在构造函数内没有创建对象,而是使用 this 关键字。使用 new 运算符构造函数时,在执行第一行代码前先创建一个对象,只有用 this 才能访问该对象。然后可以直接赋予 this 属性,默认情况下是构造函数的返回值(不必明确使用 return 运算符)。现在,用 new 运算符和类名 Car 创建对象,就更像 ECMAScript 中一般对象的创建方式了。你也许会问,这种方式在管理函数方面是否存在于前一种方式相同的问题呢?是的。就像工厂函数,构造函数会重复生成函数,为每个对象都创建独立的函数版本。不过,与工厂函数相似,也可以用外部函数重写构造函数,同样地,这么做语义上无任何意义。这正是下面要讲的原型方式的优势所在。原型方式该方式利用了对象的 prototype 属性,可以把它看成创建新对象所依赖的原型。这里,首先用空构造函数来设置类名。然后所有的属性和方法都被直接赋予 prototype 属性。我们重写了前面的例子,代码如下:function Car() {}Car.prototype.color = "blue";Car.prototype.doors = 4;Car.prototype.mpg = 25;Car.prototype.showColor = function() {alert(this.color);};var oCar1 = new Car();var oCar2 = new Car();TIY在这段代码中,首先定义构造函数(Car),其中无任何代码。接下来的几行代码,通过给 Car 的 prototype 属性添加属性去定义 Car 对象的属性。调用 new Car() 时,原型的所有属性都被立即赋予要创建的对象,意味着所有 Car 实例存放的都是指向 showColor() 函数的指针。从语义上讲,所有属性看起来都属于一个对象,因此解决了前面两种方式存在的问题。此外,使用这种方式,还能用 instanceof 运算符检查给定变量指向的对象的类型。因此,下面的代码将输出 TRUE:alert(oCar1 instanceof Car);//输出 "true"原型方式的问题原型方式看起来是个不错的解决方案。遗憾的是,它并不尽如人意。首先,这个构造函数没有参数。使用原型方式,不能通过给构造函数传递参数来初始化属性的值,因为 Car1 和 Car2 的 color 属性都等于 "blue",doors 属性都等于 4,mpg 属性都等于 25。这意味着必须在对象创建后才能改变属性的默认值,这点很令人讨厌,但还没完。真正的问题出现在属性指向的是对象,而不是函数时。函数共享不会造成问题,但对象却很少被多个实例共享。请思考下面的例子:function Car() {}Car.prototype.color = "blue";Car.prototype.doors = 4;Car.prototype.mpg = 25;`Car.prototype.drivers = new Array("Mike","John");`Car.prototype.showColor = function() {alert(this.color);};var oCar1 = new Car();var oCar2 = new Car();`oCar1.drivers.push("Bill");`alert(oCar1.drivers);//输出 "Mike,John,Bill"alert(oCar2.drivers);//输出 "Mike,John,Bill"TIY上面的代码中,属性 drivers 是指向 Array 对象的指针,该数组中包含两个名字 "Mike" 和 "John"。由于 drivers 是引用值,Car 的两个实例都指向同一个数组。这意味着给 oCar1.drivers 添加值 "Bill",在 oCar2.drivers 中也能看到。输出这两个指针中的任何一个,结果都是显示字符串 "Mike,John,Bill"。由于创建对象时有这么多问题,你一定会想,是否有种合理的创建对象的方法呢?答案是有,需要联合使用构造函数和原型方式。混合的构造函数/原型方式联合使用构造函数和原型方式,就可像用其他程序设计语言一样创建对象。这种概念非常简单,即用构造函数定义对象的所有非函数属性,用原型方式定义对象的函数属性(方法)。结果是,所有函数都只创建一次,而每个对象都具有自己的对象属性实例。我们重写了前面的例子,代码如下:function Car(sColor,iDoors,iMpg) {this.color = sColor;this.doors = iDoors;this.mpg = iMpg;this.drivers = new Array("Mike","John");}Car.prototype.showColor = function() {alert(this.color);};var oCar1 = new Car("red",4,23);var oCar2 = new Car("blue",3,25);oCar1.drivers.push("Bill");alert(oCar1.drivers);//输出 "Mike,John,Bill"alert(oCar2.drivers);//输出 "Mike,John"TIY现在就更像创建一般对象了。所有的非函数属性都在构造函数中创建,意味着又能够用构造函数的参数赋予属性默认值了。因为只创建 showColor() 函数的一个实例,所以没有内存浪费。此外,给 oCar1 的 drivers 数组添加 "Bill" 值,不会影响到 oCar2 的数组,所以输出这些数组的值时,oCar1.drivers 显示的是 "Mike,John,Bill",而 oCar2.drivers 显示的是 "Mike,John"。因为使用了原型方式,所以仍然能利用 instanceof 运算符来判断对象的类型。这种方式是 ECMAScript 采用的主要方式,它具有其他方式的特性,却没有他们的副作用。不过,有些开发者仍觉得这种方法不够完美。动态原型方法对于习惯使用其他语言的开发者来说,使用混合的构造函数/原型方式感觉不那么和谐。毕竟,定义类时,大多数面向对象语言都对属性和方法进行了视觉上的封装。请考虑下面的 Java 类:class Car {public String color = "blue";public int doors = 4;public int mpg = 25;public Car(String color, int doors, int mpg) {this.color = color;this.doors = doors;this.mpg = mpg;}public void showColor() {System.out.println(color);}}Java 很好地打包了 Car 类的所有属性和方法,因此看见这段代码就知道它要实现什么功能,它定义了一个对象的信息。批评混合的构造函数/原型方式的人认为,在构造函数内部找属性,在其外部找方法的做法不合逻辑。因此,他们设计了动态原型方法,以提供更友好的编码风格。动态原型方法的基本想法与混合的构造函数/原型方式相同,即在构造函数内定义非函数属性,而函数属性则利用原型属性定义。唯一的区别是赋予对象方法的位置。下面是用动态原型方法重写的 Car 类:function Car(sColor,iDoors,iMpg) {this.color = sColor;this.doors = iDoors;this.mpg = iMpg;this.drivers = new Array("Mike","John");if (`typeof Car._initialized == "undefined"`) {Car.prototype.showColor = function() {alert(this.color);};`Car._initialized = true;`}}TIY直到检查 typeof Car._initialized 是否等于 "undefined" 之前,这个构造函数都未发生变化。这行代码是动态原型方法中最重要的部分。如果这个值未定义,构造函数将用原型方式继续定义对象的方法,然后把 Car._initialized 设置为 true。如果这个值定义了(它的值为 true 时,typeof 的值为 Boolean),那么就不再创建该方法。简而言之,该方法使用标志(_initialized)来判断是否已给原型赋予了任何方法。该方法只创建并赋值一次,传统的 OOP 开发者会高兴地发现,这段代码看起来更像其他语言中的类定义了。混合工厂方式这种方式通常是在不能应用前一种方式时的变通方法。它的目的是创建假构造函数,只返回另一种对象的新实例。这段代码看起来与工厂函数非常相似:function Car() {`var oTempCar = new Object;`oTempCar.color = "blue";oTempCar.doors = 4;oTempCar.mpg = 25;oTempCar.showColor = function() {alert(this.color);};return oTempCar;}TIY与经典方式不同,这种方式使用 new 运算符,使它看起来像真正的构造函数:var car = new Car();由于在 Car() 构造函数内部调用了 new 运算符,所以将忽略第二个 new 运算符(位于构造函数之外),在构造函数内部创建的对象被传递回变量 car。这种方式在对象方法的内部管理方面与经典方式有着相同的问题。强烈建议:除非万不得已,还是避免使用这种方式。采用哪种方式如前所述,目前使用最广泛的是混合的构造函数/原型方式。此外,动态原始方法也很流行,在功能上与构造函数/原型方式等价。可以采用这两种方式中的任何一种。不过不要单独使用经典的构造函数或原型方式,因为这样会给代码引入问题。实例对象令人感兴趣的一点是用它们解决问题的方式。ECMAScript 中最常见的一个问题是字符串连接的性能。与其他语言类似,ECMAScript 的字符串是不可变的,即它们的值不能改变。请考虑下面的代码:var str = "hello ";str += "world";实际上,这段代码在幕后执行的步骤如下:创建存储 "hello " 的字符串。创建存储 "world" 的字符串。创建存储连接结果的字符串。把 str 的当前内容复制到结果中。把 "world" 复制到结果中。更新 str,使它指向结果。每次完成字符串连接都会执行
ECMAScript 继承机制实现
继承机制的实现
要用 ECMAScript 实现继承机制,您可以从要继承的基类入手。所有开发者定义的类都可作为基类。出于安全原因,本地类和宿主类不能作为基类ECMAScript 继承机制实现继承机制的实现要用 ECMAScript 实现继承机制,您可以从要继承的基类入手。所有开发者定义的类都可作为基类。出于安全原因,本地类和宿主类不能作为基类,这样可以防止公用访问编译过的浏览器级的代码,因为这些代码可以被用于恶意攻击。选定基类后,就可以创建它的子类了。是否使用基类完全由你决定。有时,你可能想创建一个不能直接使用的基类,它只是用于给子类提供通用的函数。在这种情况下,基类被看作抽象类。尽管 ECMAScript 并没有像其他语言那样严格地定义抽象类,但有时它的确会创建一些不允许使用的类。通常,我们称这种类为抽象类。创建的子类将继承超类的所有属性和方法,包括构造函数及方法的实现。记住,所有属性和方法都是公用的,因此子类可直接访问这些方法。子类还可添加超类中没有的新属性和方法,也可以覆盖超类的属性和方法。继承的方式和其他功能一样,ECMAScript 实现继承的方式不止一种。这是因为 JavaScript 中的继承机制并不是明确规定的,而是通过模仿实现的。这意味着所有的继承细节并非完全由解释程序处理。作为开发者,你有权决定最适用的继承方式。下面为您介绍几种具体的继承方式。对象冒充构想原始的 ECMAScript 时,根本没打算设计对象冒充(object masquerading)。它是在开发者开始理解函数的工作方式,尤其是如何在函数环境中使用 this 关键字后才发展出来。其原理如下:构造函数使用 this 关键字给所有属性和方法赋值(即采用类声明的构造函数方式)。因为构造函数只是一个函数,所以可使 ClassA 构造函数成为 ClassB 的方法,然后调用它。ClassB 就会收到 ClassA 的构造函数中定义的属性和方法。例如,用下面的方式定义 ClassA 和 ClassB:function ClassA(sColor) {this.color = sColor;this.sayColor = function () {alert(this.color);};}function ClassB(sColor) {}还记得吗?关键字 this 引用的是构造函数当前创建的对象。不过在这个方法中,this 指向的所属的对象。这个原理是把 ClassA 作为常规函数来建立继承机制,而不是作为构造函数。如下使用构造函数 ClassB 可以实现继承机制:function ClassB(sColor) {this.newMethod = ClassA;this.newMethod(sColor);delete this.newMethod;}在这段代码中,为 ClassA 赋予了方法 newMethod(请记住,函数名只是指向它的指针)。然后调用该方法,传递给它的是 ClassB 构造函数的参数 sColor。最后一行代码删除了对 ClassA 的引用,这样以后就不能再调用它。所有新属性和新方法都必须在删除了新方法的代码行后定义。否则,可能会覆盖超类的相关属性和方法:function ClassB(sColor, sName) {this.newMethod = ClassA;this.newMethod(sColor);delete this.newMethod;this.name = sName;this.sayName = function () {alert(this.name);};}为证明前面的代码有效,可以运行下面的例子:var objA = new ClassA("blue");var objB = new ClassB("red", "John");objA.sayColor();//输出 "blue"objB.sayColor();//输出 "red"objB.sayName();//输出 "John"对象冒充可以实现多重继承有趣的是,对象冒充可以支持多重继承。也就是说,一个类可以继承多个超类。用 UML 表示的多重继承机制如下图所示:例如,如果存在两个类 ClassX 和 ClassY,ClassZ 想继承这两个类,可以使用下面的代码:function ClassZ() {this.newMethod = ClassX;this.newMethod();delete this.newMethod;this.newMethod = ClassY;this.newMethod();delete this.newMethod;}TIY这里存在一个弊端,如果存在两个类 ClassX 和 ClassY 具有同名的属性或方法,ClassY 具有高优先级。因为它从后面的类继承。除这点小问题之外,用对象冒充实现多重继承机制轻而易举。由于这种继承方法的流行,ECMAScript 的第三版为 Function 对象加入了两个方法,即 call() 和 apply()。call() 方法call() 方法是与经典的对象冒充方法最相似的方法。它的第一个参数用作 this 的对象。其他参数都直接传递给函数自身。例如:function sayColor(sPrefix,sSuffix) {alert(sPrefix + this.color + sSuffix);};var obj = new Object();obj.color = "blue";sayColor.call(obj, "The color is ", "a very nice color indeed.");在这个例子中,函数 sayColor() 在对象外定义,即使它不属于任何对象,也可以引用关键字 this。对象 obj 的 color 属性等于 blue。调用 call() 方法时,第一个参数是 obj,说明应该赋予 sayColor() 函数中的 this 关键字值是 obj。第二个和第三个参数是字符串。它们与 sayColor() 函数中的参数 sPrefix 和 sSuffix 匹配,最后生成的消息 "The color is blue, a very nice color indeed." 将被显示出来。要与继承机制的对象冒充方法一起使用该方法,只需将前三行的赋值、调用和删除代码替换即可:function ClassB(sColor, sName) {//this.newMethod = ClassA;//this.newMethod(color);//delete this.newMethod;ClassA.call(this, sColor);this.name = sName;this.sayName = function () {alert(this.name);};}TIY这里,我们需要让 ClassA 中的关键字 this 等于新创建的 ClassB 对象,因此 this 是第一个参数。第二个参数 sColor 对两个类来说都是唯一的参数。apply() 方法apply() 方法有两个参数,用作 this 的对象和要传递给函数的参数的数组。例如:function sayColor(sPrefix,sSuffix) {alert(sPrefix + this.color + sSuffix);};var obj = new Object();obj.color = "blue";sayColor.apply(obj, new Array("The color is ", "a very nice color indeed."));这个例子与前面的例子相同,只是现在调用的是 apply() 方法。调用 apply() 方法时,第一个参数仍是 obj,说明应该赋予 sayColor() 函数中的 this 关键字值是 obj。第二个参数是由两个字符串构成的数组,与 sayColor() 函数中的参数 sPrefix 和 sSuffix 匹配,最后生成的消息仍是 "The color is blue, a very nice color indeed.",将被显示出来。该方法也用于替换前三行的赋值、调用和删除新方法的代码:function ClassB(sColor, sName) {//this.newMethod = ClassA;//this.newMethod(color);//delete this.newMethod;ClassA.apply(this, new Array(sColor));this.name = sName;this.sayName = function () {alert(this.name);};}同样的,第一个参数仍是 this,第二个参数是只有一个值 color 的数组。可以把 ClassB 的整个 arguments 对象作为第二个参数传递给 apply() 方法:function ClassB(sColor, sName) {//this.newMethod = ClassA;//this.newMethod(color);//delete this.newMethod;ClassA.apply(this, arguments);this.name = sName;this.sayName = function () {alert(this.name);};}TIY当然,只有超类中的参数顺序与子类中的参数顺序完全一致时才可以传递参数对象。如果不是,就必须创建一个单独的数组,按照正确的顺序放置参数。此外,还可使用 call() 方法。原型链(prototype chaining)继承这种形式在 ECMAScript 中原本是用于原型链的。上一章介绍了定义类的原型方式。原型链扩展了这种方式,以一种有趣的方式实现继承机制。在上一章学过,prototype 对象是个模板,要实例化的对象都以这个模板为基础。总而言之,prototype 对象的任何属性和方法都被传递给那个类的所有实例。原型链利用这种功能来实现继承机制。如果用原型方式重定义前面例子中的类,它们将变为下列形式:function ClassA() {}ClassA.prototype.color = "blue";ClassA.prototype.sayColor = function () {alert(this.color);};function ClassB() {}`ClassB.prototype = new ClassA();`原型方式的神奇之处在于突出显示的蓝色代码行。这里,把 ClassB 的 prototype 属性设置成 ClassA 的实例。这很有意思,因为想要 ClassA 的所有属性和方法,但又不想逐个将它们 ClassB 的 prototype 属性。还有比把 ClassA 的实例赋予 prototype 属性更好的方法吗?注意:调用 ClassA 的构造函数,没有给它传递参数。这在原型链中是标准做法。要确保构造函数没有任何参数。与对象冒充相似,子类的所有属性和方法都必须出现在 prototype 属性被赋值后,因为在它之前赋值的所有方法都会被删除。为什么?因为 prototype 属性被替换成了新对象,添加了新方法的原始对象将被销毁。所以,为 ClassB 类添加 name 属性和 sayName() 方法的代码如下:function ClassB() {}ClassB.prototype = new ClassA();ClassB.prototype.name = "";ClassB.prototype.sayName = function () {alert(this.name);};可通过运行下面的例子测试这段代码:var objA = new ClassA();var objB = new ClassB();objA.color = "blue";objB.color = "red";objB.name = "John";objA.sayColor();objB.sayColor();objB.sayName();TIY此外,在原型链中,instanceof 运算符的运行方式也很独特。对 ClassB 的所有实例,instanceof 为 ClassA 和 ClassB 都返回 true。例如:var objB = new ClassB();alert(objB instanceof ClassA);//输出 "true"alert(objB instanceof ClassB);//输出 "true"在 ECMAScript 的弱类型世界中,这是极其有用的工具,不过使用对象冒充时不能使用它。原型链的弊端是不支持多重继承。记住,原型链会用另一类型的对象重写类的 prototype 属性。混合方式这种继承方式使用构造函数定义类,并非使用任何原型。对象冒充的主要问题是必须使用构造函数方式,这不是最好的选择。不过如果使用原型链,就无法使用带参数的构造函数了。开发者如何选择呢?答案很简单,两者都用。在前一章,我们曾经讲解过创建类的最好方式是用构造函数定义属性,用原型定义方法。这种方式同样适用于继承机制,用对象冒充继承构造函数的属性,用原型链继承 prototype 对象的方法。用这两种方式重写前面的例子,代码如下:function ClassA(sColor) {this.color = sColor;}ClassA.prototype.sayColor = function () {alert(this.color);};function ClassB(sColor, sName) {`ClassA.call(this, sColor);`this.name = sName;}`ClassB.prototype = new ClassA();`ClassB.prototype.sayName = function () {alert(this.name);};在此例子中,继承机制由两行突出显示的蓝色代码实现。在第一行突出显示的代码中,在 ClassB 构造函数中,用对象冒充继承 ClassA 类的 sColor 属性。在第二行突出显示的代码中,用原型链继承 ClassA 类的方法。由于这种混合方式使用了原型链,所以 instanceof 运算符仍能正确运行。下面的例子测试了这段代码:var objA = new ClassA("blue");var objB = new ClassB("red", "John");objA.sayColor();//输出 "blue"objB.sayColor();//输出 "red"objB.sayName();//输出 "John"TIY
AJAX - 向服务器发送请求XHR 创建对象
XHR 响应XMLHttpRequest 对象用于和服务器交换数据。
向服务器发送请求
如需将请求发送到服务器,我们使用 XMLAJAX - 向服务器发送请求XHR 创建对象XHR 响应XMLHttpRequest 对象用于和服务器交换数据。向服务器发送请求如需将请求发送到服务器,我们使用 XMLHttpRequest 对象的 open() 和 send() 方法:xmlhttp.open("GET","test1.txt",true);xmlhttp.send();方法描述open(method,url,async)规定请求的类型、URL 以及是否异步处理请求。method:请求的类型;GET 或 POSTurl:文件在服务器上的位置async:true(异步)或 false(同步)send(string)将请求发送到服务器。string:仅用于 POST 请求GET 还是 POST?与 POST 相比,GET 更简单也更快,并且在大部分情况下都能用。然而,在以下情况中,请使用 POST 请求:无法使用缓存文件(更新服务器上的文件或数据库)向服务器发送大量数据(POST 没有数据量限制)发送包含未知字符的用户输入时,POST 比 GET 更稳定也更可靠GET 请求一个简单的 GET 请求:xmlhttp.open("GET","demo_get.asp",true);xmlhttp.send();在上面的例子中,您可能得到的是缓存的结果。为了避免这种情况,请向 URL 添加一个唯一的 ID:xmlhttp.open("GET","demo_get.asp?t=" + `Math.random()`,true);xmlhttp.send();如果您希望通过 GET 方法发送信息,请向 URL 添加信息:xmlhttp.open("GET","demo_get2.asp?fname=Bill&lname=Gates",true);xmlhttp.send();POST 请求一个简单 POST 请求:xmlhttp.open("POST","demo_post.asp",true);xmlhttp.send();如果需要像 HTML 表单那样 POST 数据,请使用 setRequestHeader() 来添加 HTTP 头。然后在 send() 方法中规定您希望发送的数据:xmlhttp.open("POST","ajax_test.asp",true);xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");xmlhttp.send("fname=Bill&lname=Gates");方法描述setRequestHeader(header,value)向请求添加 HTTP 头。header: 规定头的名称value: 规定头的值url - 服务器上的文件open() 方法的 url 参数是服务器上文件的地址:xmlhttp.open("GET","ajax_test.asp",true);该文件可以是任何类型的文件,比如 .txt 和 .xml,或者服务器脚本文件,比如 .asp 和 .php (在传回响应之前,能够在服务器上执行任务)。异步 - True 或 False?AJAX 指的是异步 JavaScript 和 XML(Asynchronous JavaScript and XML)。XMLHttpRequest 对象如果要用于 AJAX 的话,其 open() 方法的 async 参数必须设置为 true:xmlhttp.open("GET","ajax_test.asp",`true`);对于 web 开发人员来说,发送异步请求是一个巨大的进步。很多在服务器执行的任务都相当费时。AJAX 出现之前,这可能会引起应用程序挂起或停止。通过 AJAX,JavaScript 无需等待服务器的响应,而是:在等待服务器响应时执行其他脚本当响应就绪后对响应进行处理Async = true当使用 async=true 时,请规定在响应处于 onreadystatechange 事件中的就绪状态时执行的函数:xmlhttp.onreadystatechange=function(){if (xmlhttp.readyState==4 && xmlhttp.status==200){document.getElementById("myDiv").innerHTML=xmlhttp.responseText;}}xmlhttp.open("GET","test1.txt",true);xmlhttp.send();您将在稍后的章节学习更多有关 onreadystatechange 的内容。Async = false如需使用 async=false,请将 open() 方法中的第三个参数改为 false:xmlhttp.open("GET","test1.txt",false);我们不推荐使用 async=false,但是对于一些小型的请求,也是可以的。请记住,JavaScript 会等到服务器响应就绪才继续执行。如果服务器繁忙或缓慢,应用程序会挂起或停止。注释:当您使用 async=false 时,请不要编写 onreadystatechange 函数 - 把代码放到 send() 语句后面即可:xmlhttp.open("GET","test1.txt",false);xmlhttp.send();document.getElementById("myDiv").innerHTML=xmlhttp.responseText;XHR 创建对象XHR 响应
ECMAScript Function 对象(类)
ECMAScript 的函数实际上是功能完整的对象。
Function 对象(类)
ECMAScript 最令人感兴趣的可能莫过于函数实际上ECMAScript Function 对象(类)ECMAScript 的函数实际上是功能完整的对象。Function 对象(类)ECMAScript 最令人感兴趣的可能莫过于函数实际上是功能完整的对象。Function 类可以表示开发者定义的任何函数。用 Function 类直接创建函数的语法如下:var function_name = new function(_arg1_, _arg2_, ..., _argN_, _function_body_)在上面的形式中,每个 arg 都是一个参数,最后一个参数是函数主体(要执行的代码)。这些参数必须是字符串。记得下面这个函数吗?function sayHi(sName, sMessage) {alert("Hello " + sName + sMessage);}还可以这样定义它:var sayHi = new Function("sName", "sMessage", "alert(\"Hello \" + sName + sMessage);");虽然由于字符串的关系,这种形式写起来有些困难,但有助于理解函数只不过是一种引用类型,它们的行为与用 Function 类明确创建的函数行为是相同的。请看下面这个例子:function doAdd(iNum) {alert(iNum + 20);}function doAdd(iNum) {alert(iNum + 10);}doAdd(10);//输出 "20"如你所知,第二个函数重载了第一个函数,使 doAdd(10) 输出了 "20",而不是 "30"。如果以下面的形式重写该代码块,这个概念就清楚了:var doAdd = new Function("iNum", "alert(iNum + 20)");var doAdd = new Function("iNum", "alert(iNum + 10)");doAdd(10);请观察这段代码,很显然,doAdd 的值被改成了指向不同对象的指针。函数名只是指向函数对象的引用值,行为就像其他对象一样。甚至可以使两个变量指向同一个函数:var doAdd = new Function("iNum", "alert(iNum + 10)");var alsodoAdd = doAdd;doAdd(10);//输出 "20"alsodoAdd(10);//输出 "20"在这里,变量 doAdd 被定义为函数,然后 alsodoAdd 被声明为指向同一个函数的指针。用这两个变量都可以执行该函数的代码,并输出相同的结果 - "20"。因此,如果函数名只是指向函数的变量,那么可以把函数作为参数传递给另一个函数吗?回答是肯定的!function callAnotherFunc(fnFunction, vArgument) {fnFunction(vArgument);}var doAdd = new Function("iNum", "alert(iNum + 10)");callAnotherFunc(doAdd, 10);//输出 "20"在上面的例子中,callAnotherFunc() 有两个参数 - 要调用的函数和传递给该函数的参数。这段代码把 doAdd() 传递给 callAnotherFunc() 函数,参数是 10,输出 "20"。注意:尽管可以使用 Function 构造函数创建函数,但最好不要使用它,因为用它定义函数比用传统方式要慢得多。不过,所有函数都应看作 Function 类的实例。Function 对象的 length 属性如前所述,函数属于引用类型,所以它们也有属性和方法。ECMAScript 定义的属性 length 声明了函数期望的参数个数。例如:function doAdd(iNum) {alert(iNum + 10);}function sayHi() {alert("Hi");}alert(doAdd.length);//输出 "1"alert(sayHi.length);//输出 "0"函数 doAdd() 定义了一个参数,因此它的 length 是 1;sayHi() 没有定义参数,所以 length 是 0。记住,无论定义了几个参数,ECMAScript 可以接受任意多个参数(最多 25 个),这一点在《函数概述》这一章中讲解过。属性 length 只是为查看默认情况下预期的参数个数提供了一种简便方式。Function 对象的方法Function 对象也有与所有对象共享的 valueOf() 方法和 toString() 方法。这两个方法返回的都是函数的源代码,在调试时尤其有用。例如:function doAdd(iNum) {alert(iNum + 10);}document.write(doAdd.toString());上面这段代码输出了 doAdd() 函数的文本。!
