1. 函数
1.1 为什么需要函数
函数是 JavaScript 中的一等公民,是代码复用的基础。为什么需要函数?
1.1.1 代码复用
在开发过程中,经常会遇到需要重复执行相同或相似操作的情况。如果每次都重复编写相同的代码,会导致:
- 代码冗余: 大量重复代码增加维护成本
- 难以维护: 修改一处逻辑需要在多处同步修改
- 易出错: 手动复制粘贴容易遗漏或出错
示例 - 没有使用函数:
// 计算 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);示例 - 使用函数:
// 定义求和函数
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 提高代码可读性
函数可以通过有意义的名称描述其功能,使代码更易理解:
// 没有函数,代码难以理解
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 模块化设计
函数允许我们将复杂问题分解为更小的、可管理的子问题:
// 计算三角形面积
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 封装与信息隐藏
函数可以封装实现细节,隐藏内部逻辑,只暴露必要的接口:
// 封装用户认证逻辑
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 中有多种定义函数的方式:
方式一: 函数声明
function 函数名(参数1, 参数2, ...) {
// 函数体
return 返回值;
}
// 示例
function sayHello() {
console.log('你好!');
}
function greet(name) {
return '你好, ' + name + '!';
}函数声明的特点:
- 具有函数提升特性,可以在定义之前调用
- 必须有函数名
- 适合定义常用的工具函数
// 函数提升示例
sayHello(); // 可以在定义前调用,正常输出
function sayHello() {
console.log('你好!');
}方式二: 函数表达式
const 函数名 = function(参数1, 参数2, ...) {
// 函数体
return 返回值;
};
// 示例
const sayHello = function() {
console.log('你好!');
};
const greet = function(name) {
return '你好, ' + name + '!';
};函数表达式的特点:
- 不会提升,必须在定义后调用
- 函数可以匿名
- 常用作回调函数
// 错误: 不能在定义前调用
sayHello(); // TypeError: sayHello is not a function
const sayHello = function() {
console.log('你好!');
};方式三: 箭头函数 (ES6)
const 函数名 = (参数1, 参数2, ...) => {
// 函数体
return 返回值;
};
// 示例
const sayHello = () => {
console.log('你好!');
};
const greet = (name) => {
return '你好, ' + name + '!';
};箭头函数的简化写法:
// 单个参数可以省略括号
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 函数的调用方式
方式一: 直接调用
function sayHello() {
console.log('你好!');
}
sayHello(); // 输出: 你好!方式二: 带参数的调用
function greet(name, age) {
console.log(`你好,我是${name},今年${age}岁`);
}
greet('张三', 25); // 输出: 你好,我是张三,今年25岁方式三: 方法调用 (作为对象的方法)
const person = {
name: '张三',
age: 25,
introduce: function() {
console.log(`你好,我是${this.name},今年${this.age}岁`);
}
};
person.introduce(); // 输出: 你好,我是张三,今年25岁方式四: 构造函数调用 (使用 new)
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 调用
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 函数的参数
基本参数传递
function add(a, b) {
return a + b;
}
const result = add(3, 5);
console.log(result); // 输出: 8默认参数 (ES6)
function greet(name = '访客') {
console.log(`你好, ${name}!`);
}
greet(); // 输出: 你好, 访客!
greet('张三'); // 输出: 你好, 张三!
// 带多个默认参数
function createUser(name = '匿名', age = 18, city = '北京') {
console.log(`姓名: ${name}, 年龄: ${age}, 城市: ${city}`);
}
createUser('张三', 25); // 输出: 姓名: 张三, 年龄: 25, 城市: 北京参数是按值传递的
// 基本类型 - 传递值的副本
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, 这样程序更严谨,可以如下操作:
function getSum(x = 0,y = 0) {
console.log(x + y)
}
//这个默认值只会在缺少实参参数传递时 才会被执行,所以有参数会优先执行传递过来的实参, 否则默认为undefined
getSum()
getSum(1,2)1.3 函数返回值
1.3.1 return 语句
函数通过 return 语句返回值给调用者:
function add(a, b) {
return a + b;
}
const result = add(3, 5);
console.log(result); // 输出: 81.3.2 没有 return 或 return 后没有值
如果函数没有 return 或 return 后没有值,默认返回 undefined:
function noReturn1() {
console.log('没有 return 语句');
}
function noReturn2() {
console.log('有 return 但没有返回值');
return;
}
console.log(noReturn1()); // 输出: undefined
console.log(noReturn2()); // 输出: undefined1.3.3 提前返回
可以在函数中的任何位置使用 return 提前结束函数执行:
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)); // 输出: 老年提前返回的优势:
- 代码逻辑更清晰
- 减少嵌套层次
- 符合"早返回"原则
// 不好: 嵌套过深
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 函数只能返回一个值,但可以通过对象或数组返回多个值:
方式一: 返回对象
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方式二: 返回数组
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); // 91.3.5 返回值的使用场景
用于数学计算
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用于数据验证
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用于数据处理
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-151.3.5 返回值的使用细节
- 在函数体中使用
return关键字能将内部的执行结果交给函数外部使用 return后面代码不会再被执行,会立即结束当前函数,所以return后面的数据不要换行写return函数可以没有return,这种情况函数默认返回值为undefined
1.4 作用域
作用域是变量和函数的可访问范围,决定了在代码的哪些地方可以访问哪些变量。
1.4.1 全局作用域
在任何函数外部声明的变量拥有全局作用域,可以在代码的任何地方访问:
// 全局变量
const globalVar = '我是全局变量';
function func1() {
console.log(globalVar); // 可以访问全局变量
}
function func2() {
console.log(globalVar); // 可以访问全局变量
}
func1(); // 输出: 我是全局变量
func2(); // 输出: 我是全局变量
console.log(globalVar); // 输出: 我是全局变量全局变量的注意事项:
- 在浏览器环境中,全局变量会被添加到
window对象上 - 过多的全局变量容易造成命名冲突
- 应尽量避免使用全局变量
1.4.2 函数作用域
在函数内部声明的变量只能在函数内部访问:
function testScope() {
const localVar = '我是局部变量';
console.log(localVar); // 可以访问
}
testScope();
console.log(localVar); // ReferenceError: localVar is not defined函数作用域的特点:
function outerFunction() {
const outerVar = '外部变量';
function innerFunction() {
const innerVar = '内部变量';
console.log(outerVar); // 可以访问外部变量
console.log(innerVar); // 可以访问内部变量
}
innerFunction();
// console.log(innerVar); // 错误: 无法访问内部函数的变量
}
outerFunction();1.4.3 块级作用域 (ES6)
使用 let 和 const 声明的变量拥有块级作用域:
if (true) {
const blockVar = '块级变量';
let anotherVar = '另一个块级变量';
console.log(blockVar); // 可以访问
}
// console.log(blockVar); // ReferenceError: blockVar is not defined块级作用域的常见场景:
// 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 的区别:
// var 只有函数作用域,没有块级作用域
if (true) {
var x = 10;
}
console.log(x); // 10, 可以访问
// let 和 const 有块级作用域
if (true) {
let y = 20;
}
console.log(y); // ReferenceError: y is not defined1.4.4 作用域链
当访问一个变量时,JavaScript 会沿着作用域链向外查找:
const globalVar = '全局';
function outer() {
const outerVar = '外部';
function inner() {
const innerVar = '内部';
console.log(innerVar); // 输出: 内部
console.log(outerVar); // 输出: 外部
console.log(globalVar); // 输出: 全局
}
inner();
}
outer();作用域链查找规则:
- 先在当前作用域查找
- 如果找不到,向外层作用域查找
- 一直查找到全局作用域
- 如果全局作用域也没有,抛出
ReferenceError
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 变量遮蔽
内层作用域的变量会遮蔽外层同名变量:
const x = '全局 x';
function test() {
const x = '局部 x';
console.log(x); // 输出: 局部 x, 遮蔽了全局 x
}
test();
console.log(x); // 输出: 全局 x1.5 匿名函数
匿名函数是没有函数名的函数,常用于以下场景。
1.5.1 函数表达式中的匿名函数
const greet = function(name) {
return '你好, ' + name + '!';
};
console.log(greet('张三')); // 输出: 你好, 张三!1.5.2 作为回调函数
匿名函数常作为回调函数传递给其他函数:
// 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 事件处理
const button = document.querySelector('button');
button.addEventListener('click', function(event) {
console.log('按钮被点击了');
console.log('事件对象:', event);
});1.5.4 立即执行函数表达式 (IIFE)
定义后立即执行的匿名函数:
// 基本语法
(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 引入的箭头函数提供了更简洁的匿名函数语法:
// 基本箭头函数
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)); // 81.5.6 匿名函数的注意事项
匿名函数在栈追踪中的问题
// 命名函数 - 调试友好
function namedFunction() {
throw new Error('出错');
}
// 匿名函数 - 调试困难
setTimeout(function() {
throw new Error('出错');
}, 100);解决方法: 使用命名函数表达式:
// 给匿名函数命名
setTimeout(function errorHandler() {
throw new Error('出错');
}, 100);this 指向问题
箭头函数与普通匿名函数的 this 指向不同:
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 匿名函数的实际应用
高阶函数中的使用
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]函数工厂
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模块模式
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()); // 22. 逻辑中断
逻辑中断是指在使用逻辑运算符(&& 和 ||)时,根据第一个操作数的真假值,决定是否执行第二个操作数的特性。
2.1 逻辑中断的基本概念
JavaScript 中的逻辑运算符具有"短路求值"(Short-circuit evaluation)的特性:
- 逻辑与 (
&&): 如果第一个操作数为假值,则返回第一个操作数,不再执行第二个操作数 - 逻辑或 (
||): 如果第一个操作数为真值,则返回第一个操作数,不再执行第二个操作数
2.2 逻辑与 && 的中断
基本规则: 左边为假,则中断,返回左边值;左边为真,则继续执行右边,返回右边值
// 情况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'逻辑中断的实际应用:
// 应用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 逻辑或 || 的中断
基本规则: 左边为真,则中断,返回左边值;左边为假,则继续执行右边,返回右边值
// 情况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逻辑中断的实际应用:
// 应用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)); // 182.4 逻辑中断的综合应用
// 综合应用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 逻辑中断的注意事项
// 注意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); // true3. 布尔类型转换
JavaScript 中所有值都可以转换为布尔类型,这决定了条件语句的执行结果。
3.1 布尔类型转换的方法
方法一: Boolean() 函数
const bool1 = Boolean(0);
console.log(bool1); // false
const bool2 = Boolean('hello');
console.log(bool2); // true方法二: 双重否定 !!
const bool1 = !!0;
console.log(bool1); // false
const bool2 = !!'hello';
console.log(bool2); // true方法三: 使用 if 语句隐式转换
if (0) {
console.log('不会执行');
}
if ('hello') {
console.log('会执行'); // 输出: 会执行
}3.2 假值 (Falsy Values)
以下 6 个值转换为布尔类型时为 false:
| 值 | 类型 | 示例 |
|---|---|---|
false | Boolean | Boolean(false) → false |
0 | Number | Boolean(0) → false |
-0 | Number | Boolean(-0) → false |
0n | BigInt | Boolean(0n) → false |
'' 或 "" | String | Boolean('') → false |
null | Null | Boolean(null) → false |
undefined | Undefined | Boolean(undefined) → false |
NaN | Number | Boolean(NaN) → false |
示例:
// 数字假值
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:
// 数字真值
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' 是真值
// 数字 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: 空数组和空对象是真值
// 空数组是真值
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'
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 是假值
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: 验证表单输入
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: 提供默认值
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: 过滤数组中的假值
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: 条件渲染
// 在前端框架中的应用 (伪代码)
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: 简洁的条件判断
// 传统写法
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)); // false3.6 布尔转换总结表
| 值 | Boolean() | !! | 条件语句中 |
|---|---|---|---|
false | false | false | 不执行 |
0, -0, 0n, NaN | false | false | 不执行 |
'', "" | false | false | 不执行 |
null, undefined | false | false | 不执行 |
true | true | true | 执行 |
| 非零数字 | true | true | 执行 |
| 非空字符串 | true | true | 执行 |
对象(包括 {}, []) | true | true | 执行 |
| 函数 | true | true | 执行 |
