学习ECMAScript(2017年7月27日)

时间:2022-05-06 14:08:35

一元运算符

Delete

delete 运算符删除对开发者新增的属性或方法的引用,不能删除系统的属性或方法引用。

Void

void 运算符对任何值返回 undefined。该运算符通常用于避免输出不应该输出的值。

例如,从 HTML 的 <a> 元素调用 JavaScript 函数时。要正确做到这一点,函数不能返回有效值,否则浏览器将清空页面,只显示函数的结果。例如:

<a href="javascript:window.open('about:blank')">

Click me</a>

如果把这行代码放入 HTML 页面,点击其中的链接,即可看到屏幕上显示 "[object]"。这是因为 window.open() 方法返回了新打开的窗口的引用。然后该对象将被转换成要显示的字符串。要避免这种效果,可以用 void 运算符调用 window.open() 函数:<a
href="javascript:void(window.open('about:blank'))">Click me</a>

这使 window.open() 调用返回 undefined,它不是有效值,不会显示在浏览器窗口中。提示:请记住,没有返回值的函数真正返回的都是 undefined。

ECMAScript变量

ECMAScript 中,变量可以存在两种类型的值,即原始值和引用值。

原始值:存储在栈(stack)中的简单数据段,也就是说,它们的值直接存储在变量访问的位置。

引用值:存储在堆(heap)中的对象,也就是说,存储在变量处的值是一个指针(point),指向存储对象的内存处。

为变量赋值时,ECMAScript 的解释程序必须判断该值是原始类型,还是引用类型。要实现这一点,解释程序则需尝试判断该值是否为 ECMAScript 的原始类型之一,这里引出下一节内容

ECMAScript原始类型

Undefined类型

Undefined 类型只有一个值,即 undefined。当声明的变量未初始化时,该变量的默认值是 undefinedvar  oTemp;

alert(oTemp == undefined); 这段代码将显示 "true",说明这两个值确实相等

undefined 并不同于未定义的值。但是,typeof 运算符并不真正区分这两种值。考虑下面的代码:

var oTemp;

alert(typeof oTemp);  //输出 "undefined"

alert(typeof oTemp2);  //输出 "undefined"

前面的代码对两个变量输出的都是 "undefined",即使只有变量 oTemp2 从未被声明过。如果对 oTemp2 使用除 typeof 之外的其他运算符的话,会引起错误,因为其他运算符只能用于已声明的变量上

例如,下面的代码将引发错误:

var oTemp;

alert(oTemp2 == undefined);

当函数无明确返回值时,返回的也是值 "undefined",如下所示:

function testFunc() {}

alert(testFunc() == undefined);  //输出 "true"

Null类型

另一种只有一个值的类型是 Null,它只有一个专用值 null,即它的字面量。值 undefined 实际上是从值 null 派生来的,因此 ECMAScript 把它们定义为相等的。

alert(null == undefined);  //输出 "true"

尽管这两个值相等,但它们的含义不同。undefined 是声明了变量但未对其初始化时赋予该变量的值,null 则用于表示尚未存在的对象(在讨论 typeof 运算符时,简单地介绍过这一点)。如果函数或方法要返回的是对象,那么找不到该对象时,返回的通常是 null。

Boolean类型

它有两个值 true 和 false

Number 类型

这种类型既可以表示 32 位的整数,还可以表示 64 位的浮点数。要定义浮点值,必须包括小数点和小数点后的一位数字(例如,用 1.0 而不是 1)。这被看作浮点数字面量。

String类型

String 类型的独特之处在于,它是唯一没有固定大小的原始类型。可以用字符串存储 0 或更多的 Unicode 字符,有 16 位整数表示。

typeof运算符

判断对象是原始值,还是引用值。对变量或值调用 typeof 运算符将返回下列值之一:

· undefined - 如果变量是 Undefined 类型的

· boolean - 如果变量是 Boolean 类型的

· number - 如果变量是 Number 类型的

· string - 如果变量是 String 类型的

· object - 如果变量是一种引用类型或 Null 类型的

ECMAScript类型转换

转为字符串

1、3 种主要的原始类型 Boolean 值、数字和字符串都有 toString() 方法,可以把它们的值转换成字符串。

2、也许会问,“字符串还有 toString() 方法吗,这不是多余吗?”是的,的确如此,不过 ECMAScript 定义所有对象都有 toString() 方法,无论它是伪对象,还是真对象。因为 String 类型属于伪对象,所以它一定有 toString() 方法。

3、Boolean 类型的 toString() 方法只是输出 "true" 或 "false",结果由变量的值决定

4、Number 类型的 toString() 方法比较特殊,它有两种模式,即默认模式基模式。采用默认模式,toString() 方法只是用相应的字符串输出数字值(无论是整数、浮点数还是科学计数法),在默认模式中,无论最初采用什么表示法声明数字,Number 类型的 toString() 方法返回的都是数字的十进制表示。因此,以八进制或十六进制字面量形式声明的数字输出的都是十进制形式的。采用 Number 类型的 toString() 方法的基模式,可以用不同的基输出数字,例如二进制的基是 2,八进制的基是 8,十六进制的基是 16。

基只是要转换成的基数的另一种加法而已,它是 toString() 方法的参数:

var iNum = 10;

alert(iNum.toString(2)); //输出 "1010"

alert(iNum.toString(8)); //输出 "12"

alert(iNum.toString(16)); //输出 "A"

转为数字

ECMAScript 提供了两种把非数字的原始值转换成数字的方法,即 parseInt() 和 parseFloat()。前者把值转换成整数,后者把值转换成浮点数。只有对 String 类型调用这些方法,它们才能正确运行;对其他类型返回的都是 NaN。

在判断字符串是否是数字值前,parseInt() 和 parseFloat() 都会仔细分析该字符串。

转为数字:parseInt()

parseInt() 方法首先查看位置 0 处的字符,判断它是否是个有效数字;如果不是,该方法将返回 NaN,不再继续执行其他操作。但如果该字符是有效数字,该方法将查看位置 1 处的字符,进行同样的测试。这一过程将持续到发现非有效数字的字符为止,此时 parseInt() 将把该字符之前的字符串转换成数字。

例如,如果要把字符串 "12345red" 转换成整数,那么 parseInt() 将返回 12345,因为当它检查到字符 r 时,就会停止检测过程。

字符串中包含的数字字面量会被正确转换为数字,比如 "0xA" 会被正确转换为数字 10。不过,字符串 "22.5" 将被转换成 22,因为对于整数来说,小数点是无效字符。

parseInt() 方法还有基模式,可以把二进制、八进制、十六进制或其他任何进制的字符串转换成整数。基是由 parseInt() 方法的第二个参数指定的,所以要解析十六进制的值,需如下调用 parseInt() 方法:

var iNum1 = parseInt("AF", 16); //返回 175

当然,对二进制、八进制甚至十进制(默认模式),都可以这样调用 parseInt() 方法:

var iNum1 = parseInt("10", 2); //返回 2

var iNum2 = parseInt("10", 8); //返回 8

var iNum3 = parseInt("10", 10); //返回 10

转为数字:parseFloat()

parseFloat() 方法与 parseInt() 方法的处理方式相似,从位置 0 开始查看每个字符,直到找到第一个非有效的字符为止,然后把该字符之前的字符串转换成整数。

不过,对于这个方法来说,第一个出现的小数点是有效字符。如果有两个小数点,第二个小数点将被看作无效的。parseFloat() 会把这个小数点之前的字符转换成数字。这意味着字符串 "11.22.33" 将被解析成 11.22。

使用 parseFloat() 方法的另一不同之处在于,字符串必须以十进制形式表示浮点数,而不是用八进制或十六进制。该方法会忽略前导 0,所以八进制数 0102 将被解析为 102。对于十六进制数 0xA,该方法将返回 NaN,因为在浮点数中,x 不是有效字符。

此外,parseFloat() 方法也没有基模式

强制类型转换

ECMAScript 中可用的 3 种强制类型转换如下:

· Boolean(value) - 把给定的值转换成 Boolean 型;

· Number(value) - 把给定的值转换成数字(可以是整数或浮点数);

· String(value) - 把给定的值转换成字符串;

强制类型转换:Boolean()函数

当要转换的值是至少有一个字符的字符串、非 0 数字或对象时,Boolean() 函数将返回 true。如果该值是空字符串、数字 0、undefined 或 null,它将返回 false。

可以用下面的代码测试 Boolean 型的强制类型转换:

var b1 = Boolean(""); //false - 空字符串

var b2 = Boolean("hello"); //true - 非空字符串

var b1 = Boolean(50); //true - 非零数字

var b1 = Boolean(null); //false - null

var b1 = Boolean(0); //false - 零

var b1 = Boolean(new object()); //true - 对象

强制类型转换:Number()函数

Number() 函数的强制类型转换与 parseInt() 和 parseFloat() 方法的处理方式相似,只是它转换的是整个值,而不是部分值。

还记得吗,parseInt() 和 parseFloat() 方法只转换第一个无效字符之前的字符串,因此 "1.2.3" 将分别被转换为 "1" 和 "1.2"。

Number() 进行强制类型转换,"1.2.3" 将返回 NaN,因为整个字符串值不能转换成数字。如果字符串值能被完整地转换,Number() 将判断是调用 parseInt() 方法还是 parseFloat() 方法。

下表说明了对不同的值调用 Number() 方法会发生的情况:

用法

结果

Number(false)

0

Number(true)

1

Number(undefined)

NaN

Number(null)

0

Number("1.2")

1.2

Number("12")

12

Number("1.2.3")

NaN

Number(new object())

NaN

Number(50)

50

强制类型转换:String()函数

String() 是最简单的,因为它可把任何值转换成字符串。

要执行这种强制类型转换,只需要调用作为参数传递进来的值的 toString() 方法,即把 12 转换成 "12",把 true 转换成 "true",把 false 转换成 "false",以此类推。

强制转换成字符串和调用 toString() 方法的唯一不同之处在于,对 null 和 undefined 值强制类型转换可以生成字符串而不引发错误:

var s1 = String(null); //"null"

var oNull = null;

var s2 = oNull.toString(); //会引发错误

ECMAScript引用类型

Object 对象

Object 对象自身用处不大,不过在了解其他类之前,还是应该了解它。因为 ECMAScript 中的 Object 对象与 Java 中的 java.lang.Object 相似,ECMAScript 中的所有对象都由这个对象继承而来,Object 对象中的所有属性和方法都会出现在其他对象中,所以理解了 Object 对象,就可以更好地理解其他对象。

Object 对象具有下列属性:

constructor

对创建对象的函数的引用(指针)。对于 Object 对象,该指针指向原始的 Object() 函数。

Prototype

对该对象的对象原型的引用。对于所有的对象,它默认返回 Object 对象的一个实例。

Object 对象还具有几个方法:

hasOwnProperty(property)

判断对象是否有某个特定的属性。必须用字符串指定该属性。(例如,o.hasOwnProperty("name"))

IsPrototypeOf(object)

判断该对象是否为另一个对象的原型。

PropertyIsEnumerable

判断给定的属性是否可以用 for...in 语句进行枚举。

ToString()

返回对象的原始字符串表示。对于 Object 对象,ECMA-262 没有定义这个值,所以不同的 ECMAScript 实现具有不同的值。

ValueOf()

返回最适合该对象的原始值。对于许多对象,该方法返回的值都与 ToString() 的返回值相同。

instanceof运算符

在使用 typeof 运算符时采用引用类型存储值会出现一个问题,无论引用的是什么类型的对象,它都返回 "object"。ECMAScript 引入了另一个 Java 运算符 instanceof 来解决这个问题。

instanceof 运算符与 typeof 运算符相似,用于识别正在处理的对象的类型。与 typeof 方法不同的是,instanceof 方法要求开发者明确地确认对象为某特定类型。例如:

var oStringObject = new String("hello world");

alert(oStringObject instanceof String); //输出 "true"

这段代码问的是“变量 oStringObject 是否为 String 对象的实例?”oStringObject 的确是 String 对象的实例,因此结果是 "true"。尽管不像 typeof 方法那样灵活,但是在 typeof 方法返回 "object" 的情况下,instanceof 方法还是很有用的。

ECMAScript面向对象技术

声明和实例化

Java

JavaScript

new关键字

new关键字

对象引用

Java

JavaScript

一致

对象销毁

Java

JavaScript

垃圾回收程序

无用存储单元收集程序:意味着不必专门销毁对象来释放内存。当再没有对对象的引用时,称该对象被废除了。运行无用存储单元收集程序时,所有废除的对象都被销毁。每当函数执行完它的代码,无用存储单元收集程序都会运行,释放所有的局部变量,还有在一些其他不可预知的情况下,无用存储单元收集程序也会运行。把对象的所有引用都设置为 null,可以强制性地废除对象。

对象绑定

所谓绑定(binding),即把对象的接口与对象实例结合在一起的方法。

早绑定(early binding)是指在实例化对象之前定义它的属性和方法,这样编译器或解释程序就能够提前转换机器代码。在 Java 和 Visual Basic 这样的语言中,有了早绑定,就可以在开发环境中使用 IntelliSense(即给开发者提供对象中属性和方法列表的功能)。ECMAScript 不是强类型语言,所以不支持早绑定。

另一方面,晚绑定(late binding)指的是编译器或解释程序在运行前,不知道对象的类型。使用晚绑定,无需检查对象的类型,只需检查对象是否支持属性和方法即可。ECMAScript 中的所有变量都采用晚绑定方法。这样就允许执行大量的对象操作,而无任何惩罚。

 

Java

JavaScript

早绑定

晚绑定

对象类型

ECMAScript 中,所有对象并非同等创建的。一般来说,可以创建并使用的对象有三种:本地对象、内置对象和宿主对象。

本地对象:独立于宿主环境的 ECMAScript 实现提供的对象。简单来说,本地对象就是 ECMA-262 定义的类(引用类型)。它们包括:Object、Function、Array、String、Boolean、Number、Date、RegExp、Error、EvalError、RangeError、ReferenceError、SyntaxError、TypeError、URIError。

内置对象:由 ECMAScript 实现提供的、独立于宿主环境的所有对象,在 ECMAScript 程序开始执行时出现。这意味着开发者不必明确实例化内置对象,它已被实例化了。ECMA-262 只定义了两个内置对象,即 Global 和 Math (它们也是本地对象,根据定义,每个内置对象都是本地对象)。

宿主对象:所有非本地对象都是宿主对象(host object),即由 ECMAScript 实现的宿主环境提供的对象。所有 BOM 和 DOM 对象都是宿主对象。

作用域

Java

JavaScript

public

ECMAScript 中只存在一种作用域 - 公用作用域。ECMAScript 中的所有对象的所有属性和方法都是公用的。(通俗来讲就是public)

private

protected

默认

静态作用域

Java

JavaScript

static修饰符

严格来说,ECMAScript 并没有静态作用域。不过,它可以给构造函数提供属性和方法。还记得吗,构造函数只是函数。函数是对象,对象可以有属性和方法。通俗来说,虽然JS没有静态作用域的,但可是实现静态作用域的效果。

this关键字

Java

JavaScript

this:代表当前对象

this: 总是指向调用该方法的对象,注意引用对象的属性时,必须使用 this 关键字。如果不用对象或 this 关键字引用变量,ECMAScript 就会把它看作局部变量或全局变量。然后该函数将查找名为 color 的局部或全局变量,但是不会找到。结果如何呢?该函数将在警告中显示 null。

修改对象

Java

JavaScript

当Class文件创建完毕之后,只能对属性值进行修改,无法新增属性、方法。

通过使用 ECMAScript,不仅可以创建对象,还可以修改已有对象的行为。

prototype 属性不仅可以定义构造函数的属性和方法,还可以为本地对象添加属性和方法。

通俗来讲就是,即便对象/类创建完毕,我也可以新增属性,新能方法,修改现有属性类型,修改现有方法对象,设置还可以修改Object对象的属性和方法。

继承

Java

JavaScript

extends关键字

ECMAScript实现继承的方式不止一种。这是因为 JS中的继承机制并不是明确规定的,而是通过模仿实现的。这意味着所有的继承细节并非完全由解释程序处理。

通俗来讲,就是JS没有继承这一说法而是通过模仿来实现,这就意味着所有的细节都要开发人员自己来处理

继承方式一:对象冒充

 

原理如下:构造函数使用 this 关键字给所有属性和方法赋值(即采用类声明的构造函数方式)。因为构造函数只是一个函数,所以可使 ClassA 构造函数成为 ClassB 中的一个方法,然后调用ClassA的构造函数。ClassB 就会收到 ClassA 的构造函数中定义的属性和方法。例如,用下面的方式定义 ClassA 和 ClassB:

function ClassA(sColor) {

    this.color = sColor;

    this.sayColor = function () {

        alert(this.color);

    };

}

所有新属性和新方法都必须在删除了新方法的代码行后定义。否则,可能会覆盖超类的相关属性和方法:

function ClassB(sColor, sName) {

    this.newMethod = ClassA;

    this.newMethod(sColor);

    delete this.newMethod;

    this.name = sName;

    this.sayName = function () {

        alert(this.name);

    };

}

解释:当执行到this.newMethod(sColor);行代码时,其实执行的是ClassA构造方法,但ClassA构造方法中的this总是指向调用该方法的对象(就是这里的ClassB),这样ClassB就获取到了ClassA所有的属性、方法,然后delete删除引用后,sayName就是一个新增的方法,与ClassA中的sayName没有任何关系。

对象冒充可以支持多重继承。

缺点:在代码书写中,后面对象会覆盖前面对象的同名属性和同名方法,例如:在ClassB中首先持有ClassX,然后持有ClassY,如果存在两个类 ClassX 和 ClassY 具有同名的属性或方法,ClassY 会覆盖掉ClassX中同名的属性或方法。因为它从后面的类继承。

继承方式二:call()方法

原理:ECMAScript 的第三版为 Function 对象加入了两个方法,即 call() 和 apply()。

举例:

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." 将被显示出来。

Call()方法就是调用对象的构造方法,第一个参数就是子类对象,其他参数就是父类构造方法中的参数。

继承方式三: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);

    };

}

有超类中的参数顺序与子类中的参数顺序完全一致时才可以传递参数对象。如果不是,就必须创建一个单独的数组,按照正确的顺序放置参数。此外,还可使用 call() 方法。

继承方式四:原型链

继承这种形式在 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 的所有属性和方法,还有比把 ClassA 的实例赋予 prototype 属性更好的方法吗?

注意:调用 ClassA 的构造函数,没有给它传递参数。这在原型链中是标准做法。要确保构造函数没有任何参数。

与对象冒充相似,子类的所有属性和方法都必须出现在 prototype 属性被赋值后,因为在它之前赋值的所有方法都会被删除。为什么?因为 prototype 属性被替换成了新对象,添加了新方法的原始对象将被销毁。所以,为 ClassB 类添加 name 属性和 sayName() 方法的代码如下:

function ClassB() {

}

ClassB.prototype = new ClassA();

ClassB.prototype.name = "";

ClassB.prototype.sayName = function () {

    alert(this.name);

};

通俗来讲:通过prototype属性更新了模板,这就意味着所有通过老模板创建的对象全部销户,重新依据新模板来创建对象,所以,必须在调用prototype方法之后再新增属性、方法。

继承方式五:混合方式

通俗来讲就是:call/apply + 原型链

示例:

在上面,我们曾经讲解过创建类的最好方式是用构造函数定义属性,用原型定义方法。这种方式同样适用于继承机制,用对象冒充继承构造函数的属性,用原型链继承 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 运算符仍能正确运行。(这里,我有问题一直没想明白,那么就是ClassB的实例调用sayName时,怎么不提示underfind呢?你都ClassB.prototype = new ClassA();了,后来我想明白了,继承,是继承属性和方法,继承不能把子类的构造函数给构造没了,也就说,ClassB.prototype = new ClassA();对ClassB的构造函数没有影响)