介绍JavaScript与DOM、事件与处理程序、JavaScript函数与对象以及对象继承
JavaScript将带您进入新天地。在已经了解了HTML5标记的有关内容(也称为结构),并且了解了CSS样式(另称为外观 )。只不过所缺少的就是对JavaScript(否则称为行为)的进一步了解。 如果所知道的是结构和表现,当然,也可以创建一些美观的页面,但它们仍然仅仅是静态页面。若使用JavaScript添加行为时,就可以创建一个交互式的体验;甚至更好的是,可以创建功能完善的Web应用程序。
现在准备在Web工具箱中添加最有趣和最通用的技能:JavaScript编程!
目标是编写在加载网页时,在浏览器中运行的JavaScript代码可能响应用户的操作、更新或更改页面、与Web服务进行通信,并且通常使页面更像是一个应用程序,而不是一个文档。 让我们看看所有这些是如何工作的:
① 编写:
创建HTML标记和JavaScript代码,并将它们放入文件中,例如index.html和index.js(或者它们都可以放入HTML文件中)。
清单 1. 我的第一个JavaScript
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>我的第一个JavaScript</title>
<script>
var zcj = 69;
</script>
</head>
<body>
<h1>我的第一个JavaScript</h1>
<p></p>
<script>
zcj = zcj + 8;
</script>
</body>
</html>
② 加载:
浏览器检索并加载您的页面,从上到下解析其内容。 当浏览器遇到JavaScript时,浏览器会解析代码并检查其正确性,然后执行代码。 浏览器还构建HTML页面的内部模型,称为DOM
。其结构如图1所示:
图 1. DOM结构
③ 运行:
JavaScript继续执行,使用DOM来检查页面,更改页面,从页面接收事件或要求浏览器从Web服务器检索其他数据。
在页面上包含<script>元素(或对另一个JavaScript文件的引用)后,就可以开始编码了。 JavaScript是一种成熟的编程语言,您可以使用它来完成其他语言的几乎所有工作,甚至更多,因为我们在网页内进行编程!它能够进行顺序语句、条件语句和循环语句的编写执行。
① 顺序语句:
创建变量并赋值,通过表达式运算将其整合在一起,也有使用JavaScript库中的内置功能。
var temp = 68.6;
var beanCounter = 5;
var reallyCool = true;
var motto = "我说了算";
temp = (temp - 20) * 6 /7;
motto = motto + " 你也是!";
var pos = Math.random();
② 循环语句:
一遍又一遍地执行语句。
while (beanCounter > 0) {
proces();
beanCounter = beanCounter - 1;
}
③ 条件语句:
编写有条件的代码,具体取决于应用程序的状态。
if (isReallyCool) {
invite = "你被邀请了!";
} else {
invite = "抱歉,我们已满员。";
}
变量保存事物。 使用JavaScript变量可以容纳许多不同的数据。 让我们声明一些可容纳数据的变量:
var winners = 8; # 整数数值。
var boilingPt = 20.6; # 浮点数值。
var name = "张三"; # 字符串
var isEligible = false; # 布尔值,为true或false。
创建变量的三个步骤:
① 声明变量:
请注意,与某些语言不同,JavaScript不需要变量的类型,它只是创建一个可以容纳许多东西的通用容器。
var sco;
② 取值:
接下来,需要一个值来放入变量。 可以通过几种方式指定值:
var sco = 30; # 这个值可以是文字值,例如数字或字符串。
var sco = totalSco / pe; # 值可以是表达式的结果。
var sco = Math.random() * 10; # 使用JavaScript的内部库函数来创建值。
③ 赋值:
最后,有一个变量,并且有一个值。我们要做的就是将值赋给变量。变量sco
不再是undefined
,现在有了自己的值。当然,一旦创建了变量,就可以随时更改其值,甚至可以将其更改为具有不同类型的值。
例如:
sco = 8; # 可以将sco重置为另一个整数值。
sco = sco * 5; # 甚至在更改其值的表达式中使用变量自身。
sco = "厌倦了成为整数"; # 更改sco的值和类型(在此时更改为字符串)。
sco = null; # JavaScript中甚至有一个值表示“空值”,它被称为null。
看到的JavaScript语句如下:
sco = sco + 1;
让我们仔细看一下表达式,其在JavaScript中无处不在,因此了解可以表达式的内容很重要。
数值表达式:您可以编写得出数字的表达式...
sco = (9 / 5) * tempC + 32;
sco = x - 1;
sco = Math.random() * 10;
sco = 2.123 + 3.2;
字符串表达式:并且您可以编写产生字符串的表达式...
sco = "hello" + "world";
sco = p.innerHTML;
sco = phone.substring(0, 3);
布尔表达式:您可以编写导致布尔值true或false的表达式...
sco = 6 > 8;
sco = temp < 95;
sco = status == 4;
sco = name == "张三";
其他表达式:可以是函数,也可以是对象...
sco = function () {...};
sco = document.getElementById("name");
sco = new Array(15);
JavaScript使编程更容易上手吗?它的一种方法是通过根据需要将类型转换为其他类型来使表达式有意义。
一遍又一遍地做事...
如果我们在JavaScript程序中一次完成所有操作,那可能会是一个很无聊的程序。您做一些事情需要执行很多次,重复操作,直到满足某个条件为止。为了处理这些情况,JavaScript为您提供了几种遍历代码块的方法。
可以使用JavaScript的while循环执行某些操作,直到满足条件为止:
var sco = 10; # 假定有一桶冰淇淋,里面还剩十勺。这里是变量声明,并初始化为10。
# 当使用布尔表达式得出的结果为true或false。如果为true,则执行之后的代码,直到其为false为止。
while (sco > 0) {
alert("有冰淇淋!");
sco = sco - 1; # 每次通过一次循环时,通过从冰淇淋勺数中减去一个。
}
# 当条件(sco> 0)为false时,循环完成,并且无论程序的下一行是什么,代码都将在此处继续执行。
alert("没有冰淇淋的生活是不一样的");
使用JavaScript做出决策
我们一直在for和while语句中使用布尔表达式作为条件测试来决定是否继续循环。当然也可以使用它们在JavaScript中进行决策。
if (sco < 2) { # 这是布尔表达式,进行测试以查看还剩下多少勺。
alert("冰淇淋快用完了!"); # 如果还剩不到2勺,则发出告警!
}
做出更多决策...
if (scoops == 3) {
alert("冰淇淋快用完了!");
} else if (scoops > 9) {
alert("快点吃,冰淇淋将融化!");
} else if (scoops == 2) { # 添加了附加条件,倒数到零。
alert("走一个! 1");
} else if (scoops == 1) {
alert("走一个! 2");
} else if (scoops == 0) {
alert("已完!");
} else {
alert("还有很多冰淇淋,快点来拿。");
}
要使用JavaScript,必须将其添加到网页上,但是在何处以及如何进行操作?我们已经知道有一个<script>元素,可以在哪里使用它,以及如何影响JavaScript在页面中的执行方式,可以通过以下三种不同的方式向页面添加代码:
① 将脚本内联放置在<head>元素中:
向页面添加代码的最常见方法是在页面的顶部放置<script>元素。<head>元素中的JavaScript,它在浏览器解析了head(首先执行!)之前就执行了,然后才解析了页面的其余部分。
将<script>元素放在HTML的<head>中,以便在页面加载之前执行它们。
清单 2. <script>元素放在<head>中
<!doctype html>
<html lang="en">
<head>
<title>在浏览器上</title>
<meta charset="utf-8">
<script>
// 答案1
var count = 0;
for(var i = 0; i < 5; i++) {
count = count + i;
}
alert("count 是 " + count);
// 10
// 答案2
var tops = 5;
while(tops > 0) {
for(var spins = 0; spins < 3; spins++) {
alert("Top " + tops + " 在旋转!");
}
tops = tops - 1;
}
alert("Tops 旋转: " + count);
// 15
// 答案3
for(var berries = 5; berries > 0; berries--) {
alert("吃浆果");
}
alert("吃浆果: " + count);
// 5
//答案4
for(var scoops = 0; scoops < 10; scoops++) {
alert("还有更多的冰淇淋!");
}
alert("没有冰淇淋的生活是不一样的: " + count);
// 10
</script>
</head>
<body>
<h1>在浏览器上</h1>
</body>
</html>
② 通过引用单独的JavaScript文件来添加脚本:
可以链接到包含JavaScript代码的单独文件。将该文件的链接路径放在</script>标记的src属性中,并确保使用</ script>关闭<script>元素。
清单 3. 引用单独的JavaScript文件
<!doctype html>
<html>
<head>
<title>我的播放清单</title>
<meta charset="utf-8">
<script src ='js/mycode.js'></script>
</head>
<body>
<h1>我欣赏的播放清单</h1>
<ul id="playlist">
<li id="song1"></li>
<li id="song2"></li>
<li id="song3"></li>
</ul>
</body>
</html>
# mycode.js
function addSongs() {
var song1 = document.getElementById("song1");
var song2 = document.getElementById("song2");
var song3 = document.getElementById("song3");
song1.innerHTML = "Blue Suede Strings--Elvis Pagely";
song2.innerHTML = "Great Objects on Fire--Jerry JSON Lewis";
song3.innerHTML = "I Code the Line--Johnny JavaScript";
}
window.onload = addSongs;
③ 将代码以内联方式或作为指向单独文件的链接添加页面文档的<body></body>
元素内。
可以将代码(或对代码的引用)放在body
中, 该代码在加载body
时执行。大多数情况代码都添加到页面的head
元素内。 在body
元素的末尾添加代码有一些轻微的性能优势,但前提是确实需要特别优化页面的性能。
清单 4. 在body
元素的末尾添加代码
<!doctype html>
<html>
<head>
<title>回文</title>
<meta charset="utf-8">
</head>
<body>
<h1>Palindrome</h1>
<p id="message">
</p>
<script>
var word1 = "a";
var word2 = "nam";
var word3 = "nal p";
var word4 = "lan a c";
var word5 = "a man a p";
var phrase = "";
for(var i = 0; i < 4; i++) {
if(i == 0) {
phrase = word5;
} else if(i == 1) {
phrase = phrase + word4;
} else if(i == 2) {
phrase = phrase + word1 + word3;
} else if(i == 3) {
phrase = phrase + word1 + word2 + word1;
}
}
var message = document.getElementById("message");
message.innerHTML = phrase;
alert(phrase);
</script>
</body>
</html>
JavaScript和HTML是两回事。 HTML是标记,而JavaScript是代码。 那么如何获得JavaScript与页面中的标记进行交互的场景? 将使用文档对象模型document
。
① 当将页面加载到浏览器中时,浏览器将解析HTML并创建文档的内部模型,该模型包含HTML标记的所有元素。如图1所示。
我们称其为“文档对象模型(Document Object Model)”,您可以要求它告诉您有关页面结构或内容的任何信息。简称为DOM
。
② JavaScript可以与DOM交互以获得对元素及其内容的访问。
JavaScript也可以使用DOM来创建或删除元素(将要完成的许多其他事情)。通过检索、响应和更改DOM,JavaScript可用于编写交互式网页/应用程序。
③ 当JavaScript修改DOM时,浏览器会动态更新页面,新内容实时显示在页面上。
④ DOM的初步编程体验
文档对象模型的优点在于,它为我们提供了一种在所有浏览器中一致的方式,使得可以从代码中访问HTML的结构和内容。 每个DOM的顶部都有一个文档对象,然后是一棵树,其中包含HTML标记中每个元素的分支和叶节点。 让我们仔细看看。
图 2. DOM树
我们将这种结构与树进行比较,因为“树”是来自计算机科学的数据结构。document
也就像一棵倒立的树的根。它始终位于顶部。 document
是树的特殊部分,可以在JavaScript中使用它来访问整个DOM。像body
、div
这些就像树的树枝,而像script
、h1
这些就像树上的叶子(因为里面没有元素,只有文本)。
清单 5. DOM树html
<!doctype html>
<html>
<head>
<title>我的博客</title>
<meta charset="utf-8">
<script>
function init() {
// 获得id为“movie1”的<h2>元素对象并更改其内容。
var movie = document.getElementById("movie1");
movie.innerHTML = "战狼2,由吴京导演。 ";
}
window.onload = init; //在这里,将window.onload属性的值设置为函数名称。
</script>
</head>
<body>
<h1>我的博客</h1>
<div id="entry1">
<!--
这里是要通过JavaScript更改的<h2>元素。
-->
<h2 id="movie1">来自太空的计划9</h2>
<p>
在3:00 pm、7:00pm上映。
<span>
今晚<em>午夜</em>特别放映!
</span>
</p>
</div>
<div id="entry2">
<h2 id="movie2">禁行星</h2>
<p>
在5:00 pm、9:00pm上映。
</p>
</div>
</body>
</html>
我们将设计开发一个歌曲播放列表管理器。
无需创建大型的复杂网页,实际上,可以非常简单地开始。 让我们创建一个HTML5文档,其中包含一个form表单和一个list元素来保存播放列表:
清单 6. 歌曲播放列表管理器页面playlist.html
<!DOCTYPE html>
<html>
<!--标准HTML5头部和身体。-->
<head>
<title>韦伯维尔曲调</title>
<meta charset="utf-8">
<!--把所有的JavaScript放在playlist.js文件中-->
<script src="js/playlist.js"></script>
<!--包含了一个样式表,以使播放列表应用具有良好的外观和感觉。-->
<link rel="stylesheet" href="css/playlist.css">
</head>
<body>
<!--需要的只是一个简单的表格。-->
<form>
<!--
这是一个文本输入框,用于输入歌曲。 我们正在使用HTML5占位符属性placeholder,
该属性显示了在输入框中需要键入内容的提示。
-->
<input type="text" id="songTextInput" size="40" placeholder="歌名">
<!--
有一个id为“ addButton”的按钮,用于将新添加内容提交到播放列表。
-->
<input type="button" id="addButton" value="添加歌曲">
</form>
<ul id="playlist">
<!--
将使用的歌曲列表。 现在它是空的,不过我们将使用JavaScript代码对其进行更改...
-->
</ul>
</body>
</html>
⑴ 我们需要考虑两件事:
①. 需要编写一些JavaScript代码,这些代码将在用户单击“添加歌曲”按钮时进行评估。 此代码将在播放列表中添加一首歌曲。
②. 需要一种方式来连接那部分代码,以便在单击按钮时,JavaScript知道运行该“添加歌曲”代码。
⑵ 当用户单击(或触摸基于手持的设备)按钮时,我们就想了解它。 我们只是对“按钮点击事件”感兴趣。
每当有事件发生时,您的代码就有机会处理它。也就是说,提供事件发生时将被调用的一些代码。当按钮单击事件onclick
发生时,则需要处理它们,添加新歌到播放列表中,当新数据到达时,可能需要对其进行处理并将其显示在页面上。
⑶ 制定方案:
目标是单击“添加歌曲”,然后将歌曲添加到页面上的播放列表中。 让我们来完成这样的任务:
⑷ 访问“添加歌曲”按钮:
要让按钮在单击事件发生时通知我们,首先需要访问该按钮。 幸运的是,我们已使用HTML标记元素(<input type="button" id="addButton">
)创建了按钮,这意味着它已在DOM中表示,并且我们已经知道如何在DOM中获取元素。 在其中看到我们为按钮指定了一个属性值为addButton的id属性, 因此,我们将使用getElementById
方法获取对该按钮的引用:
var button = document.getElementById("addButton");
现在,我们只需要给按钮提供一些代码即可在发生点击时调用。为此,我们将创建一个名为handleButtonClick的函数来处理事件。这里是功能,稍后介绍细节:
// 该函数名为handleButtonClick。
// 函数提供了一种将代码打包成块的方法,然后在需要的地方重复使用代码块。
function handleButtonClick() {
alert("单击了按钮!"); // 将在调用此函数时显示一个告警框。
}
⑸ 给按钮一个点击处理程序:
已有一个按钮,还有一个可以充当处理程序的函数handleButtonClick
,将它们组合在一起。 为此,使用按钮的属性onclick
并这样设置onclick属性:
var button = document.getElementById("addButton");
button.onclick = handleButtonClick; // 将按钮的onclick属性设置为发生click事件时要调用的函数。
您可能还记得,在加载窗口之后,使用window.onload属性调用函数时,做了类似的事情。现在,将这些放在一起:
// 正在使用init函数,直到页面完全加载后才被调用和执行。
window.onload = init;
function init() {
var button = document.getElementById("addButton");
button.onclick = handleButtonClick; // 页面加载后,我们将抓住按钮并进行设置它的onclick处理程序。
}
function handleButtonClick() {
alert("单击了按钮!"); // 当我们单击按钮时,单击处理程序将显示告警框。
}
要访问函数的指针,必须去掉函数名后面的那对圆括号,即函数名本身就是变量,在此处是handleButtonClick
,代表指向函数的指针。
⑹ 仔细看看发生了什么...
让我们再次回顾一下代码:
① 第一件事是在HTML表单中抛出一个按钮。 有了这个,需要一种方法来捕获用户对该按钮的点击,以便可以执行一些代码。 为此,我们创建了一个处理程序并将其分配给按钮的onclick属性。
② 编写了一个简单的处理程序,该处理程序仅警告用户单击该按钮。
③ 编写代码后,浏览器将加载页面并显示该页面,并且绑定了处理程序。
④ 最后,用户单击按钮,该按钮立即生效,注意到它已有处理程序,然后调用之。
⑺ 获取歌曲名称
获取用户输入的歌曲名称。一旦有了它,就可以考虑如何在浏览器中显示播放列表。要从表单文本输入元素获取文本,首先必须从DOM获取输入元素,如何做到这一点,通过getElementById方法来完成。一旦完成,就可以使用input
元素的value
属性来访问用户在表单字段中键入的文本,方法如下:
function handleButtonClick() {
// 使用getElementById方法,可以获取表单中songTextInput输入元素的句柄。
// 这是我们想要从DOM获得的元素。 将使用其id为“songTextInput”来获取它。
var textInput = document.getElementById("songTextInput");
// input元素的value属性保留键入文本输入中的任何内容,它只是一个字符串。 在此,将文本分配给变量songName。
var songName = textInput.value;
// 可以使用if语句,将songName字符串变量与一个空字符串进行比较,以确保用户键入了某些内容。 如果没有输入任何内容,则提醒并要求输入歌曲名。
if (songName == "") {
alert("请输入一首歌");
} else {
alert("添加 " + songName);
}
}
⑻ 如何在页面上添加歌曲?
已经做了很多工作! 将歌曲名称输入表单中,单击“添加歌曲”按钮,然后将输入的文本信息加到页面时仍需要做的事情:
① 需要注意到,当首次输入HTML标记时,已经在页面标记中放置了一个空列表(确切地说是一个空的<ul>元素,id属性为playlist)。
② 每当输入一首新歌时,都将其添加到无序列表中。为此,需要创建一个新的<li>元素,用来保存歌曲名称; 然后,将新的<li>元素添加到DOM中的<ul>中。完成此操作后,浏览器将执行并看到页面更新,就像<li>一直存在一样。 当然,这些将用代码来完成所有工作。 DOM结构如图3所示。
图3 播放列表DOM
⑼ 如何创建一个新元素:
已经了解了如何通过DOM访问现有元素。 不过,也可以使用DOM创建新元素。 要创建一个<li>元素,操作方法如下:
// createElement创建一个全新的元素。它尚未插入DOM。它只是需要在DOM中自由放置的自由浮动元素。
var li = document.createElement("li"); // 使用document.createElement创建新元素,并返回对新元素的引用。
// 将要创建的元素类型作为字符串传递给createElement。
现在我们有了一个新的<li>元素,其中没有任何内容。 已经知道将文本放入元素的一种方法:
li.innerHTML = songName; // 这将<li>的内容设置为歌曲标题。新li元素对象目前还不是DOM的一部分!
⑽ 向DOM添加元素:
要将新元素添加到DOM,必须知道要将其放置在何处。 确实知道该放在哪里:将<li>元素放在<ul>元素中。但是该怎么做呢? 再来看看图3的DOM。 还记得它像一棵树吗?
要添<li>元素,需要使其成为<ul>元素的子元素。 为此,首先需要在树中找到<ul>元素(已为它提供了一个“playlist”的id属性),然后添加<li>,告知<ul>元素是要添加它的一个新的子元素。操作方法如下:
// 使用getElementById获取对id为“playlist”的<ul>元素的引用。
var ul = document.getElementById("playlist");
// 每次调用appendChild时,都会添加<ul>元素新的<li>元素到已经存在的其他<li>元素之后。
// 要求<ul>元素将<li>元素添加为子元素。 完成此操作后,DOM将<li>作为<ul>的子级,浏览器将更新显示内容以反映新的<li>。
ul.appendChild(li);
⑾ 全部整合在一起:
让我们将所有代码放在一起,并将其添加到handleButtonClick函数中:
function handleButtonClick() {
var textInput = document.getElementById("songTextInput");
var songName = textInput.value;
var li = document.createElement("li"); // 首先,创建一个新的<li>元素,其为歌曲名称的位置;
li.innerHTML = songName; // 然后,将该元素的内容设置为歌曲名称;
var ul = document.getElementById("playlist"); // <ul>是新增的<li>的父元素。
ul.appendChild(li); // 然后,我们使用appendChild将li对象添加到ul中。注意,要求父元素ul将li添加为新子元素。
}
整合后的代码放入清单7中。
清单 7. playlist.js
window.onload = init;
function init() {
var button = document.getElementById("addButton");
button.onclick = handleButtonClick;
// 从本地存储中获取已保存的歌曲,在playlist_store.js文件中
loadPlaylist();
}
function handleButtonClick(e) {
var textInput = document.getElementById("songTextInput");
var songName = textInput.value;
if(songName == "") {
alert("请输入一首歌");
} else {
var li = document.createElement("li");
li.innerHTML = songName;
var ul = document.getElementById("playlist");
ul.appendChild(li);
// 调用存储函数,在playlist_store.js文件中
save(songName);
}
}
⑿ 保存播放列表
存储歌曲播放列表需要HTML5 Web存储API。要做的就是创建一个新文件playlist_store.js,键入的代码内容在清单8中。然后对现有代码进行一些更改。
需要做一些调整以集成存储代码。 首先,在playlist.html的<head>元素中添加对playlist_store.js的引用:
<script src="playlist_store.js"></script>
<script src="playlist.js"></script>
现在,只需要在playlist.js中添加两行代码即可加载和保存播放列表:
function init() {
...
loadPlaylist(); // 这将在加载页面时从localStorage加载已保存的歌曲。
}
function handleButtonClick() {
...
save(songName); // 每次将歌曲添加到播放列表时,都会保存歌曲。
}
清单 8. playlist_store.js
function save(item) {
var playlistArray = getStoreArray("playlist");
playlistArray.push(item);
localStorage.setItem("playlist", JSON.stringify(playlistArray));
}
function loadPlaylist() {
var playlistArray = getSavedSongs();
var ul = document.getElementById("playlist");
if(playlistArray != null) {
for(var i = 0; i < playlistArray.length; i++) {
var li = document.createElement("li");
li.innerHTML = playlistArray[i];
ul.appendChild(li);
}
}
}
function getSavedSongs() {
return getStoreArray("playlist");
}
function getStoreArray(key) {
var playlistArray = localStorage.getItem(key);
if(playlistArray == null || playlistArray == "") {
playlistArray = new Array();
} else {
playlistArray = JSON.parse(playlistArray);
}
return playlistArray;
}
面向对象(Object-oriented,OO)语言通常有类的概念,通过类创建的任意多个对象具有相同属性和方法。 而ECMAScript
是一种由Ecma国际(前身为欧洲计算机制造商协会,European Computer Manufacturers Association)通过ECMA-262标准化的脚本程序设计语言。这种语言往往被称为JavaScript或JScript,所以它可以理解为是JavaScript的一个标准。在这里,ECMAScript没有类的概念,因此对象与基于类的语言不同。 ECMA-262将对象定义为“属性的无序集合,每个属性可以包含原型值、对象或函数。”
严格来说,这意味着对象是一个没有特定顺序的属性值的集合。 每个属性或方法均由映射到值的名称标识。 因此,将ECMAScript对象视为哈希表
:包含一组键-值
对,其中值可以是数据或函数。每个对象都是基于引用类型创建的,该引用类型可以是原生类型,也可以是开发人员定义的类型。
一个对象是特定引用类型的实例。 在ECMAScript中,引用类型是用于将数据和功能组合在一起的结构,通常被错误地称为类。 尽管ECMAScript从技术上讲是一种面向对象的语言,但是它缺少一些传统上与面向对象的编程相关联的基本结构,包括类和接口。 引用类型
有时也称为对象定义
,因为它们描述了对象应具有的属性和方法。
对象被认为是某个特定引用类型的实例。 通过使用new运算符后跟构造函数来创建新对象。 构造函数只是一个函数,其目的是创建一个新对象。 代码行如下:
var myobj = new Object();
此代码创建Object
引用类型的新实例,并将其引用存储在变量myobj中。 使用的构造函数是Object(),它将创建仅具有默认属性和方法的简单对象。 ECMAScript提供了许多原生引用类型,以帮助开发人员实现常见的计算任务。
① Object类型
到目前为止,大多数引用对象都是Object类型的实例,这是ECMAScript中最常用的类型之一。 尽管Object实例没有太多功能,但它们非常适合在应用程序中存储和传输数据。
有两种方法可以显式创建Object的实例。 首先,第一种方法是将new运算符与Object构造函数一起使用,代码如下所示:
var person = new Object();
person.name = “张三”;
person.age = 30;
其次,另一种方法是使用对象字面量表示法。 对象字面量表示法是对象定义的简写形式,旨在简化创建具有许多属性的对象。 例如, 以下使用对象字面量表示法定义上一个示例中的相同的对象:
var person = {
name : “张三”,
age : 30
};
在此示例中,左花括号({
)表示对象字面量的开始,因为它出现在表达式上下文中。 ECMAScript中的表达式上下文指的是能够返回一个表达式。赋值运算符表示后面是一个值,因此左花括号表示一个表达式的开始。接下来,指定name属性,后跟冒号,然后是属性的值。逗号用于分隔对象文字中的属性,因此,在字符串“张三”之后有一个逗号,但在值30之后没有逗号,因为age是对象中的最后一个属性。
在使用对象字面量表示法时,属性名称也可以指定为字符串或数字,例如在以下示例中:
var person = {
“name” : “张三”,
“age” : 30,
5: true
};
这个实例将创建一个具有name、age和5属性的对象。 请注意,数字属性名称会自动转换为字符串。
通过使用花括号之间为空,还可以使用对象字面量创建仅具有默认属性和方法的对象,例如:
var person = {}; // 与new Object()相同
person.name = “张三”;
person.age = 30;
② Array类型
ECMAScript数组是数据的有序列表,但与其他语言不同,它们可以在每个项中保存任何类型的数据。 这意味着可以创建一个数组,该数组的第一个位置包含一个字符串,第二个位置包含一个数字,第三个位置包含一个对象,依此类推。 ECMAScript数组的大小也可以动态调整,自动增长以容纳添加到其中的任何数据。
数组可以通过两种基本方式创建。 第一种方法是使用Array构造函数,如下所示:
var colors = new Array();
如果知道数组中的项目数,则可以将计数传递到构造函数中,然后将使用该值自动创建length属性。 例如,以下代码创建一个初始长度值为20的数组:
var colors = new Array(20);
Array构造函数也可以传递应该包含在数组中的项目。 下面创建具有三个字符串值的数组:
var colors = new Array(“red”, “blue”, “green”);
创建数组的第二种方法是使用数组字面量表示法。 通过使用方括号并在其之间放置逗号分隔的项目列表来指定数组字面量,如下所示:
var colors = [“red”, “blue”, “green”]; // 用三个字符串创建一个数组
var names = []; // 创建一个空数组
var values = [1,2,]; // 避免! 创建一个包含2或3个项目的数组
var options = [,,,,,]; // 避免! 创建一个包含5或6个项目的数组
③ Date类型
若要创建日期对象,请使用new运算符以及Date构造函数,如下所示:
var now = new Date();
使用不带任何参数的Date构造函数时,将为创建的对象分配当前日期和时间。
④ RegExp类型
ECMAScript通过RegExp类型支持正则表达式。 正则表达式很容易使用类似于Perl的语法创建,如下所示:
var expression = /pattern/flags;
⑤ Function类型
ECMAScript最有趣的部分是它的函数
,主要是因为函数实际上是对象。 每个函数都是Function类型的实例,该函数具有与其他任何引用类型一样的属性和方法。 因为函数是对象,所以函数名称仅是指向函数对象的指针,而不会与某个函数绑定。 通常使用函数声明语法来定义函数,如下所示:
function sum (num1, num2) {
return num1 + num2;
}
这几乎完全等同于使用函数表达式,例如:
var sum = function(num1, num2){
return num1 + num2;
};
在此代码中,变量sum被定义并初始化为一个函数。 请注意,在function关键字之后不包含函数名称,因为它不是必需的--通过变量sum就可以引用函数。 还要注意,函数结束后会有分号,就像任何变量初始化之后一样。
函数属性和方法:
如前所述,函数既然是ECMAScript中的对象,因此,它们具有属性和方法。 每个函数都有两个属性:length
和 prototype
。 length属性
表示函数期望的命名参数的数量,如下所示:
function sayHello(name){
alert('Hello ' + name);
}
alert(sayHello.length); // 1
该代码定义了sayName()函数,该函数指定一个参数,因此将其length属性设置为1。
prototype属性
可能是ECMAScript核心中最有趣的部分。 prototype
是引用类型的所有对象实例的 指针
,这意味着诸如toString()和valueOf()之类的方法实际上存在于prototype
名下,即由 prototype
指向,然后可以从对象实例中进行访问。
每个函数都含有两种附加的方法:apply()
和 call()
。
⑥ 原始包装类型
为了便于操作原始类型值,ECMAScript提供了3个特殊的引用类型:Boolean
、Number
和String
。每当读取一个原始类型值的时候,后台就会创建一个对应的原始包装类型的对象,从而能够调用一些方法来操作这些数据。
⑦ 单例内置对象
由ECMAScript实现提供的、不依赖于宿主环境的任何对象,在ECMAScript程序执行之前就存在了。意味着不必显式地实例化内置对象,因为它们已经实例化了。除了已经了解的内置对象,例如 Object、Array和String。 ECMA-262还定义了两个单例内置对象:Global和Math。
创建自定义对象的最简单方法是创建一个Object引用类型实例,并向其添加属性和方法,如下所示:
var person = new Object();
person.name = “张三”;
person.age = 30;
person.job = “软件工程师”;
person.sayName = function(){
alert(this.name);
};
本示例创建一个名为person
的对象,该对象具有三个属性(name
,age
和job
)和一个方法(sayName()
)。 sayName()方法显示this.name的值,该值解析为person.name。
现在使用对象字面量成为创建此类对象的首选模式。 可以使用对象字面量来重写前面的示例,如下所示:
var person = {
name: “张三”,
age: 30,
job: “软件工程师”,
sayName: function(){
alert(this.name);
}
};
此示例中的person对象等效于前一个示例中的person对象,具有所有相同的属性和方法。 所有这些属性都是使用某些定义的特性创建的,这些特性定义了它们在JavaScript中的行为。
属性类型:
① 工厂模式:
工厂模式是一种众所周知的设计模式,在软件工程中用于抽象创建特定对象的过程。 由于无法在ECMAScript中定义类,在此建立了一种函数来封装具有特定接口的对象的创建,以下示例:
function createPerson(name, age, job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
alert(this.name);
};
return o;
}
var person1 = createPerson(“张三”, 30, “软件工程师”);
var person2 = createPerson(“李四”, 28, “医生”);
在这里,函数createPerson()
接受用于构建对象的参数,该对象具有表示Person对象的所有必要信息。 可以多次调用该函数具有不同的参数,并且仍将返回具有三个属性和一个方法的对象。尽管这解决了创建多个相似对象的问题,但是工厂模式并未解决对象识别(对象是什么类型的对象)的问题。
② 构造函数模式: ECMAScript中的构造函数用于创建特定类型的对象。 有原生构造函数,例如Object和Array,在运行时它们可以自动出现在执行环境中。 也可以自定义构造函数,并定义自己的对象类型的属性和方法。 示例如下:
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
alert(this.name);
};
}
var person1 = new Person(“张三”, 30, “软件工程师”);
var person2 = new Person(“李四”, 28, “医生”);
在以上的构造函数中,每个方法都要在每个实例上重新创建一遍,通过把函数定义移到构造函数外来改进:
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = sayName;
}
function sayName(){
alert(this.name);
}
var person1 = new Person(“张三”, 30, “软件工程师”);
var person2 = new Person(“李四”, 28, “医生”);
在此示例中,sayName()
函数是在构造函数之外定义的。 在构造函数内部,sayName属性设置为等于全局sayName()函数。 由于sayName属性现在仅包含指向函数的指针,因此person1和person2最终共享在全局范围内定义的sayName()函数。 这解决了具有重复函数的功能,这些功能可以执行相同的操作,但通过引入实际上只能用于对象的方法,也会在全局范围内造成混乱。 如果对象需要多个方法,则意味着多个全局函数,并且没有很好的体现封装性。 这些问题通过使用原型模式来解决。
③ 原型模式:
创建的每个函数都有一个prototype
(原型)属性,该属性是一个指针,指向一个对象,其中包含应可用于特定引用类型的所有实例共享的属性和方法。 该对象实际上是调用构造函数后要创建的对象实例的原型对象。使用原型的好处是它的所有属性和方法在对象实例之间共享。 与其在构造函数中分配对象信息,不如将它们直接分配给原型对象:
function Person(){
}
Person.prototype.name = “张三”;
Person.prototype.age = 30;
Person.prototype.job = “软件工程师”;
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
person1.sayName(); //”张三”
var person2 = new Person();
person2.sayName(); //”张三”
alert(person1.sayName == person2.sayName); //true
在这里,属性和sayName()方法直接添加到Person函数的prototype
属性中,将构造函数留空。 不过,仍然可以调用构造函数来创建一个新对象,并具有存在的属性和方法。 与构造函数模式不同,属性和方法都在实例之间共享,因此person1和person2都在访问相同的属性集和相同的sayName()函数。 不过,实例对象一般都是要有属于自己的全部属性,这个问题可通过构造函数模式和原型模式结合使用来解决。
④ 结合构造函数模式与原型模式:
创建自定义类型的最常见方法是将构造函数模式和原型模式结合在一起。 构造函数模式定义实例属性,而原型模式定义方法和共享属性。 使用这种方法,每个实例最终都有自己的实例属性副本,但是它们都共享对方法的引用,从而节省了内存。 这种模式也允许将参数传递到构造函数中,从而有效地结合了每种模式的最佳部分。 现在可以将前面示例重写为:
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
}
Person.prototype = {
constructor: Person,
sayName : function () {
alert(this.name);
}
};
var person1 = new Person(“张三”, 30, “软件工程师”);
var person2 = new Person(“李四”, 28, “医生”);
alert(person1.sayName()); // 张三
alert(person2.sayName()); // 李四
alert(person1.sayName === person2.sayName); // true
请注意,实例属性现在仅在构造函数中定义,并且共享属性构造函数和方法sayName()在原型上定义。
混合构造函数模式与原型模式是在ECMAScript中定义自定义引用类型的最广泛使用和接受的做法。 通常,这是用于定义引用类型的默认模式。
举例:
清单 9. 创建圆的类型及实例
var Circle = function(r){
this.radius = r;
}
Circle.prototype.area = function() {
return Math.PI * this.radius * this.radius;
}
Circle.prototype.circumference = function(){
return 2 * Math.PI * this.radius;
}
var circle1 = new Circle(3);
var circle2 = new Circle(12);
var area1 = circle1 .area();
var area2 = circle2.area();
var cir1 = circle1.circumference();
var cir2 = circle2.circumference();
// 第一个圆的面积:28.274333882308138, 第一个圆的周长:18.84955592153876
alert('第一个圆的面积:'+ area1 +', 第一个圆的周长:' +cir1);
// 第二个圆的面积:452.3893421169302, 第二个圆的周长:75.39822368615503
alert(' 第二个圆的面积:' + area2+', 第二个圆的周长:'+cir2);
在清单 9中,首先创建一个函数对象,将其引用给变量Circle
,和所有函数一样,在创建它时,指向的拥有的另一个对象用作其prototype属性的值。然后向这个原型对象添加area
和 circumference
函数。接下来用 new Circle()
创建了一对圆对象。操作符 new
创建新的对象,其原型为 Circle.prototype
,其情景如图4所示:
图4 构造函数模式与原型模式创建对象实例情景
关于OO编程最常讨论的概念是继承。 许多OO语言支持两种继承:接口继承(仅继承方法签名)和实现继承(继承实际方法)。 而在ECMAScript中无法进行接口继承,因为函数没有签名。 实现继承是ECMAScript支持的唯一继承类型,这主要是通过使用原型链来完成的。
原型链
原型链
是ECMAScript中继承的主要方法。 其基本思想是使用原型的概念在两个引用类型之间继承属性和方法。 回顾一下构造函数、原型和实例之间的关系:每个构造函数都有一个prototype属性,其指向的原型对象里包含一个constructor属性,它指向该构造函数,而该构造函数的实例则具有一个指向该原型对象的内部指针。 如果原型对象实际上是另一种类型的实例怎么办? 这意味着此时的原型对象将具有指向另一个原型对象的指针,而另一原型对象的constructor属性指向另一个构造函数。 如果另一原型对象也是其它类型的实例,那么该模式要是依然成立,从而在实例和原型之间形成一条链。 这就是原型链接的基本思想。
引用类型的最佳继承范式
就是使用构造函数来继承属性,使用原型链接的混合形式来继承方法。 ECMAScript 5通过添加Object.create()方法使原型继承的概念规范化。 此方法接受两个参数,一个用作新对象原型的对象,以及另一个可选的对象,它定义要应用于新对象的其他属性。
从超类型的原型继承,然后将结果分配给子类型的原型,基本模式如下:
function inheritPrototype(subType, superType){
var prototype = Object.create(superType.prototype); // 创建对象
prototype.constructor = subType; // 加强对象,弥补因重写子类型原型而失去的默认的constructor属性
subType.prototype = prototype; // 指定对象
}
这里声明的inheritPrototype()
函数实现了基本的原型继承。该函数接受两个参数:子类型构造函数和超类型构造函数。 在其内,首先是创建超类型原型的副本。 接下来,将constructor属性分配给原型,以解决原型被覆盖时丢失默认constructor属性的问题。 最后,将子类型的原型指定为新创建的对象。 这样,调用inheritPrototype()
函数,就可实现子类型到超类型的原型继承,再结合构造函数来继承属性,如下所示:
var SuperType = function(name){
this.name = name;
this.colors = [“red”, “blue”, “green”];
};
SuperType.prototype.sayName = function(){
alert(this.name);
};
var SubType = function(name, age){
SuperType.call(this, name); // 调用超类型的构造函数
this.age = age;
};
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function(){
alert(this.age);
};
此示例在SubType
子类型构造函数内只调用了一次SuperType
父类型构造函数,以继承属性。 此外,通过调用 inheritPrototype
函数,原型链保持完整,以继承方法。
为了进一步理解JavaScript的继承,让我们创建一个名为Circle
的类型和一个名为ColoredCircle
的子类型。彩色圆是一个染有颜色的圆,有其自己的半径和色彩属性,所有彩色圆应当共享一个变亮方法。所有圆操作(包括已经存在和将来要添加的操作)都应当可供彩色圆使用。图5演示了实现的一种情景,所有彩色圆实例都共享一个彩色圆原型,并通过单色圆原型继承所有单色圆操作。
图5 圆类型、彩色圆类型及其实例
清单10设计了构建一个具有构造函数和原型的圆类型Circle
。进一步,为ColoredCircle
开发构造函数和原型, 为使彩色圆继承单色圆(基础圆)的特性(计算面积与计算周长),必须将彩色圆原型链接到圆原型。
清单 10. 一个圆类型与一个彩色圆类型及继承
var Circle = function(r){
this.radius = r;
};
Circle.prototype.area = function() {
return Math.PI * this.radius * this.radius;
};
Circle.prototype.circumference = function(){
return 2 * Math.PI * this.radius;
};
var ColoredCircle = function(radius, color){
Circle.call(this, radius); // 调用父类型构造函数
this.color = color;
};
ColoredCircle.prototype = Object.create(Circle.prototype); // 建立原型链接
ColoredCircle.prototype.constructor = ColoredCircle; // 弥补因重写子类型原型而失去的默认的constructor属性
ColoredCircle.prototype.brighten = function(amount){
this.color.red *= amount;
this.color.green *= amount;
this.color.blue *= amount;
};
var cc = new ColoredCircle(5,{red:0.2,green:0.8,blue:0.33});
alert("彩色圆半径:" + cc.radius ); // 5
alert("彩色圆面积:" + cc.area()); // 78.54
alert("彩色圆周长:" + cc.circumference()); // 31.42
cc.brighten(1.1);
alert("彩色圆的颜色:"+ JSON.stringify(cc.color)); // {"red":0.22,"green":0.88,"blue":0.363}
博文最后更新时间: