14 篇把变量、类型、运算符、流程控制讲完了——代码会写,但都是"一次性"的:同样的逻辑要用第二次就得复制一遍。这一篇补上两个"打包"的工具:函数把代码打包成可复用的块,自定义对象把相关的数据和方法打包成一个整体。最后再补上对象在网络上的形态 JSON、以及数组常用方法——它们是下一步 DOM 操作和后面 Vue、Ajax 的地基。
函数:把代码块打包复用
介绍:函数(
function)是被设计用来执行特定任务的代码块,方便程序的封装复用。
定义语法:
function functionName(参数1, 参数2 ...){
//要执行的代码
}调用语法:函数名称(实际参数列表)
课程代码 04. JS-函数.html 的具名函数:
//1. 函数定义及调用 - 具名函数
function add(a, b){
return a + b; // return 返回结果
}
let result = add(10,20); // 调用函数,实参是 10 和 20
console.log(result); // 30写代码时把定义放在调用前面(课程代码就是这么排的),读起来最清楚。
IMPORTANT
由于 JS 是弱类型语言,形参、返回值都不需要指定类型——function add(a, b) 里不写 a、b 是什么类型,也不写返回什么类型(Java 要写成 int add(int a, int b))。另外:调用函数时,实参个数与形参个数可以不一致,但是建议一致。
实参个数不一致会发生什么(课程代码注释里没展开,这里补上):
function add(a, b){
return a + b;
}
console.log(add(10)); // NaN —— b 是 undefined,10 + undefined 得不到数字
console.log(add(10, 20, 30)); // 30 —— 多出来的 30 被忽略WARNING
"实参个数可以不一致"是 JS 的灵活之处,也是 bug 的高发区:少传一个参数,函数照样执行,只是默默算出一个 NaN(或 undefined),不会报错。写代码时老老实实保证个数一致,让问题暴露在调用处。
匿名函数:函数表达式与箭头函数
匿名函数是指一种没有名称的函数,可以通过两种方式定义:函数表达式 和 箭头函数。
//2.1 函数表达式:把一个没有名字的函数赋给变量
let add = function(a, b){
return a + b;
}
//2.2 箭头函数:=> 代替 function
let add = (a, b) => {
return a + b;
}
// 匿名函数定义后,可以通过变量名直接调用
let result = add(100, 200);
console.log(result); // 300(上面两段来自课程 04. JS-函数.html:函数表达式演示的是 add(100,200),箭头函数演示的是 add(1000,2000);两者选一个用即可。)
TIP
为什么叫"匿名"?function 后面没有名字,函数本身被赋给了变量 add,所以调用时要写 add(...)(用变量名,不是原来的 functionName)。两种写法的区别:函数表达式内还是熟悉的 function;箭头函数用 => 把参数和函数体连起来,写起来更短——这也是后面写事件监听时最常用的写法(如 addEventListener('click', () => { ... }))。
箭头函数还有一个"极简形态"(了解):只有一个表达式时,{} 和 return 都能省掉——
// 完整写法
let add = (a, b) => {
return a + b;
}
// 简写:只有一个表达式,省略 {} 和 return(结果会被自动返回)
let add2 = (a, b) => a + b;自定义对象:把"一个东西"打包
定义格式:
let 对象名 = { 属性名1: 属性值1, 属性名2: 属性值2, 属性名3: 属性值3, 方法名: function (形参列表) {} }
课程代码 05. JS-自定义对象.html 用一个大括号描述了"一个人":
//1. 自定义对象
let user = {
name: 'Tom',
age: 18,
gender: '男',
sing: function(){
alert(this.name + '悠悠的唱着最炫的民族风~')
}
}对象的方法还有一种简写——把 : function 去掉,直接写 方法名(){}:
let user = {
name: 'Tom',
age: 18,
gender: '男',
sing(){
alert(this.name + '悠悠的唱着最炫的民族风~')
}
}调用格式(PPT 的两行总结):
// 对象名.属性名;
// 对象名.方法名();
alert(user.name); // Tom
user.sing(); // 弹出:Tom悠悠的唱着最炫的民族风~前面用过的 console.log(user.name)、user.sing() 都是这个套路:点号(.)就是"取这个对象身上的东西"。
方法里的 this
方法里要拿"当前对象自己"的属性,用 this——上面 sing() 里的 this.name 就是 user.name。
WARNING
课程代码里专门演示了一种错误写法:定义对象中的方法时,尽量不要使用箭头函数(this)。
let user = {
name: 'Tom',
age: 18,
gender: '男',
sing: () => { //注意: 在箭头函数中, this并不指向当前对象 - 指向的是当前对象的父级 【不推荐】
alert(this + ':悠悠的唱着最炫的民族风~')
}
}箭头函数的 this 不指向"调用它的对象",而是指向外层(父级)作用域——在我们这种顶层写法里就是 window(浏览器窗口对象),所以弹出来的是 [object Window] 而不是 Tom。给对象写方法,用 方法名(){} 或 方法名: function(){},不写箭头函数。
JSON:对象在网络上的形态
概念:JavaScript Object Notation,JavaScript 对象标记法(JS 对象标记法书写的文本)。
由于其语法简单,层次结构鲜明,现多用于作为数据载体,在网络中进行数据传输。
把"JS 对象"和"JSON 文本"摆在一起就能看出区别:
// JS 对象:属性名可以不加引号,字符串可以用单引号,还能带方法
let person = {
name: 'Tom',
age: 20,
gender: '男'
}{
"name": "Tom",
"age": 20,
"gender": "男"
}JSON 格式特点:json 格式的文本所有的 key 必须使用双引号引起来("name" 不能写成 name 或 'name')。
对象和 JSON 之间的转换靠两个方法(PPT 的两问两答):
| 方法 | 作用 |
|---|---|
JSON.parse(..) | 将 json 字符串转为 js 对象 |
JSON.stringify(..) | 将 js 对象转为 json 字符串 |
课程代码 05. JS-自定义对象.html 的最后一段:
//3. JSON - JS对象标记法
let person = {
name: 'itcast',
age: 18,
gender: '男'
}
alert(JSON.stringify(person)); //js对象 --> json字符串
let personJson = '{"name": "heima", "age": 18}';
alert(JSON.parse(personJson).name); // heima —— 先转成对象,再取 name浏览器里弹出来的第一句是:
{"name":"itcast","age":18,"gender":"男"}为什么要有这一步? PPT 用一张"请求/响应"的图说明了数据的流向:
| 步骤 | 数据形态 |
|---|---|
| 浏览器里的 JS 对象 | { name: "Tom", age: 20, gender: "男" } |
发请求前用 JSON.stringify(..) 转成文本 | {"name":"Tom","age":20,"gender":"男"} |
| 网络传输(请求 / 响应) | JSON 文本(纯字符串,任何语言都能解析) |
收到响应用 JSON.parse(..) 转回对象 | 又是 JS 对象,可以 对象.属性名 取值 |
TIP
记住这个方向感:stringify 是"对象 → 文本"(准备发出去),parse 是"文本 → 对象"(收进来用)。后面学 Ajax / Vue 时,从后端拿到的响应就是一个 JSON 文本,第一步永远是 JSON.parse(或让 Axios 自动转)。
补充:数组常用方法
数组是 JS 里最常用的"容器"(14 篇的对照表里提过:Java 的数组长度固定,JS 的数组长度可变)。用中括号定义、元素用逗号隔开:
let arr = [10, 20, 30, 'JS', true]; // 元素类型可以混搭(弱类型)
console.log(arr.length); // 5 —— 长度
console.log(arr[0]); // 10 —— 索引从 0 开始
console.log(arr[4]); // true| 方法 | 作用 |
|---|---|
push(元素) | 在末尾添加一个或多个元素,返回新长度 |
pop() | 删除并返回末尾的元素 |
unshift(元素) | 在开头添加元素 |
shift() | 删除并返回开头的元素 |
splice(开始索引, 删除个数, 新增元素…) | 从中间删除 / 插入元素(会改变原数组) |
slice(开始, 结束) | 截取一段,返回新数组(不改原数组) |
join(分隔符) | 把数组元素拼成字符串 |
concat(数组) | 拼接数组,返回新数组 |
indexOf(元素) | 返回元素第一次出现的索引,找不到返回 -1 |
includes(元素) | 判断是否包含某元素,返回 true / false |
forEach(函数) | 遍历每个元素(类似 for 循环) |
map(函数) | 遍历并加工每个元素,返回新数组 |
filter(函数) | 筛选符合条件的元素,返回新数组 |
let arr = [10, 20, 30];
arr.push(40); // 末尾加:arr 变成 [10, 20, 30, 40]
arr.pop(); // 末尾删:arr 又变回 [10, 20, 30]
arr.splice(1, 0, 15); // 在索引 1 处插入 15:arr 变成 [10, 15, 20, 30]
console.log(arr.join('-')); // 10-15-20-30(拼成字符串)
console.log(arr.includes(20)); // true
// 遍历:箭头函数在这里最常见
arr.forEach(item => {
console.log(item);
});数组里放对象是最常用的组合("多条数据"就是这样存的,后面的员工列表案例全靠它):
let users = [
{ name: 'Tom', age: 18 },
{ name: 'Jerry', age: 20 }
];
console.log(users.length); // 2
console.log(users[0].name); // Tom —— 先按索引取出对象,再 .属性名取属性
for (let i = 0; i < users.length; i++) {
console.log(users[i].name); // 依次打印 Tom、Jerry
}IMPORTANT
遍历数组最标准、最不容易出错的写法是 for (let i = 0; i < arr.length; i++)——课程后面的员工列表案例里遍历数据行用的就是它(for (let i = 0; i < trList.length; i++))。forEach()、map()、filter() 属于进阶写法,认识即可,后面写 Vue 时会大量出现。
小结
| 问题 | 答案 |
|---|---|
| 函数是什么? | 被设计用来执行特定任务的代码块,方便程序的封装复用;用 function 定义,用 函数名称(实际参数) 调用 |
| 函数要写类型吗? | 不用——JS 是弱类型,形参、返回值都不需要指定类型;实参个数与形参个数可以不一致(建议一致) |
| 匿名函数怎么写? | 函数表达式 let add = function(a,b){...}、箭头函数 let add = (a,b) => {...};定义后通过变量名调用 |
| 自定义对象的格式? | let 对象 = {属性名: 属性值, 方法名: function(){}},方法可简写成 方法名(){} |
| 怎么访问对象成员? | 对象名.属性名、对象名.方法名();方法内用 this.属性名 取自己的属性 |
| 对象方法能用箭头函数吗? | 不要——箭头函数的 this 指向外层(window),拿不到对象自己的属性 |
| JSON 是什么? | JavaScript Object Notation(JS 对象标记法),是文本,用作数据载体在网络中传输;key 必须用双引号 |
| 两个 JSON 方法? | JSON.stringify(对象) 对象 → json 字符串;JSON.parse(字符串) json 字符串 → 对象 |
| 数组怎么定义、怎么取元素? | let arr = [10, 20, 30];arr[0] 按索引取(从 0 开始)、arr.length 拿长度 |
| 数组常用方法? | push() 末尾加、pop() 末尾删、splice() 中间删/插、join() 拼字符串、forEach() 遍历 |
相关
练习题
一、知识回顾(读完直接做下面的实践题)
- 函数(function) 是被设计用来执行特定任务的代码块,方便程序的封装复用;定义用
function关键字,调用写函数名称(实际参数列表) - 因为 JS 是弱类型语言,定义函数时形参、返回值都不需要指定类型;调用时实参个数与形参个数可以不一致(但建议一致)——少传参数会默默算出
NaN,多传的会被忽略,都不报错 - 匿名函数(没有名称的函数)有两种定义方式:函数表达式
let add = function(a,b){...}和 箭头函数let add = (a,b) => {...};定义后通过变量名调用(add(10,20)) - 箭头函数只有一个表达式时,可以省略
{}和return(如let add = (a,b) => a + b;);后面写事件监听(如addEventListener('click', () => {...}))最常用它 - 自定义对象格式:
let 对象名 = { 属性名1: 属性值1, 属性名2: 属性值2, 方法名: function (形参列表) {} };方法还能简写成方法名(){} - 调用格式:
对象名.属性名、对象名.方法名();方法内部用this取当前对象自己的属性(this.name) - 对象方法里不要用箭头函数:箭头函数的
this不指向当前对象,而是指向外层(父级)——顶层写法里就是window,拿不到属性 - JSON 全称 JavaScript Object Notation(JavaScript 对象标记法),是文本,语法简单、层次结构鲜明,多用于作为数据载体在网络中传输;格式特点是所有的 key 必须用双引号引起来(不能带方法、不能用单引号)
JSON.stringify(js对象)把 js 对象转为 json 字符串;JSON.parse(json字符串)把 json 字符串转为 js 对象- 数组用
[ ]定义、索引从0开始、数组.length取长度;常用方法:push()末尾加、pop()末尾删、splice()中间删/插、join()拼成字符串、includes()判断包含、forEach()遍历;数组里放对象就是"多条数据"的存法
二、裸写题
- 提示(先自己想,实在想不出再点开)
一级 · 思路:函数 = 名字 + 参数 + 返回值;"返回"是把结果交回给调用方,还需要一个地方把结果打印出来 二级 · 方法:定义用
function 函数名(参数){ return ...; };调用写函数名(10, 20);打印用console.log()三级 · 骨架:function add(____, ____){ return ____ + ____; }/let result = add(10, 20);/console.log(____);- 参考答案(做完再点开)
javascript// 定义函数:求两个数的和 function add(a, b){ return a + b; } // 调用函数并打印结果 let result = add(10, 20); console.log(result); // 30对照课程
04. JS-函数.html的第一段。Java 版本要写成int add(int a, int b){ return a + b; }——多出的两个int就是因为 JS 是弱类型,形参和返回值都不用写类型。- 用箭头函数(匿名函数的一种)实现同样的加法,把
100和200传进去、打印结果 - 再调用一次这个函数,只传一个参数,看看打印出来是什么——解释一下为什么不是报错 (练习文件
test_15_箭头函数.html里已经给了写作区。)
- 提示(先自己想,实在想不出再点开)
一级 · 思路:匿名函数没有名字,要把它"存"在变量里才能调用;箭头函数用
=>连接参数和函数体 二级 · 方法:let 变量名 = (参数) => { return ...; };调用用变量名;打印用console.log()三级 · 骨架:let add = (a, b) => { return ____ + ____; }/let result = ____(100, 200);/ 再调用一次add(____)观察结果- 参考答案(做完再点开)
javascript// 箭头函数:匿名函数赋给变量 add let add = (a, b) => { return a + b; } let result = add(100, 200); console.log(result); // 300 // 少传一个参数:b 是 undefined console.log(add(100)); // NaN —— 100 + undefined 得不到数字,但不报错对照课程
04. JS-函数.html的第 2 部分(函数表达式与箭头函数二选一)。第 2 步说明:JS 里实参个数与形参个数可以不一致,缺的形参是undefined,参与算术运算就变成NaN(Not a Number)——这正是弱类型语言"灵活但容易埋 bug"的地方。- 用箭头函数(匿名函数的一种)实现同样的加法,把
- 提示(先自己想,实在想不出再点开)
一级 · 思路:对象用一对大括号装"属性名: 属性值";方法就是"值是函数的属性";取值用点号 二级 · 方法:
let user = { name: 'Tom', age: 18, gender: '男', intro(){ ... } };取值user.name、调用user.intro();方法里用this.name拿自己的名字;打印用console.log()三级 · 骨架:let emp = { name: '____', age: ____, gender: '____', intro(){ console.log('我是' + ____.name); } }/console.log(emp.____);/emp.____();- 参考答案(做完再点开)
javascript// 定义员工对象 let emp = { name: 'Tom', age: 18, gender: '男', // 方法:打印一句自我介绍 intro(){ console.log('我是' + this.name + ',今年' + this.age + '岁'); } } // 访问属性 console.log(emp.name); // Tom console.log(emp.age); // 18 // 调用方法 emp.intro(); // 我是Tom,今年18岁对照课程
05. JS-自定义对象.html的第一段(课程里方法名是sing,用alert弹出this.name + '悠悠的唱着最炫的民族风~')。注意:方法不要写成箭头函数——箭头函数的this指向 window,this.name就取不到 Tom 了。- 提示(先自己想,实在想不出再点开)
一级 · 思路:函数里做"多重条件判断",每个分支把等级返回出去(不是打印);测试时循环几个分数调用它 二级 · 方法:
function 函数名(分数){ if (… >= 90) { return '优秀'; } else if (…) { … } };打印用console.log()三级 · 骨架:function level(score){ if (score >= 90) { return '____'; } else if (score >= 80) { return '良好'; } else if (score >= ____) { return '及格'; } else { return '不及格'; } }/console.log(level(95));- 参考答案(做完再点开)
javascript// 判断成绩等级 function level(score){ if (score >= 90) { return '优秀'; } else if (score >= 80) { return '良好'; } else if (score >= 60) { return '及格'; } else { return '不及格'; } } // 测试四个分数 console.log(level(95)); // 优秀 console.log(level(85)); // 良好 console.log(level(72)); // 及格 console.log(level(50)); // 不及格三个检查点:① 判断顺序必须从高到低——
score >= 60若写在最前面,95 分也会被判成"及格";② 每个分支用return把结果交回去,函数里不打印;③return执行后函数立刻结束,后面的else if不会再判断。
三、综合题
- 用对象描述 3 名员工,每人四个属性:姓名、年龄、性别、职位(自己编数据)
- 把这 3 个对象放进一个数组
- 写一个函数
describe(emp):接收一个员工对象,返回一句话(如Tom,18岁,男,讲师)——用模板字符串拼 - 遍历这个数组,对每个员工调用
describe,把结果打印出来 - 打印这个数组的长度
- 最后把整个数组转成 JSON 字符串打印出来(模拟发给服务器的数据),再把这段 JSON 字符串转回对象,取出第一个员工的姓名打印 (练习文件
test_15_综合_员工列表.html里已经给了写作区。)
- 提示(先自己想,实在想不出再点开)
一级 · 思路:第 1~2 步是"对象装数据、数组装对象";第 3~4 步是"函数加工 + 循环遍历";第 6 步是对象与 JSON 文本之间的两个互转方法 二级 · 方法:数组
let emps = [ {…}, {…} ];函数function describe(emp){ return…; };遍历for (let i = 0; i < emps.length; i++);长度emps.length;互转JSON.stringify(emps)、JSON.parse(字符串)三级 · 骨架:let emps = [ { name: '____', age: 18, gender: '男', position: '____' }, … ];/function describe(emp){ return${emp.name},${emp.____}岁,…; }/for (let i = 0; i < emps.____; i++) { console.log(describe(____[i])); }/let json = JSON.stringify(____);/let obj = JSON.parse(____); console.log(obj[0].____);- 参考答案(做完再点开)
javascript//1. 用对象描述员工,放进数组(多条数据) let emps = [ { name: 'Tom', age: 18, gender: '男', position: '讲师' }, { name: 'Jerry', age: 20, gender: '女', position: '班主任' }, { name: 'Lucy', age: 22, gender: '女', position: '学工主管' } ]; //2. 写一个函数:接收员工对象,返回一句描述 function describe(emp){ return `${emp.name},${emp.age}岁,${emp.gender},${emp.position}`; } //3. 遍历数组,逐个打印描述 for (let i = 0; i < emps.length; i++) { console.log(describe(emps[i])); } //4. 数组长度 console.log(emps.length); // 3 //5. 对象数组 → JSON 字符串(准备发给服务器) let json = JSON.stringify(emps); console.log(json); // [{"name":"Tom","age":18,...}, ...] //6. JSON 字符串 → 对象,再取值 let obj = JSON.parse(json); console.log(obj[0].name); // Tom三个检查点:①
describe(emp)里用emp.name取的是传进来的那个对象,不能写成固定的名字;② 模板字符串的引号是反引号,${}里放的是表达式(emp.age);③ 第 5 步打印出的 JSON 里所有 key 都是双引号的文本——它就是"网络传输时数据的样子",第 6 步的JSON.parse是它的逆向操作。
