作用域和闭包
文章目录
一、this 的不同应用场景,如何取值
answer:
- 在箭头函数调用
- 作为普通函数调用
- 在class中调用
- 在call、apply、bind中调用
- 对象方法调用
二、手写bind函数
调用bind函数会返回一个新的函数
answer:
// 模拟 bind,这个与写jQuery的插件一样(jQuery.prototype.dialog=function(info){})
Function.prototype.bind1 = function () {
//因为我们不知道有多少个参数传进来,所以我们把传入的参数变成数组,而传入的参数是列表形式的
// 将参数拆解为数组,列表变数组
const args = Array.prototype.slice.call(arguments)
// 获取 this(数组第一项)
const t = args.shift()
// this表示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) //第2-4参数
return 'this is fn1'
}
//第一个参数是this,第二个是a,第三个是b,第四个是c
const fn2 = fn1.bind1({x: 100}, 10, 20, 30)
const res = fn2()
console.log(res)
注释:
三、实际开发中的闭包应用场景,举例说明
answer
不通过set或者get没有办法赋值或者获取什么,例如data,若没有set无法给data赋值,没有get无法获取data的内容
// 闭包隐藏数据,只提供 API
function createCache() {
const data = {} // 闭包中的数据,被隐藏,不被外界访问
return {
set: function (key, val) {
data[key] = val
},
get: function (key) {
return data[key]
}
}
}
const c = createCache()
c.set('a', 100)
console.log( c.get('a') )
四、JS创建10个<a>标签,点击的时候弹出对应的序号
answer
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)
}
注释:
i:不可以和a一起定义,因为:
当i与a一起定义为全局变量时,/标签创建是十几秒就会创建完成,此时i已经成为10了,而我们还没有点击标签,所以当我们点击标签的时候弹出的都是10;
所以:i不可以是全局变量,只能定义成块级作用域,每次点击的时候,i只能从块级中找,超出块的范围是无效的
五、作用域和自由变量(knowledge point)
1、作用域
①、全局作用域
就是不会受到什么约束,window对象,document对象都可以使用
②、函数作用域
在一个函数中定义,只能在函数中使用,超出函数就不可以使用
③、块级作用域(ES6新增)
就是在if、for、while等这些语句后面的{ }里定义变量,该变量只能在这个块里面使用,超出范围就报错
2、自由变量
- 一个变量在当前作用域没有定义,但被使用了
- 此时要向上级作用域,一层一层依次寻找,直至找到为止(感觉很像英语的就近原则!!)
- 如果到全局作用域都没有找到,则会报错xx is not defined
六、闭包
自由变量的查找,是在函数定义的地方,向上级作用域查找
作用域应用的特殊情况,有两种表现:
1. 函数作为参数被传递
函数在此处定义好后,被当成参数传到另一地方使用
函数作为返回值
function create() {
const a = 100
return function () {
console.log(a)
}
}
const fn = create()
const a = 200 //这个和上面的a=100是不一样的
fn() // 100
注释:
fn()中的a,是个变量,按照作用域的范围一层一层网上找
2. 函数作为返回值被返回
函数在此处定义好后,被返回到另一个地方执行
// 函数作为参数被传递
function print(fn) {
const a = 200
fn()
}
const a = 100
function fn() {
console.log(a)
}
print(fn) // 100
注释:
print(fn):这个fn就是上面的fn()函数
fn()中的a是一个变量,要从作用域角度找,fn中没有,则到上一级,就是a=100中找到
七、this
this取什么值,是在函数执行的时候确定的,而不是在定义的时确认的
1. 作为普通函数被调用
直接返回window
2. 使用call 、 apply 、 bind 被调用
传入什么就绑定什么
3. 作为对象方法被调用
返回对象本身
4. 在class 方法中调用
返回实例本身
5. 箭头函数
返回上级作用域的本身
//1. 作为普通函数被调用
function fn1(){
console.log(this)
}
fn1() //window
// 2. 使用call 、 apply 、 bind 被调用
fn1.call({x:100}) //{x:100}
const fn2 = fn1.bind({x:222})
fn2() //{x:222}
// 3. 作为对象方法被调用
const zhangsan = {
name : "zhangsanOk",
sayHi(){
//this 就是当前对象
console.log(this)
},
wait(){
setTimeout(function(){
//this === window
//这个setTimeout不是zhangsan.sayHi()这种执行的,而是setTimeout本身触发执行的
console.log(this)
})
}
// 4. 箭头函数
waitAgain(){
setTimeout(()=>{
//this 就是当前对象
console.log(this)
})
}
//5、在class 方法中调用
class People{
constructor(name){
this.name = name
this.age = 20
}
sayHi(){
console.log(this)
}
}
const zhang = new People('张三')
zhang.sayHi() //zhang对象
}
注释:
bind:会返回一个新的函数来执行,例如上面的,fn1.bind({x:222})会返回一个新的fn1函数
箭头函数:箭头函数只会调用上级作用域所指的this,本身的this不会调用,这就是wait()与waitAgain()的区别
call()和apply():都是在特定的作用域中调用函数,实际上等于设置函数体内this对象的值。apply和call方法的第一个参数都是特定的作用域第二个参数不同,apply第二个参数可以是Array的实例,也可以是arguments对象。call方法需要逐个列出需要传递的参数。
联系xialuo那个例子:(this 的区别)
为什么两个不一样的输出?
因为:xialuo.sayHi():对象是xialuo
xialuo.proto.sayHi():对象是xialuo.proto
所以我们执行xialuo.sayHi():时,有点类似于再次绑定(如下)