Skip to content

1. 函数

1.1 为什么需要函数

函数是 JavaScript 中的一等公民,是代码复用的基础。为什么需要函数?

1.1.1 代码复用

在开发过程中,经常会遇到需要重复执行相同或相似操作的情况。如果每次都重复编写相同的代码,会导致:

  • 代码冗余: 大量重复代码增加维护成本
  • 难以维护: 修改一处逻辑需要在多处同步修改
  • 易出错: 手动复制粘贴容易遗漏或出错

示例 - 没有使用函数:

javascript
// 计算 1 到 10 的和
let sum1 = 0;
for (let i = 1; i <= 10; i++) {
    sum1 += i;
}
console.log(sum1);

// 计算 1 到 100 的和
let sum2 = 0;
for (let i = 1; i <= 100; i++) {
    sum2 += i;
}
console.log(sum2);

// 计算 1 到 1000 的和
let sum3 = 0;
for (let i = 1; i <= 1000; i++) {
    sum3 += i;
}
console.log(sum3);

示例 - 使用函数:

javascript
// 定义求和函数
function sumTo(n) {
    let sum = 0;
    for (let i = 1; i <= n; i++) {
        sum += i;
    }
    return sum;
}

// 调用函数,代码简洁
console.log(sumTo(10));
console.log(sumTo(100));
console.log(sumTo(1000));

1.1.2 提高代码可读性

函数可以通过有意义的名称描述其功能,使代码更易理解:

javascript
// 没有函数,代码难以理解
if (year % 400 === 0 || (year % 100 !== 0 && year % 4 === 0)) {
    console.log('是闰年');
}

// 使用函数,一目了然
if (isLeapYear(year)) {
    console.log('是闰年');
}

function isLeapYear(year) {
    return year % 400 === 0 || (year % 100 !== 0 && year % 4 === 0);
}

1.1.3 模块化设计

函数允许我们将复杂问题分解为更小的、可管理的子问题:

javascript
// 计算三角形面积
function calculateTriangleArea(base, height) {
    return 0.5 * base * height;
}

// 计算圆形面积
function calculateCircleArea(radius) {
    return Math.PI * radius * radius;
}

// 计算矩形面积
function calculateRectangleArea(width, height) {
    return width * height;
}

// 根据形状类型计算面积
function calculateArea(shape, params) {
    switch(shape) {
        case 'triangle':
            return calculateTriangleArea(params.base, params.height);
        case 'circle':
            return calculateCircleArea(params.radius);
        case 'rectangle':
            return calculateRectangleArea(params.width, params.height);
        default:
            throw new Error('不支持的形状类型');
    }
}

1.1.4 封装与信息隐藏

函数可以封装实现细节,隐藏内部逻辑,只暴露必要的接口:

javascript
// 封装用户认证逻辑
function authenticateUser(username, password) {
    // 内部实现细节对外隐藏
    const user = findUserInDatabase(username);
    if (!user) {
        return { success: false, message: '用户不存在' };
    }
    
    const isValid = verifyPassword(password, user.hashedPassword);
    if (!isValid) {
        return { success: false, message: '密码错误' };
    }
    
    return { success: true, user: createSafeUserObject(user) };
}

// 调用者不需要关心实现细节
const result = authenticateUser('admin', '123456');

1.2 函数的基本使用

1.2.1 函数的定义方式

JavaScript 中有多种定义函数的方式:

方式一: 函数声明

javascript
function 函数名(参数1, 参数2, ...) {
    // 函数体
    return 返回值;
}

// 示例
function sayHello() {
    console.log('你好!');
}

function greet(name) {
    return '你好, ' + name + '!';
}

函数声明的特点:

  • 具有函数提升特性,可以在定义之前调用
  • 必须有函数名
  • 适合定义常用的工具函数
javascript
// 函数提升示例
sayHello(); // 可以在定义前调用,正常输出

function sayHello() {
    console.log('你好!');
}

方式二: 函数表达式

javascript
const 函数名 = function(参数1, 参数2, ...) {
    // 函数体
    return 返回值;
};

// 示例
const sayHello = function() {
    console.log('你好!');
};

const greet = function(name) {
    return '你好, ' + name + '!';
};

函数表达式的特点:

  • 不会提升,必须在定义后调用
  • 函数可以匿名
  • 常用作回调函数
javascript
// 错误: 不能在定义前调用
sayHello(); // TypeError: sayHello is not a function

const sayHello = function() {
    console.log('你好!');
};

方式三: 箭头函数 (ES6)

javascript
const 函数名 = (参数1, 参数2, ...) => {
    // 函数体
    return 返回值;
};

// 示例
const sayHello = () => {
    console.log('你好!');
};

const greet = (name) => {
    return '你好, ' + name + '!';
};

箭头函数的简化写法:

javascript
// 单个参数可以省略括号
const greet = name => {
    return '你好, ' + name + '!';
};

// 单行返回可以省略大括号和 return
const greet = name => '你好, ' + name + '!';

// 多个参数需要括号
const add = (a, b) => a + b;

// 无参数需要空括号
const sayHello = () => '你好!';

箭头函数的特点:

  • 语法简洁
  • 没有 this 绑定,this 指向外层作用域
  • 没有 arguments 对象
  • 不能用作构造函数(不能用 new)
  • 没有 prototype 属性

1.2.2 函数的调用方式

方式一: 直接调用

javascript
function sayHello() {
    console.log('你好!');
}

sayHello(); // 输出: 你好!

方式二: 带参数的调用

javascript
function greet(name, age) {
    console.log(`你好,我是${name},今年${age}岁`);
}

greet('张三', 25); // 输出: 你好,我是张三,今年25岁

方式三: 方法调用 (作为对象的方法)

javascript
const person = {
    name: '张三',
    age: 25,
    introduce: function() {
        console.log(`你好,我是${this.name},今年${this.age}岁`);
    }
};

person.introduce(); // 输出: 你好,我是张三,今年25岁

方式四: 构造函数调用 (使用 new)

javascript
function Person(name, age) {
    this.name = name;
    this.age = age;
}

const person = new Person('张三', 25);
console.log(person.name); // 输出: 张三
console.log(person.age);  // 输出: 25

方式五: call/apply/bind 调用

javascript
function greet(greeting) {
    console.log(`${greeting}, 我是${this.name}`);
}

const person = { name: '张三' };

// call - 逐个传递参数
greet.call(person, '你好'); // 输出: 你好, 我是张三

// apply - 参数作为数组传递
greet.apply(person, ['你好']); // 输出: 你好, 我是张三

// bind - 创建新函数,稍后调用
const boundGreet = greet.bind(person);
boundGreet('你好'); // 输出: 你好, 我是张三

1.2.3 函数的参数

基本参数传递

javascript
function add(a, b) {
    return a + b;
}

const result = add(3, 5);
console.log(result); // 输出: 8

默认参数 (ES6)

javascript
function greet(name = '访客') {
    console.log(`你好, ${name}!`);
}

greet();        // 输出: 你好, 访客!
greet('张三');  // 输出: 你好, 张三!

// 带多个默认参数
function createUser(name = '匿名', age = 18, city = '北京') {
    console.log(`姓名: ${name}, 年龄: ${age}, 城市: ${city}`);
}

createUser('张三', 25); // 输出: 姓名: 张三, 年龄: 25, 城市: 北京

参数是按值传递的

javascript
// 基本类型 - 传递值的副本
function modifyPrimitive(num) {
    num = 100;
    console.log('函数内:', num); // 100
}

let a = 10;
modifyPrimitive(a);
console.log('函数外:', a); // 10, 原值未改变

// 引用类型 - 传递引用的副本
function modifyArray(arr) {
    arr.push(4);
    console.log('函数内:', arr); // [1, 2, 3, 4]
}

let arr = [1, 2, 3];
modifyArray(arr);
console.log('函数外:', arr); // [1, 2, 3, 4], 原数组被修改

1.2.4 函数传参-参数默认值

形参: 可以看做变量,但是如果一个变量不给值,默认是什么? undefined
但是如果做用户不输入实参,刚才的案例,则出现 undefined + undefined 结果是什么?NaN

我们可以改进下,用户不输入实参,可以给 形参默认值,可以默认为 0, 这样程序更严谨,可以如下操作:

javascript
function getSum(x = 0,y = 0) {
    console.log(x + y)
}

//这个默认值只会在缺少实参参数传递时 才会被执行,所以有参数会优先执行传递过来的实参, 否则默认为undefined
getSum()
getSum(1,2)

1.3 函数返回值

1.3.1 return 语句

函数通过 return 语句返回值给调用者:

javascript
function add(a, b) {
    return a + b;
}

const result = add(3, 5);
console.log(result); // 输出: 8

1.3.2 没有 return 或 return 后没有值

如果函数没有 returnreturn 后没有值,默认返回 undefined:

javascript
function noReturn1() {
    console.log('没有 return 语句');
}

function noReturn2() {
    console.log('有 return 但没有返回值');
    return;
}

console.log(noReturn1()); // 输出: undefined
console.log(noReturn2()); // 输出: undefined

1.3.3 提前返回

可以在函数中的任何位置使用 return 提前结束函数执行:

javascript
function checkAge(age) {
    if (age < 0) {
        return '年龄不能为负数';
    }
    if (age < 18) {
        return '未成年';
    }
    if (age < 60) {
        return '成年';
    }
    return '老年';
}

console.log(checkAge(-5));  // 输出: 年龄不能为负数
console.log(checkAge(15));  // 输出: 未成年
console.log(checkAge(30));  // 输出: 成年
console.log(checkAge(70));  // 输出: 老年

提前返回的优势:

  • 代码逻辑更清晰
  • 减少嵌套层次
  • 符合"早返回"原则
javascript
// 不好: 嵌套过深
function checkAge(age) {
    if (age >= 0) {
        if (age < 18) {
            return '未成年';
        } else if (age < 60) {
            return '成年';
        } else {
            return '老年';
        }
    } else {
        return '年龄不能为负数';
    }
}

// 好: 提前返回
function checkAge(age) {
    if (age < 0) {
        return '年龄不能为负数';
    }
    if (age < 18) {
        return '未成年';
    }
    if (age < 60) {
        return '成年';
    }
    return '老年';
}

1.3.4 返回多个值

JavaScript 函数只能返回一个值,但可以通过对象或数组返回多个值:

方式一: 返回对象

javascript
function getPersonInfo() {
    return {
        name: '张三',
        age: 25,
        city: '北京',
        email: 'zhangsan@example.com'
    };
}

// 解构赋值获取返回值
const { name, age, city } = getPersonInfo();
console.log(name); // 张三
console.log(age);  // 25
console.log(city); // 北京

// 直接获取整个对象
const personInfo = getPersonInfo();
console.log(personInfo.email); // zhangsan@example.com

方式二: 返回数组

javascript
function getMinMax(arr) {
    let min = arr[0];
    let max = arr[0];
    
    for (let i = 1; i < arr.length; i++) {
        if (arr[i] < min) min = arr[i];
        if (arr[i] > max) max = arr[i];
    }
    
    return [min, max];
}

// 解构赋值
const [min, max] = getMinMax([5, 2, 8, 1, 9]);
console.log(min); // 1
console.log(max); // 9

1.3.5 返回值的使用场景

用于数学计算

javascript
function add(a, b) {
    return a + b;
}

function multiply(a, b) {
    return a * b;
}

console.log(add(3, 5));      // 8
console.log(multiply(4, 6)); // 24

用于数据验证

javascript
function isValidEmail(email) {
    const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return regex.test(email);
}

console.log(isValidEmail('test@example.com')); // true
console.log(isValidEmail('invalid-email'));     // false

用于数据处理

javascript
function formatPrice(price) {
    return '¥' + price.toFixed(2);
}

function formatDate(date) {
    const year = date.getFullYear();
    const month = String(date.getMonth() + 1).padStart(2, '0');
    const day = String(date.getDate()).padStart(2, '0');
    return `${year}-${month}-${day}`;
}

console.log(formatPrice(99.9));       // ¥99.90
console.log(formatDate(new Date()));   // 2024-01-15

1.3.5 返回值的使用细节

  • 在函数体中使用 return 关键字能将内部的执行结果交给函数外部使用
  • return 后面代码不会再被执行,会立即结束当前函数,所以 return 后面的数据不要换行写
  • return函数可以没有 return,这种情况函数默认返回值为 undefined

1.4 作用域

作用域是变量和函数的可访问范围,决定了在代码的哪些地方可以访问哪些变量。

1.4.1 全局作用域

在任何函数外部声明的变量拥有全局作用域,可以在代码的任何地方访问:

javascript
// 全局变量
const globalVar = '我是全局变量';

function func1() {
    console.log(globalVar); // 可以访问全局变量
}

function func2() {
    console.log(globalVar); // 可以访问全局变量
}

func1(); // 输出: 我是全局变量
func2(); // 输出: 我是全局变量
console.log(globalVar); // 输出: 我是全局变量

全局变量的注意事项:

  • 在浏览器环境中,全局变量会被添加到 window 对象上
  • 过多的全局变量容易造成命名冲突
  • 应尽量避免使用全局变量

1.4.2 函数作用域

在函数内部声明的变量只能在函数内部访问:

javascript
function testScope() {
    const localVar = '我是局部变量';
    console.log(localVar); // 可以访问
}

testScope();
console.log(localVar); // ReferenceError: localVar is not defined

函数作用域的特点:

javascript
function outerFunction() {
    const outerVar = '外部变量';
    
    function innerFunction() {
        const innerVar = '内部变量';
        console.log(outerVar); // 可以访问外部变量
        console.log(innerVar); // 可以访问内部变量
    }
    
    innerFunction();
    // console.log(innerVar); // 错误: 无法访问内部函数的变量
}

outerFunction();

1.4.3 块级作用域 (ES6)

使用 letconst 声明的变量拥有块级作用域:

javascript
if (true) {
    const blockVar = '块级变量';
    let anotherVar = '另一个块级变量';
    console.log(blockVar); // 可以访问
}

// console.log(blockVar); // ReferenceError: blockVar is not defined

块级作用域的常见场景:

javascript
// if 语句
if (true) {
    const x = 10;
    console.log(x); // 10
}
// console.log(x); // 错误

// for 循环
for (let i = 0; i < 3; i++) {
    console.log(i); // 0, 1, 2
}
// console.log(i); // 错误

// try-catch
try {
    const errorVar = '错误信息';
} catch (e) {
    console.log(e);
}
// console.log(errorVar); // 错误

var 与 let/const 的区别:

javascript
// var 只有函数作用域,没有块级作用域
if (true) {
    var x = 10;
}
console.log(x); // 10, 可以访问

// let 和 const 有块级作用域
if (true) {
    let y = 20;
}
console.log(y); // ReferenceError: y is not defined

1.4.4 作用域链

当访问一个变量时,JavaScript 会沿着作用域链向外查找:

javascript
const globalVar = '全局';

function outer() {
    const outerVar = '外部';
    
    function inner() {
        const innerVar = '内部';
        console.log(innerVar); // 输出: 内部
        console.log(outerVar); // 输出: 外部
        console.log(globalVar); // 输出: 全局
    }
    
    inner();
}

outer();

作用域链查找规则:

  1. 先在当前作用域查找
  2. 如果找不到,向外层作用域查找
  3. 一直查找到全局作用域
  4. 如果全局作用域也没有,抛出 ReferenceError
javascript
const x = '全局 x';

function func1() {
    const x = 'func1 x';
    
    function func2() {
        const x = 'func2 x';
        console.log(x); // 输出: func2 x (使用最近的定义)
    }
    
    func2();
}

func1();

1.4.5 变量遮蔽

内层作用域的变量会遮蔽外层同名变量:

javascript
const x = '全局 x';

function test() {
    const x = '局部 x';
    console.log(x); // 输出: 局部 x, 遮蔽了全局 x
}

test();
console.log(x); // 输出: 全局 x

1.5 匿名函数

匿名函数是没有函数名的函数,常用于以下场景。

1.5.1 函数表达式中的匿名函数

javascript
const greet = function(name) {
    return '你好, ' + name + '!';
};

console.log(greet('张三')); // 输出: 你好, 张三!

1.5.2 作为回调函数

匿名函数常作为回调函数传递给其他函数:

javascript
// setTimeout 的回调
setTimeout(function() {
    console.log('1秒后执行');
}, 1000);

// 数组方法的回调
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(function(num) {
    return num * 2;
});
console.log(doubled); // [2, 4, 6, 8, 10]

// 过滤数组
const evens = numbers.filter(function(num) {
    return num % 2 === 0;
});
console.log(evens); // [2, 4]

1.5.3 事件处理

javascript
const button = document.querySelector('button');

button.addEventListener('click', function(event) {
    console.log('按钮被点击了');
    console.log('事件对象:', event);
});

1.5.4 立即执行函数表达式 (IIFE)

定义后立即执行的匿名函数:

javascript
// 基本语法
(function() {
    console.log('立即执行');
})();

// 带参数
(function(name) {
    console.log('你好,' + name);
})('张三');

// 创建私有作用域
const result = (function() {
    let privateVar = '私有变量';
    return {
        getValue: function() {
            return privateVar;
        }
    };
})();

console.log(result.getValue()); // 输出: 私有变量

IIFE 的用途:

  • 创建私有作用域,避免污染全局命名空间
  • 在模块化开发中隔离代码
  • 执行一次性初始化代码

1.5.5 箭头函数形式的匿名函数

ES6 引入的箭头函数提供了更简洁的匿名函数语法:

javascript
// 基本箭头函数
const greet = (name) => {
    return '你好, ' + name + '!';
};

// 简化写法
const greet = name => '你好, ' + name + '!';

// 作为回调函数
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(num => num * 2);
console.log(doubled); // [2, 4, 6, 8, 10]

// 多个参数需要括号
const add = (a, b) => a + b;
console.log(add(3, 5)); // 8

1.5.6 匿名函数的注意事项

匿名函数在栈追踪中的问题

javascript
// 命名函数 - 调试友好
function namedFunction() {
    throw new Error('出错');
}

// 匿名函数 - 调试困难
setTimeout(function() {
    throw new Error('出错');
}, 100);

解决方法: 使用命名函数表达式:

javascript
// 给匿名函数命名
setTimeout(function errorHandler() {
    throw new Error('出错');
}, 100);

this 指向问题

箭头函数与普通匿名函数的 this 指向不同:

javascript
const obj = {
    name: '张三',
    
    // 普通函数 - this 指向调用者
    regular: function() {
        setTimeout(function() {
            console.log(this.name); // undefined
        }, 100);
    },
    
    // 箭头函数 - this 继承外层作用域
    arrow: function() {
        setTimeout(() => {
            console.log(this.name); // 张三
        }, 100);
    }
};

obj.regular(); // undefined
obj.arrow();   // 张三

1.5.7 匿名函数的实际应用

高阶函数中的使用

javascript
function processArray(arr, callback) {
    return arr.map(callback);
}

const numbers = [1, 2, 3, 4, 5];
const squared = processArray(numbers, x => x * x);
console.log(squared); // [1, 4, 9, 16, 25]

函数工厂

javascript
function createMultiplier(factor) {
    return function(number) {
        return number * factor;
    };
}

const double = createMultiplier(2);
const triple = createMultiplier(3);

console.log(double(5)); // 10
console.log(triple(5)); // 15

模块模式

javascript
const counter = (function() {
    let count = 0;
    
    return {
        increment: function() {
            count++;
            return count;
        },
        decrement: function() {
            count--;
            return count;
        },
        getCount: function() {
            return count;
        }
    };
})();

console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.getCount()); // 2

2. 逻辑中断

逻辑中断是指在使用逻辑运算符(&&||)时,根据第一个操作数的真假值,决定是否执行第二个操作数的特性。

2.1 逻辑中断的基本概念

JavaScript 中的逻辑运算符具有"短路求值"(Short-circuit evaluation)的特性:

  • 逻辑与 (&&): 如果第一个操作数为假值,则返回第一个操作数,不再执行第二个操作数
  • 逻辑或 (||): 如果第一个操作数为真值,则返回第一个操作数,不再执行第二个操作数

2.2 逻辑与 && 的中断

基本规则: 左边为假,则中断,返回左边值;左边为真,则继续执行右边,返回右边值

javascript
// 情况1: 左边为假值,中断并返回左边值
const result1 = 0 && 100;
console.log(result1); // 0

const result2 = '' && 'hello';
console.log(result2); // '' (空字符串)

const result3 = false && true;
console.log(result3); // false

const result4 = null && 100;
console.log(result4); // null

const result5 = undefined && 100;
console.log(result5); // undefined

// 情况2: 左边为真值,继续执行并返回右边值
const result6 = 1 && 100;
console.log(result6); // 100

const result7 = 'hello' && 'world';
console.log(result7); // 'world'

const result8 = true && 200;
console.log(result8); // 200

const result9 = {} && 'object';
console.log(result9); // 'object'

const result10 = [] && 'array';
console.log(result10); // 'array'

逻辑中断的实际应用:

javascript
// 应用1: 条件执行
function login() {
    console.log('执行登录操作');
    return true;
}

const isLogin = true;
isLogin && login(); // isLogin 为 true,执行 login()

// 应用2: 验证后执行
function getUserData(userId) {
    console.log('获取用户数据:', userId);
    return { name: '张三' };
}

const userId = '123';
userId && getUserData(userId);

// 应用3: 防止空值错误
const user = {
    name: '张三',
    address: {
        city: '北京'
    }
};

// 安全访问嵌套属性
const city = user.address && user.address.city;
console.log(city); // 北京

// 应用4: 函数存在性检查
const utils = {
    formatDate: function(date) {
        return '2024-01-15';
    }
};

utils.formatDate && utils.formatDate(new Date());

2.3 逻辑或 || 的中断

基本规则: 左边为真,则中断,返回左边值;左边为假,则继续执行右边,返回右边值

javascript
// 情况1: 左边为真值,中断并返回左边值
const result1 = 1 || 100;
console.log(result1); // 1

const result2 = 'hello' || 'world';
console.log(result2); // 'hello'

const result3 = true || false;
console.log(result3); // true

const result4 = {} || 'object';
console.log(result4); // {}

const result5 = [] || 'array';
console.log(result5); // []

// 情况2: 左边为假值,继续执行并返回右边值
const result6 = 0 || 100;
console.log(result6); // 100

const result7 = '' || 'default';
console.log(result7); // 'default'

const result8 = false || true;
console.log(result8); // true

const result9 = null || 'value';
console.log(result9); // 'value'

const result10 = undefined || 100;
console.log(result10); // 100

逻辑中断的实际应用:

javascript
// 应用1: 设置默认值
let username = '';
const displayName = username || '匿名用户';
console.log(displayName); // '匿名用户'

let age = 0;
const displayAge = age || 18;
console.log(displayAge); // 18

// 应用2: 函数参数默认值
function greet(name) {
    const userName = name || '访客';
    console.log('你好, ' + userName);
}

greet('张三');  // 你好, 张三
greet();        // 你好, 访客

// 应用3: 对象属性默认值
const config = {
    timeout: null,
    retries: undefined
};

const timeout = config.timeout || 5000;
const retries = config.retries || 3;

console.log(timeout); // 5000
console.log(retries); // 3

// 应用4: 处理可能为空的值
function getValue(obj, key, defaultValue) {
    return obj[key] || defaultValue;
}

const data = {
    name: '张三',
    // age: undefined
};

console.log(getValue(data, 'name', '未知')); // 张三
console.log(getValue(data, 'age', 18));      // 18

2.4 逻辑中断的综合应用

javascript
// 综合应用1: 链式验证
function validateUser(user) {
    return user && user.isLoggedIn && user.hasPermission && '用户已验证';
}

const user1 = { isLoggedIn: true, hasPermission: true };
console.log(validateUser(user1)); // '用户已验证'

const user2 = { isLoggedIn: false, hasPermission: true };
console.log(validateUser(user2)); // false

// 综合应用2: 多级默认值
function getConfig(config) {
    return {
        theme: config.theme || config.color || 'light',
        fontSize: config.fontSize || config.size || 14,
        language: config.language || config.lang || 'zh-CN'
    };
}

const userConfig = { color: 'dark', size: 16 };
const finalConfig = getConfig(userConfig);
console.log(finalConfig); // { theme: 'dark', fontSize: 16, language: 'zh-CN' }

// 综合应用3: 条件赋值
const score = 85;
const grade = score >= 90 && 'A' || score >= 80 && 'B' || score >= 70 && 'C' || 'D';
console.log(grade); // B

// 综合应用4: 函数存在性检查并调用
const math = {
    add: function(a, b) {
        return a + b;
    }
    // multiply 未定义
};

const sum = math.add && math.add(3, 5) || '函数不存在';
console.log(sum); // 8

const product = math.multiply && math.multiply(3, 5) || '函数不存在';
console.log(product); // 函数不存在

2.5 逻辑中断的注意事项

javascript
// 注意1: 数字 0 和空字符串是假值,但可能是有效值
const count = 0;
const displayCount = count || 10;
console.log(displayCount); // 10, 但我们可能想要 0

// 解决方法: 使用 nullish coalescing operator (??)
const displayCount2 = count ?? 10;
console.log(displayCount2); // 0

// 注意2: 空数组是假值,但可能是有效值
const items = [];
const displayItems = items || ['default'];
console.log(displayItems); // ['default'], 但我们可能想要 []

// 注意3: 逻辑中断返回的是原始值,不是布尔值
const result1 = 0 && 100;
console.log(result1, typeof result1); // 0 'number'

const result2 = '' || 'default';
console.log(result2, typeof result2); // 'default' 'string'

// 如果需要布尔值,使用 Boolean() 或双重否定
const bool1 = !!(0 && 100);
console.log(bool1); // false

const bool2 = !!('' || 'default');
console.log(bool2); // true

3. 布尔类型转换

JavaScript 中所有值都可以转换为布尔类型,这决定了条件语句的执行结果。

3.1 布尔类型转换的方法

方法一: Boolean() 函数

javascript
const bool1 = Boolean(0);
console.log(bool1); // false

const bool2 = Boolean('hello');
console.log(bool2); // true

方法二: 双重否定 !!

javascript
const bool1 = !!0;
console.log(bool1); // false

const bool2 = !!'hello';
console.log(bool2); // true

方法三: 使用 if 语句隐式转换

javascript
if (0) {
    console.log('不会执行');
}

if ('hello') {
    console.log('会执行'); // 输出: 会执行
}

3.2 假值 (Falsy Values)

以下 6 个值转换为布尔类型时为 false:

类型示例
falseBooleanBoolean(false)false
0NumberBoolean(0)false
-0NumberBoolean(-0)false
0nBigIntBoolean(0n)false
''""StringBoolean('')false
nullNullBoolean(null)false
undefinedUndefinedBoolean(undefined)false
NaNNumberBoolean(NaN)false

示例:

javascript
// 数字假值
console.log(Boolean(0));     // false
console.log(Boolean(-0));    // false
console.log(Boolean(0n));    // false
console.log(Boolean(NaN));   // false

// 字符串假值
console.log(Boolean(''));    // false
console.log(Boolean(""));    // false

// 其他假值
console.log(Boolean(null));         // false
console.log(Boolean(undefined));    // false
console.log(Boolean(false));        // false

// 在条件语句中的表现
if (0) {
    // 不会执行
}

if ('') {
    // 不会执行
}

if (null) {
    // 不会执行
}

if (undefined) {
    // 不会执行
}

3.3 真值 (Truthy Values)

除了假值之外的所有其他值转换为布尔类型时都为 true:

javascript
// 数字真值
console.log(Boolean(1));          // true
console.log(Boolean(-1));         // true
console.log(Boolean(3.14));       // true
console.log(Boolean(Infinity));   // true

// 字符串真值
console.log(Boolean('hello'));     // true
console.log(Boolean('0'));        // true (注意: 字符串 '0' 是真值!)
console.log(Boolean('false'));     // true (注意: 字符串 'false' 是真值!)
console.log(Boolean(' '));        // true (空格字符串是真值!)

// 对象真值
console.log(Boolean({}));         // true (空对象)
console.log(Boolean([]));         // true (空数组)

// 函数真值
console.log(Boolean(function(){})); // true

// 在条件语句中的表现
if (1) {
    console.log('会执行'); // 输出: 会执行
}

if ('hello') {
    console.log('会执行'); // 输出: 会执行
}

if ({}) {
    console.log('会执行'); // 输出: 会执行
}

if ([]) {
    console.log('会执行'); // 输出: 会执行
}

3.4 常见的布尔转换陷阱

陷阱1: 字符串 '0' 是真值

javascript
// 数字 0 是假值
if (0) {
    console.log('不会执行');
}

// 字符串 '0' 是真值
if ('0') {
    console.log('会执行'); // 输出: 会执行
}

// 数值输入验证
const input = '0';
if (input) {
    console.log('输入有效'); // 会输出,但可能不是预期结果
}

// 解决方法: 转换为数字或进行严格判断
if (Number(input)) {
    console.log('输入有效');
}

if (input !== '' && input !== null && input !== undefined) {
    console.log('输入有效');
}

陷阱2: 空数组和空对象是真值

javascript
// 空数组是真值
const arr = [];
if (arr) {
    console.log('数组是真值'); // 输出: 数组是真值
}

// 空对象是真值
const obj = {};
if (obj) {
    console.log('对象是真值'); // 输出: 对象是真值
}

// 检查数组是否为空
if (arr.length > 0) {
    console.log('数组不为空');
}

// 检查对象是否为空
if (Object.keys(obj).length > 0) {
    console.log('对象不为空');
}

陷阱3: 空字符串和字符串 'false'

javascript
const str1 = '';
const str2 = 'false';

console.log(Boolean(str1)); // false
console.log(Boolean(str2)); // true (字符串 'false' 是真值!)

// 如果需要将字符串 'false' 转换为布尔值 false
function parseBoolean(str) {
    if (str === 'false' || str === 'False' || str === 'FALSE' || str === '0') {
        return false;
    }
    return !!str;
}

console.log(parseBoolean('true'));   // true
console.log(parseBoolean('false'));  // false
console.log(parseBoolean('0'));      // false
console.log(parseBoolean(''));       // false

陷阱4: NaN 是假值

javascript
const result = Number('hello');
console.log(result);        // NaN
console.log(Boolean(result)); // false

// 数学运算中的 NaN
const a = 0 / 0;
console.log(Boolean(a));    // false

// 检查 NaN
if (isNaN(result)) {
    console.log('结果不是有效数字');
}

// 更严格的检查 (推荐)
if (Number.isNaN(result)) {
    console.log('结果是 NaN');
}

3.5 布尔转换的实际应用

应用1: 验证表单输入

javascript
function validateForm(data) {
    const errors = [];

    if (!data.username) {
        errors.push('用户名不能为空');
    }

    if (!data.email) {
        errors.push('邮箱不能为空');
    }

    if (!data.age) {
        errors.push('年龄不能为空');
    }

    return {
        isValid: errors.length === 0,
        errors
    };
}

const formData1 = { username: '张三', email: 'test@test.com', age: 25 };
console.log(validateForm(formData1)); // { isValid: true, errors: [] }

const formData2 = { username: '', email: '', age: '' };
console.log(validateForm(formData2)); 
// { isValid: false, errors: ['用户名不能为空', '邮箱不能为空', '年龄不能为空'] }

应用2: 提供默认值

javascript
function createButton(config) {
    return {
        text: config.text || '确定',
        disabled: !!config.disabled, // 转换为布尔值
        visible: config.visible !== false, // 默认为 true
        onClick: config.onClick || function() {}
    };
}

const button1 = createButton({});
console.log(button1); 
// { text: '确定', disabled: false, visible: true, onClick: [Function] }

const button2 = createButton({
    text: '取消',
    disabled: 'true',
    visible: false
});
console.log(button2);
// { text: '取消', disabled: true, visible: false, onClick: [Function] }

应用3: 过滤数组中的假值

javascript
const data = [0, 1, '', 'hello', null, undefined, false, true, [], {}];

// 方法1: 使用 Boolean 作为回调函数
const filtered1 = data.filter(Boolean);
console.log(filtered1); // [1, 'hello', true, [], {}]

// 方法2: 使用 !! 运算符
const filtered2 = data.filter(item => !!item);
console.log(filtered2); // [1, 'hello', true, [], {}]

// 方法3: 使用双参数判断
const filtered3 = data.filter(item => item !== null && item !== undefined);
console.log(filtered3); // [0, 1, '', 'hello', false, true, [], {}]

应用4: 条件渲染

javascript
// 在前端框架中的应用 (伪代码)
function renderUser(user) {
    if (!user) {
        return '<div>加载中...</div>';
    }

    if (!user.avatar) {
        user.avatar = 'default-avatar.png';
    }

    return `
        <div class="user">
            <img src="${user.avatar}" />
            <h3>${user.name || '匿名用户'}</h3>
            <p>${user.bio || '这个人很懒,什么都没写'}</p>
            ${user.isVip ? '<span class="vip">VIP</span>' : ''}
        </div>
    `;
}

console.log(renderUser(null));
console.log(renderUser({ name: '张三', isVip: true }));

应用5: 简洁的条件判断

javascript
// 传统写法
function checkPermission(user) {
    if (user) {
        if (user.isLoggedIn) {
            if (user.hasPermission) {
                return true;
            }
        }
    }
    return false;
}

// 使用布尔转换和逻辑中断
function checkPermission(user) {
    return !!(user && user.isLoggedIn && user.hasPermission);
}

// 测试
const user1 = { isLoggedIn: true, hasPermission: true };
const user2 = { isLoggedIn: false, hasPermission: true };
const user3 = null;

console.log(checkPermission(user1)); // true
console.log(checkPermission(user2)); // false
console.log(checkPermission(user3)); // false

3.6 布尔转换总结表

Boolean()!!条件语句中
falsefalsefalse不执行
0, -0, 0n, NaNfalsefalse不执行
'', ""falsefalse不执行
null, undefinedfalsefalse不执行
truetruetrue执行
非零数字truetrue执行
非空字符串truetrue执行
对象(包括 {}, [])truetrue执行
函数truetrue执行

Released under the MIT License.