网页开发入门|15 脚本(下)

使用脚本让我们有能力重写页面,或者通过也页面中的元素进行修改。

网页开发入门|15 脚本(下)

在上节课,我们了解了一些脚本的基本用法。

使用脚本可以让我们有能力重写页面,或者通过获取页面中元素对局部进行修改。但无论要重写页面,还是要进行局部修改,我们都需要借助DOM对象。

再次强调「对象」是一个很重要的概念。

找不到对象?
找不到对象?

可以把「对象」理解为虚拟世界中的物体或者东西,比如浏览器、页面、页面中的元素等等,甚至一个用户、一则浏览记录、一组数据都是「对象」。

每个对象都有一些字段field功能function,在js中统称为属性property

对象包含字段与功能
对象包含字段与功能

在更多的场合里,大家更习惯称对象的功能为方法函数,称字段为属性。我们的课程也将采取后者,即更加符合习惯的叫法。

为了区别我们之前已经学到的「标签属性attribute」和「样式属性property」,我们将使用「对象属性field」来称呼对象的字段。

三个不同的概念都被翻译为「属性」
三个不同的概念都被翻译为「属性」

比如,documentw3c标准中规定好的,用来指代整体页面的DOM对象,所以我们在脚本中可以直接使用它的属性或者方法。

document对象有一个属性URL,它的属性值便是当前页面的网址,在代码中可以通过document.URL来获取这个属性值。

配合alert()功能,可以把这个信息弹出来看一下:

alert(document.URL)
弹窗中显示了网址信息
弹窗中显示了网址信息

document对象还有一个方法getElementById(),完整的写法为document. getElementById(),通过它可以获取指定id的页面元素。具体使用方法,在上节课已经有所演示,这里不再重复。

可以看到,无论是属性还是方法,都可以在对象的后面紧接着使用.来进行访问access

通过.来访问docment的属性和方法
通过.来访问docment的属性和方法

大多数情况下,在方法名字的后面,我们会紧接着使用一对(),表明立刻执行这个方法,这个过程有的时候也称作方法的调用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!

在常见的计算机语言中,=的含义都不同于数学上等于,而更加接近于<=,意思为赋值,即将等号右边的内容所代表的值,传递到左边。

p对象
p对象

我们在等号的后边使用了一个字面量'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'
改变class类
改变class类

添加和删除元素

除了丰富的对象属性,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******监听属性,无法实现我们分离JSHTML的初衷了。

没关系,我们会在下一节课解决这个问题。

<script>标签应该写在哪呢?

理论上<script>标签也是一种元信息,应该放到<head>标签下,但实践中并非如此。

因为用于网页传输的http协议,是一种「流式」传输协议。所谓流式传输,就像我们在线看视频的缓冲,我们并不需要等待整个视频下载完毕才能播放,而是可以边看边缓冲。

在线看视频,可以看边缓冲,而无需下载整个视频文件
在线看视频,可以看边缓冲,而无需下载整个视频文件

网页也是类似的,它不会等待整个网页的代码全部传输完毕才显示页面内容,而是传输了多少,就加载多少。

这样的一种「流式」的特性,与上述的「加载即执行」的特性,结合在一起带来一个问题:脚本中所要获取的DOM元素,很有可能还没有传输过来,导致脚本执行失败。

如果将<script>放入<head>中,可以在浏览器控制台中看到一个异常
如果将<script>放入<head>中,可以在浏览器控制台中看到一个异常

所以在实践中,经常可以看到包含了操作DOM元素脚本的<script>标签,被放到<body>的最最后。其他的辅助脚本,比如一些三方库会放到<head>标签中。

如果你忘记了元信息和<header>标签,你可以回到《06 层叠样式》一节中查看一下。

延伸阅读:脚本与程序

通过本节课的学习,我们可以看到对象,特别是DOM对象,是JS脚本中最为核心的概念。

这样的一种思想,被称作面向对象编程(OOP,object-oriented programming ),JS语言也被称作是一种面向对象的语言。

面向对象(大雾)
面向对象(大雾)

但是也经常会在网络看到质疑JS语言是编程语言的说法,这是怎么回事呢?

这是因为这里有一个经常会被人忽略,从而引起误会的点:脚本中描述的动作,实际上是浏览器做出的,而不是脚本自身。

也可以这样理解,脚本里面列出了诸多命令。而浏览器按照约定的顺序阅读脚本,对脚本的含义进行「解释」,从而做出对应的反应。

脚本一句话,程序跑死马
脚本一句话,程序跑死马

这里的约定便是「脚本语言」的语法。

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

基于java的二进制文件(用16进制表示)
基于java的二进制文件(用16进制表示)

直接控制硬件,而不需要「解释」的过程,使得程序的工作效率非常的高。

浏览器就是一个「程序」,我们从网络上下载回来的是它的可执行文件,而不是它的源代码。

敲黑板:「程序」是二进制可执行文件,不是代码!

但我们如果想要开发一个程序,也不太可能直接编写二进制,而是先写成适合人类阅读的语言,然后翻译成二进制。

可执行文件 VS 源代码
可执行文件 VS 源代码

这种语言就是「编程语言」,翻译的过程称作「编译」。

这样看来,「脚本语言」确实不是严格意义上的「编程语言Programming language」。

脚本语言本身并不执行什么,而是用于串联其他的程序,因此有时候也会被称为「胶水语言Glue language」。

python也是一种脚本语言,非常适合用作胶水
python也是一种脚本语言,非常适合用作胶水

但时至今日,编程语言与脚本语言的边界越来越模糊:

有一些非常流行脚本语言,例如JS,为了提高运行的效率,也是会被编译的。

最早在Chrome浏览器中引入的V8引擎,就采用了这样一种机制,它接收JS代码,会先进行编译,得到可运行的程序,然后再执行程序。

v8引擎
v8引擎

V8使得网页处理脚本的速度获得飞跃,也才使得Web App网页应用成为现实,为网页技术的发展掀开了新的篇章。

而没有采用类似机制的IE6,从此逐渐走上了末路。

基于V8引擎的NodeJs,将JS语言带入到后端开发的世界里,它在高IO场景下的出色表现,使其如今以及成为最流行的后端技术路线之一。

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