批改状态:合格
老师批语:
1.1 值传递:应用于原始类型:包括字符串、数值、布尔。
let a = 1;let b= a;console.log("a = %d, b = %d", a, b); // a = 1, b = 1
现在我们对a变量进行一个更新:
a = 2;console.log("a = %d, b = %d", a, b); // a = 2, b = 1
从上述案例结果可以看出,把变量a的值更新为2,b还是1,说明更新a的值是不影响b的,因此,原始类型是值传递,仅仅是传递了一个值,并没有把地址传过去。
1.2 引用传递:应用于引用类型,包括对象(object)、数组(array)。
let obj1 = {a: 1, b: 2}console.log(obj1); // a = 1, b = 2let obj2 = obj1;console.log(obj2); // a = 1, b = 2
现在更新obj1的值:obj1.a = 10,那么obj2也会进行同步更新,因为对象时引用传递的。
obj1.a = 10;console.log(obj1); // a = 10, b = 2console.log(obj2); // a = 10, b = 2
有这么一种情况,
const f2 = x = (x.a = 10);let o = {a: 1, b: 2};console.log(o); // a: 1, b: 2// 调用f2函数f2(o);// 打印对象o的值console.log(o); // a: 10, b: 2
此时,对象o看起来是函数中对于o.a的更新生效了,实际上仍是值传递。
对于引用类型,只有全新复制才算是更新,修改属性不算。例如以下案例就不是更新变量:
const obj = {x: 1, y: 2};obj.x = 20;
以下操作才是更新变量:
obj = {};
const f3 = x => (x = {});f(o);console.log(o); // a: 10, b: 2
- 函数中对于对象参数/引用参数的更新并没有影响到入参。
const f1 = x => (x = 10);let m = 5;console.log("m = %d", m);// 调用函数f1f1(m); // m = 5
入参:调用函数时传入的参数,简称“入参”。
函数中对参数的更新,并不会影响到入参。
深拷贝:值传递
浅拷贝:引用传递
ES6中允许按照一定模式从数组和对象中提取值,然后再对变量进行赋值,这样的操作称为解构赋值。即:快速从集合数据(数组/对象)解构独立变量。
在ES6以前,在变量赋值的时候只能为变量指定对应的值:
let a = 1;let b = 2;let c = 3;
从ES6以后,允许用以下方式写:
let [a, b, c] = [1, 2, 3];
以上代码的意思是从数组中取值,按照对应的位置给变量赋值,此时:
a = 1,b = 2,c = 3
从本质上来说,ES6的这种写法类似于“模式匹配”,所以,解构需要等号两边类型一致,左边的变量就会被赋予相对应的值。
使用嵌套数组进行解构
let [a, [[b], c]] = [1, [[2], 3]];
结果:
a = 1,b = 2,c = 3
let [ , , third] = ["one", "two", "three"];
结果:
third的值为:”three”
let [first, , third] = ["one", "two", "three"];
结果:
first = “one”, thrid = “three”
解构赋值允许指定默认值:
let [x, y = '100'] = [10];
结果:
x = 10,y = 100
关于在指定默认值的时候有一个坑,具体请看下面代码:
let [x = 1] = [undefined];console.log(x);
let [x = 1] = [null];console.log(x);
上述两块代码x的值分别是多少呢?我当时没有在代码中测试,猜的结果都是1,但是在控制台试了一下,发现并不都是1,而是1和null,结果如图:


这是为什么呢?通过查阅相关资料发现:
ES6内部使用的是严格相等运算符(===)来判断某个位置是否有值,所以,当一个数组变量中存在不严格等于undefined的值时,默认值是不生效的,怎么理解这句话呢?其实很好理解,我们在控制台测试一下null和undefined是否严格相等即可,例如:
undefined == null // true
undefined === null // false

解构不仅可以用于数组,还可以用于对象。
现有如下对象:
const user = {name: '残破的蛋蛋',age: 18,sex: '男',weight: 500};
现在我想单独把姓名和年龄两个对象属性拿出来并赋值,可以这样写:
({age, name} = {name: '残破的蛋蛋', age: 18});console.log(name, age); // 残破的蛋蛋 18
对象的解构与数组不同的是:数组的元素是按次序排列的,变量的值是由它的位置决定的,但是对象的属性没有固定的次序,但是变量名称必须与属性名称相同才能取到值。
let {gender} = {name: '残破的蛋蛋', age: 18});console.log(gender); // undefined
上述代码中,由于左边的变量在右边没有对应的同名属性,导致无法正确的取到值,所以结果为undefined。
如果变量名与属性名不一致,必须按照如下格式写:
({sex: gender} = {name: '残破的蛋蛋', age: 18, sex: '男'});console.log(gender)
相当于是给sex起了一个别名gender。
let userInfo = {username: '残破的蛋蛋',email: 'admin@php.cn',getUserInfo: () => {return `${this.username} ${this.email}`}};console.log(user.getUserInfo()); // 残破的蛋蛋 admin@php.cnlet {username, email} = userInfo;console.log(username, email); // 残破的蛋蛋 admin@php.cn
当属性与同一个作用域的变量名相同时,可以省略变量名称,使用属性来引用变量值。同时,方法也能简化,省略冒号和function 关键字
userInfo = {// username: username,username,// email: email,email,// 方法也能简化:getInfo() {return `${this.userName} ( ${this.userEmail} )`;},};
字符串也是可以解构赋值的,因为在解构赋值种,字符串被转换成了一个类数组的对象。
const [a, b, c, d, e] = 'Hello';console.log(a, b, c, d, e); // H e l l o
call,apply,bind的作用是改变函数运行时的this指向问题
现有如下函数和对象:
function hello (name) {console.log(this);this.name = name;console.log(this.name);}let obj = {name: 'admin'};
现在,如果我想在对象obj里添加一个hello方法,我需要再写一遍hello吗?这样的话不就太繁琐了吗?但是使用bind,call,apply就可以很好的解决这个问题。
let f = hello.bind(obj, '残破的蛋蛋');console.log(f());
需要注意的是,bind返回的是一个函数的源代码,所以console.log(f)返回的就是hello函数的源代码,故需要加一个圆括号表示调用。
第一个参数是要绑定给this的值,后面传入的是一个参数列表。当第一个参数为null、undefined的时候,默认指向window。
f = hello.call(obj, '残破的蛋蛋2');
apply()函数接收两个参数,第一个参数是要绑定给this的值,第二个参数是一个参数数组。当第一个参数为null、undefined的时候,默认指向window。
f = hello.apply(obj, ['残破的蛋蛋3']);
call、apply、bind函数的区别:bind返回的是函数的源代码;apply和call是立即调用。
apply和call用法基本相同,唯一的差别在于:当需要传多个变量时,call接受的是多个单一变量,而apply接受的是一个数组。
不能在箭头函数里使用call和apply,因为对于箭头函数来说,其内部的this永远指向定义它时所在的对象,而不是调用它的对象。
const product = {data: [{name: "iPhone12", price: 6499, num: 10},{name: "MacBook Pro", price: 16499, num: 2},{name: "iPad mini", price: 5499, num: 5},],getAmounts() {return this.data.reduce((t, c) => (t += c.price * c.num), 0);},test() {console.log(this);},test1: () => {console.log(this);}};console.log(product.test()); // productconsole.log(product.test1()); // window
可以使用访问器属性,将方法伪造成一个属性去访问:
get total() {return this.data.reduce((t, c) => (t += c.price * c.num), 0);}colsole.log(product.total);
JavaScript流程控制可以简单的理解为:控制代码按照一定的结构顺序来执行,流程控制主要分为三种,分别是:顺序结构、分支结构、循环结构
顺序结构是程序中最简单、最基本的流程控制,程序会按照代码的先后顺序进行执行,程序中大部分的代码都是这样执行的。
JavaScript提供了两种最常用的分支结构语句:if和switch语句。
if(条件表达式) {// 条件成立需要执行的代码块}
案例:根据学生的成绩判断是及格、优秀、还是不及格需要补考:
学生成绩在60分以下需要补考,60-80之间为及格,80-100之间为优秀,成绩小于0或者大于100提示成绩分数非法!
let score = 80;if (score >= 60) {console.log('及格了'); // 及格了}
if(条件表达式) {// 如果条件成立需要执行的代码块} else {// 否则执行该代码块}
let score = 59;if (score >= 60) {console.log('及格了');} else {console.log('补考吧,兄弟');}// 补考吧,兄弟
if(条件表达式1) {// 代码块1} else if ((条件表达式2)) {// 代码块2} else if ((条件表达式3)) {// 代码块3} else {// 上述条件都不成立时,执行该代码块}
案例:
let score = 90;if (score >= 60 && score < 80) {console.log('合格');} else if (score >= 81 && score <= 100) {console.log('学霸');} else if (score > 100 || score < 0) {console.log('非法分数');} else {console.log('补考吧,兄弟');}// 学霸
switch根据不同的条件来执行不同的代码
switch(条件表达式) {case 条件1:// 条件表达式 === 条件1时执行的代码break;case 条件2:// 条件表达式 === 条件2时执行的代码break;case 条件3:// 条件表达式 === 条件3时执行的代码break;default:// 条件表达式 !== 以上任一条件时执行}
上述学生成绩的案例也可以使用switch语句来进行改写:
let score = 90switch (true) {case score >= 60 && score < 80:console.log('合格'); break;case score >= 81 && score <= 100:console.log('学霸'); break// 判断成绩是否是一个有效的分数?case score > 100 || score < 0:console.log('非法分数'); break// 默认分支default: console.log('补考吧,兄弟');}
在JS代码中,break关键字是用来中断代码执行的,在switch…case语句中,如果找到了符合条件的语句,就会中断执行。因此,在使用switch语句时,千万不要忘记写break了。
如果给出的条件在case中都不满足,就运行default下的代码。
switch…case语句使用的是严格比较(===),意思是值必须与要匹配的类型相同。只有操作数属于同一类型时,严格比较才能为 true。
for (语句 1; 语句 2; 语句 3) {// 执行的代码块}
其中,这三条语句的执行顺序分别为:
实例:
// 定义一个数组const arr = [1, 2, 3, 4, 5];for (let i = 0; i < arr.length; i++) {document.write(arr[i]);}
结果:1 2 3 4 5
- 关于这三条语句的个人理解:
一般情况下,语句1是用来初始化循环中所用的变量的,比如上述实例中初始化了i = 0。但是语句1,并不是只能初始化一个值,也可以初始化多个值,用逗号分隔,比如:
for (i = 0, len = arr.length; i < len; i++) {text += arr[i] + "<br>";}
而且,当在循环开始前,已经初始化过变量,那么语句1还能省略:
let i = 0, len = arr.length;for (; i < len; i++) {document.write(arr[i]);}
语句2用于计算初始化中的条件,如果语句2返回的是true,那么循环继续,如果返回的是false,那么就结束循环。
语句3是将初始值递增/递减的,同时,语句3也可以省略:
let i = 0, len = arr.length;for (; i < len;) {document.write(arr[i]);i++;}
在JavaScript中,for…in语句遍历对象属性:
for (变量 in 对象) {在此执行代码}
变量可以使数组元素,也可以是对象的属性
实例:声明一个用户的信息对象:
const user = {name: '残破的蛋蛋',age: 18,sex: '男',weight: 500};
现在要求我们把用户的信息打印出来,使用for..in可以这样操作:
for (let key in user) {console.log(`${key} => ${user[key]}`);}

条件 ?表达式1:表达式2
如果条件为真,则执行表达式1,否则执行表达式2
let score = 80;score >= 60 ? '及格' : '不及格';
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号