JavaScript基础知识4(数组、函数、参数、作用域、具名和匿名函数、逻辑运算符短路、转化布尔类型)
- 数组
- 数组是什么?
- 数组的基本使用
- 定义数组和数组单元
- 访问数组和数组索引
- 数据单元值类型
- 数组长度属性
- 操作数组
- 函数
- 声明和调用
- 声明(定义)
- 调用
- 参数
- 形参和实参
- 形参的默认值
- 返回值
- 如何返回多个值
- 作用域
- 1. 全局作用域
- 2. 函数作用域
- 3. 块级作用域
- 示例:全局作用域、函数作用域和块级作用域
- 变量提升(Hoisting)
- 块级作用域和函数作用域对比
- 总结
- 变量的访问原则
- 具名函数和匿名函数
- 具名函数
- 定义和使用
- 匿名函数
- 定义和使用
- 匿名函数和具名函数的比较
- 示例对比
- 使用场景
- 总结
- 函数表达式
- 逻辑运算符短路
- 短路特性示例
- `&&`(逻辑与)
- `||`(逻辑或)
- 实际应用
- 默认值
- 条件执行
- 示例代码
- 总结
- 转化为Boolean型
数组
知道什么是数组及其应用的场景,掌握数组声明及访问的语法。
数组是什么?
数组:(Array)是一种可以按顺序保存数据的数据类型
**使用场景:**如果有多个数据可以用数组保存起来,然后放到一个变量中,管理非常方便
数组的基本使用
定义数组和数组单元
<script>// 1. 语法,使用 [] 来定义一个空数组// 定义一个空数组,然后赋值给变量 classes// let classes = [];// 2. 定义非空数组let classes = ['小明', '小刚', '小红', '小丽', '小米']
</script>
通过 []
定义数组,数据中可以存放真正的数据,如小明、小刚、小红等这些都是数组中的数据,我们这些数据称为数组单元,数组单元之间使用英文逗号分隔。
访问数组和数组索引
使用数组存放数据并不是最终目的,关键是能够随时的访问到数组中的数据(单元)。其实 JavaScript 为数组中的每一个数据单元都编了号,通过数据单元在数组中的编号便可以轻松访问到数组中的数据单元了。
我们将数据单元在数组中的编号称为索引值,也有人称其为下标。
索引值实际是按着数据单元在数组中的位置依次排列的,注意是从下标 0
开始的
观察上图可以数据单元【小明】对应的索引值为【0】,数据单元【小红】对应的索引值为【2】
<script>let classes = ['小明', '小刚', '小红', '小丽', '小米']// 1. 访问数组,语法格式为:变量名[索引值]document.write(classes[0]) // 结果为:小明document.write(classes[1]) // 结果为:小刚document.write(classes[4]) // 结果为:小米// 2. 通过索引值还可以为数组单重新赋值document.write(classes[3]) // 结果为:小丽// 重新为索引值为 3 的单元赋值classes[3] = '小小丽'document.wirte(classes[3]); // 结果为: 小小丽
</script>
数据单元值类型
数组做为数据的集合,它的单元值可以是任意数据类型
<script>// 6. 数组单值类型可以是任意数据类型// a) 数组单元值的类型为字符类型let list = ['HTML', 'CSS', 'JavaScript']// b) 数组单元值的类型为数值类型let scores = [78, 84, 70, 62, 75]// c) 混合多种类型let mixin = [true, 1, false, 'hello']
</script>
数组长度属性
重申一次,数组在 JavaScript 中并不是新的数据类型,它属于对象类型。
<script>// 定义一个数组let arr = ['html', 'css', 'javascript']// 数组对应着一个 length 属性,它的含义是获取数组的长度console.log(arr.length) // 3
</script>
操作数组
数组做为对象数据类型,不但有 length
属性可以使用,还提供了许多方法:
1. push 动态向数组的尾部添加一个单元
2. unshit 动态向数组头部添加一个单元
3. pop 删除最后一个单元
4. shift 删除第一个单元
5. splice 动态删除任意单元
使用以上4个方法时,都是直接在原数组上进行操作,即成功调任何一个方法,原数组都跟着发生相应的改变。并且在添加或删除单元时 length
并不会发生错乱。
<script>// 定义一个数组let arr = ['html', 'css', 'javascript']// 1. push 动态向数组的尾部添加一个单元arr.push('Nodejs')console.log(arr)arr.push('Vue')// 2. unshit 动态向数组头部添加一个单元arr.unshift('VS Code')console.log(arr)// 3. splice 动态删除任意单元arr.splice(2, 1) // 从索引值为2的位置开始删除1个单元console.log(arr)// 4. pop 删除最后一个单元arr.pop()console.log(arr)// 5. shift 删除第一个单元arr.shift()console.log(arr)
</script>
函数
理解函数的封装特性,掌握函数的语法规则
声明和调用
函数可以把具有相同或相似逻辑的代码“包裹”起来,通过函数调用执行这些被“包裹”的代码逻辑,这么做的优势是有利于精简代码方便复用。
声明(定义)
声明(定义)一个完整函数包括关键字、函数名、形式参数、函数体、返回值5个部分
在JavaScript中,你可以使用以下两种方式来声明函数:
-
函数声明(Function Declaration):
function functionName(parameters) {// 函数体 }
使用函数声明方式可以在任何地方声明函数,包括在其他函数内部。函数声明会被提升(hoisted),这意味着你可以在函数声明之前调用该函数。
-
函数表达式(Function Expression):
var functionName = function(parameters) {// 函数体 };
使用函数表达式方式将函数赋值给一个变量。函数表达式可以是匿名的,也可以是具名的。函数表达式在代码执行到达时创建,因此你必须在函数表达式之后才能调用该函数。
以下是一个示例,展示了如何使用函数声明和函数表达式来定义函数:
// 函数声明
function greet(name) {console.log('Hello, ' + name + '!');
}greet('Alice'); // 调用函数// 函数表达式(匿名)
var sayHello = function() {console.log('Hello!');
};sayHello(); // 调用函数// 函数表达式(具名)
var multiply = function(a, b) {return a * b;
};var result = multiply(5, 3); // 调用函数并接收返回值
console.log(result); // 输出结果
无论你选择使用函数声明还是函数表达式,都可以根据需要来定义和调用函数。请根据你的具体情况选择适合的方式。
调用
声明(定义)的函数必须调用才会真正被执行,使用 ()
调用函数。
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>JavaScript 基础 - 声明和调用</title>
</head>
<body><script>// 声明(定义)了最简单的函数,既没有形式参数,也没有返回值function sayHi() {console.log('嗨~')}// 函数调用,这些函数体内的代码逻辑会被执行// 函数名()sayHi()// 可以重复被调用,多少次都可以sayHi()</script>
</body>
</html>
注:函数名的命名规则与变量是一致的,并且尽量保证函数名的语义。
小案例: 小星星
<script>// 函数声明function sayHi() {// document.write('hai~')document.write(`*<br>`)document.write(`**<br>`)document.write(`***<br>`)document.write(`****<br>`)document.write(`*****<br>`)document.write(`******<br>`)document.write(`*******<br>`)document.write(`********<br>`)document.write(`*********<br>`)}// 函数调用sayHi()sayHi()sayHi()sayHi()sayHi()</script>
参数
通过向函数传递参数,可以让函数更加灵活多变,参数可以理解成是一个变量。
声明(定义)一个功能为打招呼的函数
- 传入数据列表
- 声明这个函数需要传入几个数据
- 多个数据用逗号隔开
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>JavaScript 基础 - 函数参数</title>
</head>
<body><script>// 声明(定义)一个功能为打招呼的函数// function sayHi() {// console.log('嗨~')// }// 调用函数// sayHi()// 这个函数似乎没有什么价值,除非能够向不同的人打招呼// 这就需要借助参数来实现了function sayHi(name) {// 参数 name 可以被理解成是一个变量console.log(name)console.log('嗨~' + name)}// 调用 sayHi 函数,括号中多了 '小明'// 这时相当于为参数 name 赋值了sayHi('小明')// 结果为 小明// 再次调用 sayHi 函数,括号中多了 '小红'// 这时相当于为参数 name 赋值了sayHi('小红') // 结果为 小红</script>
</body>
</html>
总结:
- 声明(定义)函数时的形参没有数量限制,当有多个形参时使用
,
分隔 - 调用函数传递的实参要与形参的顺序一致
形参和实参
形参:声明函数时写在函数名右边小括号里的叫形参(形式上的参数)
实参:调用函数时写在函数名右边小括号里的叫实参(实际上的参数)
形参可以理解为是在这个函数内声明的变量(比如 num1 = 10)实参可以理解为是给这个变量赋值
开发中尽量保持形参和实参个数一致
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>JavaScript 基础 - 函数参数</title>
</head>
<body><script>// 声明(定义)一个计算任意两数字和的函数// 形参 x 和 y 分别表示任意两个数字,它们是两个变量function count(x, y) {console.log(x + y);}// 调用函数,传入两个具体的数字做为实参// 此时 10 赋值给了形参 x// 此时 5 赋值给了形参 ycount(10, 5); // 结果为 15</script>
</body>
</html>
形参的默认值
用户不输入实参,可以给形参默认值,可以默认为0,这样程序更严谨,可以如下操作:
function getSum(x = 0, y = 0) {
document .write(x + y)
}
getSum() // 结果是0,而不是N
getSum(1, 2) //结果是3
说明:这个默认值只会在缺少实参参数传递时才会被执行,所以有参数会优先执行传递过来的实参,否则默认为undefined
返回值
函数的本质是封装(包裹),函数体内的逻辑执行完毕后,函数外部如何获得函数内部的执行结果呢?要想获得函数内部逻辑的执行结果,需要通过 return
这个关键字,将内部执行结果传递到函数外部,这个被传递到外部的结果就是返回值。
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>JavaScript 基础 - 函数返回值</title>
</head>
<body><script>// 定义求和函数function count(a, b) {let s = a + b// s 即为 a + b 的结果// 通过 return 将 s 传递到外部return s}// 调用函数,如果一个函数有返回值// 那么可将这个返回值赋值给外部的任意变量let total = count(5, 12)</script>
</body>
</html>
总结:
- 在函数体中使用return 关键字能将内部的执行结果交给函数外部使用
- 函数内部只能出现1 次 return,并且 return 下一行代码不会再被执行,所以return 后面的数据不要换行写
- return会立即结束当前函数
- 函数可以没有return,这种情况默认返回值为 undefined
如何返回多个值
在 JavaScript 中,可以使用数组、对象或解构赋值的方式来返回多个值。
方法一:使用数组
function returnMultipleValues() {var value1 = 10;var value2 = 'Hello';var value3 = true;var result = [value1, value2, value3];return result;
}// 调用函数并获取返回的多个值
var values = returnMultipleValues();
var value1 = values[0];
var value2 = values[1];
var value3 = values[2];
在这个例子中,returnMultipleValues
函数返回一个包含三个值的数组 result
。通过调用该函数并将返回值赋给变量 values
,我们可以通过索引来获取每个值。
方法二:使用对象
function returnMultipleValues() {var value1 = 10;var value2 = 'Hello';var value3 = true;var result = {value1: value1,value2: value2,value3: value3};return result;
}// 调用函数并获取返回的多个值
var values = returnMultipleValues();
var value1 = values.value1;
var value2 = values.value2;
var value3 = values.value3;
在这个例子中,returnMultipleValues
函数返回一个包含三个值的对象 result
。通过调用该函数并将返回值赋给变量 values
,我们可以通过属性名来获取每个值。
方法三:使用解构赋值
function returnMultipleValues() {var value1 = 10;var value2 = 'Hello';var value3 = true;return [value1, value2, value3];
}// 调用函数并使用解构赋值获取返回的多个值
var [value1, value2, value3] = returnMultipleValues();
在这个例子中,returnMultipleValues
函数返回一个包含三个值的数组。通过调用该函数并使用解构赋值,我们可以直接将返回值的每个元素分配给对应的变量。
作用域
作用域(Scope)在编程中指的是变量、函数和对象在代码中的可访问性范围。JavaScript 中的作用域主要分为以下几种:
- 全局作用域(Global Scope)
- 函数作用域(Function Scope)
- 块级作用域(Block Scope)
1. 全局作用域
在最外层定义的变量或函数拥有全局作用域,它们可以在代码中的任何地方访问。
var globalVar = "I am global";function test() {console.log(globalVar); // 可以访问全局变量
}test(); // 输出:I am global
console.log(globalVar); // 输出:I am global
2. 函数作用域
在函数内部定义的变量只能在该函数内部访问,它们具有函数作用域。
function test() {var localVar = "I am local";console.log(localVar); // 可以访问局部变量
}test(); // 输出:I am local
console.log(localVar); // 报错:localVar 未定义
3. 块级作用域
使用 let
和 const
关键字在块(如 if
语句、for
循环等)内部定义的变量,具有块级作用域,只能在块内部访问。var
声明的变量不具备块级作用域。
if (true) {let blockVar = "I am block scoped";console.log(blockVar); // 可以访问块级变量
}
console.log(blockVar); // 报错:blockVar 未定义
示例:全局作用域、函数作用域和块级作用域
var globalVar = "global";function functionScope() {var functionVar = "function scoped";if (true) {let blockVar = "block scoped";console.log(globalVar); // 可以访问全局变量console.log(functionVar); // 可以访问函数内变量console.log(blockVar); // 可以访问块级变量}console.log(blockVar); // 报错:blockVar 未定义
}functionScope();console.log(globalVar); // 输出:global
console.log(functionVar); // 报错:functionVar 未定义
console.log(blockVar); // 报错:blockVar 未定义
变量提升(Hoisting)
JavaScript 的变量提升指的是变量声明被提升到其作用域的顶部。在实际执行时,变量声明会被提升到作用域的顶部,但赋值不会被提升。只有 var
声明的变量会被提升,而 let
和 const
声明的变量不会。
console.log(hoistedVar); // 输出:undefined
var hoistedVar = "I am hoisted";function hoistingExample() {console.log(hoistedFunctionVar); // 输出:undefinedvar hoistedFunctionVar = "I am hoisted in function";
}hoistingExample();
块级作用域和函数作用域对比
function blockVsFunctionScope() {if (true) {var functionScoped = "I am function scoped";let blockScoped = "I am block scoped";}console.log(functionScoped); // 可以访问,输出:I am function scopedconsole.log(blockScoped); // 报错:blockScoped 未定义
}blockVsFunctionScope();
总结
- 全局作用域:变量在任何地方都可以访问。
- 函数作用域:变量只能在函数内部访问。
- 块级作用域:使用
let
和const
声明的变量只能在块内部访问。 - 变量提升:
var
声明的变量会被提升,但let
和const
声明的变量不会。
理解作用域有助于编写清晰和错误更少的代码。正确地使用不同作用域的变量声明方式,可以提高代码的可维护性和可读性。
变量的访问原则
- 只要是代码,就至少有一个作用域
- 写在函数内部的局部作用域
- 如果函数中还有函数, 那么在这个作用域中就又可以诞生一个作用域
- 访问原则:在能够访问到的情况下先局部,局部没有在找全局
- 变量访问原则是什么?
➢采取就近原则的方式来查找变量最终的值
具名函数和匿名函数
具名函数
具名函数是指有名字的函数,可以通过其名字在代码的任何地方调用。具名函数通常用于递归和需要重复调用的场景。
定义和使用
function greet() {console.log("Hello, World!");
}greet(); // 调用函数,输出 "Hello, World!"
具名函数也可以用于递归调用:
function factorial(n) {if (n <= 1) {return 1;}return n * factorial(n - 1);
}console.log(factorial(5)); // 输出 120
匿名函数
匿名函数是没有名字的函数,通常用作立即执行函数表达式(IIFE)、回调函数或赋值给变量。匿名函数在定义时不能通过名字调用,但可以通过其赋值的变量或在特定上下文中使用。
定义和使用
- 赋值给变量:
let greet = function() {console.log("Hello, World!");
};greet(); // 调用函数,输出 "Hello, World!"
- 立即执行函数表达式(IIFE):
(function() {console.log("This is an IIFE!");
})(); // 立即执行,输出 "This is an IIFE!"(function(x,y) {console.log(x+y);
})(1,2); //输出3
无需调用,立即执行,其实本质已经调用了
多个立即执行函数之间用分号隔开
- 作为回调函数:
setTimeout(function() {console.log("This is a callback function!");
}, 1000); // 1秒后输出 "This is a callback function!"
匿名函数和具名函数的比较
- 命名:具名函数有函数名,而匿名函数没有。
- 可读性:具名函数通过名字调用,代码可读性较高;匿名函数通常用于一次性任务或回调。
- 调试:具名函数在调试时有函数名标识,更易于跟踪;匿名函数在调试工具中显示为匿名。
- 递归:具名函数可以自调用,而匿名函数在没有赋值给变量的情况下无法自调用。
示例对比
具名函数示例:
function add(a, b) {return a + b;
}console.log(add(3, 4)); // 输出 7
匿名函数示例:
let add = function(a, b) {return a + b;
};console.log(add(3, 4)); // 输出 7
使用场景
- 具名函数:适用于需要重复调用、递归或提高代码可读性的场景。
- 匿名函数:适用于一次性任务、回调函数或立即执行的场景。
总结
具名函数和匿名函数各有其优势和适用场景。具名函数通过名字调用,适用于递归和提高代码可读性的场景;匿名函数适用于回调、立即执行和赋值给变量的场景。根据具体需求选择合适的函数类型,可以编写出更加简洁和高效的代码。
函数表达式
// 声明
let fn = function() { console.log('函数表达式')
}
// 调用
fn()
逻辑运算符短路
&&
(逻辑与):如果第一个操作数为 false
,则整个表达式的结果一定为 false
,因此不再计算第二个操作数。
||
(逻辑或):如果第一个操作数为true
,则整个表达式的结果一定为true
,因此不再计算第二个操作数。
短路特性示例
&&
(逻辑与)
如果第一个操作数为 false
,表达式短路,直接返回第一个操作数的值,不再计算第二个操作数。
console.log(false && "Hello"); // 输出 false
console.log(0 && "World"); // 输出 0
console.log(null && "JavaScript"); // 输出 null
console.log(undefined && "Programming"); // 输出 undefined
如果第一个操作数为 true
,表达式继续计算并返回第二个操作数的值。
console.log(true && "Hello"); // 输出 "Hello"
console.log(1 && "World"); // 输出 "World"
console.log("Non-empty string" && "JavaScript"); // 输出 "JavaScript"
||
(逻辑或)
如果第一个操作数为 true
,表达式短路,直接返回第一个操作数的值,不再计算第二个操作数。
console.log(true || "Hello"); // 输出 true
console.log(1 || "World"); // 输出 1
console.log("Non-empty string" || "JavaScript"); // 输出 "Non-empty string"
如果第一个操作数为 false
,表达式继续计算并返回第二个操作数的值。
console.log(false || "Hello"); // 输出 "Hello"
console.log(0 || "World"); // 输出 "World"
console.log(null || "JavaScript"); // 输出 "JavaScript"
console.log(undefined || "Programming"); // 输出 "Programming"
实际应用
短路特性在实际编程中非常有用,可以用来简化代码和实现条件逻辑。
默认值
使用 ||
运算符提供默认值:
let name = userInput || "Guest";
如果 userInput
是 null
、undefined
、false
、0
或空字符串,name
将被赋值为 "Guest"
。
条件执行
使用 &&
运算符进行条件执行:
user && user.updateProfile();
只有在 user
存在(非 null
和非 undefined
)的情况下,才会调用 updateProfile
方法。
示例代码
function getUserName(user) {return user && user.name || "Anonymous";
}let user1 = { name: "Alice" };
let user2 = null;console.log(getUserName(user1)); // 输出 "Alice"
console.log(getUserName(user2)); // 输出 "Anonymous"
在这个例子中,如果 user
为 null
或 undefined
,getUserName
函数将返回 "Anonymous"
。如果 user
存在并且有 name
属性,则返回 user.name
。
总结
&&
(逻辑与):如果第一个操作数为false
,则短路并返回第一个操作数,否则返回第二个操作数。||
(逻辑或):如果第一个操作数为true
,则短路并返回第一个操作数,否则返回第二个操作数。
短路特性有助于提高代码执行效率,并且可以用来简化条件逻辑和提供默认值。在编写 JavaScript 代码时,充分利用这些特性可以使代码更加简洁和高效。
转化为Boolean型
真值和假值
在布尔转换中,某些值会被认为是 “真值”(truth),其他值会被认为是 “假值”(false)。
假值(Falsy)列表
以下值在布尔上下文中会被转换为 false:
- false
- 0
- -0
- 0n(BigInt 零值)
- “”(空字符串)
- null
- undefined
- NaN
其他值则都为真值(truth)
隐式转换:
- 有字符串的加法’’ ‘’+1,结果是“1"
- 减法- (像大多数数学运算一样)只能用于数字,它会使空字符串""转换为0
- null 经过数字转换之后会变为0
- undefined 经过数字转换之后会变为NaN