使用构造函数定义类型
构造函数是一个实例化特定类型的 Object 的函数。你使用 new 关键字调用构造函数。以下是包含内置 JavaScript 对象和自定义对象的构造函数的几个示例。
构造函数示例
//创建一个通用对象
var myObject = new Object();
//创建一个Date对象
var myBirthday = new Date(1961, 5, 10);
//创建用户定义的对象
var myCar = new Car();
构造函数包含 this 关键字,它是对新创建的空对象的引用。它通过创建属性并为其赋初始值来初始化新对象。构造函数将返回对所构造的对象的引用。
编写构造函数
可以将 new 运算符和预定义的构造函数(如 Object()
、Date()
和 Function()
)一起使用来创建对象。还可以创建定义一组属性和方法的自定义构造函数。下面是自定义构造函数的一个示例。
function Circle (xPoint, yPoint, radius) {
this.x = xPoint; // 圆的中心的x分量
this.y = yPoint; // 圆的中心的y分量.
this.r = radius; // 圆的半径.
}
当你调用 Circle 构造函数时,你需要提供圆的圆心和半径的值。最后以包含这三个属性的 Circle 对象结束。以下是实例化 Circle 对象的方式。
var aCircle = new Circle(5, 11, 99);
使用自定义构造函数创建的所有对象的类型为 object
。JavaScript 中仅包括六种类型:object
、function
、string
、number
、boolean
和 undefined
。
原型和原型继承
在 JavaScript 中,prototype
是函数的一个属性,同时也是由构造函数创建的对象的一个属性。函数的原型为对象。它主要在函数用作构造函数时使用。
function Vehicle(wheels, engine) {
this.wheels = wheels;
this.engine = engine;
}
在上面的示例中,Vehicle
函数的原型是使用 Vehicle
构造函数实例化的任何对象的原型。
使用原型添加属性和方法
可以使用 prototype 属性向对象添加属性和方法,甚至于已创建的对象也是如此:
var testVehicle = new Vehicle(2, false);
Vehicle.prototype.color = "red";
var testColor = testVehicle.color;
testColor 的值为“red”。
你甚至可以向预定义的对象添加属性和方法。例如,你可以在 String 原型对象上定义一个 Trim 方法,脚本中的所有字符串都将继承该方法。
String.prototype.trim = function()
{
//用空字符串替换前导和末尾空格
return this.replace(/(^\s*)|(\s*$)/g, "");
}
var s = " leading and trailing spaces ";
//显示" leading and trailing spaces(35)"
window.alert(s + " (" + s.length + ")");
//删除前导和末尾空格
s = s.trim();
//显示"leading and trailing spaces (27)"
window.alert(s + " (" + s.length + ")");
可使用原型通过 Object.create
从一个对象派生另一个对象
prototype
对象可用于从一个对象派生另一个对象。例如,你可以使用Object.create
函数派生使用我们之前定义的 Vehicle
对象的原型(以及所需的任何新属性)的新对象 Bicycle
。
var Bicycle = Object.create(Object.getPrototypeOf(Vehicle), {
"pedals" :{value: true}
});
Bicycle 对象具有属性 wheels
、engine
、color
和 pedals
,并且其原型为 Vehicle.prototype
。JavaScript 引擎会查找 Bicycle
的 pedals
属性,并查看原型链以便查找 Vehicle
的 wheels
、engine
和 color
。
更改对象的原型
在 Internet Explorer 11 中,可以通过 __proto 属性用新原型替换对象或函数的内部原型。使用此属性时,将继承新原型的属性和方法以及其原型链中的其他属性和方法。
以下示例演示如何更改对象的原型。此示例演示当更改对象原型时,对象的继承属性将如何更改。
function Friend() {
this.demeanor = "happy";
}
function Foe() {
this.demeanor = "suspicious";
}
var friend = new Friend();
var foe = new Foe();
var player = new Object();
player.__proto__ = foe;
friend.ally = "Tom";
if (console && console.log) {
console.log(player.demeanor === "happy" ); // Returns false
console.log(player.demeanor === "suspicious"); // Returns true
console.log(player.ally === "Tom"); // Returns false
// Turn the foe to a friend.
player.__proto__ = friend;
console.log(player.demeanor === "happy"); // Returns true
console.log(player.demeanor === "suspicious"); // Returns false
console.log(player.ally === "Tom"); // Returns true
}
数据属性和访问器属性
本节包括您可能需要的有关数据属性和访问器属性的所有信息。
数据属性
数据属性 是可获取和设置值的属性。数据属性将 value
和 writable
属性包含在其描述符中。
下表列出了数据属性描述符的特性。
数据描述符特性 | 说明 | 默认 |
---|---|---|
Value | 属性的当前值。 | undefined |
writable | true 或 false。如果 writable 设置为 true,则可以修改属性值。 | False |
enumerable | true 或 false。如果 enumerable 设置为 true,则可以由 for…in 语句枚举属性。 | False |
configurable | true 或 false。如果 configurable 设置为 true,则可以更改属性的特性且可以删除属性。 | False |
如果描述符没有 value
、writable
、get
或 set
特性且指定的属性名不存在,则会添加数据属性。
在 configurable
特性为 false
且 writable
为 true
时,可以更改 value
和 writable
特性。
在未使用 defineProperty
的情况下添加的数据属性
如果您在未使用 Object.defineProperty、Object.defineProperties 或 Object.create 函数的情况下添加数据属性,则 writable、enumerable 和 configurable 特性都将设置为 true。在添加属性后,可以使用 Object.defineProperty 函数修改属性。
可以使用以下方式来添加数据属性:
赋值运算符 (=),如下所示:obj.color = "white";
对象文本,如下所示:obj = { color: "white", height: 5 };
构造函数,如使用构造函数定义类型中所述
访问器属性
只要设置或检索属性值,访问器属性 就会调用用户提供的函数。访问器属性的描述符包含 get 特性和/或 set 属性。
下表列出了访问器属性描述符的特性。
访问器描述符特性 | 说明 | 默认 |
---|---|---|
get | 返回属性值的函数。此函数没有参数。 | undefined |
set | 设置属性值的函数。它具有一个包含要分配的值的参数。 | undefined |
enumerable | true 或 false。如果 enumerable 设置为 true,则可以由 for…in 语句枚举属性。 | False |
configurable | true 或 false。如果 configurable 设置为 true,则可以更改属性的特性且可以删除属性。 | False |
在未定义 get 访问器时,如果尝试访问属性值,则将返回 undefined 值。在未定义 set 访问器时,如果尝试向访问器属性赋值,则什么也不会发生。
属性修改
如果对象已包含一个带指定名称的属性,则会修改该属性的特性。在修改属性时,描述符中未指定的特性保持不变。
如果现有属性的 configurable
特性为 false
,则唯一允许的修改是将 writable
特性从 true
更改为 false
。
可以将数据属性更改为访问器属性,反之亦然。如果这样做,描述符中未指定的 configurable
和 enumerable
特性将保留在属性中。描述符中未指定的其他特性将设置为其默认值。
可以通过多次调用 Object.defineProperty
函数以增量方式定义可配置的访问器属性。例如,一次 Object.defineProperty
调用可能仅定义一个 get 访问器。稍后调用同一属性名称可能会定义一个 set
访问器。之后,该属性将同时具有 get
访问器和 set
访问器。
若要获取适用于现有属性的描述符对象,可以使用 Object.getOwnPropertyDescriptor
函数 (JavaScript)。
可以使用 Object.seal
函数 (JavaScript)和 Object.freeze
函数 (JavaScript)来阻止修改属性的特性。
递归 (JavaScript)
递归是一项非常重要的编程技巧,函数通过它调用其本身。
递归示例
示例之一就是阶乘的计算。 数字 n 的阶乘通过乘以 1 * 2 * 3 *… n 进行计算。 下面的示例演示如何使用计算结果的 while 循环反复计算阶乘。
function factorial(num)
{
// If the number is less than 0, reject it.
if (num < 0) {
return -1;
}
// If the number is 0, its factorial is 1.
else if (num == 0) {
return 1;
}
var tmp = num;
while (num-- > 2) {
tmp *= num;
}
return tmp;
}
var result = factorial(8);
document.write(result);
// Output: 40320
可以使示例递归变得非常简单。 只需再次调用 factorial 并传入下一个最小值,而不是使用 while 循环计算此值。 当此值为 1 时,递归将停止。
function factorial(num)
{
// If the number is less than 0, reject it.
if (num < 0) {
return -1;
}
// If the number is 0, its factorial is 1.
else if (num == 0) {
return 1;
}
// Otherwise, call this recursive procedure again.
else {
return (num * factorial(num - 1));
}
}
var result = factorial(8);
document.write(result);
// Output: 40320
变量作用域 (JavaScript)
JavaScript 有两个范围:全局和局部。 在函数定义之外声明的变量是全局变量,它的值可在整个程序中访问和修改。 在函数定义内声明的变量是局部变量。 每当执行函数时,都会创建和销毁该变量,且无法通过函数之外的任何代码访问该变量。 JavaScript 不支持块范围(通过一组大括号 {…} 定义新范围),但块范围变量的特殊情况除外。
JavaScript 中的范围
虽然局部变量可具有与全局变量相同的名称,但它是完全独立的;更改一个变量的值不会影响另一个变量。 在声明局部变量的函数中,仅局部版本具有意义。
// Global definition of aCentaur.
var aCentaur = "a horse with rider,";
// A local aCentaur variable is declared in this function.
function antiquities(){
var aCentaur = "A centaur is probably a mounted Scythian warrior";
}
antiquities();
aCentaur += " as seen from a distance by a naive innocent.";
document.write(aCentaur);
// Output: "a horse with rider, as seen from a distance by a naive innocent."
在 JavaScript 中,变量就像它们在所在范围的开始被声明一样来计算。 有时,这会导致意外行为,如此处所示。
var aNumber = 100;
tweak();
function tweak(){
// This prints "undefined", because aNumber is also defined locally below.
document.write(aNumber);
if (false)
{
var aNumber = 123;
}
}
当 JavaScript 执行一个函数时,它首先会查找所有变量声明,例如 var someVariable;。 它使用初始值 undefined 创建变量。 如果使用一个值声明变量(例如 var someVariable = “something”;),则该变量的初始值仍为 undefined,并且仅当执行包含声明的行时才采用已声明的值。
JavaScript 会在执行任何代码之前处理所有变量声明,无论是在条件块中声明还是在其他构造中声明。 JavaScript 一旦找到所有变量,就会执行函数中的代码。 如果在函数内部隐式声明变量(即,该变量出现在赋值表达式的左侧但尚未使用 var 进行声明),则它将创建为全局变量。
在 JavaScript 中,内部(嵌套)函数将存储对局部变量的引用(即使在函数返回之后),这些局部变量存在于与函数本身相同的范围中。 这一组引用称为闭包。 在以下示例中,对内部函数的第二次调用所输出的消息与第一次调用相同(“Hello Bill”),因为外部函数的输入参数 name 是存储在内部函数闭包中的局部变量。
function send(name) {
// Local variable 'name' is stored in the closure
// for the inner function.
return function () {
sendHi(name);
}
}
function sendHi(msg) {
console.log('Hello ' + msg);
}
var func = send('Bill');
func();
// Output:
// Hello Bill
sendHi('Pete');
// Output:
// Hello Pete
func();
// Output:
// Hello Bill
块范围变量
Internet Explorer 11 引入了对 let 和 const 这两个块范围变量的支持。 对于这些变量,大括号 {…} 定义新范围。 将其中一个变量设置为特定值时,该值仅适用于其设置所在的范围。
以下示例说明如何使用 let 和块范围。
let x = 10;
var y = 10;
{
let x = 5;
var y = 5;
{
let x = 2;
var y = 2;
document.write("x: " + x + "<br/>");
document.write("y: " + y + "<br/>");
// Output:
// x: 2
// y: 2
}
document.write("x: " + x + "<br/>");
document.write("y: " + y + "<br/>");
// Output:
// x: 5
// y: 2
}
document.write("x: " + x + "<br/>");
document.write("y: " + y + "<br/>");
// Output:
// x: 10
// y: 2
复制、传递和比较数据 (JavaScript)
在 JavaScript 中,数据的处理方式取决于其数据类型。
按值与按引用
数字和布尔值(true 和 false)按值进行复制、传递和比较。 按值进行复制或传递时,将在计算机内存中分配一个空间,然后将原始项的值复制到该空间中。 如果随后更改原始项,也不影响副本(反之亦然),因为两者是各自不同的实体。
对象、数组和函数按引用进行复制、传递和比较。 按引用进行复制或传递时,实质上创建原始项的指针,并像副本一样使用该指针。 如果随后更改原始项,则同时更改原始项和副本(反之亦然)。 实际上只有一个实体;“副本”实际上不是副本,而只是对数据的另一个引用。
按引用进行比较时,两个变量必须恰好引用同一个实体,才能成功进行比较。 例如,两个不同 Array 对象比较之后的结果始终为不等,即使二者所含的元素相同也是如此。 若要使比较成功,其中一个变量必须是对另一个变量的引用。 若要检查两个数组是否包含相同的元素,请比较 toString() 方法的结果。
最后,字符串按引用进行复制和传递,但按值进行比较。 注意,如果有两个 String 对象(用 new String(“something”) 创建),则按引用进行比较,但是,如果其中一个值为或两个值均为字符串值,则按值进行比较。
由于 ASCII 和 ANSI 字符集的构造方式,因此在序列顺序中大写字母排在小写字母之前。例如,“Zoo”相比之下小于“aardvark”。如果要执行不区分大小写的匹配,则可以对两个字符串调用
toUpperCase()
或toLowerCase()
。
将参数传递给函数
按值将参数传递给某个函数时,将会创建该参数的单独副本(一个仅存在于该函数内部的副本)。 即使按引用传递对象和数组,如果在该函数中用一个新值直接覆盖它们,则在该函数之外也不会反映新值。 只有对对象属性或数组元素的更改才会在函数外可见。
例如(使用 Internet Explorer 对象模型):
// This clobbers (over-writes) its parameter, so the change
// is not reflected in the calling code.
function Clobber(param)
{
// clobber the parameter; this will not be seen in
// the calling code
param = new Object();
param.message = "This will not work";
}
// This modifies a property of the parameter, which
// can be seen in the calling code.
function Update(param)
{
// Modify the property of the object; this will be seen
// in the calling code.
param.message = "I was changed";
}
// Create an object, and give it a property.
var obj = new Object();
obj.message = "This is the original";
// Call Clobber, and print obj.message. Note that it hasn't changed.
Clobber(obj);
window.alert(obj.message); // Still displays "This is the original".
// Call Update, and print obj.message. Note that is has changed.
Update(obj);
window.alert(obj.message); // Displays "I was changed".
测试数据
执行按值测试时,将比较两个不同的项,以查看其是否彼此相等。 通常,这种比较是逐字节进行的。 按引用测试时,将检查两项是否为指向同一个原始项的指针。 如果是,则其比较结果为相等;否则,即使其值完全相同(逐字节),其比较结果仍为不相等。
按引用复制和传递字符串可节省内存;但是,由于字符串一经创建即无法更改,因此可按值比较字符串。 这样可测试两个字符串的内容是否相同,即使其中一个是完全独立于另一个生成的也是如此。
使用数组 (JavaScript)
JavaScript 中的数组是稀疏的。 即,如果有一个包含三个编号分别为 0、1 和 2 的元素的数组,则可以创建元素 50,而不必担心元素 3 到 49。 如果数组有一个自动长度变量(有关数组长度的自动监视的说明,请参见内部对象),则长度变量将设置为 51 而不是 4。 可以创建其中的元素编号之间无间隙的数组,但您不需要这样做。
在 JavaScript 中,对象和数组之间几乎相同。 它们之间的两个主要差异是:非数组对象没有自动长度属性;而数组没有对象的属性和方法。
寻址数组
使用中括号 ([]) 寻址数组,如以下示例所示。 中括号将一个数值或一个计算结果为整数的表达式括起来。
var entryNum = 5;
sample = new Array();
sample[1] = "Maple Street";
sample[entryNum] = 25;
document.write (sample[1]);
document.write (" ");
document.write (sample[entryNum]);
// Output: Maple Street 25
作为关联数组的对象
通常使用点运算符“.”访问对象的属性。 例如,
myObject.aProperty
在此示例中,属性名为标识符。 您也可以使用索引运算符 ([]) 来访问对象的属性。 在此情况下,您将对象视为其中的数据值与字符串关联的关联数组。 例如,
myObject["aProperty"] // Same as above.
索引运算符通常与访问数组元素关联。 在将其用于对象时,索引是表示属性名的字符串文本。
请注意两种对象属性访问方式之间的重要差异。
无法像操作数据一样操作被视为标识符(点 (.) 语法)的属性名。
可以像操作数据一样操作被视为索引(括号 ([]) 语法)的属性名。
如果您在运行时之前不知道属性名称将是什么(例如,当您基于用户输入构造对象时),此差异将显得十分有用。 若要从关联数组中提取所有属性,必须使用 for…in
循环。
类型化数组 (JavaScript)
可以使用类型化数组来处理来自网络协议、二进制文件格式和原始图形缓冲区等源的二进制数据。类型化数组还可用于管理具有已知字节布局的内存中二进制数据。
示例
以下代码显示如何将 ArrayBuffer
对象用作 XMLHttpRequest
的响应。可以通过使用 DataView
对象的不同方法,或通过将字节复制到适当的类型化数组,来操作响应中的字节。
有关使用具有
XmlHttpRequest
的不同响应类型的详细信息,请参阅、和。8d7738d1-4bfd-4cf1-8015-174def089556C0006BBD-17F9-4C6A-AF81-2ACAF109111DBE09137C-6546-441B-B953-DCBF72B77069
...
<div id="xhrDiv"></div>
...
var name = "http://www.microsoft.com";
var xhrDiv = document.getElementById("xhrDiv");
var req = new XMLHttpRequest();
req.open("GET", name, true);
req.responseType = "arraybuffer";
req.onreadystatechange = function () {
if (req.readyState == req.DONE) {
var arrayResponse = req.response;
var dataView = new DataView(arrayResponse);
var ints = new Uint32Array(dataView.byteLength / 4);
xhrDiv.style.backgroundColor = "#00FF00";
xhrDiv.innerText = "Array is " + ints.length + "uints long";
}
}
req.send();
ArrayBuffer
ArrayBuffer 对象表示用于存储不同类型化数组的数据的原始数据缓冲区。无法读取或写入 ArrayBuffer
,但可以将它传递给类型化数组或 DataView 对象 来解释原始缓冲区。可以使用 ArrayBuffer
来存储任何类型的数据(或混合类型的数据)。
DataView
可以使用 DataView 对象来读取不同类型的二进制数据,并将它写入 ArrayBuffer 中的任何位置。
类型化数组
类型化数组的类型表示可对其创建索引和进行操作的 ArrayBuffer 对象的视图。所有数组类型都具有固定长度。
名称 | 大小(以字节为单位) | 描述 |
---|---|---|
Int8Array 对象 | 1 | 8 位补码带符号整数 |
Uint8Array 对象 | 1 | 8 位无符号整数 |
Int16Array 对象 | 2 | 16 位补码带符号整数 |
Uint16Array 对象 | 2 | 16 位无符号整数 |
Int32Array 对象 | 4 | 32 位补码带符号整数 |
Uint32Array 对象 | 4 | 32 位无符号整数 |
Float32Array 对象 | 4 | 32 位 IEEE 浮点 |
Float64Array 对象 | 8 | 64 位 IEEE 浮点 |