JavaScript学习笔记 (上)

我在重新阅读过《像计算机科学家一样思考Python》一书并且总结编程范式后,找到了JavaScript这门像Pyhton一样前后端通吃、兼容性强、应用广的语言。我首先选择的是一本被认为是经典的小书,叫做《JavaScript DOM编程艺术》,这本书和那本《Think Pyhton》一样,都是属于那种给你全局视野和整体认知的导论,因此读起来并不吃力,我配合着谋智的官方HTML、CSS、JavaScript文档一同学习,DOM书本中没有的,我就查了些文档进行补充,最后形成了这篇读书笔记。

注意:本文只能够提供给你一个逻辑清晰的语言语法架构,按照这样的顺序进行组织:变量、类型和结构、操作符、语句、表达式、条件,循环,迭代判断和函数基础。对于类型和结构而言,按照其基本顺序进行介绍:定义、迭代和分片、元素的选择、增删改以及一些高级用法和陷阱。

1 JavaScript 语法概要

1.0 综述

JavaScript像别的语言一样,都提供了三种基本的功能:

  • Math:接收输入、存储和计算一些变量的值;

  • Loop:JS允许使用事件监听,当你点击一个按钮或者进行某些操作时会自动触发;

  • Operation:JS允许对HTML网页元素进行提取、改变和进一步处理;

本文所讲的JavaScript为客户端JS,其含义为下载到用户的计算机上并执行,用来改变网页外观和状态、相应用户行为的动态语言。还有一类称之为服务端JS,比如Node.js,用来进行服务器和用户的交互。

JS原生的三大类Application Programming Interface,其分别为DOM(Document Object Model) API,提供操纵HTML和CSS文档的能力、Geo API,提供地理位置标注的能力,Canvas/webGL API,提供2D、3D绘图的能力,Audio/Video API,提供音频和视频处理的能力。此外还有第三方API,比如各种天气、快递查询API等。

更详细了解JavaScript的具体细节,请访问:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript 查阅详细文档。

1.1 书写规则

1.1.1 基本语句

JS的语句区别于Python,没有强制的换行要求,但是尽量采用;进行语句的分隔。

var a = 5; var b = "hello";

1.1.2 注释

JS的注释标准规范为前两种,第三种对于多行的JS来说,需要在每一行的开头加<!–,但是不要求加后面的类似于HTML的封闭符号,不建议使用。

// 这是注释

/* 多行注释 
这是多行的注释
*/

<!-- HTML注释方式 -->

1.2 变量

不同于Python不需要声明变量,对于JS来说,推荐使用var语句声明变量,刚声明的变量值为undefind,需要赋值才可以正常使用。

var variable_a , variable_b , variable_c = variable_d = 5 , variable_e = "Hello"

1.3 操作符

算术操作符有:

1 + 1 - 1 * 1 / 1; //加减乘除均可

var a = 0 ; a++ //++表示累加1,--表示累减1

var b = 0 ; b+=2 //+=,-=是一般语言的简要运算表示方法

字符串拼接:

"a" + "b" = "ab";

"a" + 2018 = "a2018"; // 这个要注意

比较操作符有:

<=, >= , == , === , !=== , != // 其中三个等号表示其类型也必须相同,而两个等号表示其值相同,比如 null 等于 ""。

逻辑操作符有:

&& 与 ;|| 或 ,!() 非。

1.4 条件、嵌套、递归

if语句的表示和Python类似,不过采用括号而不是缩进代替内部语句范围区分,除此之外,不使用冒号而是用括号表示表达式运算:

if (expression){
    do somethings;
}else if (expression) {
    do something;
} else {
    do something;
}

另外介绍一个和If很像的条件判断,叫做switch:

switch (var_name) {
    case value1 :
        do something;
        break;
    case value2 :
        do something;
        break;
    default:
        do something;
}

这就相当于一个简化版的If语句,var_name 字段放置变量,而 value 字段则只放置值。需要注意的是,如果在case分支中不加 break; 字段的话,程序会自动执行其余的语句,哪怕他们不符合剩下语句case的判断要求。defualt 字段表示如果都不符合,则执行此段语句,因为我们一般会在平常的 case字段中使用 break; ,所以也就不会执行 default; 字段了。

三元操作符如下:

var a = b = 0;

(a = b) ? if yes then do something ; else do something

第一个字段为判断条件,后面用问号隔开,第二个字段为这个条件为真时的操作,只能写一句话,可以放个函数,第三个字段是条件为假时的执行语句,第二、三个字段采用分号区隔。最后,第三个字段不能嵌套另外一个三元操作符。

while也很简单:

var x = 1
while (x < 10){
    console.log("x is small than 10");
    x++
}

特别的,有一个叫做do…while的循环,其不论如何,循环先进行一次,接下来才进行判断。

var result = '';
var i = 0;
do {
i += 1;
result += i + ' ';
} while (i < 5);

for循环不太一样,其在expression字段指定了三个语句,分别是初始条件、判断条件、迭代语句,如下:

for (var count = 0; count < 5; count++){
    do something.
}; //这句话的意思是,声明一个count变量为0,然后count执行第二个语句的判断,如果小于5则执行语句,之后累加count,继续这个循环,一直到count不小于5,自动结束。

var i = 0;
for (; i < 9; i++) {
    console.log(i);
    // 当然,你可以刻不写初始值,然后在外面界定。
};

for (var i = 0;; i++) {
    console.log(i);
    if (i > 3) break;
    // 同样的,你也可以不指定判断值,然后在中间嵌套一个if break语句。
};

var i = 0;
for (;;) {
    if (i > 3) break;
    console.log(i);
    i++;
    // 我已经不想说话了...
}

1.5 类型和结构

JS有整数、小数、字符串、布尔值等类型。需要注意,其布尔值类型为“true、false”,开头首字母小写。使用 var a = true; 新建一个bool变量。

JS的类型可以采用下列语句查看:var a = "Hello" ; typeof a;

JS的结构有二,其一为数组,它类似于Python的列表(list)和字典(dict)的混合物。但一般将其看作list使用。

1.5.1 字符串

字符串常用方法如下,包含了长度判断、分片、查找、替换和变形等常用操作,需要注意,其一般不使用Python冒号,分片采用slice()而非[]。

声明、遍历和分片:

var s = "Hello, World!";

s.length

显示字符串长度,属性而非方法

s[num]

从某个元素下标提取字符元素,其不支持多个字符的提取,即不支持s[m:n],但可以使用slice进行提取。推荐使用slice而不是使用此种方式,因为容易造成混淆。

s.slice(m,n)

分片,m,n 不写为复制字符串,类似于[:],只写一个数字为从这个数字开始到最后。写两个数字为从第一个数字到第二个数字,包含第一个数字但是不包含第二个数字对应索引的字母元素。

元素的查找、替换和处理:

s.indexOf("llo")

返回查找内容的字符串开头匹配位置

s.split(",")

根据某个元素分片,结果返回一个数组。

s.replace("World","Marvin")

注意,替换返回一个全新的结果,你需要保存这个结果到变量中去。其次,其只会匹配查找到的第一个,而不会匹配其余的。替代方法是使用正则表达式:

s.replace(/\World/g,"Marvin") /g 表示全局模式。

s.toUpperCase(); s.toLowerCase() // 大小写转换

字符串和Number的转换可以这样:

var num = Number("123455");

1.5.2 数组

JS的数组表示方法为:[value1,value2,value3],其暗含了下标,分别为0,1,2。列表是可以变化的。

新建数组可以采用:

var variable_a = Array(); //也可以声明Array(num)指定列表的大小,不过一般不使用。

var variable_a = Array(value1,value2,value3,value4); //这种声明方式直接给指定下标赋值。

var fruits = ['Apple', 'Banana']; //也可以直接采用列表形式创建数组

数组支持分片,可以给其中的元素赋值:

array_obj[2] = 233

数组自带一些函数,比如:

array_obj.length //一个属性,返回数组的长度,不是方法,不需要加括号。

array_obj.slice(); // Make a Copy

array_obj.push(item) //添加到列表结尾, 类似的还有 a_o.unshift(item) 添加到开头

array_obj.pop() //删除最后一个元素, 类似的还有 a_o.shift() 。从开头删除第一个元素,对于JS,没有remove、del的数组元素删除方法。

注意:数组可以采用 array_obj["string"] = xxx 的方式给予一个特殊字段以赋值,而非是下标,这类似于Python的字典,但不完全是,这种方法不推荐使用,因为容易混淆,并且其破坏了数组的结构。

数组和字符串的转换

var myList = ["a",12,[2,1,2]];

myList.join(";") => a;12;2,1,2 // 和Python不同,join是Array的方法而不是字符串方法。

//对于Python来说,如下:";".join(["a","b","c"])。

str.split(" ") // 可以从str切片成为一个列表 

另外还有一个arr.toString() 但是不推荐使用。

注意

对于数组,不推荐使用 ` += ` 操作符。因为其会将可变的数组强制转化成为字符串然后进行拼合。

1.5.3 对象

对象是JS一个非常神奇的结构,其采用 { } 表示,内部使用 key-value表示,比如:

var object = {
    foo: 'bar',
    age: 42,
    baz: {myProp: 12}
    } // 这看起来像是Python字典,但其实不是

var obj_1 = new Object(); // 这样也可以新建一个对象,这看起来像是Python的一个类,但其实也不是

但是,其可以采用obj_a.attr1这样的句点表示法来提取value1这个值,这类似于Python的类。因此,对于JS来说,其attr1这个字段是可以不用加双引号作为一个字符串的key值的,而value则必须为整数、小数或者字符串,其不能为一个未声明的变量。

操作对象的属性,可以用:

object.foo; // "bar"
object['age']; // 42

对象内不仅包含采用句点表示法可以提取的元素/称之为属性,还有一些可供使用的函数。

对象的种类

对象有三种,其一为用户自定义的对象,称之为User-defind Object,其二为Native Object,比如Bool(),是JS自带的对象,其三为Host Object,是浏览器驱动的对象。

1.6 函数

函数有参数、返回值以及里面的语句,其涉及全局变量和局部变量的问题,一般而言,对于JS,常常采用var声明变量,在函数内声明的var变量是局部变量,否则,就是全局变量(如果在全局能够找到的话)。

function calc_sales(units_a, units_b, units_c) {
    return units_a * 79 + units_b * 129 + units_c * 699;
}; // 一个有返回值的函数

calc_sales(2,2,3) // 通过这样调用即可。

还有一种称之为匿名函数,类似于Pyhton的lambda,如下:

var btn = document.querySelect(".do");
btn.onclick = function() {
    do something when push the button;
    like "document.addEventListener("click",function_name)"
}


2 DOM简介

2.1 HTML

欲了解和在线测试HTML更多细节,请访问官方文档:https://developer.mozilla.org/zh-CN/docs/learn/HTML/Introduction_to_HTML

Document-Object-Model,简称为DOM。其将HTML、CSS和JavaScript结合起来了。

对于HTML来说,其组成元素为一个树状结构,比如:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>getElementsByTagName example</title>
<script>
    function getAllParaElems(){
        var iobj = document.getElementsByTagName('p');
        var len = iobj.length;
        alert("there is "+len+" p");
    }
</script>
<script src="main.js"></script>
</head> 
<body style="border: solid rgb(36, 204, 36) 3px">
<p title="aaa">Some outer text</p>
<p title="222">Some outer text</p>      

<div id="div1" style="border: solid blue 3px">
    <p>Some div1 text</p>
    <p>Some div1 text</p>
    <p>Some div1 text</p>     
</div>

<p>Some outer text</p>
<p>Some outer text</p>

<button onclick="div2ParaElems();">
    show all p elements in div2 element</button>
        
</body>
</html>

其中,对于任意标签来说:

<p title="XXX" class="233" id="666">YYY</p>

其中,p为元素节点,这些元素是嵌套的,比如html为根元素,而嵌套在里面的有两个次级元素,分别是head和body,p为次次级嵌套元素。对于一个标签来说,至少要有一个元素节点组成,元素可以有属性,称之为属性节点,其包含在元素标签前半段内,最后半段的标签用来说明元素的终止,划分其范围。YYY称之为文本节点,其会显示在页面上。

有两个特殊的属性节点称之为class和id,其被设计用来标识和选择节点。

2.2 CSS

欲了解和在线测试CSS样式表的更多内容,请访问官方文档:https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Introduction_to_CSS

CSS是样式表Customer Style Sheet的简称,其用来处理HTML各节点元素的表达,比如颜色、形状、动画、字体、位置等。CSS类似于JS的语法,如下:

h1 {
    font-size: 2.6rem;
}

p {
    font-size: 1.4rem;
    color: red;
    font-family: Helvetica, Arial, sans-serif;
}

不过,和JS语法不同,其字段的属性不需要为特定类型,但分号表示断行,大括号表示区域划分还是一样的。

可以使用 #id 和 .class 来根据属性节点的id/class快速提取节点文本。

2.3 JavaScript

对于JS来说,其内涵了document这个Object实例,可以操纵HTML页面的元素,一些常用的函数及方法如下:

document.getElementById("id"); // 根据ID来选择文档中的元素
document.getElementsByTagName("p"); //根据元素名称来选择,比如p段落,h1一级标题等。
document.getElementsByClassName("class_name"); //根据class名称选择元素。

对于上述选择到的Element节点,使用.getAttribute()方法来进行属性的值的提取,也可以使用.setAttribute()方法来对其属性进行设置。需要注意,因为document已经加载,但是这个方法亦然更改了元素属性,这表示文档是动态变化的,这赋予了网页丰富交互的能力。

注意:一般来说,JS脚本一般在HTML文档最后加载,因为这可以首先展示网页内容,提高响应速度。其次,很多时候,必须文档加载完毕后我们才可以对其内容,节点进行操作,否则首先执行脚本,而文档还没有加载出来的时候,是无法提取document任何元素的。

JS文档在HTML文档中的位置有三种

内部的JS如下所示:

<script>

// JavaScript goes here

</script>

外部的JS如下所示:

<script src="script.js"></script>

内联的JS如下所示:

function createParagraph() {
    var para = document.createElement('p');
    para.textContent = 'You clicked the button!';
    document.body.appendChild(para);
}

<button onclick="createParagraph()">Click me!</button>

内联是一个坏主意,而且它还不高效——你会需要在每个想要 JavaScript 应用到的按钮上包含 onclick=”createParagraph()” 属性。相反,你可以使用内/外部JS动态修改属性:使用一个纯 JavaScript 结构允许你使用一个指令来选取所有的按钮:

var buttons = document.querySelectorAll('button');

for(var i = 0; i < buttons.length ; i++) {
buttons[i].addEventListener('click', createParagraph);
}


3 事件

3.1 HTML对象的事件

JavaScript事件中,作为前端最为重要的是浏览器定义的Event API,这些因浏览器引擎支持不同而不同。还有一类像是Vue.js,其作为服务器后端定义了事件的API。事件API是用户和DOM进行交互的产物,一些常见的API如下:

对于一个HTML页面上的按钮而言:

<button class="btn1">Press Me</button>

<script>
    var btn1 = document.querySelector(".btn1");
    btn1.onclick = function(event) {
        do something;
        console.log(event);
        console.log(event.target);
    }
    btn2.addEventListener("click",function) // 这种方法也可以,但是function不能加括号。
    btn2.removeEvent... // 可以删除事件连接

</script>

btn1.onlick 表示按下按钮的事件,传入此lambda函数后,当浏览器检测到按下按钮时会执行函数内容。除此之外,还有鼠标按下、移入(onmouseover)、移出(onmouseleave/out)、聚焦(onfocus)、失去焦点(onblur)、双击(ondblclick)等事件。此外,window这个对象也有一些事件,其中最常用的是键盘按下(onkeydown)、抬起(onkeyup)的事件。这些事件都是浏览器API定义的。

此外,对于同一个对象的一种事件定义多个函数的话,其只会执行最后一个函数,前面的赋值都会被复写掉,比如:

btn1.onclick = function1;

btn1.onclick = function2;

btn1.onfocus = function3;
// 只有最后两句会被执行

最后,btn.event 后面可以接上匿名函数,也可以接上一个定义过的函数,但是这个之前定义的函数不能加括号,否则会被立刻执行,如果有参数,则需要嵌套一个匿名函数执行。

var function2(*args) = {};

btn1.onclick = function2(*args); // 错误示范

btn1.addEventListener("click",function2(*args)) // 错误示范

btn1.onclick = function(){function2(*args)} ; // 正确示范

btn1.addEventListener("click",function(){function2(*args)}) //正确示范

3.2 Event对象和应用

注意到,在上述例子中,function有一个叫做 event 的参数,其也可以写成 “e” ,类似于Python Qt 中的 def CloseEvent(event): 声明。当事件被捕获后,其会将这个事件的各种参数传递到这个函数中,存在于event这个变量中,这个变量是一个对象,你可以在控制台查看它的详细信息。

这些event的属性中,最有用的是event.target属性,其表示了事件来自的对象,你可以对其各种属性,比如id、title、textContent进行操作。比如设置: event.target.style = {} 。事件可以中断,使用 e.preventDefault 进行中断,典型应用就是用户名密码检查,如果不符合格式就不进行Submit。

最后介绍一下现代浏览器的Event Capturing 和Event Bubbling流程,对于前者,浏览器从HTML root标签开始遍历查找event类型,比如我们声明的”onclick”事件在一个三级结构中:

<html>
    <div onclick>
        <video onclick> </video>
    </div>
</html>

这个问题在于,当我们点击视频的时候,会自动触发套在其上的div标签,因此这两个标签:div和video很难选择。当代浏览器在查找event的时候是从html标签开始向内查找,一直找到所有的onclick事件为止。接着是Event Bubbling过程,如果浏览器有一个onclick事件被触发,则从内向外进行相应,也就是从video标签到div标签进行相应,如果视频在播放,这时候点击,视频标签会被选中,然后才是div标签,这就对这两个嵌套标签的相同事件进行了区分。

对于较老的浏览器,比如IE,可以使用 e.stopPropagation() 进行处理。

这为我们提供了一个很绝妙的操作:当一个<ul>标签嵌套<li>标签时,只需要对ul进行事件的定义,而使用 event.target.nodeName === "LI" 进行子节点的判断,就可以对子节点进行操作。




————————————————————————

实践指南:

谋智提供了官方的《JavaScript第一步》入门指南,其中包含了一个很有意思的“猜1-100以内的一个数字”小游戏。这个游戏将会用到一些DOM和JS基础知识,比如语句、判断、表达式、函数,这对复习所学很有帮助。此外,指南还提供了一个在“浏览器调试控制台”查看错误并解决问题的说明文章,这对实践来说非常有帮助。你可以点此试一试自己的水平:https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/First_steps

此外,不同于很多官方文档,谋智的JS手册提供的Guide很好玩,里面有丰富的例子和实践,在此,你可以学习到很多HTML和JS交互的内容。你可以使用浏览器F12里面的控制台进行JS语言特性的练习,而不是自己写一个JS文件,然后放到浏览器中跑,因为JS运行错误只有在控制台中会显示,这对于调试来说非常方便。

更新日志:

2018年2月8日 阅读《DOM编程艺术》,查阅文档,撰写了基本语法和DOM相关部分内容。

2018年2月9日 阅读谋智的JS手册,跟着做了一些练习,对字符串、循环与条件、HTML和JS的联动有了一些实践和深入认识。吐槽:相比较Py,JS的一致性较差,虽然对于熟手来说这不是问题,但设计理念的不同还是造成了其应用范围的差异。

2018年2月10日 完成谋智的JS函数/事件部分学习,完成了一个简要图库的设计,更新了这部分的文档。我发现Event这东西和Qt的信号/槽机制模型非常类似,因此学习起来也不太困难。最后,我发现WebStorm和PyCharm对于语法补全和检查的功能太棒了,所以正全面转向IDE(写作此文时正在使用VS Code)。

点赞