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的使用和取值

发布了16 篇原创文章 · 获赞 36 · 访问量 2385

猜你喜欢

转载自blog.csdn.net/m0_46269977/article/details/105616595
今日推荐