I hate web programming, but looks like I really have to learn it. Notes from Liao Xuefeng’s JS course
Introduction
Variables and Objects
- Declare a variable with
var
:var x = 3
- JavaScript has both
null
andundefined
, but most of the time we just usenull
- Print out value in browser:
console.log()
- Ternary if-else Operator:
condition ? then : else
Strict Mode
JavaScript在设计之初,为了方便初学者学习,并不强制要求用var
申明变量。这个设计错误带来了严重的后果:如果一个变量没有通过var
申明就被使用,那么该变量就自动被申明为全局变量:
1 | i = 10; // i现在是全局变量 |
在同一个页面的不同的JavaScript文件中,如果都不用var
申明,恰好都使用了变量i
,将造成变量i
互相影响,产生难以调试的错误结果。
与之相对,使用var
申明的变量是局部变量。
在strict模式下运行的JavaScript代码,强制通过var
申明变量,未使用var
申明变量就使用的,将导致运行错误。启用strict模式的方法是在JavaScript代码的第一行写上:
1 | ; |
Strings
Strings are immutable in JS.
1 | var s = 'Hello, world!'; |
Array
1 | var arr = [1, 2, 3.14, 'Hello', null, true]; |
You can assign values to array, but 如果通过索引赋值时,索引超过了范围,同样会引起Array
大小的变化:
1 | var arr = [1, 2, 3]; |
arr.push(e)
adds variable e to the end of the array,arr.pop()
delete an element from the end of the arrayarr.unshift(e)
adds variable e to the beginning of the array,arr.shift()
delete an element from the beginning of the array
Object
JavaScript Object is a key-value map that can be accessed both in traditional object way and the python dict way.
1 | var xiaohong = { |
xiaohong
的属性名 middle-school
不是一个有效的变量,就需要用''
括起来。访问这个属性也无法使用.
操作符,必须用 ['xxx']
来访问。其他规则的变量虽然也可以用 xiaohong['name']
来访问 xiaohong
的 name
属性,不过 xiaohong.name
的写法更简洁。
1 | xiaohong['middle-school']; // 'No.1 Middle School' |
给不存在的属性赋值以声明新的属性,也可以使用 delete
删除一个既有的属性
1 | xiaoming.age = 18; // 新增一个age属性 |
Use in
to detect whether an object has a certain property. 不过要小心,如果in
判断一个属性存在,这个属性不一定是xiaoming
的,它可能是xiaoming
继承得到的。比如 toString
定义在 object
对象中,而所有对象都继承 object
,所以 xiaoming
也拥有 toString
属性。要判断一个属性是否是xiaoming
自身拥有的,而不是继承得到的,可以用hasOwnProperty()
方法:
1 | 'name' in xiaoming; // true |
Loop
for loop:
for (i=0; i<arr.length; i++) {...}
for of loop:
for (var key of xiaoming) {...}
. This is a better version of for in loop JS has. To filter out those inherited properties, use1
2
3
4for (var key of xiaoming) {
if (xiaoming.hasOwnProperty(key)) {
console.log(key); // 'name', 'age', 'city'
} }while loop:
while (n > 0) { ... }
do while loop:
do { ... } while()
Function
1 | var abs = function (x) { |
由于JavaScript允许传入任意个参数而不影响调用,因此传入的参数比定义的参数多也没有问题,虽然函数内部并不需要这些参数:
1 | abs(10, 'blablabla'); // 返回10 |
传入的参数比定义的少也没有问题:此时abs(x)
函数的参数x
将收到undefined
,计算结果为NaN
。
1 | abs(); // 返回NaN |
JSON
JSON 是 JavaScript Object Notation 的缩写,实际上是 JavaScript 的一个子集。所以我们可以非常方便地将 Java Object 转换成 JSON。
1 | var s = JSON.stringify(xiaoming, null, " "); |
要输出得好看一些,可以加上参数,按缩进输出:
1 | JSON.stringify(xiaoming, null, ' '); |
第二个参数用于控制如何筛选对象的键值,如果我们只想输出指定的属性,可以传入Array
:
1 | JSON.stringify(xiaoming, ['name', 'birth'], ' '); |
结果:
1 | { "name": "小明", |
还可以传入一个函数,这样对象的每个键值对都会被函数先处理:
1 | function convert(key, value) { |
上面的代码把所有属性值都变成大写:
1 | { |
如果我们还想要精确控制如何序列化小明,可以给xiaoming
定义一个toJSON()
的方法,直接返回JSON应该序列化的数据:
1 | var xiaoming = { |
拿到一个JSON格式的字符串,我们直接用JSON.parse()
把它变成一个JavaScript对象:
1 | JSON.parse('[1,2,3,true]'); // [1, 2, 3, true] |
jQuery
$
is an alias to jQuery
. Therefore, if you encounter error Uncaught TypeError: $ is not a function
, you can just replace the $
with jQuery
instead (e.g. jQuery('#abc')
)
Intro to Selector
jQuery Object
一个 jQuery 命令返回的对象是 jQuery 对象。它类似数组,每个元素都是一个引用了DOM节点的对象。以下面的查找为例,
如果
id
为abc
的<div>
存在,返回的jQuery对象如下:1
[<div id="abc">...</div>]
如果
id
为abc
的<div>
不存在,返回的jQuery对象如下:1
[]
总之jQuery的选择器不会返回undefined
或者null
,这样的好处是你不必在下一行判断if (div === undefined)
。
jQuery对象和DOM对象之间可以互相转化:
1 | var div = $('#abc'); // jQuery对象 |
通常情况下你不需要获取DOM对象,直接使用jQuery对象更加方便。如果你拿到了一个DOM对象,那可以简单地调用$(aDomObject)
把它变成jQuery对象,这样就可以方便地使用jQuery的API了。
按ID查找
在ID前加上 #
以进行ID查找
1 | // 查找<div id="abc">: |
按tag查找
直接写上tag名称进行tag查找:
1 | var ps = $('p'); // 返回所有<p>节点 |
按class查找
在class名称前加一个.
进行按class查找
1 | var a = $('.red'); // 所有节点包含`class="red"`都将返回 |
通常很多节点有多个class,我们可以查找同时包含red
和green
的节点:
1 | var a = $('.red.green'); // 注意没有空格! |
按属性查找
一个DOM节点除了id
和class
外还可以有很多属性,很多时候按属性查找会非常方便,比如在一个表单中按属性来查找:
1 | var email = $('[name=email]'); // 找出<??? name="email"> |
当属性的值包含空格等特殊字符时,需要用双引号括起来。
按属性查找还可以使用前缀查找或者后缀查找:
1 | var icons = $('[name^=icon]'); // 找出所有name属性值以icon开头的DOM |
这个方法尤其适合通过class属性查找,且不受class包含多个名称的影响:
1 | var icons = $('[class^="icon-"]'); // 找出所有class包含至少一个以`icon-`开头的DOM |
AND查找
直接写多个条件,条件间不加空格来执行AND查找。
如果我们查找$('[name=email]')
,很可能把表单外的<div name="email">
也找出来,但我们只希望查找<input>
,就可以这么写:
1 | var emailInput = $('input[name=email]'); // 不会找出<div name="email"> |
前文中的同时查找多个class也是一个例子。同样的,根据tag和class来组合查找也很常见:
1 | var tr = $('tr.red'); // 找出<tr class="red ...">...</tr> |
OR查找
把多个选择器用,
组合起来,查找所有符合任一选择器条件的 DOM 节点。
1 | $('p,div'); // 把<p>和<div>都选出来 |
要注意的是,选出来的元素是按照它们在HTML中出现的顺序排列的,而且不会有重复元素。例如,<p class="red green">
不会被上面的$('p.red,p.green')
选择两次。
Descendent Selector
以如下结构为例
1 | <!-- HTML结构 --> |
层级选择器(Descendant Selector)
如果两个DOM元素具有层级关系,就可以用$('ancestor descendant')
来选择,层级之间用空格隔开。
要选出上例中的 JavaScript,可以用层级选择器:
1 | $('ul.lang li.lang-javascript'); // [<li class="lang-javascript">JavaScript</li>] |
因为<div>
和<ul>
都是<li>
的祖先节点,所以上面两种方式都可以选出相应的<li>
节点。
要选择所有的<li>
节点,用:
1 | $('ul.lang li'); |
这种层级选择器相比单个的选择器好处在于,它缩小了选择范围,因为首先要定位父节点,才能选择相应的子节点,这样避免了页面其他不相关的元素。例如:
1 | $('form[name=upload] input'); |
就把选择范围限定在name
属性为upload
的表单里。如果页面有很多表单,其他表单的<input>
不会被选择。
多层嵌套也是允许的:
1 | $('form.test p input'); // 在form表单选择被<p>包含的<input> |
子选择器(Child Selector)
Child Selector $('parent>child')
与 Descendant Selector 几乎完全一样,但是限定了层级关系必须是父子关系,就是<child>
节点必须是<parent>
节点的直属子节点。还是以上面的例子:
1 | $('ul.lang>li.lang-javascript'); // 可以选出[<li class="lang-javascript">JavaScript</li>] |
过滤器(Filter)
过滤器一般不单独使用,它通常附加在选择器上,帮助我们更精确地定位元素。观察过滤器的效果:
1 | $('ul.lang li'); // 选出JavaScript、Python和Lua 3个节点 |
查找和过滤
本节使用如下例子
1 | <!-- HTML结构 --> |
查找
用find()
查找:
1 | var ul = $('ul.lang'); // 获得<ul> |
节点中移动
此小节介绍的所有函数都是当无参数调用时,返回目标节点。传入参数时,参数是过滤条件,当符合条件时返回目标节点,不符合时返回空 jQuery 对象
如果要从当前节点开始向上查找,使用parent()
方法:
1 | var swf = $('#swift'); // 获得Swift |
对于位于同一层级的节点,可以通过next()
和prev()
方法,例如当我们已经拿到Swift
节点后:
1 | var swift = $('#swift'); |
函数式编程
filter()
方法可以过滤掉不符合选择器条件的节点:
1 | var langs = $('ul.lang li'); // 拿到JavaScript, Python, Swift, Scheme和Haskell |
或者传入一个函数,要特别注意函数内部的this
被绑定为DOM对象,不是jQuery对象:
1 | var langs = $('ul.lang li'); // 拿到JavaScript, Python, Swift, Scheme和Haskell |
map()
方法把一个jQuery对象包含的若干DOM节点转化为其他对象:
1 | var langs = $('ul.lang li'); // 拿到JavaScript, Python, Swift, Scheme和Haskell |
Modifying DOM Contents
HTML
本小节使用以下例子:
1 | <!-- HTML结构 --> |
使用 text()
和 html()
方法分别获取节点的文本和原始HTML文本。无参数调用是获取文本/HTML,传入参数就变成设置文本/HTML。
1 | $('#test-ul li[name=book]').text(); // 'Java & JavaScript' |
一个jQuery对象可以包含0个或任意个DOM对象,它的方法实际上会作用在对应的每个DOM节点上。如果作用在一个空的 jQuery 节点上,也不会报错
1 | $('#test-ul li').text('JS'); // 两个节点都变成了JS |
CSS
和 HTML 类似,对 jQuery 对象下的 css('name')
读取 CSS 属性, css('name', 'value')
方法设置 CSS 属性
1 | var div = $('#test-div'); |
Modifying DOM Structure
对于如下HTML片段,
1 | <div id="test-div"> |
添加 DOM
首先要拿到<ul>
节点:
1 | var ul = $('#test-div>ul'); |
然后,调用append()
传入HTML片段。append()
把DOM添加到最后,prepend()
则把DOM添加到最前。
1 | ul.append('<li><span>Haskell</span></li>'); |
除了接受字符串,append()
还可以传入原始的DOM对象或jQuery对象
1 | // 创建DOM对象: |
同级节点可以用after()
或者before()
方法。即如果要把新节点插入到指定位置,例如,JavaScript和Python之间,那么,可以先定位到JavaScript,然后用after()
方法:
1 | var js = $('#test-div>ul>li:first-child'); |
删除 DOM
要删除DOM节点,拿到jQuery对象后直接调用remove()
方法就可以了。如果jQuery对象包含若干DOM节点,实际上可以一次删除多个DOM节点:
1 | var li = $('#test-div>ul>li'); |
Event
假设要在用户点击了超链接时弹出提示框,我们用jQuery这样绑定一个click
事件:
1 | /* HTML: |
on
方法用来绑定一个事件,我们需要传入事件名称和对应的处理函数。
另一种更简化的写法是直接调用click()
方法:两者完全等价。我们通常用这种写法。
1 | a.click(function () { |
事件类型
鼠标事件
- click: 鼠标单击时触发;
- dblclick:鼠标双击时触发;
- mouseenter:鼠标进入时触发;
- mouseleave:鼠标移出时触发;
- mousemove:鼠标在DOM内部移动时触发;
- hover:鼠标进入和退出时触发两个函数,相当于mouseenter加上mouseleave。
键盘事件
键盘事件仅作用在当前焦点的DOM上,通常是<input>
和<textarea>
。
- keydown:键盘按下时触发;
- keyup:键盘松开时触发;
- keypress:按一次键后触发。
其他事件
- focus:当DOM获得焦点时触发;
- blur:当DOM失去焦点时触发;
- change:当
<input>
、<select>
或<textarea>
的内容改变时触发; - submit:当
<form>
提交时触发; - ready:当页面被载入并且DOM树完成初始化后触发。
ready
其中,ready
仅作用于document
对象。由于ready
事件在DOM完成初始化后触发,且只触发一次,所以非常适合用来写其他的初始化代码。假设我们想给一个<form>
表单绑定submit
事件,下面的代码没有预期的效果:
1 | <html> |
因为JavaScript在此执行的时候,<form>
尚未载入浏览器,所以$('#testForm)
返回[]
,并没有绑定事件到任何DOM上。所以我们自己的初始化代码必须放到document
对象的ready
事件中,保证DOM已完成初始化。这样写就没有问题了。因为相关代码会在DOM树初始化后再执行。
1 | <html> |
由于ready
事件使用非常普遍,所以可以这样简化:
1 | $(document).ready(function () { |
甚至还可以再简化为:
1 | $(function () { |
上面的这种写法最为常见。如果你遇到$(function () {...})
的形式,牢记这是document
对象的ready
事件处理函数。
完全可以反复绑定事件处理函数,它们会依次执行:
1 | $(function () { |
事件参数
有些事件,如mousemove
和keypress
,我们需要获取鼠标位置和按键的值,否则监听这些事件就没什么意义了。所有事件都会传入Event
对象作为参数,可以从Event
对象上获取到更多的信息:
1 | $(function () { |
取消绑定
一个已被绑定的事件可以解除绑定,通过off('click', function)
实现:
1 | function hello() { |
需要特别注意的是,下面这种写法是无效的:
1 | // 绑定事件: |
这是因为两个匿名函数虽然长得一模一样,但是它们是两个不同的函数对象,off('click', function () {...})
无法移除已绑定的第一个匿名函数。
为了实现移除效果,可以使用off('click')
一次性移除已绑定的click
事件的所有处理函数。
同理,无参数调用off()
一次性移除已绑定的所有类型的事件处理函数。
事件触发条件
一个需要注意的问题是,事件的触发总是由用户操作引发的。例如,我们监控文本框的内容改动:
1 | var input = $('#test-input'); |
当用户在文本框中输入时,就会触发change
事件。但是,如果用JavaScript代码去改动文本框的值,将不会触发change
事件:
1 | var input = $('#test-input'); |
有些时候,我们希望用代码触发change
事件,可以直接调用无参数的change()
方法来触发该事件:
1 | var input = $('#test-input'); |
input.change()
相当于input.trigger('change')
,它是trigger()
方法的简写。
为什么我们希望手动触发一个事件呢?如果不这么做,很多时候,我们就得写两份一模一样的代码。