目录
- 引言
- 一、为什么要定义变量
- 二、定义变量的一些技巧
- 1. 解构赋值
- 1.1 Object解构赋值
- 1.2 Array解构赋值
- 1.3 总结规律
- 2. 字符串拼接
- 三、变量作用域
- 四、总结
引言
本系列教程旨在帮助一些零基础的玩家快速上手前端开发。基于我自学的经验会删减部分使用频率不高的内容,并不代表这部分内容不重要,只是对于初学者来说没必要一开始就学的面面俱到。我希望可以先通过主干内容带大家入门前端,细节技巧性内容,可以在后续的开发工作中自行发现并掌握。
通过【前端开发入门】JavaScript快速入门–基本认识 这篇文章的讲解,我希望你已经掌握了基础的js数据类型知识以及简易调试代码的技能。
本篇主要介绍js变量的意义、简单技巧以及作用域概念。
一、为什么要定义变量
解释为什么要定义变量这件事,得先从前端开发的数据交换模式说起。除了极个别纯展示类网站,大多数网站都会动态产生一些数据并更新在页面上。
数据来源通常是:
- 网络请求服务器后台得来的数据。
- 用户输入产生的数据
- 代码运行逻辑中产生的数据。
数据的类型通常是:JSON、String、Number、Boolean、Object、Array、Undefined
以网络请求获取数据为例:后端因为设计逻辑的需求或者后端数据完整性规范需求,通常在给到前端数据时都会有多余的内容。前端需要通过一系列转换最终形成可用数据,呈现在页面上。在日后学习到前端框架例如vue、react等,前端所做的大部分工作都是操作数据,数据会自动和页面dom做绑定,数据变化则页面变化,无需关心如何由数据变换到页面的dom结构。
既然要经过一系列操作转换数据,那么就需要变量将数据截获,从而在内存中进行运算得出想要结果。
我想到了一个比较有趣的比喻:数据就是好吃的食物,双手就是变量,你本身是前端页面。你不可能手背后伸着脑袋去吃到食物,而是需要双手先接到食物,把食物掰成一口能吃下的大小,然后吃掉。
二、定义变量的一些技巧
在讲解定义变量的技巧前,先明确一个核心点
我们在编写js代码时一定要先定义后使用
错误示例:
console.log(a); // 报错: Cannot access 'a' before initialization
let a = 0;
let b = 1;
add(a, b); // 报错: Cannot access 'add' before initialization
const add = (a, b) => {return a + b;
};
修改示例:
let a = 0;
console.log(a); // 输出:0
let b = 1;
const add = (a, b) => {return a + b;
};
console.log(add(a, b));// 输出:1
1. 解构赋值
1.1 Object解构赋值
设定一个场景1:发起了一个网络请求,获取了一组JSON格式(也是一种Object)的数据,你需要取出一部分作为你定义函数的传参,然后将函数输出结果渲染到页面上。
现阶段还没介绍网络请求部分(我希望在前端框架处再说),所以我们伪造一个已经请求到的数据,
{name: "qbbmnn",redBall: 10,greenBall: 20,blueBall: 30,
}
我需要在页面中展示我的名字以及我手中 redBall
和 greenBall
总共有多少个
// 解构赋值只取name、redBall、greenBall
const { name, redBall, greenBall} = {name: "qbbmnn",redBall: 10,greenBall: 20,blueBall: 30,
};
// 定义处理数据函数
const handleData = (name, redBallNum, greenBallNum) => {return {name: name,allNum: redBallNum + greenBallNum,};
};
// 调用处理数据函数,传入待处理的三个变量,将函数return结果赋值给变量result
const result = handleData(name, redBall, greenBall)
// console控制台输出结果result
console.log(result );// 输出:{name: 'qbbmnn', allNum: 30}
解释下以上代码:
- 通过解构赋值的方法定义了三个变量
name
、redBall
、greenBall
,分别对应网络请求获取到数据部分属性的名称,定义的变量值即为对应的属性值。因为存在确定的属性名对应关系,所以在定义变量名时没有顺序关系,可以随意排列。例如:const { redBall,name ,greenBall } - 定义了一个匿名箭头函数,并将函数赋值给变量handleData 。函数具有三个
形参
,注意这里形参
和调用函数时传入实参
的区别。形参
可以是任何值,因为函数可以调用多次传入不同值。实参
是当前这次调用函数所需要的传值,每次调用函数所用的实参
都有可能不同。实参
数量可以与形参
数量相同,也可以更少,两者从第一位开始匹配依次赋值,当实参
数量少时,未被匹配的形参
自动赋值为undefined
。 - 使用result变量承接函数处理完成后的返回值。
- 输出result内容。
1.2 Array解构赋值
设定一个场景2:数据和场景1一样,现在需要将redBall刷成greenBall,greenBall刷成blueBall,blueBall刷成redBall,三者互换身份,更新数据。
// 对Object解构赋值定义redBall, greenBall, blueBall
let { redBall, greenBall, blueBall } = {name: "qbbmnn",redBall: 10,greenBall: 20,blueBall: 30,
};
// 对Array解构赋值,调换顺序
[redBall, greenBall, blueBall] = [greenBall, blueBall, redBall];
// 输出结果
console.log(redBall); // 输出:20
console.log(greenBall); // 输出:30
console.log(blueBall); // 输出:10
解释下以上代码:
- 通过对Object数据解构赋值定义了三个变量
redBall
、greenBall
、blueBall
- 将三个变量整合为数组,重新排序,然后通过数组的解构赋值将变量重新赋值。
- 注意这里将定义变量时的标识符改为
let
,因为这些变量在后续操作中需要改变。 - 输出结果。
1.3 总结规律
从以上两个例子中我们可以得到解构赋值的一些规律
- 针对Object类型的解构赋值。因为存在键值对的特性,每个属性都有特定的名称,我们无需关心赋值的顺序,只需要名字匹配即可。我们也不关心赋值数量问题,只取自己需要的属性。
- 针对Array类型的解构赋值。此时为数组排列,只存在序号没有特定的属性名,所以赋值时需要按照顺序赋值,按照等号右侧的顺序依次将值赋予左侧变量。如果等号右侧值数量不够,则赋予左侧多余变量值为
undefined
- 解构赋值意思就是等号左右两侧拥有相同的数据结构(Object或者Array),将右侧数据对应的赋值给左侧变量。
2. 字符串拼接
设定一个场景3:仍然使用场景1的数据,这次我们要展示一整段字符串,包含所有的信息。
let { name, redBall, greenBall, blueBall } = {name: "qbbmnn",redBall: 10,greenBall: 20,blueBall: 30,
};// 字符串之间通过 + 拼接内容
const message ="我是:" +name +" 我手里有:" +redBall +"颗红色球," +greenBall +"颗绿色球," +blueBall +"颗蓝色球。";
console.log(message); // 输出:我是:qbbmnn 我手里有:10颗红色球,20颗绿色球,30颗蓝色球。// 模板字符串,使用 ${变量名} 将变量插入字符串中的任意位置,模板字符串用 `` 符号(英文键盘tab上边那个键)包裹起来。
const message2 = `我是:${name } 我手里有:${redBall }颗红色球,${greenBall }颗绿色球,${blueBall }颗蓝色球。`console.log(message2); // 输出:我是:qbbmnn 我手里有:10颗红色球,20颗绿色球,30颗蓝色球。
目前将变量与字面量字符串拼接的方式,以上两种方式都可以,但是建议使用模板字符串。
三、变量作用域
变量作用域顾名思义变量起作用的区域
按照定义变量的位置可以将变量作用域划分为:全局作用域、函数作用域、块级作用域。
在我眼里变量的作用域只有两种,即全局作用域和块级作用域(不是很严谨但是很好记),判断条件为变量是否定义在 {}
符号内部。外部为全局作用域,内部为块级作用域。
举几个例子:
// 1. globalVar 定义在 {} 外部
const globalVar = "I am globalVar";// 2. 函数的 {} 从某种意义上也可以算作块级作用域的标志
function test() {const functionVar = "I am functionVar"console.log(globalVar); // 输出: I am globalconsole.log(functionVar); // 输出: I am functionVar
}
test();
console.log(globalVar); // 输出: "I am global"
console.log(functionVar); // 报错:Uncaught ReferenceError ReferenceError: functionVar is not defined// 3. 循环体的 {} 当然也被认为是块级作用域的标志
for (let i = 0; i < 10; i++) {console.log(globalVar); // 输出 * 10: "I am global"console.log(functionVar); // 报错:Uncaught ReferenceError ReferenceError: functionVar is not defined
}// 4. 极端一些你直接使用 {} 开辟一个块级作用域也是可以的。
{const blockVar = "I am blockVar"console.log(globalVar); // 输出: "I am global"console.log(blockVar); // 输出: "I am blockVar"console.log(functionVar); // 报错:Uncaught ReferenceError ReferenceError: functionVar is not defined
}// 5. 块级作用域的嵌套使用
const globalVar1 = "I am globalVar"
{const globalVar1 = "I am globalVar reset" const blockVar = "I am blockVar"const blockFunc = () => {const globalVar1 = "I am globalVar reset once again" const blockFuncVar = "I am blockFuncVar "console.log(globalVar1); // 输出: "I am globalVar reset once again" console.log(blockVar); // 输出:"I am blockVar"console.log(blockFuncVar); // 输出: "I am blockFuncVar "}blockFunc();console.log(globalVar1); // 输出: "I am globalVar reset" console.log(blockVar); // 输出: "I am blockVar"console.log(blockFuncVar); // 报错:Uncaught ReferenceError ReferenceError: functionVar is not defined
}
由以上的例子总结出一些规律:
- 由1、2、3、4可知,定义在全局作用域的变量在任何地方都可以访问到该变量。但是定义在块级作用域内的变量,外层的作用域(此时为全局作用域)无法访问到。
- 由4、5可知,同一作用域内无法定义相同的变量,但是通过块级作用域的
{}
符号划分出两片作用域,那就可以分别定义相同名字的变量,例如:blockVar
。 - 由5可知,变量作用域是具有嵌套效果的。内层可以访问外层变量,外层不能访问内层变量。如果多层级共同定义了同一个变量,那么采取就近原则,读取最靠近当前的作用域内定义的那个变量版本,例如:
globalVar1
。
四、总结
以上即为js定义变量的常见内容,包含Object、Array的解构赋值以及应用场景;定义变量的作用域;虽说是基础常识,但是还是需要在实践中熟悉并掌握,使用它们成为一种习惯。
再接再厉~