1.1 开发工具

学习js开发,现在有2种方式:

  1. 一个是使用浏览器自带的开发者工具,进入console标签页,比如
  2. 安装node js,使用vs code开发,推荐大家使用这种,这个更方便,除非你要学习dom相关的开发:

1.2 Hello World

创建一个hello.js文件,输入如下内容:

console.log("Hello World!");

在控制台输入node hello.js就能输出 "Hello World!"结果。如果想在浏览器的console里看到结果,那么创建一个hello.html,输入:

<script src="hello.js"></script>

在浏览器中打开hello.html,在控制台中就会输出同样的结果。

1.3 初识javaScript

// 这个双//开头后面的就是注释。
// 下面这些注释都是解释js这门语言的。
// 一个变量代表一个值。
// 变量使用let关键字来声明:
let x; // 声明一个变量x。
// 变量可以使用 = 赋值。
x = 0; // 现在变量 x 的值是0
x // => 0: x 的值是0
// js支持多种类型的值。
x = 1; // 数字。
x = 0.01; // 数字类型可以是整数或实数。
x = "hello world"; // 双引号包含的代表字符串类型
x = 'JavaScript'; // 单引号也表示字符串类型
x = true; // 布尔值,值为真。
x = false; // 布尔值假。
x = null; // null表示特殊值Null:“空值”。
x = undefined; // Undefined 与 Null类似,也表示空值。

还有2个最重要的类型,就是对象和数组。

// JavaScript 最重要的类型就是对象.
// 一个对象就是一个键值对集合,就是字符串到值的映射。
let book = { // 对象使用花括号声明。
    topic: "JavaScript", // 属性"topic" 值是 "JavaScript"
    edition: 7 // 属性 "edition" 值是7
}; // 右花括号表示声明结束。
// 访问对象属性使用点 . 或者方括号 []:
book.topic // => "JavaScript",点访问方式
book["edition"] // => 7: 方括号访问方式
book.author = "Flanagan"; // 创建新属性并给它赋值
book.contents = {}; // 创建属性contents,值为空对象(空对象就是没有属性的对象)。
// 使用 ?. 进行条件访问(ES2020):
book.contents?.ch01?.sect1 // => undefined: book.contents 没有 ch01 属性。
// JavaScript 支持数组 (也叫索引列表):
let primes = [2, 3, 5, 7]; // 4个值的数组, 使用[]表示。
primes[0] // => 2: 访问数组索引0
primes.length // => 4: 数组大小
primes[primes.length - 1] // => 7: 数组最后一个元素
primes[4] = 9; // 给索引4赋值
primes[4] = 11; // 赋值
let empty = []; // [] 表示空数组。
empty.length // => 0
// 数组和对象可以包含其它数组和对象。
let points = [ // 包含2个元素的数组
    { x: 0, y: 0 }, // 每个元素都是一个对象
    { x: 1, y: 1 }
];
let data = { // 一个对象有2个属性
    trial1: [[1, 2], [3, 4]], // 每个属性的值都是一个数组。
    trial2: [[2,3], [4,5]] // 数组的每个元素也是一个数组。
};

再来看看操作符:

// 操作符运用在操作数上可以产生新值。
// 数学操作符是比较简单的:
3 + 2 // => 5: 加
3 - 2 // => 1: 减
3 * 2 // => 6: 乘
3 / 2 // => 1.5: 除
points[1].x - points[0].x // => 1: 复杂的操作数也能产生结果
"3" + "2" // => "32": + 数字相加, 字符串连接
// JavaScript 定义了一些简便操作符
let count = 0; // 定义一个变量
count++; // 变量加1
count--; // 变量减1
count += 2; // 加 2: 相当于 count = count + 2;
count *= 3; // 乘 3: 相当于 count = count * 3;
count // => 6: 变量名也相当于表达式(表达式就是能产生值的代码)
// 相等和关系运行符用来判断两个值是否相等,不相等,小于,大于等等。
// 最终结果是 true 或 false。
let x = 2, y = 3; // 这里的单个 = 是赋值操作,不是相等操作符
x === y // => false: 是否相等 
        // 注意这里的 === 是全等判断,判断的两个数不涉及类型转换
        // 还有一种相等判断操作符是 ==,判断的2个数如果类型不同,
        // 会转换其中的一个类型,建议使用全等运行符进行相等判断
x !== y // => true: 是否不相等
x < y // => true: 是否小于
x <= y // => true: 是否小于等于
x > y // => false: 是否大于
x >= y // => false: 是否大于等于
"two" === "three" // => false: 2个字符串不同
"two" > "three" // => true: "tw" 在字母序上大于 "th"
false === (x > y) // => true: false 等于 false
// 逻辑操作符可以组合或者翻转布尔值
(x === 2) && (y === 3) // => true: 所有比较都是true。&& 是 AND 操作
(x > 3) || (y < 3) // => false: 所有比较都是false. || 是 OR 操作
!(x === y) // => true: ! 翻转布尔值结果



函数 是可以被调用的。

// 函数是可以被调用的,含有参数的代码块。
function plus1(x) { // 定义一个函数,名叫 "plus1",参数是 "x"
    return x + 1; // 把传进来的参数 x 值加 1, 然后返回
} // 函数代码包含在花括号中
plus1(y) // => 4: y 是 3, 所以调用plus1返回 3+1,也就是 4
let square = function (x) { // 函数也是值,可以赋值给变量
    return x * x; // 计算 x * x
}; // 分号表示赋值结束
square(plus1(y)) // => 16: 最终返回值16



箭头函数在ES6中被引入,当你想把匿名函数作为参数传给另一个函数的时候,箭头函数就很有用。我们现在用箭头函数的形式把前面的函数重新定义一把:

const plus1 = x => x + 1; // 定义一个函数,参数是 x,返回 x + 1
const square = x => x * x; // 定义一个函数,参数是 x,返回 x * x
plus1(y) // => 4: y 是 3, 调用plus1函数,返回4
square(plus1(y)) // => 16



如果把函数应用到对象上,就得到了方法

// 把函数作为值赋给对象的属性时,我们称它们为方法。
// 所有JS对象(包括数组)都有方法:
let a = []; // 创建一个空数组
a.push(1, 2, 3); // push() 方法向数组添加元素
a.reverse(); // 另一个方法,逆序数组的所有元素
// 我们可以定义自己的方法。 使用 "this" 关键字指向当前对象(也就是函数所属的对象),
// 下面这个例子使用前面定义的points对象。
points.dist = function () { // 定义一个方法计算两点之间的距离
    let p1 = this[0]; // 取得第1个元素
    let p2 = this[1]; // 取得第2个元素
    let a = p2.x - p1.x; // x 坐标差
    let b = p2.y - p1.y; // y 坐标差
    return Math.sqrt(a * a + // 取平方根
        b * b); // Math.sqrt() 计算平方根
};
points.dist() // => Math.sqrt(2): 计算两点之间的距离

下面这些函数用到了控制流语句,也就是判断,循环等语句:

// JavaScript 使用了跟C,C++,JAVA等语句类似的条件和循环语句。
function abs(x) { // 一个计算绝对值的函数
    if (x >= 0) { // if 语句...
        return x; // 如果判断是true,就执行这条语句。
    } // if语句块结束。
    else { // 如果if判断失败,就执行else块
        return -x; // 返回 -x。
    } // else块结束。
} // 注意函数结束在if或else语句块中。
abs(-10) === abs(10) // => true
function sum(array) { // 计算数组的和
    let sum = 0; // sum 初始化为0.
    for (let x of array) { // 循环遍历数组,每次循环x得到其中一个元素的值。
        sum += x; // sum加上x的值
    } // 循环体结束。
    return sum; // 返回sum值。
}
sum(primes) // => 28: 返回前5个素数的和 2+3+5+7+11,注意primes在上面已经定义过了
function factorial(n) { // 计算阶乘
    let product = 1; // product 初始化为1
    while (n > 1) { // 如果n >1成立,则执行{}体内语句,然后重新判断 n > 1是否成立
        product *= n; // 相当于 product = product * n;
        n--; // 相当于 n = n - 1
    } // 循环结束
    return product; // 返回阶乘的结果
}
factorial(4) // => 24: 1*4*3*2
function factorial2(n) { // 使用不同的循环结构
    let i, product = 1; // 开始是 1
    for (i = 2; i <= n; i++) // i 每次循环自动加1 (i++), 从 2 开始直到 n
        product *= i; // 计算乘积. {} 暂时不需要,因为只有一行执行语句。
    return product; // 返回结果
}
factorial2(5) // => 120: 1*2*3*4*5pt

javascript还支持面向对象风格的编程风格,比如计算两点之间的距离,可以创建一个类和一个方法:

class Point { // 根据传统,类名开头字母大写。
    constructor(x, y) { // 构造方法创建一个新实例。
        this.x = x; // this 关键字指向新创建的当前实例。
        this.y = y; // 创建x, y新属性,并且把参数x, y的值存起来。
    } // 构造方法中不需要return语句。
    distance() { // 类方法,用来计算两点之间的距离
        return Math.sqrt( // 返回 x² + y² 的平方根。
            this.x * this.x + // this 指向调用该方法的实例
            this.y * this.y // 计算两点之间的距离
        );
    }
}
// 使用 new Point() 构造函数来创建一个Point实例
let p = new Point(1, 1); // 定义了一个点 (1,1).
// 调用实例p的方法
p.distance() // => Math.SQRT2

1.4 示例:字符频率直方图

创建一个charfreq.js,使用node运行,计算一下字符出现次数的频率直方图,在windows的cmd中执行如下命令:

node charfreq.js < charfreq.js

如果是在vs code中,通过菜单 Terminal->New Terminal打开终端,这个终端是powershell,< 输入重定向符不起作用,参考一下别人的方法,可以这样输入

Get-Content .\charfreq.js | node .\charfreq.js

输出结果如下:

T: ########### 11.19%
E: ########## 10.22%
R: ####### 6.81%
S: ###### 6.46%
A: ###### 6.11%
N: ###### 5.83%
O: ###### 5.52%
I: ##### 4.62%
H: #### 4.11%
C: ### 3.33%
L: ### 3.17%
U: ### 3.13%
/: ### 2.78%
D: ## 2.35%
F: ## 2.11%
M: ## 2.11%
P: ## 1.80%
.: ## 1.76%
G: ## 1.53%
(: # 1.45%
): # 1.45%
Y: # 1.45%

完整代码如下,我就不翻译了,自己看吧,里面涉及了类,map,异步,解构,字符串模板等东东:

/**
 * This Node program reads text from standard input, computes the frequency
 * of each letter in that text, and displays a histogram of the most
 * frequently used characters. It requires Node 12 or higher to run.
 *
 * In a Unix-type environment you can invoke the program like this:
 *    node charfreq.js < corpus.txt
 */

// This class extends Map so that the get() method returns the specified
// value instead of null when the key is not in the map
class DefaultMap extends Map {
    constructor(defaultValue) {
        super();                          // Invoke superclass constructor
        this.defaultValue = defaultValue; // Remember the default value
    }

    get(key) {
        if (this.has(key)) {              // If the key is already in the map
            return super.get(key);        // return its value from superclass.
        }
        else {
            return this.defaultValue;     // Otherwise return the default value
        }
    }
}

// This class computes and displays letter frequency histograms
class Histogram {
    constructor() {
        this.letterCounts = new DefaultMap(0);  // Map from letters to counts
        this.totalLetters = 0;                  // How many letters in all
    }

    // This function updates the histogram with the letters of text.
    add(text) {
        // Remove whitespace from the text, and convert to upper case
        text = text.replace(/\s/g, "").toUpperCase();

        // Now loop through the characters of the text
        for(let character of text) {
            let count = this.letterCounts.get(character); // Get old count
            this.letterCounts.set(character, count+1);    // Increment it
            this.totalLetters++;
        }
    }

    // Convert the histogram to a string that displays an ASCII graphic
    toString() {
        // Convert the Map to an array of [key,value] arrays
        let entries = [...this.letterCounts];

        // Sort the array by count, then alphabetically
        entries.sort((a,b) => {              // A function to define sort order.
            if (a[1] === b[1]) {             // If the counts are the same
                return a[0] < b[0] ? -1 : 1; // sort alphabetically.
            } else {                         // If the counts differ
                return b[1] - a[1];          // sort by largest count.
            }
        });

        // Convert the counts to percentages
        for(let entry of entries) {
            entry[1] = entry[1] / this.totalLetters*100;
        }

        // Drop any entries less than 1%
        entries = entries.filter(entry => entry[1] >= 1);

        // Now convert each entry to a line of text
        let lines = entries.map(
            ([l,n]) => `${l}: ${"#".repeat(Math.round(n))} ${n.toFixed(2)}%`
        );

        // And return the concatenated lines, separated by newline characters.
        return lines.join("\n");
    }
}

// This async (Promise-returning) function creates a Histogram object,  
// asynchronously reads chunks of text from standard input, and adds those chunks to
// the histogram. When it reaches the end of the stream, it returns this histogram
async function histogramFromStdin() {
    process.stdin.setEncoding("utf-8"); // Read Unicode strings, not bytes
    let histogram = new Histogram();
    for await (let chunk of process.stdin) {
        histogram.add(chunk);
    }
    return histogram;
}

// This one final line of code is the main body of the program.
// It makes a Histogram object from standard input, then prints the histogram.
histogramFromStdin().then(histogram => { console.log(histogram.toString()); });