JS随笔:基础语法与控制结构

19 阅读10分钟

JS随笔:基础语法与控制结构

本篇是「JS随笔」系列中的基础篇,围绕 JavaScript 的基础语法与控制结构展开,覆盖变量与数据类型、常用运算符、表达式、条件与循环、值的类型转换与相等性判定等核心知识点,便于系统化查漏补缺与面试/复习使用。


原文地址

墨渊书肆/JS随笔:基础语法与控制结构


介绍

起源

JavaScript 在 1995 年由网景公司( Netscape )的布兰登·艾奇( Brendan Eich )创造,最初命名为 Mocha,后来改为 LiveScript,最终定名为 JavaScript。它最初是为了使网页更具交互性而设计的,现在已经成为 Web 开发领域不可或缺的一部分,并且随着时间的发展,其应用范围远远超出了浏览器。

应用场景

  • Web前端开发:通过 JS,开发者可以创建动态的 Web 页面,如交互式表单、动态内容更新、动画效果等。
  • Web后端开发:借助 Node.js,开发者可以在服务器端编写服务端逻辑。
  • 桌面应用程序:通过 Electron 等框架,可以开发跨平台桌面应用
  • 移动应用开发:利用 React NativeFlutterIonicUniApp 等框架,可以开发原生移动应用
  • 物联网(IoT)开发:使用 WebSocketsWebRTCService Workers 可以与物联网设备进行交互,也可以使用 D3.jsChart.js进行数据可视化开发。
  • 游戏开发:尽管 JS 不是主流游戏开发语言,但在H5和微信小游戏领域基于 cocos 也有丰富的应用。

JS特性

  • 基于原型:对象可以通过其他对象继承属性和方法。
  • 动态类型:变量类型可以根据赋值自动改变。
  • 弱类型:比较运算符会自动转换数据类型。
  • 垃圾回收:支持自动内存管理(垃圾回收)。
  • 异步编程:支持异步编程模式,如回调函数、Promises、async/await 等。

变量与数据类型

变量声明

使用 var、let 或 const 关键字声明变量。var 作用域为函数或全局,而 letconst 提供了块级作用域。

var x = 8;
let y = 11;
const z = 33;

数据类型

JS 基本类型有 7 种:

  • 空值( null
  • 未定义( undefined
  • 布尔值( boolean
  • 数字( number
  • 字符串( string
  • 对象( object
  • 符号( symbol,ES6 中新增 )

其中 object 是复合类型,包括很多内置对象:

  • String
  • Number
  • Boolean
  • Object
  • Function
  • Array
  • Date
  • RegExp
  • Error

tips:null 有时会被当作一种对象类型,但是这其实只是语言本身的一个 bug, 即对 null 执行 typeof null 时会返回字符串 object。实际上,null 本身是基本类型

操作运算符

  • 算术运算符+, -, *, /, %, **(指数)
  • 赋值运算符=, +=, -=, *=, /=, %=
  • 比较运算符==, ===, !=, !==, >, <, >=, <=
  • 逻辑运算符&&, ||, !

表达式

表达式是计算并产生值的代码片段。常见类型:

  • 字面量表达式:数值、字符串、布尔值、对象、数组、函数、undefined、null。

    let age = 30;
    let name = "Tom";
    let flag = true;
    let now = undefined;
    let nothing = null;
    
  • 变量表达式

    let x = 12;
    let y = x;
    
  • 算术表达式

    let sum = 3 + 8;        // 11
    let difference = 3 - 2; // 1
    
  • 赋值表达式

    let score = 22;
    
  • 函数调用表达式

    let result = Math.max(1, 2);  // 2
    
  • 对象与数组表达式

    let person = { name: "Jack", age: 30 };
    let numbers = [1, 2, 3, 4];
    
  • 逻辑表达式

    let isEnable = age >= 30 && flag;
    
  • 条件(三元)表达式

    let status = age >= 30 ? "Adult" : "Minor";
    
  • 逗号表达式

    let x = (console.log("First"), 8);
    

控制结构

JS 控制结构用于流程控制,包括条件判断和循环。

  • 条件语句

    if (condition1) {
      // ...
    } else if (condition2) {
      // ...
    } else {
      // ...
    }
    
    switch (expression) {
      case value1:
        // ...
        break;
      case value2:
        // ...
        break;
      default:
        // ...
    }
    
    let result = condition ? value1 : value2;
    
  • 循环语句(for, while, do-while)

    for (let i = 0; i < 10; i++) {
      console.log(i); // 0..9
    }
    
    var j = 0;
    while (condition) {
      if (j === 5) break;
      console.log(j); // 0..4
    }
    
    var k = 0;
    do {
      if (k === 5) break;
      console.log(k); // 0..4
    } while (condition);
    
  • 跳转语句(break, continue, return)

    for (let i = 0; i < items.length; i++) {
      if (someCondition) break;
    }
    
    for (let i = 0; i < items.length; i++) {
      if (skipCondition) continue;
      // ...
    }
    
    function myFunction() {
      if (condition) {
        return result;
      }
      // ...
    }
    

值的类型转换

比较与计算中常见显式与隐式转换:

  • 显示转换

    let num = Number("123");       // 123
    let num2 = parseInt("123", 10);// 123
    let str2 = String(123);        // "123"
    let b1 = Boolean(1);           // true
    let b0 = Boolean(0);           // false
    
  • 隐式转换

    let result = "5" + 5;       // "55"
    let result2 = [1, 2]+ [3, 4];// "1,23,4"
    let n1 = null + 1;          // 1
    let n2 = undefined + 1;     // NaN
    let n3 = +"123";            // 123
    let n4 = +"abc";            // NaN
    

宽松相等(==)与严格相等(===

== 允许在相等比较中进行强制类型转换,而 === 不允许。

42 == "42";  // true
"42" == 42;  // true
true == "42";// false(true -> 1)
"42" == true;// false(true -> 1)
null == undefined; // true
[42] == 42;        // true

JS 中“假”值""0-0NaNnullundefinedfalse

"0" == false; // true
false == 0;   // true
"" == 0;      // true
0 == [];      // true

数组(概要)

数组是灵活的序列容器:

  • 特性:动态大小、类型无关、索引从 0 开始
  • 基础操作:创建、访问/修改、栈方法(push/pop)、队列方法(shift/unshift
  • 排序sortreverse
  • 搜索indexOflastIndexOf
  • 迭代forEachmapfilterreduce

在实际开发中,数组最容易踩的两个点是:

  • sort() 默认按字符串比较:[10, 2, 3].sort() 会得到 ['10','2','3'] 的字典序
  • 变异方法 vs 不变方法:push/splice/sort/reverse 会修改原数组,而 slice/map/filter 会返回新数组

一般建议:

  • 与状态管理或 React/Vue 等框架配合时,优先选择返回新数组的方式,方便做“不可变更新”
  • 需要频繁在中间插入/删除大量元素时,评估是否改用链表/映射等结构,避免数组频繁搬移元素
let numbers = [1, 2, 3, 4, 5];
numbers.push(6);
let squares = numbers.map((x) => x * x);
let sum = numbers.reduce((a, b) => a + b);

作用域与提升

  • 词法作用域:由代码在编写时的结构决定,嵌套的作用域按从内到外的链条查找标识符
  • 变量提升var 声明会被提升;let/const 存在暂时性死区,不可在声明前访问
  • 函数提升:函数声明会整体提升;函数表达式不会提升其赋值结果
console.log(a); // undefined(var 提升)
var a = 1;

// let/const 的暂时性死区
// console.log(b); // ReferenceError
let b = 2;

foo(); // OK(函数声明提升)
function foo() { /* ... */ }

// bar(); // TypeError: bar is not a function
var bar = function() { /* ... */ };

this 绑定与调用位置

  • 默认绑定:非严格模式下指向全局对象,严格模式下为 undefined
  • 隐式绑定:作为对象方法调用时,this 绑定到该对象
  • 显式绑定call/apply/bind 指定 this
  • 构造调用new 调用将 this 绑定到新实例
  • 箭头函数:不绑定 this,捕获外层 this
const obj = {
  x: 1,
  getX() { return this.x; }
};
obj.getX();                // 1(隐式绑定)
const getX = obj.getX;
getX();                    // undefined 或报错(严格模式)
getX.call(obj);            // 1(显式绑定)

function C() { this.v = 42; }
new C();                   // { v: 42 }

const A = () => this;
A.call({ a: 1 });          // 仍为外层 this

函数与闭包

  • 函数声明 vs 表达式:声明有提升,表达式按赋值时机可控
  • 闭包:函数与其词法作用域的组合;可访问其外层作用域变量
  • 常见用途:封装私有变量、部分应用、惰性计算、事件处理

理解闭包的关键在于:函数会“记住”自己定义时所在的词法环境,而不是调用时的环境。 当内部函数被返回或在其他地方被调用时,它仍然可以访问创建它时所在作用域中的变量, 这些变量会一直被保留,直到不再有任何闭包引用它们为止。

function makeCounter() {
  let count = 0;
  return function() {
    count += 1;
    return count;
  };
}
const counter = makeCounter();
counter(); // 1
counter(); // 2

闭包也有一些常见坑:

  • 捕获循环变量时,如果使用 var,所有闭包会共享同一个变量引用,容易出现“循环结束后值全都一样”的现象
  • 闭包持有对外部对象的引用时,若不及时释放,会延长这些对象的生命周期,造成不必要的内存占用

柯里化(简述)

柯里化的核心思想是:把接收多个参数的函数拆解成一连串“每次只接收一个(或一部分)参数”的函数。 这样做的直接好处是:

  • 更容易进行“预配置”:先固定一部分参数,得到语义更强的函数
  • 便于函数组合:将复杂逻辑拆成多个简单小函数,再按需串联
function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) return fn.apply(this, args);
    return function(...rest) { return curried.apply(this, args.concat(rest)); };
  };
}
const add = (a, b, c) => a + b + c;
const add1 = curry(add)(1);
add1(2, 3); // 6

在业务代码中,一个常见用法是对“工具函数”做部分应用:

const match = curry((re, str) => re.test(str));
const isPhone = match(/^1\d{10}$/);
const isEmail = match(/^[^@]+@[^@]+$/);

isPhone('13812345678'); // true
isEmail('foo@bar.com'); // true

柯里化适合配合函数式风格(如 map/filter/reduce 管道)使用,但在日常开发中无需“强行柯里化一切”; 更好的策略是:当你发现某些参数在大量调用中总是固定不变时,再考虑将该函数做成可柯里化的形式。

递归与尾调用

  • 递归:函数调用自身解决更小子问题;注意终止条件与栈深
  • 尾调用优化:某些实现可对尾调用进行优化,降低栈占用(具体取决于引擎)
function factorial(n, acc = 1) {
  if (n <= 1) return acc;
  return factorial(n - 1, acc * n);
}
factorial(5); // 120

错误与异常

  • 抛出与捕获throw 抛出异常;try/catch/finally 捕获与收尾
  • 错误类型TypeErrorReferenceErrorSyntaxErrorRangeError
  • 实践:仅在异常路径抛错;为预期失败返回明确结果或错误码
function parseJSONSafe(str) {
  try {
    return { ok: true, data: JSON.parse(str) };
  } catch (e) {
    return { ok: false, error: e.message };
  } finally {
    // 这里可以做资源清理或统计上报
  }
}

async 函数中,try/catch 可以捕获 await 的异常:

async function fetchSafe(url) {
  try {
    const res = await fetch(url);
    if (!res.ok) return { ok: false, error: res.statusText };
    return { ok: true, data: await res.json() };
  } catch (e) {
    return { ok: false, error: e.message };
  }
}

更通用的实践是:在边界层(如接口适配器、控制器)集中捕获错误,内部函数尽量返回结构化结果, 这样既保持调用方易用,又避免异常在系统内四处传播难以追踪。

正则表达式基础

  • 声明:字面量 /pattern/flags 或构造器 new RegExp()
  • 常用标志i(忽略大小写)、g(全局)、m(多行)、s(点匹配换行)
  • 常用方法testmatchreplacesplit

从实战角度看,正则的价值主要体现在三个方面:

  • 轻量校验:如手机号、邮箱、简单路径匹配
  • 文本提取:日志解析、URL 参数截取等
  • 批量替换:在重构或格式化场景下快速替换符合模式的片段

同时也有几个常见坑:

  • 过于复杂的正则可读性极差,应考虑拆分成多个小步骤或配合解析器
  • 带有 g 标志的正则在多次 test 时会改变 lastIndex,导致结果看似“随机”

在现代 JS 中,推荐尽量为复杂正则配备示例与注释,并在可能的情况下使用命名捕获与非捕获分组,提升可维护性。

/\d{3}-\d{4}/.test('123-4567'); // true
'abc123'.replace(/\d+/g, '#');  // 'abc#'

小结

  • 以词法作用域与提升为基础理解运行时行为
  • 正确选择 this 绑定与函数形式,减少隐性 bug
  • 利用闭包与柯里化提升抽象能力与可维护性
  • 通过递归/尾调用与正则表达式补足实战技能