JS基础作用域和闭包
题目
1.this在不同场景下应该如何取值?
2.如何手写bind函数?
3.实际开发中闭包的应用场景、举例说明
4.创建10个a标签,点击的时候弹出对应序号
知识点
1.作用域和变量
2.闭包
3.this
一、作用域
作用域:代表一个变量的合法使用范围
三级作用域:
1.全局作用域:(全局都可以使用,如doucument对象)
2.函数作用域:(一个函数中定义的只能在当前函数内使用)
3.块级作用域(es6新增):块:如if while等包含大括号、如在大括号外使用则报错
if(true) {
let x = 10
}
console.log(x) // x is not defined
二、自由变量
一个变量在当前作用域中没有定义,但被使用了
它会向上级作用于,一层一层一次寻找,直到找到为止
如果到全局作用域中都没有找到,则会报 xx is not defined 的常见错误
三、闭包
作用域应用的特殊情况,有两种表现:
1.函数作为参数被传递(函数在一个地方定义好之后,在另一个地方去执行)
2.函数作为返回值被返回(函数定义好之后,会被返回到另一个地方执行)
// 函数作为返回值
function create() {
const a = 100
return function() { //此处的a是自由变量一定是向上查找
console.log(a)
}
}
const fn = create()
console.log(fn)
const a = 200
fn() // 100
//函数作为参数被传递
function print(fn) {
const a = 200
fn()
}
const a = 100
function fn() { //在此处向上寻找
console.log(a)
}
print(fn) // 100
注意
所有的自由变量的查找,是在函数定义的地方去查找,向上级作用于查找,不是在执行的地方查找 ! ! ! !
四、this的使用
使用场景:
1.作为普通函数使用
2.使用call apply bind被调用
3.作为对象方法被调用
4.在class方法中被调用
5.箭头函数
判断this取什么值的技巧:
this取什么值是在函数执行的时候决定的,而不是在函数定义的时候决定
//作为普通函数使用
function fn1() {
console.log(this)
}
fn1() //this是全局变量window
//使用call调用
fn1.call({x: 100}) //this是对象{x: 100}
//使用bind调用
const fn2 = fn1.bind({x: 200}) //bind会返回新的函数执行
fn2() // this是对象 {x: 200}
//作为对象方法被调用
const whl = {
name: "whl",
sayHi() {
//this是当前对象
console.log(this) //作为对象方法执行
},
wait() {
setTimeout(function() {
console.log(this) // this === window 相当于作为普通函数调用
})
},
waitAgain() {
setTimeout(() => {
// 箭头函数永远取上级作用域的this即相当于waitAgain中的this,相当于sayHi中的this,都是指当前对象
console.log(this)
})
}
}
whl.sayHi()
whl.wait()
whl.waitAgain()
class People {
constructor(name) {
this.name = name
this.age = 20
}
eat() {
console.log(this) //this代表whl1实例
}
}
const whl1 = new People('whl1')
whl1.eat()
五、题目解答
1.this在不同场景下应该如何取值?
1.作为普通函数被调用 – 返回window
2.使用call apply bind被调用 – 传入什么绑定什么
3.作为对象方法被调用 – 返回对象本身(如果是隐式原型的方法 – undefined)
4.在class方法中被调用 – 返回当前实例本身
5.箭头函数 – 永远会找上级作用域this的值来确定
2.手写bind函数
// 模拟 bind
Function.prototype.bind1 = function () {
// 将参数拆解为数组
const args = Array.prototype.slice.call(arguments)
// 获取 this(数组第一项)
const t = args.shift() // 拿走数组第一项
// fn1.bind(...) 中的 fn1
const self = this
// bind返回一个函数
return function () {
return self.apply(t, args)
}
}
function fn1(a, b, c) {
console.log('this', this)
console.log(a, b, c)
return 'this is fn1'
}
const fn2 = fn1.bind1({x: 100}, 10, 20, 30)
const res = fn2()
console.log(res)
3.实际开发中的闭包应用场景,举例说明
隐藏数据,如做一个简单的cache工具
//闭包隐藏数据,只提供API
function createCache() {
const data = {} //闭包中的数据被隐藏不被外界访问
return {
set: function(key,val) {
data[key] = val
},
get: function(key)
return data[key]
}
}
const a = createCache()
a.set('a',100)
console.log(a.get('a')) // 100
如果想不通过get和set修改data的值是没有办法的,因为data是在createCache的作用域里的,不会被外界访问到
4.面试题 创建10个a标签,点击的时候弹出对应的序号
错误情况:(点击每个标签都是弹出10)
let i,a
for (i = 0; i < 10; i++) {
a = document.createElement('a')
a.innerHTML = i + '<br>'
a.addEventListener('click', function (e) {
e.preventDefault()
alert(i)
})
document.body.appendChild(a)
}
因为代码很快就会执行,所以创建a标签时,addEventListener中click事件还没有执行,只有点击才会执行,又因为i是全局变量,点击时候i已经变成了10
解决:把let i放在里面。则i是块作用域,每次for循环执行的时候都会形成一个相应的块,i不一样
let a
for (let i = 0; i < 10; i++) {
a = document.createElement('a')
a.innerHTML = i + '<br>'
a.addEventListener('click', function (e) {
e.preventDefault()
alert(i)
})
document.body.appendChild(a)
}
此题考点:(对于不会立刻执行的函数)全局作用域和块级作用域的使用方式
六、小结
1.作用域和变量
2.闭包:两种常见方式&自由变量的查找规则
3.this的使用和取值