Javascript笔记

Javascript笔记

七月 01, 2021

Javascript笔记


Javascript简介

  • Javascript是世界上最流行的脚本语言,因为你在电脑、手机、平板上浏览的所有的网页,以及无数基于HTML5的手机App,交互逻辑都是由JavaScript驱动的。

  • 简单地说,JavaScript是一种运行在浏览器中的解释型的编程语言。

JavaScript快速入门

有一位先哲说的好啊,学习一门语言先看文档,看文档前先看快速入门。

快速入门


基本语法

1. 语法规则

​ 首先来介绍一下Javascript的语法规则。

  • Js语法和Java类似,每个句子以;结尾,语句块用{···code···}。(但是,JavaScript并不强制要求在每个语句的结尾加;,浏览器中负责执行JavaScript代码的引擎会自动在每个语句的结尾补上;
  • Js语法的注释为///* … */

2. 基本数据类型

  1. Number

    JavaScript不区分整数和浮点数,统一用Number表示,以下都是合法的Number类型。

    1
    2
    3
    4
    5
    6
    100; //整形
    2.71828; //浮点型
    -100; //负数
    1.23e3; //科学计数法
    NaN; //Not a Number。无法计算时使用NaN表示。
    Infinity; //无限大。当数值超过了Js的Number所能表达的最大值时,则表示为Infinity

    四则运算和Python相似。即(+-**/%)但是在这里提一下整除。

    1
    2
    3
    4
    parseInt(a / b); //丢弃小数部分,保留整数部分
    Math.ceil(a / b); //向上整除 4/3=2
    Math.floor(a / b); //向下整除 4/3=1
    Math.round(a / b); //四舍五入
  2. 字符串

    JavaScript的字符串就是用''""括起来的字符表示。

    但是这里有许多的特例。如果'本身也是一个字符,那就可以用""括起来。如果字符串内部既包含'又包含",可以用转义字符\来标识。

    exp: 'I\'m \"OK\"!';

    然后需要了解转义字符\n, \t, \\

    ASCII字符可以以\x##形式的十六进制表示,例如:

    1
    '\x41'; // 完全等同于 'A'

    还可以用\u####表示一个Unicode字符:

    1
    '\u4e2d\u6587'; // 完全等同于 '中文'

    由于多行字符串用\n写起来比较费事,所以ES6标准新增了一种多行字符串的表示方法,用反引号`...`表示。

    下面介绍一下字符串的操作

    字符串支持拼接。string_A + string_B

    字符串支持模板字符串。(ES6支持)

    1
    2
    3
    var name = '小明';
    var age = 20;
    var message = `你好, ${name}, 你今年${age}岁了!`; //使用$引用变量(需要使用反引号进行引用)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var s = "Hello World!";
    s.length; //返回字符串长度
    s[0]; //H
    s[11]; //!
    s[12]; //undefined
    s.toUpperCase(); //转大写
    s.toLowCase(); //转小写
    s.indexOf("world"); //6
    s.indexOf("World"); //-1
    s.substring(0, 5); //从索引0~5(不包含5)hello
    s.substring(7); //索引7到最后 world!
  3. 布尔值

    布尔值的解释比较简单,和一般编程语言都是类似的。这里说一下几个符号

    1
    2
    3
    && // and
    || // or
    ! // not

    接下来就是比较重要的比较运算符了。

    当我们对数据类型进行比较的时候(任意数据类型),我们会得到一个布尔值。例如

    1
    2
    3
    4
    5
    6
    2>5; //false
    2<5; //true
    1==1; //true
    // 特别的
    false == 0; //true
    false === 0; //false

    不难看出,出现了特例1=====

    ==比较时,它会自动转换数据类型再比较,很多时候,会得到非常诡异的结果

    ===比较时,它不会自动转换数据类型,如果数据类型不一致,返回false,如果一致,再比较。

    因此,绝大部分时间请使用===进行比较

    特例2NaN

    1
    NaN === NaN; //false

    因此,唯一可以判断NaN的方法是通过isNaN()函数。

    特例3:浮点数比较

    请看代码

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

    这个特性和C语言一样。因为计算机无法精确表示无限循环小数。

    要比较两个浮点数是否相等,只能计算它们之差的绝对值,看是否小于某个阈值:

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

    特例4:null和undefined

    null很好理解。和其他编程语言类似,null就是一个空值。就是Java里的nullpython里的None

    undefined也很好理解,就是未定义的意思。但是使用起来有些讲究。因为它表示的是值未定义。一般情况下,仅仅在判断函数参数是否传递的情况下有用。其余时刻都用null

  4. 数组

    数组是一组按顺序排列的集合,集合的每个值称为元素。JavaScript的数组可以包括任意数据类型。例如:

    1
    [1, 2, 3.14, 'Hello', null, true];

    介绍两种创建数组的方法。

    方法一:new Array();

    方法二:var arr = [];

    当然对于我来说,我更喜欢第二种方式。因为可读性更高,别人一看到[..., ...]就知道是数组。

    基本用法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    var arr = [1, 2, 3]; //定义数组arr
    arr.length; //arr的长度

    arr.indexOf(1); // 0
    arr.indexOf(4); // -1

    arr.slice(0, 2); // [1, 2]
    arr.slice(2); // [3]
    // slice可以不带参数,见特例3

    arr.push(1); // arr -> [1, 2, 3, 1]
    arr.push('1'); // arr -> [1, 2, 3, '1']
    arr.push(['1']); // arr -> [1, 2, 3, ['1']]
    arr.pop(); // arr -> [1, 2]
    // push/pop向数组尾部添加/删除一个元素

    arr.unshift(0); // arr -> [0, 1, 2, 3]
    arr.shift(); // arr -> [2, 3]
    // unshift/shift向数组头部添加/删除一个元素

    arr.sort(); // arr会排序。(Array的排列顺序是根据Ascii码大小进行的排序)

    arr.reverse(); // arr -> [3, 2, 1]

    arr.splice(0, 2); // arr -> [3], return [1, 2]
    arr.splice(0, 2, "1", "2"); // arr -> ['1', '2', 3], return [1, 2]
    // splice()方法是修改Array的“金钥匙”。其中(起始位置, 个数, [替换元素])
    // splice()的返回值是删掉的元素, 改变的是arr。

    var new_arr;
    new_arr = arr.concat([4, 5, 6]); // new_arr = [1,2,3,4,5,6]
    new_arr = arr.concaat(4, 5, 6); // new_arr = [1,2,3,4,5,6]
    new_arr = arr.concat(4, [5, 6]); // new_arr = [1,2,3,4,5,6]
    // 需要注意的是,arr本身的值没有变,还是[1,2,3]
    // 由例子来看,concat()方法本身可以自动拆分

    arr.join('-'); // 1-2-3
    // 如果arr的元素不是字符串形式,则自动转换为字符串形式。

    多维数组形同Java,不再赘述。

    //特例1
    arr.length = 6; // arr -> [1,2,3,undefined,undefined,undefined]
    arr.length = 2; // arr -> [1,2]

    //特例2
    arr[4] = 5; // arr -> [1, 2, 3, undefined, 5]

    //特例3
    var copy = arr.slice(); // copy = [1, 2, 3]
    copy === arr; // false
    // 由此我们可以复制一个arr。

    //特例4
    var arr = [];
    arr.pop(); // 空数组继续pop不会报错,而是返回undefined
    arr; // []

    //特例5
    var arr = [];
    arr.shift(); // 空数组继续shift不会报错,而是返回undefined
    arr; // []
  5. 对象

    JavaScript的对象是一组由键-值组成的无序集合,例如:

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

    JavaScript对象的键都是字符串类型,值可以是任意数据类型。上述person对象一共定义了6个键值对,其中每个键又称为对象的属性,例如,personname属性为'Bob'zipcode属性为null

    要获取一个对象的属性,我们用对象变量.属性名的方式:

    1
    2
    person.name; // 'Bob'
    person.zipcode; // null

    访问属性是通过.操作符完成的,但这要求属性名必须是一个有效的变量名。如果属性名包含特殊字符,就必须用''括起来:

    1
    2
    3
    4
    var xiaohong = {
    name: '小红',
    'middle-school': 'No.1 Middle School'
    };

    xiaohong的属性名middle-school不是一个有效的变量,就需要用''括起来。访问这个属性也无法使用.操作符,必须用['xxx']来访问:

    1
    2
    3
    xiaohong['middle-school']; // 'No.1 Middle School'
    xiaohong['name']; // '小红'
    xiaohong.name; // '小红'

    实际上JavaScript对象的所有属性都是字符串,不过属性对应的值可以是任意数据类型。

    如果访问一个不存在的属性会返回什么呢?JavaScript规定,访问不存在的属性不报错,而是返回undefined

    1
    2
    3
    4
    var xiaoming = {
    name:'xiaoming';
    }
    xiaoming.age; // undefined

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var 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属性也不会报错

    如果我们要检测xiaoming是否拥有某一属性,可以用in操作符:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    var xiaoming = {
    name: '小明',
    birth: 1990,
    school: 'No.1 Middle School',
    height: 1.70,
    weight: 65,
    score: null
    };
    'name' in xiaoming; // true
    'grade' in xiaoming; // false

    不过要小心,如果in判断一个属性存在,这个属性不一定是xiaoming的,它可能是xiaoming继承得到的:

    1
    'toString' in xiaoming; // true

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

    要判断一个属性是否是xiaoming自身拥有的,而不是继承得到的,可以用hasOwnProperty()方法:

    1
    2
    3
    4
    5
    var xiaoming = {
    name: '小明'
    };
    xiaoming.hasOwnProperty('name'); // true
    xiaoming.hasOwnProperty('toString'); // false

3. 变量

​ 定义规则:变量名是大小写英文、数字、$_的组合,且不能用数字开头。变量名也不能是JavaScript的关键字,如ifwhile等。

​ 申明一个变量用var语句,比如:

1
2
3
4
5
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

剩下的赋值问题都是基础知识,不赘述了。

4. strict模式

​ 这里只是提一嘴。(并不是重点)

​ JavaScript在设计之初,为了方便初学者学习,并不强制要求用var申明变量。这个设计错误带来了严重的后果:如果一个变量没有通过var申明就被使用,那么该变量就自动被申明为全局变量:

1
i = 10; // i现在是全局变量

​ 在同一个页面的不同的JavaScript文件中,如果都不用var申明,恰好都使用了变量i,将造成变量i互相影响,产生难以调试的错误结果。

​ 使用var申明的变量则不是全局变量,它的范围被限制在该变量被申明的函数体内,同名变量在不同的函数体内互不冲突。

​ 为了修补JavaScript这一严重设计缺陷,ECMA在后续规范中推出了strict模式,在strict模式下运行的JavaScript代码,强制通过var申明变量,未使用var申明变量就使用的,将导致运行错误。

​ 启用strict模式的方法是在JavaScript代码的第一行写上:

1
'use strict';

5. 条件判断

​ 条件判断部分,Javascript使用的结构和C和Java一样。

1
2
3
4
5
6
7
8
9
if (xxxx){
xxx;
}
else if (xxxx){
xxx;
}
else{
xxx;
}

嵌套和其他语言一样,不再赘述。

JavaScript把nullundefined0NaN和空字符串''视为false,其他值一概视为true,因此上述代码条件判断的结果是true

6. 循环

​ Javascript有三种循环方式forwhiledo ... while

​ 和C/C++很类似,但也有特殊点。

  1. for循环

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    var x = 0;
    vaar i;
    for(i = 0; i < 10; i++){
    x = x + i;
    }

    // for ... in ...用法
    // 1.
    var o = {
    name: 'Jack',
    age: 20,
    city: 'Beijing'
    };
    for (var key in o) {
    console.log(key); // 'name', 'age', 'city'
    }

    // 2. 过滤掉对象的继承属性
    var o = {
    name: 'Jack',
    age: 20,
    city: 'Beijing'
    };
    for (var key in o) {
    if (o.hasOwnProperty(key)) {
    console.log(key); // 'name', 'age', 'city'
    }
    }

    // 3. Array对象 -> 类似python
    var a = ['A', 'B', 'C'];
    for (var i in a) {
    console.log(i); // '0', '1', '2'
    console.log(a[i]); // 'A', 'B', 'C'
    }
2. while循环

while循环不需要这么麻烦。只需要括号里的条件为`false`,则退出循环。

1
2
3
while(true){
break;
}
3. do...while循环
1
2
3
4
5
var n = 0;
do {
n = n + 1;
} while (n < 100);
// n -> 100
需要注意的是,do ... while至少执行一次。

7. Map & Set (ES6)

1. Map

Map是一组键值对的结构,具有极快的查找速度。

​ 访问Map的方法也很简单,通过访问键得到值。

​ 初始化Map需要一个二维数组,或者直接初始化一个空MapMap具有以下方法:

1
2
3
4
5
6
7
var m = new Map(); // 空Map
m.set('Adam', 67); // 添加新的key-value
m.set('Bob', 59);
m.has('Adam'); // 是否存在key 'Adam': true
m.get('Adam'); // 67
m.delete('Adam'); // 删除key 'Adam'
m.get('Adam'); // undefined

​ 由于一个key只能对应一个value,所以,多次对一个key放入value,后面的值会把前面的值冲掉:

1
2
3
4
var m = new Map();
m.set('Adam', 67);
m.set('Adam', 88);
m.get('Adam'); // 88

​ 相比起之前说到的对象,对象最大的劣势在于,键值对的键只能使用字符串,而Map的键不受影响。

2. Set

SetMap类似,也是一组key的集合,但不存储value。由于key不能重复。所以,在Set中,没有重复的key。

​ 要创建一个Set,需要提供一个Array作为输入,或者直接创建一个空Set

1
2
var s1 = new Set(); // 空Set
var s2 = new Set([1, 2, 3]); // 含1, 2, 3

​ 重复元素在Set中自动被过滤:

1
2
var s = new Set([1, 2, 3, 3, '3']);
s; // Set {1, 2, 3, "3"}

​ 通过add(key)方法可以添加元素到Set中,可以重复添加,但不会有效果:

1
2
3
4
s.add(4);
s; // Set {1, 2, 3, 4}
s.add(4);
s; // 仍然是 Set {1, 2, 3, 4}

通过delete(key)方法可以删除元素:

1
2
3
4
var s = new Set([1, 2, 3]);
s; // Set {1, 2, 3}
s.delete(3);
s; // Set {1, 2}

8. Iterable(ES6)

​ 遍历Array可以采用下标循环,遍历MapSet就无法使用下标。为了统一集合类型,ES6标准引入了新的iterable类型,ArrayMapSet都属于iterable类型。

​ 具有iterable类型的集合可以通过新的for ... of循环来遍历。

​ 使用for ... of循环遍历集合,用法如下:

1
2
3
4
5
6
7
8
9
10
11
12
var a = ['A', 'B', 'C'];
var s = new Set(['A', 'B', 'C']);
var m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]);
for (var x of a) { // 遍历Array
console.log(x);
}
for (var x of s) { // 遍历Set
console.log(x);
}
for (var x of m) { // 遍历Map
console.log(x[0] + '=' + x[1]);
}

​ 此时我们抛出一个疑问,这么看来,for…in 和 for…of没有什么区别啊。

​ 这个问题需要举个例子来说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var a = ['A', 'B', 'C'];
a.name = 'Hello';
for (var x in a) {
console.log(x); // '0', '1', '2', 'name'
}
// for ... in

var a = ['A', 'B', 'C'];
a.name = 'Hello';
for (var x of a) {
console.log(x); // 'A', 'B', 'C'
}
// for ... of

// for ... in 遍历的实际上是对象的属性名称。一个Array数组实际上也是一个对象,它的每个元素的索引被视为一个属性。for ... in循环将把name包括在内,但Array的length属性却不包括在内。
// for ... of循环则完全修复了这些问题,它只循环集合本身的元素:

然后我们要提一下iterable内置的forEach方法,它接收一个函数,每次迭代就自动回调该函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var a = ['A', 'B', 'C'];
a.forEach(function (element, index, array) {
// element: 指向当前元素的值
// index: 指向当前索引
// array: 指向Array对象本身
console.log(element);
});
// Array

var s = new Set(['A', 'B', 'C']);
s.forEach(function (element, sameElement, set) {
console.log(element);
});
// Set
// Set与Array类似,但Set没有索引,因此回调函数的前两个参数都是元素本身

var m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]);
m.forEach(function (value, key, map) {
console.log(value);
});
// Map
// Map的回调函数参数依次为value、key和map本身

如果对某些参数不感兴趣,由于JavaScript的函数调用不要求参数必须一致,因此可以忽略它们。例如,只需要获得Arrayelement

1
2
3
4
var a = ['A', 'B', 'C'];
a.forEach(function (element) {
console.log(element);
});

函数

1. 函数

  1. 函数定义

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    function abs(x) {
    if (x >= 0) {
    return x;
    } else {
    return -x;
    }
    }
    // 第一种。如果没有return语句,函数执行完毕后也会返回结果,只是结果为undefined。

    var abs = function (x) {
    if (x >= 0) {
    return x;
    } else {
    return -x;
    }
    };
    // 第二种。由于JavaScript的函数也是一个对象,上述定义的abs()函数实际上是一个函数对象,而函数名abs可以视为指向该函数的变量。
    // 在这种方式下,function (x) { ... }是一个匿名函数,它没有函数名。但是,这个匿名函数赋值给了变量abs,所以,通过变量abs就可以调用该函数。

    上述两种定义完全等价,注意第二种方式按照完整语法需要在函数体末尾加一个;,表示赋值语句结束。

2. 函数调用

调用函数时,按顺序传入参数即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
abs(10); // return 10
abs(-9); // return -9
// 调用函数时,按照顺序传入参数即可

abs(10, 'blablabla'); // 返回10
abs(-9, 'haha', 'hehe', null); // 返回9
//由于 JavaScript 允许传入任意个参数而不影响调用,因此传入的参数比定义的参数多也没有问题,虽然函数内部并不需要这些参数

abs(); // NaN
// 传入的参数比定义的少也没有问题。此时abs(x)函数的参数x收到的是 undefined , 计算结果是 NaN 。

function abs(x) {
if (typeof x !== 'number') {
throw 'Not a number';
}
if (x >= 0) {
return x;
} else {
return -x;
}
}
// 如果想避免接收到 undefined ,我们可以对参数进行过滤,抛出异常。
3. argument