什么是JavaScript关闭和JavaScript范围
#javascript #网络开发人员 #初学者 #关闭

在这篇文章中,我解释了JavaScript封闭方式的工作原理以及JavaScript范围是什么意思。最后,我还将举一些JavaScript关闭的示例。这最初是写为blog post on my personal site

JavaScript中的范围是什么?

要获得JavaScript关闭和JavaScript范围的工作方式,我们需要了解范围的含义。范围是一组规则,告诉您可以从哪里访问变量和功能。

在何处设置变量并定义功能将影响您可以从哪里访问它们。这是JavaScript中的一个示例。

const firstVariable = 1;
]
function firstFunction() {
    return 2;
}

function anotherFunction() {    
    const secondVariable = 2;

    function secondFunction() {
        return 3;
    }
}


// will log 1 2
console.log(firstVariable, firstFunction()) 

// Uncaught ReferenceError: secondVariable is not defined
console.log(secondVariable, secondFunction()) 

那么,这里有什么交易?为什么我们可以访问第一变量和第一个功能,而不能访问第二个变量和第二函数?这是因为JavaScript范围的工作原理。

范围如何在JavaScript中工作

JavaScript仅具有函数范围(有一些例外),直到ES6出来。这意味着每当您定义新功能时,都会创建一个新的范围。在函数外部无法访问函数中定义的变量。这就是为什么我们在尝试记录第二变量值并调用第二功能时会出现错误的原因。

const firstVariable = 1;
]
function firstFunction() {
    return 2;
}

function anotherFunction() {    
    const secondVariable = 2;

    function secondFunction() {
        return 3;
    }
}


// will log 1 2
console.log(firstVariable, firstFunction()) 

// Uncaught ReferenceError: secondVariable is not defined
console.log(secondVariable, secondFunction()) 

JavaScript范围是嵌套的

内部范围可以访问外部范围,但是外部范围无法访问内部范围中设置的变量。

function outerFunction() {
  let i = 777;
  console.log(i); // 777
  console.log(b); // Refference Error (outer scope can't access variables in inner scope)

  function innerFunction() {
    console.log(i); // 777 (inner scope can access the enclosing outer scope)
    let b = "hello";
  }

  innerFunction();
}
outerFunction();
Scope consists of a series of bubbles that each act as a container or bucket in which identifiers are declared , these bubbles nest inside each other and this nesting is defined at author time.
Kyle Simpson (Author of YDKJS series)

//global scope
var i = 666;
console.log(i); // 666

function foo() {
  // scope of foo
  var i = 777;
  console.log(i); // 777
}

foo();

JavaScript范围如何在ES6中工作

es6引入了两种新方法来使用LET和CONS来声明变量,该方法可用于块范围。每当您使用LET或CONST声明变量时,它们都会范围范围(任何由一对卷曲括号{}包围的代码)。 ES6还引入了另一个带有模块的范围,但封闭也以相同的方式在模块中工作。

//global scope
var i = 666;
console.log(i); // 666

{
  // new block scope
  // you'll probably see these types of blocks used with
  // if,else,for,while,etc rather that used like this
  let i = 777;
  console.log(i); // 777
}
note: You might have heard the term lexical scope in JavaScript which is just a technical term for scope that is defined during the code compilation process called lexing. It basically means that scope is defined when you are writing the code (declaring functions, or blocks), instead of when it is actually executed.

什么是关闭?

封闭是一种编程技术,用于允许函数访问在其之外定义的值。它是将其外部变量捆绑在一起的函数的组合。

当功能访问其外部定义的变量时,您将闭合。

什么是JavaScript关闭?

在JavaScript中,由于范围是嵌套的,因此创建闭合实际上很容易。

let usernames = ['John', 'Jack', 'James', 'Jhonny'];
let searchTerm = 'Ja';

// Here the function is accessing the searchTerm variable which is outside the function
// this is a closure
let filteredUsernames = usersnames.filter(function (username) { 
    return username.startsWith(searchTerm)
  });

由于可以嵌套函数,因此您也可以使用像这样的嵌套功能使用封闭:

function outerFunction() {
  let a = 666;
  function innerFunction() {
    console.log(a);
  }
  return innerFunction;
}
let myFunc = foo(); // now myFunc is just a reference to innerFunction inside the outerFunction.

myFunc(); // 666;

调用外部函数时,“ A”变量将设置为666。因此,您可能希望“ A”变量在调用外部函数后不可用。但是该变量在外部函数的范围内。由于可以嵌套范围,因此内部函数仍然可以访问“ A”变量。因此,只要代码的某些部分仍然可以调用内部函数,它仍然可以记住“ A”变量。

封闭可能会导致功能记住其范围内的变量。使用范围,您将功能与可以在其外部访问的数据结合在一起。

JavaScript中关闭的示例

现在您知道关闭是什么,如何使用它?关闭对于将功能与应使用的某些内部状态相结合非常有用。如果您有面向对象的编程经验,这可能很熟悉。

function getClickCountUpdater() {
  let counter = 0;
  return function() {
    counter = counter + 1;
    console.log(counter);
  };
}

const updateClickCounter = getClickCountUpdater();

// now to change the counter, you need to use this function, you can't change it directly
updateClickCounter(); // 1;
updateClickCounter(); // 2;

counter = counter+1; // ReferenceError: counter is not defined
console.log(counter); // ReferenceError: counter is not defined

// You can create another count updater function which will have a seprate counter variable in it's scope
const anotherCounter = getClickCounUpdater();
anotherCounter(); // 1;

封闭也可用于创建函数的不同变化。跟踪不应从其他任何地方更改的函数中的某些值也很有用。当您想对这样的特定函数进行一些登录时,这非常有用:

function createAd(width, height) {
  let adCounter = 0;
  return function (link) {
    adCounter++;
    console.log(`${width}x${height} ad created ${adCounter} times!`);
    return {
      _type: 'ad',
      link: link,
      width: width,
      height: height,
      // other generic ad options
    }
  }
}

const create300x250Ad = createAd(300, 250);
const create728x90Ad = createAd(720, 90);

// 300x250 ad created 1 times!
const new300x250Ad = create300x250Ad('https://linkToTheAd.com/path');

// 300x250 ad created 2 times!
const another300x250Ad = create300x250Ad('https://linkToTheAd.com/path');

JavaScript中关闭的好处是什么?

封闭也可以用于进行一些性能优化。假设您有一个需要创建访问变量的函数,但是该变量占据了很多内存。您可以使用闭合来重复使用该变量。

function findAdLinkById(id) {
  const adMap = {
    'e645-2456': 'https://link.com/path',
    '2282-3238': 'https://another-link.com/another-path',
    // 1000 more items

    '2901-2192': 'https://one-more-link.com/path'
  }
  return adMap[id]
} 

// whenever this function gets called it needs to store a new adMap value in memory which makes calling this function slower the more we call it
const firstAd = findAdLinkById('e645-2456')
const secondAd = findAdLinkById('2282-3238')
const thirdAd = findAdLinkById('2901-2192')

function createFindAdLinkById() {
  const adMap = {
    'e645-2456': 'https://link.com/path',
    '2282-3238': 'https://another-link.com/another-path',
    // 1000 more items

    '2901-2192': 'https://one-more-link.com/path'
  }
  return (id) => adMap[id]
} 
const findAdLinkById = createFindAdLinkById()

// whenever this function gets called it reuses the same adMap value which makes it faster to run
const firstAd = findAdLinkById('e645-2456')
const secondAd = findAdLinkById('2282-3238')
const thirdAd = findAdLinkById('2901-2192')

注意:虽然这会更具性能,但它也将使用更多的内存。因此,如果您还需要优化内存消耗,则应尝试其他方法。

最常见的关闭用例之一是异步JavaScript。

let id = 1;

getUserFromAPI(id).then(response => {
  // this works because the function callback here can still remember the value of the id even though this code won't run immediately
  console.log(`Fetched user ID: ${id}`)
})

大多数情况下,您甚至可能没有意识到何时使用关闭。基本上,每当您使用来自功能内部函数外部的变量时,您都会使用闭合。

因此,这些是关闭的一些实际用例:

  • 限制对某些变量的访问
  • 限制访问某些功能的访问
  • 跟踪跨函数呼叫的值
  • 记录功能调用
  • 优化函数调用的内存使用
  • 创建相同功能的不同变体
  • 用异步函数记住值,以后被执行

封闭会导致性能问题

当您有意使用它们时,关闭可能非常有用。有时它们也会导致内存问题,因为封闭存储在内存中。如果我们经常创建不必要的关闭,则可能导致内存泄漏。

我们需要确保实际上使用存储在内存中的变量。我们不应该过多地创建不必要的封闭。让我们看一下前面的示例,看看关闭如何导致过多的内存使用量。

function createFindAdLinkById() {
  const adMap = {
    'e645-2456': 'https://link.com/path',
    '2282-3238': 'https://another-link.com/another-path',
    // 1000 more items

    '2901-2192': 'https://one-more-link.com/path'
  }
  return (id) => adMap[id]
} 

// now instead of having just one closure, we have 2000 closures
for (let i = 0; i <= 2000; i++) {
  const findAdLinkById = createFindAdLinkById()
  const firstAd = findAdLinkById('e645-2456')
  const secondAd = findAdLinkById('2282-3238')
  const thirdAd = findAdLinkById('2901-2192')
}

因此,每当您创建关闭时,都需要意识到实际使用了多少内存。我希望这很有用。让我知道您是否还有有关关闭的其他问题。