网页开发入门|15 脚本(下)
使用脚本让我们有能力重写页面,或者通过也页面中的元素进行修改。

在上节课,我们了解了一些脚本的基本用法。
使用脚本可以让我们有能力重写页面,或者通过获取页面中元素对局部进行修改。但无论要重写页面,还是要进行局部修改,我们都需要借助DOM
对象。
再次强调「对象」是一个很重要的概念。

可以把「对象」理解为虚拟世界中的物体或者东西,比如浏览器、页面、页面中的元素等等,甚至一个用户、一则浏览记录、一组数据都是「对象」。
每个对象都有一些字段field与功能function,在js
中统称为属性property。

在更多的场合里,大家更习惯称对象的功能为方法或函数,称字段为属性。我们的课程也将采取后者,即更加符合习惯的叫法。
为了区别我们之前已经学到的「标签属性attribute」和「样式属性property」,我们将使用「对象属性field」来称呼对象的字段。

比如,document
是w3c
标准中规定好的,用来指代整体页面的DOM
对象,所以我们在脚本中可以直接使用它的属性或者方法。
document
对象有一个属性URL
,它的属性值便是当前页面的网址,在代码中可以通过document.URL
来获取这个属性值。
配合alert()
功能,可以把这个信息弹出来看一下:
alert(document.URL)

document
对象还有一个方法getElementById()
,完整的写法为document. getElementById()
,通过它可以获取指定id
的页面元素。具体使用方法,在上节课已经有所演示,这里不再重复。
可以看到,无论是属性还是方法,都可以在对象的后面紧接着使用.
来进行访问access。

大多数情况下,在方法名字的后面,我们会紧接着使用一对()
,表明立刻执行这个方法,这个过程有的时候也称作方法的调用call。
你可以通过阅读W3C的文档了解更多的属性和方法。
本节课,我们将通过对页面中的局部元素进行修改,了解如何使用DOM对象的属性和方法。
修改元素内容
让我们回到上节课script.html
的代码里。
<p id="greeting">hello, world!</p>
<button onclick="
const paragraph = document.getElementById('greeting');
alert(paragraph)
">click here</button>
这段代码,通过document.getElementById('greeting')
获取到页面的一个p
元素的DOM对象,并使用常量paragraph
来代指它,然后通过alert()
弹出来。
p
元素的DOM对象有一个innerText
属性,存储着元素内部的文本,我们可以把上面代码的最后一句改为alert(paragraph.innerText)
来看一下效果。
<p id="greeting">hello, world!</p>
<button onclick="
const paragraph = document.getElementById('greeting');
alert(paragraph.innerText)
">click here</button>

可以看到我们确实获取到了p
元素中的文本内容。
你可以通过查询w3c
的官方文档来了解每一类DOM对象的方法和属性。
如何修改文本内容呢?我们又要用到神奇的=
。
paragraph.innerText = 'hi, world!'
这段代码可以将paragraph
的内部文字改为hi, world!
。
在常见的计算机语言中,=
的含义都不同于数学上等于,而更加接近于<=
,意思为赋值,即将等号右边的内容所代表的值,传递到左边。

我们在等号的后边使用了一个字面量'hi, world!'
,它所代表的值即是hi, world!
。=
将这个值传递到左边,覆盖p
元素的innerText
对象属性原本的值,于是便实现了p
元素内部文本内容的修改。
使用下面的代码可以看到整体的效果。
<p id="greeting">hello, world!</p>
<button onclick="
const paragraph = document.getElementById('greeting');
paragraph.innerText = 'hi, world!'
">change text</button>

修改元素样式
当然,除了修改元素的内容以外,我们还可以修改它的样式。
修改样式需要用到DOM对象的style
属性,用法与上面的innerText
非常类似。
将css
代码的字面量赋值给paragraph.style
,便可以改变元素的样式。
paragraph.style = 'color: white; background-color: red'

有些时候,我们并不希望将元素的整个样式改掉,而只是希望修改它的一些样式属性。
幸运的是,paragraph.style
也是一个DOM对象,几乎每一个可用的css
样式属性,都在这个DOM对象里以对象属性的形式存在,我们可以轻易的使用。
paragraph.style.width = '200px'

如果配合css
的类选择器,还可以通过向p
元素增加class
的方式来修改元素的样式。
.title {
text-align: center;
font-size: 50px;
background-color: black;
color: white;
}
paragraph.className = 'title'

添加和删除元素
除了丰富的对象属性,DOM还有很多的对象方法,通常用于把对象看作一个整体时的一些操作。
比如remove()
方法,可以用来删除元素。
paragraph.remove()

这个方法将会从页面把p
元素整体移除,不仅是元素内部的文字,也包含着元素在页面上所占的位置,以及我们对其设定的任何样式,都会消失不见,不留下任何痕迹,仿佛从来没有出现过一样。
添加元素会比删除相对复杂一些。
首先,我们要创建一个新元素,这需要使用docment.createElement()
。
const goodbye = document.createElement('p')
goodbye.innerText = 'Byebye!'
这时虽然元素被创建了,但是目前并没有被放置到页面的任何一个位置里。
这有点像在我们经营着一家饭店,目前后厨已经做好了菜,接下来还需要让服务员上菜。
所以接下来,我们需要使用document.getElementById()
,获取一个元素,也就是「服务员」,通过它来定位上菜的位置。
const greeting = document.getElementById('greeting')
现在我们可以选择将菜上到服务员的前面,或者后面。
greeting.before(goodbye)
greeting.after(goodbye)

还有的时候,我们是要向某个父元素中插入子元素。
const secendGreeting = document.createElement('i')
secendGreeting.innerText = 'How are you?'
const greeting = document.getElementById('greeting');
greeting.append(secendGreeting)

script标签
与我们在讲CSS
样式代码时提到的<style>
标签类似,脚本也有自己的专属标签<script>
。
使用<script>
标签,可以让我们的脚本代码与HTML
分离,提高了代码整体的可维护性。
比如,我们将上面插入子元素的脚本放到<script>
标签中:
<script>
const greeting = document.getElementById('greeting')
const secendGreeting = document.createElement('i')
secendGreeting.innerText = 'How are you?'
greeting.append(secendGreeting)
</script>
重新加载一下页面你会发现,我们并没有点击按钮,脚本就已经生效了。

在<script>
标签中的脚本,与在on******
监听属性中的不同,它不会等待某个事件发生才执行,而是在加载load时会立刻执行。
但如果仅仅是这样的话,<script>
标签好像就不能代替on******
监听属性,无法实现我们分离JS
和HTML
的初衷了。
没关系,我们会在下一节课解决这个问题。
<script>
标签应该写在哪呢?
理论上<script>
标签也是一种元信息,应该放到<head>
标签下,但实践中并非如此。
因为用于网页传输的http
协议,是一种「流式」传输协议。所谓流式传输,就像我们在线看视频的缓冲,我们并不需要等待整个视频下载完毕才能播放,而是可以边看边缓冲。

网页也是类似的,它不会等待整个网页的代码全部传输完毕才显示页面内容,而是传输了多少,就加载多少。
这样的一种「流式」的特性,与上述的「加载即执行」的特性,结合在一起带来一个问题:脚本中所要获取的DOM元素,很有可能还没有传输过来,导致脚本执行失败。

所以在实践中,经常可以看到包含了操作DOM元素脚本的<script>
标签,被放到<body>
的最最后。其他的辅助脚本,比如一些三方库会放到<head>
标签中。
如果你忘记了元信息和<header>
标签,你可以回到《06 层叠样式》一节中查看一下。
延伸阅读:脚本与程序
通过本节课的学习,我们可以看到对象,特别是DOM对象,是JS脚本中最为核心的概念。
这样的一种思想,被称作面向对象编程(OOP,object-oriented programming ),JS语言也被称作是一种面向对象的语言。

但是也经常会在网络看到质疑JS语言是编程语言的说法,这是怎么回事呢?
这是因为这里有一个经常会被人忽略,从而引起误会的点:脚本中描述的动作,实际上是浏览器做出的,而不是脚本自身。
也可以这样理解,脚本里面列出了诸多命令。而浏览器按照约定的顺序阅读脚本,对脚本的含义进行「解释」,从而做出对应的反应。

这里的约定便是「脚本语言」的语法。
这样的特性,使脚本区别于经典意义上的「程序」——即那些二进制的可执行文件,也就是那些可以被电脑硬件直接理解的家伙们。

直接控制硬件,而不需要「解释」的过程,使得程序的工作效率非常的高。
浏览器就是一个「程序」,我们从网络上下载回来的是它的可执行文件,而不是它的源代码。
敲黑板:「程序」是二进制可执行文件,不是代码!
但我们如果想要开发一个程序,也不太可能直接编写二进制,而是先写成适合人类阅读的语言,然后翻译成二进制。

这种语言就是「编程语言」,翻译的过程称作「编译」。
这样看来,「脚本语言」确实不是严格意义上的「编程语言Programming language」。
脚本语言本身并不执行什么,而是用于串联其他的程序,因此有时候也会被称为「胶水语言Glue language」。

但时至今日,编程语言与脚本语言的边界越来越模糊:
有一些非常流行脚本语言,例如JS,为了提高运行的效率,也是会被编译的。
最早在Chrome浏览器中引入的V8引擎,就采用了这样一种机制,它接收JS代码,会先进行编译,得到可运行的程序,然后再执行程序。

V8使得网页处理脚本的速度获得飞跃,也才使得Web App
网页应用成为现实,为网页技术的发展掀开了新的篇章。
而没有采用类似机制的IE6
,从此逐渐走上了末路。
基于V8引擎的NodeJs
,将JS语言带入到后端开发的世界里,它在高IO场景下的出色表现,使其如今以及成为最流行的后端技术路线之一。

我将本课程所有的代码托管在gitee
上了,你可以点击这个链接,或到gitee.com
上搜索弦五 网页开发入门,查看和获取本课程的全部代码。