JS之this、作用域与闭包
this
this:当前函数的执行环境
当一个函数被调用时,JS引擎会为当前函数创建一个执行环境,用来存储函数内部的变量和对象等,this就用来指代当前执行环境的上下文
与this相关的关键字:call/apply/bind,它们可以用来改变函数的执行环境,实现函数的高阶调用、绑定函数上下文等
this永远指向一个对象,this的指向取决于函数的调用位置,或通过call/apply/bind改变
context(上下文环境,webpack钩子,compiler对象,compilation对象始终贯穿于整个钩子的)
全局作用域下的this指向
全局作用域:没有包含在任何函数中的代码块
// 1. 全局作用域下的this
console.log(this) //window
// 打印宿主环境根对象,浏览器:Window, node:global
function test(){
console.log(this)
}
test() // window
结论:this只会指向对象,不会指向其他的数据类型
function fun(){
console.log(this)
}
var obj = {
s: "a",
f: fun
}
var s = "b"
obj.f() // obj
fun() // window
// 对象初始化时,会绑定this
结论:this的指向和调用有关和定义无关
对象方法中的this指向
var name = 'window'
const obj2 = {
name: "obj2",
fun: function(){
console.log('fun',this.name) // obj2
function fun2() {
console.log('fun2',this.name) // window
}
fun2()
// 通过箭头函数可以解决this指向问题
const fun3 = () => {
console.log('fun3', this.name) // obj2
}
fun3()
}
}
obj2.fun()
对象的方法会进行this自动绑定,这并不代表方法的内部函数也会自动绑定this
构造函数中的this指向
function Person(name){ // 构造器Person
this.name = name
this.sayName = function (){
console.log(this.name) // p1
}
}
const p1 = new Person('p1')
p1.sayName()
call/apply方法中的this指向
函数对象具有call和apply方法,调用call和apply方法时,this指向当前函数对象
call和apply方法允许开发者在调用方法时自定义this
let obj1 = { obj: obj2}
let obj2 = { fun() { console.log(this) }}
obj2.fun.call(obj1) // this是obj1
obj2.fun.call(obj1.obj) //this 是obj2
实现call
Function.prototype.selfCall = function (context, ...params){
context== null ? context = window : null
// context 为空则赋值window,否则不做操作
!/^(object|funciton)$/.test(typeof context)?context = Object(context) :null
// 判断context参数的数据类型,不为对象或函数,则强制转换
let _this = this,result = null,UNIQUE_KEY = Symbol('UNIQUE_KEY')
// 此时的this指向调用selfCall方法的函数对象
context[UNIQUE_KEY] = _this
result = context[UNIQUE_KEY](...params)
// 为context增加一个方法,并调用
// 调用该方法中this指向context
delete context[UNIQUE_KEY]
return result
}
注意:undefined == null
是true
实现bind
Function.prototype.selfBind = function (context, ...outParams){
let _this = this
// this指向调用selfBind方法的函数对象
return function(...innerParams) {
_this.selfCall(context, ...outParams.concat(...innerParams))
}
}
bind 和call、apply的区别: bind不会立即执行,而是返回一个新函数
如果需要改变函数中的this指向 就不能使用箭头函数
const say = () => {
console.log(this)
}
const obj = {}
say.call(obj) // window
箭头函数中的this指向
箭头函数中的this指向调用该函数的上下文环境(即当前作用域所属的对象)
let obj = {
name: 'lily',
fun: function(){
setTimeout(() => console.log(this.name), 100)
}
}
obj.fun() // lily
函数作为参数和返回值时的this指向
链式调用时常用
事件绑定中的this指向
函数中的 this 指向通常是绑定事件的元素
总结
情况 | this值 |
---|---|
以函数形式调用时(隐式绑定) | 永远都是根对象 |
以方法形式调用时(隐式绑定) | 调用方法的对象 |
以构造函数形式调用时(隐式绑定) | 新创建的对象 |
用call、apply、bind调用时(显示绑定) | 传参指定的那个对象 |
作用域与闭包
作用域
JS中作用域分为两种:词法作用域和动态作用域
词法作用域: 变量只能在定义它的代码块中使用,一旦离开该代码块,变量就会消失
动态作用域: 变量可以在函数内部被重新定义,并可以在不同的函数调用之间共享
闭包
闭包: 一个函数能够访问并操作其创建时所在词法作用域中的变量的能力;闭包就是能够将变量保存到函数外部的一种机制
创建方式:
使用嵌套函数:用于实现模块化编程、封装私有属性
使用call/apply/bind:用来改变函数执行环境,实现高阶调用
使用场景: 实现私有属性,缓存计算结果,模拟事件处理程序
缺点: 容易导致内存泄露,难以维护
var boxs = document.querySelectorAll('.box')
var boxDoms = Array.prototype.slice.call(boxs)
function $(selector){
const dom = document.querySelector(selector)
return{
css:function (styleObj){
Object.entries(styleObj).forEach(([key,value]) =>{
dom.style[key] = value
})
return this
},
animate: function(){}
}
}
$('.box').css({
color: 'red'
})
for(var i = 0; i< boxDoms.length;i++){
const dom = boxDoms[i]
setTimeout(function(){
console.log(i)
dom.addEventListener('click',function(){
console.log(i)
})
},1000)
}
boxDoms.forEach(function(dom,i){
setTimeout(()=>{
console.log(i)
dom.addEventListener('click',()=>{
console.log(i)
})
},1000)
})
闭包的具体使用场景:
1.函数柯里化
2.单模式处理、工厂模式处理
3.react hooks
函数柯里化的实现
function curry(fn){
// 在没有默认值的时候,fn.length指的是形参的个数,如果参数有默认值,那么就取第一个具有默认值之前的参数个数
const arity = fn.length
return function curried(...args) {
console.log(this)
if(args.length >= arity) {
return fn.apply(this, args)
} else {
return function (...moreArgs) {
return curried.apply(this, args.concat(moreArgs))
}
}
}
}
compose函数实现
function compose(...funcs){
if(funcs.length === 0) {
return (arg) => arg
}
if(funcs.length === 1) {
return funcs[0]
}
return funcs.reduce(
(a,b) =>
(...arg) => {
return a(b(...arg))
}
)
}