JavaScript-入门

1_LyZcwuLWv2FArOumCxobpA

MDN

Mozilla Developer Network是一个非常重要的Web技术文档平台.由Mozilla维护,包括HTML,CSS,JavaScript等各种web技术的完整文档.许多开发者都视其为Web开发的官方参考手册.

MDN web doc

简介

  1. JavaScript和Java除了语法上有点像以外毫无关系.但是java非常火,网景公司希望蹭热度推广JS,就改了这么个名字.
  2. ECMAScript: 网景开发了JS,一年后微软又模仿开发了JScript.为了JS成为全球标准,几个公司联合ECMA组织定制了JS语言的标准,称为ECMAScript.所以这个标准就是为了JS而生.也称ES标准.其实ES6是一个重大更新.
  3. JS设计到诞生只用了10天时间,所以无法避免,很多设计缺陷.

集成与运行

JS代码可以集成到网页的任何地方,不过一般放在<head>中,用<script></script>包起来,或者使用<script src="..."></script>引入JS文件.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<html>
<head>
<!-- 方式一 -->
<script>
alert('Hello, world');
</script>
<!-- 方式二 推荐 -->
<script src="/static/js/abc.js"></script>
<!-- 可以引入多个js文件, 浏览器会按顺序执行 -->
<script src="/static/js/def.js"></script>
<!-- type属性其实是没必要的,因为默认就是text/javascript -->
<script type="text/javascript", src="/static/js/ghi.js"> </script>
</head>
<body>
...
</body>
</html>

chrome随便打开一个网页,按F12–> 点解consoletab,即刻在那里执行你的js代码.

基本语法

  • 每个语句以;结尾,虽然这不是强制的,因为浏览器中负责执行js代码的引擎会自动补上,但是建议不要忽略,因为有些情况下引擎会理解错误加错分号导致语义改变.
  • 语句块用{...}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 一个完整的赋值语句
var x = 1;
// 字符串
'hello world';
// 一个;表示一个语句,不建议一行写多个语句!
var x = 1; var y = 2;
// 语句块
if (2 > 1) {
x = 1;
y = 2;
z = 3;
if (x < y) {
z = 4;
}
if (x > y) {
z = 5;
}
}
/*
缩进也不是强制的,但是建议一定要有缩进,可以帮助理解代码的层次
嵌套没有限制,但过多的嵌套会导致代码难以理解
*/

注意: JS严格区分大小写

数据类型和变量

Number

JS不缺分整数和浮点数,统一用Number表示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
123; //整数
0.456; //浮点数
1.2345e3; //科学计数法表示1.2345*10^3,就是1234.5
-99; //负数
NaN; // NaN表示Not a Number,当无法计算结果时用NaN表示
Infinity; //表示无限大,当数值不超过了JS的Number所能表示的最大值时,就表示为Infinity

// number可以四则运算,规则和数学一致
1 + 2; // 3
(1 + 2) * 5 / 2; // 7.5
2 / 0; // Infinity
0 / 0; // NaN
10 % 3; // 1 (%求余)
10.5 % 3; // 1.5

//因为js不区分整数和浮点数,所以
12.00 == 12
// js的最大整数范围为+-2^53
// 计算圆面积
var r = 123.456;
var s = 3.14 * r * r;
console.log(s);
// 打印Number能表示的最大整数
console.log(Number.MAX_SAFF_INTEGER);

字符串

使用''""括起来的任意文本

布尔值

true/false, 支持布尔运算//: &&/||/!

1
2
3
4
true && true; // 这个&&语句计算结果为true
true || false; // 这个||语句计算结果为true
! false; // 结果为true
! (2 > 5); // 结果为true

比较运算符

1
2
3
2 > 1; // true
2 >= 3; // false

js允许任意数据类型做对比,注意相等运算符

1
2
false == 0; // true
false === 0; // false

js有两种相等运算符:

==: 它会自动转换数据类型,很多时候会得到奇怪的结果

===: 表示不自动转换类型,如果类型不一致,返回false,如果一致再比较

这是js的一个设计缺陷,所以要注意: 始终坚持使用===来比较

1
2
3
4
// 有一个例外,NaN这个特殊的Number与所有其他值都不相等,包括它自己
NaN === NaN; // false
// 唯一能判断NaN的是只有
isNaN(NaN); // true

注意浮点型数的比较

1
1 / 3 === (1 - 2 / 3); // false

这就不是设计缺陷了,而是因为计算机无法精确表示无限循环小数.要比较两个浮点数是否相等,只能计算他们的差的绝对值,看看是否少于某个阈值

1
Math.abs(1 / 3 - (1 - 2 / 3)) < 0.0000001; // true

BigInt

js内置的BigInt类型可以用来表示比2^53还要大的数,表示方法是整数后加一个n,或者使用BigInt()把字符串和Number转换成BigInt

1
2
3
4
5
6
7
// 使用BigInt:
var bi1 = 9223372036854775807n;
var bi2 = BigInt(12345);
var bi3 = BigInt("0x7fffffffffffffff");
console.log(bi1 === bi2); // false
console.log(bi1 === bi3); // true
console.log(bi1 + bi2);

BigInt也可以正常加减乘除,结果依然是一个BigInt,但BigInt和Number不能放一齐运算

1
2
3
4
console.log(1234567n + 3456789n); // OK
console.log(1234567n / 789n); // 1564, 除法运算结果仍然是BigInt
console.log(1234567n % 789n); // 571, 求余
console.log(1234567n + 3456789); // Uncaught TypeError: Cannot mix BigInt and other types

null和undefined

  • null表示一个空值,不同于0和空字符串'',它就是表示,类似python的None,java的null和swift的nil
  • js的设计者希望用null表示空值,undefined表示值未定义.但事实上这并没有什么卵用.
  • 大多数情况下,我们都用null,仅仅在判断函数参数是否传递的情况下使用undefined.

数组

一组按顺序排列的集合,集合的每个值称为元素.元素可以是任意数据类型.

1
2
3
4
5
6
7
// 两种创建方式
[1, 2, 3.14, 'Hello', null, true]; // 强烈建议使用这一种
new Array(1, 2, 3);
// 元素使用索引(index)来访问,从0开始
var arr = [1, 2, 3.14, 'Hello', null, true];
arr[0]; // 返回索引为0的元素,就是1
arr[6]; // 索引超出范围,返回undefined

类比python的list

对象

键-值对的无序集合,都是字符串类型,可以是任意类型.

又称为对象的属性.

1
2
3
4
5
6
7
8
9
10
11
12
var person = {
name: 'Bob',
age: 20,
tags: ['js', 'web', 'mobile'],
city: 'Beijing',
hasCar: 'true',
zipcode: null
}

// 获取特定属性
person.name;
person.zipcode;

类比python的字典或groovy的map

变量

  • 使用var关键字来声明
  • 变量名可以是:
    • 大小写英文
    • 数字
    • $_的组合,不能用数字开头
    • 不能是JS的关键字
    • 也可以用中文,但是,请不要自找麻烦
  • =赋值
  • 可以反复赋值,但只能用var声明一次
1
2
3
4
5
6
7
var a; // 申明了变量a,此时a的值为undefined
var $b = 1; // 申明了变量$b,同时给$b赋值,此时$b的值为1
var s_007 = '007'; // s_007是一个字符串
var Answer = true; // Answer是一个布尔值true
var t = null; // t的值是null
var a = 123; // 多次赋值只能使用一次var
a = 'ABC'

这种变量赋值不固定的语言就是动态语言,比如python也是.java则是静态语言.

strict模式

JS设计之初并没有强制要求使用var申明变量,这导致了一个严重的后果: 如果一个变量没有通过var申明就被使用,该变量就自动被申明为全局变量:

也就是同一页面的的不同js文件中公用的一个变量,假如不同js文件都使用了同样一个变量名的变量,就会相互影响.

使用var声明的变量,它的范围会限制在该变量被申明的函数体内,同名变量就不会冲突.

为了弥补这个严重的设计缺陷,ECMA后续退出了strict模式,开启该模式后,如果未使用var申明变量就使用,就会导致运行错误.

1
2
3
// 在js代码的第一行上写上即刻开启
'use strict';
// 这是一个字符串,不支持strict模式的浏览器会把它当作一个字符串语句执行,支持的则会开启strict模式

建议所有js都开启strict模式.

还有一种申明变量的方式是let,这也是现代js推荐的一种方式

1
2
3
// 用let申明变量:
let s = 'hello'
console.log(s)

现代JS编程中基本不使用var而是使用const+let的组合来定义变量

字符串

转义字符

除了常见的转义功能

1
2
3
4
// 可以以`\x##`形式用十六进制表示ASCII字符
'\x41'; // 完全等同于'A'
// 还可以用`\u####`形式表示一个Unicode字符
'\u4e2d\u6587'; // 完全等同于 '中文'

多行字符串

有点特别,ES6后,js使用反引号表示:

1
2
3
`这是一个
多行
字符串`;

模板字符串

与python和groovy类似,使用+号连接

1
2
3
4
let name = '小明';
let age = '20';
let message = '你好,' + name + ',你今年' + age + '岁了!';
alert(message)

如果有很多变量,使用+就有点麻烦,ES6后提供一种新的模板字符串:

1
let message = `你好, ${name}, 你今年${age}岁了!`; // 反引号+shell的变量引用:)

字符串操作

1
2
3
4
5
6
// 获取字符串长度
let s = 'Hello, world!';
s.length;
// 获取字符串某个指定位置的字符,使用下标
s[0]; // 'H'
s[13]; // undefined,因为超出范围

字符串是不可变的,如果对字符串的某个索引赋值,不会有任何错误,也不会有任何改变

1
2
3
4
5
6
7
8
9
10
// 转回大写
s.toUpperCase();
// 小写
s.toLowerCase();
// 根据字符串搜索索引
let s = 'hello world';
s.indexOf('world'); // 返回7
// 返回子字符串,类似python的字符串切片
s.substring(0, 5); // 从索引0开始到索引4(不包括5)
s.substring(7); // 从索引7开始到结束

数组

大小

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 通过array的属性length获取长度
let arr = [1, 2, 3.14, 'Hello', null, true];
console.log(arr.length);
// 如果直接个length属性赋值,回导致array大小变化,没有赋值的元素则是undefined
arr.length = 7;
console.log(arr); // arr变为[1, 2, 3.14, 'Hello', null, true, undefined]
// array是可变的,可以通过索引直接修改元素
let arr = ['A', 'B', 'C'];
arr[1] = 99;
console.log(arr); // ['A', '99', 'C']
// 通过索引赋值时,如果索引超出了范围,同样会引起Array大小的变化
let arr = ['A', 'B', 'C'];
arr[5] = 'x';
console.log(arr); // arr变为['A', 'B', 'C', undefined, undefined, 'x']

大多数语言都不允许直接改变数组的大小.但JS对此没有任何报错,所以在使用JS array时要注意不要越界

切片/复制

1
2
3
4
5
6
7
8
// array同样也有indexOf()
let arr = [10, 20, '30', 'xyz'];
arr.indexOf(10); // 元素10的索引为0
// slice(),类似python的切片
arr.slice(0, 3); // 索引0-2,不包括3
arr.slice(3); // 索引3到结尾
let aCopy = arr.slice(); // 不传任何参数就是从头到尾,一般用于复制array
aCopy === arr; // false. 类似python的深复制

添加/删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let arr = [1, 2]
arr.push('A', 'B') //在array末尾添加元素,返回新数组的长度:4
arr; // [1, 2, 'A', 'B']
arr.pop(); // 从末尾提取元素,返回'B'
arr; // [1, 2, 'A']
let arr = [];
arr.pop(); // 空数组pop不会报错,而是返回undefined
arr; // []
let arr = [1, 2];
arr.unshift('A', 'B'); // 在arr开头添加,返回arr的新长度: 4
arr; // ['A', 'B', 1, 2]
arr.shift(); // 从arr开头提取,返回'A'
arr; // ['B', 1, 2]
let arr = [];
arr.shift() // shift空数组不会报错,返回undefined

排序/反转

1
2
3
4
5
6
let arr = ['B', 'C', 'A'];
arr.sort(); // 排序
arr; // ['A', 'B', 'C']
let arr = ['one', 'two', 'three'];
arr.reverse(); // 反转
arr; // ['three', 'two', 'one']

万能方法

splice()是修改array的万能方法.

第一个参数是array的开始索引,第二个参数是范围.

1
2
3
4
5
6
7
8
9
10
let arr = ['Microsoft', 'Apple', 'Yahoo', 'AOL', 'Excite', 'Oracle'];
// 从索引2开始删除3个元素,然后再添加两个元素:
arr.splice(2, 3, 'Google', 'Facebook'); // 返回删除的元素 ['Yahoo', 'AOL', 'Excite']
arr; // ['Microsoft', 'Apple', 'Google', 'Facebook', 'Oracle']
// 只删除,不添加:
arr.splice(2, 2); // ['Google', 'Facebook']
arr; // ['Microsoft', 'Apple', 'Oracle']
// 只添加,不删除:
arr.splice(2, 0, 'Google', 'Facebook'); // 返回[],因为没有删除任何元素
arr; // ['Microsoft', 'Apple', 'Google', 'Facebook', 'Oracle']

数组连接

1
2
3
4
let arr = ['A', 'B', 'C'];
let added = arr.concat([1, 2, 3]);
added; // ['A', 'B', 'C', 1, 2, 3]
arr; // ['A', 'B', 'C']

数组转字符串拼接

类似python的join().

如果array的元素不是字符串,会自动转成字符串然后再拼接

1
2
let arr = ['A', 'B', 'C', 1, 2, 3];
arr.join('-'); // 'A-B-C-1-2-3'

多维数组

1
2
3
let arr = [[1, 2, 3], [400, 500, 600], '-'];
let x = arr[1][1];
console.log(x); // 500

对象

可以类比python的dict,groovy的map,但是它实际上也是一个对象.因为它可以继承.

JS的对象用来描述现实世界中的某个对象.

1
2
3
4
5
6
7
8
9
// 描述小明
let xiaoming = {
name: '小明',
birth: 1990,
school: 'No.1 Middle School',
height: 1.70,
weight: 65,
score: null
};

注意: 最后一个键值对末尾不需要加,,如果加了,某些旧版本浏览器会报错.

  • 键值对的每个键(key),称为对象的属性.
  • 属性的访问通过.操作符.
  • 如果属性名包含特殊字符,要通过''括起来.
  • 如果访问一个不存在的属性,返回undefined
1
2
3
4
let xiaohong = {
name: '小红',
'middle-school': 'No.1 Middle School'
};

js的对象是动态类型,可以自由地给一个对象添加或删除属性.

1
2
3
4
5
6
7
8
9
10
11
let xiaoming = {
name: '小明'
};
xiaoming.age; // undefined
xiaoming.age = 18; // 新增一个age属性
xiaoming.age; // 18
delete xiaoming.age; // 删除age属性
xiaoming.age; // undefined
delete xiaoming['name']; // 删除name属性
xiaoming.name; // undefined
delete xiaoming.school; // 删除一个不存在的school属性也不会报错

in操作符来判断对象是否拥有某一属性

1
2
'name' in xiaoming; // true
'grade' in xiaoming; // false

但有时候这个属性不一定是xiaoming的,也有可能是xiaoming继承得到的

1
'toString' in xiaoming; // true

这是有因为toString定义在object对象中,而所有对象最终都会在原型链上指向object,所以xiaoming也会有这个属性

要判断一个属性是否自身拥有,而不是继承所得,使用hasOwnProperty()

1
2
xiaoming.hasOwnProperty('name'); // true
xiaoming.hasOwnProperty('toString'); // false

条件判断

1
2
3
4
5
6
7
8
9
10
'use strict';
let age = 20;

if (age >= 6) {
console.log('teenager');
} else if (age >= 18) {
console.log('adult');
} else {
console.log('kid');
}
  • if {} else if {} else {}
  • 注意{}不要省略
  • 注意判断的顺序: js中,如果某个条件成立,后续就不会再判断,上面的例子中age = 20,它满足了第一个条件age >= 6,就不会再往下判断,所以结果就是teenager.
  • null,undefined,0,NaN和空字符串'',都是false;其他都是true

循环

for

1
2
3
4
5
6
for x = 0;
let i;
for (i=1; i<10000; i++) { // 初始条件; 结束条件; 递增条件
x = x + i;
}
x; // 50005000
  • i=1;是初始条件,记得先声明
  • i<10000;这是判断条件,满足时就继续循环,不满足就结束
  • i++是递增条件
1
2
3
4
5
6
7
// 最常见的做法:遍历数据
let arr = ['Apple', 'Google', 'Microsoft']
let i, x;
for (i=0; i<arr.length; i++){
x = arr[i];
console.log(x);
}

for的3个循环条件都是可以省略的,如果没有结束条件,就要手动加break来跳出循环,否则就是一个死循环

1
2
3
4
5
6
7
let x = 0;
for (;;) { // 死循环
if (x > 100) { // 通过if + break手动跳出
break;
}
x ++;
}

for…in

一般用来把一个对象的所有属性一次循环出来

1
2
3
4
5
6
7
8
let o = {
name: 'Jack';
age: '20',
city: 'Beijing'
};
for (let key in o) {
console.log(key); // 'name', 'age', 'city'
}

Array也是对象,它每个元素的索引将被视为对象的属性

1
2
3
4
5
let a = ['A', 'B', 'C']
for (let i in a) {
console.log(i); // '0', '1', '2' 注意for..in对array循环得到的是String而不是Number
console.log(a[i]); // 'A', 'B', 'C'
}

while

1
2
3
4
5
6
7
let x = 0;
let n = 99;
while (n > 0) {
x = x + n;
n = n - 2;
}
x; // 2500

do…while

while的区别是,不是在每次循环前判断条件,而是在每次循环后再判断

1
2
3
4
5
let n = 0;
do {
n = n + 1;
} while (n < 100);
n; // 100

需要注意的是使用do..while的话,循环体至少会执行一次

Map和Set

JS的对象表现形式很类似其他语言的MapDict,但是JS的对象的键必须是字符串.然而实际上键是其他数据类型,比如Number,也是很合理的.所以ES6后引入了Map

1
2
3
let m = new Map();
let s = new Set();
// 没有报ReferenceError错,则表示浏览器支持ES6

Map

是一个键值对结果,具有极快的查找速度,不论这个表多大,查找速度都不会变慢.

1
2
3
4
5
6
7
8
9
let m1 = new Map([['Michael', 95], ['Bob', 75], ['Tracy', 85]]) // 初始化需要一个二维数组
let m2 = new Map() // 或者直接初始化一个空Map
// Map具有以下方法
m2.set('Adam', 67); // 添加新的key-value
m2.set('Bob', 59);
m2.has('Adam'); // 是否存在key 'Adam': true
m2.get('Adam'); // 67
m2.delete('Adam'); // 删除key 'Adam'
m2.get('Adam'); // undefined

一个key只能对应一个value,多次对同一个key放入不同value,后面的value会把前面的value冲掉

Set

是一组key的集合,且不能重复.

1
2
3
4
5
6
7
8
9
let s = new Set([1, 2, 3]); // 初始化需要提供一个array
let s = new Set(); // 或者初始化一个空set
// 重复的元素会在set中自动过滤
let s = new Set([1, 2, 3, 3, '3']);
s; // Set {1, 2, 3, "3"}
s.add(4); // 添加元素,重复添加不会有效果(去重)
s; // {1, 2, 3, "3", 4}
s.delete(3); // 删除key
s; // {1, 2, "3", 4}

iterable

遍历Array可以使用下标循环,但是遍历MapSet就没有下标.为了统一集合类型,ES6引入新的iterable类型,Array,MapSet都属于这个类型.

iterable类型的集合可以通过新的for...of循环来遍历.(也是ES6引入的语法)

1
2
3
4
5
6
let a = ['A', 'B', 'C'];
let s = new Set(['A', 'B', 'C']);
let m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]);
for (let x of a) {
console.log(x);
}

for...in有一个历史问题,它遍历的实际上是对象的属性名称.一个Array也是一个对象,它每个元素的索引都被视为一个属性.当我们手动给Array对象添加额外的属性后,for...in会带来意想不到的效果

1
2
3
4
5
let a = ['A', 'B', 'C'];
a.name = 'Hello';
for (let x in a) {
console.log(x); // '0', '1', '2', 'name'
}

for...of则修复了这个问题,它只循环集合元素本身.

1
2
3
4
5
let a = ['A', 'B', 'C'];
a.name = 'Hello';
for (let x of a) {
console.log(x); // 'A', 'B', 'C', 'Hello'
}

然而,更好的方式是使用ES5.1引入的iterable内置的forEach方法,它接收一个函数,每次迭代就自动回调该函数.

1
2
3
4
5
6
7
let a = ['A', 'B', 'C'];
a.forEach(function (element, index, array) {
// element: 指向当前元素的值
// index: 指向当前索引
// array: 指向Array对象本身,这里就是a
console.log(`${element}, index = ${index}`);
});

Set没有索引,所以回调函数的前两个参数都是元素本身

1
2
3
4
let s = new Set(['A', 'B', 'C']);
s.forEach(function (element, sameElement, set){
console.log(element);
});

Map回调函数参数依次为: value, keymap

1
2
3
4
let m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]);
m.forEach(function (value, key, map){
console.log(value)
})

JS并不要求回调函数的参数完全一致,因此可以省略不感兴趣的参数.

1
2
3
4
let a = ['A', 'B', 'C'];
a.forEach(function (element){
console.log(element); // 只获得array的element
})

JavaScript-入门
http://example.com/2024/11/04/js-begining/
作者
Peter Pan
发布于
2024年11月4日
许可协议