関数が宣言されるたびに、スコープが生成されます。外側のスコープは内側のスコープにアクセスできません(内部の変数と関数を非表示にします)が、内側のスコープは外側のスコープにアクセスできます。変数や関数を非表示にするための非常に便利なテクニックです。
スコープ隠蔽に基づく方法は、
最小認可または最小露出原則と呼ばれます。 この原則は、ソフトウェア設計において、特定のモジュールやオブジェクトの 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);
の即時実行は関数宣言として扱われませんが、 は 関数式 として扱われます。
関数宣言と関数式を区別する:
関数宣言
と 関数式最も重要なの違いは、名前識別子がどこにバインドされるかです。 関数によって宣言された関数名は、現在のスコープにバインドされます。グローバル スコープで関数宣言を行うと、関数名にアクセスして、グローバル スコープでそれを実行できます。関数式の関数名は、現在のスコープではなく、独自の関数にバインドされます。たとえば、関数式をグローバルに作成します。作成した関数式の関数名を直接実行すると、現在のスコープにそのような識別子がなく、関数式内のスコープでこの関数にアクセスするため、エラーが報告されます。名前はへの参照を返します。
スコープ クロージャ、そうですね、クロージャという 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 サイトの他の関連記事を参照してください。