面向Pythoneer的JS教程
date
Jan 7, 2024
slug
javascript-for-pythoneer
status
Published
tags
Compute Science
summary
网络上的JavaScript教程多如牛毛,但每次看一点就开始眼高手低,毕竟写了多年Python,不免心想:“就这?就这?”,但是每次想用React写点东西的时候,心里又不免犯嘀咕:“啥玩意?这啥玩意?”。本文尝试站在Pythoneer的角度,来写一份JavaScript简明教程,以帮助Pythoneer快速上手JavaScript
type
Post
Python和JavaScript同为动态语言,有很多相似之处,但在语法上又有相当多的不同,本文尝试站在Pythoneer的角度,来写一份JavaScript简明教程,以帮助Pythoneer快速入门JavaScript,使Pythoneer达到能看懂JavaScript代码并在ChatGPT辅助下能写出JavaScript代码的水平。
"Pythoneer" 是由 "Python" 和后缀 "-eer"(表示从事某种活动的人)组合而成的词,用来指代熟练掌握并从事 Python 编程的人。这个词通常用来描述对 Python 有深入了解并在其领域内有一定经验的开发者或爱好者。
大多编程语言都有变量、循坏语句、条件语句、函数和面向对象这几部分语法。本文首先将以代码的形式主要从这几部分来介绍JavaScript与Python的区别,,以帮助Pythoneer更快的看懂并写出优雅的JS代码,然后将介绍文档对象模型,使得可以与HTML联动。尽管这些知识体系是网状的,但本文还是尽量按照顺序结构展开介绍,因此最好按照本文顺序来阅读。最后,为了方便阅读,本文将使用JS作为JavaScript的简称,使用Py作为Python的简称,另外,本文多次提到的ES6(全称为ECMAScript 6),是指JavaScript的第六个版本。
条件语句
JS和Py的条件语句均包括
if
和switch
(Python3.10引入matct...case
,与switch功能相同),不过语法上有些许不同,比如JS中没有elif
。if 条件语句
以下是JS中的
if
语法(和C/C++好像是一样的😓),简单和Py做个对比。if (condition) { statement1 statement2 ... } else { statement1 statement2 ... }
首先,JS中的条件语句是需要用括号括起来的,这里其实可以把条件判断语句看作是名为
if
返回类型为bool
类型的函数。其次,JS采用花括号来表示一个代码块,而Py采用缩进和空格来表示代码块。此外,在JS中,如果一个代码块只有一行,则不需要用花括号括起来。
最后,JS是没有
elif
的,如果想要此功能,只能多嵌套几个if
和else
,当然,由于单语句不需要用花括号括起来,因此一般会这么写,使得看起来更加简洁一些:if (condition) { statement ... } else if{ statement ... } else if{ statement ... } else{ statement ... }
switch…case
switch
用于根据表达式的值选择不同的分支执行,Py中的这个功能直到3.10版本才引入(关键字是match...case
,但功能和switch
是一样的),以至于很多Pythoneer都不习惯用它,毕竟if elif else
完全可以胜任match
的工作。不过此处还是简单看一下JS中的switch
语法吧:switch (expression) { case value1: expression break; case value2: expression break; default: expression }
除了圆括号和花括号的区别之外,最大的不同应该是JS使用了关键字default来表示默认分支,而Py使用单下划线
_
来表示默认分支。此外,JS中的每个分支后面都写了一行break
,如果省略break
,则在执行完该分支代码后,会继续往后面的分支执行,直到遇到break
。很多JS工程师其实并不推荐使用
switch
,一方面它不如else if
功能强大,因为switch
只能与整数或枚举常量进行相等性比较;另一方面,真的很容易忘记写break
语句,而如果省略break
语句,程序将会继续执行下一个case中的代码块,直到遇到break
或者switch
语句结束,这就容易造成bug。三元表达式
讲到这里,顺便看一下三元运算表达式吧,Py中常用此语句来简化条件表达语句,通常用于在一行代码中根据条件选择不同的值。类似的,JS中也有这样的语法。
// Python 的三元表达式语法 x = true_value if condition else false_value // js的三元表达式语法 x = condition ? trueValue : falseValue
JS的这些语法真的和c/c++很像,这个三元运算符简直一模一样
循环和迭代
同Py一样,JS中的循环也有
for
和 while
两种(且语法和c/c++极为相似 again)。For循环
先来看JS中基础的for循环语法:
for (start clause; stop clause; step clause) { statement1 statement2 ... } // 栗子 for (let i = 0; i < 10; i++ ) { writeln(i); }
单纯看
for
循环的语法,c/c++简直一模一样,此处需要关注的点在括号()
分号;
和花括号{}
。即初始化表达式、条件和递增表达式需要用括号括起来,且之间需要分号;
隔开。另外,循环体代码块需要用花括号圈起来。注:上述代码块中的let为声明并定义变量,将在后面一章详细介绍。
Py的
for
循环可以用来遍历序列对象(比如列表,元组等),这一点JS也可以,且有for...in
和 for ... of
两种。简单讲,for...in
用于遍历属性键,for of
用于遍历属性值。具体如下:for...of
语法用于遍历可迭代对象(如数组、字符串、Map、Set 等),它会遍历对象的可迭代属性值。例如://字符串String let str="Hello"; for (let e of str){ console.log(e) } // H e l l o //数组Array let arr=["a","b","c"]; for (let e of arr){ console.log(e) } // a b c //集合Set let set=new Set([1,2,3,3,4]);//Set(4) {1, 2, 3, 4} for (let e of set){ console.log(e) } // 1 2 3 4
for...in
语句用于遍历数组或者对象的属性(对数组或者对象的属性进行循环操作),其所遍历的为对象的属性名(键),而非属性值。//字符串 let str="Hello" for (let i in str){ console.log(i) } // 0 1 2 3 4 //数组 let arr=["a","b","c"] for (let i in arr){ console.log(i) } // 0 1 2 //普通对象 let obj={a:1,b:2,c:3} for (let i in obj){ console.log(i) } // a b c
这里很容易绕晕,因为和Py差别很大,JS中的
for...of
才是和Py中的 for...in
是表达相同的含义,为遍历对象的元素;而JS中的for...in
由于历史遗留原因,遍历的其实是对象的属性名称,因此,如果想要遍历列表的元素,应该是用for...of
JS的for除了
for...in
和 for...of
之外,还有一个forEach
需要关注,forEach
是数组对象的一个方法,即作用于数组对象,用于遍历数组对象的每一个元素,并对每一个元素执行回调(callback)函数。具体语法及各部分解释如下:ArrayObject.forEach(callback(currentValue, index, arr), thisValue))
ArrayObject
:指定要调用forEach
方法的数组对象。
forEach
:数组对象的方法,用于遍历数组中的每个元素并对其执行指定的操作。
callback
:回调函数,用于指定在数组中的每个元素上要执行的操作。回调函数可以接受三个参数:currentValue
:当前元素的值。index
:当前元素的索引。arr
:数组本身。
thisValue
:可选参数,用于在执行回调函数时指定this
的值。
forEach
的两个示例:// 示例1 let arr = [1, 2, 3, 4, 5]; arr.forEach(function(element) { consol在·e.log(element); }); // 示例2 let obj={a:1}; let arr=[1,2,3]; arr.forEach(function(currentValue,index,arr){ console.log(currentValue+this.a); console.log(index); console.log(arr); },obj) //2 0 [1,2,3] //3 1 [1,2,3] //4 2 [1,2,3]
forEach
作为数组自带的方法,没有办法与break
、continue
以及return
配合使用,而for…of
可以做到。此外,forEach
方法在遍历数组时,并不会返回一个新的数组,而是用于在每个元素上执行指定的操作。因此,如果需要对数组中的每个元素执行某种操作,可以使用 forEach
方法;而如果需要对数组中的每个元素进行转换并返回一个新数组,则可以考虑使用 map
方法。While循环
JS中的
while
语法如下:while (condition) { statement1 statement2 ... }
和c/c++又是简直一模一样,另外,c/c++中还有个
do...while
,类似的,JS中也有:do { statement1 statement2 ... } while (condition);
与Py相比,需要关注的点也是括号
()
和分号{}
。函数
函数基础
从下面的Hello World 开始学习JS中的函数。
main(); function main() { alert("Hello World!"); writeln("Hello World!"); }
JS中定义函数的关键字为
function
;且不同于Py,JS使用花括号{}
来表示一个代码块;看到花括号想必也发现了JS使用;
来作为一条语句结束的标志。分号
;
在JS中有些时候是可选的,但是作为JS菜鸟还是建议都写上吧!上面代码快最奇怪的一点就是为什么把函数执行语句放置在了函数定义语句前面也可以运行?这是因为JS中函数提升的原因,函数提升会把任意位置的函数定义都提升到首部,这样写起来,函数执行语句可以在函数定义的前面。
再来看另外一种函数定义的方式:
var main = function () { alert("Hello World!"); writeln("Hello World!"); }; main();
上述代码将一个函数赋给变量
main
, 同Py一样,JS的函数也是可以被赋给某一变量的对象。语法上,JS使用function(param1, param2){}
来定义匿名函数。此外,最新版本的JS引入了箭头函数,可以用更为简洁的方式来定义匿名函数,语法为 (param1, param2) => expression
,当然这两种匿名函数的定义方式在功能上是完全等价的。下面的代码块展示了分别用JS和Py定义匿名函数。// Py匿名函数定义 anon = lambda a, b: a * b // JS 匿名函数方式一 var anon = function(a, b){return a + b} // JS 匿名函数方式二 var anon = (a, b) => a + b;
在JS中把一个函数赋给变量的操作(即匿名函数的使用)是随处可见的,而Py中,你要是经常这么写估计会被打吧 😀
需要注意的是,上述将函数赋给变量之后,相当于定义了一个变量,是没有函数提升的,因此,只能在名为main的变量定义之后执行。
Py函数比较方便的一点就是可以直接返回多个变量,JS函数本身只能返回一个值。但是可以使用以下几种方法来模拟函数返回多个值的效果:
- 使用对象(推荐):将多个值封装在一个对象中,然后从函数中返回这个对象。在接受返回值时,利用对象解构特性,可更直观地获取函数返回的多个值,使代码更加清晰易读。
function getMultipleValues() { return { value1: 'some value', value2: 'another value' }; } const { value1, value2 } = getMultipleValues(); console.log(result.value1); // 输出 'some value' console.log(result.value2); // 输出 'another value'
- 使用数组:将多个值存储在一个数组中,然后从函数中返回这个数组。这样可以让函数返回多个值,并且通过数组的索引来访问这些值。
function getMultipleValues() { return ['some value', 'another value']; } const [value1, value2] = getMultipleValues(); console.log(value1); // 输出 'some value' console.log(value2); // 输出 'another value'
闭包
闭包在Py中最多的应用应该是装饰器,但是在JS中简直随处可见。而本文并不尝试教会你闭包,仅仅是过一遍JS闭包的知识点,因此如果你作为一个Pythoneer,但不懂闭包,请先去翻阅《流畅的Python》,而没必要在此浪费时间,毕竟本文是面向Pythoneer的JS教程,而不是面向JSer的Python教程。
何为闭包?《流畅的Python》一书给出的解释是:闭包是一个函数,它保留了定义函数时存在的自由变量的绑定。如此一来,调用函数时,虽然定义作用域不可用了,但是仍能使用那些绑定。
闭包就是延伸了作用域的函数,包括函数(姑且叫 f 吧)主体中 引用的非全局变量和局部变量。这些变量必须来自包含 f 的外部函数的 局部作用域。《流畅的Python》
JS的闭包和Py闭包的概念是一样的,只是需要注意的是,在 JS 中,闭包捕获的是变量的引用,而在 Py 中,闭包捕获的是变量的值(Python 3 中可以使用
nonlocal
关键字来实现类似引用的效果)。因此,尽管是同样功能的代码,尽管都是闭包,其执行结果是不一样的。如下Py代码def parent(): a = 10 def double_(): a = a + a print(a) def square(): a = a * a print(a) return double_, square d, s = parent() d() s() d()
运行时会报
UnboundLocalError: local variable 'a' referenced before assignment
。而逻辑相同的JS代码就可以正常执行,如下:function parent() { let a = 10; let double = function () { a = a + a; console.log(a); }; let square = function () { a = a * a; console.log(a); }; return { double, square }; } let { double, square } = parent(); double(); square(); double(); // 输出 20 400 800
JS中的变量有三种作用域,分别为:
- 全局作用域;
- 函数内作用域;
- 代码块内作用域,块是通过花括号
{}
来定义的。
通过如下代码来理解不同的作用域
function fact(n) { let result = 1; for(let i = n; i > 0; i--) { let y = 'block'; var z = 'function' result = result * i; } writeln(x) //writeln(y, i) 报错,因为此时y和i不在该作用域内 writeln(z) return result; } var x = 'global' writeln(fact(10))
首先,变量
x
为全局作用域,任何代码都可以访问;result
变量为函数内作用域,仅在函数内可以访问;变量y
和i
为块作用域,仅仅在for
循环的代码块内可以访问;变量z
为函数内作用域,因为该变量是通过var
来声明及定义的。let
和var
在关于变量作用域上的区别如下: var
声明的变量存在变量提升,即在声明之前就可以访问到变量,而 let
声明的变量不会存在变量提升,只有在声明后才能访问到变量;用 var
声明的变量属于函数作用域或全局作用域,而使用 let
声明的变量属于块级作用域。因此let
更符合开发者预期的作用域规则,可以避免一些由于变量提升和作用域问题带来的 bug,所以在实际开发中更推荐使用 let
来声明变量。变量有自己的作用域,函数也是有个类似的概念,叫环境(或者称上下文),且同变量一样,函数的作用环境在函数定义时就决定了,然后当函数内的某一个变量找不到其定义时,往该函数的上一层环境中查找。而闭包就是函数和其相关的引用环境的组合体。
通俗点说,当一个函数可以访问定义该函数时的作用域之外的变量时,就形成了闭包。这意味着函数可以“记住”它被创建时的上下文(此处的上下文即环境),即使在其他地方执行。
理解了这一层,再来看下面的代码
function counter_maker() { let x = 0; let ctr = function() { x = x + 1; return x; } return ctr; } let counter1 = counter_maker() writeln(counter1()) let counter2 = counter_maker() writeln(counter2()) for(let i = 0; i < 5; i++) { writeln(counter1()) writeln(counter2()) }
闭包
counter_maker
在定义的时候已经创建好了,其包括函数ctr的环境就是counter_maker
函数的作用域。因此,其内部的包括函数可以访问counter_maker
函数中定义的变量x。
变量
基本变量
JS中有五种基本类型,如下:
- number
- string
- boolean
- undefined
- null
- symbol (new)
想必你已经发现了,JS只有一种数据类型
number
,不像Py,JS没有整形和浮点型的区分;另外,相较于Py,JS中多了null
和undefined
两种类型,null
和 undefined
都表示缺少值,但它们有如下区别:null
:null
是一个表示“空”、“无”或“值未知”的特殊关键字。当一个变量被赋值为null
时,它表示该变量的值为空。null
是一个原始值,而不是对象。在比较时,null
只等于它自身或者undefined
,而不等于任何其他值。
undefined
:undefined
表示一个未初始化的变量或一个缺少值的属性。当一个变量被声明但未被赋值时,它的值为undefined
。此外,在访问对象属性时,如果该属性不存在,则返回的值也是undefined
。undefined
也是一个原始值。
总结来说,
null
表示一个空的值,通常是有意赋予变量的,这与Python中的None
相似;而 undefined
表示一个未定义的值,通常是由 JavaScript 引擎赋予变量的默认值,Py完全没有这个概念,毕竟Python中的变量不需要声明,因此python中没有与undefined
对应的类型。另外,JS中的bool类型为
true
和false
(均为小写!),且类似于Py中会把空列表空字典空字符串判断为False
,JS中的null
undefined
NaN
0
""
和false
也都会被判断为false
。变量声明及定义
不同于Py,JS变量的作用域有三种:全局、函数、代码块。且有别于Py,JS变量在赋值前需要声明,且有三种变量声明的方式:
let
、var
和 const
,三者在变量声明和作用域方面有如下不同:var
:var
是在 ES5 中引入的声明变量的关键字。它具有函数作用域,这意味着当你在函数内声明一个变量时,它只在该函数内部可见。如果在函数外部声明,它将成为全局变量。var
声明的变量可以被重复声明,而且在声明之前就可以访问到(变量提升的原因,会是 undefined)。
let
:let
是在 ES6 中引入的声明变量的关键字。它具有块级作用域,这意味着当你在一个块(如{}
)内声明一个变量时,它只在该块内部可见。let
声明的变量不可以被重复声明,而且在声明之前是不可访问的。
const
:const
也是在 ES6 中引入的声明变量的关键字。它用于声明常量,一旦被赋值就不能再被修改。const
声明的变量也具有块级作用域,且不可以被重复声明。
总结来说,
var
是旧版本的声明变量的方式,具有函数作用域,可以被重复声明;let
是新版本的声明变量的方式,具有块级作用域,不可以被重复声明;const
也是新版本的声明变量的方式,用于声明常量,也具有块级作用域,且不可以被重复声明,并且一旦赋值就不能再被修改。感觉
let
和var
有点冗余,毕竟函数体也是一个代码块。不过看到有一种建议是:仅仅使用let,而不是用var来声明变量。字符串
js中的字符串和Python相似,也都是不可变对象,不过js中对字符串没法通过运算符
[]
来索引,只能通过字符串方法来索引。下表列举了python和js中的一些字符串操作方法对比:Python | JavaScript | 描述 |
str[3] | str.charAt(3) | 返回第三个字符 |
str[2:5] | str.substring(2,5) | 返回从下标从2到5(不包括)的子串 |
len(str) | str.length | 返回字符串的长度 |
str.find('x') | str.indexOf('x') | 找到字符 x 第一次出现的下标,要是没找到,返回-1 |
str.split() | str.split(/\s+/) | 将字符串按照空格分开为字符串列表。 |
str.split(',') | str.split(',') | 将字符串按照 , 分隔为字符串列表 |
str + str | str.concat(str) | 拼接两个字符串 |
str.strip() | str.trim() | 移除字符串首尾的空格 |
str.replace(a,b) | str.replace(a,b) | 替换字符串中的子串 a 为子串b |
JS中要判断一个字符串是否包含另一个字符串,只能使用
indexOf
字符串方法。此方法返回一个数字,以指示作为参数传递的字符串在原始字符串中的位置。如果给定的字符串不存在,indexOf
返回-1
,JS中字符串索引操作符不支持负的索引值,因此-1
即明确表示未找到另外一个字符串。Py中可以通常使用
"""str"""
来表示跨行字符串,而JS使用``
来表示跨行字符串,如下:let mlstr = `Hello world this is a "multi-line." Isn't it nice. ` alert(mlstr); //"Hello world\nthis is a \"multi-line.\"\nIsn't it nice.\nstring.\n"
Py有强大的字符串format功能,JS原生虽没有内置的字符串格式化函数。但可通过模板字符串(template literals)实现类似的功能。模板字符串是 ES6 引入的一种字符串形式,允许在字符串中嵌入变量和表达式。模板字符串使用反引号
``
来定义,并通过 ${}
插入变量或表达式。例如:let name = "Alice"; let greeting = `Hello, ${name}!`; console.log(greeting); // 输出: Hello, Alice!
容器对象
这部分介绍一些容器对象,比如字典、数组等。
列表
js中的
Arrays
和Python中的list
列表很相似,都可以用来存放不同类型的元素。但是操作方法完全不同,下表汇总了Py和JS中对列表操作的方法对比Python | JavaScript | Notes |
l.append(newitem) | l.push(newitem) | 添加新元素 newitem 到列表l 末尾 |
l.pop() | l.pop() | 弹出并返回列表 l 最后一个元素 |
l.pop(0) | l.shift() | 移除并返回列表 l 中的第一个元素 |
l.insert(0,newitem) | l.unshift(newitem) | 添加新元素 newitem 到列表l 开头 |
l.insert(idx, newitem) | l.splice(idx, 0, newitem) | 向数组 l 中的idx 位置插入新元素 |
del l[idx] | l.splice(idx, 1) | 删除索引为 idx 的元素 |
l1 + l2 | l1.concat(l2) | 拼接两个列表 |
JS中的
splice
方法好像出场率有点高,这里着重点解释一下,splice方法
可以用来修改数组的内容,该方法接受多个参数,其中最常用的是如下三个参数:start
:表示开始修改的位置,即从哪个索引开始进行操作。
deleteCount
:表示要删除的元素个数。
item1, item2, ...
:表示要插入到数组的元素。
举个例子,假设有一个数组
arr = [1, 2, 3, 4, 5]
,我们可以使用 splice
方法来删除和插入元素:arr.splice(2, 1)
表示从索引 2 开始删除 1 个元素,即删除了数组中的第三个元素,arr
将变成[1, 2, 4, 5]
。
arr.splice(2, 0, 'a', 'b')
表示从索引 2 开始删除 0 个元素,并且插入'a'
和'b'
,arr
将变成[1, 2, 'a', 'b', 4, 5]
。
另外,JS和Py在下标索引上存在如下异同:
- 相似之处:
- 下标索引:Py和JS数组的索引都是从0开始的,即第一个元素的索引为 0,第二个元素的索引为 1,依此类推。
- 访问元素:均可以通过使用方括号和索引值来访问数组中的特定元素。
- 不同之处:
- 负数索引:在 Py中,可以使用负数索引来从数组末尾开始索引元素,例如
-1
表示倒数第一个元素。而在 JavaScript 中,负数索引并不会从末尾开始计数。 - 范围索引:在 Py中,可以使用切片
[start:end:step]
来获取数组中的一个范围,例如arr[1:3]
表示获取索引为 1 和 2 的元素。而在 JS中,没有类似的内置语法,需要使用slice
方法来实现类似的功能。 - 越界访问:在 JavaScript 中,如果使用超出数组范围的索引访问元素,会返回
undefined
。而在 Python 中,如果使用超出列表范围的索引访问元素,会抛出IndexError
异常。
下表展示了Py和JS关于切片
slice
语法上的一些异同:Python | JavaScript | Notes |
l[2:4] | l.slice(2,4) | 选取下标从2到4(不包括4)的元素,并返回新的列表 |
l[2:] | l.slice(2) | 返回下表从2开始的子列表 |
l[:4] | l.slice(0,4) | 返回从起始索引到4的子列表 |
l[-5:-1] | l.slice(-5,-1) | both slice from 5 from the end to 1 from the end inclusive |
在列表的应用中,最常见的操作还有判断某元素是否在列表中,python直接可以使用
if item in mylist
来判断,而js不可以,因为对 对js中的Arrays
执行in
判断的时候,判断的并不是Arrays
里面的元素,而是Arrays
的属性。正确的操作应该是if (mylist.includes(item))
或者if (mylist.indexOf(item) > -1)。这里的知识点和循环for...in是一样的。
Python中“万物皆对象”,同样的,js也是“万物皆对象”,且JS中的对象均可像字典一样操作,比如,在JS中,可以给任意一个对象添加属性
myObj.someattribute = somevalue。
列表也不例外,而当我们用in
来判断元素item
是否在列表中时,其实判断的是该列表对象是否存在名为item
的属性,如下:let mylist = [1, 2, 3, "foo", "bar"]; mylist.name = "aaaaa"; if ("name" in mylist) { console.log("what?"); } else { console.log("ha?"); } // 将会输出 what?
上述代码告诫Pythoneer,JS就是JS, 和Py是不同的,看JS代码时不能眼高手低。
Py经常以交换两个变量的简洁写法自豪,那JS呢?JS可以通过解构赋值来简洁的交换两个变量:
let a = 5; let b = 10; [a, b] = [b, a]; console.log(a, b); // 输出 10 5
在这段代码中,
[b, a] = [a, b]
使用了数组解构赋值的语法。这段代码的含义是将数组 [a, b] 中的第一个元素赋值给 b
,将数组中的第二个元素赋值给 a
。因为在赋值语句的右边我们使用了数组 [b, a]
,所以赋值操作会按照数组的顺序进行。因此,当执行
[a, b] = [b, a]
这段代码时,它实际上完成了交换变量 a
和 b
的值的操作。字典 / 对象
JS中万物皆对象,且所有对象都可以像字典一样操作。此外,JS字典除了可以使用索引操作符
[]
来添加和检索键值对之外,还可以使用.
运算符。不同于Py,JS字典使用
Object.keys(myDict)
来获取字典myDict
所有键,而想要获取字典的所有值,就只能使用for
循环,通过键来得到每个值。当然,有了map
函数,也可以用一行代码来解决,如下:const myDict = {foo: "bar", baz: 22, 33: 'hello'}; // 使用map来获取字典的值 const vals = Object.keys(myDict).map(key => myDict[key])
注:上述代码中的
key => myDict[key]
为箭头函数,在前面函数小节已经介绍过了,他和Py中的匿名函数是类似的。Py中的字典,经常还有如下两个操作:
my_dict.items()
,获取所有键值对。JS没有这样的操作,因为JS没有元组这种数据类型,想要类似的功能,就只能通过for循环来变量字典键。
my_dict.get(key,"default")
,查询键为key
的值,如果不存在则给定一个默认值default
。JS没有这样的操作,但是可以有替代解决方案:myDict['nobodyhome'] || 'default'
。当字典myDict
中没有键'nobodyhome'
时,会返回undifined
,此时布尔或(||
)运算中的下一个值被计算,因此'default'
就变成了返回值。
面向对象
注:JS和Py一样,将类内成员函数称为方法,类内成员变量称为属性。
JS使用class 关键字来创建一个类,类体在一对大括号 {} 中,可以在大括号 {} 中定义类成员的方法或构造函数。每个类中包含了一个特殊的方法
constructor()
,它是类的构造函数,这该方法用于创建和初始化一个由 class 创建的对象。另外,类内定义方法是不需要关键字function
的。定义好类之后,将使用
new
关键字来创建对象,如下class Site { constructor(name, url) { this.name = name; this.url = url; } get_url() { return this.url; } } let site = new Site("Bilibili", "https://www.bilibili.com/"); console.log(site.get_url())
创建对象时会自动调用构造函数方法 constructor()。
(更多关于类的知识不打算在此处展开了,毕竟用的少..)
文档对象模型
学JS干嘛用?当然是写前端了。本节将介绍JS与HTML的联动。首先介绍DOM,然后介绍如何用JS来操作DOM。
当web浏览器接收到HTML时,所做的第一件事就是构建页面的树状结构表示。这种层次化的树状web页面表示称为文档对象模型或DOM(Document Object Model)。对于以下的HTML页面,有如下图所示的DOM.
<html> <body> <h1>Hello World</h1> <button>Push Me Pull You</button> <script> function foo() { alert("Hello World") } </script> </body> </html>

DOM(Document Object Model)是一种用于处理网页内容的编程接口。它将网页文档表示为一个树状结构,其中每个元素都是一个对象,可以通过编程方式访问和操作。DOM允许开发者使用脚本语言(如JS)动态地改变文档的内容、结构和样式。通过DOM,你可以选择特定的元素、添加新的元素、移除现有的元素,以及响应用户的交互事件。
接下来学习的就是如何通过JS来操作HTML页面。先来认识一下
document
,JS中的document
是指代整个HTML文档的对象,它是DOM的一部分。严格来讲,document
是一个引用对象的名称,这个对象就是当前页面的树状结构。因此,可以将"document" 看作是JavaScript中用来访问和操作HTML文档的接口,通过document
对象,来选择特定的元素、修改元素的内容和样式,添加新的元素,以及响应用户的交互事件,从而实现动态的网页交互效果。JS是一种面向对象的语言,通过document的点运算符
.
便可以调用一些方法或者属性来操作DOM,网站HTML DOM Element Object列举了所有的元素方法和属性。下面将介绍一些常用的方法和属性。通常,最常用的操作是获取DOM中的某个元素,一般而言,可以通过如下几种方法来获取想要的元素:
/ 通过id选择元素 let elementById = document.getElementById('myElementId'); // 通过类名选择元素 let elementsByClass = document.getElementsByClassName('myClassName'); // 通过标签名选择元素 let elementsByTag = document.getElementsByTagName('div'); // 通过CSS选择器选择元素 let elementBySelector = document.querySelector('.mySelector');
上述方法中除了
getElementById
获取到的是一个元素之外,其余方法获取到的均是装有元素的列表。因为只有元素的id是唯一的,所以通过id是获取到一个元素,而其他方法获取到的是一个满足搜索条件的列表。获取到想到的元素之后,可以通过点运算符
.
直接获取或者修改该元素的属性。例如:// 修改元素的文本内容 elementById.textContent = '新的文本内容'; // 修改元素的HTML内容 elementById.innerHTML = '<strong>加粗的文本</strong>'; // 修改元素的样式 elementById.style.color = 'red';
如果想往DOM中新增和移除元素呢?也是可以的,具体方法如下:
// 创建新元素 let newElement = document.createElement('p'); newElement.textContent = '新的段落'; // 将一个新的子元素添加到父元素的末尾,此处parentElement如果是documnet,那就是追加到末尾 parentElement.appendChild(newElement); // 在指定位置之前插入新的子元素 parentElement.insertBefore(newElement, referenceElement); // 在指定位置相对于当前元素插入新元素 element.insertAdjacentElement(position, newElement); // 移除现有元素 let elementToRemove = document.getElementById('elementToRemove'); elementToRemove.remove(); // 从父元素中移除指定的子元素 parentElement.removeChild(childElement);
最后,用一个例子来结束本指南。
<html> <head> </head> <body> <h1>Hello World</h1> <button onclick="changeThisPageFunc();">我是按钮</button> <main>sd <h1>Hello Main</h2> <p>我是一段话。</p> </main> <script type="text/javascript"> changeThisPageFunc = function() { let myMain; document.body.style.backgroundColor = "lightblue"; myMain = document.querySelector('main'); myMain.innerHTML = "<h3>我是点击按钮之后一段话<h3>"; myMain.style.height = "50px"; myMain.style.width = "50%"; myMain.style.backgroundColor = "lightgreen"; } </script> </body> </html>
上述HTML包含了一个简单的HTML文档,具体包括了一个标题、一个按钮进而一段话,以及点击按钮响应的JavaScript函数代码。函数执行的动作为:
- 将整个页面的背景颜色设置为"lightblue"。
- 选择
<main>
元素,并将其内容替换为一个新的<h3>
标签,显示"我是点击按钮之后一段话"。
- 将
<main>
元素的高度设置为"50px",宽度设置为"50%",背景颜色设置为"lightgreen"。
注:上述函数中
innerHTML
属性是用于获取或设置元素的HTML内容,执行完innerHTML="<h3>我是点击按钮之后一段话<h3>"
后,main
标签仍然存在,但是其内容已经被替换为新的内容。小结
如果你恰好掌握了C/C++,那么看的过程一定很快乐。当然,不会也没关系,把一些较大的区别记住即可:
- 采用花括号
{}
来表示代码块,用逗号;
来表示语句结束。
- 箭头函数