JavaScript

... for the 6th major edition of the standard, published in June 2015. Since then a yearly edition has been published. For example, the 13th edition was published in June 20221.

Unlike most programming languages, the JavaScript language has ==no concept of input or output==. It is designed to run as a scripting language in a ==host environment==, and it is up to the host environment to provide mechanisms for communicating with the outside world. The most common host environment is the ==browser==, but JavaScript interpreters can also be found in other places, including Adobe Acrobat/Photoshop, SVG images, server-side environments such as ==Node.js==, NoSQL databases like Apache CouchDB, desktop environments like GNOME , and others.

JavaScript is a multi-paradigm, dynamic language with types and operators, standard built-in objects, and methods. Its syntax is based on the Java and C languages.

img ***HTML**, **CSS**, and **JavaScript** are **front-end** (or **client-side**) languages, which means they are run by the browser to produce a website front-end that your users can use[^web-basic]. There are **back-end** (or **server-side**) languages, running on the server to get some data out of a database and generate some HTML to contain the data, before the result is then sent to the browser to be displayed. Example server-side languages include **ASP.NET**, **Python**, **PHP**, and **NodeJS**.*

JavaScript First Steps - Learn web development | MDN (mozilla.org)

JavaScript building blocks - Learn web development | MDN (mozilla.org)

JavaScript object basics - Learn web development | MDN (mozilla.org)

变量

变量类型

  • Number:双精度64位浮点数/整数(整数也按浮点数处理);

    parseInt('123', 10)  // 将字符串转换为整数
    parseInt('123.45')   // 将字符串转换为浮点数
    

    NaNInfinity-Infinity

    Number.isNaN(NaN)
    isFinite(1 / 0); // false
    
  • BigInt

  • String:UTF-16编码。

    'hello'.length;  // 字符串长度(编码单位*)
    

    *:一个字符由一个或两个UTF-16编码单位组成,一个编码单位占用2字节。

  • Boolean

  • Function

  • Object

    • Function
    • Array
    • Date
    • RegExp
    • Math:提供数学计算方法和常量。
    • Error
  • Symbol (ES2015)

  • undefinednullnull表示故意设置的空值,undefined表示变量未初始化。

  • boolean:表示truefalsefalse0、空字符串("")、NaNnullundefined 都可转换为false;其他值则转换为trueif等语句块的判断条件可执行自动转换。

    Boolean(1)  // true
    

    boolean类型支持&&||!逻辑运算。

变量声明

let a = 1;       // block-scope variable
const c = 3.14   // block-scope constant
var b = "hello"; // function-scope variable

变量类型由初始化的值决定,如果未赋值,其类型为undefined

运算符

数值运算符

+, -, *, / and %

+可用于字符串拼接。

赋值运算符

=+=、……、++--(可前缀/后缀)

比较运算符

<, >, <= and >=.

==进行比较时会对不同类型尝试强制转换;===!==则直接比较。

没有!=运算符,使用逻辑!转换比较结果。

逻辑运算符

&&||支持短路

字符串

str[i]//.charAt(5);

.indexOf/lastIndexOf(substr)

.slice(i[,j]);

.length

toUpperCase() and toLowerCase()

.split(" ");

.trim();

.replace("How", "Where");

.replace(/javascript/gi, "JavaScript"):正则表达式替换;

字符串插值
const poem = "The Wide Ocean", author = "Pablo Neruda";
const favePoem = `My favorite poem is ${poem} by ${author}.`; //支持换行, 支持包含引号
引号转义

交替使用''""

使用\'\"进行转义;

使用"``"

对象类型

构造对象实例

JavaScript对象可以看作name-value组成的集合,类似于Python字典、C/C++散列表或JavaHashMap等。

构造空对象:

const obj = new Object();
const obj = {};  // prefered JSON format*

*JSON - JavaScript Object Notation

构造包含属性和方法的对象。

const obj = {
  name: 'Carrot',
  details: { color: 'orange', size: 12 },
  var,                           //*
  func_name(){/*...*/}           //定义函数
  func_name: function(){/*...*/} //使用匿名函数语法
};

*:简写,使用变量名和变量的值。

使用函数定义构造对象(函数对象)。函数既是对象类型也作为对象的构造函数。

function Person(name, age) {
  this.name = name;
  this.age = age;
  this.fullName = function(){/**/}
} 
使用对象原型构造对象

上述使用函数构造对象的方法在实例化对象时会重复定义函数成员,使用对象原型ClassName.prototype定义所有实例共享的函数对象与静态成员。

Person.prototype.fullName = function() {/*...*/}; 
const you = new Person('You', 24);

new创建一个空对象,并将其赋值给Person函数的this。未使用new,则this表示的是全局变量空间。

访问属性

obj.details.color
obj['details']['color']

对象内部可使用this引用当前对象的属性。

数组

数组是一类特殊的JavaScript对象。

const a = new Array();
a[0] = 'dog'; a[1] = 'cat'; a[2] = 'hen'; //*
a.push(item); // 追加元素
a.length;     // -> 3
const a = ['dog', 'cat', 'hen'];

*:数组可动态扩展,对应下标未赋值的元素的值为undefined(类似于Shell脚本)。

数组迭代
  1. 使用循环进行迭代。

  2. 使用forEach方法进行迭代。

    array.forEach(iter_func)
    
数组方法

Array - JavaScript | MDN (mozilla.org)

语法

换行:

注释:单行// coments;多行/*comments*/

流程控制

条件选择
if (name === 'puppies') {}
else if (name === 'kittens') {}
else {} // 与C语言结构相同
switch (action) {
  case 'draw':
    break; // 如果不添加break, 则会继续执行后续分支
  case 'eat';
    break;
  default:
    doNothing();
} // 比较总是使用"==="
循环
while (n > 0) {}
do {
  input = get_input();
} while (!(input === 'exit'));
for (let i = 0; i < 5; ++i){}
for (const v of array) {}     // 迭代数组元素
for (const prop in object) {} // 迭代对象属性

函数

function add(x, y,...args) {
  const total = x + y;
  return total;  // 如果未指定返回值, 返回undefined
}

参数列表

函数参数列表仅具有声明意义,实际调用时可传入少于或多于声明的参数个数。缺少的参数将被视为undefined,多余的参数被忽略

变长参数列表
  1. 使用...args捕获所有未声明的传入参数。
  2. 可在函数内部,通过arguments访问传入的所有参数。
  3. 参数展开:调用时func(...args)

函数对象

函数也是一类特殊对象,可用于变量赋值或参数传递赋值。

匿名函数
let avg = function(){};
closures

Whenever JavaScript executes a function, a 'scope' object is created to hold the local variables created within that function.

A closure is the combination of a function and the scope object in which it was created.

嵌套函数

可访问其上级函数作用域中的变量。

异步

Introducing asynchronous JavaScript - Learn web development | MDN (mozilla.org)

标准库

输入输出

console是封装控制台的对象,可向控制台(浏览器控制台/Node.js终端)输出日志:

console.log(obj)  // trace/debug/info/warn/error

log系列函数可直接输出对象。

时间日期

Date();

浏览器应用

HTML文件中通过<script>标签引用JavaScript脚本(可位于<head><body>,相对HTML文件路径),默认情况下脚本执行顺序与引用顺序一致。脚本执行环境由浏览器解释器构建,通过运行环境中的document对象(Document)对HTML页面进行修改(页面元素内容、CSS等)。

<!DOCTYPE html>
<html lang="en">
  <head>
      <meta charset="UTF-8" />
      <title>JavaScript Tutorial</title>
      <script src="scripts/main.js" defer></script> <!--*-->
      <script>/*inline script*/</script>
  </head>

  <body>
    <h1></h1>
  </body>
</html>

*defer在HTML文档解析完成后再执行脚本程序(按出现顺序执行),async:异步获取脚本内容而不阻塞页面加载,当脚本下载完后会立即开始执行(仅用于独立执行的脚本)。

img

对于与页面内容紧密相关的脚本,可将其置于对应的HTML元素之后。

使用独立的脚本文件的优点:1)分离HTML和Javascript代码,使两者更易于书写、理解和维护;2)独立的脚本文件可在本地缓存,从而加快页面的加载速度。

const myHeading = document.querySelector('h1'); //*
myHeading.textContent = 'Hello world!';

*Returns the first element that is a descendant of node that matches selectors.

JavaScript的作用:

  • 动态更新HTML和CSS文件;
  • 处理用户交互。

Introduction to web APIs - Learn web development | MDN (mozilla.org)

事件处理

document.querySelector('html')  // 获取页面对象
    .addEventListener(          // 为对象增加事件处理方法
        'click',
        function () {
            alert('Ouch! Stop poking me!');
        }
    );

交互组件

alert(msg);
let value = prompt(msg);

存储

localStorage API

localStorage.setItem('name', 'gary');
const stored_name = localStorage.getItem('name');

优化

program.min.jsprogram.js优化后的文件,减少网络传输以加快网页加载:将长变量转换为短名。

just-in-time compiling

The JavaScript source code gets compiled into a faster, binary format while the script is being used, so that it can be run as quickly as possible.

Node.js

Node.js项目

模块

在Node.js中,一般将代码合理拆分到不同的文件中,每一个文件就是一个模块,而文件路径就是模块名。在编写每个模块时,

  • require函数用于加载和使用模块(执行模块代码并返回导出内容,一个模块中的代码仅在模块第一次被使用时执行一次)。另外,可以require加载一个JSON文件。

    let data = require('./data/two-nodes.json')
    console.log(data);
    
  • exports对象用于保存当前模块要导出的公有对象。通过require函数引用的就是模块的exports对象。

    exports.hello = function () {
        console.log('Hello World!');
    };
    
  • module对象可以访问到当前模块包括exports在内的信息,主要用途是替换当前模块的exports对象。

***import的用法。

import defaultExport from "module-name";
import * as name from "module-name";
import { export } from "module-name";
import { export as alias } from "module-name";
模块路径解析规则

通过文件路径:模块名可使用相对路径(./开头)或绝对路径(以/X:开头);

通过搜索路径:模块名按搜索路径下的层级索引(类似于foo/bar),搜索顺序:

  • 内置模块
  • node_modules目录
  • $NODE_PATH

模块名中的.js扩展名可以省略。

主模块

通过命令行参数传递给NodeJS以启动程序的模块被称为主模块。主模块通过引用程序的其它模块完成工作。

入口程序
function main(argv) {
    copy(argv[2], argv[3]);  //argv[0]是node路径, argv[1]是主模块文件路径。
}
main(process.argv);

process是Node.js执行环境中的全局变量。

把由多个子模块组成的大模块称做,并把所有子模块放在同一个目录里。其中一个模块作为入口模块,其导出对象将引用其他模块的导出对象,作为整个包的导出对象。当入口模块的文件名是index.js,加载模块时可以使用模块所在目录的路径代替模块文件路径。

可通过包目录下的package.json修改默认的入口模块名和位置。

{
  "name": "cat",
  "main": "./lib/main.js"
}

工程目录

- /home/user/workspace/node-echo/   # 工程目录
    - bin/                          # 存放命令行相关代码
        node-echo
    + doc/                          # 存放文档
    - lib/                          # 存放API相关代码
        echo.js
    - node_modules/                 # 存放三方包
        + argv/
    + tests/                        # 存放测试用例
    package.json                    # 元数据文件
    README.md                       # 说明文件

执行Node.js程序

启动Node.js程序:

  • 直接在命令行通过node解释执行脚本;

    node src/index.js
    
  • 在Linux系统下,通过脚本首行#! /usr/bin/env node指定当前脚本的解释器,并为该脚本添加可执行权限;

  • 在Windows系统下,通过一个cmd或PowerShell脚本封装node命令;

    @node "./node-echo.js" %*
    

输入输出

标准输入输出

process.stdinprocess.stdoutprocess.stderr

process.stdout.write(util.format.apply(util, arguments) + '\n');

文件读写

var fs = require('fs')
function copy(src, dst) {
    fs.writeFileSync(dst, fs.readFileSync(src));
}
异步读写

通过回调函数处理数据或错误。

fs.readFile(pathname, function (err, data) {
    if (err) {
        // Deal with error.
    } else { 
        // Deal with data.
    }
});

命名约定:默认为异步方法,带Sync的方法为同步方法。

流式读写
function copy(src, dst) {
    fs.createReadStream(src).pipe(fs.createWriteStream(dst));
}

Stream基于事件机制工作。

var rs = fs.createReadStream(pathname);
rs.on('data', function (chunk) { 
    rs.puase();
    doSomething(chunk, function(){rs.resume()}); 
});
rs.on('end', function () { cleanUp(); });
Buffer

http://nodejs.org/api/buffer.html

文本编码

iconv-lite转码。

文件系统

路径操作

http://nodejs.org/api/path.html

path.normalize(pathname)
path.join(name1, name2)
path.extname('foo/bar.js'); // => ".js"
访问目录信息
fs.readdirSync(pathname)
fs.statSync(pathname)

网络操作

http服务

var http = require('http');
http_server = http.createServer(http_handler);
http_server.listen(8124);
let http_handler = function (request, response) {
    /*接收请求数据*/
    var body[];
    request.on('data', function (chunk) {
        body.push(chunk);
        // response.write(chunk); // 将请求数据原样返回
    });
    request.on('end', function () {
        body = Buffer.concat(body);
        console.log(body.toString());
        // response.end();
    });
	/*发送响应*/ 
    response.writeHead(200, { 'Content-Type': 'text-plain' });
    response.end('Hello World\n');
}

request是一个对象,可以访问request.url

http客户端
var options = {
        hostname: 'www.example.com',
        port: 80,
        path: '/upload',
        method: 'POST',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded'
        }
    };
var request = http.request(options, function (response) {});
request.write('Hello World');
request.end();
// => http.get('http://www.example.com/', function (response) {});
https

http://nodejs.org/api/https.html

URL
url.parse('http://user:pass@host.com:8080/p/a/t/h?query=string#hash');
// 返回数据:
{ protocol: 'http:',
  auth: 'user:pass',
  host: 'host.com:8080',
  port: '8080',
  hostname: 'host.com',
  hash: '#hash',
  search: '?query=string',
  query: 'query=string',
  pathname: '/p/a/t/h',
  path: '/p/a/t/h?query=string',
  href: 'http://user:pass@host.com:8080/p/a/t/h?query=string#hash' 
}

请求参数

http://nodejs.org/api/querystring.html

进程管理

process对象用于管理控制当前进程。

创建进程

var child = child_process.spawn('node', [ 'xxx.js' ], 
                                stdio:[0,1,2,'ipc']);
// 获取子进程的输出
child.stdout.on('data', function (data) {
    console.log('stdout: ' + data);
});
child.stderr.on('data', function (data) {
    console.log('stderr: ' + data);
});
child.on('close', function (code) {
    console.log('child process exited with code ' + code);
});
同步调用
var child_process = require('child_process');
var util = require('util');

function copy(source, target, callback) {
    child_process.exec(
        util.format('cp -r %s/* %s', source, target), callback);
}
cluster

cluster模块是对child_process模块的进一步封装,用于解决单进程服务无法充分利用多核CPU的问题。使用该模块可以让每个核上运行一个工作进程,并统一通过主进程监听端口和分发请求。

降低进程权限
http.createServer(callback).listen(80, function () {
    var env = process.env,
        uid = parseInt(env['SUDO_UID'] || process.getuid(), 10),
        gid = parseInt(env['SUDO_GID'] || process.getgid(), 10);

    process.setgid(gid);
    process.setuid(uid);
});
退出进程
try {
    // ...
} catch (err) {
    // ...
    process.exit(1);
}

进程间通信

信号
child.kill('SIGTERM')
IPC
/* parent.js: 收发数据 */
child.on('message', function (msg) {
    console.log(msg);
});
child.send({ hello: 'hello' });

/* child.js: 接收数据 */
process.on('message', function (msg) {
    msg.hello = msg.hello.toUpperCase();
    process.send(msg);
});

数据格式为序列化JSON文本。

守护进程

异步编程

异步方式下,函数执行结果不是通过返回值,而是通过回调函数的参数传递。

JS自身提供的异常捕获和处理机制try..catch..,只能用于同步执行的代码。由于异步函数会打断代码执行路径,异步函数执行过程中以及执行之后产生的异常抛出到执行路径被打断的位置过程中,如果没有遇到try语句,就作为一个全局异常抛出。

使用域简化异步代码的异常处理

在域(就是运行环境)中,如果一个异常没有被捕获,将作为一个全局异常被抛出。Node.js通过process对象提供了捕获全局异常的方法。

process.on('uncaughtException', function (err) {
    console.log('Error: %s', err.message);
});

创建子域以尽早捕获异常。

http.createServer(function (request, response) {
    var d = domain.create();
    d.on('error', function () {/*error handler*/ });
    d.run(function () {
        asyncMethod(request, function (data) {/*processing*/});
    });
});

使用uncaughtExceptiondomain捕获异常,代码执行路径里涉及到了C/C++部分的代码时,如果不能确定是否会导致内存泄漏等问题,最好在处理完异常后重启程序比较妥当。而使用try语句捕获异常时一般捕获到的都是JS本身的异常,不用担心上诉问题。

http://nodejs.org/api/domain.html

参考资料