你不可不知道的JavaScript作用域和闭包

时间:2017-09-13 15:37:58 点击: 0 评论:0 作者:匿名 来源:1299R

web设计为您整理

原文出处:JavaScript Scope and Closures

传送门:https://css-tricks.com/javascript-scope-closures/

作用域和闭包是 JavaScript 中重要的部分,但是当我开始学习时遇到了很多的困惑。这里就是一篇关于作用域和闭包的文章,能够帮助你理解它们。

让我们先从作用域开始

作用域

JavaScript 作用域指定了哪些变量你能够访问。有两种作用域--全局作用域和局部作用域

全局作用域

如果一个变量在函数外面或者大括号({})外申明,那么就是定义了一个全局作用域的变量。

这个只是对于浏览器中的 JavaScript 来说,你在 Node.js 中申明的全局变量是不同的,但是我们在这片文章中不涉及 Node.js。

const globalVariable = 'some value'

一旦你申明了全局变量,那么你可以在任何地方使用它,甚至在函数中也行。

const hello = 'Hello CSS-Tricks Reader!'
function sayHello () {
  console.log(hello)
}
console.log(hello) // 'Hello CSS-Tricks Reader!'
sayHello() // 'Hello CSS-Tricks Reader!'

虽然你能够在全局作用域中申明函数,但是不建议这么做。因为这可能会和其他的的变量名冲突。如果你使用 const 或者 let 申明变量,你将在命名冲突时收到一个错误的信息,这是不值得的。

// Don't do this!
let thing = 'something'
let thing = 'something else' // Error, thing has already been declared

如果你使用 var 申明变量,你的第二个申明的同样的变量将覆盖前面的。这样会使你的代码很难调试。

// Don't do this!
var thing = 'something'
var thing = 'something else' // perhaps somewhere totally different in your 
codeconsole.log(thing) // 'something else'

所以,你应该使用局部变量,而不是全局变量。

局部作用域

在你代码特定范围之内申明的变量可以称为处于局部作用域中,这些变量也被称为局部作用域。

在 JavaScript 中,有两种局部作用于:函数作用域和块作用域。

让我们先说说函数作用域

函数作用域

当你在函数中申明一个变量,你就只能够在这个函数范围内使用它。在范围之外你不能使用。

在这个例子中,变量 hello 在 sayHello 作用域中。

function sayHello () {  
    const hello = 'Hello CSS-Tricks Reader!'
    console.log(hello)
}
sayHello() // 'Hello CSS-Tricks Reader!'
console.log(hello) // Error, hello is not defined
块作用域

当你在一个大括号中({})使用 const 或者 let 申明变量,那么这个变量只能够在这个大括号范围内使用。

在这个例子中,变量 hello 就在大括号范围中。

{  
    const hello = 'Hello CSS-Tricks Reader!'
    console.log(hello) // 'Hello CSS-Tricks Reader!'
}
console.log(hello) // Error, hello is not defined

块作用域是函数作用域的一个子集,因为函数需要用花括号声明。

函数提升和作用域

当你申明一个函数时,它总是会提升到作用域顶部。这两种写法是相等的。

// This is the same as the one below
sayHello()function sayHello () {
  console.log('Hello CSS-Tricks Reader!')
}// This is the same as the code abovefunction sayHello () {
  console.log('Hello CSS-Tricks Reader!')
}
sayHello()

当申明一个函数表达式时,函数不会提升到作用域顶部。

sayHello() // Error, sayHello is not defined
const sayHello = function () {
  console.log(aFunction)
}

函数不能相互调用各自的作用域

当你定义函数时,他们不能够相互使用各自的作用域,虽然它们可以互相调用。

在这个例子中,second 不能够使用 firstFunctionVariable 变量。

function first () {  
const firstFunctionVariable = `I'm part of first`
}
function second () {
  first()  
  console.log(firstFunctionVariable) // Error, firstFunctionVariable is not defined
}

嵌套作用域

当一个函数在另一个函数内定义,内部的函数能够访问外部函数的变量。我们称之为词法作用域。

然而,外部的函数不能够访问内部函数的变量。

function outerFunction () {
  const outer = `I'm the outer function!`

  function innerFunction() {
    const inner = `I'm the inner function!`
    console.log(outer) // I'm the outer function!
  }  
  console.log(inner) // Error, inner is not defined
  }

这个图片介绍了它是如何工作的,你能够想象一面单面镜。你能够看见外面的人,外面的人却无法看见你。

嵌套作用域

如果你遇见了嵌套作用域,可以理解成多层单面玻璃。

嵌套作用域

当你彻底理解作用域之后,你才能够进一步理解闭包的原理。

闭包

当你在一个函数内部创建一个函数时,你就创建了一个闭包。内部函数就是闭包。这个闭包总是会 return 出来,所以你能够稍后使用外部函数中的变量。

function outerFunction () {
  const outer = `I see the outer variable!`  
  function innerFunction() {
    console.log(outer)
  }  
  return innerFunction
}
outerFunction()() // I see the outer variable!

当内部函数需要 return 时,你可以直接 reutrn 函数声明,这样的代码更加的精练。

function outerFunction () {
  const outer = `I see the outer variable!`  
  return function innerFunction() {
    console.log(outer)
  }
}
outerFunction()() // I see the outer variable!

因为闭包允许变量来自外部的函数,他们通常被用来

1.控制副作用

2.创建私有变量

用闭包控制副作用

当你从一个函数中返回一个值时会产生副作用。很多事情都会有副作用,比如 Ajax 请求,timeout 或者一个 console.log。

function (x) {
  console.log('A console.log is a side effect!')
}

当你使用闭包来解决副作用时,你通常会关心这样弄乱你代码,像 Ajax 或者 timeouts。

让我们通过一个例子来理清这些。

比如你想要为你给你朋友的生日制作一个蛋糕。这个蛋糕需要一秒钟制作完成,所以你写了一个函数,在一秒后打印出 made a cake。

我使用 ES6 的箭头函数来使得例子更加的简短和容易理解

function makeCake() {
  setTimeout(_ => console.log(`Made a cake`), 1000)
  )
}

正如你所看见的,这个“制作蛋糕”的函数有一个副作用:延迟。

更进一步,你想要你的朋友选择一个蛋糕口味,你能加一个口味到你的 makeCake 函数。

function makeCake(flavor) {
  setTimeout(_ => console.log(`Made a ${flavor} cake!`), 1000))
}

当你运行这个函数时,提示蛋糕在一秒钟后立即制成。

makeCake('banana')
// Made a banana cake!

这个问题是你不想要在知道口味之后立即制作蛋糕,而是在正确的时间之后再制作。

为了解决这个额问题,你能写一个 prepareCake 函数存储你的口味。然后,在 prepareCake 中 return makeCake 函数。

使用这个方法,你能够在任何时候调用 return 的函数,蛋糕会在一秒钟之后制作。

function prepareCake (flavor) {
  return function () {
    setTimeout(_ => console.log(`Made a ${flavor} cake!`, 1000))
  }
}
const makeCakeLater = prepareCake('banana')
// And later in your code...
makeCakeLater()
// Made a banana cake!

这就是闭包被用来减少副作用的 —— 在你想要的时候通过创建一个函数来激活内部的闭包。

闭包中的私有变量

正如你现在所知道的,在函数内部创建的变量不能够被外部的函数访问。正因为他们不能够被外部函数访问,所以称之为私有变量。

然后,有时候你需要在函数外部访问私有变量,你能够使用闭包来实现。

function secret (secretCode) {
  return {
    saySecretCode () {
      console.log(secretCode)
    }
  }
}
const theSecret = secret('CSS Tricks is amazing')
theSecret.saySecretCode()// 'CSS Tricks is amazing'

saySecretCode

saySecretCode 在这个例子中是将 secretCode 暴露给外层的 secret 的唯一函数(闭包)。像这样也被称之为特权函数

使用 DevTools 调试作用域

Chrome 和 Firefox 的 DevTools 使得调试当前作用域中的变量变得简单。这里有两种方式使用这个功能。

第一种方式是在代码中添加 debugger,JavaScript 解释器遇见 debugger 会在浏览器中暂停,这样你就能够调试。

这里是一个 prepareCake 的例子:

function prepareCake (flavor) {
  // Adding debugger
  debugger  
  return function () {
    setTimeout(_ => console.log(`Made a ${flavor} cake!`, 1000))
  }
}
const makeCakeLater = prepareCake('banana')

如果你在 Chrome 中打开 DevTools 然后找到 Sources 选项(或者 Firefox 中的 Debugger 选项),你就能看到可用的变量。

变量

你也能把 debugger 放在闭包中,注意这个时候局部变量是怎么变化的。

function prepareCake (flavor) {
  return function () {
    // Adding debugger
    debugger
    setTimeout(_ => console.log(`Made a ${flavor} cake!`, 1000))
  }
}
const makeCakeLater = prepareCake('banana')

局部变量

第二种方式是在 sources (或者 debugger) 选项中点击行数使用 debugging 功能来直接在你的代码中添加断点。

添加断点

结束语

作用域和闭包并不是非常的难理解。一旦你真正理解了其中的原理,他们就变得非常的简单了。

当你在函数中声明一个变量,你只能够在该函数中使用它。这些变量就被限制在了函数范围之内。

如果你在其他函数中定义一个内部函数,这个内部函数被称之为闭包。它保留了在外部函数中声明的变量。

期待你提出问题,我将尽我所能的回复你。

作者博客传送门:https://zellwk.com/blog

作者免费课程传送门:https://jsroadmap.com

济南SEO,网站建设就找小纯洁

评论

"你不可不知道的JavaScript作用域和闭包"的0条评论

点击刷新