写点什么

JavaScript 学习(二)

作者:Studying_swz
  • 2022-11-08
    天津
  • 本文字数:15021 字

    阅读完需:约 49 分钟

JavaScript学习(二)

5、面向对象编程

面向对象编程


JavaScript 的面向对象编程和大多数其他语言如 Java、C#的面向对象编程都不太一样。如果你熟悉 Java 或 C#,很好,你一定明白面向对象的两个基本概念:


  1. 类:类是对象的类型模板,例如,定义 Student 类来表示学生,类本身是一种类型,Student 表示学生类型,但不表示任何具体的某个学生;

  2. 实例:实例是根据类创建的对象,例如,根据 Student 类可以创建出 xiaoming 、xiaohong 、 xiaojun 等多个实例,每个实例表示一个具体的学生,他们全都属于 Student 类型。


所以,类和实例是大多数面向对象编程语言的基本概念。不过,在 JavaScript 中,这个概念需要改一改。JavaScript 不区分类和实例的概念,而是通过原型(prototype)来实现面向对象编程。


原型是指当我们想要创建 xiaoming 这个具体的学生时,我们并没有一个 Student 类型可用。那怎么办?恰好有这么一个现成的对象:


var robot = {  name: 'Robot',  height: 1.6,  run: function () {    console.log(this.name + ' is running...');  }};
复制代码


我们看这个 robot 对象有名字,有身高,还会跑,有点像小明,干脆就根据它来“创建”小明得了!于是我们把它改名为 Student ,然后创建出 xiaoming


var Student = {  name: 'Robot',  height: 1.2,  run: function () {    console.log(this.name + ' is running...');  }};var xiaoming = {  name: '小明'};xiaoming.__proto__ = Student;
复制代码


注意最后一行代码把 xiaoming 的原型指向了对象Student,看上去xiaoming仿佛是从Student 继承下来的:


xiaoming.name; // '小明'xiaoming.run(); // 小明 is running...
复制代码


xiaoming 有自己的 name 属性,但并没有定义run()方法。不过,由于小明是从 Student 继承而来,只要 Student 有 run() 方法, xiaoming 也可以调用:JavaScript 的原型链和 Java 的 Class 区别就在,它没有“Class”的概念,所有对象都是实例,所谓继承关系不过是把一个对象的原型指向另一个对象而已


如果你把 xiaoming的原型指向其他对象:


var Bird = {  fly: function () {    console.log(this.name + ' is flying...');  }};xiaoming.__proto__ = Bird;
复制代码


现在 xiaoming 已经无法 run() 了,他已经变成了一只鸟:


xiaoming.fly(); // 小明 is flying...
复制代码


在 JavaScrip 代码运行时期,你可以把 xiaoming Student变成 Bird ,或者变成任何对象。


class 继承


在上面的章节中我们看到了 JavaScript 的对象模型是基于原型实现的,特点是简单,缺点是理解起来比传统的类-实例模型要困难,最大的缺点是继承的实现需要编写大量代码,并且需要正确实现原型链。


有没有更简单的写法?有!新的关键字 class 从 ES6 开始正式被引入到 JavaScript 中。 class 的目的就是让定义类更简单。


我们先回顾用函数实现 Student 的方法:


function Student(name) {  this.name = name;}// 现在要给这个Student新增一个方法Student.prototype.hello = function () {  alert('Hello, ' + this.name + '!');}
复制代码


如果用新的 class 关键字来编写 Student ,可以这样写:


class Student {  constructor(name) {    this.name = name;  }  hello() {    alert('Hello, ' + this.name + '!');  }}
复制代码


比较一下就可以发现, class 的定义包含了构造函数constructor 和定义在原型对象上的函数hello() (注意没有 function 关键字),这样就避免了 Student.prototype.hello = function () {...} 这样分散的代码。最后,创建一个 Student 对象代码和前面章节完全一样:


var xiaoming = new Student('小明');xiaoming.hello();
复制代码


class 继承


用 class 定义对象的另一个巨大的好处是继承更方便了。想一想我们从 Student 派生一个PrimaryStudent 需要编写的代码量。现在,原型继承的中间对象,原型对象的构造函数等等都不需要考虑了,直接通过 extends 来实现:


class PrimaryStudent extends Student {  constructor(name, grade) {    super(name); // 记得用super调用父类的构造方法!    this.grade = grade;  }  myGrade() {    alert('I am at grade ' + this.grade);  }}
复制代码

6、操作 BOM

6.1、浏览器

由于 JavaScript 的出现就是为了能在浏览器中运行,所以,浏览器自然是 JavaScript 开发者必须要关注的。目前主流的浏览器分这么几种:


  • IE 6~11:国内用得最多的 IE 浏览器,历来对 W3C 标准支持差。从 IE10 开始支持 ES6 标准;

  • Chrome:Google 出品的基于 Webkit 内核浏览器,内置了非常强悍的 JavaScript 引擎——V8。由于 Chrome 一经安装就时刻保持自升级,所以不用管它的版本,最新版早就支持 ES6 了;

  • Safari:Apple 的 Mac 系统自带的基于 Webkit 内核的浏览器,从 OS X 10.7 Lion 自带的 6.1 版本开始支持 ES6,目前最新的 OS X 10.11 El Capitan 自带的 Safari 版本是 9.x,早已支持 ES6;

  • Firefox:Mozilla 自己研制的 Gecko 内核和 JavaScript 引擎 OdinMonkey。早期的 Firefox 按版本发布,后来终于聪明地学习 Chrome 的做法进行自升级,时刻保持最新;

  • 移动设备上目前 iOS 和 Android 两大阵营分别主要使用 Apple 的 Safari 和 Google 的 Chrome,由于两者都是 Webkit 核心,结果 HTML5 首先在手机上全面普及(桌面绝对是 Microsoft 拖了后腿),对 JavaScript 的标准支持也很好,最新版本均支持 ES6。


其他浏览器如 Opera 等由于市场份额太小就被自动忽略了。


另外还要注意识别各种国产浏览器,如某某安全浏览器,某某旋风浏览器,它们只是做了一个壳,其核心调用的是 IE,也有号称同时支持 IE 和 Webkit 的“双核”浏览器。不同的浏览器对 JavaScript 支持的差异主要是,有些 API 的接口不一样,比如 AJAX,File 接口。对于 ES6 标准,不同的浏览器对各个特性支持也不一样。在编写 JavaScript 的时候,就要充分考虑到浏览器的差异,尽量让同一份 JavaScript 代码能运行在不同的浏览器中。


JavaScript 可以获取浏览器提供的很多对象,并进行操作。

6.2、window

window 对象不但充当全局作用域,而且表示浏览器窗口。window 对象有 innerWidth innerHeight 属性,可以获取浏览器窗口的内部宽度和高度。内部宽高是指除去菜单栏、工具栏、边框等占位元素后,用于显示网页的净宽高。兼容性:IE<=8 不支持。


'use strict';// 可以调整浏览器窗口大小试试:console.log('window inner size: ' + window.innerWidth + ' x ' +window.innerHeight);
复制代码


对应的,还有一个 outerWidth 和 outerHeight 属性,可以获取浏览器窗口的整个宽高。

6.3、navigator

navigator 对象表示浏览器的信息,最常用的属性包括:


  • navigator.appName:浏览器名称;

  • navigator.appVersion:浏览器版本;

  • navigator.language:浏览器设置的语言;

  • navigator.platform:操作系统类型;

  • navigator.userAgent:浏览器设定的User-Agent字符串。


'use strict';console.log('appName = ' + navigator.appName);console.log('appVersion = ' + navigator.appVersion);console.log('language = ' + navigator.language);console.log('platform = ' + navigator.platform);console.log('userAgent = ' + navigator.userAgent);
复制代码


请注意, navigator 的信息可以很容易地被用户修改,所以 JavaScript 读取的值不一定是正确的。很多初学者为了针对不同浏览器编写不同的代码,喜欢用 if 判断浏览器版本,例如:


var width;if (getIEVersion(navigator.userAgent) < 9) {  width = document.body.clientWidth;} else {  width = window.innerWidth;}
复制代码


但这样既可能判断不准确,也很难维护代码。正确的方法是充分利用 JavaScript 对不存在属性返回undefined 的特性,直接用短路运算符||计算:


var width = window.innerWidth || document.body.clientWidth
复制代码

6.4、screen

screen 对象表示屏幕的信息,常用的属性有:


  • screen.width:屏幕宽度,以像素为单位;

  • screen.height:屏幕高度,以像素为单位;

  • screen.colorDepth:返回颜色位数,如 8、16、24。


console.log('Screen size = ' + screen.width + ' x ' + screen.height);
复制代码

6.5、location

location 对象表示当前页面的 URL 信息。例如,一个完整的 URL:


 http://www.example.com:8080/path/index.html?a=1&b=2#TOP
复制代码


可以用location.href获取。要获得 URL 各个部分的值,可以这么写:


location.protocol; // 'http'location.host; // 'www.example.com'location.port; // '8080'location.pathname; // '/path/index.html'location.search; // '?a=1&b=2'location.hash; // 'TOP'
复制代码


要加载一个新页面,可以调用location.assign()。如果要重新加载当前页面,调用location.reload() 方法非常方便。


location.reload();location.assign('https://blog.kuangstudy.com/'); // 设置一个新的URL地址
复制代码

6.6、document

document 对象表示当前页面。由于 HTML 在浏览器中以DOM形式表示为树形结构, document 对象就是整个 DOM 树的根节点。document 的 title 属性是从 HTML 文档中的 xxx 读取的,但是可以动态改变:


document.title = '狂神说Java!';
复制代码


请观察浏览器窗口标题的变化。要查找 DOM 树的某个节点,需要从document对象开始查找。最常用的查找是根据 ID 和 Tag Name。


我们先准备 HTML 数据:


<dl id="code-menu" style="border:solid 1px #ccc;padding:6px;">  <dt>Java</dt>  <dd>Spring</dd>  <dt>Python</dt>  <dd>Django</dd>  <dt>Linux</dt>  <dd>Docker</dd></dl>
复制代码


用 document 对象提供的 getElementById() 和 getElementsByTagName() 可以按 ID 获得一个 DOM 节点和按 Tag 名称获得一组 DOM 节点:


var menu = document.getElementById('code-menu');var drinks = document.getElementsByTagName('dt');var i, s;s = '提供的饮料有:';for (i=0; i<drinks.length; i++) {  s = s + drinks[i].innerHTML + ',';}console.log(s);
复制代码


document 对象还有一个 cookie 属性,可以获取当前页面的 Cookie。


Cookie 是由服务器发送的 key-value 标示符。因为 HTTP 协议是无状态的,但是服务器要区分到底是哪个用户发过来的请求,就可以用 Cookie 来区分。当一个用户成功登录后,服务器发送一个 Cookie 给浏览器,例如 user=ABC123XYZ(加密的字符串)... ,此后,浏览器访问该网站时,会在请求头附上这个 Cookie,服务器根据 Cookie 即可区分出用户。


Cookie 还可以存储网站的一些设置,例如,页面显示的语言等等。


JavaScript 可以通过document.cookie读取到当前页面的 Cookie:


document.cookie; // 'v=123; remember=true; prefer=zh'
复制代码


由于 JavaScript 能读取到页面的 Cookie,而用户的登录信息通常也存在 Cookie 中,这就造成了巨大的安全隐患,这是因为在 HTML 页面中引入第三方的 JavaScript 代码是允许的:


<!--www.example.com--><html>  <head>    <script src="http://www.foo.com/jquery.js"></script>  </head>...</html>
复制代码


如果引入的第三方的 JavaScript 中存在恶意代码,则www.foo.com网站将直接获取到www.example.com 网站的用户登录信息。为了解决这个问题,服务器在设置 Cookie 时可以使用 httpOnly ,设定了 httpOnly 的 Cookie 将不能被 JavaScript 读取。这个行为由浏览器实现,主流浏览器均支持 httpOnly选项,IE 从 IE6 SP1 开始支持。为了确保安全,服务器端在设置 Cookie 时,应该始终坚持使用 httpOnly

6.7 history

history 对象保存了浏览器的历史记录,JavaScript 可以调用 history 对象的 back() forward () ,相当于用户点击了浏览器的“后退”或“前进”按钮。


这个对象属于历史遗留对象,对于现代 Web 页面来说,由于大量使用 AJAX 和页面交互,简单粗暴地调用 history.back() 可能会让用户感到非常愤怒。


新手开始设计 Web 页面时喜欢在登录页登录成功时调用 history.back() ,试图回到登录前的页面。这是一种错误的方法。


任何情况,你都不应该使用 history 这个对象了。

7、操作 DOM

7.1、选择器

由于 HTML 文档被浏览器解析后就是一棵 DOM 树,要改变 HTML 的结构,就需要通过 JavaScript 来操作 DOM。始终记住 DOM 是一个树形结构。操作一个 DOM 节点实际上就是这么几个操作:


  • 更新:更新该 DOM 节点的内容,相当于更新了该 DOM 节点表示的 HTML 的内容;

  • 遍历:遍历该 DOM 节点下的子节点,以便进行进一步操作;

  • 添加:在该 DOM 节点下新增一个子节点,相当于动态增加了一个 HTML 节点;

  • 删除:将该节点从 HTML 中删除,相当于删掉了该 DOM 节点的内容以及它包含的所有子节点。


在操作一个 DOM 节点前,我们需要通过各种方式先拿到这个 DOM 节点。最常用的方法是document.getElementById() document.getElementsByTagName() ,以及 CSS 选择器document.getElementsByClassName() 。由于 ID 在 HTML 文档中是唯一的,所以 document.getElementById() 可以直接定位唯一的一个 DOM 节点。 document.getElementsByTagName() 和 document.getElementsByClassName() 总是返回一组 DOM 节点。要精确地选择 DOM,可以先定位父节点,再从父节点开始选择,以缩小范围。例如:


// 返回ID为'test'的节点:var test = document.getElementById('test');// 先定位ID为'test-table'的节点,再返回其内部所有tr节点:var trs = document.getElementById('test-table').getElementsByTagName('tr');// 先定位ID为'test-div'的节点,再返回其内部所有class包含red的节点:var reds = document.getElementById('testdiv').getElementsByClassName('red');// 获取节点test下的所有直属子节点:var cs = test.children;// 获取节点test下第一个、最后一个子节点:var first = test.firstElementChild;var last = test.lastElementChild;
复制代码


第二种方法是使用 querySelector()querySelectorAll() ,需要了解 selector 语法,然后使用条件来获取节点,更加方便:


// 通过querySelector获取ID为q1的节点:var q1 = document.querySelector('#q1');// 通过querySelectorAll获取q1节点内的符合条件的所有节点:var ps = q1.querySelectorAll('div.highlighted > p');
复制代码


注意:低版本的 IE<8 不支持 querySelector 和 querySelectorAll 。IE8 仅有限支持。

7.2、更新 DOM

拿到一个 DOM 节点后,我们可以对它进行更新。可以直接修改节点的文本,方法有两种:一种是修改innerHTML属性,这个方式非常强大,不但可以修改一个 DOM 节点的文本内容,还可以直接通过 HTML 片段修改 DOM 节点内部的子树:


// 获取<p id="p-id">...</p>var p = document.getElementById('p-id');// 设置文本为abc:p.innerHTML = 'ABC'; // <p id="p-id">ABC</p>// 设置HTML:p.innerHTML = 'ABC <span style="color:red">RED</span> XYZ';// <p>...</p>的内部结构已修改
复制代码


innerHTML 时要注意,是否需要写入 HTML。如果写入的字符串是通过网络拿到了,要注意对字符编码来避免XSS攻击。


第二种是修改innerText属性,这样可以自动对字符串进行 HTML 编码,保证无法设置任何 HTML 标签:


// 获取<p id="p-id">...</p>var p = document.getElementById('p-id');// 设置文本:p.innerText = '<script>alert("Hi")</script>';// HTML被自动编码,无法设置一个<script>节点:// <p id="p-id">&lt;script&gt;alert("Hi")&lt;/script&gt;</p>
复制代码


修改 CSS 也是经常需要的操作。DOM 节点的 style 属性对应所有的 CSS,可以直接获取或设置。因为 CSS 允许 font-size 这样的名称,但它并非 JavaScript 有效的属性名,所以需要在 JavaScript 中改写为驼峰式命名 fontSize


// 获取<p id="p-id">...</p>var p = document.getElementById('p-id');// 设置CSS:p.style.color = '#ff0000';p.style.fontSize = '20px';p.style.paddingTop = '2em';
复制代码

7.3、插入 DOM

appendChild


当我们获得了某个 DOM 节点,想在这个 DOM 节点内插入新的 DOM,应该如何做?如果这个 DOM 节点是空的,例如, ,那么,直接使用 innerHTML = 'child'`就可以修改 DOM 节点的内容,相当于“插入”了新的 DOM 节点。


如果这个 DOM 节点不是空的,那就不能这么做,因为 innerHTML 会直接替换掉原来的所有子节点。有两个办法可以插入新的节点。一个是使用 appendChild ,把一个子节点添加到父节点的最后一个子节点。例如:


<!-- HTML结构 --><p id="js">JavaScript</p><div id="list">  <p id="java">Java</p>  <p id="python">Python</p>  <p id="scheme">Scheme</p></div>
复制代码


JavaScript 添加到的最后一项:


var  js = document.getElementById('js'),  list = document.getElementById('list');list.appendChild(js);
复制代码


现在,HTML 结构变成了这样:


<!-- HTML结构 --><div id="list">  <p id="java">Java</p>  <p id="python">Python</p>  <p id="scheme">Scheme</p>  <p id="js">JavaScript</p></div>
复制代码


因为我们插入的 js 节点已经存在于当前的文档树,因此这个节点首先会从原先的位置删除,再插入到新的位置。更多的时候我们会从零创建一个新的节点,然后插入到指定位置:


var  list = document.getElementById('list'),  haskell = document.createElement('p');haskell.id = 'haskell';haskell.innerText = 'Haskell';list.appendChild(haskell);
复制代码


这样我们就动态添加了一个新的节点:


<!-- HTML结构 --><div id="list">  <p id="java">Java</p>  <p id="python">Python</p>  <p id="scheme">Scheme</p>  <p id="haskell">Haskell</p></div>
复制代码


动态创建一个节点然后添加到 DOM 树中,可以实现很多功能。举个例子,下面的代码动态创建了一个节点,然后把它添加到 节点的末尾,这样就动态地给文档添加了新的 CSS 定义:


var d = document.createElement('style');d.setAttribute('type', 'text/css');d.innerHTML = 'p { color: red }';//head 头部标签document.getElementsByTagName('head')[0].appendChild(d);
复制代码


可以在 Chrome 的控制台执行上述代码,观察页面样式的变化。


insertBefore


如果我们要把子节点插入到指定的位置怎么办?可以使用parentElement.insertBefore(newElement, referenceElement); ,子节点会插入到referenceElement 之前。还是以上面的 HTML 为例,假定我们要把 Haskell 插入到 Python之前:


<!-- HTML结构 --><div id="list">  <p id="java">Java</p>  <p id="python">Python</p>  <p id="scheme">Scheme</p></div>
复制代码


可以这么写:


var  list = document.getElementById('list'),  ref = document.getElementById('python'),  haskell = document.createElement('p');haskell.id = 'haskell';haskell.innerText = 'Haskell';list.insertBefore(haskell, ref);
复制代码


新的 HTML 结构如下:


<!-- HTML结构 --><div id="list">  <p id="java">Java</p>  <p id="haskell">Haskell</p>  <p id="python">Python</p>  <p id="scheme">Scheme</p></div>
复制代码

7.4、删除 DOM

删除一个 DOM 节点就比插入要容易得多。要删除一个节点,首先要获得该节点本身以及它的父节点,然后,调用父节点的 removeChild 把自己删掉:


// 拿到待删除节点:var self = document.getElementById('to-be-removed');// 拿到父节点:var parent = self.parentElement;// 删除:var removed = parent.removeChild(self);removed === self; // true
复制代码


注意到删除后的节点虽然不在文档树中了,但其实它还在内存中,可以随时再次被添加到别的位置。当你遍历一个父节点的子节点并进行删除操作时,要注意, children 属性是一个只读属性,并且它在子节点变化时会实时更新。例如,对于如下 HTML 结构:


<div id="parent">  <p>First</p>  <p>Second</p></div>
复制代码


当我们用如下代码删除子节点时:


var parent = document.getElementById('parent');parent.removeChild(parent.children[0]);parent.removeChild(parent.children[1]); // <-- 浏览器报错
复制代码


浏览器报错: parent.children[1] 不是一个有效的节点。原因就在于,当 First 节点被删除后, parent.children 的节点数量已经从 2 变为了 1,索引 [1] 已经不存在了。因此,删除多个节点时,要注意 children 属性时刻都在变化。

8、操作表单

8.1、回顾

用 JavaScript 操作表单和操作 DOM 是类似的,因为表单本身也是 DOM 树。不过表单的输入框、下拉框等可以接收用户输入,所以用 JavaScript 来操作表单,可以获得用户输入的内容,或者对一个输入框设置新的内容。


HTML 表单的输入控件主要有以下几种:


  • 文本框,对应的 <input type="text"> ,用于输入文本;

  • 口令框,对应的 <input type="password"> ,用于输入口令;

  • 单选框,对应的<input type="radio">,用于选择一项;

  • 复选框,对应的 <input type="checkbox"> ,用于选择多项;

  • 下拉框,对应的<select>,用于选择一项;

  • 隐藏文本,对应的 <input type="hidden"> ,用户不可见,但表单提交时会把隐藏文本发送到服务器。

8.2、获取值

如果我们获得了一个 <input> 节点的引用,就可以直接调用 value 获得对应的用户输入值:


// <input type="text" id="email">var input = document.getElementById('email');input.value; // '用户输入的值'
复制代码


这种方式可以应用于 text passwordhidden 以及 select 。但是,对于单选框和复选框, value 属性返回的永远是 HTML 预设的值,而我们需要获得的实际是用户是否“勾上了”选项,所以应该用 checked 判断:


// <label><input type="radio" name="weekday" id="monday" value="1">Monday</label>// <label><input type="radio" name="weekday" id="tuesday" value="2">Tuesday</label>var mon = document.getElementById('monday');var tue = document.getElementById('tuesday');mon.value; // '1'tue.value; // '2'mon.checked; // true或者falsetue.checked; // true或者false
复制代码

8.3、设置值

设置值和获取值类似,对于 textpasswordhidden 以及select,直接设置 value 就可以


// <input type="text" id="email">var input = document.getElementById('email');input.value = 'test@example.com'; // 文本框的内容已更
复制代码


对于单选框和复选框,设置 checkedtruefalse 即可。

8.4、提交表单

最后,JavaScript 可以以两种方式来处理表单的提交(AJAX 方式在后面章节介绍)。方式一是通过 <form> 元素的 submit() 方法提交一个表单,例如,响应一个 buttonclick 事件,在 JavaScript 代码中提交表单:


<!-- HTML --><form id="test-form">  <input type="text" name="test">  <button type="button" onclick="doSubmitForm()">Submit</button></form><script>function doSubmitForm() {  var form = document.getElementById('test-form');  // 可以在此修改form的input...  // 提交form:  form.submit();}</script>
复制代码


这种方式的缺点是扰乱了浏览器对 form 的正常提交。浏览器默认点击 <button type="submit"> 时提交表单,或者用户在最后一个输入框按回车键。因此,第二种方式是响应 <form> 本身的onsubmit 事件,在提交 form 时作修改:


<!-- HTML --><form id="test-form" onsubmit="return checkForm()">  <input type="text" name="test">  <button type="submit">Submit</button></form><script>function checkForm() {  var form = document.getElementById('test-form');  // 可以在此修改form的input...  // 继续下一步:  return true;}</script>
复制代码


注意要 return true 来告诉浏览器继续提交,如果return false,浏览器将不会继续提交 form,这种情况通常对应用户输入有误,提示用户错误信息后终止提交 form。在检查和修改 <input> 时,要充分利用 <input type="hidden"> 来传递数据。例如,很多登录表单希望用户输入用户名和口令,但是,安全考虑,提交表单时不传输明文口令,而是口令的 MD5。普通 JavaScript 开发人员会直接修改 <input>


<!-- HTML --><form id="login-form" method="post" onsubmit="return checkForm()">  <input type="text" id="username" name="username">  <input type="password" id="password" name="password">  <button type="submit">Submit</button></form><script src="https://cdn.bootcss.com/blueimp-md5/2.10.0/js/md5.min.js"></script><script>function checkForm() {  var pwd = document.getElementById('password');  // 把用户输入的明文变为MD5:  pwd.value = md5(pwd.value);  // 继续下一步:  return true;}</script>
复制代码


这个做法看上去没啥问题,但用户输入了口令提交时,口令框的显示会突然从几个 * 变成 32 个* (因为 MD5 有 32 个字符)。要想不改变用户的输入,可以利用<input type="hidden">实现:


<script src="https://cdn.bootcss.com/blueimp-md5/2.10.0/js/md5.min.js"></script><!-- HTML --><form id="login-form" method="post" onsubmit="return checkForm()">  <input type="text" id="username" name="username">  <input type="password" id="input-password">  <input type="hidden" id="md5-password" name="password">  <button type="submit">Submit</button></form><script>function checkForm() {  var input_pwd = document.getElementById('input-password');  var md5_pwd = document.getElementById('md5-password');  // 把用户输入的明文变为MD5:  md5_pwd.value = md5(input_pwd.value);  // 继续下一步:  return true;}</script>
复制代码

9、jQuery

9.1、什么是 JQuery

你可能听说过 jQuery,它名字起得很土,但却是 JavaScript 世界中使用最广泛的一个库。江湖传言,全世界大约有 80~90%的网站直接或间接地使用了 jQuery。鉴于它如此流行,又如此好用,所以每一个入门 JavaScript 的前端工程师都应该了解和学习它。jQuery 这么流行,肯定是因为它解决了一些很重要的问题。实际上,jQuery 能帮我们干这些事情:


  • 消除浏览器差异:你不需要自己写冗长的代码来针对不同的浏览器来绑定事件,编写 AJAX 等代码;

  • 简洁的操作 DOM 的方法:写 $('#test') 肯定比document.getElementById('test') 来得简洁;

  • 轻松实现动画、修改 CSS 等各种操作。


jQuery 的理念“Write Less, Do More“,让你写更少的代码,完成更多的工作!官网:https://jquery.com/jQuery 只是一个 jquery-xxx.js 文件,但你会看到有 compressed(已压缩)和 uncompressed(未压缩)两种版本,使用时完全一样,但如果你想深入研究 jQuery 源码,那就用 uncompressed 版本。使用 jQuery 只需要在页面的 head 引入 jQuery 文件即可:


<html><head>  <script src="//code.jquery.com/jquery-1.11.3.min.js"></script>  ...</head><body><a id="test-link" href="#0">点我试试</a><script>// 获取超链接的jQuery对象:var a = $('#test-link');a.on('click', function () {  alert('Hello!');});
// 方式二a.click(function () { alert('Hello!');});</script></body></html>
复制代码


公式: $(selector).action()


  • 美元符号定义 jQuery

  • 选择符(selector)"查询"和"查找" HTML 元素

  • jQuery 的 action() 执行对元素的操作

9.2、选择器

了解


选择器是 jQuery 的核心。为什么 jQuery 要发明选择器?回顾一下 DOM 操作中我们经常使用的代码:


// 按ID查找:var a = document.getElementById('dom-id');// 按tag查找:var divs = document.getElementsByTagName('div');// 查找<p class="red">:var ps = document.getElementsByTagName('p');// 过滤出class="red":// TODO:// 查找<table class="green">里面的所有<tr>:var table = ...for (var i=0; i<table.children; i++) {  // TODO: 过滤出<tr>}
复制代码


这些代码实在太繁琐了!jQuery 的选择器就是帮助我们快速定位到一个或多个 DOM 节点。


按 ID 查找如果某个 DOM 节点有 id 属性,利用 jQuery 查找如下:


// 查找<div id="abc">:var div = $('#abc');
复制代码


按 tag 查找按 tag 查找只需要写上 tag 名称就可以了:


var ps = $('p'); // 返回所有<p>节点ps.length; // 数一数页面有多少个<p>节点
复制代码


按 class 查找按 class 查找注意在 class 名称前加一个.


var a = $('.red'); // 所有节点包含`class="red"`都将返回// 例如:// <div class="red">...</div>// <p class="green red">...</p>
复制代码


按属性查找一个 DOM 节点除了idclass 外还可以有很多属性,很多时候按属性查找会非常方便,比如在一个表单中按属性来查找:


var email = $('[name=email]'); // 找出<??? name="email">var passwordInput = $('[type=password]'); // 找出<??? type="password">var a = $('[items="A B"]'); // 找出<??? items="A B">
复制代码


当属性的值包含空格等特殊字符时,需要用双引号括起来。按属性查找还可以使用前缀查找或者后缀查找:


var icons = $('[name^=icon]'); // 找出所有name属性值以icon开头的DOM// 例如: name="icon-1", name="icon-2"var names = $('[name$=with]'); // 找出所有name属性值以with结尾的DOM// 例如: name="startswith", name="endswith"
复制代码

9.3、操作 DOM

修改 Text 和 HTML


jQuery 对象的 text() 和 html() 方法分别获取节点的文本和原始 HTML 文本,例如,如下的 HTML 结构:


<!-- HTML结构 --><ul id="test-ul">  <li class="js">JavaScript</li>  <li name="book">Java &amp; JavaScript</li></ul>
复制代码


分别获取文本和 HTML:


$('#test-ul li[name=book]').text(); // 'Java & JavaScript'$('#test-ul li[name=book]').html(); // 'Java &amp; JavaScript'
复制代码


如何设置文本或 HTML?jQuery 的 API 设计非常巧妙:无参数调用 text() 是获取文本,传入参数就变成设置文本,HTML 也是类似操作,自己动手试试:


var j1 = $('#test-ul li.js');var j2 = $('#test-ul li[name=book]');j1.html('<span style="color: red">JavaScript</span>');j2.text('JavaScript & ECMAScript');
复制代码


一个 jQuery 对象可以包含 0 个或任意个 DOM 对象,它的方法实际上会作用在对应的每个 DOM 节点上。在上面的例子中试试:


$('#test-ul li').text('JS'); // 是不是两个节点都变成了JS?
复制代码


修改 CSS


jQuery 对象有“批量操作”的特点,这用于修改 CSS 实在是太方便了。考虑下面的 HTML 结构:


<!-- HTML结构 --><ul id="test-css">  <li class="lang dy"><span>JavaScript</span></li>  <li class="lang"><span>Java</span></li>  <li class="lang dy"><span>Python</span></li>  <li class="lang"><span>Swift</span></li>  <li class="lang dy"><span>Scheme</span></li></ul>
复制代码


要高亮显示动态语言,调用 jQuery 对象的css('name', 'value')方法,我们用一行语句实现:


$('#test-css li.dy>span').css('background-color', '#ffd351').css('color','red');
复制代码


注意,jQuery 对象的所有方法都返回一个 jQuery 对象(可能是新的也可能是自身),这样我们可以进行链式调用,非常方便。jQuery 对象的css()方法可以这么用:


var div = $('#test-div');div.css('color'); // '#000033', 获取CSS属性div.css('color', '#336699'); // 设置CSS属性div.css('color', ''); // 清除CSS属性
复制代码


css() 方法将作用于 DOM 节点的style 属性,具有最高优先级。如果要修改 class 属性,可以用 jQuery 提供的下列方法:


var div = $('#test-div');div.hasClass('highlight'); // false, class是否包含highlightdiv.addClass('highlight'); // 添加highlight这个classdiv.removeClass('highlight'); // 删除highlight这个class
复制代码


显示和隐藏 DOM


要隐藏一个 DOM,我们可以设置 CSS 的display属性为 none ,利用 css() 方法就可以实现。不过,要显示这个 DOM 就需要恢复原有的 display 属性,这就得先记下来原有的 display 属性到底是 block 还是 inline 还是别的值。考虑到显示和隐藏 DOM 元素使用非常普遍,jQuery 直接提供 show() 和 hide() 方法,我们不用关心它是如何修改 display 属性的,总之它能正常工作:


var a = $('a[target=_blank]');a.hide(); // 隐藏a.show(); // 显示
复制代码


注意,隐藏 DOM 节点并未改变 DOM 树的结构,它只影响 DOM 节点的显示。这和删除 DOM 节点是不同的。


获取 DOM 信息


利用 jQuery 对象的若干方法,我们直接可以获取 DOM 的高宽等信息,而无需针对不同浏览器编写特定代码:


// 浏览器可视窗口大小:$(window).width(); // 800$(window).height(); // 600// HTML文档大小:$(document).width(); // 800$(document).height(); // 3500// 某个div的大小:var div = $('#test-div');div.width(); // 600div.height(); // 300div.width(400); // 设置CSS属性 width: 400px,是否生效要看CSS是否有效div.height('200px'); // 设置CSS属性 height: 200px,是否生效要看CSS是否有效
复制代码


attr() removeAttr()方法用于操作 DOM 节点的属性:


// <div id="test-div" name="Test" start="1">...</div>var div = $('#test-div');div.attr('data'); // undefined, 属性不存在div.attr('name'); // 'Test'div.attr('name', 'Hello'); // div的name属性变为'Hello'div.removeAttr('name'); // 删除name属性div.attr('name'); // undefined
复制代码


操作表单


对于表单元素,jQuery 对象统一提供 val() 方法获取和设置对应的 value 属性:


/*  <input id="test-input" name="email" value="">  <select id="test-select" name="city">  <option value="BJ" selected>Beijing</option>  <option value="SH">Shanghai</option>  <option value="SZ">Shenzhen</option>  </select>  <textarea id="test-textarea">Hello</textarea>*/var  input = $('#test-input'),  select = $('#test-select'),  textarea = $('#test-textarea');  input.val(); // 'test'input.val('abc@example.com'); // 文本框的内容已变为abc@example.com
select.val(); // 'BJ'select.val('SH'); // 选择框已变为Shanghai
textarea.val(); // 'Hello'textarea.val('Hi'); // 文本区域已更新为'Hi'
复制代码


可见,一个 val() 就统一了各种输入框的取值和赋值的问题。


添加 DOM


要添加新的 DOM 节点,除了通过 jQuery 的 html() 这种暴力方法外,还可以用 append() 方法,例如:


<div id="test-div">  <ul>    <li><span>JavaScript</span></li>    <li><span>Python</span></li>    <li><span>Swift</span></li>  </ul></div>
复制代码


如何向列表新增一个语言?首先要拿到<ul>节点:


var ul = $('#test-div>ul');
复制代码


然后,调用 append() 传入 HTML 片段:


ul.append('<li><span>Haskell</span></li>');
复制代码


append() 把 DOM 添加到最后,prepend()则把 DOM 添加到最前。如果要把新节点插入到指定位置,例如,JavaScript 和 Python 之间,那么,可以先定位到 JavaScript,然后用 after() 方法:


var js = $('#test-div>ul>li:first-child');js.after('<li><span>Lua</span></li>');
复制代码


也就是说,同级节点可以用 after() 或者 before() 方法。


删除节点


要删除 DOM 节点,拿到 jQuery 对象后直接调用 remove() 方法就可以了。如果 jQuery 对象包含若干 DOM 节点,实际上可以一次删除多个 DOM 节点:


var li = $('#test-div>ul>li');li.remove(); // 所有<li>全被删除
复制代码

9.4、事件

jQuery 能够绑定的事件主要包括:鼠标事件 click: 鼠标单击时触发; dblclick:鼠标双击时触发; mouseenter:鼠标进入时触发; mouseleave:鼠标移出时触发; mousemove:鼠标在 DOM 内部移动时触发; hover:鼠标进入和退出时触发两个函数,相当于 mouseenter 加上 mouseleave。键盘事件键盘事件仅作用在当前焦点的 DOM 上,通常是 和 。(打不出来了!!)keydown:键盘按下时触发; keyup:键盘松开时触发; keypress:按一次键后触发。其他事件 focus:当 DOM 获得焦点时触发; blur:当 DOM 失去焦点时触发; change:当 、 或 的内容改变``时触发; submit:当 提交时触发; ready:当页面被载入并且 DOM 树完成初始化后触发。


初始化事件


我们自己的初始化代码必须放到 document 对象的ready 事件中,保证 DOM 已完成初始化:


<html><head>  <script>  $(document).on('ready', function () {    $('#testForm).on('submit', function () {      alert('submit!');    });  });  </script></head><body><form id="testForm">  ...</form></body>
复制代码


这样写就没有问题了。因为相关代码会在 DOM 树初始化后再执行。由于 ready 事件使用非常普遍,所以可以这样简化:


$(document).ready(function () {  // on('submit', function)也可以简化:  $('#testForm).submit(function () {    alert('submit!');  });});
复制代码


甚至还可以再简化为:


$(function () {// init...});
复制代码


事件参数


有些事件,如 mousemovekeypress ,我们需要获取鼠标位置和按键的值,否则监听这些事件就没什么意义了。所有事件都会传入 Event 对象作为参数,可以从 Event 对象上获取到更多的信息:


<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title><style>  #testMouseMoveDiv{    width: 300px;    height: 300px;    border: 1px solid black;  }</style></head><body>
mousemove: <span id="testMouseMoveSpan"></span>
<div id="testMouseMoveDiv"> 在此区域移动鼠标试试</div>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script><script> $(function () { $('#testMouseMoveDiv').mousemove(function (e) { $('#testMouseMoveSpan').text('pageX = ' + e.pageX + ', pageY = ' + e.pageY); }); });</script></body></html>
复制代码


参考视频:https://www.bilibili.com/video/BV1JJ41177di?from=search&seid=12603398025247640906&spm_id_from=333.337.0.0

发布于: 刚刚阅读数: 4
用户头像

Studying_swz

关注

还未添加个人签名 2020-12-23 加入

还未添加个人简介

评论

发布
暂无评论
JavaScript学习(二)_JavaScript_Studying_swz_InfoQ写作社区