🪴 无人之路

Search

Search IconIcon to open search

Hands on JavaScript

Last updated Dec 22, 2022 Edit Source

# JavaScript基础

最近工作中会涉及到前端工作,因此要用到JavaScript,故而学习一波。参考:

# 环境准备:Jupyter Notebook + IJavaScript

打算在Jupyter Notebook中使用JavaScript,便于交互式的学习和探查。于是找到了IJavascript这个Jupyter Notebook的JavaScript Kernel. 其安装和使用参考主页:http://n-riesco.github.io/ijavascript/

注意📢:在安装的过程中(brew install pkg-config node zeromq),可能会出现“Error: No such file or directory @ rb_sysopen”的错误,可以参考:https://blog.csdn.net/weixin_43770545/article/details/127715990, 做相应的问题排查和修复。

安装好IJavaScript之后,可以直接通过jupyter-lab(或者jupyter notebook)命令启动,然后选择JavaScript内核的Notebook。

image-20221222100114102

然后就能在其中测试和探索JavaScript的各种功能。

下面是跟随https://developer.mozilla.org/en-US/docs/Web/JavaScript/Language_Overview 教程一路run下来的结果。

# Data types:数据类型

JavaScript中有7中原生类型:

其他的数据都属于Object,包括:

疑问❓:没有dict或者map吗?

# Numbers

JavaScript有两种数字类型:Number和Bigint。

Number既能表示整数(-(2^53-1)~2^53-1),又能表示浮点数(最大值:1.79*10^308)

1
console.log(3/2); // 1.5, not 1
1.5
1
2
// 浮点数可以存在不精确的情况
console.log(0.1+0.2)
0.30000000000000004
1
2
3
4
5
6
7
// Number literals can also have prefixes to indicate the base 
// (binary, octal, decimal, or hexadecimal), or an exponent suffix.

console.log(0b111110111); // 503
console.log(0o767); // 503
console.log(0x1f7); // 503
console.log(5.03e2); // 503
503
503
503
503

Bigint用来指定数值是整数,它是在数字后面跟一个后缀:n

1
console.log(-3n)
-3n
1
// console.log(-3.1n)
1
console.log(-3n/2n)
-1n
1
2
// Bigint与Number不能混合运算
// console.log(-3n + 2); //TypeError: Cannot mix BigInt and other types, use explicit conversions

Math是一个提供标准数据运算的Object。

1
Math.sin(3.5);
-0.35078322768961984
1
2
var r0 = 2;
const circumference0 = 2 * Math.PI * r0;
1
circumference0
12.566370614359172

可以使用下面的方式做数字和字符串之间的转换:

1
parseInt('123');
123
1
parseInt('123n');
123
1
parseInt('-123');
-123
1
parseInt('123.4'); // 可以解析小数,只是得到其整数部分
123
1
parseFloat('123.4'); 
123.4
1
parseInt('0b111110111'); // 不能正确解析其他进制的数
0
1
Number('0b111110111'); 
503
1
Number('0o767'); 
503
1
+'0o767'
503
1
-'0b111110111'
-503

NaN表示Not a Number,比如解析一个非数字表达;传入NaN做运算,会返回NaN

Infinity表示无穷大,除以0会产生这个值。它有正负之分。

1
parseInt('Not a Number');
NaN
1
NaN + 1
NaN
1
1/0
Infinity
1
-1/0
-Infinity

# String

JavaScript中的字符串就是Unicode(准确说是UTF-16编码)字符的序列。

1
2
console.log('Hello, world');
console.log('你好,世界!');
Hello, world
你好,世界!
1
2
3
// 单引号和双引号都OK
console.log("Hello, world");
console.log("你好,世界!");
Hello, world
你好,世界!
1
2
// 字符和字符串之间也没有差别:字符就是长度为1的字符串
'Hello'[1] === 'e'
true
1
2
// 字符串长度:length属性
'Hello'.length
5
1
2
3
// 字符串相加
const age = 25;
console.log('I am ' + age + ' years old.') // String concatenation
I am 25 years old.
1
2
// 也可用字符串模板:使用反引号 `` + ${}
console.log(`I am ${age} years old.`)
I am 25 years old.

# 其他类型

在JavaScript中,null表示deliberate non-value(故意的空值),undefined表示absence of value(没有定义的值)。null只能通过null关键字获取,而undefined可以通过下面的多种方式获取:

1
2
3
4
5
function returnNothing() {
    return
}

console.log(returnNothing())
undefined
1
2
3
var arr = [1, 2, 3]

console.log(arr.iDontExist)
undefined
1
2
let xxx;
console.log(xxx)
undefined

JavaScript的布尔值truefalse,任何值都可以转换成布尔值,其规则如下:

1
Boolean("")
false
1
Boolean(0)
false
1
Boolean(0.0)
false
1
Boolean(undefined)
false
1
Boolean('Hello')
true

# Variables: 变量

JavaScript中,变量可以由下面的三个关键字申明:let, const, var。他们之间的差别可以参考:

JavaScript 中的 Var、Let 和 Const 有什么区别

总结其异同点:

现在let是主流的申明方式。

下面通过几个例子来说明。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// var的作用域是全局或者函数范围

var greeter = 'hi';

function newFunc() {
    var hello = 'hello';
}

console.log(greeter);
// console.log(hello); // ReferenceError: hello is not defined
hi
1
2
3
4
5
6
7
// var变量可以重新申明和修改
var greeter = 'hi';
greeter = 'hello';
console.log(greeter);

var greeter = 'hi hi hi';
console.log(greeter);
hello
hi hi hi
1
2
3
4
5
// var 的变量提升
// 变量提升是 JavaScript 的一种机制:在执行代码之前,变量和函数声明会移至其作用域的顶部。这意味着如果我们这样做:

console.log(greeter0);
var greeter0 = 'hi 0';
undefined
1
2
3
4
// 上面的代码会被解释为:
var greeter0;
console.log(greeter0);
greeter0 = 'hi 0';
hi 0





'hi 0'
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// var的问题:因为全局作用域造成的不希望发生的赋值和引用

var greeter1 = 'hi';
var times = 4;

if(times > 3) {
    var greeter1 = 'Hello'
}

console.log(greeter1)
Hello

上例中,if block中的greeter1影响了全局greeter1的值。这有可能是我们不希望看到的。这就是使用let的原因:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// let的作用域是block {} 级别的

let greeter3 = 'hi';
let times1 = 4;

if(times1 > 3) {
    let greeter3 = 'Hello';
    console.log(greeter3);
}

console.log(greeter3);
Hello
hi
1
2
3
4
5
// let变量能被修改,但不能被重新申明
let greeter5 = 'hi';
greeter5 = 'hello';

console.log(greeter5);
hello
1
// let greeter5 = 'hi'; //SyntaxError: Identifier 'greeter5' has already been declared

constlet类似,只不过其申明的变量必须保持常量,不能被修改。

1
2
const greeter6 = 'hi';
// greeter6 = 'hello'; //TypeError: Assignment to constant variable.

不过其申明的object类型的变量,其属性是可以被改变的。

1
2
3
4
5
6
7
8
const obj_greeter = {
    message: 'hi',
    times: 4
}

obj_greeter.message = 'hello'

console.log(obj_greeter)
{ message: 'hello', times: 4 }
1
2
3
obj_greeter.receiver = 'Jack'

console.log(obj_greeter)
{ message: 'hello', times: 4, receiver: 'Jack' }

JavaScript是动态类型,意味着同一个变量名可以指向不同类型的数据。

1
2
let a = 1;
a = 'foo';
'foo'

# Operators:运算符

JavaScript支持的运算符包括:

更详尽的运算符说明,见 链接

下面是一些示例。

1
2
// 求余数
123 % (3.5)
0.5
1
2
// 注意顺序
"3" + 4 + 5;
'345'
1
3 + 4 + '5';
'75'
1
2
3
4
5
6
7
// 双等号 vs 三等号

console.log(123 == '123');
console.log(1 == true);

console.log(123 === '123');
console.log(1 === true);
true
true
false
false
1
2
3
// 逻辑运算
const a1 = 0 && 'Hello'; // 0 because 0 is "falsy"
console.log(a1);
0
1
2
const b1 = "Hello" || "world"; // "Hello" because both "Hello" and "world" are "truthy"
console.log(b1);
Hello

# Grammar: 语法风格

JavaScript的语法风格接近于C语言。有下面几点值得注意:

# Control Structure: 控制结构

和大多数语言一样,JavaScript包括下面的控制结构:

# Objects: 对象类型

JavaScript的object类型用来放键值对key-value数据,相当于Python中的dict. object是非常动态的, 它的属性可以随时被添加、删除、重排、突变mutated。object的key总是string或者symbol。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
const obj = {
    name: 'Carrot',
    for: 'Max',
    details: {
        color: 'orange',
        size: 12
    }
}

obj
{ name: 'Carrot', for: 'Max', details: { color: 'orange', size: 12 } }
1
2
3
4
5
// dot notation: 只能是一个静态的标识符
console.log(obj.name);

// bracket notation:可以是一个动态的变量
console.log(obj['name'])
Carrot
Carrot
1
2
3
4
// 用变量来标识一个key
let userName1 = 'nick';
obj[userName1] = 'Catty';
obj
{
  name: 'Carrot',
  for: 'Max',
  details: { color: 'orange', size: 12 },
  nick: 'Catty'
}
1
2
3
// 链式访问
console.log(obj.details.color);
console.log(obj['details']['size']);
orange
12
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// object是按照引用传值
const obj1 = {}

function doSth(o) {
    o.x = 1;
}

doSth(obj1);

obj1
{ x: 1 }
1
2
3
4
5
// 引用
const obj2 = obj1;
obj1.y = 2;

obj2
{ x: 1, y: 2 }

# Arrays: 数组

JavaScript中的数组是一种特殊的object,通过[index]来访问元素。数组长度通过.length来获取。

1
2
const arr1 = ['dog', 'cat', 'hen'];
a.length
3
1
2
3
4
// 可以给任意非负整数的下标赋值
arr1[10] = 'fox'

arr1
[ 'dog', 'cat', 'hen', <7 empty items>, 'fox' ]
1
2
// 数组访问越界是不会报错的,只会放回一个undefined
console.log(arr1[100])
undefined
1
2
3
4
5
6
7
8
9
// 数组元素可以是任意类型的
arr1.push
arr1.push(false);
arr1.push(null);
arr1.push(101);
arr1.push({});
arr1.push([]);

arr1
[
  'dog',
  'cat',
  'hen',
  <7 empty items>,
  'fox',
  false,
  null,
  101,
  {},
  []
]
1
2
3
4
arr1[14].x = 1;
arr1[15].push('ok');

arr1
[
  'dog',
  'cat',
  'hen',
  <7 empty items>,
  'fox',
  false,
  null,
  101,
  { x: 1 },
  [ 'ok' ]
]
1
2
3
4
5
// 遍历: 通过for

for(let i = 0; i < arr1.length; i++) {
    console.log('#' + i + ' : ' + arr1[i])
}
#0 : dog
#1 : cat
#2 : hen
#3 : undefined
#4 : undefined
#5 : undefined
#6 : undefined
#7 : undefined
#8 : undefined
#9 : undefined
#10 : fox
#11 : false
#12 : null
#13 : 101
#14 : [object Object]
#15 : ok
1
2
3
4
// 遍历: for...of
for(const a of arr1) {
    console.log(a);
}
dog
cat
hen
undefined
undefined
undefined
undefined
undefined
undefined
undefined
fox
false
null
101
{ x: 1 }
[ 'ok' ]

Array有一系列的方法,比如cancat, map, filter, slice等,详见 Array

1
2
3
// map
const babies = ['dog', 'cat', 'hen', 'fox'].map((name) => 'baby ' + name);
babies
[ 'baby dog', 'baby cat', 'baby hen', 'baby fox' ]
1
2
3
4
5
6
// concat: 数组拼接
const arr2 = ['a', 'b', 'c'];
const arr3 = ['d', 'e', 'f'];
const arr4 = arr2.concat(arr3);

arr4
[ 'a', 'b', 'c', 'd', 'e', 'f' ]

# Functions: 函数

函数在JavaScript中非常重要,是其核心组成部分。下面是一个基础的函数声明:

1
2
3
4
function add(x, y) {
    const total = x + y;
    return total;
}
1
2
// 函数传参个数可以少于定义的参数个数,缺少的参数会被定义成undefined
add(); // Equivalent to add(undefined, undefined)
NaN
1
2
// 如果传入的参数个数多余定义的,多余的参数被直接忽略。总之,函数不会因为参数的多少而报语法错
add(1, 2, 3, 4);
3

函数的参数可以是rest parameter语法,通过一个数组来存储未明确指定的参数值,类似Python的*args(注:在语法层面没有**kwargs)。

1
2
3
4
5
6
7
8
9
function avg(...args) {
    let sum = 0;
    for (const item of args) {
        sum += item;
    }
    return sum / args.length;
}

avg(1, 2, 4, 5)
3
1
2
3
// 还可以这么调用
const arr5 = [5, 6, 7, 8]
avg(...arr5)
6.5

虽然JavaScript的函数不支持**kwargs这样的命名参数,但是可以通过传入object类型参数,通过object destructuringpack/unpack

1
2
3
4
5
6
7
// Note the { } braces: this is destructuring an object
function area({ width, height }) {
  return width * height;
}

// The { } braces here create a new object
console.log(area({ width: 2, height: 3 }));
6
1
console.log(area({ width1: 2, height1: 3 }));
NaN
1
2
3
4
5
6
// 与其他语言一样,JavaScript的函数支持参数的默认值
function avg3(v1, v2, v3=0) {
    return (v1 + v2 + v3)/3
}

avg3(1, 2)
1

# Anonymous functions: 匿名函数

匿名函数就是没有名字的函数。在实践中,匿名函数常常会作为参数传递给其他函数,或者立刻传入参数让函数立刻被调用(通常只被调用一次),抑或被另一个函数作为返回值返回。

下面是一个匿名函数的定义方式:function后面不带函数名。

1
2
3
4
5
6
7
8
9
const avgFunc = function (...args) {
    let sum = 0;
    for (const item of args) {
        sum += item;
    }
    return sum / args.length;  
};

avgFunc(1,2,4);
2.3333333333333335

另一种定义的方式是通过arrow function expression,也就是=>:

1
2
3
4
5
6
7
8
9
const avgFunc2 = (...args) => {
    let sum = 0;
    for (const item of args) {
        sum += item;
    }
    return sum / args.length;  
};

avgFunc2(1,2,4);
2.3333333333333335
1
2
3
// 对简单的表达式,可以不用return
const sumFunc = (a, b, c) => a + b + c;
sumFunc(1, 2, 3);
6
1
2
const sumFunc1 = (a, b, c) => {return a + b + c;};
sumFunc1(1, 2, 3);
6

# Functions are first-class objects: 函数是头等公民

函数像其他类型一样,可以被赋值给其他参数,或者传参给其他函数,以及被其他函数作为返回值返回。

1
const addxy = (x) => (y) => x + y;
1
addxy(1)
[Function (anonymous)]
1
addxy(1)(2)
3
1
2
3
// Function accepting function
const babies2 = ["dog", "cat", "hen"].map((name) => `baby ${name}`);
babies2
[ 'baby dog', 'baby cat', 'baby hen' ]

# Inner functions: 内部函数

在函数内部定义的函数,内部函数可以访问上层函数作用域内的变量。这个特性可以在内部函数间共享上层函数的变量,从而避免污染全局变量。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
function parentFunc() {
    const a = 1;
    
    function innerFunc() {
        const b = 4;
        return a + b;
    };
    
    return innerFunc();
};

parentFunc();
5

# Classes: 类

JavaScript的类定义类似Java:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class Person {
    constructor(name) {
        this.name = name;
    };
    
    sayHello() {
        return `Hello, I am ${this.name}!`;
    };
};

const p = new Person('Maria');
p.sayHello();
'Hello, I am Maria!'

# Asynchronous programming: 异步编程

JavaScript is single-threaded by nature. There’s no paralleling; only concurrency. Asynchronous programming is powered by an event loop, which allows a set of tasks to be queued and polled for completion.

There are three idiomatic ways to write asynchronous code in JavaScript:

For example, here’s how a file-read operation may look like in JavaScript:

 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
// Callback-based

/*
fs.readFile(filename, (err, content) => {
  // This callback is invoked when the file is read, which could be after a while
  if (err) {
    throw err;
  }
  console.log(content);
});
// Code here will be executed while the file is waiting to be read
*/


// Promise-based
/*
fs.readFile(filename)
  .then((content) => {
    // What to do when the file is read
    console.log(content);
  }).catch((err) => {
    throw err;
  });
// Code here will be executed while the file is waiting to be read
*/

// Async/await

/*
async function readFile(filename) {
  const content = await fs.readFile(filename);
  console.log(content);
}
*/

# Modules: 模块

模块通常是一个js文件,可以被一个文件路径或者URL指定。可以通过import或者export在module之间交换数据。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
/*

import { foo } from "./foo.js";

// Unexported variables are local to the module
const b = 2;

export const a = 1;

*/

这就是JavaScript的基本语法知识。Keep going!