目录
函数进阶
参数默认值
// 形参默认值
// 语法:只要在定义时,为形参赋个值
// 当函数调用时,没有参数,使用默认值
function fn(age=10 ,gemder='男') {
}
fn(10)
fn()
动态参数arguments
// 🏆 动态参数
// arguments 是函数内部内置的伪数组变量,它包含了调用函数时传入的所有实参
// 总结:
// 1. arguments 是一个伪数组,只存在于函数中
// 2. arguments 的作用是动态获取函数的实参
// 3. 可以通过for循环依次得到传递过来的实参
function fn() {
console.log(arguments);
let sum = 0
for (let i = 0; i < arguments.length; i++) {
sum += arguments[i]
}
console.log(sum);
}
fn(10, 20, 30)
fn(40, 60)
/*arguments应用场景:在形参的个数不确定的情况,可以使用arguments来接收不确定的实参。一个伪数组,
但可以像数组一个通过下标读取元素,或者使用for循环进行遍历,或者通过 .1ength读取长度*/
剩余参数...
// 🏆剩余参数: ...
// 剩余参数允许我们将一个不定数量的参数表示为一个数组
// 1. ... 是语法符号,置于最末函数形参之前,用于获取多余的实参
// 2. 借助 ... 获取的剩余实参,是个真数组
// 剩余参数必须位于形参最后
function fn(a, ...arr) {
console.log(arr);
let sum = 0
for (let i = 0; i < arr.length; i++) {
sum += arr[i]
}
console.log(sum);
}
fn(10)
fn(10, 20)
fn(10, 20, 23)
fn(10, 20, 23, 45)
// 小结:..剩余参数
// 接收到是一个真数组,
// 也可以解决参数不确定的问题,推荐使用剩余参数
// 🏆剩余参数和动态参数arguments的区别
// 🏆剩余参数和动态参数arguments的区别
// 1.arguments用于获取实际参数不管是否已经定义了形参
// 2.arguments是伪数组不能使用数组方法
// 1.剩余参数(...变量名)将形参剩下的参数保存到数组里面去(可以设定形参)
// 2.剩余参数(...变量名)是真数组
展开运算符
//展开运算符:...
// 作用:用于将数组展开,形成由逗号分隔的多个值
// ...数组展开的应用:
// 💎 求数组的最大值与最小值
let score = [231, 534, 8, 123, 54, 678, 23]
// Math.max()接收的参数是逗号分隔的多个值
console.log(Math.max(score)); //NaN
console.log(Math.max(...score));//678
console.log(Math.min(...score));//678
//💎2.合并数组
let arr_0 = [10, 20, 30];
let arr_2 = [40, 50, 60]
//10,20,30 40, 50, 60
let narr = [...arr_0, ...arr_2]
console.log(narr);
//💎 3.展开实参
function fn(a, b, c) {
console.log(a, b, c);
}
let data = ["hello", 'world', "来阿萨德"]
fn(...data)
// 💎对象传递
const obj1 = {
uname: '张三',
age: 67
}
const obj2 = {
...obj1//没有直接得到obj1的堆的地址,而是新建了一个堆,没有和obj1产生引用关系
}
obj2.uname = '刘凡'
console.log(obj2);
console.log(obj1);//obj1没有更改,因为是深拷贝?
// 小结:
// 剩余参数: ...用在形参前
// 展开运算符:....用在数组前
// 将数组展开成,由逗号分隔的多个值。
// 应用场景:只要是可以出现逗号分隔多个值的位置都可以使用 数组展开
箭头函数
// // 🏆1.箭头函数语法 省略function
// let fn1 = (a, b) => { console.log(a * b); }
// fn1(2, 65)
// //2.如果形参只有1个,可以省略小括号 //没有形参也要写小括号
// let fn2 = n => { console.log(n); }
// fn2(10)
// //3.如果函数体内的代码只有1条,花括号也可以省略
// let fn3 = n => console.log(n);
// fn3(13)
// 4.如果函数体内的代码只有1条语句,再把花括号和return省略,默认会将仅有的一条语句的值返回,不写大括号只写return反而报错
// let fn4 = (x, y) => x * y;
// let res = fn4(7, 6);
// console.log(res);
//5.箭头函数里面如果只有一行代码并且返回的是一个对象需要在对象的外面使用()把整个对象包裹起来
// const fn = () => ({ uname: '张三', age: 18 })
// const obj = fn()
// console.log(obj)
/*小结:
箭头函数是一种函数表达式,没有函数提升,必须遵循先定义后使用的原则
语法:()=>{}
两种省略条件:
只有1个形参可以省略小括号
只有1条语句可以省略花括号,省略花括号时,默认会将仅有的一条语句的值返回。
*/
// 🏆箭头函数是没有arguments .
// 可以使用剩余参数... 代替
let fn5 = (...args) => {
let sum = 0
for (let i = 0; i < args.length; i++) {
sum += args[i]
}
// console.log(arguments);
console.log(args);
console.log(sum);
}
fn5(10, 321, 432)
箭头函数中的this
//🏆箭头函数里是没有this,
//虽然没有this,但我们以后写代码时,可能会在箭头函数里出现this
//如果出现了this,会触发链式查找,找上一级的this
<button class="btn">click</button>
<script>
const btn = document.querySelector('.btn')
btn.addEventListener('click',function(){console.log(this);})//btn
btn.addEventListener('click', () =>console.log(this) )//window
</script>
let obj = {
id: 10,
say: function () {
console.log(this);//obj
showinfo = () => {
console.log(this);//obj
}
showinfo()
}
}
obj.say();
第一个this是普通函数中所以,this为调用对象obj
第二个this是箭头函数中的,因为箭头函数没有this,所以触发链式查找,找到箭头函数外的this,箭头函数外事普通函数,所以也是obj.
解构赋值
数组解构
// 🏆数组解构是将数组的单元值快速批量赋值给一系列变量的简洁语法。
// 基本语法:
// 1. 赋值运算符 = 左侧的 [] 用于批量声明变量,右侧数组的单元值将被赋值给左侧的变量
// 2. 变量的顺序对应数组单元值的位置依次进行赋值操作
let arr = [213, 2334, 435]
let [a, b, c] = arr
// 🏆基本语法:典型应用交互2个变量
// let x = 78;
// let y = 2;
// [y, x] = [x, y]
// console.log(x, y);
// 🏆js 前面必须加分号的两种情况
// 💎立即执行函数
// ; (function () { })()
// 💎数组开头的
// ;[y, x] = [x, y]
//🏆1.只解构前两个元素
// let [a,b] ▪ arr;
// console.log(a,b);
//🏆2.按需解构
// let [a,,c] = arr;
// console.log(a,c);
//🏆3.也可以使用剩余参数...将多个值解构到一个数组中
// let [a,...other] = arr;
// console.log(a, other);
// 🏆4.解构函数的返回值
// function fn() {
// return ['hello', 'world']
// }
// let [a, b] = fn();
// console.log(a, b);
// function getValue() {
// return [100, 60]
// }
// const [max, min] = getValue()
// console.log(max, min);
// 🏆多维数组解构
const arr1 = [1, 2, [3, 4]]
// 需求获取3
const [,,e]=arr1
console.log(e);//是一个数组[3,4]
const [a, b, [c,d]] = arr1
console.log(c);//
对象解构
//🏆对象解构:将对象里的属性赋值给多个变量
// let obj = { age: 20, gender: '男' }
// //语法:{变量,变量}=对象
// let { gender,age } = obj;//对象中的属性是无序的,所以前后顺序没关系
// console.log(age, gender);
// //说明:默认变量名必须与对象的属性名相同
// //变量名与属性名不致时的语法:{原名:新名,原名:新名}=对象
// let { age: a, gender: g } = obj;
// console.log(a, g);
// 🏆多级对象解构
//在实际开发的过程中,我们得到的数据一般就是一个对象数组
let stus = [
{
id: 12,
name: 'zhangsan',
car: {
brand: 'BMW',
price: { a: '50w' }
}
}
]
let [{ car: { brand, price } }] = stus
console.log(brand, price);
数组的forEach方法
//forEach()也是用于对数组进行遍历的一个语法
// 语法:
// 数组.forEach(function(item,index){
//代码
// })
// 说明:
// 是元素的值
// item
// 是元素的下标
let arr = [10, 20, 30, 35, 45]
let sum = 0
arr.forEach(item => sum += item)
console.log(sum);
js垃圾回收机制
//🏆 引用计数
// IE采用的引用计数算法, 定义“内存不再使用”的标准很简单,就是看一个对象是否有指向它的引用。
// 算法:
// 1. 跟踪记录每个值被引用的次数。
// 2. 如果这个值的被引用了一次,那么就记录次数1
// 3. 多次引用会累加。
// 4. 如果减少一个引用就减1。
// 5. 如果引用次数是0 ,则释放内存。
let oa = {
id: 10
}
// {id:10}被oa引用了,{id:10}引用次数为1
let ob = oa
// {id:10}被引用的次数为2
ob = 10
// {id:10}被引用的次数为1,引用减少了一次
oa = true
// {id:10}被引用的次数为0,此时{id:10}被js视为垃圾,后面启用垃圾回收机制清除
// 🏆此种垃圾机制有一个致命问题
let obj_1 = {
id: 10,
gender: '男'
}
let obj_2 = {
id: 11,
gender: '女'
}
obj_1.a = obj_2
obj_2.a = obj_1
// 对象相互嵌套使用,引用次数不为0,不会被回收机制清除
obj_1 = 10
obj_2 = 10
// 并且重新复赋值后,找不到存储对象数据的地址
// 如果两个对象相互引用,尽管他们已不再使用,垃圾回收器不会进行回收,导致内存泄露。
// 🏆 标记清除法
// 现代的浏览器已经不再使用引用计数算法了。
// 现代浏览器通用的大多是基于标记清除算法的某些改进算法,总体思想都是一致的。
// 核心:
// 1. 标记清除算法将“不再使用的对象”定义为“无法达到的对象”。
// 2. 就是从根部(在JS中就是全局对象)出发定时扫描内存中的对象。 凡是能从根部到达的对象,都是还需要使用的。
// 3. 那些无法由根部出发触及到的对象被标记为不再使用,稍后进 行回收。
let obj_1 = {
id: 10,
gender: '男'
}
let obj_2 = {
id: 11,
gender: '女'
}
obj_1.a = obj_2
obj_2.a = obj_1
//💎情况1:两个对象相互引用
//标记清除对于此种情况,JS会扫描当前代码所有使用过的内存
// 看内存区域是否有变量引用,如果没有被变量引用,就视为垃圾
function fn() {
let i = 10
}
fn()
//💎情况2:函数内的局部变量
//如果是函数内的局部变量,当函数执行结束后,会被标记为垃圾,等待着被回收
JS如何将一个数据识别为垃圾?
1. 引用计算:
2.标记清除:
内存泄露:内存中的无法使用的数据,并且无法被销毁*/
闭包
// 🏆概念:一个函数对周围状态的引用捆绑在一起,内层函数中访问到其外层函数的作用域
// 一个函数里嵌套一个函数 ,里面的函数想访问 外层函数 的变量
// 简单理解:闭包 = 内层函数 + 外层函数的变量
// function outer() {
// // 外部函数的局部变量
// let a = 10
// function inner() {
// // 内部函数使用 访问外部变量
// console.log(a);
// }
// inner()
// }
// outer()
// 其他的闭包函数
function outer() {
let a = 10
function inner() {
console.log(a);
}
//将Inner函数返回,并不是inner函数执行的结果
return inner
}
let f = outer()//获得outer的返回值 返回的是inner函数
// console.log(f);//f是内部函数
f()
inner()//直接写inner 无法访问
// 内部函数return
// function outer() {
// let a = 10
// return function () {
// console.log(a);
// }
// }
// let f = outer()
// console.log(f);//f是内部函数
// f()
// function outer() {
// let a = 10
// return function () {
// console.log(a);
// }
// }
// let f = outer()
// console.log(f);//f是内部函数
// f()
/*🏆闭包的作用:
1.闭包可以让函数所操作的数据私有化
2.延长局部变量的生命周期,让局部变量活的久一点
缺点:
1.闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包
2.闭包会在父函数外部,改变父函数内部变量的值。
// 函数多次调用时可以共享一个数据
function outer() {
let i = 0;
return function () {
i++; console.log(i);
}
}
let f = outer();// i = 2;调用outer函数 得到 outer函数的返回值 inner函数,将返回值 赋给全局变量f
f();// 1
f();// 2
f();// 3
f();// 4
f();// 5
f();// 6
变量提升函数提升
//🏆 预解析
// 所谓预解析:就是js代码在执行之前,会一个编译阶段(编译阶段将我们所写的代码翻译成机器指令)
//在预解析阶段完成一件事,函数提升
🏆函数提升:
在代码执行之前的一个阶段(预解析),JS会查找代码中的函数的声明,并将函数的声明提到.
最终体现在代码上的影响就是函数的调用可以在声明之前,不会报错*/
fn();
function fn() { console.log('hello'); }
// 🏆变量提升
//1et声明的变量是没有变量提升,
//只要var声明的变量才有提升,var是旧版本的用于声明变量的方式。
// console.log(i);
// let i = 10;
//为什么var声明的变量,访问可以在前,声明在后,而不报错
// 因为Var量具有变量提升(变量声明前置)
//在预解析阶段,js会查找代码中的使用var声明的变量,并将声明部分提升到 当前作用域的最前面
console.log(j);// undefined
var j = 20;
预解析:就js代码执行之前的一个阶段
这个阶段,帮完成,函数提升 与 变量提升(只var声明的变量才会提升)
let与const是没有提升的