ホームページ  >  記事  >  ウェブフロントエンド  >  JS 関数とクロージャに関する簡単な説明

JS 関数とクロージャに関する簡単な説明

青灯夜游
青灯夜游転載
2019-11-30 16:58:352157ブラウズ

関数が宣言されるたびに、スコープが生成されます。外側のスコープは内側のスコープにアクセスできません(内部の変数と関数を非表示にします)が、内側のスコープは外側のスコープにアクセスできます。変数や関数を非表示にするための非常に便利なテクニックです。

JS 関数とクロージャに関する簡単な説明スコープ隠蔽に基づく方法は、

最小認可

または最小露出原則と呼ばれます。 この原則は、ソフトウェア設計において、特定のモジュールやオブジェクトの API 設計のように、必要なコンテンツは最小限に公開し、そのコンテンツは隠すべきであることを意味します。

隠し変数と関数は、同じ名前の識別子間の競合を解決できます

。競合により、変数が誤って上書きされる可能性があります。 例:

var a = 2;
function foo(){
  var a = 3;
  console.log(a);
}
foo();
console.log(a);

この手法はいくつかの問題を解決できますが、理想的ではなく、さらなる問題を引き起こす可能性があります。まず、名前付き関数 foo() を宣言する必要があります。これは foo を意味します。名前自体がスコープを「汚染」します。第二に、この関数は関数名 foo() を通じて明示的に呼び出して、その中のコードを実行する必要があります。


関数が関数名を必要とせず、自動的に実行できる場合、これはより理想的です。幸いなことに、js は、これら 2 つの問題を同時に解決するソリューションを提供します - (IIFE) 即時に呼び出される関数式 -

関数を即時に実行します

var a = 2;
(function foo(){
    var a = 3;
    console.log(a);
})()
console.log(a);
まず第一に、

関数

の即時実行は関数宣言として扱われませんが、 関数式 として扱われます。
関数宣言と関数式を区別する:

関数が宣言の最初の単語であるかどうかを確認します。最初の単語である場合は関数宣言であり、そうでない場合は関数宣言です。関数式。関数 " (function " は " function " ではなくすぐに実行されるため、これは関数式です。

関数宣言

関数式最も重要なの違いは、名前識別子がどこにバインドされるかです。 関数によって宣言された関数名は、現在のスコープにバインドされます。グローバル スコープで関数宣言を行うと、関数名にアクセスして、グローバル スコープでそれを実行できます。関数式の関数名は、現在のスコープではなく、独自の関数にバインドされます。たとえば、関数式をグローバルに作成します。作成した関数式の関数名を直接実行すると、現在のスコープにそのような識別子がなく、関数式内のスコープでこの関数にアクセスするため、エラーが報告されます。名前はへの参照を返します。

スコープ クロージャ、そうですね、クロージャという 2 つの単語は少し理解するのが難しいです (パッケージが閉じられていると想像できますが、その中にはいくつかの謎めいたものが隠されています)クロージャの定義は次のように述べています: 関数がそれが配置されているスコープを記憶してアクセスできる場合、関数が現在のスコープ外で実行された場合でも、クロージャが生成されます。 ).

function foo() {
    var a = 2;
    function bar() {
        console.log(a);
    }
    bar();
}
foo();

上記のコード bar() は外部スコープの変数にアクセスできます。上記の定義によると、これはクロージャですか? 技術的にはそうかもしれませんが、私たちが理解しているのは、スコープが変数を検索するということです現在のスコープ内で、見つからない場合は、上方向に検索を続けます。見つかった場合は、戻ります。見つからない場合は、グローバル スコープまで検索を続けます。 -- そして、これらがクロージャです。関数 bar() には、foo() のスコープをカバーするクロージャがあります。

function foo(){    var a  = 2;    function bar (){
        console.log(a);
    }    return bar;
}var baz = foo();
baz();

上のコードは、クロージャをよりよく示しています。

bar() 関数は、外部で実行されるときに定義されます。スコープ (この時点ではグローバル スコープで実行されます) foo() 関数の実行後、エンジンには

ガベージ コレクターがあることがわかっているため、通常は foo() の内部スコープ全体が破棄されることが予想されます。

未使用のメモリ領域を解放するために使用されます。foo() が実行されているので、コンテンツはもう使用されないと思われるため、リサイクルのためのアライメントを考慮するのは自然です。リサイクル後は、内部の関数と変数が削除されることになりますfoo() が実行された後、baz 変数には bar 関数への参照が格納されます。bar 関数である baz を実行すると、console.log(a) が記録されます。クロージャを理解していない人は、エラーが報告されます。実際には 2 が出力されます。;???何?

foo() 関数のスコープは実行後に破棄されませんか? どうすれば変数にアクセスできますか? -- これはクロージャ。

当foo()执行后,bar函数被返回全局作用域下,但是bar函数还保留着当时的词法作用域(当时写代码是的顺序就已经定义了作用域,这个作用域叫词法作用域--外面函数套着里面的函数的那种)甚至直到全局作用域。所以bar还留有foo()函数的引用。使得foo()函数没有被回收。

闭包可以说不出不在,只是你没有发现认出他。在定时器,事件监听器,ajax请求,跨窗口通信或者任何其他的异步(或者同步)任务中,只要使用了回调函数,实际上就是使用闭包。

for instance

function wait(message) {
    setTimeout(function timer() {
        console.log(message);
    }, 1000);
}
wait("hello");

在上面的代码中将一个内部函数(名为timer)传递给setTimerout(...).timer具有涵盖wait(...)的作用域的闭包。因此还保有对变量message的引用。wait()执行1000毫秒后,它的内部作用域不会消失,timer函数依然保有wait()作用域的闭包。

而闭包和立即执行函数息息相关。

循环和闭包

for(var i = 1; i <= 5; i++){
    setTimeout(function timer(){
        console.log(i);
    },i*1000);
}

上面代码我们以为输出的会是1-5,可事实上输出的是5个6,这是为啥啊 -- 闭包啊。

延迟函数的回调会在循环结束时执行。事实上,当定时器运行时即使每个迭代的是setTimerout(...,0),所有的回调函数依然是循环结束后才会执行。我猜是跟js执行机制有关系吧。至于为什么都是6. 因为即使5个函数是在各个迭代中分别定义的,但是他们又被封闭在一个共享的全局作用域中因此实际上只有一个i.而怎么解决呢,立即执行函数来了!!!

for (var i = 1; i <= 5; i++) {
    (function (i) {
        setTimeout(function timer() {
            console.log(i);
        }, i * 1000);
    })(i)

}

打印出来1,2,3,4,5了欧,这回是你想要的数了。解释一下,5次循环创建了5个立即执行函数,这5个函数的作用域都不相同,立即函数接收的参数是当前循环的i.所以当timer执行时访问的就是自己立即执行函数对应的作用域。也就是说5个timer函数分别对应5个作用域,每个作用域保存的变量i都不同,解决啦!!!

你懂闭包了吗?

js执行机制

JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。那么,为什么JavaScript不能有多个线程呢?这样能提高效率啊。JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。

单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。JavaScript语言的设计者意识到这个问题,将所有任务分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。只要主线程空了,就会去读取"任务队列",这就是JavaScript的运行机制。

哪些语句会放入异步任务队列及放入时机一般来说,有以下四种会放入异步任务队列:setTimeout 和 setlnterval  ,DOM事件,ES6中的Promise,Ajax异步请求

 本文来自 js教程 栏目,欢迎学习!

以上がJS 関数とクロージャに関する簡単な説明の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はcnblogs.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。