最近工作中会涉及到前端工作,因此要用到 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。
然后就能在其中测试和探索 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)
console.log(3/2); // 1.5, not 1
复制代码
// 浮点数可以存在不精确的情况console.log(0.1+0.2)
复制代码
// Number literals can also have prefixes to indicate the base // (binary, octal, decimal, or hexadecimal), or an exponent suffix.
console.log(0b111110111); // 503console.log(0o767); // 503console.log(0x1f7); // 503console.log(5.03e2); // 503
复制代码
Bigint 用来指定数值是整数,它是在数字后面跟一个后缀:n
// Bigint与Number不能混合运算// console.log(-3n + 2); //TypeError: Cannot mix BigInt and other types, use explicit conversions
复制代码
Math 是一个提供标准数据运算的 Object。
var r0 = 2;const circumference0 = 2 * Math.PI * r0;
复制代码
可以使用下面的方式做数字和字符串之间的转换:
parseInt('123.4'); // 可以解析小数,只是得到其整数部分
复制代码
parseInt('0b111110111'); // 不能正确解析其他进制的数
复制代码
NaN表示 Not a Number,比如解析一个非数字表达;传入NaN做运算,会返回NaN。
Infinity表示无穷大,除以 0 会产生这个值。它有正负之分。
parseInt('Not a Number');
复制代码
String
JavaScript 中的字符串就是 Unicode(准确说是 UTF-16 编码)字符的序列。
console.log('Hello, world');console.log('你好,世界!');
复制代码
// 单引号和双引号都OKconsole.log("Hello, world");console.log("你好,世界!");
复制代码
// 字符和字符串之间也没有差别:字符就是长度为1的字符串'Hello'[1] === 'e'
复制代码
// 字符串长度:length属性'Hello'.length
复制代码
// 字符串相加const age = 25;console.log('I am ' + age + ' years old.') // String concatenation
复制代码
// 也可用字符串模板:使用反引号 `` + ${}console.log(`I am ${age} years old.`)
复制代码
其他类型
在 JavaScript 中,null表示 deliberate non-value(故意的空值),undefined表示 absence of value(没有定义的值)。null只能通过 null 关键字获取,而undefined可以通过下面的多种方式获取:
function returnNothing() { return}
console.log(returnNothing())
复制代码
var arr = [1, 2, 3]
console.log(arr.iDontExist)
复制代码
JavaScript 的布尔值true和false,任何值都可以转换成布尔值,其规则如下:
false, 0, 空字符串"", NaN, null以及undefined被转换成false
其他值都被转换成true
Variables: 变量
JavaScript 中,变量可以由下面的三个关键字申明:let, const, var。他们之间的差别可以参考:
JavaScript 中的 Var、Let 和 Const 有什么区别。
总结其异同点:
var声明是全局作用域或函数作用域,而let和const是块作用域。
var变量可以在其范围内更新和重新声明; let变量可以被更新但不能重新声明; const变量既不能更新也不能重新声明。
它们都被提升到其作用域的顶端。但是,虽然使用变量 undefined 初始化了var变量,但未初始化let和const变量。
尽管可以在不初始化的情况下声明var和let,但是在声明期间必须初始化const。
现在let是主流的申明方式。
下面通过几个例子来说明。
// var的作用域是全局或者函数范围
var greeter = 'hi';
function newFunc() { var hello = 'hello';}
console.log(greeter);// console.log(hello); // ReferenceError: hello is not defined
复制代码
// var变量可以重新申明和修改var greeter = 'hi';greeter = 'hello';console.log(greeter);
var greeter = 'hi hi hi';console.log(greeter);
复制代码
// var 的变量提升// 变量提升是 JavaScript 的一种机制:在执行代码之前,变量和函数声明会移至其作用域的顶部。这意味着如果我们这样做:
console.log(greeter0);var greeter0 = 'hi 0';
复制代码
// 上面的代码会被解释为:var greeter0;console.log(greeter0);greeter0 = 'hi 0';
复制代码
// var的问题:因为全局作用域造成的不希望发生的赋值和引用
var greeter1 = 'hi';var times = 4;
if(times > 3) { var greeter1 = 'Hello'}
console.log(greeter1)
复制代码
上例中,if block 中的 greeter1 影响了全局 greeter1 的值。这有可能是我们不希望看到的。这就是使用 let 的原因:
// let的作用域是block {} 级别的
let greeter3 = 'hi';let times1 = 4;
if(times1 > 3) { let greeter3 = 'Hello'; console.log(greeter3);}
console.log(greeter3);
复制代码
// let变量能被修改,但不能被重新申明let greeter5 = 'hi';greeter5 = 'hello';
console.log(greeter5);
复制代码
// let greeter5 = 'hi'; //SyntaxError: Identifier 'greeter5' has already been declared
复制代码
const与let类似,只不过其申明的变量必须保持常量,不能被修改。
const greeter6 = 'hi';// greeter6 = 'hello'; //TypeError: Assignment to constant variable.
复制代码
不过其申明的 object 类型的变量,其属性是可以被改变的。
const obj_greeter = { message: 'hi', times: 4}
obj_greeter.message = 'hello'
console.log(obj_greeter)
复制代码
{ message: 'hello', times: 4 }
复制代码
obj_greeter.receiver = 'Jack'
console.log(obj_greeter)
复制代码
{ message: 'hello', times: 4, receiver: 'Jack' }
复制代码
JavaScript 是动态类型,意味着同一个变量名可以指向不同类型的数据。
Operators:运算符
JavaScript 支持的运算符包括:
+, -, *, /, %, **: 数学运算
=, +=, -=: 赋值
++, --: 自加自减
+: 字符串拼接
>, <, >=, <=: 不等比较
==: 双等号,不同类型会强制转换(type coercion), 对应的不等号是!=
===: 三等号,不同类型不会强制转换, 对应的不等号是!==
&&, ||, !: 逻辑运算:与或非
&, |, ~: 位运算:与或非
更详尽的运算符说明,见链接。
下面是一些示例。
// 双等号 vs 三等号
console.log(123 == '123');console.log(1 == true);
console.log(123 === '123');console.log(1 === true);
复制代码
// 逻辑运算const a1 = 0 && 'Hello'; // 0 because 0 is "falsy"console.log(a1);
复制代码
const b1 = "Hello" || "world"; // "Hello" because both "Hello" and "world" are "truthy"console.log(b1);
复制代码
Grammar: 语法风格
JavaScript 的语法风格接近于 C 语言。有下面几点值得注意:
注释:单行用//,多行用/* */
表达式的结尾用';':但这个分号也是可选的
Control Structure: 控制结构
和大多数语言一样,JavaScript 包括下面的控制结构:
Objects: 对象类型
JavaScript 的 object 类型用来放键值对 key-value 数据,相当于 Python 中的 dict. object 是非常动态的, 它的属性可以随时被添加、删除、重排、突变 mutated。object 的 key 总是 string 或者 symbol。
const obj = { name: 'Carrot', for: 'Max', details: { color: 'orange', size: 12 }}
obj
复制代码
{ name: 'Carrot', for: 'Max', details: { color: 'orange', size: 12 } }
复制代码
// dot notation: 只能是一个静态的标识符console.log(obj.name);
// bracket notation:可以是一个动态的变量console.log(obj['name'])
复制代码
// 用变量来标识一个keylet userName1 = 'nick';obj[userName1] = 'Catty';obj
复制代码
{ name: 'Carrot', for: 'Max', details: { color: 'orange', size: 12 }, nick: 'Catty'}
复制代码
// 链式访问console.log(obj.details.color);console.log(obj['details']['size']);
复制代码
// object是按照引用传值const obj1 = {}
function doSth(o) { o.x = 1;}
doSth(obj1);
obj1
复制代码
// 引用const obj2 = obj1;obj1.y = 2;
obj2
复制代码
Arrays: 数组
JavaScript 中的数组是一种特殊的 object,通过[index]来访问元素。数组长度通过.length来获取。
const arr1 = ['dog', 'cat', 'hen'];a.length
复制代码
// 可以给任意非负整数的下标赋值arr1[10] = 'fox'
arr1
复制代码
[ 'dog', 'cat', 'hen', <7 empty items>, 'fox' ]
复制代码
// 数组访问越界是不会报错的,只会放回一个undefinedconsole.log(arr1[100])
复制代码
// 数组元素可以是任意类型的arr1.pusharr1.push(false);arr1.push(null);arr1.push(101);arr1.push({});arr1.push([]);
arr1
复制代码
[ 'dog', 'cat', 'hen', <7 empty items>, 'fox', false, null, 101, {}, []]
复制代码
arr1[14].x = 1;arr1[15].push('ok');
arr1
复制代码
[ 'dog', 'cat', 'hen', <7 empty items>, 'fox', false, null, 101, { x: 1 }, [ 'ok' ]]
复制代码
// 遍历: 通过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
复制代码
// 遍历: for...offor(const a of arr1) { console.log(a);}
复制代码
dogcathenundefinedundefinedundefinedundefinedundefinedundefinedundefinedfoxfalsenull101{ x: 1 }[ 'ok' ]
复制代码
Array 有一系列的方法,比如 cancat, map, filter, slice 等,详见Array。
// mapconst babies = ['dog', 'cat', 'hen', 'fox'].map((name) => 'baby ' + name);babies
复制代码
[ 'baby dog', 'baby cat', 'baby hen', 'baby fox' ]
复制代码
// 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 中非常重要,是其核心组成部分。下面是一个基础的函数声明:
function add(x, y) { const total = x + y; return total;}
复制代码
// 函数传参个数可以少于定义的参数个数,缺少的参数会被定义成undefinedadd(); // Equivalent to add(undefined, undefined)
复制代码
// 如果传入的参数个数多余定义的,多余的参数被直接忽略。总之,函数不会因为参数的多少而报语法错add(1, 2, 3, 4);
复制代码
函数的参数可以是 rest parameter 语法,通过一个数组来存储未明确指定的参数值,类似 Python 的*args(注:在语法层面没有**kwargs)。
function avg(...args) { let sum = 0; for (const item of args) { sum += item; } return sum / args.length;}
avg(1, 2, 4, 5)
复制代码
// 还可以这么调用const arr5 = [5, 6, 7, 8]avg(...arr5)
复制代码
虽然 JavaScript 的函数不支持**kwargs这样的命名参数,但是可以通过传入 object 类型参数,通过object destructuring来pack/unpack。
// Note the { } braces: this is destructuring an objectfunction area({ width, height }) { return width * height;}
// The { } braces here create a new objectconsole.log(area({ width: 2, height: 3 }));
复制代码
console.log(area({ width1: 2, height1: 3 }));
复制代码
// 与其他语言一样,JavaScript的函数支持参数的默认值function avg3(v1, v2, v3=0) { return (v1 + v2 + v3)/3}
avg3(1, 2)
复制代码
Anonymous functions: 匿名函数
匿名函数就是没有名字的函数。在实践中,匿名函数常常会作为参数传递给其他函数,或者立刻传入参数让函数立刻被调用(通常只被调用一次),抑或被另一个函数作为返回值返回。
下面是一个匿名函数的定义方式:function 后面不带函数名。
const avgFunc = function (...args) { let sum = 0; for (const item of args) { sum += item; } return sum / args.length; };
avgFunc(1,2,4);
复制代码
另一种定义的方式是通过 arrow function expression,也就是=>:
const avgFunc2 = (...args) => { let sum = 0; for (const item of args) { sum += item; } return sum / args.length; };
avgFunc2(1,2,4);
复制代码
// 对简单的表达式,可以不用returnconst sumFunc = (a, b, c) => a + b + c;sumFunc(1, 2, 3);
复制代码
const sumFunc1 = (a, b, c) => {return a + b + c;};sumFunc1(1, 2, 3);
复制代码
Functions are first-class objects: 函数是头等公民
函数像其他类型一样,可以被赋值给其他参数,或者传参给其他函数,以及被其他函数作为返回值返回。
const addxy = (x) => (y) => x + y;
复制代码
// Function accepting functionconst babies2 = ["dog", "cat", "hen"].map((name) => `baby ${name}`);babies2
复制代码
[ 'baby dog', 'baby cat', 'baby hen' ]
复制代码
Inner functions: 内部函数
在函数内部定义的函数,内部函数可以访问上层函数作用域内的变量。这个特性可以在内部函数间共享上层函数的变量,从而避免污染全局变量。
function parentFunc() { const a = 1; function innerFunc() { const b = 4; return a + b; }; return innerFunc();};
parentFunc();
复制代码
Classes: 类
JavaScript 的类定义类似 Java:
class Person { constructor(name) { this.name = name; }; sayHello() { return `Hello, I am ${this.name}!`; };};
const p = new Person('Maria');p.sayHello();
复制代码
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:
// 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 之间交换数据。
/*
import { foo } from "./foo.js";
// Unexported variables are local to the moduleconst b = 2;
export const a = 1;
*/
复制代码
这就是 JavaScript 的基本语法知识。Keep going!
评论