this指针/闭包/作用域
一、作用域
1.基本概念
在特定的场景下,特定范围内,查找变量的一套规则。广泛的来说,我们都指的是:词法作用域,静态作用域。
- 分类
- 全局作用域
- 函数作用域 在函数内部生命的所有变量,在函数范围内持续可用。
- 块作用域 es6的概念,是一个用来对之前的最小授权原则进行扩展的工具包,将代码在函数中隐藏信息扩展为块中。
function showResult(a) {
let e = 3
console.log(a + e)
console.log(a + b)
console.log(a + d)
console.log(a + c)
var b = 3
let c = 5
}
showResult(2)
结果:
5
NaN
//d is not defined
// console.log(a + c)报错 cannot access 'c' before initailization
分析:
1.e属于函数作用域,正常取值正常打印
2.b使用var声明,存在变量提升,在预编译阶段给函数头部先声明了一个var b,但是没有赋值,所以相当于b为undefined,按照顺序执行,结果为NaN
3.d 取值是发现未定义
4.c使用let声明,会报错。在console.log(a + c) 和 let c = 5 形成一个暂时性死区,这里c不能使用
暂时性死区:let 是与块级作用域,所以let会绑定这整个区域,让let声明的对象无法再进行其他声明。
函数参数中也是默认使用let声明,所以函数的参数使用默认值也会出现暂时性死区
-
能够成块级作用域的用法
- if
- for
- {...}
2.函数表达式
在js的预编译阶段,对变量进行内存分配->变量提升,值为undefined -> 对所有的非表达式的声明进行提升
var name = function () {
console.log('name1')
}
function name() {
console.log('name2')
}
name()
结果:name1
分析:
在预编译阶段时候会var name = undefined和function name提升到顶部,然后给name进行赋值function,函数的提升的优先级要更高,在编译的执行顺序变成:
function name() {
console.log('name2')
}
var name = undefined
name = function () {
console.log('name1')
}
3.执行上下文
根据代码编写的逻辑,作用域是一层套一层的,变量也是往上找,就是我们所谓的词法作用域,它关注的是函数在何处声明。
上下文关注函数在哪里调用。
二、闭包
函数嵌套函数时,内部函数可以访问外部函数作用域的变量,且内部函数可以在全局环境下可访问,形成了一段不被销毁的代码空间,就形成了闭包。
当函数的执行
上下文没有在原本的词法作用域内,就形成了闭包。
function fnGenerator(){
let num = 1
return () => {
console.log(num ++ )
}
}
var fn = fnGenerator()
常见的闭包场景:
- 函数式编程
- 浏览器中window下一些event 和 handler
- setTimeOut 也会形成闭包
- 发布-订阅
三、this
this的指向是根据执行上下文动态决定的。
- 简单的指向:匿名函数的this一般指向
顶级作用域,window或者Global 对象调用时,绑定在对象上- call/apply/bind:,绑定在指定的参数上
new关键字,利用构造函数绑定在新创建的对象上- 箭头函数,根据外层的规则决定this的指向
优先级;new > call/apply/bind > 对象 例题:
var number = 5;
var obj = {
number:3,
fn1:(function(){
var number;
this.number *=2;
number = number *2
number = 3
return function(){
var num = this.number
this.number = 2
console.log(num)
number *= 3
console.log(number)
}
})()
}
var fn1 = obj.fn1
fn1.call(null)
obj.fn1()
console.log(window.number)
打印结果:10 9 3 27 2
分析:
1.当执行var fn1 = obj.fn1时,由于obj.fn1是自执行函数,所以作用域指向顶级作用域。此时代码执行的片段如下:
自执行函数的this指向window
var number; //number为undefined
this.number *=2; //this.number 指的是window的number 所以 Window.number = 10
number = number *2
number = 3
return function(){ //将函数体返回给obj.fn1。同时赋给fn1,形成闭包。
var num = this.number
this.number = 2
console.log(num)
number *= 3
console.log(number)
}
2.此时fn1已经接受了上一步的函数体,执行函数如下:
fn1.call(null) 相当于直接执行fn1()
var num = this.number // this.number = 10 -> num = 10
this.number = 2 //此处又将window.number 变为 2
console.log(num) // 打印结果:10
number *= 3 //因为是闭包,所以作用域向上找。number = 3
console.log(number) //打印结果:9
3.obj.fn1() 也是看function(){...}代码段,分析如下:
var num = this.number //this指向obj,所以 num = obj.number = 3
this.number = 2
console.log(num) //打印结果:3
number *= 3 //因为是闭包,所以作用域向上找。上一次执行number已经改变 -> 变成9
console.log(number) //打印结果:27
4.console.log(window.number),在步骤2时已经改变number值,这里打印为2。
但是如果这里将function变成箭头函数:
var number = 5;
var obj = {
number:3,
fn1:(() => {
var number;
this.number *=2;
number = number *2
number = 3
return () => {
var num = this.number
this.number = 2
console.log(num)
number *= 3
console.log(number)
}
})()
}
var fn1 = obj.fn1
fn1.call(null)
obj.fn1()
console.log(window.number)
打印结果就会变成:10 9 2 27 2
这里主要是因为执行obj.fn1()时函数变为了箭头函数,这里的执行上下文变成了全局,所以这里的this指向就变成window,所以num = window.number = 2
如果将fn1 和 obj.fn1 的this指向换一个对象
var number = 5;
var obj = {
number:3,
fn1:(function() {
var number;
this.number *=2;
number = number *2
number = 3
return function() {
var num = this.number
this.number *= 2
console.log(num)
number *= 3
console.log(number)
}
})()
}
var obj2 = {
number:108
}
var fn1 = obj.fn1
fn1.call(obj2)
obj.fn1.call(obj2)
console.log(window.number)
打印结果: 108 9 216 27 10
思考:什么场景下需要考虑
this的指向问题?
- 函数式编程
- 柯里化
- 闭包
- 防抖节流