什么是作用域?什么是自由变量?
全局作用域
声明在任何函数之外的顶层作用域的变量就是全局变量,这样的变量拥有全局作用域
所有未定义直接赋值的变量拥有全局作用域
所有 window 对象的属性拥有全局作用域
全局作用域有很大的弊端,过多的全局作用域变量会污染全局命名空间,容易引起命名冲突
函数作用域
在函数内部定义的变量称为局部变量,拥有函数作用域
只有函数被调用的时候才会形成函数作用域
内层作用域可以访问外层作用域,反之不行
块级作用域
使用 ES6 中新增的 let 和 const 指令可以声明块级作用域
块作用域内的变量只要出了自己被定义的那个代码块,那么就无法访问了。
在循环中比较适合绑定块级作用域,这样就可以把声明的计数器变量限制在循环内部
自由变量
在函数中被使用,但它既不是函数参数、也不是函数的局部变量,而是一个不属于当前作用域的变量,此时它相对于当前作用域来说,就是一个自由变量
如何理解闭包
自由变量的查找规则:自由变量的查找,是在函数定义的地方,向上级作用域查找,不是在执行的地方=>js是词法作用域
::: tip 词法作用域和动态作用域
- 词法(静态)作用域: 在代码书写的时候完成划分,作用域链沿着它定义的位置往外延伸
- 动态作用域: 在代码运行时完成划分,作用域链沿着它的调用栈往外延伸
var name = 'fan';
function showName() {
console.log(name);
}
function changeName() {
var name = 'hang';
showName();
}
changeName(); // 词法: fan 动态:hang
:::
在函数中被使用,但它既不是函数参数、也不是函数的局部变量,而是一个不属于当前作用域的变量,此时它相对于当前作用域来说,就是一个自由变量。而引用了自由变量的函数,就叫闭包
LHS、RHS是什么?
LHS、RHS,是引擎在执行代码的时候,查询变量的两种方式。其中的 L、R,分别意味着 Left、Right。这个“左”和“右”,是相对于赋值操作来说的。当变量出现在赋值操作的左侧时,执行的就是 LHS 操作,右侧则执行 RHS 操作:
name = 'xiuyan';
在这个例子里,name 变量出现在赋值操作的左侧,它就属于 LHS。LHS 意味着 变量赋值或写入内存,
它强调的是一个写入的动作,所以 LHS 查询查的是这个变量的“家”(对应的内存空间)在哪。
var myName = name
console.log(name)
在这个例子里,第一行有赋值操作,但是 name 在操作的右侧,所以是 RHS;第二行没有赋值操作,name 就可以理解为没有出现在赋值操作的左侧,这种情况下我们也认为 name 的查询是 RHS。RHS 意味着 变量查找或从内存中读取,它强调的是读这个动作,查询的是变量的内容。
闭包代码输出问题
常见的循环体输出问题
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
console.log(i); //5 5 5 5 5
创建 10 个 a,点击弹出对应的序号
<div id="root"></div>
const root = document.getElementById('root')
for (let i = 1; i <= 10; i++) {
const a = document.createElement('a')
a.innerText = i
a.style.display = 'block'
a.onclick = function (e) {
e.preventDefault()
alert(i)
}
root.appendChild(a)
}
手写bind函数
::: tip 提示bind()
方法创建一个新的函数(这个包装了目标函数),在 bind()
被调用时,这个新函数的 this
被指定为 bind()
的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
:::
bind
的用法
function fn1(a, b, c) {
console.log('this', this)
console.log(a, b, c)
return 'this is fn1'
}
const fn2 = fn1.bind({ x: 100 }, 10, 20, 30)
const res = fn2()
console.log(res)
- 手写
bind
// 剩余参数是ES6的新特性,一般说来,不支持bind的情况一般也不支持剩余参数,所以,不推荐这种写法
Function.prototype.bind1 = function () {
// 将参数拆解为数组
// call支持最低版本chorme-1
const args = Array.prototype.slice.call(arguments)
// 获取this,数组第一项
const _this = args.shift()
const self = this
// 返回一个函数
return function () {
// apply支持最低版本chorme-1
// bind支持最低版本chorme-7
self.apply(_this, args)
}
}
// ----------------------------------------
const obj = {
a: 2,
b() {
console.log(this.a)
},
}
const _obj = {
a: 3,
b() {
console.log(this.a)
},
}
obj.b.bind1(_obj)()
this指向问题
this
取什么值函数执行时确认的,不是在函数定义时确认的
- 作为普通函数使用,指向
window
call apply bind 三者的用法和区别open in new window
call()
、apply()
、bind()
都是用来重定义this
这个对象的
bind
返回的是一个新的函数,你必须调用它才会被执行 ,其余两个都是立即执行的
bind
和call
参数形式一致,apply
需要把参数写到数组里
call
apply
bind
- 作为对象方法被调用,指向当前对象
- 在
class
方法中调用,指向当前对象 - 箭头函数的
this
永远取它上层作用域的this