1.作用域和作用域链
作用域
全局作用域:脚本中通用的作用域
局部作用域:函数调用时诞生的作用域
全局对象-window
浏览器自带的一个全局顶级对象,其中存放了浏览器提供的各种API
// window
window.alert('提供了浏览器中的弹窗功能')
// 友好写法:凡是window对象中的属性,使用时可以省略 window. 前缀
alert('弹窗')
全局污染
在全局作用域中声明的变量/函数都会存储到window里
window对象本意是存储系统提供的API,把自定义属性存放在这里,就属于污染
导致:
- 同名属性会覆盖, 导致系统的API被替换
- 外部自定义脚本的变量冲突
解决方案:
- 在局部作用域--匿名函数自调用形成的作用域声明变量,在用匿名函数自调用时,前面要用;将匿名函数域前面的代码分开
(function () {
// 把自定义的代码 放在这里写
function alert() {
var a = 1
}
alert()
})()
- ES6中退出let/const这两种新的声明变量的方案,带来新的顶级对象-脚本对象,用于存储自定义变量
作用域链
作用域也可以根据代码层次分层,以便子作用域可以访问父作用域,这种分层结构就形成了作用域链
- 在使用变量时,会先查找当前作用域,如果当然作用域找不到,就往父作用域查找
- 子作用域可以访问父作用域,通常是指沿着链式的作用域链查找,而不能从父作用域引用子作用域中的变量和引用
- 遵循 `就近原则` : 使用最近的上级作用域的
var a = 1;
var b = 2;
function fun() {
var a = 3;
// 1.先找当前函数作用域内是否有a变量
// 2.在当前函数作用域内部找到变量a,使用变量a
console.log(a);
// 1.先找当前函数作用域内是否有b变量
// 2.在当前函数作用域内部找到变量b,没有找到
// 3.继续像上一层作用域寻找,到变量b,使用变量b
console.log(b);
}
fun();
// 1.先找当前函数作用域内是否有a变量,找到变量a,使用变量a
console.log(a);
执行过程:
1. 创建全局变量a,保存在全局作用域
2. 创建全局变量b,保存在全局作用域
3. 创建全局变量fun(保存函数对象),保存在全局作用域
4. 调用fun函数
4.1 创建fun函数作用域
4.2 创建局部变量a,保存在fun函数作用域
4.2.1 输出变量a
4.2.2 先找当前函数作用域内是否有a变量
4.2.3 在当前函数作用域内部找到变量a,使用变量a
4.3 输出变量b
4.3.1 先找当前函数作用域内是否有b变量
4.3.2 在当前函数作用域内部找到变量b,没有找到
4.3.4 继续像上一层作用域寻找,到变量b,使用变量b
5.输出变量a,此时的a使用全局作用域中的a
函数在调用结束后,会释放(销毁/干掉)函数作用域对象,作用域对象中的局部变量也跟着释放,所以函数内的局部变量在函数外无法访问
2.闭包
函数的被动技能:
- 声明时 先查看自身用到哪些来自外部作用域的变量, 就会把这些外部作用域保存在自身的scopes 属性里
闭包:
- 这些被保存在 函数的scopes 属性里的 函数作用域对象 称为闭包
为什么要这么设计?
- 确保函数在任何位置运行时, 都能正常使用这些来自外部的变量
缺点:
- 为了自身的运行, 需要保存这些外部的变量, 就会造成额外的内存消耗: 浪费内存
function fn() {
var a = 0;
function fun() { // 内部函数,闭包
console.log(a) // 使用了外层函数中的变量
}
fun()
}
fn()
闭包使用场景:
私有属性:专为指定函数服务的变量
所以不能放在全局作用域中
解决:放在局部作用域中声明, 借助闭包的特性存储在函数的scopes里
命名规范: 习惯上给 私有属性 用_开头
3.arguments
函数自带的变量,一个数组,保存函数传入的所有参数
属于类数组类型,属性结构和数组相同,但是原型不是数组
遍历方案:使用for...of 或 利用Object.setPrototypeOf修改其原型为数组
function fun(){
console.log(arguments)
}
fun(1,2,3)
// 运行结果: [1, 2, 3]
函数重载
参数数量不固定的函数,根据参数的个数或类型不同,执行不同的逻辑操作
function show() {
if (arguments.length == 3) {
return arguments[0] * arguments[1] * arguments[2]
}
if (arguments.length == 2) {
return arguments[0] + arguments[1]
}
if (arguments.length == 1) {
// 再次细分
if (typeof arguments[0] == 'string') {
return arguments[0].toUpperCase()
}
if (typeof arguments[0] == 'number') {
return arguments[0] * arguments[0]
}
if (typeof arguments[0] == 'boolean') {
return arguments[0] ? '真' : '假'
}
}
}
console.log(show(10, 20, 30)) //计算3个数字的乘积
console.log(show(10, 20)) //计算2个数字的和
console.log(show(10)) //计算数字的平方
console.log(show('kaikai')) //返回字符串的大写格式
console.log(show(true)) // 返回: '真'
console.log(show(false)) // 返回: '假'
4.函数的this
对象.函数() : this指向对象
函数() : this 指向window
new 函数(): 实例对象
箭头函数:没有this 按照作用域链原则到上层查找使用
5.call,apply,bind
强行修改函数中 this 指向的方案:
apply: 升级版 - 函数临时放到对象执行, 额外: 把数组 转换成 参数列表形式
bind: 把函数+对象+实参 绑定打包在一起, 后续调用时 再临时组合
call: 最基础 - 把函数临时放到某个对象中执行
var r1 = { a: 10, b: 20 }
function area(c, d, e) {
return this.a + this.b + c + d + e
}
console.log(
// 参数1: 指定this代表的对象
// 参数2开始... : 实参
area.call(r1, 100, 200, 300)
// 参数1: 指定this代表的对象
// 参数2: 实参列表
area.apply(r1,[100,200,300])
// bind: 绑定
// 把 函数 和 运行时所需的对象 + 实参 绑定在一起 打包成1个整体
let total = area.bind(r1, 100, 200, 300)
total();
)