JavaScript

 JavaScript 󰈭 5213字

基本语法

数据变量与类型

JS中整数和浮点数统一对待,甚至可以对浮点数做%运算,但是整数数值的范围是[-2^53, 2^53] 非常神秘

表达一个字符串既可以用'也可以用"

JS有缺陷的一点在于,使用相等运算符==时,可以对不同类型的元素进行比较,这往往会导致奇怪的结果;为此,建议总是使用===这一相等运算符,该运算符会先比较类型是否相同,不同则返回false,相同再比较变量是否相同。

浮点数的相等比较依然要设置eps而且我怀疑js中的精度可能比double垃圾很多

js中null表示一个“空”的值,这和C中null表示0不同。

建议通过[]来创建一个数组,下标一样从0开始。

对象的定义方式与C++类似。

变量通过var来声明,因其类型不固定,因而这种语言称为动态语言

为了显示变量的值,可以使用语句console.log(variable)

如果没有使用``var来声明一个变量,那么它就会成为一个全局变量。可以通过‘use strict’来强制加上var`关键字。

字符串

可以通过'\x41'十六进制地表示一个字符,通过\u4e2d表示一个unicode字符。

在输入多行字符串的时候为了避免较多'\n'带来的麻烦,可以使用`` `来表示多行字符串。

1`多
23字符串`;

众所周知C++可以使用+来连接若干个string,js更进一步可以使用模版字符串来连接字符串。

1var name = 'rqdmap';
2var age = 1;
3var mes = `Hello ${name}, you are ${age} years old`;
4console.log(mes);

必须使用`` ,使用"`均无法成功显示字符串的原本信息。

通过s.length来获得字符串的长度。

通过下标可以进行随机访问字符串的任意位置,但需要注意越界访问不会报错,只会返回undefined

js中的字符串是不可通过索引值进行改变的 大概是因为C++直接操作地址而这里不知道实现了什么别的结构

但是可以通过再次赋值改变整个字符串的值。

常用的一些操作方法

toUpperCase()

toLowerCase()

indexOf(“xxxx”): 搜索子串在主串中的位置,如果没有搜索到返回-1

substring(int a): 从索引a开始到字符串结束

substring(int a, int b): 返回子串[s[a], s[b])

数组

js中的数组元素可以是不同类型,通过索引进行访问。

直接给array.length进行赋值会导致数组的长度发生变化。

可以通过索引值改变数组的某个元素。

至此可以推断出js中的字符串不是字符的数组,而是一个基本的类型

如果越界赋值也会导致数组的长度发生改变。

1var arr = [1, 2, 3];
2arr[5] = 'x';
3arr; // arr变为[1, 2, 3, undefined, undefined, 'x']

这种特性不知道是好是坏

常用操作

indeOf: 与string中的indexOf类似

slice: 与substring类似;如果不给slice传任何参数,就会整个截取该数组,据此可以很容易的完成数组的复制。

push/pop: 对数组的结尾进行添/删元素

unshift/shift:对数组的头部进行添/删元素

push和unshift可以一次添加若干个元素,而pop和shift只能删去一个元素;如果空数组继续删除,不会报错而会返回undefined.

sort: 按默认顺序排序

reverse: 反转

splice: 从指定的索引删除若干元素,然后再从该位置加入若干元素。

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

concat: 数组拼接

concat可以接受任意多个元素,并且将接收到的array不断拆开、将其元素拼接入原数组。

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

join:将数组的每个元素用指定的字符串进行连接,如果元素不是字符串则会先自动将其转换成字符串。

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

对象

JavaScript的对象是一种无序的集合数据类型,它由若干键值对组成。

如果某个属性名中含有特殊字符,可以通过object['属性名']来进行访问;当然建议按照规范进行命名,这样就可以直接通过.操作符进行访问了。

类似的,如果访问一个不存在的属性,那么会返回undefined.

由于js的对象是动态属性,所以可以很容易为一个对象增添/删除属性。

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

可以使用in来判断一个属性是否在一个对象中。

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

不过in也会判断那些继承来的属性,为了判断对象自身所拥有的属性,可以使用hasOwnProperty()方法。

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

语句结构

if语句中,js把null, undefined, 0, NaN''视为false,其余均视为true

循环语句结构与C++类似,可以在for语句初始化值的位置定义变量,也可以使用自增自减运算符,也可以使用+=系列运算符。

类似于C++的范围for语句,js有for in语句,用于将对象、数组的属性全部列出来:

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

如果想要过滤掉父类的属性,可以在循环后面加一句o.hasOwnProperty(key)的判断语句。

对array进行for in循环时,数组的索引被认为是其属性,循环得到的不是number而是string

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

Map与Set

有两种方法对Map进行初始化

 1//利用二维数组初始化一个Map
 2var m = new Map([['Michael', 95], ['Bob', 75], ['Tracy', 85]]);
 3m.get('Michael'); // 95
 4
 5//也可以初始化一个空Map,然后利用以下方法添加元素。
 6var m = new Map(); // 空Map
 7m.set('Adam', 67); // 添加新的key-value
 8m.set('Bob', 59);
 9m.has('Adam'); // 是否存在key 'Adam': true
10m.get('Adam'); // 67
11m.delete('Adam'); // 删除key 'Adam'
12m.get('Adam'); // undefined

一个键只能对应一个值,所以多次对一个键赋值会覆盖掉之前的值。

类似地,可以初始化一个空Set或者用一维数组初始化一个Set。重复元素自动被删除。

1var s = new Set([1, 2, 3]);
2s.add(4);
3s; // Set {1, 2, 3, 4}
4s.add(4);
5s; // 仍然是 Set {1, 2, 3, 4}
6s.delete(4);
7s; // Set{1, 2, 3}

iterable

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

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

而以往的for in就无法遍历set和map。

for in循环访问的是对象的属性,而for of只循环集合本身的元素。

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

函数

函数定义

不同于C++,js中函数不定义返回值类型,直接用function进行定义即可。

1function abs(a){return a < 0? -a: a;}

如果函数没有返回值,那么该函数也会返回undefined.

此外,js中函数也是一个对象,所以可以使用以下的匿名函数定义一个函数

1var abs = function(a){return a < 0? -a: a;};

不过由于第二种方法是赋值语句,所以记得加上;

函数调用

js传入参数的数量不被严格的限制:如果多于需要的数量,那么多余的将不会被使用;如果少于需要的数量,函数中的参数会收到undefined

有一种方法可以判断是否收到undefined

1var abs = function(a){
2    if(typeof a !== 'number') throw 'Not a number';
3    return a < 0? -a: a;
4}

在函数内部可以使用**arguments**来指向所有的传入参数,argument也有属性length,可以用length来判断传入的参数是否足够。

如果传入的参数过多,可以使用ES6标准引入的rest参数:

 1function foo(a, b, ...rest) {
 2    console.log('a = ' + a);
 3    console.log('b = ' + b);
 4    console.log(rest);
 5}
 6
 7foo(1, 2, 3, 4, 5);
 8// 结果:
 9// a = 1
10// b = 2
11// Array [ 3, 4, 5 ]
12
13foo(1);
14// 结果:
15// a = 1
16// b = undefined
17// Array []

rest必须写在最后,并用...标示。

js会自动在行末添加分号,所以要注意return和其返回值不要换行分隔。

变量作用域

打开'use strict'后就禁止不加var声明变量,那么var变量作用域只在当前函数内。

如果嵌套的内层函数有与外层同名的变量,与C++一样的覆盖规则是成立的。

JS在函数中有变量提升这一特性,在运行函数前扫描整个函数,将变量的声明提升到函数的最前面。

所以可以写一些快乐的毒瘤代码

如果不在任何函数内声明一个变量,那么该变量就是全局变量。全局变量被绑定在了JS默认的一个全局对象window的属性上。因而可以通过window.来访问一个全局变量。

当多个文件同时工作时,为了避免全局变量命名的冲突,可以将所有的全局变量绑定在一个全局变量上,这样就可以实现“命名空间“的工作。

 1// 唯一的全局变量MYAPP:
 2var MYAPP = {};
 3
 4// 其他变量:
 5MYAPP.name = 'myapp';
 6MYAPP.version = 1.0;
 7
 8// 其他函数:
 9MYAPP.foo = function () {
10    return 'foo';
11};

事实上,因为函数内的变量会被提升到首部,所以如下的C++风格代码并不能完成它本来想完成的功能:

1function fun(){
2 		for(var i = 1; i <= n; i++) 
3      //do something;
4}

为此,ES6引入新的关键字let,可以声明一个块级作用域的变量,利用let关键字就可以完成上述的操作了。

ES6也引入了const来声名一个常量。

1const pi = 3.14;

解构赋值

这是ES6引入的一种可以对多个变量同时赋值的语法。

1//直接对三个变量进行赋值
2var [x, y, z] = ['hello', 'JavaScript', 'ES6'];
3
4//如果右值有嵌套,那么多个变量之间也要保持格式一致。
5let [x, [y, z]] = ['hello', ['JavaScript', 'ES6']];
6
7//可以忽略掉某些元素
8let [, , z] = ['hello', 'JavaScript', 'ES6']; 

也可以从一个对象中抽取出若干属性进行赋值

1var person = {
2    name: '小明',
3    age: 20,
4    gender: 'male',
5    passport: 'G-12345678',
6    school: 'No.4 middle school'
7};
8var {name, age, passport} = person;

如果使用了不存在的属性名将会获得undefined

可以通过如下语句将某属性的值赋值到另一个名称的属性上

1let {name, passport:id} = person;

解构赋值也可以设置默认值,避免产生undefined的问题

1var person = {
2    name: '小明',
3    age: 20,
4    gender: 'male',
5    passport: 'G-12345678'
6};
7
8// 如果person对象没有single属性,默认赋值为true:
9var {name, single = true} = person;

如果想要对已经声明过的变量做解构赋值,需采用如下形式我也不知道为什么

1({x, y} = { name: '小明', x: 100, y: 200});

函数方法

在JS中可以使用如下语法定义一个对象专用的方法:

1var xiaoming = {
2    name: '小明',
3    birth: 1990,
4    age: function () {
5        var y = new Date().getFullYear();
6        return y - this.birth;
7    }
8};

对象也可以使用一种“全局方法”。

1function getage() {
2    var y = new Date().getFullYear();
3    return y - this.birth;
4}
5var xiaoming = {
6    name: '小明', birth: 1990,
7    age: getage,
8};
9console.log(xiaoming.age());

好处在于可以按需使用“全局方法”,但是如果不加任何对象的调用了这个方法则会返回undefined。这是因为该对象默认成为window,方法中的this指向了window。为了解决this的正确指向问题,可以通过strict模式来使得这种情况下this总指向undefined来及时的报错避免该问题。

此外,在对象某个方法中的子方法,无法通过this指向原对象,只会指向undefined,实际使用时可以通过创建局部变量来解决。

可以利用apply方法来手动指定全局方法的this对象并给出参数。

 1function getAge() {
 2    var y = new Date().getFullYear();
 3    return y - this.birth;
 4}
 5var xiaoming = {
 6    name: '小明',
 7    birth: 1990,
 8    age: getAge
 9};
10getAge.apply(xiaoming, []); // 25, this指向xiaoming, 参数为空

第一个参数为对象名,第二个参数为一个数组,其中元素为要传入给函数的参数。

方法call与apply基本类似,唯一不同的地方在于call将函数需要的参数一个个列出为形参。

利用apply可以对原函数进行加“壳”

1var oldfun = fun;
2window.fun = function(){
3  //dosomething
4  return oldfun.apply(xxx, [...])
5}

vscode可能没有配置完备,导致window.xxxx系列函数无法在vscode执行;然而切换到chrome的控制台便可以顺利执行。

window is a browser thing that doesn’t exist on Node.

高阶函数

高阶函数是指接受其他函数作为参量的函数。

并附上回调函数的一个解释: A “callback” is any function that is called by another function which takes the first function as a parameter.

map方法

指定一个映射方法,通过map方法将原本的元素一一进行置换。

1'use strict'
2
3function pow(a){return a * a;}
4var arr = [1, 2, 3, 4];
5console.log(arr.map(pow));
6//[ 1, 4, 9, 16 ]

reduce方法

指定一个必须传入2个元素的方法,将序列前两项按照该方法做运算后的结果继续与第三个元素做运算,其后以此类推。

1[x1, x2, x3, x4].reduce(f) = f(f(f(x1, x2), x3), x4)

给出一些实例:

1var arr = [1, 3, 5, 7, 9];
2arr.reduce(function (x, y) {
3    return x + y;
4}); // 25
5
6var arr = [1, 3, 5, 7, 9];
7arr.reduce(function (x, y) {
8    return x * 10 + y;
9}); // 13579

JS中如何将字符转化为整数?

如果不使用parseInt系列方法:

  1. 使用Number(c) - Number(‘0’)来类似于C++的转化为Int

  2. 通过运算符自动解析字符串为数值

     1'use strict'
     2
     3var s = '12345';
     4var arr = new Array;
     5for(let i = 0; i < s.length; i++)  arr[i] = s[i] * 1;
     6//[ 1, 2, 3, 4, 5 ]
     7
     8for(let i = 0; i < s.length; i++)  arr[i] = s[i] - 0;
     9//[ 1, 2, 3, 4, 5 ]
    10
    11//无法通过'+'将字符串解析为数值!
    12for(let i = 0; i < s.length; i++)  arr[i] = s[i] + 0;
    13//[ '10', '20', '30', '40', '50' ]
    

如果想要使用这类不是很知道实现原理的内置函数时,就可能会产生如下意想不到结果。

1console.log(['1', '2', '3'].map(parseInt));
2//[ 1, NaN, NaN ]

这是因为parseInt()实际会接受两个参数,第一个是表达式字符串,第二个是基数。而在使用map传入时会将字符作为字符串表达式、序号为基数传入parstInt函数,那么就无法正确解析{‘2’, 1}和{‘3’, 2}表达式了。

嗨! 这里是 rqdmap 的个人博客, 我正关注 GNU/Linux 桌面系统, Linux 内核, 后端开发, Python, Rust 以及一切有趣的计算机技术! 希望我的内容能对你有所帮助~
如果你遇到了任何问题, 包括但不限于: 博客内容说明不清楚或错误; 样式版面混乱等问题, 请通过邮箱 rqdmap@gmail.com 联系我!
修改记录:
  • 2023-09-01 18:14:49单独划分ACM专题; 移动部分博客进入黑洞归档
  • 2023-05-29 23:05:14大幅重构了python脚本的目录结构,实现了若干操作博客内容、sqlite的助手函数;修改原本的文本数 据库(ok)为sqlite数据库,通过嵌入front-matter的page_id将源文件与网页文件相关联
  • 2023-05-08 21:44:36博客架构修改升级
  • 2022-11-16 01:27:34迁移老博客文章内容
JavaScript