作用域、闭包是一个头疼的玩意,特别对应初次接触者来说真可谓是头疼,作为面试他也是面试题中的常青树。
题目
-
this 的不同应用场景
-
创建 10 个
<a>标签,点击的时候弹出来对应的序号js
// 创建 10 个`<a>`标签,点击的时候弹出来对应的序号 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) } -
实际开发中闭包的应用
-
手写 bind 函数
知识点
作用域
所谓作用域,即一个变量的合法使用范围
js
let a = 0
function fn1() {
let a1 = 100
function fn2() {
let a2 = 200
function fn3() {
let a3 = 300
return a + a1 + a2 + a3
}
fn3()
}
fn2()
}
fn1()
作用域分类
- 全局作用域:在全局定义的变量,全局都可用,像 document
- 函数作用域:在某个函数中定义的变量,只能用于当前函数,像 a b
- 块级作用域(ES6):只能活跃于当前的块,示例如下
js
// ES6 块级作用域
if (true) {
let x = 100
}
console.log(x) // 会报错
自由变量
- 一个变量在该作用域没有被定义,但被使用
- 向上级作用域去寻找该变量,层层往上找 —— 如上面的示例
js
let a = 0
function fn1() {
let a1 = 100
function fn2() {
let a2 = 200
function fn3() {
let a3 = 300
return a + a1 + a2 + a3
}
fn3()
}
fn2()
}
fn1()
闭包
闭包 —— 作用域应用的一个特殊情况。一般有两种书写形式:
- 函数作为参数被传入
- 函数作为返回值
js
// 函数作为返回值
function create() {
let a = 100
return function () {
console.log(a)
}
}
let fn = create()
let a = 200
fn()
// 函数作为参数
function print(fn) {
let a = 200
fn()
}
let a = 100
function fn() {
console.log(a)
}
print(fn)
从上面代码中可得出 函数中自由变量在使用时 **函数定义的地方(不是执行的地方)**去寻找上级作用域
这种现象称为闭包
闭包示例
隐藏定义的数据 只提供API
js
function createCache() {
let data = {} // 闭包中的数据,被隐藏,不被外界访问
return {
set: function (key, val) {
data[key] = val
},
get: function (key) {
return data[key]
}
}
}
let c = createCache()
c.set('a', 100)
console.log( c.get('a') )
this
常见场景:
- 作为普通函数调用
- 使用
callapplybind - 作为对象方法调用
- 在 class 的方法中调用
- 箭头函数
js
// this 场景题 - 1
function fn1() {
console.log(this)
}
fn1() // 打印什么
fn1.call({ x: 100 }) // 打印什么
const fn2 = fn1.bind({ x: 200 })
fn2() // 打印什么
// this 场景题 - 2
const zhangsan = {
name: '张三',
sayHi() {
console.log(this)
},
wait() {
setTimeout(function() {
console.log(this)
})
},
waitAgain() {
setTimeout(() => {
console.log(this)
})
}
}
zhangsan.sayHi() // 打印什么
zhangsan.wait() // 打印什么
zhangsan.waitAgain() // 打印什么
// this 场景题 - 2
class People {
constructor(name) {
this.name = name
this.age = 20
}
sayHi() {
console.log(this)
}
}
const zhangsan = new People('张三')
zhangsan.sayHi() // 打印什么
this的指向不用特意去死记硬背 抓准核心点:看调用对象
题目解答
this 的不同应用场景
- 作为普通函数调用 -> 一般指向函数调用者
- 使用
callapplybind-> 改变this指向 - 作为对象方法调用 -> 一般指向调用对象
- 在 class 的方法中调用 -> 一般指向实例
- 箭头函数 -> 指向箭头函数定义作用域
创建 10 个<a>标签,点击的时候弹出来对应的序号
js
// 创建 10 个`<a>`标签,点击的时候弹出来对应的序号
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)
}
经典作用域问题,解决方案:
- 使用let定义i 形成局部作用域 限制自由变量的查找层级
- 通过闭包
实际开发中闭包的应用
参考之前的示例。再次强调闭包中自由变量的寻找方式!!!
js
// 隐藏数据,只提供 API
function createCache() {
let data = {} // 闭包中的数据,被隐藏,不被外界访问
return {
set: function (key, val) {
data[key] = val
},
get: function (key) {
return data[key]
}
}
}
let c = createCache()
c.set('a', 100)
console.log( c.get('a') )
手写 bind 函数
先回顾 bind 函数的应用
js
function fn1(a, b) {
console.log('this', this)
console.log(a, b)
}
const fn2 = fn1.bind({ x: 100 }, 10, 20) // bind 返回一个函数,并会绑定上 this
fn2()
手写 bind
js
Function.prototype.bind1 = function () {
// 将参数解析为数组
const args = Array.prototype.slice.call(arguments)
// 获取 this(取出数组第一项,数组剩余的就是传递的参数)
const t = args.shift()
const self = this // 当前函数
// 返回一个函数
return function () {
// 执行原函数,并返回结果
return self.apply(t, args)
}
}