JS进阶1之(作用域,变量提升,闭包,函数声明,箭头函数
一、作用域
作用域:规定了变量被使用的范围,超过这个范围就不能被使用
作用域分为全局作用域 局部作用域
function fn() {
//函数每次执行都会开辟新的内存空间 当函数执行完毕之后
//就会关闭这块作用域空间 并且这里面的变量都会销毁
//定义变量可以不写let 默认位全局变量
let n = 3
s = 4
console.log('n', n);
}
fn()
1.1局部作用域
1.1.1函数作用域
在函数内部声明的变量只能在函数内部中使用,外部则无法访问
函数的参数也是函数内部的局部变量
不同函数内部声明的变量无法互相访问
函数执行完毕,函数内部的变量实际被清空了
1.1.2块作用域
在js中用{}包裹的代码称为代码块
代码块内部声明的变量外部将有可能无法被访问
let声明的变量会产生块作用域 var不会
const声明的常量也会产生块作用域
{
let n = 6;
//只能当前作用域使用
}
if (true) {
let s = 1
var x = 2
}
// console.log('s', s);//获取不到
console.log('x', x); //var可以
1.2全局作用域
<script> 标签和 .js 文件的【最外层】就是所谓的全局作用域,在此声明的变量在函数内部也可以被访问
函数中未使用任何关键字声明的变量为全局变量
给window 对象动态添加的属性默认也是全局的
尽可能少使用全局变量 防止全局变量污染
1.3作用域链
本质:底层的变量查找机制,在函数被执行时,会优先查找当前函数作用域中查找变量,如果当前作用域查找不到则会依次逐级查找父级作用域直到全局作用域
嵌套关系的作用域串联起来形成了作用域链
相同作用域链中按着从小到大的规则查找变量
子作用域可以访问父作用域,父作用域不可以访问子作用域(就近原则)
1.4闭包
一个作用域有权访问另一个作用域的局部变量(子函数访问父函数的变量并返回)
好处:延申变量的使用范围
闭包本质上仍是函数,只不过不是从函数内部返回的
闭包能够创建外部可访问的隔离作用域,避免全局变量污染
弊端:过度使用闭包 造成内存泄漏
回调函数也能访问函数内部的局部变量
function fn() {
let num = 123
//子函数 作用域链 闭包
return function fn1() {
console.log(num);
}
fn1();
}
let re = fn()
console.log(re); //re为函数本身fn1() {
// console.log(num);
// }
re() //得到num的值123 num没有被销毁
1.5定义变量
let var const
var声明的变量不具有块级作用域 let 具有块级作用域
{
var a = 3
let b=4
}
console.log('var定义的a', a);//可以得到
console.log('let定义的b', b);//报错
var 声明变量可以重新定义声明 let不可以
var n = 1
var n = 2
console.log('n', n); //2
console.log('let定义的b', b); //得不到
let m = 1
let m = 2
console.log('m', m); //报错
var声明的变量 相当于window添加属性 let直接就是变量
var a = 1
console.log('window', window); //可以得到a
let b = 2
console.log('b', window);//得不到b
var 和let 都可以只声明 不赋值,但const必须赋值(必须初始化)
变量只定义不赋值 就是undefined
let var都可以改变之前声明的值 const不能改变
const其他和let一样
建议:1.常量名都用大写 纯大写 2.一般固定不变的值使用常量
1.6变量提升(预解析)
代码在执行之前,先要预解析,解析变量和函数
在代码执行之前,先要扫码代码一下(预解析),把变量和函数解析出来
任何一个作用域执行代码之前都要先预解析
只定义 不赋值
变量在未声明即被访问时会报语法错误
变量在声明之前即被访问,变量的值为 undefined
let 声明的变量不存在变量提升,推荐使用 let
// var n 内存中有n
// var m
//预解析:代码执行之前先要预解析
//变量的提升
//会把代码中声明变量,提前解析到(提升到)当前作用域的最前面,
//但是只定义,不赋值
var n = 1
var m = 2
if (true) {
n++
}
function fn() {
// let n没有初始化之前不让使用
//死区: 不让使用变量
//死区结尾
console.log(s);// Cannot access 's' before initialization
let s = 2
}
fn();
二、函数
2.1函数提升(预解析)
函数在声明之前即被调用
fn()//可以调用
function fn() {
console.log('函数');
}
函数表达式不存在提升现象
bar();
var bar = function () {
console.log('函数表达式不存在提升现象...');
}
函数提升出现在相同作用域当中
2.2参数
2.2.1 默认值
声明函数时为形参赋值既是参数的默认值
如果参数没有自定义默认值(没有传值)则为undefined
调用函数时没有传入对应实参时,参数的默认值被当做实参传入
//不传参时,形参是undefined
//参数设置默认值
function fn(name = '阿飞', age = 22) {
console.log(`我的名字是${name},今年${age}`);
}
fn('赵云', 70);//
//求任意两个数和
function getSum(x = 0, y = 0) {
let s = x + y;
console.log('求和', s);
}
getSum()
2.2.2 动态参数
arguments 是函数内部内置的伪数组变量,它包含了调用函数时传入的所有实参
作用:动态获取函数的实参
function getMax() {
console.log(arguments);
let s = arguments[0];
for (let i = 0; i < arguments.length; i++) {
if (s < arguments[i]) {
s = arguments[i];
}
}
console.log('最大值', s);
}
getMax(2, 4, 6, 7, 3, 22, 78);
2.2.3 剩余参数
... 是语法符号,置于最末函数形参之前,用于获取多余的实参
借助 ... 获取的剩余实参
//剩余参数 只能放到最后的位置 传给c ...c
// function fn(a, b, ...c) {
// console.log(a, b, c);
// }
// fn(undefined, 2, 3, 4, 5, 6)
//真数组 ...剩余参数
function fn(...a) {
console.log(a);
}
fn(2, 3, 4, 5, 6)
2.3箭头函数
箭头函数是一种声明函数的简洁语法,它与普通函数并无本质的区别,差异性更多体现在语法格式上
// let fn=function() {}
//在()与{}之间加个箭头
let fn = (a, b) => {
return a + b
}
let re = fn(1, 2)
console.log(re);
箭头函数只有一个参数时可以省略圆括号 ()
// let fn = (a) => {
// return a * a
// }
//1.如果参数只有一个,可以省略小括号
let fn = a => {
return a * a
}
console.log(fn(3));
箭头函数函数体只有一行代码时可以省略花括号 {},并自动做为返回值被返回
//2.如果函数体只有一行代码,可以省略大括号
//如果大括号省略的话,会自动返回结果,不需要return
let fn1 = b => b * b;
console.log(fn1(6));
箭头函数中没有 arguments,只能使用 ... 动态获取实参
涉及到this的使用,不建议用箭头函数
//箭头函数使用
// setInterval(() => {
// console.log('定时器');
// }, 1000)
//注意点
//1.表达式函数不会预解析 箭头函数不存在预解析(先定义再调用)
// let fn = () => {
// }
// fn()
//2.箭头函数中不存在arguments 使用剩余参数...
// let fn = () => {
// console.log(arguments);
// }
// fn(1, 2)
//3.箭头函数中不存在this
//箭头函数中的this指向上级作用域的this
//箭头函数中的this指向箭头函数所在作用域的this
//函数中的this指向调用者
//如果涉及到使用this的时候,尽量不要使用箭头函数
// let fn = () => {
// console.log(this);//指向window
// }
// fn()
// let obj = {
// name: 'aaa',
// fs: function () {
// console.log(this);//为obj
// }
// }
// obj.fs()
let obj = {
name: 'aaa',
fs: function () {
setInterval(() => {
console.log(this);//指向obj
}, 10000)
}
}
obj.fs()
三、结构赋值
解构赋值:解开数据结构赋值给变量(简单的取数据)
3.1数组结构
数组解构是将数组的单元值快速批量赋值给一系列变量的简洁语法
核心:一一对应
赋值运算符 = 左侧的 [] 用于批量声明变量,右侧数组的单元值将被赋值给左侧的变量
let [a,b,c,d,e,f]=['张飞', '赵云', '关于', '刘备', '典韦', '貂蝉']
变量的数量大于单元值数量时,多余的变量将被赋值为 undefined
// 变量多 值少
// let [a, b, c, d, e, f, g, h, i] = ['张飞', '赵云', '关于', '刘备', '典韦', '貂蝉']
// g h i为undefined
// console.log('变量多 值少', a, b, c, d, e, f, g, h, i);
变量的数量小于单元值数量时,可以通过 ... 获取剩余单元值,但只能置于最末位
//变量少 值多
// let [a, b, c] = ['张飞', '赵云', '关于', '刘备', '典韦', '貂蝉']
// console.log('变量少 值多', a, b, c,);
//剩余值 ...c
let [, a, b, ...c] = ['张飞', '赵云', '关于', '刘备', '典韦', '貂蝉']
console.log('剩余值', a, b, c,);
按需取值,用逗号隔开
//按需取值
// let [, , a, b, , c] = ['张飞', '赵云', '关于', '刘备', '典韦', '貂蝉']
// console.log('按需取值 逗号占位', a, b, c);
复杂取值
let [, , a, b, , [, c, d]] = ['张飞', '赵云', '关于', '刘备', '典韦', ['林冲', '鲁智深', '武松']]
console.log('复杂情况', a, b, c, d);
允许初始化变量的默认值,且只有单元值为 undefined 时默认值才会生效
3.2对象结构
对象解构是将对象属性和方法快速批量赋值给一系列变量的简洁语法
赋值运算符 = 左侧的 {} 用于批量声明变量,右侧对象的属性值将被赋值给左侧的变量
对象属性的值将被赋值给与属性名相同的变量
对象中找不到与变量名一致的属性时变量值为 undefined
允许初始化变量的默认值,属性不存在或单元值为 undefined 时默认值才会生效
// 普通对象
let user = {
name: '小明',
age: 18
};
// 批量声明变量 name age
// 同时将数组单元值 1 2 3 依次赋值给变量 a b c
let {name, age} = user;
console.log(name); // 小明
console.log(age); // 18
没有一一对应这一说
不可定义重复的变量名 用: 改名字
let uname = '重复'
// //把对象解开结构赋值给变量 没有一一对应
// //不可定义重复的变量名 用: 改名字
let { uname: userName, sex, abc } = {
uname: "张三丰",
age: 33,
sex: '男',
score: 99,
index: 6
};
console.log('对象结构赋值', userName, sex, abc);
// let { dog: { uname } } = {
// uname: "张三丰",
// age: 33,
// sex: '男',
// score: 99,
// index: 6,
// dog: {
// uname: '小狗',
// age: 1
// }
// }
// console.log('dog.uname', uname);
function fn({ uname, age, index }) {
// console.log('obj', obj);
console.log('cc', uname, age, index);
}
fn({
uname: "张三丰",
age: 33,
sex: '男',
score: 99,
index: 6,
})