如何用一句话解释javascript闭包问题(面试经典题)

如何用一句话解释javascript闭包问题

什么是闭包?  

个人的解释是函数内部再return一个函数或者内部函数要访问他的外部变量

在学闭包之前必须先了解 变量的作用域

①函数内部可以直接读取全局变量

example:

var num = 123;
(() => console.log(num))()
// 123

log的结果num为123

② 在函数外部无法读取函数内部的局部变量

(ps函数内部定义的变量必须要用var 等来声明,否则执行js的时候被视为全局变量)
example:

(()=>{
  var n = 999
})()

console.log(n) // error

因为我们无法直接访问函数内部的变量的值,所以要通过闭包来读取函数局部变量的值
example:

(()=> {
    var a = 123
    return ()=> a 
})()
// 或者没有return
((time)=> {
  var a = 123
  setTimeout(()=> {
    // 处理逻辑...
  }, time)
})()

闭包作用?
① 访问函数内部变量的值(上面说了)
② 让闭包的值始终保存在内存中
example:

var counter = (()=>{
  var num = 999
  function  changeBy(val){
    num += val
  }
  return {
     increment: () =>{ changeBy(1) },
     decrement: () =>{ changeBy(-1) },
     result: () =>{ return  num }
  }
})()


> console.log( counter.result() )   // 999 //接下来我们改变下局部变量num值
> counter.increment()   //num + 1 //再打印num的值,已经发生改变
> console.log(counter.result())  // 1000 counter.decrement()
> counter.decrement() console.log(counter.result())   // 998

在counter中,changeby被counter.increment, counter.decrement, counter.result所共享,在这个匿名自调用函数下的changeBy和num都无法被外部所访问,必须通过匿名函数返回的三个公共函数来访问。

每次声明变量的时候都会创建一个新的函数储存空间

var counter = function() {
  var num = 999
  function changeBy(val) {
    num += val
  }
  return {
    increment: () =>{ changeBy(1) },
    decrement: () =>{ changeBy(-1) },
    result: () =>{ return num }
  }
}
var n1 = counter()
var n2 = counter()
n1.result() // num = 999
n1.increment() //num + 1
// 此时再打印,num已经改变
n1.result() // num = 1000
n2.result() // num = 999

数内部私有上述代码中分别用n1,n2接收了counter函数的返回值,但是n1和n2他们各自都是独立的,每个闭包都是引用自己词法作用域内的变量 num。

闭包内的this指向问题
example:

var name = "The Window";
var object = {
  name : "My Object",
  getNameFunc : function(){
    return function(){
      //此时的this指向全局Window
      return this.name 
    }
  }
}
alert(object.getNameFunc()()) // "The Window"

如果想要this的指向当前函数,在函数内部定义私有变量
example:

var name = "The Window";
var object = {
  name : "My Object",
  getNameFunc : function(){
    // 因为函数内部私有变量外部无法读取
    // 此时的this指向object
    var that = this
    return function(){
      return that.name
    }
  }
}
alert(object.getNameFunc()()) // "My Object"

#最后分享一个闭包案例:

Helpful notes will appear here

E-mail:

Name:

Age:

(ps样式就不写了,主要是看闭包运行机制)
javascript逻辑部分:

function showHelp(help) {
  document.getElementById('help').innerHTML = help
}
function setupHelp() {
  var helpText = [
    {'id': 'email', 'help': 'Your e-mail address'},
    {'id': 'name', 'help': 'Your full name'},
    {'id': 'age', 'help': 'Your age (you must be over 16)'}
  ]
  for (var i = 0; i < helpText.length; i++) {
    var item = helpText[i]
    document.getElementById(item.id).onfocus =
        function() {
      showHelp(item.help)
    }
  }
setupHelp()

循环helpText数组获取对应的每一项的‘

’元素并注册onfocus事件处理函数,让最上面的

提示框写入对应的help提示内容
逻辑看起来没啥问题,运行这段代码之后,无论鼠标焦点在哪个input上, 最上面的

元素始终显示的是数组的最后一项的‘help’信息,并没有达到想要的效果。
原因是赋值给onfocus的是闭包。用我自己的话讲就是setupHelp函数下面还有个onfocus事件处理函数形成了闭包,在setupHelp函数域中存在一个变量item,在js执行机制中,循环在 onfocus事件触发之前早已执行完成,item的值已被锁定在循环对象的最后一项。

es6箭头函数=>完美解决闭包这个问题:

看代码=>

function showHelp(help) {
  document.getElementById('help').innerHTML = help;
}
(() => {
  var helpText = [
    {'id': 'email', 'help': 'Your e-mail address'},
    {'id': 'name', 'help': 'Your full name'},
    {'id': 'age', 'help': 'Your age (you must be over 16)'}
  ]
    helpText.map(a => {
    return document.getElementById(a.id).onfocus = function() {
      showHelp(a.help)
    }
  })
})()

map循环使用匿名箭头函数就不存在闭包问题了。
解决这个闭包问题另一种方案就是使用更多的闭包:
example:

function showHelp(help) {
  document.getElementById('help').innerHTML = help
}

function makeHelpCallback(help) {
  return function() {
    showHelp(help)
  };
}

function setupHelp() {
  var helpText = [
    {'id': 'email', 'help': 'Your e-mail address'},
    {'id': 'name', 'help': 'Your full name'},
    {'id': 'age', 'help': 'Your age (you must be over 16)'}
  ]

for (var i = 0; i < helpText.length; i++) {
    var item = helpText[i]
    document.getElementById(item.id).onfocus = makeHelpCallback(item.help)
  }
}

setupHelp()

makeHelpCallback函数为后面的每个回调提供了一个新的执行环境(不再处于setupHelp同一个函数的作用域内),help的此时指向helpText数组中所对应的值

还可以匿名函数闭包:把函数内部私有变量和闭包内的回调放在同一作用域

function showHelp(help) {
  document.getElementById('help').innerHTML = help;
}
(() => {
  var helpText = [
    {'id': 'email', 'help': 'Your e-mail address'},
    {'id': 'name', 'help': 'Your full name'},
    {'id': 'age', 'help': 'Your age (you must be over 16)'}
  ]
  for (var i = 0; i < helpText.length; i++) {
    // document.getElementById(item.id).onfocus = callbackHelp(item.help)
    (()=> {
      var item = helpText[i]
      document.getElementById(item.id).onfocus = () => {
        showHelp(item.help)
      }
    })()
  }
 })()

性能问题
总结一点,为了没必要的使用闭包,函数内的私有变量用let关键词来代替var
如果不是某些特定任务需要使用闭包, 在其它函数中创建函数是不明智的,因为闭包在处理速度和内存消耗方面对脚本性能具有负面影响。
为了避免定义没有好处的闭包,在声明函数内部定义函数的时候,可以通过函数的prototype原型上添加事件处理函数,直接上代码吧:
example:

function MyObject(name, message) {
  this.name = name.toString()
  this.message = message.toString()
  this.getName = function() {
    return this.name
  }
  this.getMessage = function() {
    return this.message
  }
}

在上面代码中,this.getName,this.getMessage两个没有必要的闭包,
为了性能不再使用无用的闭包可以修改成:

function MyObject(name, message) {
  this.name = name.toString()
  this.message = message.toString()
}
MyObject.prototype.getName = function() {
  return this.name
}
MyObject.prototype.getMessage = function() {
  return this.message
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值